Skip to content

feat(OMN-21015): one look for the six local pages, with charts and run links - #399

Merged
jonahgabriel merged 68 commits into
devfrom
lakshman/OMN-21015-uniform-pages
Oct 15, 2026
Merged

jonahgabriel merged 68 commits into
devfrom
lakshman/OMN-21015-uniform-pages

Conversation

@Patel230

@Patel230 Patel230 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stage 5 of OMN-21015: the six local pages (Overview, Runs, Workflow, Usage, Credentials, API Keys) get one look, and the figures the runtime already serves are drawn as charts and made easier to act on. Nothing new is served and nothing is computed in the browser: every chart, bar and meter draws a served value, and a value that is not served is still named, never shown as 0.

This is a draft. #396 merged on 2026-10-15 as bb804fff and #397 on 2026-10-14 as 89f1ad8c, so this diff against dev is now this branch's own part only. Head: b558da7e, level with dev bb804fff.

Update of 2026-10-15 (claude-070f1c89)

  • Brought level with dev after feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's squash merge. 7e2dce61 merges feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's final head 02af97fe. LocalDashboardPage.tsx keeps this branch's version plus dev's one change there: RunPicker remounts on key={requested} instead of copying the id in an effect. The two navigation screenshots keep this branch's capture. 0264bced then merges dev bb804fff with the ours strategy. That is valid only because bb804fff's tree equals 02af97fe's, which the parent already merges; both facts were checked with git rev-parse and git merge-base.
  • 0ede655b: the served-since list is empty. The 2026-10-14T04:30:11Z recapture (feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397) lists requested_model, queue_wait_ms and execution_ms, so run-timing T10 required their entries to go. This clears "the 2 inherited catalogue failures".
  • b558da7e: Workflow declared workflow-run-path-steps twice. The merge in 7e2dce61 kept both sides' copy. An independent review found it. The copy is removed, and a page test now fails when any local page repeats a binding id: on the lab it failed on 0ede655b and passes on b558da7e.
  • Lab, at b558da7e (.201): tsc --noEmit exits 0. The whole vitest suite at 0264bced gave 2,535 passed, 15 skipped and 2 failed. One failure is the T10 tripwire that 0ede655b fixes; the other is synthetic-fixtures-hygiene, which needs git metadata an archive does not carry. Run-timing then passed 21/21 and pages.test.ts 76/76. Browser specs (tests/e2e/local-dashboard*.spec.ts, Chromium, one worker, 14:42Z): 39 expected, 0 unexpected, 0 flaky.
  • Independent review: 7e2dce61, 0264bced and 0ede655b were reviewed; the one finding is fixed in b558da7e, and the review of b558da7e and the dev merge bcbc4e04 passed. The eleven commits not independently reviewed before were reviewed from source on 2026-10-15. Two findings are already fixed at this head (a tooltip HTML path, fixed by 104a0365; an array read as a number, fixed by 5e7a519b). One stands, recorded under Not in this change (typography). Their browser behaviour is covered by the 39 browser specs above.

What changed

  • Workflow startup and browser assertion repair (codex-01a13694, signed commits fa8dcf64 and 687f1ba6): the runner-IP reusable caller now grants pull-requests: read, matching the nested OCC preflight it calls. GitHub rejected the previous caller before any job started (run 38445173636). The run-chart browser test now compares the full resolved link URL as text, replacing the partial regex escaping that CodeQL rejected at line472.

  • One look for the six pages (d443b2d2, 82511d8c, 9c9903bd): shared panel, heading, table, empty-state and chip styles in src/styles/local-dashboard.css, with lucide icons on panel headings. The page name and description block at the top is gone from view on all six pages (the heading stays for screen readers); one toolbar row holds Overview's window switch and the runtime chip.

  • Spend per day as a bar chart (e6b4b3b5, 69e5600f): src/pages/local/charts.tsx draws one series per currency column the page declares for the panel, in the declared order, over the served day rows: a bar per day and series, side by side and never stacked (e34d580b). It names no figure itself, so rule R4 of the savings-source guard holds. The table under it is unchanged.

  • Tier mix as a donut, pass rate as a meter (6cc0ce7d, 29d2d2f0): src/pages/local/shares.tsx. A slice sweeps exactly the served pct_of_tier_routed of the ring, a fraction between 0 and 1, clockwise from twelve o'clock in served order (e34d580b); pointing at a tier names it in the middle. Shares that add up to more than the whole are not drawn and the reason is said; the meter reads the served overall_pass_rate. A value off that scale draws nothing and says why. A share above zero is never written as 0.0%, and one below the whole never as 100.0%.

  • Runs open from where they are named, ids and commands copy, Workflow draws the steps (c8668a3e): a run id in Overview's last run and recent runs opens that run on Workflow; a Copy button sits beside the Workflow run id and beside the commands the empty states tell the reader to type; Workflow shows the served steps as a row of nodes above the timeline, in the order served, each leading to its step.

  • Run tables (96f2d677): a run id takes one line, so a row is shorter; Usage's token bars are coloured per series.

  • A chart legend that works from the keyboard (25eddedf): the spend chart's legend is a row of buttons above the chart, one per plotted series, each saying whether its series is shown. A series keeps its colour when another is hidden, a refresh keeps what was hidden hidden, and with every series hidden the figure says so. The series use the theme's chart colours, and the chart's own text uses the page's font.

  • Savings per day on Usage as bars (e55d8ebe, f94e18f4): the same chart above the table that was already there, plotting the one money column the panel declares, in the colour the same figure has on Overview. Usage gets no pie: a pie of models needs each model's share, and the runtime serves counts, not shares.

  • The first screen (783fb6e5, eb5b1b51): Overview opens with the four headline tiles in one row, then the spend chart, the tier donut and the newest run in the next; Usage opens with its two charts side by side, then the table. The Last run card leads with status, cause, model and age. This is what the dashboard requirements ask of the Overview without scrolling at 1440x900 (tokens, spend and savings; the newest run's status, model and age), with the charts added. The page files declare the same arrangement. Recent runs now starts below the first screen.

  • A run's summary says more (eb5b1b51): the Workflow run summary also shows Required bar, Tokens in, Tokens out, Finish reason, Truncated and Escalations, six columns delegation.decisions.v1 already serves and no page showed. Each is shown as served; the Workflow contract declares them.

  • Where a run's time went (b4ecb26c, eed935a4): the Workflow run summary also shows Requested model, Queue wait and Execution, and draws the run's three timings (queue wait, execution, duration) as bars, side by side and never stacked. A timing the row does not carry has no bar and is named under the chart; a served zero keeps its row. The three columns are served since omnimarket v0.4.309 (OMN-19448); the captured lab catalogue is older, so they are on a served-since list that a test forces to shrink once a recapture lists them. The three timing tiles stand together, last in the summary, in the order their bars are drawn in (5e7a519b).

  • Runs draws the runs it lists (b4686d79): above the list, Duration per run (one bar per listed run, coloured by its status) and Tokens per run (in and out side by side, never stacked). Both draw exactly the rows the table lists, after the same filters and on the same page of the list, in its order. A click on a bar opens that run on Workflow; a key of buttons hides a status from the drawing and not from the list. Nothing is added up or averaged.

  • A chart reads only a number or decimal text as a point (5e7a519b): a value served as a list holding one number was read as that number and drawn; it is a gap now, on every chart. Found in review of eed935a4.

  • A run's model and backend filter from its row (529452d8): on Runs the model and the backend in a row are buttons that set that one filter and go back to the first page; Clear filters is shown while any filter is set and resets all five. They are the filters the selects already have, so the charts, the pager and the address follow.

  • From one run to the next (0dfc9ef7): on Workflow a run that is among the served runs links to the newer and the older run, in the Runs list's newest-first order. A pasted id that is not among them gets no links.

  • A tooltip writes served text as text (104a0365): the chart library puts a tooltip formatter's result into the page as HTML, and the Runs charts' tooltips (since b4686d79) and the spend chart's tooltip (since it was added on this branch) wrote a served run id, model or day into it unescaped, so markup in a served value was rendered. Every piece of text a tooltip writes is escaped now. Found in review of b4686d79 from the source; reproduced before the fix; nothing on dev is affected.

  • Run tables fit again (same commit): see the regression under How it was verified.

  • Two guards, no product change (2dc622da): a page test that every piece of text on the six pages sits in the header or in a panel's shared parts, and a source guard that page code holds no typed figure and turns no missing value into a number.

Overlap-Reviewed: #396 — this lane's own change for the same ticket, and the base of this stack: its head f83bfd7c is an ancestor of this branch, so this diff contains it unchanged.

Overlap-Reviewed: #397 — this lane's own change for OMN-19987, also in the stack: its head 9680b968 is merged into this branch (signed merge 5831722e). This change adds the step diagram above its timeline and an anchor on each timeline step; it changes no step rendering.

Overlap-Reviewed: #384 — this lane's own change for OMN-20008: it adds a percentage line under the Savings figure in LocalDashboardPage.tsx and one required field in overview.contracts.yaml. A trial merge of its head fdfdc3c7 into this branch is clean (2026-10-13, 16:55Z).

Overlap-Reviewed: #337 — a parked draft for OMN-19730, last pushed 2026-09-28: in src/App.tsx it adds a lazily loaded execution-graph page and an early return when the address carries view=execution-graph. The one line of that file in this diff comes from #397 (the Workflow arm of the page switch turns address sync on); none of this branch's own commits touches src/App.tsx. The two do not meet.

How it was verified

The three merges of 2026-10-14 (claude-e014f5ba): dev and #397's head merged with no conflict (signed d644ac09, e3329f61). #396's head conflicted in two files (signed a351ebc0):

Fresh correction verification, codex-01a13694 (2026-10-13): the URL control shows the old regex accepts /bogus/workflow?... and the exact resolved URL rejects it. All 12 tests in tests/e2e/local-dashboard-omn21015.spec.ts pass in lab Chromium147 with 0 skipped and 0 flaky (19:49:07Z,18.1s); JSON, source hashes and1440x900 Runs screenshot are retained in OmniNode-Workflow/records/readbacks/2026-10-14-seven-dashboard-codex-01a13694/. The lab source hashes match the committed workflow and browser spec. All applicable declared pre-commit checks pass for each changed file, including YAML, TypeScript, normalization symmetry, script references and the required-check skip-vector check. The first hook attempt lacked sibling-source environment paths; those inputs were configured, then every hook re-ran successfully. The first lab attempt could not launch its missing browser; the official Playwright headless shell was installed only in this isolated test directory, and the suite then ran successfully. Hosted startup and CodeQL results on the new head remain pending publication. No whole-ticket or current-release acceptance claim.

On head 104a0365, 2026-10-13, unless a line names an earlier head.

  • A regression this branch introduced and then fixed. The older browser specs for the local pages were not run while this branch was built (no hosted job runs them). Run at f94e18f4, 7 of 27 failed where dev passes all 27. Four were this branch's doing:
    • the Runs table scrolled sideways (80px in local-dashboard-omn20759.spec.ts) on rows with a long host and model, and Overview's Recent runs by 103px in the contracts spec's fixture. Cause: the one-line run id (a 9ch floor), letter-spaced headers and a padded pill. Now a 7ch cut, no header letter spacing and a slimmer pill in the two wide tables: no sideways scroll, and a row 149px tall with that fixture, as before the redesign (it had grown to 187px);
    • the bar charts' value axis labelled its origin "$0", which the unmeasured-zero check found on Overview and Usage. The origin carries no label now.
      The other three were already failing on feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396 and feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 as pushed (stale expectations: a fifth filter, Workflow no longer "partial", the savings total shown twice). All are fixed in 783fb6e5; all 39 local-dashboard browser tests pass, run twice at 104a0365. Running them before that commit found one more of the same kind: the new tokens axis wrote "0" at its origin, which the older check for an unmeasured zero on Runs refuses; fixed in the same commit. Ten tests in three other specs (delegation-*, mobile-crawl) fail identically on dev and are untouched.
  • The first screen, measured in Chromium at 1440x900: with nothing scrolled, the four headline figures, the baseline beside the savings, both chart drawings, and the newest run's status, cause, model and age lie wholly inside the window, and no table on the page is cut off (the spend table lost its last column, 41px, before it was made to wrap). Before the reorder the spend chart ended 2,026px down. On the live build over real rows the same holds in both themes.
  • Whole suite: 2,506 tests passed, 15 skipped, 2 failed. The 2 failures are inherited from feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397: src/pages/local/pages.test.ts checks the Workflow page against a captured lab catalogue that does not hold run-trace.v1 yet. They are not caused by this change and are not fixed here.
  • Type check and lint: tsc --noEmit exits 0; eslint --max-warnings=0 on src/pages exits 0.
  • Tests were written before the code they test, with the exceptions listed under Local gates. New test files: charts.omn21015.test.tsx, charts.omn21015.legend.test.tsx, shares.omn21015.test.tsx, LocalDashboardPage.omn21015.interactive.test.tsx, LocalDashboardPage.omn21015.one-look.test.tsx, LocalDashboardPage.omn21015.usage-savings-chart.test.tsx, LocalDashboardPage.omn21015.layout.test.tsx, LocalDashboardPage.omn21015.run-summary.test.tsx, omn21015.no-sample-figures.test.ts.
  • Mutation controls, each mutant caught by at least one test: meter and shares 10 of 10; run links, copy and diagram 13 of 13; table timestamp 3 of 3; the first two review fixes 12 of 12; the legend 12 of 12; the one-look guard 9 of 9 (first run 8); the donut and the bars 16 of 16 (first run 11 of 13, two fixtures sharpened); the Usage chart 4 of 4; the series colour 5 of 5; the layout, the table floor, the axis label and the run summary 18 of 18; the run timing 16 of 16; the Runs charts 18 of 18; the row filters 8 of 8 (first run 4 of 7: three tests could not tell a pager left on a later page, a Clear button ignoring one filter, or a click wiping the other filters, and were sharpened); the run links 6 of 6. The tooltip fix has a control in Chromium: with the escape taken out the test fails. The no-sample-figures guard reports 9 of 9 faults planted in the real page code.
  • The donut, measured in Chromium (tests/e2e/local-dashboard-omn21015.spec.ts, 1440x900): 36,000 points round the ring are each asked which slice's stroke they lie in. Shares of 0.0001, 0.2499 and 0.75 each cover their share to within ten samples, no point lies in two slices, none is left bare, and each slice starts where the one before it ended. The first version laid a dash on a circle and failed this test: Chromium drew a 0.2499 share off by 0.0012, because it measures that circle as 99.36 units where the markup assumed 100. Slices are real arcs now.
  • The legend in a real browser (same spec): Tab reaches the second toggle, Enter removes that series' bars from the drawing and leaves the other two, Space brings them back. With a toggle that hides nothing the test fails.
  • Findings from independent review, each fixed test-first:
    • at 5831722e: a 2px floor on a tier segment drew a small share too wide; the Copy button kept saying Copied after the text beside it changed. Fixed in 29d2d2f0, passed on re-review.
    • at 2dc622da: the no-sample-figures guard missed a signed number, a bracketed number and a missing value read as a bracketed 0. The scanner now looks through brackets, casts, non-null marks and a leading sign; six planted controls failed first. Fixed in e34d580b, passed on re-review at f94e18f4.
  • Every figure, old page against new page, over real served rows (lab store of two real delegations, 16:53Z, both drawn at one held instant): the pages as they were before this change and the pages of eb5b1b51 were drawn over one capture of the runtime's answers. 147 figures were compared (every field, table cell, headline, caption, typed state and step text; Overview 85, Runs 34, Workflow 13, Usage 11, Credentials 1, API Keys 3) and all 147 are the same; the six new run-summary fields are listed as new and equal the served row. The four drawings that have a served counterpart (donut slices, pass-rate meter, step diagram and its links) equal the served rows. Two planted wrong values in what the new page is served were reported in all 5 places they are shown. At 14:20Z the 254 served fields equalled the store's own rows, and two values planted in a copy of the store rows were both reported; no delegation was run since.
  • Both themes: the first screen of Overview and Usage was captured at 1440x900 from the live build of eb5b1b51 in the dark and the light theme, with 0 console errors; all six pages were captured in both themes at f94e18f4.
  • In a browser over real served rows: the build of eb5b1b51 was served from the lab store and walked at 1440x900 at 16:51Z (the all-pages walk was not repeated at eed935a4; the Workflow page was, below). All six pages rendered with 0 console errors and 0 failed requests, the Workflow walk passed, and the run tables and the tier-mix table fit their panels with no sideways scroll.
  • Run timing over a real served row (build of b4686d79, lab store, 17:52Z, dark and light theme, the theme in force read from the page): the newest real run's row serves its duration (4960 ms) and neither its queue wait nor its execution time. The page shows exactly that: one bar, the two tiles as Not recorded, and both named under the chart. 0 console errors, no sideways scroll, the run's steps start 625 px down. A run carrying all three timings is measured in Chromium over constructed rows only, not on a real run. An earlier version of this line said a 17:23Z read was in both themes; that read was the dark theme twice, and this one replaces it.
  • Runs charts over real served rows (same build, 17:52Z, dark and light theme): two real runs. Duration bars 106.29 and 138.94 px for served 4960 and 6484 ms (ratio 0.7650 against 0.7650); tokens in 0.7737 against 0.7737, tokens out 0.8769 against 0.8769. Both charts and the head of the list are on the first screen, no sideways scroll, 0 console errors, and a click on the second run's bar opened that run on Workflow. Not seen on real rows: a failed run's colour, the status key, a run with no value, a second page of runs.
  • Row filter, Clear filters and run links over real served rows (build of 104a0365, 18:16Z): pressing a row's backend put it in the address and the select and showed Clear filters, which undid all three; on Workflow the Older run and Newer run links walked the two real runs there and back. 0 console errors. The store's two runs share one backend and model, so the filter narrowed nothing there; narrowing is shown over constructed rows.

Not verified:

  • The bar charts' bars are tested through a mocked chart library: the options handed to it are asserted, not the pixels. The donut and the legend are the exceptions (measured in Chromium).
  • The live store is thin: one day and two passed runs on one tier. On it the donut is one full ring and each bar chart has one day. No failed run, no second day, no second tier was on screen, so those were seen only in tests.
  • The Playwright specs are run by hand; no hosted job runs them.

Failure paths

n/a: this change renders served rows; it writes no receipt, verdict or report, so there is no size cap, unanswered request or incomplete verdict to test.

What the drawings do when the data is not there is tested instead: no day rows, no declared currency column, a tier with no served share, a share or pass rate off the 0 to 1 scale, a run with no id, a run with no steps, a step kind the page does not know, no clipboard, and a clipboard that refuses. None of them draws 0 or an empty chart.

Open defects

None.

Resolved since the last update

  • Timestamps in the Runs table wrap over several lines Kept: the existing layout checks require it. Measured on the lab at b558da7e (Chromium, 1440x900): local-dashboard-omn20759 and local-dashboard-contracts pass 21/21 as built. With CellTime rendering the timestamp on one line (no <wbr>), 3 fail, among them "the Runs table fits its panel at 1440x900 with every column" and "Overview and Runs scroll to their last row while the header stays reachable". RU-1 asks for the time column, not one line.
  • Recent runs on Overview starts below the first screen Not a defect against the requirement. Q2's pass condition is "the newest run's status, model and age are visible; a failure shows its typed cause". The Last run card (OV-4) carries those, and local-dashboard-omn21015.spec.ts:247 asserts it is on the first screen at 1440x900, with a failed newest run's cause. That passed at b558da7e.
  • feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396 and feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 failed browser tests that dev passes Resolved: both have landed, and all 39 local-dashboard browser specs pass at b558da7e.
  • Eleven commits not independently reviewed Reviewed from source on 2026-10-15: no outstanding finding beyond the typography note under Not in this change.
  • The 2 inherited catalogue failures Resolved: feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 recaptured the catalogue, and 0ede655b empties the served-since list.
  • No evidence companion yet Resolved in 9f6d8f82: the in-repo contract now binds AC5 (the OMN-19980 savings-source guard plus the chart tests) and AC9 (the run-link tests) to this change's own tests. Both criteria are copied byte for byte from the ticket. On the lab each check fails at dev 0ba6c5ac with only its tests laid over, and passes at this head (AC5 77/77, AC9 24/24).

Local gates

  • No gate was bypassed — no --no-verify, no SKIP=, no --no-gpg-sign, no core.hooksPath override. Every pre-commit and pre-push hook ran.
  • Any hook that failed was fixed at the input, not worked around.

Three things a reader should know, each also in its commit message:

  • e6b4b3b5 named two savings fields in page code and failed the savings-source guard's rule R4. Only that commit's own tests had been run before it. 69e5600f fixes it; the whole of src/pages is run before every commit since.
  • d443b2d2 was committed after a hook run that had failed; the hooks were then re-run over it and passed.
  • The table timestamp cell (CellTime) was written before its test. The test (I8) was added before the commit and catches 3 of 3 mutants.
  • The legend's browser test and the two guards in 2dc622da were written against code that already existed. Each rests on a control instead: a mutant or a planted fault it is shown to catch.
  • In f94e18f4 the chart's two tests of its new colour prop were written with the prop; the page test that needed it failed first.
  • Existing tests that changed in 783fb6e5, each to say the same thing about the page as it is now: in tests/e2e/local-dashboard-contracts.spec.ts, the Basis column is looked up in the table that holds the runs; "quality and tier mix below Recent runs" and "Recent runs starting above the fold" are dropped (no panel cut off is still held); the Overview test holds the four tiles and the newest run's status, model and age inside the window (in eb5b1b51), where it held the whole Last run panel; Workflow carries no "partial" chip; the savings total is read in its own card; Runs has five filters. In src/pages/local/pages.test.ts, Usage's table is found by its id, not as the first component. The 25 screenshots those specs rewrite are committed as regenerated.
  • Existing tests that changed in e34d580b, and why: the chart test that pinned line series now pins bars; the legend test reads a series' colour from its bars; the tier tests read a slice's start and sweep where they read a bar width.

The clone these commits were made in has no pre-commit hook installed, so the repository's declared pre-commit stage was run by hand before each commit: 12 or 13 hooks passed on each of the last eight commits. On 25eddedf, 783fb6e5 and eb5b1b51 the first run failed on the end-of-file fixer, which corrected the files; the hooks were run again over the corrected files and passed before each commit was made.

Not in this change

  • The local pages do not use the shared typography. Independent review (2026-10-15, records/handoffs/20261015T144311Z-agent-to-codex.md) found that new captions, legend labels, filter buttons and run links in b4ecb26c, b4686d79, 529452d8 and 0dfc9ef7 render text without Text, and .local-run-neighbours a sets font-size: 0.8125rem. A change to the shared typography tokens does not reach them. The six local pages have been built this way on dev from the start: LocalDashboardPage.tsx has 0 <Text> uses beside 60 raw text elements, and local-dashboard.css holds 23 literal font sizes and no token. This branch follows that convention (0 and 69; 47). eslint --max-warnings=0 on src/pages passes, so local/no-typography-inline is not broken. Fix: moving all six pages onto Text and the tokens, a change to pages already on dev, which is not made here and is not decided.

  • Tier mix by model (AC2), savings per run (AC3) and the table half of AC4: each needs a field the runtime does not serve yet. That is stage 2.

  • The 20-delegation lab walk (AC6) and the dashboard-access line on API Keys (AC10).

  • The stale "PARTIAL" chips in the sidebar on Usage, Credentials and API Keys, which belong to other tickets.

1 sources read: the ticket thread (12 comments, one page), this PR's body, #396's and #397's merge records, the lab runs at 0264bced and b558da7e, the two independent reviews of 2026-10-15.
2 live at 2026-10-15 18:05 UTC: PR draft, head 9f6d8f82 pushed, level with dev 0ba6c5ac.
3 weakest claim checked: "the ours-strategy merge drops nothing", by comparing bb804fff's tree with 02af97fe's (equal) and checking 02af97fe is an ancestor of 7e2dce61.

Requirement RU-1 lists backend among the Runs filters, and the page had
status, cause, model and window only. The backend is the backend_id that
delegation.decisions.v1 already serves and the table already shows, so
this is a filter over a served field and nothing is derived.

A run served without a backend is filtered as Not recorded, the same word
its cell shows, so it is never matched under another backend and never
unreachable. The filter is carried in the address beside the other four,
and an address naming a backend the data no longer has says so in the
select instead of showing an empty choice.
…UTC) and all time

Requirement OV-3 asks for the headline figures in a selectable window, and
the page read the all-time row only. The fold already serves one row per
UTC day beside the all-time row, so "today" is the day row whose
window_start is today's UTC date: a row the runtime served, never a sum
made in the browser.

The page keeps the chosen window and hands it to every card that reads
metering-summary.v1, so the cards cannot show different windows side by
side. A UTC day with no runs has no row; the cards say so instead of
showing 0 or another day's figures. The baseline rule the all-time row
already followed (the row refreshed last wins, a tie is refused by name)
applies to a day's rows in the same way.
… UTC day

The demo shows spend over time against its baseline; the local Overview
had headline totals only. metering-summary.v1 already serves one row per
UTC day with the actual and the baseline spend, so the panel is a new
declared table over rows that are already there.

The panel renders the columns the page declares and nothing else: the page
code names no savings field, the money columns are written as served, and
the last line of each table is the served all-time row, the same row the
headline cards read. It adds nothing up. A figure that was not measured
says so, a day under an unresolved baseline shows no baseline figure, and
after a baseline change each baseline keeps its own table.

The test's two-day fixture is a real record: one install's three stored
metering rows, read on 2026-10-13, without its tenant id.
…acked by model

Requirement US-1 asks for tokens per day as a stacked series split by
model; the page had the per-model-per-day table only. The rows are the
ones usage-by-model-day.v1 already serves.

Each segment is one served row's count and grows by that count, so the
stacking is done by the layout: nothing is added up in the browser and no
total is written. Tokens in and tokens out are separate bars and never one
figure. A model whose count was not recorded is named under its bar rather
than drawn as zero, and a served zero is drawn as a zero.

The series has its own empty-state sentence. With the table's sentence it
would have said the same thing twice on the Usage page, which an existing
test of that page rightly reads as one statement.
…rder served

The Workflow page still shows fields of the newest decision and says the
full path waits on run-trace.v1. This adds the component that will show
that path; it is not bound to the page yet, because the exposure is not
served until omnimarket's node lands.

- RunTimeline renders one run's steps exactly as given and sorts
  nothing: the order is the exposure's step sequence (WF-1).
- The failing step is the one the terminal row names in
  failing_step_sequence; it is looked up, never inferred (WF-2).
- A duration that was not measured reads "Not recorded", never 0 ms; an
  unknown step kind is shown as served; a run with no steps says so.
- Ten component tests over a chain recorded from a real delegation on
  the lab: served order kept when rows arrive in another order, the
  timed-out run marks the provider call and its cause, a run that
  recovered marks nothing, a terminal naming an unserved step marks
  nothing.

Not in this commit: the page binding, entry by id or newest run (WF-3),
and removing the "waits on" text. Those follow the exposure.
The fixture carried the lab's model id in three rows, which the
hardcoded-model-config hook refuses, and it had no final newline.

- The model id is replaced by a neutral name. Every other field is as
  the lab run published it, and no test reads the model name.
- The test file's header says so.

The repository's pre-commit hook is not installed in this checkout, so
the previous commit did not run it. Run by hand on the three files with
the core repository path set: every hook passes.
…d order

usage-by-model-day.v1 serves the newest day first, then models ascending.
The fixture had the oldest day first while its comment called that the
served order, and the one order test would have passed a component that
sorted days newest first.

The fixture now matches the exposure, and the order test is two: rows in
the served order keep it, and rows served oldest first are rendered oldest
first. A re-sort in either direction now fails exactly one of them.
On the Overview's Spend per day table a missing cell read "Baseline unresolved"
whenever the row's baseline was unresolved, including actual spend and the run
count, which are measured from the runs and do not depend on a baseline. The
metering fold serves exactly that row when no run was measured and no baseline
resolves.

The cell now says "Not measured" for the run-measured figures and keeps
"Baseline unresolved" for the rest on an unresolved row. The page names the
run-measured fields only, so the savings-source guard's rule R4 still holds.
A test pins the grouping to the captured lab catalogue's column list, so a
column served later has to be placed before the suite passes again.

Found in review of the previous head.
… count

Each segment of the Tokens per day bars carried its label, padding and a
border and grew from its content width, so the drawn widths did not follow the
counts: in the review's Chromium fixture 1 token beside 1,000 drew at a ratio
of 0.129 instead of 0.001.

The drawn segments are now empty and grow from a zero basis, so a segment's
width is its count's share of the bar and nothing else. Every figure is
written in a list beside the bar, where a very small count still reads; the
bar itself is hidden from assistive technology.

The unit tests assert the zero basis, the empty segment and a stylesheet rule
with no padding, border, margin or width. jsdom lays nothing out, so a
Playwright spec measures the widths in Chromium on the Usage page: 1 against
1,000 and 300 against 1,200, each within 0.0005 of the counts' ratio. With
padding and a border put back on the segment both tests fail (the spec reads
0.0153 for 1 against 1,000).

Found in review of the previous head.
…ce.v1

Until now the page showed four lines derived from the newest decision row
and said the full path was not served. The node serves run-trace.v1 in
omnimarket#3839, so the page now binds it.

The exposure is read for exactly one run, with ?correlation_id=. Which run:
the one the address names, else the newest by the decisions binding's
written_at. That is also OMN-21015's AC9 (requirement WF-3): a run in the
Runs list links to its own path, a pasted correlation id opens that run,
and no id shows the newest run.

Three things in the page loader had to change for "one run" to be safe:
- reads in flight were shared per exposure. They are now shared per
  exposure and parameters; otherwise a second run would have been handed
  the first run's answer.
- last good rows are kept across a failed read only for the same run.
- run-trace.v1 is never read without an id. With no run to name it is not
  read at all, because unfiltered it serves every run's steps together.

The page never falls back to another run. An id no run has says that run
has no steps. Steps answered for another run are refused. A failed or
unserved read is named as that, not as a run with no steps. A truncated
answer says it is not the whole run.

The placeholder steps derived from the decision row are gone. The
decision's verdict, backend, host and tier stay on the page as a summary of
the run shown, taken from that run's own row.

Not green yet: two tests in src/pages/local/pages.test.ts refuse a binding
to an exposure the captured lab catalogue does not list, and run-trace.v1
is not in it until omnimarket#3839 is served on a lab install. The
catalogue capture is its own commit.
…ial chip

Four things seen when the page was read in a browser at 1440x900 against
a lab store holding two real runs:

- The run ids in the Runs list are links to Workflow, but looked like the
  plain text beside them. They now take the brand colour and an underline.
- The run summary's finish time wrapped over three lines in its field
  cell. The terminal step shows the same moment on one line, so the field
  is removed.
- The summary's last field ran into the first step: there was no gap
  between the two. There is one now.
- The sidebar still marked Workflow as partial, which it says of a page
  whose exposure is not served yet. Workflow reads run-trace.v1 now, so its
  chip goes. Usage, Credentials and API Keys keep theirs; those flags
  belong to their own tickets.
Read live against a lab store holding two real runs, the page was hard to
read: each step was one dot-separated line with a timestamp to the
microsecond, and nothing told a pass from a failure at a glance.

Each step is now a block on a rail. It carries its name, its served
outcome as a pill (green when it succeeded, red when it failed), its
duration at the right, its served time under the name, its details as
labelled chips ("backend: local-coder") and its reason as a line of its
own. The dot on the rail takes the outcome's colour, and the step the
terminal names as failing is ringed.

The run's decision row is a row of tiles with the verdict as a pill. The
run id is set in the mono face, with a tag when it is the newest run and
not one that was asked for.

What is shown did not change. Every value is a served field and the order
is the order served. A timestamp's text is the served string, character
for character; only its clock part is set in a stronger tone, and nothing
is parsed into a date. A duration that was not measured still reads Not
recorded.
Read live against a lab store, the six pages worked and looked like six
different drafts: bare headings, figures in three sizes, plain-text
verdicts, unframed tables.

This is the shared layer, and it changes how things look only:

- every page header carries the icon its sidebar entry uses, beside the
  title and what the page shows;
- every panel has a small upper-case label with its own icon;
- headline figures are set in the mono face at one size, and the two
  savings figures are green;
- a run's verdict is a pill (green passed, red failed) in the Runs list
  and in Overview's recent runs, as it already is on Workflow;
- tables are framed, with a quiet upper-case header row, and ids and times
  in the mono face;
- a state that is not a figure (not served, not measured, no rows) is one
  framed line, and an error is a red-edged one.

No figure is added, removed, reordered or reformatted. The icons are
Lucide, which the sidebar already uses. Charts and the remaining pages'
own parts follow in their own commits.
…d day rows

The demo's Overview leads with baseline against actual spend over time.
Ours carried the same rows as a table only.

The chart is a second way of showing metering-summary.v1's day rows, so it
plots only what those rows serve: one point per served day row, in the
order served. spendByDayGroups shapes them and nothing else decides what
is drawn:

- the all-time row is not a day and is not a point;
- a figure a row does not carry is a gap, never 0, and gaps are not
  bridged;
- an unresolved baseline has no baseline points, and its actual spend is
  still plotted;
- two baseline models are two charts, never one joined line, because
  their modelled figures are priced differently;
- each value is the served decimal, and the tooltip shows the served text,
  not the number the axis needed.

The table of the same rows stays under the chart. It is drawn with
echarts, which the repository already depends on, through its SVG
renderer; colours come from the theme's status tokens.

Ten tests, written before the module and red until it existed (the type
check could not resolve it). They mock the chart library and compare the
option it is handed with the shaped rows.
Read live, the header took two tall lines to say what the sidebar entry and
the breadcrumb already say: the page name, then a sentence under it.

It is now a single row: a smaller icon, the title, and what the page shows
on the same line, cut with an ellipsis when it does not fit. The text is
unchanged and still in the page; only the space it takes is given back to
the figures.
…te as a meter

Two served fractions were shown only as bare numbers: a tier's share of the
tier-routed runs read "1", and the quality pass rate read "1".

The tier mix now leads with one bar, a segment per served tier in the order
served, each as wide as its served pct_of_tier_routed, with a legend that
names every segment and its share. The quality panel leads with a meter of
the served overall_pass_rate. The table and the field list under them are
unchanged, so neither adds a figure.

Both fields are fractions between 0 and 1. That was read in the runtime,
not assumed from the name: the Postgres view (migration 0055) and the local
store's view both serve count / tier_routed_total with no factor of 100.
So:

- nothing is recomputed from counts; the bar follows the served share even
  when the counts would give another number;
- a row that is not tier-routed is not a segment;
- a tier with no served share is left out of the bar and named under it;
- a share or a rate off the 0 to 1 scale means the page and the runtime
  disagree about the unit, and nothing is drawn: the bar says why, the
  meter is absent.

An older tier-mix test fed the panel 71.43 and 28.57, a scale the runtime
never serves. Its fixture now uses 0.7143 and 0.2857; what it asserts (the
table shows the served value as served) is the same.

Fifteen tests, written first and red until the module existed; ten mutants,
each caught. On a lab store with two real runs the one segment measured the
full width of its bar for a served share of 1.0, and the meter read 100.0%
for a served rate of 1.0.
Lakshman, reading the pages live: the name of the page and a sentence about
it at the top repeat what the sidebar entry and the breadcrumb already say,
on every page, and push the figures down.

The title and its description no longer take any space. They stay in the
page for a screen reader and for anything that finds a page by its heading.
What is left at the top is one toolbar row: the page's own control when it
has one (Overview's window switch moves into it) and the runtime mode at the
right.
…and names none itself

The chart added two commits back chose its own series: it read
counterfactual_usd and spend_usd by name. The savings-source guard
(OMN-19980, rule R4) allows page code to read a savings figure only through
a contract, and it failed on that file:

  R4 src/pages/local/charts.tsx:16 ServedSeries: counterfactual_usd

I committed that chart after running its own tests and the Overview page
tests, not the whole suite, so the guard never ran on it. The whole suite
ran before this commit: 2,304 passed, 15 skipped, and the 2 known
catalogue tests failed.

The chart now takes its series from the panel's declared columns, the same
ones its table renders: one series per declared column whose format is
currency, in declared order, under its declared header. For Overview that
is actual spend, baseline spend and savings. The module names no figure, a
column the page does not declare is never drawn, and a declared count is
not plotted as money. Colours go by a series' place in the declaration.

The tests were changed first and went red (9 of 11) against the old
module. One failure mode is added to them: the chart deciding for itself
what to plot.
…nds copy, Workflow draws the steps

Three things a reader could not do on the local pages.

Open a run from Overview. The run id in Last run and in each Recent runs
row is now the same link Runs already had, to that run's path on Workflow.
A run with no recorded id has nothing to open and stays plain text.

Copy what is shown. A small Copy button sits beside Workflow's run id and
beside the two commands the empty states tell the reader to type. It copies
exactly the text it is given and reports what happened: Copied only after
the clipboard took it, Copy failed when there is no clipboard or it
refused.

See the run's shape. Workflow leads with the served steps as a row of
nodes, one per served step in the order served, an arrow between each. It
reads the same rows as the timeline under it and decides nothing: no step
is sorted, merged or dropped, a kind the page does not know is a node named
as served, and the marked node is the step the terminal names as failing.
Each node leads to its own step in the timeline.

Last run's status is now the same pill the tables use.

Eleven tests, written first and red (11 of 11) before the code; thirteen
mutants, each caught. Whole suite with this change: 2,304 passed, 15
skipped, and the 2 known catalogue tests failed. On a lab store with two
real runs the page showed five nodes for each run's five served steps.
… token bars get colours

In the two wide run tables a run id broke over eight lines and set the
height of every row. The id link is now shown on one line and cut with an
ellipsis. The whole id is still the link's text and its title, and
Workflow shows it in full with a Copy button.

Measured in Chromium at 1440x900 on a lab store with two real runs, before
and after:

  Runs rows       168 px -> 130 px
  Overview rows    92 px ->  74 px
  both tables     1096 of 1096 px, before and after (no sideways scroll)

A table timestamp may now break before its time and before its fraction.
The text is the served text, character for character. A 13ch floor on that
column was tried and measured to push the Runs table 17 px past its panel
at 1440, the fault OMN-20759 fixed, so the column has no floor and the
timestamp still wraps over several lines there. Fixing that needs a column
dropped or the table scrolled, which is not this commit's call.

Usage's token bars were two greys. Each series now has its own colour, the
same on the segment and on its legend swatch. No segment's size changes.

The timestamp cell was written before its test. Two of three controls on
it were not caught by the existing tests; a test (I8) was then added and
all three are caught.
…er a newer one

Codex's review of efcf5e8 found it: reads of one page can overlap. The
refresh button and the page's interval each start a read while an earlier
one is still unanswered, and every read wrote its answer when it finished.
On Workflow, with no id asked for, that put an older run back on screen:

  1. the first read finds run A newest and waits on A's steps;
  2. run B lands, the reader refreshes, B's steps are shown;
  3. A's steps arrive late and replace B, under the "newest run" tag.

The fault is in the page's load effect, so it held for every local page;
Workflow is where a late answer names a different run.

Only the read started last may write now. A read that was overtaken
answers a question the page has already asked again, so its rows, its
failure and its end of the loading state are all dropped, however late it
answers. A read of another page or another asked-for run was already
dropped; that check is unchanged.

Four tests, written first. W17 (the sequence above) and W19 (an overtaken
read that fails late shows its failure while the newer read is unanswered)
failed on efcf5e8. W18 (later refreshes are still shown after one read was
dropped) and W20 (an overtaken read whose catalogue request fails shows a
page error) pass on efcf5e8 and exist for the fix: each is the only test
that catches one way of getting it wrong. Seven mutants of the fix, each
caught; W20 was added because the first run of them caught six.

Workflow page tests: 26 passed, three runs in a row.
…says only what it copied

Codex's review of 5831722 found both.

Tier bar. The stylesheet gave every segment a 2px floor, so a small served
share was drawn wider than it is: shares of 0.0001 and 0.9999 drew as 2px
and 1,096px, eighteen times the served ratio. The floor is gone; a
segment's width is its served share and nothing else. A tier too small to
see is read from the figure written under the bar, so that figure may not
round a share away: a share above zero is written <0.1% where it used to
read 0.0%, and one below the whole >99.9% where it used to read 100.0%.
Exactly 0 and exactly 1 are written as before. The pass-rate meter uses
the same writer and follows the same rule.

Copy button. It kept saying Copied after the text beside it changed. On
Workflow the run id changes under a mounted button when a newer run
arrives, so the button named an id the clipboard did not hold. A copy
still unanswered at that moment marked the new id when it answered. The
button now keeps what the last copy did together with the text it copied
and says so only while that text is still the one shown; of overlapping
copies only the one started last reports.

Tests first. Eight failed on 5831722 with assertion failures (I9 x2, I10
x2, I12, H9, H10 x2), and the real-browser test of the drawn widths
failed with the ratio Codex measured (0.00182 against 0.00010). Three more
(I11 x2, one H10 case) pass on 5831722 and guard the fix against
over-correcting. Twelve mutants of the fix, twelve caught. The browser
test passes three runs in a row at 1440x900.

The Usage screenshot under tests/e2e is rewritten by the same spec run; it
now shows the page as this branch draws it.
…rk from the keyboard

Stage 5's U4 asks that a legend toggle hides its series and works from the
keyboard. The chart library draws its legend inside the drawing, where Tab
never reaches it. So the legend is now page markup: one button per plotted
series above the chart, each saying whether its series is shown, and the
chart is handed only the series that are shown.

What a reader's choice may not change:

- a series keeps its colour, which belongs to its place in the page's
  declaration and not to its place among the shown ones;
- the tooltip still pairs each name with its own served text (the chart
  numbers the series it was handed, so the lookup is among the shown ones);
- a refresh of the served rows keeps what was hidden hidden;
- each baseline's chart has its own legend;
- with every series hidden no empty chart is drawn; the figure says why.

Two things seen in the browser and fixed with it. The status green and the
brand lime were too alike to tell two lines apart, so the series use the
theme's chart colours (blue, orange-red, teal, yellow). The chart's own text
fell back to the browser's serif, so it is handed the page's font.

Tests first: ten legend tests failed before the legend existed (the buttons
were not there), and the font test failed before its change. Twelve mutants
of the legend, twelve caught. One of them, state shared across baselines, is
a weak control: it breaks seven tests at once, so it shows the test for that
case can fail but not that it fails alone.

In a real browser (tests/e2e, run by hand, 1440x900): Tab reaches the second
toggle, Enter removes that series' line from the drawing and leaves the other
two, Space brings it back. With a toggle that hides nothing the test fails.
The browser test was written after the legend; that control is what shows it
can fail.
…s, and no number that was not served

Stage 5 names two properties of the six local pages that nothing held yet.

U1, one look. A page is a header row and a grid of panels; a panel is a
heading with an icon and, under it, only the shared parts (headline figure,
caption, field list, table, chart, bar of shares, meter, typed state, filter
row, pager, the run parts). The new page test reads every piece of text a
page draws and fails on any that sits outside them. Each page is read twice:
with every exposure it binds served, over constructed rows, and with nothing
served, because the typed empty states are parts too. So that the guard is
not looking at an empty page, each served page is also held to one value
only the constructed rows carry.

U6, no sample data. The pages were redesigned beside a demo that shows
sample figures. The new source guard reads the code that draws the pages and
refuses a figure typed into the markup, a figure kept as text the page can
show, a missing value turned into a number (?? 0, || 0), and a number put
straight into the markup. Six fallbacks to a number that are not figures (a
decimal precision, the refresh interval twice, a page size, a panel width,
a tooltip index) are on a read list with a reason each; the list can only
shrink. It cannot see a number computed at run time from served values;
that is the per-figure comparison's job.

Both guards pass on the pages as they are: they pin a property, they do not
drive a change. What shows they can fail:

- one look: nine mutants of the page code, nine caught. The first run
  caught eight: a mutant that keeps the served rows from the panels was
  missed, because the check that a page drew something accepted a headline
  that read "Not measured". That check now asks for a served value.
- no sample data: ten planted snippets refused, eight honest ones let
  through, and five faults planted in the real page code each reported. The
  first run flagged two sentences that carry a digit inside a name
  (run-trace.v1, a ticket id); the markup rule now looks for a word that is
  a figure on its own.

No product code changes.
…ard that sees through brackets

Lakshman asked for a bar chart and a pie chart on the dashboard (chat,
2026-10-13 about 14:55Z). Both draw served values only.

Spend per day is a bar chart. A day is one figure, not a point on a slope:
with one served day a line was three loose dots, and a line between two
days suggested values in between that nothing served. Each series is a bar
per day, side by side and never stacked, because the sum of actual, baseline
and savings is no figure the runtime serves. The legend buttons, the tooltip
and the colours are unchanged.

Tier mix is a donut. Each slice is one served tier and sweeps exactly its
served share of the ring, clockwise from twelve o'clock in the order served.
Shares that add up to less than the whole leave the rest of the ring empty;
shares that add up to more than the whole are not a split of one whole, so
nothing is drawn and the reason is said (a hair over 1 from the runtime's
own rounding is allowed). Pointing at a tier, in the list or on the ring,
marks it and names it in the middle; the list takes keyboard focus for the
same. The flat bar this replaces is gone, so the share is drawn once.

A slice is a real arc. The first version laid a dash on a circle, and the
real-browser test showed why that is wrong: Chromium measures that circle
as 99.36 units where the markup assumed 100, and drew a quarter turn 0.5%
short, with or without a declared path length. An arc it draws to within
one part in 36,000.

The no-sample-figures guard missed three planted faults in review of
2dc622d: a signed number, a bracketed number and a missing value read as
a bracketed 0. The scanner now looks through brackets, casts, non-null
marks and a leading sign. It then refused a layout string this same commit
had added to the bar chart, which was removed.

Tests first, except where said:
- bars: one test RED on its assertion (line, expected bar);
- donut: its tests were RED because the component did not exist, and the
  arc version's six were RED against the dash version;
- guard: six planted controls RED before the scanner changed;
- in Chromium at 1440x900, 36,000 points round the ring are each asked
  which slice they lie in: RED against the dash version (a 0.2499 share off
  by 0.0012), green three runs against arcs. The legend test now counts
  bars.
Sixteen mutants of the donut and the bars, sixteen caught; the first run
caught eleven of thirteen, and two fixtures were sharpened (shares whose
sum rounds over 1, and 0.1 + 0.2). The review's three plants and the five
earlier ones are each reported in the real page code.

Existing tests that changed, and why: the chart test that pinned line
series now pins bars; the legend test reads a series' colour from its bars;
the tier tests read a slice's start and sweep where they read a bar width.
Lakshman asked for bar charts on the dashboard (chat, 2026-10-13 about
14:55Z). Usage had none: its savings per day was a table only.

The panel now mounts the same chart Overview uses, above the table that was
already there. It plots the money columns the page declares for this panel,
so usage.page.yaml now marks the Savings column as currency; the chart
names no field itself and the savings-source guard passes unchanged. One
bar per served day, the all-time row is no day, a day with no served figure
has no bar, and with no day rows there is no chart and the panel's typed
state stays. The table is untouched.

Usage gets no pie. A pie of models needs each model's share, and the runtime
serves counts per model and day, not shares; working a share out in the
browser is what the ticket's AC4 rules out until an exposure serves it.

Tests first: two of four RED before the change (no figure, no option handed
to the chart); the other two pin the table and the empty state and pass
before and after. Four mutants, four caught. One expectation of mine was
wrong and was corrected before the change: the table writes a served
0.300000 as $0.30, not $0.300000.
A series' colour followed its place among a chart's plotted columns.
Savings is third on Overview (teal) and the only series on Usage, where it
came out first (blue): one figure, two colours, on pages that are meant to
share one colour scheme.

A panel can now say which of the four chart colours each of its plotted
columns takes. Usage's savings panel gives its one series the third, the
colour the same figure has on Overview. A panel that says nothing colours
by place, as before, and a place off the palette is ignored.

The colour was first put in the page file, as an attribute of the declared
column. The page-file validator refuses a field it does not know (three of
its tests failed), so that was taken out again and the page files are
unchanged by this commit.

Tests first for the page: a test that renders Overview, then Usage, and
compares the Savings colour failed (blue against teal) and passes now. The
chart's own two tests of the new prop were written with it. Five mutants,
five caught.
…today is chosen

Seen on a lab store of two real days (2026-10-14, omnimarket dev 07fec7b8): with Figures for set to Today (UTC) the metering tiles showed today's 4 runs while Run locally read 6 of 6 runs local and Tier mix read 6, with nothing saying those two were all time. Only the panels bound to metering-summary.v1 have a row per window; the decisions, model-routing and quality-gate exposures serve no day rows.

With today chosen, each Overview panel not bound to metering-summary.v1 now carries one line: All time: this panel does not follow the window above. Nothing is shown when all time is chosen, so the all-time page is unchanged, and no figure moves.

Two tests, written first. Q6 failed before the change (no note on Run locally) and passes after it. Q7 holds the other side: no note on a panel that follows the window, and none after all time is chosen again. Planted faults, each caught and the file restored: the note on every panel (Q7 fails), the note whatever the window (Q7 fails), the note never shown (Q6 fails).

My first version of Q6 listed Compression and Cache hit rate as outside the window. They are bound to metering-summary.v1 and follow it; the lists in the test now follow overview.contracts.yaml.
…ages

Two conflicts. src/pages/LocalDashboardPage.tsx: this branch's panel markup (icon, data-component, data-kind) is kept and #396's line for panels outside the chosen window is added under the heading. tests/e2e/local-dashboard-contracts.spec.ts: two comments worded differently on each side; this branch's wording is kept, no assertion differs.

On the merged tree: tsc exits 0; whole suite 2,533 passed, 15 skipped, 2 failed, the 2 being the catalogue tests inherited from #397.
…serves run-trace.v1

The Workflow page binds onex.snapshot.projection.run-trace.v1, and the two
catalogue tests in pages.test.ts check every binding against the captured
lab catalogue. The capture of 2026-10-07 predates the run-trace node, so
they failed.

Recaptured read-only with GET /projections from the lakshman lab lane at
2026-10-14T04:30:11Z, after that lane was upgraded to omnibase_infra dev
ba12aff1 with omnimarket dev 7acb97b7 (0.4.313, which contains
omnimarket#3839). It is a dev build, not a released tag.

Same shape and order as before: the four fields per exposure, sorted by
topic. Rendering the lane's pre-upgrade response the same way reproduces
the 2026-10-07 file byte for byte. The new capture only adds: five topics
(run-trace.v1 among them, status ok) and new columns on eight; no topic
removed and no status changed.
…ced figure

omnidash#397 re-captured the lab catalogue (captured_at 2026-10-14T04:30:11Z),
and metering-summary.v1 now lists savings_pct_of_counterfactual. S9 failed on
the merge with dev, as designed, until the new column is placed in a group.

It is the ratio of the modelled saving to the baseline's spend, so it is
baseline-priced: on an unresolved-baseline day it reads Baseline unresolved,
which is what declaredCell already gives a column outside RUN_MEASURED_FIELDS.
…AC7 and AC8

omnidash#394 made repo evidence required: dod-verify on #396 refused with
"pull request cites OMN-21015 but carries no contracts/OMN-21015.yaml".

contracts/OMN-21015.yaml copies the four criteria from the ticket body and binds
each to the page test that proves it (test_passes, run with npx vitest from the
omnidash tree). It validates against omnibase_core's ModelTicketContract, and
omnimarket 0.4.310's node_dod_verify (the pinned verifier), hosted audience, on a
clean copy with no node_modules, returns verified: 4 verified, 0 failed, 0
skipped, no unbound criterion.
…iform-pages

#396 landed on dev as bb804ff, whose tree equals 02af97f. LocalDashboardPage.tsx
keeps this branch's version plus dev's one change there: RunPicker remounts on
key={requested} instead of copying the id in an effect. The two navigation
screenshots keep this branch's capture.
bb804ff is the squash of omnidash#396; its tree equals 02af97f, which the
parent commit already merges, so this branch's tree is kept unchanged.
…logue lists all three columns

The 2026-10-14 recapture (omnidash#397) lists requested_model, queue_wait_ms and
execution_ms on delegation.decisions.v1, so T10 required their entries to go.
…ach page's binding ids are unique

The merge of #396's head (7e2dce6) kept both sides' copy of the run-trace
binding. A page test now fails when any local page repeats a binding id; it
failed on 0ede655's workflow contract and passes with the copy removed.
…gn's own tests

The contract carried only #396's four bindings, so repo-evidence found no
evidence of this change's own. It now also binds:
- AC5 to the OMN-19980 savings-source guard plus the chart tests (each chart plots
  only served values, nothing computed in the browser);
- AC9 to the run-link tests (a run named on Overview or Runs opens that run, and
  Workflow links to its neighbours in served order).
Both criteria are copied byte for byte from the ticket. On the lab, each check
fails at dev 0ba6c5a with only its tests laid over (the run-link and run-chart
tests fail on assertions) and passes at this head: AC5 77/77, AC9 24/24.
repo-evidence refused AC5 as always-pass. Its control lays only paths that match
its test-side pattern (tests/ trees and Python test files) over the merge base, so
this repo's co-located src/**/*.test.tsx files are not laid over. At the base only
the OMN-19980 guard existed, and it passed alone. The check now runs the two chart
tests, which do not exist at the base (exit 1 there) and pass at this head (36/36
on the lab). The guard still runs in the full suite.
@Patel230
Patel230 marked this pull request as ready for review October 15, 2026 19:37
@jonahgabriel
jonahgabriel merged commit d0a647b into dev Oct 15, 2026
35 checks passed
@jonahgabriel
jonahgabriel deleted the lakshman/OMN-21015-uniform-pages branch October 15, 2026 19:49
@Patel230

Copy link
Copy Markdown
Contributor Author

Merged at 19:49:24Z as d0a647b3 from head 4f23fbc3. Read 19:53Z.

  • What landed for OMN-21015: AC5, the six pages' redesign with every chart plotting only served values, and AC9, opening a run from Overview or Runs, with Workflow linking to its neighbours. Both are bound in contracts/OMN-21015.yaml.
  • The head: a signed merge of dev 91e64d10, which carried fix(OMN-9050): omnidash reads one omnibase_core pin from the handshake #408's single core pin. Every check on it passed, including the ONEX Schema Compatibility Check, dod-verify and evidence verify.
  • Review: an independent review passed every commit, and the last delta (5d9c316d..4f23fbc3) is the 8 CI files from dev, byte-identical to dev.
  • Next for the ticket: AC2's page half follows on lakshman/OMN-21015-tier-mix-models, which already contains this head. AC3 and the table half of AC4 land in their own PRs.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants