Sentence Case Across the Live Monitor
Making the terminal UI read consistently without disturbing a single machine string
While a build runs, Gantry shows a live terminal monitor: a card per agent, status pills, timing and metrics panes, and generated blurbs describing what each unit is doing. That surface had grown inconsistent. Some labels were lowercase, some capitalized, stage names appeared as their raw internal tokens, and harness identifiers like `opencode` rendered exactly as typed rather than as a reader would expect to see them written.
The hard part is that the same monitor also carries text machines depend on — stage labels a parser matches, harness ids used as keys, journal and ledger entries, the headless output stream. Those must not change. This job rewrote only the human-facing text into sentence case, leaving every machine-facing string byte-for-byte identical, so the display reads cleanly while nothing downstream that parses it notices a difference.
Build
The cleanup divided along the display surface — a shared casing helper first, then three regions that reused it, then the generated-text prompts — and the exact-render tests that pinned the old wording are where three of four sprints hit a red gate before landing.
The decomposition follows where the display text lives rather than what it says. The first sprint carried both a job and a dependency for the others: it built the reusable pieces — a helper that turns raw stage tokens into readable phrases and a single place that maps a harness id to its true written form — and applied them to the agent cards. Only once that shared vocabulary existed did the next two sprints spread it across the remaining regions: the always-visible status pills and empty-roster placeholders, then the secondary state, environment, and metrics panes. The final sprint went somewhere else entirely — the prompt files that ask the decoration model for card blurbs — so that generated copy starts as proper prose at its source instead of being patched with a capitalization fallback at render time.
Where the difficulty actually lived shows up in the gates. Three of the four sprints went red at least once and were repaired in place. That is the signature of this particular cut: changing visible casing breaks every exact-render test and snapshot that had pinned the old text, and the plan required updating those assertions rather than loosening them — the gate is precisely what would have caught a machine string drifting along with a human one. The one sprint that stayed green throughout was the prompt-wording change, which alters generated text no test asserts letter-for-letter. The boundary between what a person reads and what a parser consumes is the whole premise of the job, and the repeated gate failures are that boundary being checked, not a crack in the split.
No review forced a re-plan; the four regions held as drawn. The stress that remained came at the end, when integrating the wide, shallow diff back onto a moving main branch went red before it settled — the cost of a change that touches many display sites at once rather than one deep one.
Feature
Before this, the monitor's text was cased ad hoc and no single function knew a harness id's proper written form; the job added a sentence-case converter and one centralized harness-name lookup, applied across the render sites, and both still hold at today's HEAD.
Beforehand there was no agreed answer to how the live monitor should present itself. Stage names surfaced as their internal lowercase tokens, status and placeholder text was cased by hand at each call site, and turning `opencode` into `OpenCode` — a name a naive capitalize gets wrong — had no home, so it was either done inconsistently or not at all. The design replaces that with two shared points: a `sentence_case` converter used across the render layer, and a single `display_harness_name` lookup that every site rendering a harness id calls. Generated blurbs were fixed upstream in the decoration prompts, which now ask for sentence-case prose directly, keeping the render path free of a masking fallback. Throughout, the machine-facing values — the dim lowercase stage labels that double as ledger keys, the raw ids, the serialized data — were deliberately left as they were.
Standing in the current tree, the structure is intact. The sentence-case converter is still called from multiple render sites, and the harness-name lookup is still the shared path — it has since moved into the domain layer, where it delegates to the engine's own harness resolution, a more central home than the TUI module it started in. The sentence-case instruction is still present in the decoration prompt files. The focused tests the job added — that card footers use the true harness casing, that status-line and pager labels read as sentences, that the environment view shows display names without leaking raw ids — are all still in place and still guard the behavior. Most of the introduced lines survive despite heavy churn across these files since.
Two things drifted, both housekeeping rather than reversal: one config prompt file and a small TUI module the job had touched are no longer present, having been removed by later work unrelated to casing. What the job set out to establish — one way the monitor reads, enforced in shared code and pinned by tests — is what remains.