The chat screen asked the claw for everything the cache did not hold by
counting events, and a session that ran for weeks cost the whole record
the first time it opened here. The stream now names each frame's seq and
the cache stores it, so a reopening screen sends since=<seq> and receives
only the gap. The cache migrates in place: the old count of events is
exactly the last seq plus one, because the record numbers densely.
A screen with no cache opens on the newest turn and walks back through
the new history route when the reader reaches the top, one whole turn
per page, so the phone does not hold a weeks-long transcript to show the
last few messages. Pages are folded through the same reducer the stream
uses and prepended, since the list runs oldest first: appended, they
would land at the newest end. A reset comment (the record no longer
holds the cursor) drops the mirror, and the tail that follows rebuilds
it. The outcome tag colours a steered turn as an accent rather than a
failure, matching the web UI.
The (+) door stages gallery picks and camera shots as removable
chips against the daemon's own budget (eight a message, 5 MiB
decoded each, refusals surfaced, never silent drops); send reads
the files back as bare base64, the old {"text"} body unchanged
when there are none. Sent rows preview their staged files at once
and a replay fetches by attachment id like any attachment card.
Model options carry the vision badge only when the listing knows.
The diagnostics page showed the status job "waiting" while its runs
and the report-now kick died without a trace: any exception before the
first log line was invisible, indistinguishable from a job that never
starts. Every worker now wraps its run — a crash logs its type, message,
and first frame; a cancellation logs as such and rethrows. The screen
gains the report-now kick's own row (a one-time job's empty list is
"never run", not an error) and the tap lands in the log, so the next
stale-row mystery names its cause instead of hiding it.
A stale "last seen" had no client-side answer: a dead worker, a down
claw, and a claw that drops writes all looked identical from the
settings screen. The new page separates them: the WorkManager jobs'
live state (a cancelled schedule renders as "not scheduled"), the two
clocks — the phone's last landed report against the claw's last stamp
— whose disagreement names the broken end, auto-detected system checks
(battery optimization, background restriction, notifications) with
jumps into the exact settings page, a report-now kick, and a 200-line
in-memory flight recorder where every worker run logs its outcome.
The fleet card reads last-seen from the claw, and only a landed report
moves it. Periodic work is deferred while the device idles — Doze
stretches the fifteen-minute floor to hours — so a phone sitting in the
human's hand could show itself as "last seen 3h ago" with nothing wrong
on the claw.
Kick a status run on resume, the same way the commands worker already
is, and reload the fleet whenever a run lands. The landed-run signal is
a new clock in StatusRepository, written in the same edit that saves the
snapshot (which only happens once every post of the run succeeded), so
the card reloads on a report that actually reached the claw.
The stored `colors` key names a palette — rhema (the default,
Jadeite), aquamarine, black-and-white, or dracula — and the
Appearance page of /settings cycles it live, like the theme and
icon toggles beside it. Each palette carries a dark and a light
appearance under the existing `theme` key; dracula's light side is
derived, the official palette ships only dark.
The appearance decision moves off token comparison: Theme now
answers is_dark() from the ground/text luminance, so palettes
beyond the original two get the right per-profile fallback tones.