Desktopper

Desktopper turns ordinary language into a local schema-driven app.

It runs on Python and SQLite and renders in Safari. You can read the code on GitHub or skip ahead and see the proof.

The problem

Small local apps usually go one of two ways. Either you hand-write the HTML, JavaScript, and SQL yourself, or you let a model invent the whole stack. I have done both, and neither ages well. I wanted to describe tables and screens in plain language without giving the model the render path.

Why I built it

Day to day, I wanted to spin up a work dashboard fast, with real CRUD on resources I would actually use, without hand-building every screen or handing the whole stack to a model.

A local macOS app was enough for that. In Ops I say what should change, and the model answers with an additive schema diff in JSON instead of a pile of markup. Python checks the diff and applies it to SQLite, and Safari then shows lists, forms, and layouts from that schema. The model never writes HTML, JavaScript, or SQL.

What I built

Desktopper is a local macOS app that treats the schema like the product. Ops is where you talk about the model, and Home is the generated UI, with lists, forms, related child lists, belongs-to links, scopes, field groups, empty states, and layouts.

Flask serves a frozen shell, and everything durable (data, secrets, knobs) sits under Application Support. The stack has no Node or npm.

Once the schema is in place, Desktopper adds Python hooks on create, update, delete, or startup, along with reminders and notices, formulas and rules, auth with column grants, CSV import profiles, and peer sync with last-write-wins. The process binds where you tell it and refuses 0.0.0.0.

Three decisions

These are the three choices I would defend in a design review.

1. The model sends diffs, not markup.

The LLM proposes a change and the host applies it, while persistence and the UI shell stay in Python and static files. I was counting on that boundary holding when the model gets creative, because once the model writes HTML, JavaScript, or SQL, I am trusting a generated stack I cannot audit.

2. Home is generated, and Ops is separate.

Safari loads a frozen HTML/CSS/JS shell, and the screens come from the schema. Home has no chat box. You open Ops when you mean to change the model, and the app does not redirect you there. I kept the mode switch deliberate so a work dashboard stays a dashboard, because mixing chat into the runtime UI would blur the line between using the app and rewriting it.

3. Secrets stay off the wire.

Keys live in the Application Support .env and never in YAML, and knobs live in conf.yaml. The server will not serve either one over HTTP, and the bind stays local. I cared about local ownership because this is a tool I rely on every day, and if secrets rode the same path as screens, I would stop trusting the app on a machine I care about.

Proof

You can follow the same path the runtime takes. Ops intent becomes a schema diff, Python applies it, and Home renders from what landed in SQLite.

Desktopper runtime architecture: Ops intent to LLM schema diff, Python validate/apply to SQLite, Home frozen Safari shell from schema
In the runtime map, Ops intent goes to the LLM for an additive schema diff, and Python validates it and applies it to SQLite under Application Support. Home loads a frozen Safari shell and renders lists, forms, and layouts from that schema. At no point does the model write HTML, JavaScript, or SQL.

In the public repo, the README spells out that the model does not write HTML, JavaScript, or SQL and that there is no Node/npm. It also covers Ops versus Home, the appdir bootstrap, and the 0.0.0.0 refusal, and the unittests live under tests/.

Why I built it this way

I hold the same boundaries elsewhere. I want clear ownership and durable persistence, and I want a design a technical peer can judge quickly.

I needed work dashboards with CRUD more than I needed another generated demo. If ordinary language is going to change that app, it should change the schema and stop there, with Python owning validation and storage and the shell staying frozen. That is the stack I am willing to trust for a work tool I would actually use.