An Airtable-looking app whose AI columns are computed on your machine, by the database itself. No API key, no queue, no data leaving the laptop.
It is a demo for three SQLite extensions:
| what it does here | |
|---|---|
| sqlite-ai | Runs a local GGUF model inside SQLite. A trigger calls llm_text_generate() on insert, so a new row comes back already summarised, classified and scored for sentiment — in the same transaction that created it. |
| sqlite-vector | Embeds every row and answers "complaints about billing" by meaning rather than by keyword — including rows that never use the word billing. |
| sqlite-sync | CRDT sync to SQLite Cloud and between devices, carrying the AI values with the rows, so a second device gets enriched data without running a model at all. |
The point of the app is the contrast. Airtable is a cloud database with a spreadsheet face; this is a local AI engine with a spreadsheet face. Everything except sync works with the network off.
demo.mp4
2 min 46 s: sample data arriving unlabelled, a ✨ column filling in a row at a time as the model runs, semantic search finding rows that never use the word searched for, and a row reaching a second device with its AI values already attached.
The same recording is committed as docs/demo.mp4, so it is still
there when the repository is cloned, mirrored, or read anywhere but github.com.
Install once:
- Node 18+
- Rust, via rustup —
rust-toolchain.tomlpins the version, so cargo fetches the right toolchain itself - on macOS, the Xcode command line tools:
xcode-select --install. Without them the build fails at the link step with an error that does not mention Xcode.
Then:
make install
make runmake install downloads the three extensions (6 MB, versions pinned and
checksum-verified against extensions.lock) and the two models — Qwen3-1.7B for
labelling, all-MiniLM-L6-v2 for search — into vendor/, then installs the web
dependencies. It is 1.14 GB in total, the models are the slow part, and the
download resumes if it drops. Re-running it skips whatever is already there.
The first make run takes a few minutes to build; later ones about 30 seconds.
make on its own lists everything else.
The app opens with an empty grid. Nothing is downloaded, generated or synced until you ask for it.
Add sample data (the button in the middle of the empty grid) inserts 24 pieces of customer feedback. They arrive as plain text — no AI values yet.
Add an AI column. The + at the right of the header opens a field-type menu. Three of the entries are marked ✨ — AI Summary, AI Sentiment, AI Category. Pick one and the column fills in a row at a time, at roughly a second a row. That second is a language model running on your machine, inside a SQL trigger.
Adding the other two ✨ columns is instant. One generation produces all three values, so the second and third columns are already computed and merely revealed.
Add a row. Type into the row at the bottom of the grid. It comes back labelled before it finishes inserting.
Search semantically. Type in the toolbar box with Semantic selected. Try complaints about billing: the results include rows that never say "billing" anywhere. Switch to Keyword for the same query and watch most of them vanish.
Beside the toggle is an optional distance cutoff. It filters results already on screen, so it re-filters instantly. It is an exploration knob, not a relevance setting — distances are not comparable between different queries — and it is off by default.
Filter by category or sentiment. Those dropdowns stay disabled until the matching ✨ column exists and has values, because there would be nothing to filter on.
Delete rows. Click a row number to select it, then press Delete.
The app is fully usable with no account — that is most of what it is arguing. The
sync chip reads Local only until you add credentials.
- Create a database in the SQLite Cloud dashboard.
- Create a
feedbacktable there with the same columns in the same order. The exact DDL is theSCHEMAconstant incore/src/schema.rs. - Click OffSync on the database and enable
feedbackonly. - In the app, click the Local only chip (or the ⚙ beside it) and paste in the database ID and API key.
The credentials are checked against the service before they are stored, so a typo is reported there and then rather than becoming a mysterious "Offline" later. They are kept on this device and reused next launch, and the chip becomes Sync now immediately — no restart.
If you prefer a file, cp .env.example .env and set EDGETABLE_DB_ID and
EDGETABLE_API_KEY. The environment wins over anything saved in the app, and the
panel says so when it does.
Sync is manual, always — nothing goes over the network unless you press it.
The chip tells you where you stand:
Local only |
no credentials; everything works, nothing leaves. Click it to connect |
Sync now · 3 unsent |
three rows changed here that the server has not seen |
Syncing… |
in flight |
Synced · 12s ago |
up to date, and when that was true |
Offline · 3 unsent |
last attempt failed; your changes are safe and will go up next time |
A first sync onto an empty device takes a few seconds — the service builds a snapshot before it can send one.
Start the app as usual, then in another terminal:
make run-secondA second window opens on the same machine with its own database, which is the
whole trick: the site id sqlite-sync identifies a device by lives in the database
file, not in the app, so two files are two devices. Its title bar says
EdgeTable — device-b so the two are tellable apart on a projector.
Connect it to the same cloud database (its chip will read Local only), then: add a
row on one, press Sync there, press Sync on the other. The row arrives with
its AI values already filled in — computed once, on the device that created it.
make run-second attaches to the dev server make run started, so run that one
first. Point it elsewhere with make run-second DEVICE_B_DB=/tmp/laptop-c.db, or
set EDGETABLE_DB yourself for a third.
make reset empties both devices when you want to rehearse from scratch.
- Delete all rows really deletes them. Sync propagates the deletion to every device. There is no undo.
- Clear this device empties this copy only. Other devices keep their rows, and the next Sync pulls them back here.
Deliberately out of scope: views (kanban, calendar), linked records, formulas, permissions, collaboration cursors, attachments, undo history, and column sorting. Those are product surface. Teams can build product surface; the argument here is about the engine underneath it.
Working end to end: the grid, inline editing, the ✨ AI columns and their progressive backfill, semantic and keyword search, filters, row delete, the sample data and clear controls, and manual sync against SQLite Cloud.
Not built yet: column sorting, CSV paste for bulk insert, and a packaged .app
(today it runs from source via make run). The sample set is 24 rows.
Tested by an engine selftest (9 assertions, real models, real database) and 24 headless UI tests.
notes/ENGINEERING.md— how it is built, what was measured, and the things a change here must not breaknotes/FINDINGS.md— measured extension behaviour, including several undocumented gotchasnotes/plan.md— the build plan and the demo script