Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/README.md
Snapshot at 34e3175
1# Owner dashboard assets and client
2
3A single responsive view for projects, agent lanes, packets and immutable fork-bound
4claims, blockers, receipts, review decisions and receive-ordered activity. This
5package builds static assets and a typed transport. It does not deploy a Worker.
6FH-012 mounts these assets at `/app`, validates Cloudflare Access JWTs (`aud` and
7`iss` from owner configuration), and protects the browser's owner API calls.
8FH-008 supplies the sidebar/gadget host and its Cap'n Web `ui` capability.
9
10Every user-visible product name and tagline is read from root `brand.json` at
11build time. No owner hostname or owner API credential is embedded in the assets.
12Production requests use the page's origin, `credentials: same-origin`, no-store,
13and redirect refusal. Access supplies its assertion at the edge; the client never
14sets an assertion or bearer header. MCP authentication remains a host concern.
15
16## Run and check
17
18After the root and exact pinned submodule dependencies are installed:
19
20```sh
21pnpm --filter forkhorse-dashboard dev:fixture
22# Open the printed loopback origin at /fixtures.html.
23pnpm --filter forkhorse-dashboard test
24pnpm --filter forkhorse-dashboard types:check
25pnpm --filter forkhorse-dashboard build
26pnpm --filter forkhorse-dashboard build:fixture
27pnpm test
28pnpm lint
29```
30
31`dist/` contains the production `index.html` and assets with `/app/` paths.
32`dist-fixture/` is a separate, ignored fixture build. The fixture dev server alone
33mounts the labeled fictional API. A static fixture build requires that same fixture
34API to be served by a test host; it does not emulate responses in the browser.
35Restart the dev server (or POST to its test-only `/__fixtures/reset`) to reset fixture approvals/promotions. All fixture actions
36are local. Passing these checks is not Access, Cloudflare, deployment or live Git
37acceptance.
38
39## Owner decisions on Overview
40
41The owner-only **Needs you** card routes to evidence, with no approval or promotion
42controls. It lists uncertain promotions, reconciler alerts, open collisions,
43failed required receipts, then reviews ready to promote or approve. Readiness
44uses FH-045's shared state derivation and review gate; Returned, Fork moved,
45Main moved, Out of date and promoted reviews are excluded, including approved
46reviews whose core stale flag is false. Five rows show initially, with expansion in place and a persistent
47empty state. Agent connections render no card.
48
49Review rows use the existing detail route; hosts without detail reads fall back
50to the review queue. Collisions open Work packet details, and reconciler rows
51focus the matching Agents row (or the Agents heading when the canonical repo
52has no agent row). Promotion intents without a review route focus Last promotion.
53Within each kind, review request times and Core's receive-sequence alert/collision
54IDs provide oldest-first ordering. A current-head alert without a sequence follows
55the recorded alerts; older capability snapshots without metadata retain their
56supplied order and available evidence.
57
58The loopback fixture harness includes `needs-all`, `needs-promotion`,
59`needs-reconciler`, `needs-blocker`, `needs-failed`, `needs-promote`,
60`needs-approve`, `needs-advisory`, `needs-empty`, and `needs-main-moved`. The last
61fixture pairs a nonstale approved Main moved review with a ready-to-promote
62review, showing only the latter in the card. Use `role=agent` to check
63absence and `host=queue-only` to check the capability fallback. These snapshots
64are synthetic, and fixture routes are excluded from production assets.
65
66## Agent status and resume
67
68FH-078 shows one Agents row per enrolled identity, ordered by the existing
69saddlecloth rank. Its most urgent live claim determines Status and Packet;
70additional live claims appear as a count. Last push comes from loaded fork
71activity, with exact time in a tooltip. Returned reviews, failed or running
72receipts, current review/approval and pushing-fork collision gates precede
73claim activity. The table uses the existing state words, tones and usage figures.
74
75A held claim is not a heartbeat. After 30 minutes without a loaded push the
76row says **Claimed, no recent push**, with the observed push or claim time.
77That absence requires the oldest loaded activity to reach back at least
7830 minutes. Shorter history keeps the active-claim state and says
79**Not in loaded activity** when no push is visible. A claim newer than the
80loaded history can say **No push yet**. Idle agents, including legacy identities
81without enrollment timestamps, start collapsed; Needs you reveals a targeted
82idle row before focusing it.
83
84The agent Overview starts with **Resume where you left off** when the host
85supplies a caller name found in the existing state. This read-only reduced
86card shows Packet, Fork, assignment-scoped Last push and the table's Status.
87It has no Next row because this host does not provide `myWork` next actions.
88No live claim reads **No live claim.** An unknown caller has no card.
89The owner Overview has no Resume card. No adapter or endpoint changes are needed.
90
91Labeled local scenarios `agents-status`, `agents-short-activity`, `agent-resume`
92and `agent-idle` cover all statuses and the activity-history boundary. Use
93`role=agent` for the read-only frame copy and reduced Resume card. Fixtures
94and source authoring do not establish acceptance; focused/root tests, lint,
95fresh-clone/D5 and light/dark capture review remain separately gated.
96
97## Race judge advice
98
99FH-063c reads the fixed optional race `judge` and `verdict: {ranking, rationale,
100at}` seam. The UI calls this **Judge's advice**, shows the ranked entrant
101saddlecloths, and keeps advice visible after a race finishes. Missing judge fields
102leave the existing race view unchanged. Rationale is escaped plain text; more
103than five rendered lines can expand in place, with the full text retained in the DOM.
104
105The owner can promote the pick through the existing review confirmation and
106promote endpoint, with the same visibility gate as the review queue. A pending
107promotion or required before-promote re-check hides Promote. Later review or push
108evidence also hides the advice action until new advice arrives. Agents see advice
109without owner controls. No dashboard advice submission or new write endpoint is
110introduced. Review detail and Needs you carry the pick's advisory context.
111
112`judge-*` loopback scenarios cover waiting, owner/agent, later pushes/reviews,
113promotion gates, finished results, long rationale and no-judge views. These are
114fixed-contract fixtures only; accepted-core integration and live acceptance are
115separate gates.
116
117## Host composition
118
119Serve `dist/index.html` for `/app` and `/app/` (or redirect `/app` to `/app/`), and
120serve its immutable hashed assets under `/app/assets/`. Protect both the HTML and
121asset paths with the configured Access application. Protect state, activity,
122approve and promote routes with the same owner identity in core. Never inject an
123owner API token. `/app?project=demo` selects a project. The HTML may contain this
124inert owner-configured project list before the module entry (escape JSON for HTML
125when generating it):
126
127```html
128<script id="dashboard-config" type="application/json">
129 { "projects": [{ "id": "demo", "label": "Example project" }] }
130</script>
131```
132
133There is no project-list route in FH-003/FH-005. The shell therefore uses configured
134project IDs and an “Open project by ID” field. It never invents project data.
135
136For a sidebar app or gadget, import `mountDashboard`, include `src/styles.css`,
137and pass `brand`, project options, and a host-owned `DashboardTransport`. Its
138interface has `getState`, `getActivity`, `approve`, `promote`, and optional
139`subscribe`, owner-only `getReceipt` and `setSettings`. The UI does not import Cap'n Web or mint a capability. The future
140host adapter implements that interface from its authorized `ui` capability.
141
142## Wire contract
143
144`StateResponse` in `src/contracts.ts` mirrors the public FH-005 JSON snapshot.
145`createHttpTransport` maps it through `stateView` into a `ProjectState` view:
146
147- `GET /projects/:id/state` supplies project, credential-free agents, forks with
148 heads, packets, live-only claims including `{packet, agent, repoName}`, blockers,
149 reviews, ledger, recent activity, reconciler alerts and `promotionPending`.
150- Approval state comes from `{sha, receiptDigest}` or `null`; `stale` and `promoted`
151 are explicit flags. A changed approval SHA is treated as stale. Digest binding applies to required review-created receipts; a fresh before-promote run or advisory receipt movement does not mean the fork moved.
152- FH-048 blockers retain `resolved: null | {at, by, reason?}` history. Missing legacy resolution reads as open; missing `at` and `pushingPacket` read as unknown. Open blockers gate the pushing fork and attributed packet (all reviews on that fork when attribution is null), never the claimant.
153- FH-032 adds `project.settings.requireReceipt` and the five-state `reviews[].receipt` summary: status, trigger, digest, timestamps, duration and check outcomes. No logs enter `/state`.
154- Owner-only `GET /projects/:id/reviews/:rid/receipt` returns the stored `{digest, receipt}` runner record. The panel shows the last 200 log lines and can expand all bounded output. A changed receipt/selection discards late responses. Agents have no log control or request.
155- Owner-only `PATCH /projects/:id/settings` sends only `{requireReceipt}` and reloads authoritative state. An absent setting is unavailable, never synthesized as On or Off.
156- Core currently does not return the canonical head in state. The shell says
157 “Head unavailable” when commits are recorded, or “No commits yet” for an empty
158 snapshot; it does not infer a current head from push history.
159- `GET /projects/:id/activity?after=:sequence&limit=100` follows the last receive
160 sequence in the recent snapshot. It deduplicates and displays the latest 100
161 available events; event timestamps do not determine ordering.
162- Approve and Promote send `{}` to `POST /projects/:id/reviews/:rid/approve` and
163 `POST /projects/:id/reviews/:rid/promote`. Promote reads `{sha}`. Every action
164 reloads state; writes are never automatically retried. A failed refresh clears
165 actionable old state. A `409` message such as `stale base, rebase` stays visible.
166
167The client hides decisions for a moved fork, stale review or packet blocker.
168With `requireReceipt: true`, approve requires a passing summary and digest;
169already-approved reviews may enter promote so core can freshly re-run checks,
170including after a setting flip. A failed before-promote run hides Promote until
171a fix; a timed-out or couldn't-run attempt keeps Promote with its retry caption.
172An approved Re-checking row has no gate caption. A required missing receipt shows
173Not started in its cell and Waiting for checks only under State. Its exact receipt-failure 409 keeps the dialog
174open with the new receipt available and a Warning icon. That handled conflict
175has no duplicate page banner or raw core error copy. With `requireReceipt: false`, all receipt
176outcomes are advisory and promote shows no re-check state. The server remains
177the authority. Unknown settings withhold actions until a valid snapshot arrives.
178All API/config text is escaped before entering markup.
179
180HTTP works without sockets. `createHttpTransport` accepts an optional host-supplied
181`socketUrl(project)` for an existing same-origin WebSocket route. There is no socket
182endpoint in FH-003/FH-005; this package does not claim one exists. Socket messages
183invalidate state, then the UI re-reads HTTP; writes never pass through the socket.
184The host should provide invalidation/reconnect behavior through its capability
185transport if it needs more than this optional socket. Unmount closes subscriptions.
186
187## Fixture acceptance
188
189The package tests mount the real view in jsdom and use fixture JSON at the HTTP
190boundary. They exercise Approve and Promote endpoints, duplicate clicks, stale
191conflicts, session expiry, project selection, empty views, escaping, exact fork and
192receipt gates, activity cursors and socket origin/cleanup. Browser acceptance uses
193`/fixtures.html` at phone and desktop widths against the loopback fixture API.
194The API deliberately returns the FH-005 wire shape, not a separate mock UI model.
195
196## Kumo workspace UI
197
198The same real Kumo components render the standalone dashboard and agent frame.
199Overview, Work, Review and Agents use underline tabs with hash selection. Activity
200is a collapsible panel and a Kumo modal drawer below 1024px. Packet details replace
201the feed without navigating. React retains focus during snapshot refreshes.
202
203The agent frame subscribes to the pinned host theme once, implements
204`GatekeeperAppThemeReceiver.setTheme`, and uses upstream `applyAccentColor`.
205The standalone copy follows system appearance. The reviewed host palette, font
206and radius definitions are in `src/host-tokens.json`; component CSS consumes
207semantic variables. Appearance never supplies authorization.
208
209Approval still binds the exact SHA and receipt digest. Promotion confirms the
210target branch and exact head in a Kumo Dialog, then the existing server gate runs
211again. Agent connections disable these actions and refuse them in the controller
212and transport. Ask an agent opens the host composer without sending the prompt.
213
214Contract gaps remain visible: no older-history cursor
215on the snapshot capability; no packet update time,
216or connected-agent identity. Those fields/actions are labeled
217unavailable or pending rather than synthesized. Activity preference storage is
218best-effort: an opaque sandbox may deny browser storage.
219
220## FH-033 offline receipts evidence
221
222The receipt fixtures (`receipts`, `advisory`, `recheck`, `rerun-failed`) implement
223FH-032's fixed wire contract and the 16:55 advisory ruling. `role=agent` exercises
224the shared read-only view; it does not simulate owner authority. Check copy and
225warning dots follow the receipts spec: Timed out and Couldn't run. Durations use
226supplied milliseconds, including the timed-out check's own duration. Receipt
227completion has a polite announcement; elapsed time changes never spin.
228
229Required handback remains fixture captures in both themes, narrow/phone frames,
230owner and agent details/settings, promote pending/409, advisory actions, empty,
231error/loading, focus, theme accent and first paint. The fetched base already emits
232the sidebar bundle after its loading caption at the end of body. FH-033 leaves
233that gatekeeper source unchanged and compares baseline/final first paint.
234Farrier design PASS, superprime code acceptance and the post-FH-032L live receipt
235capture are separate gates; this package's offline checks cannot establish them.
236
237## FH-036 receipt history
238
239Review shows the five newest public receipt summaries, including promoted reviews.
240Finished receipts sort by their finish time; running receipts sort by their start
241and show no invented finish time. Each row opens the existing receipt panel.
242Overview's Last promotion opens a receipt only when the ledger review ID, commit
243and receipt digest match its public summary. Missing or replaced summaries leave
244the promotion line without an outcome. Owner logs retain the existing Access
245session route; agents have no logs control.
246
247The `history-required` and `history-advisory` fixtures include shuffled promoted
248reviews, more than five receipts, both triggers and an advisory failure. These
249are offline fixture evidence; design review, owner acceptance and live checks
250remain separate.
251
252
253## FH-040 review detail (FH-039 contract integration pending)
254
255The queue opens a full-width detail at `#review/<rid>`; Back returns to the queue.
256Recent receipt rows still open the receipt panel. Checks, Approval and Main each
257have their own status and evidence control. The final milestone is Promoted to
258main. Promotion evidence comes only from this review's ledger entry.
259
260The optional host transport methods `getReview`, `getDiff` and `returnReview` follow
261FH-039's fixed contract. The HTTP transport uses the existing same-origin session
262for `GET /projects/:id/reviews/:rid`, `GET …/diff?path=…`, and owner-only
263`POST …/return` with `{reason}`. Reads and writes never carry browser tokens.
264A host without `getReview` keeps non-linking queue rows and resolves detail
265bookmarks back to the queue. It does not invent detail from packet claims or push history. FH-039 core integration and
266capability-host support must be checked separately after FH-039 lands.
267
268Diffs fetch only when expanded, accept at most 64 KiB, retain added/removed
269prefixes, and render as escaped plain text. Returned reasons retain line breaks
270for owners and agents. Return requires a visible labelled textarea with 1–2000
271characters and a counter. The controller refuses agent writes, stale/promoted/
272returned reviews and duplicate writes; an uncertain owner write is never retried.
273The dialog alone owns Return errors, including the promoted 409.
274
275`review-*` scenarios in `test/review-fixtures.ts` are labeled offline FH-039
276contract fixtures: current, approved, promoted, returned, stale, advisory failed,
277400-file changes (200 visible), detail loading/error/not-found, and return 409.
278These fixtures do not establish Cloudflare or owner acceptance. Dashboard tests
279exercise routes/back navigation, independent evidence, escaping, lazy diffs,
280return bounds, agent restrictions, owner conflicts and existing receipt gates.
281Farrier design PASS and the superprime's code review remain separate gates.
282Use `?scenario=review-current&host=queue-only#review/review-1` in the fixture entry
283to exercise the dashboard fallback with `getReview` and `getDiff` absent.
284
285
286## FH-045 stale causes and quick durations
287
288Review detail and queue derive the first proven state from the public snapshot:
289Promoted, Returned, Fork moved, Main moved, then Out of date. Returned reviews
290never show a stale banner. Fork moved requires a known fork head different from
291the review SHA. Main moved requires a ledger entry whose latest `after` differs
292from the review base, no `current-head` reconciler alert, and an explicitly null
293`promotionPending`. It applies even when core reports `stale: false`; Approve,
294Promote and Return are hidden. Out of date requires `stale: true`. If main movement
295cannot be proven and the review is non-stale, its normal state stays visible.
296Approval binding and server checks still govern owner writes.
297
298All receipt surfaces format durations below one second in whole milliseconds
299(for example `227 ms`); values from one second retain the existing format.
300Synthetic fixture projects include `review-main-moved` (the non-stale approved
301photo-finish loser), `review-main-moved-stale`, `review-out-of-date`, and stale and
302non-stale reconciler/pending-promotion variants. Open their detail at
303`#review/review-1` or queue at `#review`. These fixtures establish offline rendering
304only. Design PASS and independent code acceptance remain external handback gates.
305
306## FH-051 archive (FH-050 integrated; final acceptance pending)
307
308The owner sees Archive in the packet details header only when the public core
309fields allow it: no live standard claim or open race entrant, no raw open review
310(`promoted`, `stale`, `returned`), and no unresolved blocker on either its claiming
311or pushing packet. A derived Main moved or invalid-approval display state does
312not close a raw core review. Legacy absent archive/resolution fields remain
313unarchived/open. Agents see the archived list without write controls.
314
315Optional `archivePacket` and `unarchivePacket` transport capabilities use owner
316`POST /projects/:id/packets/:pid/archive` and `…/unarchive`, then reload the
317snapshot. The packet panel stays open and focus follows Archive/Unarchive.
318Conflicts re-fetch without retrying and show the panel's sanitized Warning line;
319a missing packet closes its panel. Archived packets leave Work, Review, Needs
320you and the Open packets count. The ghost Show archived toggle is local view
321state, resets on reload, and appends archived rows newest-first with neutral
322badges, full-contrast text and polite announcements.
323
324Recent receipts, promoted review bookmarks, Last promotion and the ledger retain
325all historical evidence. Activity remains Git-push-only: FH-050's private keyed
326archive audit is not exposed by `/state`, and this UI adds no feed events. The
327archived panel caption and badge tooltip read `archived.at` from the packet.
328
329`archive-*` scenarios are labeled offline fixtures checked against the merged FH-050/FH-052 public contract, including
330standard claim, race entrant, raw open review, non-stale Main moved, pushing
331blocker and archive 409/404/error. Package tests and local captures establish
332fixture behavior only. The normal merge retains FH-049 collision history and the
333accepted race, graph and token projections. Active race rows still open the track;
334Packet details opens the existing panel for archiving, and archived race rows
335open the panel directly for Unarchive. Reserved root/fresh-clone/D5 acceptance,
336Saddler's design PASS and Superprime's code review
337remain separate handback gates.
338## FH-049 collision resolution (FH-048 integrated; final acceptance pending)
339
340Owner-only `POST /projects/:id/blockers/:bid/resolve` sends `{reason}` (1–200 characters, with non-whitespace content) through the same-origin session. Agents have no control or request. The dialog shows paths and editable secondary quick-reason buttons in a wrapping row, supports Cmd/Ctrl+Enter, and refreshes authoritative state after success or uncertainty without retrying a write. A repeat uses the returned resolution cause. The resolution record supplies the audit history; no activity event is assumed.
341
342Resolved collisions remain as quiet rows in packet details and matching pushing-fork review details, excluded from counts, gates and Needs you. FH-048 review detail retains its claimant-packet filter; pushing-fork gates and history derive from all `/state.blockers`. Snapshot blockers survive an omitted detail list; a refreshed snapshot wins over stale detail resolution. Needs you opens and focuses the collision; success returns focus to Collisions and offers Open review in a 10-second toast and once on the newest resolved history row while the pushing packet has a current review. Legacy push times are never synthesized.
343
344The `collision-*` scenarios are local contract fixtures, including history, 404, generic failure and claim-release/promote races. They provide no live core or Cloudflare acceptance.
345
346## Recorded races (FH-055)
347
348Race packets open a read-only full-width page at `#race/<packet>` in either host.
349The track is a semantic table and becomes stacked lanes below 640px. It shows only
350recorded milestones in claim order (equal timestamps sort by agent): Claimed, First push, Review requested, Checks
351passed, Approved, Promoted. The shared race clock starts at the earliest claim;
352there is no running timer or estimated progress. Results use core's recorded places.
353
354The optional FH-052/053 projection carries `races`, agent enrollment `createdAt`,
355and `ledgerEntryId` (the exact string `ledger[].id`). Legacy agents are numbered
356alphabetically first, then enrolled agents by `createdAt`; response order never
357assigns identity. Fixture race, enrollment, ledger and review slices are checked
358against the core public types, including recorded `places` and quick receipts.
359The reviewed `race-silk-*`, `race-cloth-ink`, and
360`race-hurdle-ink` values live in `src/host-tokens.json`, with identity always carried
361by the number. Missing ledger evidence or activity counts are omitted.
362
363Results counts only default-branch pushes recorded on each entrant's fork from its
364claim through its closure, the race finish, or the current time. It shares
365the fork graph's packet interval filter. If the oldest loaded activity is later
366than any entrant's claim, Results omits Pushes rather than showing a partial count.
367`race-reused-forks` exercises historical fork totals of 20 and 3 with one push each
368inside the race; the Results counts are 1 and 1.
369
370The race view uses the accepted FH-057 `LaneRunner`, `LaneMarker`, `LaneConnector`
371and enrollment identity helpers from `src/lane.tsx`. Marker shapes and silk styling
372are shared with the fork graph; race-specific CSS only places them in the track.
373
374The offline fixture harness includes `race-open`, `race-finished`, `race-photo`,
375`race-empty`, `race-loading`, `race-error`, `race-unknown`, `race-standard`, and
376`race-advisory`. Open `fixtures.html?scenario=race-photo#race/race-01` for the recorded
377rejection panel, or `?scenario=race-open#work`, `#review`, and `#overview` for its
378Work badge, Promote warning, and Needs you copy. These snapshots are synthetic;
379they do not establish live race, deployment, or owner acceptance.
380
381### Forks on Overview (FH-057)
382
383The read-only hero projects `/state` into a clock-time graph above Needs you. It uses
384live claims, loaded push activity, reviews and their current receipt summaries, and
385ledger `recordedAt` times. Optional FH-052/053 races appear only when supplied.
386Dense events fold at the measured plot width; their tooltips and the always-accessible
387table retain the recorded facts. Below 640px only the table renders. The graph caps
388at eight lanes with in-place expansion, a 24-hour maximum default window, guarded
389viewer disclosure storage, and reduced-motion support.
390
391`src/lane.tsx` owns the shared primitives for FH-055 and FH-057:
392`LaneRunner({entrant, state})`, `laneSilkStyle(state, agent)`, `saddlecloth(state, agent)`,
393`Saddlecloth({identity})`, `LaneMarker({kind})`, `LaneLine({path})` (SVG), and
394`LaneConnector({reached})` (decorative race-table connector). Marker kinds include
395`unreached`, `reached`, `claim`, `push`, `review`, `passed`, `failed`, `approved`, and
396`promoted`. `agentIdentities(agents)` ranks legacy agents alphabetically before
397agents ordered by enrollment timestamp, with alphabetical tie-breaking. The number
398always accompanies the repeating `race-silk-1/2/3` colors. Unknown agents get no
399invented number. Both `--lane-silk` and FH-055's `--entrant-silk` are supported.
400
401Missing history stays missing: a promoted lane without its claim uses its earliest
402loaded event and an open stub, with no invented branch junction. Standard review
403approval timestamps and released standard claims are not exposed by the current
404core snapshot. Approval marks render only if a recorded `approvedAt` is supplied;
405the Approved state word remains available without a timestamp. Race release and
406approval times come from recorded entrant fields. Earlier push history is explicitly
407labeled when the loaded activity cannot cover a claim's start.
408
409Fixture scenarios: `fork-hero`, `fork-resolved`, `fork-old`, `fork-many`, `fork-empty`,
410`fork-dense`, and `fork-live` on `fixtures.html?scenario=…`. The live scenario has a
411**Fixture new push** control that appends a synthetic push and invalidates the
412snapshot through the normal read path. These routes and controls never ship in
413production assets. Captures prove fixture rendering only; reserved root tests,
414worker-host integration, fresh-clone/D5 checks, and design/code sign-off remain
415separate acceptance gates.
416
417### Fork graph bundle fixes (FH-057b)
418
419The shared graph carries its closed-table hiding styles in the component as well as
420in CSS, so an older host stylesheet cannot reveal the accessibility twin. Closed
421cell controls leave keyboard tab order; opening the disclosure restores them.
422Unloaded starts use a dashed lead-in with a recorded-start tooltip. Main SHA labels
423reserve space for the newest promotion first, then fit older labels into the remaining
424space. Every event cell shows its three newest events, with `+n earlier` exposing the
425omitted facts on focus or hover.
426
427For offline bundle verification, build the dashboard and gatekeeper sidebar assets,
428then open `gatekeeper-fixtures.html?bundle=owner&scenario=fork-hero` or
429`gatekeeper-fixtures.html?scenario=fork-hero` on the dashboard fixture server.
430The owner route uses the built production entry and CSS against the local fixture API;
431the gatekeeper route loads its built self-contained sidebar in an opaque-origin frame
432with a synthetic host capability. Light/dark controls use the app theme path.
433`fork-history` exercises dense main labels and capped cells. These harnesses are excluded
434from production assets and prove offline bundle behavior only.
435
436## Reported usage and race Compare (FH-062 local candidate)
437
438The optional FH-060 wire projection keeps reported and metered cumulative snapshots
439separate on claims, race entrants, and `usageAtReview`. Every token figure carries
440its source. Full figures show report freshness on a separate caption line; dense
441queue/graph cells show a compact figure with freshness and full details in the Tooltip. Review detail uses the immutable review snapshot;
442promotion gates use the current entrant's core-computed budget state. Over-budget
443and unchecked budgeted entrants have no Promote control. Unbudgeted legacy races
444and ordinary packets need no usage report. An explicit core `budget: "unchecked"`
445on a race with `tokenBudget: null` does not show Budget unchecked or hide Promote.
446There is no new Needs you kind.
447
448The accepted FH-055 race view includes Tokens before Result in its milestone track
449and results table, with a facts-only Compare section directly below the track.
450Compare uses FH-055 claim order and its shared recorded race clock. Checks
451count passing receipts out of the entrant's receipts in both Compare and Results.
452Files come from exact review detail, and push counts from
453the shared FH-055b `packetPushes` interval helper. Results omits its Pushes column
454and Compare omits its Recorded pushes row when loaded history does not cover every
455entrant's claim. Counts end at entrant close, race finish, or now. No score, inferred line count,
456price, or automatic ranking is computed. Missing detail is Unavailable. The queue's
457Tokens column appears only when at least one queued review has a snapshot. Fork
458usage appears only in label tooltips and the table twin, never lane geometry.
459Budget figures appear once per source as a ratio; only core's selected source gets
460the true-ratio meter and state. Within budget has no dot. Full ties receive no
461fastest/fewest caption. Below 640px Compare stacks by fact and entrant, with no
462sideways scrolling. Usage refusals appear only in queue Actions. Compact usage
463cells and folded metadata keep the queue and graph table within a docked pane.
464
465Agents totals select the latest snapshot per packet/fork/source, across live claims,
466entrants, and retained reviews, before summing. The local-day window selects reports
467received today. A cumulative report can include earlier consumption, so the UI
468explicitly describes these as cumulative reports received today, not consumption
469today. Daily deltas and distinct reclaimed-claim lifetimes need more core history.
470
471The fixture harness provides `usage-race`, `usage-over`, `usage-unchecked`,
472`usage-both`, `usage-legacy`, `usage-unbudgeted`, and `usage-reordered`, with `role=agent` for the read-only host. Fixture
473checks and browser captures are synthetic. See
474[test/captures/fh-062/README.md](test/captures/fh-062/README.md).
475
476Accepted FH-055 main is normally merged at `3d87cdca649740984524882a00408cb9780ae327`
477(second parent `ea577a1d96fc52528bd2854d58ee2a29d9ccf679`). Its glyphs, claim order,
478neutral unreached rings, centered hurdles, badges and silks remain in the shared track.
479Accepted FH-055b main `f924b7a40fa94bc457677dfa3898c940b1676e37` is normally
480merged at `97cf0650f6560a0d5d814a85c98d27cc6a342d53`, with parents
481`90cabe62afb723d0645e62d3c97976e3f8030fc5` and that accepted main commit.
482Accepted FH-057b main `c3dec6e43994b49007b6ad06ab55d13c61607955` is normally
483merged onto the local FH-062 head `edb9903c9829a4871b0814fc5bb978a2395ed1d4`.
484Its table clipping, focus behavior, compact visible event cells, and full accessible
485event labels compose with the usage tooltip and Tokens column.
486Accepted FH-057c main `05efc0a346a7e91d4f4e56b52eb4266f12475f5b` is normally
487merged onto the saved local FH-062 head `594b51b5d4d914b51dac2e0cc01b30f92e8da57b`.
488The graph omits hour labels without room at plot edges and retains every hour grid line.
489Accepted FH-057d main `1749a394caa4f78b7fbf6a1645659e89e78e9b99` is normally
490merged onto saved FH-062 head `761b777bf2c170fe0b6e6674d330458d157d2111`.
491Failed checks retain danger dots, timed-out/infrastructure receipts warning dots,
492and review/running receipts info dots, alongside the usage tooltip and Tokens column.
493Accepted FH-060 main `f1910e6f571ed7359d30db37d3ddca86f9236803`, including
494FH-049 collision UI and FH-050 archive contract, is normally merged onto saved
495FH-062 head `d8befb64e9232c67a78044447aa8ec81445555c8`. Collision gates retain
496owner/agent explanations; resolving a collision leaves the current entrant budget
497gate in force. All eight design fixes remain. Full root tests/lint, fresh-clone/D5 and host workerd acceptance are
498serialized operator checks. Saddler PASS and owner review remain open. This local
499candidate has no live Cloudflare, deployment, or owner-acceptance evidence.
500
501## First-run guidance
502
503Empty owner views show a placeholder owner CLI command using the existing
504clipboard control. Copying it performs no API request. The CLI reads the owner
505credential from the owner's private environment; the dashboard never reads,
506stores or inserts that credential or actual endpoint/config values into commands.
507Agent hosts show the same guidance without commands or Copy controls. Empty
508reviews offer owners a Go to Work button using existing tab navigation.
509
510Overview shows one empty state: enroll agents, create packets, then wait for an
511agent to claim work. Existing forks, promotion history and race entries preserve
512the graph even when no active claim remains. Archive-only Work and reviews with
513retained history keep their existing empty-queue behavior. Race packets remain in
514Work. The additive Races tab lists all recorded races and has its own first-run
515guidance. Missing race bookmarks and real races waiting for entrants retain their
516existing messages.
517
518The labeled loopback scenarios are `empty-projects`, `empty-agents`,
519`empty-packets`, `empty-forks`, `empty-reviews` and `empty-races`. Select tabs with
520`#overview`, `#work`, `#agents` or `#review`; `role=agent` exercises hidden owner
521commands. `empty-projects` supplies no configured projects and makes no project
522read. These are synthetic fixtures, not evidence from a new deployed instance.
523
524## Races tab (FH-081)
525
526The last tab, `#races`, reads the existing state snapshot without new requests or
527owner write controls. Its badge counts open races with entrants. Rows retain lane
528order and enrollment identities, separate the judge's advice from the winner,
529and link to the existing race view. Opening from this list changes that view's
530back link to “← Races”; opening from Work retains “← Work”. Judge and start metadata
531fold beneath the title at viewport widths up to 899px or pane widths up to 819px.
532
533`fixtures.html?scenario=races-list#races` supplies labeled synthetic open,
534zero-entrant, finished, photo-finish, archived, judged and awaiting-advice races.
535`scenario=races-empty` covers first-run guidance; `role=agent` hides the owner CLI
536command. Additive fixture tests cover ordering, separate winner/advice semantics,
537tab naming, read-only requests, refreshes and return navigation. Browser captures
538and scroll measurements at 1440/1024/900/800, dashboard tests/types/lint/build,
539fresh-clone review and Saddler approval are operator acceptance gates.
540