04Boundaries
The core cannot grow a UI framework.
A music player for GNOME and Android
Built with AI agents. What gets merged is decided by the gates, not by the agent.
The core carries no interface. That is not an architectural preference, it is the reason the second platform had a price tag instead of a rewrite — and the reason a third one would have a price tag too.
The seek bar, shaped by reprise-viewWhat the colours mean ↓
Idea to alpha
5weeks
Counted from the first commit — a design document, not a line of product code — to a running alpha on all four frontends. The weeks are the record in timeline.md, and the number beside it is how many rows it has.
11–17 Jul
CORE
The idea, the workspace split into `reprise-core` and a Linux platform layer, and the UX rulebook that has governed every change since.
18–24 Jul
SURFACES
One frontend became four: `reprise-cli`, `reprise-mcp` and `reprise-stems` joined the GNOME app.
25–31 Jul
DEPTH
No new surface. The single-owner runtime, its versioned protocol and its client went in underneath the ones that already existed.
1–7 Aug
ANDROID
The shared presentation layer, then the FFI bridge and the Android app on top of it — the library running on a phone.
8–14 Aug
SIGNATURE
The GNOME conformance rulebook, and the showroom itself: a prerendered page that reads its own numbers out of the tree.
CH.01
A GNOME desktop app in GTK4 and an Android app in Kotlin with Media3 sit on the same Rust core. So do a CLI and an MCP server — four frontends over one verified application layer, not four codebases that happen to share a name.
Core and edges
Where the lines sit
CH.02
An agent will tell you its work is finished. So will a green test. Neither counts here.
One incident · 2026-08-14
A queue test failed on header heights. The headers were fine. The fixture never installs the app stylesheet, so it was measuring widgets the app never renders.
what the test measured
Fixture, no stylesheet
what ships
The app, with its stylesheet
“A geometry assertion against unstyled widgets passes while the shipped button is a different size.”
Since then no pull request may claim Fixes #444 until three mutations turn the suite red. If one leaves it green, the claim does not go in.
Fail closed
27 checks green · ready to merge
What the checks refuse
The core cannot grow a UI framework.
It installs as a desktop app, not as a demo.
Every action works without a mouse.
A rule without a test fails the build.
Tests, lints, formatting, documented API.
The branch, the shell scripts, the worktrees.
A rule ID leads to a test, the test to a commit, the commit to the decision. None of that makes an agent trustworthy. It makes trust unnecessary.
CH.03
The two apps look different on purpose. GNOME conventions on the desktop, Material on the phone. Making them match would not show craft, it would show missing platform UX. What is shared is the signature — and that is the harder half: two rendering stacks, GSK against Skia, two layout systems, two languages, the same visualisation and the same physics. Not a shared component. A shared specification.
The seek bar is the case in point. The decision: show the structure of the track instead of an empty gutter. The implementation: a portable visuals layer that neither frontend owns. The result: physics that were measured afterwards rather than asserted.
The spectral seek bar, live
Move across the measured track to inspect its values. The bars are shaped by the same functions the apps use — bars.rs, waveform.rs and spectral_colour.rs — with only the band values standing in for live PCM.
Every bar is the RMS of its slice, mapped through the track's own p10–p95 window and smoothed against flicker. A compressed master still shows verse against chorus instead of one loud wall.
The tint is the spectral centroid: coral is low and weighty, teal high and airy.
Click any plate to enlarge
CH.04
A core with no interface is only a claim until something without a screen uses it. The CLI and the MCP server run as separate processes against the same database as the desktop app, and a change-log notifier shows their edits live in a running GTK window without a restart. That is the boundary being load-bearing rather than documented.
reprise-cli library summary
reprise-cli search "portishead"
reprise-cli playlist create "Focus"
reprise-cli scan ~/Music
reprise-cli instrumental create 481
reprise-cli jobs status --batch b-2f9c
reprise-cli events tail --since 0
reprise-cli concerts list --all --json
Every command takes --json for machine consumption and --db for a scratch library, so automation never has to touch the real one. Deleting a playlist refuses to run without --yes.
Tools over stdio, each behind one of six capability flags read live from the library. A revocation takes effect on the next call. Responses never carry filesystem paths, cache locations or credentials — and the source resources omit stored URLs, because those can hold access tokens.
CH.05
The title index was rebuilt for one reason: the track list was too slow over a large library. What it bought was measured after the fact rather than asserted before it — and what it cost sits in the same table, not in the small print.
| What | Before | After | Delta |
|---|---|---|---|
| Title window over 100'000 tracks | 53'605 µs | 1'333 µs | −97.51 % |
| Playback ID projection | 8'125 µs | 298 µs | −96.33 % |
| Main-thread CPU while idle | 110 ms/s | 64 ms/s | −41.8 % |
| Tag reads on a warm start | 419 | 0 | −100 % |
The price sits next to it, not in the small print: the title index costs 2'379'776 extra database bytes, up 9.85 %. The track list stays pinned by test to eight cached SQL windows and 1'600 retained rows — unchanged between 10'000 and 100'000 tracks.