The upgrade-path gate asserts serve menubar-json == the spawned one-shot
payload; an always-present hydration block on serve broke that on every
platform. Absence already means complete (the rule one-shot consumers live
by), so the explicit complete:true block bought nothing the invariant did
not already guarantee. Consumers were written for the optional field and
need no change.
Each surface holds its OWN serve child: the Swift menubar (mac/), GNOME and
Windows clients spawn `codeburn serve --stdio` too, and their payload
decoders ignore an unknown key — so they would have shown a partial total
with no way to say so. The paint is now opt-in via
CODEBURN_SERVE_PROGRESSIVE, which the Electron app sets on its own serve
child because its renderer shows the indexing banner. Unset means the child
answers only with complete data, so no surface can present partial totals
unlabelled.
The TTY got the fast cold start in #1109; every JSON surface still waited
for the full parse because a payload has no way to say "partial". Add one.
- `hydration: { complete, indexedFiles, totalFiles }` on the menubar payload,
add-only and emitted ONLY by the resident serve child (which polls, and
therefore converges). Absence keeps meaning "complete", so every one-shot
output is byte-identical and no script can be handed partial data.
- serve: a cold child answers `status --format menubar-json` from the files
the requested period can show (same mtime floor as the TUI) and schedules
the unfloored fill behind it. Partial answers are never memoized; requests
waiting behind the fill are heartbeated by id so the client's no-output
watchdog stays armed.
- `stale` keeps its own meaning: a first paint is fresh but partial, so it
reports through `hydration` and never sets `stale`.
- desktop app and web dash render an honest "indexing history · N/M files"
notice while `complete` is false.