M5-14: Run graph and control room on iPhone and Android¶
Status: ready | Depends on: M4-39, M5-03 | Milestone: M5
Goal¶
After this ticket the run graph and the control room of M4-39 work on the phone: screen M11 ("Running now" as cards in one column from the tab "Running", the switch "Cards" and "List", filters as a sheet, Stop where allowed) and screen M12 (the run graph as indented rows with a narrow time bar below each label in portrait and the full time axis beside the labels in landscape, opened from the live box sheet of M3 and from a card). Both use the derivation and the views of packages/bothy_viz from M4-39 in density touch; the visible cards are live only while the app is in the foreground. Android uses the same screens with its own controls (01 5.4). Why: the owner wants to see what runs are doing right now also on the go (2026-10-24; D-RV-11).
Read first¶
docs/design/run-visualization.md,docs/decisions.d/run-visualization.md.docs/PLAN.md: section 3; section 11 rows M5-02, M5-03, M5-10, M5-14; section 10 row M4-39.- Dependencies: the header names M4-39 and M5-03; everything below them (M5-02, M4-21, M4-23, M2-51, M6-11) is implied.
docs/spec/01-product.md: 5.3 M3, M11, M12; 5.4 (Android); 8; 13 (R-PR-15, R-PR-16).docs/spec/09-agents.md5.6;docs/spec/04-sync-protocol.md11.3 items 2 and 7;docs/spec/15-client-architecture.md(foreground rule of the gateway connection on mobile).docs/spec/25-visualization.md7.1 (rowlib/src/run_graph/), 7.3, 7.4.- Briefs:
docs/briefs/M4-39-run-graph-desktop.md,M5-02-iphone-layout.md,M5-03-android-layout.md,M5-10-mobile-a11y.md; merged code wins on names. - Code on the base branch:
packages/bothy_viz/lib/src/run_graph/,apps/client/lib/running/, the phone layouts of M5-02 and M5-03.
Fixed requirements that apply¶
Owner's fixed build rules (apply verbatim):
- No MVP. New from scratch and directly in the final state; stages are only the order.
- Strictly modular: clear module and service boundaries, dependencies only through defined interfaces, no cross access.
- Documentation in code: every public module, class, function and interface with a docstring/doc comment (purpose, parameters, return value, errors, example where useful); comments explain the why, not the what. API documentation is generated from code (OpenAPI from the routes, dartdoc, Python API docs in MkDocs).
- Documentation in the repo: README per module/service, ADR for every architecture decision, CHANGELOG per release, keep the documentation site current. A change without matching documentation is not done.
- Quality gates (SonarQube, linters, type checking, security scanners, test coverage) block the merge.
Rules for every brief:
- Test first: write the failing test, run it and keep the red output for the report, then make it green, then clean up.
- No en dash and no em dash in any file you write; the
text-rulespre-commit hook rejects them, and the ARB test of M4-06 rejects them in interface texts. Use a hyphen or rephrase. - White label: examples and test data use
example.comand the people Alex, Sam and Robin; no company, host or personal names, no paths under a home directory. Interface texts come frompackages/l10n, never as literals in widgets. - Limits only through named keys from
AppConfig(ui.livebox_visible_steps,a11y.live_announce_min_interval,sync.max_focused_threads), never a literal limit in code. - No permission logic in the client: Stop only where the run's capabilities allow
run.cancel(15 1).
Product requirements implemented here:
- M11 as 01 describes it: cards in one column, switch "Cards" and "List", filters as a sheet, a tap opens M12, Stop at full width where allowed; touch targets at least
density.target-min. - M12 as 01 describes it: the same rows as D14 in density
touch, a narrow time bar below each label in portrait and the time axis beside the labels in landscape; tap collapses or expands; "As list". - Live only in the foreground: visible cards are focused through
background_thread_ids(04 6.2, as in M4-39), never throughthread_ids; the focus of visible cards ends when the app moves to the background; on return the views reload the documents through the client rule of 04 11.3. - Screen readers (VoiceOver, TalkBack) read the list forms with the tree semantics of M4-39.
Scope¶
packages/bothy_viz/lib/src/run_graph/: the touch layout of the trace view (bars below the labels in narrow widths) as a parameter of the existing view, no second derivation.apps/client/lib: the phone routes for M11 and M12, the "Graph" button in the live box sheet of M3, the tab "Running" on the control room, Android back behaviour per 01 5.4.- ARB keys for new texts; goldens for phone sizes.
Fragments: docs/decisions.d/M5-14.md (D-M5-14-<n>), changelog fragments of the touched components; README of apps/client.
Out of scope¶
- Server changes (M2-51); the desktop views and the derivation (M4-39); the web console; TV mode.
- Push notifications about stalls (01 6.2: a stall shows a notice, no push).
Boundaries¶
- Work in a dedicated git worktree on the branch
feat/M5-14-run-graph-mobile; one writer per worktree. - Conventional Commits with the package or component as scope (for example
feat(client): ...,feat(bothy-viz): ...) and a DCO sign-off on every commit (git commit -s); squash into meaningful commits before the merge (docs/quality/push-discipline.md). - Never push to
main, never tag and never create remote repositories; the only remote steps are the branch push and the pull request ofAGENTS.md. Never publish a Dart package. - Do not modify files outside the scope above, except the README and fragment files named in this brief.
- Follow the host rules supplied by the launcher (ports, machine rules); device and simulator runs on the Mac-bound host only through the launcher's rules.
- Tests bind only
127.0.0.1with port 0 and make no network call; the fake server replaces the server. Time comes from an injected clock, never from a fixed wait. - The Flutter SDK at the version pinned in the root
pubspec.yamlmust be onPATH; without it the gates fail by design (R-QA-02), and the ticket stops withVerdict: BLOCKED <tool> missinginstead of skipping. - Never lower a threshold, disable a gate or delete a test.
- If the core contract or
docs/api/lacks something this ticket needs, stop withVerdict: BLOCKED <reason>instead of building a deviation (P-11). - This brief was written before milestone M5 closed, in the manner of D-W-100 and D-W-120. Where the merged code of an earlier ticket names a package, type, function, file, provider or make target differently from this brief, follow the code and list each difference in the report; where it behaves differently from the specification, stop with
Verdict: BLOCKED <reason>.
Design notes¶
- One view, two layouts. The trace view of M4-39 gets a layout parameter (
wide,narrow) chosen from the available width; portrait phones usenarrow(bar below the label), landscape and tabletswide. The derivation is not touched. - Foreground. The control room registers its focus only while the app lifecycle is
resumed; onpausedit releases it. The cards then show the summary until the next foreground. - Rulings for
docs/decisions.d/M5-14.md(D-M5-14-<n>): the width threshold betweennarrowandwide(from the design tokens, never a literal), the filter sheet.
Tests (write them first)¶
packages/bothy_viz/test/run_graph/narrow_layout_test.dart: the same rows and values innarrowandwide; the bar below the label innarrow.- Goldens of M11 and M12 in density
touch, portrait and landscape, light and dark, text scaling 1 and 2,deanden, contextreduced. apps/client/test/running/mobile_control_room_test.dart: tab "Running" opens M11; a tap opens M12; Stop only with the capability; the focus is released onpausedand requested again onresumed.apps/client/integration_test/mobile_run_graph_test.darton the iPhone and Android layouts against the fake server: a scripted run with a subagent grows live in M12.- Accessibility: a checklist run with VoiceOver and TalkBack over both list forms (report), as M5-10 does.
Run the added tests by name with ten repetitions (--plain-name) through the machine's test slot.
Commands that must pass, from the repository root:
cd packages/bothy_viz && flutter analyze --fatal-infos && flutter test --coveragecd apps/client && flutter analyze --fatal-infos && flutter test --coveragemake apps-platform; otherwise its SKIP linemake arch,make lint,make fmt-check,make coverage,make docs,make fragments-checkmake check
Acceptance criteria¶
- M11 and M12 work on iPhone and Android layouts as 01 describes, with the derivation of M4-39 unchanged.
- Live only in the foreground; the focus is released in the background.
- Goldens recorded; VoiceOver and TalkBack checklist done.
- Rulings and fragments recorded;
make checkis green.
Definition of done¶
- Code and tests as specified; doc comment on every public symbol (purpose, parameters, return value, errors, example where useful); comments explain why and cite 01.
apps/client/README.mdupdated.- Rulings in
docs/decisions.d/M5-14.md; changelog fragments as in Scope. - An ADR only if you take an architecture decision beyond this brief (MADR 4 as in
docs/adr/0000-template-madr-4.md, headings and text in English, next free number,make adr-index). make checkgreen.- An independent code review over the diff, then the red team gate (
docs/quality/red-team-gate.md, verdictJAorNEIN); onNEINfix with a test per finding and have the delta checked again. Record both verdicts. - Land the change as
AGENTS.md("How work lands onmain") describes: a pull request from this branch, squash-merged only when review, red team andmake check,ci-okand the review threads are green; thengit worktree remove.
Report¶
Write to {{REPORT_PATH}}:
- First line exactly
Verdict: DONEorVerdict: BLOCKED <reason>. - Commits (SHA and subject), changed files with line counts, test results with counts (the red run before the change and the green run after), the summary of
make check. - Review and red team verdicts and the findings fixed.
- What is not proven.
- Follow-ups that could not be done in this scope, each with its reason, and spec gaps found.
Then write {{DONE_PATH}} with exit=0 sha=<sha> or exit=1 <reason>.