I2486 — No screen waits for a source: a page shows the latest reading with its age, read again behind the reader
Take every handler, git call and file read off the path of a UI request: the executor keeps the latest reading of each source a page asked for, a screen is rendered from those readings at once, and a worker reads a source again behind the reader whenever the reading is not of the repository as it is now.
DONE wave 1 · p0 · implementation profile · runs alone
Part of development-runtime — The runtime has a development surface.
Objective
Take every handler, git call and file read off the path of a UI request: the executor keeps the latest reading of each source a page asked for, a screen is rendered from those readings at once, and a worker reads a source again behind the reader whenever the reading is not of the repository as it is now.
Why
The owner said on 2026-10-12 that the UI 'is tragically slow' and is to be solved 'architecturally'. Measured on the UI-performance batch's build (c66f49e406, debug, load 20): /ui 2.2-2.5 s on every request, because health.report (2.4 s, probes, not cacheable) is in its first paint; the first request after a start or a change 5.6-8.1 s for roadmap, evidence and governance, because a cache keyed by the repository's state is empty exactly then; /ui/panels/delivery 75.7 s. A page costs its slowest source and no cache changes that. ADR 0142 records the decision.
Current state
A screen asks its sources through ctx.execute and waits for all of them (ui/source.rs ask_all). A cached source answers at once while the repository stands still; an uncached one and the first read after any change run the handler on the request.
Desired state
capability::readings keeps, per source and input, what it last answered, when, how long it took and in which state of the repository. Context::read hands that over: at once when it is of the repository as it is now, with a worker reading it again behind the caller once it is older than a second; after at most 50 ms, marked stale and refreshing, when the repository has moved, and then every other source read lately is read again too; after waiting for the first reading when there is none, said as pending past a minute. One worker per source, four at once in all, above the executor's own cache and single flight. execute is unchanged and exact. ui::source asks for readings; an answer carries its standing (age, current/stale/pending, refreshing); a source line says a reading's age once it is older than five seconds and says when it predates the last change; a line whose reading is catching up carries data-catching-up and the page's script reads the screen again after 0.4 s, doubling to 6.4 s, until none is left. The first reader of the UI starts one background pass over SCREENS and PANELS. MAJORDOMUS_CAPABILITY_CACHE=off makes a reading run its handler; MAJORDOMUS_READING_PATIENCE_MS sets the patience, and wait makes every read exact.
Scope
- apps/majordomus-cli/src/capability/readings.rs
- apps/majordomus-cli/src/capability/executor.rs
- apps/majordomus-cli/src/capability/handler.rs
- apps/majordomus-cli/src/capability/mod.rs
- apps/majordomus-cli/src/ui
- apps/majordomus-cli/src/http/router.rs
- apps/majordomus-cli/tests/executor.rs
- share/ui/ui.js
- docs/MAJORDOMUS_UI.md
- docs/CLI.md
- test/cases/1096_no_screen_waits_for_a_source.sh
- .ai/repo/adrs/0142-no-screen-waits-for-a-source.md
- .ai/repo/rules/project/no-screen-waits-for-a-source.v1.md
- .ai/repo/project/issues/I2486.yaml
Dependencies
- I2461DONE majordomus ui is served at /ui as its own product, with screens composed from capabilities
What waits on this
- I2487READY A screen is streamed, and one stream tells an open page which readings changed
- I2488READY The HTTP stack is chosen by measurement: the same routes and several hundred open streams on tiny_http and on axum
- I2489DONE A screen over its budget fails the build: the UI's latency budgets are benchmark policy, and bench --check holds them
- I2652DONE The UI shows what the knowledge base holds: the candidates awaiting review and whether the deriver still writes, read and never acted on
Acceptance criteria
- A source slower than the caller's patience and never read is handed over as pending before its handler finishes, and a caller that then waits joins the same reading: one handler run
- A source that has been read is handed over without its handler running on the caller's thread, however slow the handler is, and is read again behind the caller once the reading is older than a second
- After the repository moved, a slow source is handed over as it was, marked stale and refreshing, within the patience; a caller that waits gets the current reading; a source read lately and not asked for is read again
- A source that fails or panics is a reading that says so, with an age, and is read again
- A screen whose source is catching up marks that source's line with data-catching-up and says how old the reading is; a screen whose sources are current does not
- No screen under src/ui calls a handler: outside ui/source.rs and tests, nothing there calls execute
- With MAJORDOMUS_READING_PATIENCE_MS=0, a served screen answers after a change of the repository without waiting for its sources, and shows the changed value once the readings have caught up
Validation
- bin/majordomus plan validate
- cargo test --test executor
- cargo test --lib ui::
- MJ_TEST_JOBS=2 bash test/run.sh 1096_no_screen_waits_for_a_source
Evidence required
- readings_executor_tests
- ui_standing_unit_tests
- no_screen_waits_case_1096
- ui_latency_before_after
Evidence
| covers | type | command | result | at commit |
|---|---|---|---|---|
| readings_executor_tests | test | cargo test --manifest-path apps/majordomus-cli/Cargo.toml --test executor | test result: ok. 17 passed; 0 failed, on a tree equal to master 3442ce68c5; six of them are the readings tests: pending, at once, stale on a move, failure and panic, bounded workers and priority | 3442ce6 |
| ui_standing_unit_tests | test | cargo test --manifest-path apps/majordomus-cli/Cargo.toml --lib ui::source && cargo test --manifest-path apps/majordomus-cli/Cargo.toml --lib ui::view | ui::source 8 passed, ui::view 17 passed, 0 failed, on a tree equal to master 3442ce68c5: a source line says a reading's age and whether it predates the change | 3442ce6 |
| no_screen_waits_case_1096 | test | bash test/run.sh 1096_no_screen_waits_for_a_source | ok 1096_no_screen_waits_for_a_source, on a tree equal to master 3442ce68c5: no file under src/ui calls execute, and a served screen after a commit says it is catching up and then shows the new commit | 3442ce6 |
| ui_latency_before_after | manual | curl timings against release builds serving one clone, base c66f49e406 against the change (pull request 1041) | warm, every screen: 0.12-0.92 s before, 2-12 ms after; first request after a commit: roadmap, evidence and governance 1.2-4.2 s before, 55-140 ms after on a Linux host; /ui 1.2-1.55 s before, 3-162 ms after on a Mac at load 21, slowest single sample 330 ms. Measured on 2026-10-12, not repeated on 3442ce68c5; the Cockpit still computes on the request and is not part of this | 3442ce6 |
Timeline
- started
- 2026-10-13T12:45:05Z
- verified
- 2026-10-13T12:45:29Z
- completed
- 2026-10-13T12:47:30Z
Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.
Canonical record: .ai/repo/project/issues/I2486.yaml. Read it back with majordomus plan show I2486.