Five lessons from keeping lemon-code and its macOS app in two repos, with a contract in between and a SQLite database only one side may write
Published: November 6, 2026
Reading time: 8 min
Series: lemon-code in 30 days, part 9 of 13
I'd open lemon-code's macOS app and, instead of the chat, see fallback data and a fixed banner at the bottom covering the input field. The backend was up. The problem was a momentary SQLite lock that the app counted as a dropped connection.
That cascade became ADR 0032. It sums up building a CLI and a native app at the same time: most bugs live at the boundary. Here are the five lessons that stuck.
Picture a home circuit breaker that trips whenever an appliance reports a warning. Some warnings are recoverable; treating each one like a short cuts power to the whole house. The appliance isn't the problem. The uncalibrated breaker is.
// Summary of the RPC client's current classification.
enum RPCFailure {
case launchFailed, noJSONResponse, requestTimedOut, rpc, emptyResult
}
func countsTowardBreaker(_ error: RPCFailure) -> Bool {
switch error {
case .launchFailed, .noJSONResponse, .requestTimedOut: return true
case .rpc, .emptyResult: return false
}
}
RPC errors and empty results stay visible. They just don't count toward opening the breaker.
The app's first commit was the contract, not a window. That killed the temptation to "just read SQLite directly to show the project list". Every time the app needed data, the question was: which contract method returns this?
Free and paid products to turn ideas into a foundation you can actually ship.
13 products availableContinue exploring similar topics
The first commit of lemon-code's macOS app had no screens. It was the contract with the CLI. Here's why that order kept me from rewriting the agent in Swift.
lemon-code has six ADRs numbered 0049, all created on the same day. That mistake teaches more about project memory with multiple agents than any well-numbered ADR.
I reached for an HTTP provider on tasks an installed CLI could handle. lemon-code's CLI-first rule fits in four lines of precedence.
A 47-point checklist to find bugs, security risks, and performance issues before launch.
Production-tested templates trusted by developers. Save weeks of setup on your next project.
The app repo's rules: lemon-code is the only execution engine, the app never writes to the core SQLite, and UI flows must work without a terminal. With two repos, that's what prevents two diverging implementations of the same business rule.
The current classification counts process launch failures, missing JSON responses and request timeouts. RPC errors (including domain or validation errors) and empty results do not count toward opening the breaker.
The core already ran SQLite in WAL mode with normal synchronous. Concurrent connections to the session database could still return "database busy". The relevant SqliteSessionStore connections use a 5,000 ms busy_timeout, which waits up to five seconds before giving up.
// SQLite connection that waits instead of failing (illustrative, Bun)
import { Database } from "bun:sqlite";
const db = new Database("lemon.db");
db.exec("PRAGMA journal_mode = WAL;");
db.exec("PRAGMA synchronous = NORMAL;");
db.exec("PRAGMA busy_timeout = 5000;"); // wait up to 5 s for the lock
Notifications have to be dismissible and stay away from the input field. Also in ADR 0032. A banner covering the input turns a recoverable error into an unusable app.
From Git: the app has 152 commits, 73 prefixed feat and 24 fix. The CLI has 448 commits overall. These numbers show activity and the amount of boundary work; they don't measure quality.
busy_timeout.Building a CLI and native app together is mostly boundary work. The rules that worked were simple: contract first, one engine, a calibrated breaker, a database that waits instead of failing, and notices that stay out of the way.
The next part of the series looks at local coding models on the Mac.
If your team maintains a core and a desktop or mobile client in parallel and bugs keep showing up at the boundary, that's an architecture review I do at LEMON.DEV.