Pilot 4 · SaaS · Incident response on an unattended field network

RelayGrid

A console that never says more than it was told.

Six views, three edge states and thirty-five public states, built as a running Ark UI application rather than a picture of one. It is a duty desk for relay cabinets nobody visits: a process notices that frames stopped arriving, a coordinator takes the incident, four decisions are committed, a crew is dispatched, a handheld goes quiet, and a record is closed with a sentence that cannot be edited afterwards. This page reads down the morning that shift actually happened in, entry by entry, on the record's own clock.

Role
Product design and front-end build
Surface
SaaS · 1440 × 900 primary, with 1280 × 800 and 768 × 1024
Component system
Ark UI 5.37.2 on Zag.js 1.41.2 — 8 component namespaces across 6 import sites in src/
Slice
6 views · 3 edge states · 35 public states · 14 QA‑only
Measured
55 renders · 186 Playwright tests · 49 Storybook tests · 39 axe runs
Accepted
Gate C round 1 failed on composition at 81; one upstream repair spent; round 2 passed at 86
07:38:04 +00:00 · Tue 17 Mar ingest.watcher

Nobody reported this. A process noticed it had stopped hearing.

RG-4417Cabinet RC-118 stopped reporting — was opened at 07:38:04 +00:00 by a process called ingest.watcher, twelve seconds after the last frame arrived. Ilva Rethen, the duty coordinator, finds it on her board two hours later. Nobody phoned. Nobody filed anything. The only witness is a silence in a telemetry stream.

Every name, site, asset, timestamp and coordinate in this project is invented. Positions are stated in a fictional local projected grid rather than as latitude and longitude, so the plane depicts no real place and no coordinate in it resolves to one. No employer is named anywhere in the product, and no service level, compliance claim, certification or attainment figure exists in any copy slot.

The whole product is one rule, and the rule is typographic

A console like this mixes two kinds of statement constantly: things a machine measured, and things a person or a heuristic concluded. Confusing them is how an operations tool becomes dangerous — a suggested severity read as a fact, a projected arrival read as an observation. RelayGrid separates them by face rather than by badge.

IBM Plex Mono carries every identifier, every measurement with its unit, every absolute timestamp and every coordinate. IBM Plex Sans carries every sentence a person wrote.

Build pack · TASK_FLOW.md — visual grammar

So link.state down, frames_missed 134 and 11:04:37 +00:00 look like readings, and Fitting the spare fan and re-terminating the two greened cores looks like a person talking, and you can tell which is which across the room without reading the label. Nothing derived is ever typeset like something measured. This page is set by the same rule.

What the console refuses to be

Five negatives were fixed before any view was drawn, and each one is a shape the interface had to be built into rather than a thing removed at the end.

  • Not a KPI dashboardNo tile, no trend, no delta, no sparkline, no percentage and no chart of any kind appears in any of the six views. A runtime guardrail counts canvas, [class*=chart], [class*=kpi], [class*=sparkline] on every view and requires 0.
  • Not a map pageThe plane is a pane and never a view. Every view that carries a plane also carries a list reading of the same layer, so no record in the product is reachable only by clicking a mark.
  • Not a card gridThe queue is rows on 1 px rules. So is the crew list, the checklist, the telemetry ledger and the stream. There is no card grid anywhere in the slice — and none on this page either.
  • Not a terminalThe machine-captured cause block is monospaced because it was captured, not to look like a console. No scan line, no cursor block, no phosphor, no glow, and 0 gradient tokens in the whole project.
  • Not an inference engineAnything the system derived — a proposed severity, a suspected duplicate — is set in the sans face, labelled as a proposal, and ends at a human control that can decline it.
09:46:00 +00:00 · Incident Board Ilva Rethen

Nine open, one proposed, and the difference is drawn

The board is a fixed 320 px incident queue against a 1032 × 836 geographic plane, with an 88 px rail of operational nouns to the left of both. Nine sites carry an open incident this morning and nine marks are drawn for them.

The header does not say ten incidents. It says what is true:

9 open · 1 proposed severity not yet committed

Proposed severities are not filtered.

Product copy · src/data/fiction.ts — board header and filter note

Those two lines carry the same ontological split the type system carries. A severity that ingest.watcher proposed is not the incident's severity yet, so RG-4417's chip reads S1 PROPOSED and is drawn with a dashed border and no fill, while RG-4386's committed S1 is solid and filled. One CSS rule (.chip--proposed) is the whole distinction, and the filter note exists because a coordinator who filters to S1 would otherwise be told a lie of omission about the row that has not been triaged yet.

Four navigation registers, derived from how much the view commits you to

The rail never changes. What changes is what sits beside it, and the register is chosen by the level of commitment the view asks for.

Rail plus panes

Two panes of unequal function side by side. The queue and the stream are the mass; the plane is the second pane.

1 · Incident Board  ·  5 · Live Response

Rail, object header, panes

A breadcrumb row, then an object header carrying identity, severity, the response target and the action set, then the panes.

2 · Incident Detail  ·  4 · Dispatch  ·  6 · Close Incident

Rail plus a centred decision column

One 704 px decision column and one 320 px summary rail, with 140 px of ground each side. No pane, no plane, no queue.

3 · Triage Wizard

Modal over a dimmed parent

Exactly two dialogs exist in the whole slice, and both guard an act that cannot be taken back: creating a record that may duplicate another, and closing an incident.

the log dialog  ·  the completion dialog

The queue is not the rail, and is separated from it on four axes

A 320 px list placed beside a rail gets read as navigation. So the rail is 88 px against the queue's 320; the rail's surface is #0B0D0E, one step below the console ground, and the queue's is #191D20, one step above it; rail items are a glyph over a label while queue rows are two lines of text with a status mark and measured columns; and the rail is divided from the queue by a rule-pane while queue rows are divided from each other by a hairline.

EX-01 Incident Board — the workspace at rest board-map-default · 1440 × 900 · capture 1 of 55
RelayGrid's Incident Board on a near-black ground. An 88-pixel rail on the left carries Incidents with a badge of 9, Response and Dispatch. Next to it a 320-pixel queue lists nine incidents, each with a status dot, an identifier such as RG-4417, a title, a site and asset line and an age in the right-hand column; the first row carries a dashed orange S1 PROPOSED chip and the second a solid filled S1. To the right, a geographic plane divided into four quadrants by dashed rules carries nine circular site marks, each with a monospaced label plate beside it, a docked PLANE KEY legend of eight keys at the lower left and a 5 km scale bar at the lower right.
  1. Queue · 320 pxNine rows on hairlines. The status dot, the identifier, the title, the site and asset, and the age — five facts per row, and the age is the only one the machine is still changing.
  2. Severity chipS1 PROPOSED dashed and unfilled against S1 solid and filled. The difference between a proposal and a decision is visible from across the room.
  3. Plane · 1032 × 836Nine site marks, nine label plates, four zone designators and a boundary cross. No mark on any plane in this product is unlabelled, and a Playwright test walks four views to prove it.
  4. LegendDocked, not summoned. Eight keys covering the six mark treatments, the zone boundary and the crew route — so a reader never has to learn the plane from a tooltip.
  5. Map / ListThe same layer, two readings. The list reading is a six-column table of the same nine sites, which is what keeps the plane from being the only route to a record.
  6. No filled controlThis view has none, by decision. Nothing on the entry surface of the product is asking to be pressed.

Laid straight onto the page: the capture's ground and this page's ground are the same #111315, so the screen has no edge to draw and needs no device frame. Captured at device scale 2 by the state audit, and used here byte-identical.

09:47:41 +00:00 · Edge 3 Ilva Rethen

The second report of the same cabinet

Someone phones in the fault Ilva is already looking at. She logs it, enters asset RC-118, and an open incident already names that asset. The dialog does not close and does not throw the entry away. It states the operation in the direction it will happen:

POSSIBLE DUPLICATE

An open incident already names asset RC-118. Merging writes RG-4423 into RG-4417 and leaves one record.

Product copy · board-log-duplicate

Both records are stacked with their own identifiers, opening times and titles — the one being created at 09:47:10 +00:00 by Ilva Rethen, the one already open at 07:38:04 +00:00 by ingest.watcher — and the exact sentence that will be written into the survivor is shown in an editable field before anything is committed:

THIS SENTENCE IS WRITTEN INTO RG-4417

RG-4423 was merged into RG-4417 by Ilva Rethen at 09:47:41 +00:00. Both reports name asset RC-118 at NW-12 North Bench.

Product copy · src/data/fiction.ts — merge sentence

No control in this dialog is filled, because neither way forward is the confident one. And declining still records the relationship: Keep both creates RG-4423 carrying a cross-reference chip to RG-4417, so the fact that two people saw the same cabinet survives the decision not to merge.

EX-02 Edge 3 — the duplicate notice, inside the dialog that caused it board-log-duplicate · 1440 × 900 · edge state 3
The Incident Board dimmed behind a modal dialog. At the top of the dialog an orange-tinted band reads POSSIBLE DUPLICATE, with the sentence: an open incident already names asset RC-118, merging writes RG-4423 into RG-4417 and leaves one record. Below it two panels side by side hold the record being created and the open record, each with its identifier, opening time, actor and description. Beneath them a label reading THIS SENTENCE IS WRITTEN INTO RG-4417 sits over an editable field containing the merge sentence, and a row of three unfilled controls reads Cancel, Merge into RG-4417 and Keep both.
  1. Direction, firstThe notice names which record survives and which is folded into it, before either control is offered. A merge that does not say which way it runs is a coin toss with a button.
  2. Both records, stackedIdentifier, opening stamp, actor and description for each — enough to decide, without leaving the dialog to go and look.
  3. The sentence, editableWhat will be written into the history is shown as text a person can change, not as a consequence they discover afterwards.
  4. No filled controlMerge into RG-4417 and Keep both are siblings of equal weight. The product has no opinion about which is right, and does not pretend to.
09:48:22 +00:00 · Incident Detail Ilva Rethen

Ownership is a stamp, not an avatar

On an unowned incident, Start triage does not exist. Not greyed, not hidden behind a tooltip — the control is not in the view, because acting on an incident nobody has taken is not a thing the product supports. Activating Take incident stamps the header Held by Ilva Rethen · 09:48:22 +00:00 and Start triage appears, filled.

The whole path is gated that way: ownership gates triage, triage gates dispatch, dispatch gates the live view, and a complete evidence checklist gates the close. Every gate is an actor and an absolute time written into the record, never an inference from an avatar being present.

An absent reading is named, never zeroed

The telemetry ledger on this view has a row with nothing in it. It does not print 0, and it does not print a dash:

Relative humidity  ·  No reading since 04:12:00 +00:00

Product copy · src/data/detail.ts — telemetry ledger

A dash is ambiguous between zero, not measured and not applicable, and on a sensor network those are three completely different mornings. The row keeps its unit column empty and its stamp column full, which is exactly as much as the product knows.

Six departures, one decided trigger each

Every transition in the slice advances on a single named control the product actually exposes. Nothing advances on a timer, a guess or a test hook — which is why the same list drives the end-to-end suite, and why a green run means a coordinator can complete the arc rather than that a script can.

  1. T1

    Incident Boardto Incident Detail

    The user activates the queue row whose identifier is RG-4417. The whole 320 × 72 row is the control, and the plane's viewport centre and zoom are carried across unchanged, so the site the user was looking at is the site the detail opens on.

  2. T2

    Incident Detailto Triage Wizard

    The incident is owned, so Start triage is present and filled; the user activates it. On an unowned incident this control does not exist.

  3. T3

    Triage Wizardto Dispatch

    All four decision cards carry an answer, so Continue to dispatch is enabled. The four answers are written into the record and become the scope the crew list is filtered by.

  4. T4

    Dispatchto Live Response

    The assignment is complete — two crew chips, one window, three equipment lines — so Send dispatch is enabled. Live Response opens with the dispatch already written into the stream as its newest entry.

  5. T5

    Live Responseto Close Incident

    All four evidence checklist items are captured, so Close incident is enabled in the object header.

  6. T6

    Close Incidentto Incident Board

    The user activates Close incident in the summary column, the completion dialog opens, and the user activates Confirm and close. The board re-opens with RG-4417 gone from the queue and its mark drawn in the cleared-today treatment. The build implements this leg only as far as the dialog. The shell keeps no state across a view change, so confirming returns the board exactly as it opened — the sentence above is the task flow's, and it is the one place in this diagram where the spec and the build disagree.

  7. Incidents in the breadcrumb of views 2 and 5 is not a flow step; it returns to view 1 as a standing surface. Views 3, 4 and 6 carry Exit triage, Discard assignment and Back to response instead, because leaving a half-made decision has to say what happens to the decision.

Three departures from the happy path, on three different views

No single view carries the product's whole failure surface, and no failure takes a screen. Each edge has a primary exit and a decline, and neither exit removes the thing that failed.

  • Edge 1 · on 4 · Dispatch

    One crew handheld does not acknowledge

    The crew list keeps every row in place. The row that failed is tinted across its full width, keeps its identifier and both of its measured columns, and gains a status cell reading NOT ACKNOWLEDGED. The row above it is untouched.

    Exit
    Retry dispatch — the second attempt is acknowledged at 10:06:52 +00:00, and the stream carries both attempts as separate entries.
    Decline
    Assign a different crew — Petra Klaas takes the place. The failed row stays in the list, because a technician who could not be reached is a fact about the morning.
  • Edge 2 · on 5 · Live Response

    A handheld stops reporting for longer than its interval

    Presence is rewritten as a measurement rather than as a mood: the lane stops open-ended at its last frame, the mark becomes a dashed square holding the technician's initials, and a system entry records the transition with an absolute stamp. The other technician's lane and mark are untouched.

    Exit
    The handheld reports again. The gap stays in the stream as a closed span with both stamps, so the record still says the reporting stopped.
    Decline
    The mark is opened. The popover names her, gives Last fix 11:04:37 +00:00, prints the grid position last and dimmest, and offers no action that pretends to reach her.
  • Edge 3 · on 1 · Incident Board

    A second record is logged against an asset already open

    The dialog does not close and does not throw the entry away. The operation is stated in its direction, both records are stacked, and the sentence that will be written into the survivor is editable before anything happens.

    Exit
    Merge into RG-4417 — one record where the user was about to create two, and the merge sentence in its stream.
    Decline
    Keep bothRG-4423 survives carrying a cross-reference, so the relationship is recorded even when the merge is declined.

One disagreement inside the build pack, recorded rather than reconciled

STATE_INVENTORY.md row 14 names the hand-over stamp as 09:52:14 +00:00. COPY_DECK.md §2.2 and the build both stamp it 09:48:22 +00:00. The copy deck is the copy authority, so the build is right and the inventory row is the outlier — written into the audit's capture notes rather than quietly corrected in one of the two documents.

2 of 4 decisions · Triage Wizard Ilva Rethen

Four decisions, and none of them is thrown away

Triage is the only view in the product with no pane, no plane and no queue: one 704 px decision column, one 320 px summary rail, and 140 px of ground on each side. Four accordion cards, one open at a time, each collapsed card carrying a dot-separated preview of the options it holds.

The counter in the summary rail reads 2 of 4. It counts decisions inside one view — it is not a flow-step list, and a guardrail test asserts that no landmark anywhere in the product names the views as steps. The test checks five of the six view names, not six. Dispatch is left out because the navigation rail's third destination carries that exact word on every view, so the token cannot tell a step list from a nav item: adding it to the list fails all six runtime checks, on the rail alone. The five that do discriminate — Incident Board, Incident Detail, Triage Wizard, Live Response, Close Incident — return 0 hits on all six views at all three viewports.

The consequence attaches to the option, not to a banner

Selecting S1 does not raise a warning strip at the top of the screen. The sentence describing what S1 does appears under S1, in the card, where the decision is being made:

S1 — the site is off the network

A response target of two hours is offered by default and the on-call lead is told at the same moment as the crew.

Product copy · triage-step2-consequence

And the card's footer says exactly where the proposal came from and what the user is doing to it — which is the ontological split from 07:38:04, restated at the one moment it becomes irreversible:

ingest.watcher proposed S1. Committing it here is what makes it the incident's severity.

Product copy · src/data/triage.ts — decision 2 footer

The wizard refuses to punish revision

Two sentences do all of the work that a "you have unsaved changes" dialog usually does badly. The first sits in the summary rail from the first decision onward; the second appears only when a committed decision is re-opened after later ones were answered:

Every answer is kept as you make it. Leaving does not throw it away.

Decisions 3 and 4 keep the answers you already gave. Changing severity does not clear them.

Product copy · src/data/triage.ts — autosave and re-open

Re-opening decision 2 after decision 4 is a public state in its own right (triage-step2-reopened): the later answers stay, dimmed, and the tracker returns to 2 of 4. Backtracking never erases an answer the user already gave.

The one deliberate rename, and why it is not a style preference

The composition spec asks for "severity and SLA in header". The product paints RESPONSE TARGET in that slot and never the letters S, L and A. An SLA is a commitment made to somebody outside the team, and inventing one would be inventing a guarantee a self-initiated concept has no standing to make. The slot keeps its function exactly — a clock bound to the severity, set at triage, counting down in the object header of every incident view — and the wording states what it actually is: a target this desk set for itself. There is no attainment percentage, no breach count and no compliance figure anywhere in the product.

One place the build does not match the inventory, and says so

STATE_INVENTORY.md's reach table says triage-step2-reopened is reached by a decision-2 card Change control. The build has no such control: a committed card is re-opened by activating the card itself, which is the Ark UI accordion trigger carrying 2 Set severity and the committed answer. Recorded in the audit's capture notes rather than smoothed over.

EX-03 Triage Wizard — decision 2, with the consequence on the option triage-step2-consequence · 1440 × 900
The Triage Wizard on a near-black ground. A centred decision column holds four cards: card 1, Confirm the fault, collapsed and showing its committed answer Confirmed by telemetry; card 2, Set severity, open with the question How severe is this and three radio options — S1 the site is off the network, selected, with a consequence sentence beneath it, then S2 the site is degraded and S3 the site is reporting with a fault; and cards 3 and 4 collapsed, each showing a dot-separated preview of its options. A summary rail on the right lists the four decisions with a 2 of 4 counter, an ANSWERED SO FAR block holding the two committed answers, a sentence saying every answer is kept as you make it, and a filled orange Next decision button.
  1. One card openArk UI's accordion. The three closed cards each print their options as a dot-separated preview, so the shape of the whole decision is legible without opening anything.
  2. Consequence on the optionThe two-hour target and the on-call notification are stated under the option that causes them — never as a banner at the top of the view.
  3. Summary railProgress, current work and remaining work as three distinct treatments: a tick, a highlighted row, and a numbered stub.
  4. One filled controlNext decision in orange is the only filled control on the surface. The rest of the view is outlined or plain.
10:04:31 +00:00 · Edge 1 HH-211 · last handshake

One handheld did not answer

The dispatch goes to two technicians. Tobin Farr's handheld acknowledges it. Mira Osgood's does not. This is the failure the happy path is deliberately routed through, because a console whose demonstration never loses anything has not been demonstrated.

The assignment column does not say dispatch failed. It says what was observed, and then draws the conclusion the observation supports and no more:

ONE HANDHELD DID NOT ANSWER

Mira Osgood's handheld did not acknowledge the dispatch. Its last handshake was at 10:04:31 +00:00, which is after the dispatch was sent, so the handheld is reachable and the dispatch is not confirmed.

Tobin Farr's copy was acknowledged. He is already routed.

Product copy · dispatch-failed

Reachable and not confirmed are two different facts and the sentence keeps them apart, because the operator's next move depends on which one is true. The recovery is scoped to the one technician who did not answer, and it is offered twice — Retry dispatch, and Assign a different crew — neither of which removes the failed row from the list.

A refusal names its condition

The crew list also holds a row that cannot be assigned. It is not hidden and not greyed into silence: it keeps its position, keeps its measured columns, and carries the specific reason.

Dane Whitlow  ·  On-call lead  ·  9.8 km

Holding the on-call rota until 18:00:00 +00:00. Assigning him would leave the rota uncovered.

Product copy · src/data/dispatch.ts — crew list

Note what that sentence does not do. It does not say unavailable, and it does not say you do not have permission. It names the condition, gives the time it ends, and states the consequence of overriding it — which is the only version of a refusal an experienced operator can argue with.

EX-04 Edge 1 — the failure that does not evict the row that failed dispatch-failed · 1440 × 900 · edge state 1
RelayGrid's Dispatch view. On the left a crew list headed CREW with a line reading four technicians inside zone NW, three can be assigned; four rows follow, two of them selected with a cyan leading rule, and the second row, Mira Osgood, carries a red-tinted band and a NOT ACKNOWLEDGED status cell while keeping its distance and last-handshake columns. A fourth row, Dane Whitlow, is dimmed and carries a two-line reason about holding the on-call rota. Beneath the list an EQUIPMENT block shows five rows with three checked. In the centre a route plane shows two crew marks and a site mark joined by a dashed cyan route. On the right an Assignment column lists the crew, the window, the equipment, then a block headed ONE HANDHELD DID NOT ANSWER with two paragraphs and the controls Retry dispatch and Assign a different crew.
  1. The failed rowTinted across its full width, and still carrying its identifier, its distance and its last handshake. Everything that was true about it before is still true.
  2. The row aboveUntouched. One handheld not answering is not an outage, and the view does not treat it as one.
  3. Disabled with a reasonDane Whitlow keeps his place in the list and in the focus order, and the condition is rendered as visible text rather than as a tooltip nobody opens.
  4. Two scoped exitsBoth offered, neither filled, and both keeping the failed row on the record.
  5. Crew marks are squares, site marks are circlesThe two never share a shape, so the plane can never confuse who is where with what is where. SCREEN_SPEC.md and TOKENS.json go further and call the site mark "the only circle in the product … no control, no chip, no avatar, no panel and no card is circular" — and the build does not honour that clause. Six more circles are in app.css, among them the queue's 8 px severity dot, the plane key's 20 px swatch and the triage wizard's 20 px radio, which is the circular control the policy names. The shape rule that holds is the one above; the exclusivity is the spec over-reaching, and it is carried into the standing list at the end.
  6. Assignment columnOnly as tall as what has been committed. Its emptiness shrinks as the operator completes the task, which is the honest reading of the state — and the audit records that emptiness rather than padding it.
11:04:37 +00:00 · Edge 2 ingest.watcher

The sentence where the product stops

Mira Osgood's handheld stops reporting on site. Everything a lesser console would do here is a guess: last known location, estimated position, a status of offline that reads like a statement about the person rather than about the radio.

RelayGrid changes exactly three things and then stops. Her lane in the crew strip ends open-ended at 11:04:37 +00:00 rather than continuing. Her mark on the plane becomes a dashed square. A stream entry attributed to ingest.watcher records the transition with its absolute stamp. Her colleague's lane and mark are untouched.

Mira Osgood's handheld stopped reporting. Last frame 11:04:37 +00:00.

Product copy · live-technician-offline — stream entry

Opening her mark gives four rows — role, dispatch, handheld, last fix — then the grid position, printed last and dimmest, and then the sentence that is the best thing in the product:

This is where the handheld last reported. RelayGrid does not know where she is now.

Product copy · src/data/live.ts — offline mark popover

The popover offers no action. There is no ping device, no request location, no mark as safe — because none of those would do anything and offering them would imply they might. The coordinate is demoted below the identity for the same reason: it is the least reliable thing on the card and it is typeset like it.

The clock is the product's, not the test's

This state is reached by running the frozen clock forward through thirteen minutes of the product's own once-a-second interval, so the reporting interval genuinely elapses before the transition renders. Nothing is stubbed into place and no state is seeded. The one thing the harness does control is what the product is told by a handheld stub — never what it renders.

EX-05 Edge 2 — a lane that stops, and a plane that claims nothing after it live-technician-offline · 1440 × 900 · edge state 2
RelayGrid's Live Response view. An object header carries the incident identifier, its title, an S1 severity chip, an elapsed clock reading 03:26:56 and a response target. Below it a strip headed CREW AGAINST THE CLOCK shows an hour scale from 09:00 to 13:00 with a labelled cyan NOW rule, and two crew lanes: Tobin Farr's filled cyan up to the NOW rule, and Mira Osgood's, tagged NOT REPORTING, stopping short of it in grey. Beneath the strip a composer reads Add a note to this incident with Attach a capture and Post controls, then a line reading three of four evidence items are captured, then a dated stream: a system entry about the handheld stopping, a bordered human entry from Tobin Farr with a photograph of a relay cabinet interior and its file name, size and stamp, and a state-change entry showing Dispatched struck through and replaced by On site. A plane on the right shows the site mark, a dashed cyan route down to a crew square marked TF, and a dashed grey square marked MO.
  1. The lane stopsOpen-ended, not faded out and not continued in a lighter colour. The strip is a measurement of contact, and contact ended.
  2. The other laneRuns to NOW untouched. One radio going quiet says nothing about the other.
  3. Dashed squareThe same dashed grammar the plane uses for no reading, and the legend carries a key for it. The mark keeps her initials.
  4. System entryAttributed to ingest.watcher by name. No system action in this product is anonymous.
  5. Human entryBordered, with the technician's name and a real photograph as evidence — 2.1 MB · 10:46:51 +00:00 · HH-204. System entries are one line behind a dot; the difference is structural.
  6. State change as a diffDispatched struck through, On site beside it. The old value stays readable, because a record that overwrites itself is not a record.
11:30:41 +00:00 · Close Incident Ilva Rethen

Written into the record, and not editable after

The link comes back on its own at 11:30:41 +00:00. Four evidence items are captured — a fault photograph, a repair photograph, three lines of parts used, and a confirmed link state. Close incident stops being disabled.

The completion dialog is the second and last modal in the product, and it does three things in order. It states what closing means in the product's own terms. It states what happens to the work that is not finished. And then it shows the literal sentence that will be written into the record, in a field, for editing:

You are about to close RG-4417. It leaves the board and its record stops accepting stream entries.

Two open actions become follow-ups and stay on RC-118.

THIS SENTENCE IS WRITTEN INTO THE RECORD

Cabinet RC-118 stopped reporting after the door seal perished and the fan filter packed. The cabinet fan was replaced and two corroded cores were re-terminated. The link reported its first frame at 11:30:41 +00:00. A door seal is outstanding and has been logged as a follow-up.

Edit it before you close. It cannot be edited afterwards.

Product copy · close-confirm-dialog

That last line is the whole argument for the dialog existing. A confirmation that says are you sure? is asking the user to re-check a decision they already made. A confirmation that shows the exact text it is about to make permanent is giving them something to actually do.

The unfinished work is not silently dropped either. Two open actions — order a door seal for RC-118 and fit it at the next planned visit and replace the spare cabinet fan in van stock — are named in the summary column and the dialog states that they become follow-ups against the asset. Closing the incident does not close the loop, and the product does not pretend it does.

The one content inconsistency the audit found and did not paper over

STATE_INVENTORY.md's reach table sends close-checklist-incomplete through Live Response's Close incident, but the screen spec and the task flow both require that control to be aria-disabled until all four items are captured, so it cannot navigate from there. The build follows the spec and the Close view opens on its incomplete checklist. That leaves a judgement for a human rather than a detector: Live Response says four of four are captured one screen earlier, and the Close view then counts three. Written into the audit's capture notes at the state it affects.

EX-06 Close Incident — the sentence, in an editable field, before it is permanent close-confirm-dialog · 1440 × 900
The Close Incident view dimmed behind a modal dialog headed Close RG-4417. The dialog states that the incident will leave the board and its record will stop accepting stream entries, that two open actions become follow-ups and stay on RC-118, then under a label reading THIS SENTENCE IS WRITTEN INTO THE RECORD an editable field holds a four-sentence completion summary, followed by the line Edit it before you close, it cannot be edited afterwards, and the controls Cancel and Confirm and close. Behind the dialog an EVIDENCE checklist shows four captured items with their file names and stamps, and a Record column lists the incident's identity, actors and times.
  1. Consequence, in the product's termsNot "this cannot be undone" but what specifically stops: it leaves the board, and the record stops accepting stream entries.
  2. The unfinished workNamed, counted and given a destination. Two open actions become follow-ups against the asset.
  3. The sentenceShown as editable text, not as a summary generated after the fact. What the record will say is a thing the operator can still change.
  4. No destructive defaultCancel and Confirm and close, with the destructive one in the alert hue and separated by position rather than by colour alone.
09:46 → 11:33 +00:00 · one take Playwright

The same morning again, walked once, without a cut

Everything above is a frame held still. This is the morning at its own pace: 27.08 seconds, one take, no cuts and no speed change, recorded straight from the running Vite build at the primary viewport. Every step goes through a control the product exposes, resolved by role and accessible name, using the same drivers the acceptance suite uses — so what plays back is the prototype behaving rather than a storyboard.

The path is T1 through T6's dialog, and it passes through two of the three edge states on the way, because a console whose demonstration never fails has not been demonstrated. A severity arrives dashed and stays dashed until it is committed in the wizard; a dispatch is refused by one handheld and the row that failed keeps its place; a technician's handheld goes quiet and the product says what it therefore does not know. The pauses exist so the screens can be read — the only thing directed here is the pace.

The one thing the recording does to the product, and why

Nothing is stubbed for the camera and no state is seeded. The single intervention is to advance the product's own once-a-second clock through the reporting interval Edge 2 waits on: Live Response opens at 10:52:00 and the handheld's last frame is 11:04:37, so the interval genuinely elapses on camera instead of the state being rendered as though it had. It is the same mechanism the state audit uses, and the clock is the product's, not the wall's.

Where the take stops, and the prototype limit that decides it

The last frame is the completion dialog. Confirm and close is not activated, and that is a limit of the prototype rather than a directing choice. src/App.tsx holds a single stateId and nothing else; confirming routes to board-map-default, which is the state the app boots on, and the board is unmounted and remounted, so it re-derives every row from the module-level fixture in src/data/fiction.ts — whose count line is a constant string. The cleared-today treatment the plane can draw is bound to the S1 filter, never to a close. So the board after the close is the board before it. Measured on the take that preceded this one: the frame at 27.10 s against the frame at 2.00 s, mean absolute luma difference 0.89 / 255, peak 22 / 255, 0.13 % of pixels above a threshold of 12 — VP8 noise, and nothing else.

TASK_FLOW.md T6 specifies a board that re-opens with RG-4417 absent from the queue and its site drawn cleared-today. The build does not implement that sentence, and the spec was never corrected to say so. Rather than end on a frame that argues the morning changed nothing, the take stops at the decision — and this paragraph stands in for the frame it does not have.

EX-09 The critical flow walked once, with two edge states on the path recorded by Playwright against the running Vite build · 1440 × 900
Length, measured with ffprobe — the §17 window is 20–30 s
27.08 s
Frame, VP8 in WebM, no audio track
1440 × 900
Frame rate, and frames counted in the file
25 fps · 677
Bytes on disk
2,527,642
Cuts · speed changes · seeded states · stubs
0
  1. CoversBoard → the queue row activated twice → detail, unowned → Take incidentStart triage → four decisions, with S1 committed on the second → Continue to dispatch → crew, window and equipment → Send dispatchedge 1Retry dispatch → Live Response → edge 2, then her mark opened → the last evidence item → Close incident → the completion dialog, held. It stops there, one control short of T6 — see the note above.
  2. Captions11 cues in media/relaygrid-flow.vtt, written by the run that recorded the take, off the same clock as the beats — so the two cannot drift apart. The recording is silent; the cues describe the action. A cue may name only what is drawn or written inside the frame it is timed to — not a value that lives in an accessible name, in the fixture, or below the fold of a scrolling pane. Each of the 11 was read against a frame extracted from the shipped file at that instant.
  3. Driven byAccessible names only, through tests/states.ts — the same drivers the acceptance suite uses. There are no QA routes and no debug hooks in this build, so every frame is a surface a duty coordinator can reach.
  4. Recorded bytests-video/critical-flow.spec.ts via playwright.video.config.ts, both under apps/relaygrid/. Its testDir is ./tests-video against the acceptance suite's ./tests, so the take can never join or alter the acceptance count — which is 186 before and after.

The poster frame is ex-01-board-map.webp, the same capture EX-01 publishes, because the take opens on the state that capture holds. No QA control appears in any frame: the take visits the six views, one dialog and one popover, and all sixteen of those dwell states were swept for a States menu, a Help overlay, a reduced-motion toggle, a Debug panel, an Inspector, a role=switch and a leaked state ID — 0 of each.


Part two The examination — Gate C, and the one repair it authorised

F1 Gate C · round 1 composition 81 / 84

Two of the nine sites the map would not show you

Gate C round 1 failed. Every automated number was green: overlap, overflow, truncation, touch target and contrast at 0 across all fifty-five renders, 186 Playwright tests passed, 49 Storybook tests passed, 39 axe runs with zero violations. Composition scored 81 against a threshold of 84, and no average is allowed to rescue a failed axis.

The finding was on board-map-default at 768 × 1024, and it was one sentence long: SW-11 was completely invisible, and SE-16 was half cut. The map that this product is named for was asserting a site that a reader could not see.

No detector can see this, and none reported it

The plane is SVG over a raster. The mark and the legend are both painted surfaces, so the overlap detector resolves them to two opaque objects and correctly skips the pair as one painting over the other; and the mark is not text, so nothing that measures text can reach it. Five detectors returned zero on this render and they were right to. The defect was found by opening the capture and counting the marks.

The spec had already removed this defect class. It came back one layer up.

This is the part worth recording. Aster Shift — the previous project — failed its own Gate C round 1 on almost exactly this: two of its arc's hour labels collided with the band they were meant to sit outside, because the implementation measured clearance from the label's centre along a ray. RelayGrid's screen spec cites that failure by name and removes it by construction: clearance is measured from the plate's near edge to the mark's outer edge, at every bearing and every viewport; four anchors are tried in a fixed order; the winning anchor must clear the pane inset, every docked overlay, every plate already placed and every other mark. A table of all nine placements is printed in the spec and the resolver uses it verbatim at 1440.

All of that worked. Zero plates collided with the legend at any viewport. The defect reappeared one level above the rule that had been fixed: the placement algorithm knew the legend was an obstacle for plates, and nobody had told it the legend was an obstacle for marks.

Why it only happens at 768

At 1440 and 1280 the legend is a 232 × 246 panel docked in a corner, so the plane is drawable edge to edge. At 768 the whole board reflows — three columns become one, the plane becomes a 712 × 263 strip — and .dock--legend collapses to an opaque full-width band measuring 0, 208, 712 × 55. It does not float above the plane. It takes the bottom 55 px of it, twenty-one per cent of the pane.

The nine marks were nevertheless scaled by the pane's full 263 px, so the bottom row was placed into space that was already spent. SW-11 landed at cy 220.22 with a painted circle running 212.2 → 228.2, entirely below the legend's top edge at 208 — nought pixels of it visible, its plate and a one-pixel leader rule running into the band the only trace it existed.

Round 1 — FAIL 8 marks visible of 9. SW-11 is a plate with a leader rule running into the legend; SE-16 is a sliver on the band's top edge.
Round 2 — PASS 9 of 9. Every mark clear of the band, the quadrant cross where the spec puts it, and every mark inside the quadrant its designator names.
EX-07board-map-default@768, the plane strip and its legend, cropped in CSS at 100, 316 → 1536, 856 from the two committed 1536 × 2048 captures. Both are device-scale-2 captures shown at half size, so this is one product pixel to one page pixel. Nothing is re-rendered and nothing is retouched; the "before" file is recovered from the round-1 commit.
Before — the plate, the leader rule, and no mark. Visible circle 0.00 / 16 px.
After — the mark on its preferred anchor, no leader rule. Visible circle 16.00 / 16 px, clearance +25.84 px.
EX-08 — both windows cut at 640, 580 → 1040, 800 and shown at the capture's own device resolution, one image pixel to one page pixel. This is the 2× read, which is the only kind of read that could have found this.

The dilemma the escalation described turned out to be false

The harness pass measured this precisely and escalated it rather than guessing at a fix, which was the right call — the repair touches geometry the spec owns. But the escalation framed it as a forced choice: lay the marks out in the band the strip leaves, which moves the zone quadrants with them, or draw marks in the wrong quadrant.

It is worth recording why that was wrong, because the false dilemma is the more interesting artefact than the bug. The two objects were being anchored to the same thing when they should not have been. A quadrant boundary is a fact about the pane. A mark is a fact about the area of the pane you can actually see. Once those are allowed to differ, all nine marks land inside their own quadrants with the boundary cross exactly where the spec puts it, on the pane's own centre at (356.0, 131.5). There was never a trade to make.

The repair is one file, and it corrects a belief rather than a coordinate

Four edits, all inside resolveBoardMarks and its header. The mark scale now divides by drawableH — derived from the same legend rectangle the plates already avoid, so the band the marks dodge and the band the plates dodge can never disagree again. A dock still scales by the pane it is docked to, under a separate name. Outside the strip layout the two are equal by construction, which is why 1440 and 1280 are unchanged by proof as well as by measurement.

SCREEN_SPEC, COPY_DECK and TOKENS were not touched. The §0.8 placement table, the eight-pixel clearance and the four 0.5 zone fractions are all unchanged.

apps/relaygrid/src/data/resolvePlacement.ts excerpt · comment verbatim
/* THE PLANE'S DRAWABLE HEIGHT — the band a mark can actually be seen in.
   At 1440 and 1280 the legend is a 232 px panel docked in a corner; the plane
   is drawable from edge to edge and marks scale by the full pane height. At 768
   it is an OPAQUE full-width band that TAKES the bottom 55 px: it does not float
   above the plane, it covers it. Scaling the nine marks by the full 263 px there
   placed the bottom row inside that occupied strip — `SW-11` entirely behind it
   with only its plate and leader rule visible, `SE-16` cut to 7.7 px of a 16 px
   circle (Gate C round 1, F1). Neither is something a detector can report: mark
   and legend are both painted surfaces and the mark is not text.

   The marks therefore scale into what REMAINS, 263 − 55 = 208 px, by one
   uniform factor — so the placement table's proportions are carried over intact
   rather than nine marks being nudged one at a time. The quadrant rectangles are
   NOT moved with them: they stay on the pane's own centre cross, and every one
   of the nine still lands inside the quadrant its designator names. */
const drawableH = stripLayout ? legendRect.y : paneH;
const sy = drawableH / PANE_1440.h;

All nine marks, measured on the running build

Pane-local, board-map-default@768, pane 712 × 263. .dock--legend top edge is y 208.00, measured, and unchanged by the repair. Painted outer radius 8 px, so a mark is fully visible if and only if cy + 8 ≤ 208.
Mark cy before cy after Δ Gap to legend Visible before Visible after
NW-2136.4928.86−7.63+171.1416.00 / 1616.00 / 16
NW-1267.3253.24−14.08+146.7616.00 / 1616.00 / 16
NE-1994.3874.64−19.74+125.3616.00 / 1616.00 / 16
NE-0394.3874.64−19.74+125.3616.00 / 1616.00 / 16
NW-07103.8282.11−21.71+117.8916.00 / 1616.00 / 16
SE-04171.77135.85−35.92+64.1516.00 / 1616.00 / 16
SW-02176.17139.33−36.84+60.6716.00 / 1616.00 / 16
SE-16208.26164.71−43.55+35.297.74 / 1616.00 / 16
SW-11220.22174.16−46.05+25.840.00 / 1616.00 / 16
Occluded marks at 768
2 → 0
Smallest clearance between a painted mark and the legend's top edge
−12.22 px → +25.84 px
Plates pushed past their anchor with a leader rule, at 768
1 → 0
Plates pushed past their anchor, at 1440 and 1280
0 / 0 → 0 / 0
Marks, plates and zone designators at 1440 and 1280
byte‑identical
Of the 55 renders, the number whose layout changed
2
Files changed
1
SCREEN_SPEC · COPY_DECK · TOKENS values changed
0 · 0 · 0
Thresholds moved · exclusion entries added · tests relaxed
0 · 0 · 0
Repairs spent, of the one §12 permits
1 of 1

What the repair left behind, offered as a judgement rather than a measurement

Holding the boundary cross at the pane's centre while the marks compress into the top 208 px moves the southern row closer to the boundary line than it is at the other two widths. SE-04's painted circle crosses the dashed rule by 3.65 px and SW-02's by 0.17 px. Both centres are unambiguously inside their own quadrants, and what crosses is the upper edge of a painted circle against a one-pixel dashed keyline drawn at half opacity in the layer below the marks.

It was recorded rather than repaired for two reasons. Moving the boundaries is the only way to open that gap, and the review had stated twice that they must not move. And the ambiguity a mark on a boundary would normally create does not arise here, because every plate carries its zone as the first two characters of its designator — SE-04 is labelled SE whatever the line does. The review accepted it as recorded.

The two rounds, scored

Gate C, ten axes plus hard failures. Thresholds were not touched between the rounds. Round 1 scored only the axis that failed; the rest were deferred to the full 55-render read at round 2.
Axis Bar R1 R2
Product realism≥ 8591
Visual hierarchy≥ 8587
Composition≥ 848186
Typography≥ 8290
Information density88
Interaction clarity92
Reference pattern fidelity≥ 8088
Brand specificity≥ 8088
Responsive quality≥ 8087
Generic-template risk≤ 2011
Hard failures000

The same defect class, caught twice more, in two other places

It is not a one-off. The 5 km scale bar at 768 was painted entirely inside the legend — a 120 × 24 total overlap, with the plate struck through the Cleared today key — and no detector reported that either, for exactly the same reason. And on the Live Response plane, a crew mark was authored at a position the 232 × 246 docked legend covers at every size that pane takes, putting the mark, its initials and the last third of its route behind the panel with the initials measuring 1.69:1 against a legend label. Both were found by looking, both are fixed, and the second carries its own postmortem in source at src/data/live.ts. Three instances of one class in one project is a strong argument that opaque overlays over drawn content need a rule, not vigilance.

55 / 55 renders measured audit:states

Coverage is walked, not claimed

Every ● public state is driven through the real build by a control the product exposes, resolved by role and accessible name, then photographed and measured by six DOM detectors. There is no QA route to any of the thirty-five, because the build has none: a state the harness can reach is a state a coordinator can reach.

  • 35public states, all enumerated and all reached
  • 0unreachable
  • 55renders — 35 at 1440, 8 at 1280, 12 at 768
  • 14QA-only states, in Storybook, outside src/
  • 186Playwright tests passed, 62 × 3 viewports
  • 39axe runs, 13 surfaces × 3 viewports

The six detectors, across all 55 renders

overlap — intersection ≥ 1 px on the minor axis
0
overflow — beyond the frame > 0.5 px, spill > 1 px
0
truncation — an ellipsis or line clamp actually firing
0
touch target — under 24 × 24 px, and under 44 × 44 at 768
0
contrast — below 4.5:1, or 3:1 for large text
0
dead space — a void over 0.20 × the region's height
16
thresholds moved, or selector-list entries added to reach a zero
none

The sixteen are stated rather than closed, one at a time, each with its measured void, its region height and the limit it was judged against. Two are a filter and a search that matched nothing — the void is the result. Two are the list reading's nine rows, which is the data. Seven are the Dispatch assignment column, whose void at 1440 runs from 401 px with nothing committed to 201 px at its fullest, because a commitment panel is only as tall as what has been committed. It does not shrink monotonically. In task order the measured voids are 401333359201: setting a window after assigning two crew adds 26 px of void. The column renders Set window 10:30:00 – 12:30:00 +00:00 only while crew are assigned and no window is set, so committing the window removes that control and replaces it with a shorter Window line. The published table reads monotonic only because it is sorted by void. Five are the Close view's four-item evidence checklist. Closing any of them would mean inventing an incident, a search result, a crew member or a piece of evidence on a closed incident, which is the fabrication the acceptance standard exists to prevent.

Where the states live

  • 1 · Incident Board 9 public · 3 QA map-default · row-selected · mark-identified · list-reading · filter-s1 · search-no-match · log-dialog · log-duplicate · log-keep-both
  • 2 · Incident Detail 4 public · 3 QA unowned · owned · missing-reading · asset-history
  • 3 · Triage Wizard 5 public · 2 QA step1-open · step2-consequence · step3-open · step4-complete · step2-reopened
  • 4 · Dispatch 6 public · 2 QA scoped · crew-assigned · crew-unavailable · window-set · failed · failure-reassign
  • 5 · Live Response 7 public · 2 QA default · composer-focus · evidence-attached · handover-entry · technician-offline · offline-mark-identified · ready-to-close
  • 6 · Close Incident 4 public · 2 QA checklist-incomplete · checklist-complete · open-actions · confirm-dialog

Six of the thirty-five public states belong to the three edge states — two per edge, a primary exit and a decline — leaving twenty-nine public non-edge states. Fourteen further states are QA-only stress variants: a twenty-four-row queue, a six-crew lane strip, the longest cause block, a stream carrying only its origin entry. Every one of the fourteen has a story and only a story, and a test asserts that none of their identifiers appears anywhere in src/ or in the built bundle.

The four suites, and what each one is for

Playwright — 62 tests per viewport, run at 1440, 1280 and 768
186 passed
  of which axe · edge states · keyboard · guardrails · target size · critical flow
39 · 42 · 39 · 39 · 21 · 6
Storybook — 6 suites, every story carrying a play()
49 / 49 passed
  of which stories that exist only to hold a QA-only state
14
State audit — 55 renders, six detectors each, 9 contact sheets
35 / 35 reached
Pixel-measured contrast runs on the map raster
253
  runs that could not be measured at all, by either method
0
  worst measured ratio anywhere, against a 4.5:1 requirement
5.65 : 1

Every story has a play() and every play() was executed in a real browser by the test runner. A written-but-unrun play() was the failure mode of the first project in this rebuild; there are none here.

Accessibility, measured rather than asserted

axe-core 4.12.1 runs over thirteen surfaces — the six views, the three edge states and four overlay states — at all three viewports. 39 runs, 0 violations at every impact. The tag set is wcag2a wcag2aa wcag21a wcag21aa wcag22aa, the scope is body so that Ark UI's portalled dialogs, popovers and menu are inside the scan, and there is no disableRules() call, no excluded selector and no waiver list anywhere in the spec.

The scan waits for fonts painted, images decoded and the 160 / 260 ms cross-fades landed before it runs. That is a correctness fix rather than a relaxation: axe computes contrast from composited colour, and a scan taken mid-fade reports the ratio of a frame nobody reads.

Contrast on a dark product, read off the pixels

A DOM walk cannot read the ground under text that sits on an image, and RelayGrid paints label plates, zone designators and crew initials over a raster texture. Those runs are measured on the composited pixels of the frame: the glyphs are painted transparent, the device is captured, and every pixel of the em band behind each line is scored against the type colour at a one-pixel lattice. The run takes the worst pixel, against the same 4.5:1 and 3:1 thresholds — a measurement, not an allowance. 253 runs, 0 indeterminate, worst 5.65:1.

The keyboard, on a component system chosen for behaviour

Thirteen keyboard tests per viewport exist to show the Ark UI primitives were imported for what they do rather than for their markup. A dialog opens from the keyboard, traps focus and returns it on Escape. The severity filter and the plane's reading control commit with the arrow keys. A triage decision is answered with arrows alone. The queue is selected and opened from the keyboard, and the crew list assigns two technicians the same way. Equipment is picked with Space. A committed triage decision is re-opened with Enter. The plane's marks are reachable and operable. And a soft-disabled control keeps its place in the focus order and states why — which is the accessibility half of the "disabled with a reason" rule.

The guardrail scans the bundle that ships, which it was not doing

tests/public-guardrails.spec.ts reads src/ and dist/ from disk, but not for the same things. Both are searched for the ten forbidden surfaces — a States menu, a Help overlay, a reduced-motion toggle, a Debug panel, an Inspector, a flow-step list, a QA toolbar, a KPI or metric tile, a chart or sparkline, and any SLA or compliance claim. dist/ only is searched for all fourteen QA-only state identifiers, this page's disclosure sentence and the strings .stories., storybook and @playwright; the fourteen identifiers are checked against src/ by a separate test, and the disclosure and the tooling strings are not checked against src/ at all. Then it walks all six views at runtime and searches the accessibility tree for a control named after any of those surfaces, the rendered text for the five discriminating view names, this page's disclosure and any SLA or attainment figure, and the DOM for a canvas, chart, KPI or sparkline — and asserts getByRole('switch') returns 0 on every view, because reduced motion is the media query alone.

A green check over the wrong bytes is worse than no check

That guardrail was passing while scanning a stale bundle. npm run build had been failing on a TypeScript error — MAP_GEO is declared as const, so MAP_GEO.siteOuterR carries the literal type 8 and a default parameter written targetR = MAP_GEO.siteOuterR took that literal type instead of widening to number. Vite's dev server strips types and never saw it, so the application ran while the build did not, and dist/ predated the source. Two annotations fixed the build; the reason it matters is that the guardrail had been scanning a bundle that was not what ships.

Four pairs of states render identically, and the reason is a good one

The audit's digest table reports four pairs sharing a normalised DOM digest: detail-missing-reading / detail-owned, dispatch-crew-unavailable / dispatch-scoped, live-default / live-evidence-attached, and close-checklist-complete / close-open-actions. Each of the four is a treatment that is the resting content of its partner — the named absence in the telemetry ledger, the disabled crew row, the attachment on stream entry 1, the open-actions block in the summary column. Each is captured separately, and each driver asserts the distinguishing content is on screen before the shot is taken. The rule the state registry gives is the right one: a treatment nobody photographed is a treatment nobody reviewed.

Pilot 4 of six · rebuild v2 Claude Opus 4.8

Sources, stack, and what was not taken

On the use of references

Before any view was drawn, a set of shipping operations and incident-management products was studied for structure only: how a flat navigation rail expresses depth inside the working surface rather than in the rail; how a map legend that is permanent changes what a marker has to carry; how ownership, action and record are sequenced; how a control that cannot be used states its own precondition at the point of refusal; and how a failure is scoped to the object that failed rather than to the page. Those findings were written up as patterns, and it is the patterns that were carried across.

No source brand, wordmark, colour, copy line, dataset, taxonomy, participant, image, layout trace or business rule was taken. No reference application is named on this page and no reference screenshot is published in this package — the private study material is excluded from the repository and from every artefact in it. Every raster in this package is either a capture of this build or an image generated for it.

The map uses no cartography of any licence

The obvious way to build a plane like this is a tile server, and it would have made the product unpublishable. RelayGrid's plane is two self-owned layers instead. The base is one generated texture, recorded with its prompt, its prediction id and its digest. Everything above it is self-authored SVG drawn at build time from the fictional grid coordinates in the copy deck — a 64 px graticule, four zone polygons, one route drawn as a single dashed <line>, the marks and their plates. No map extract, satellite raster, aerial photograph, shapefile, geocoder result, basemap SDK or tile server is used, requested at runtime or bundled. The delivered texture was opened at full size and read: it carries contour bands and a survey grid, and no place name, road, coastline, compass, scale or lettering of any kind.

The stack, as installed

@ark-ui/react 5.37.2
Eight component namespaces imported across six sites in src/Dialog, Popover, Menu, Listbox, RadioGroup, Checkbox, Accordion and Portal, plus the createListCollection helper. Counting the parts each namespace brings with it, 34 distinct namespaced sub-components are rendered — Dialog.Backdrop, Listbox.Item, RadioGroup.ItemHiddenInput and the rest — with Portal used directly on top of those. A guardrail test asserts the product imports Ark UI and neither Radix nor React Aria, which are the systems the three earlier projects use
react 18.3.1 · typescript 5.9.3
tsc -b && vite build exits 0. It did not before this pass — see the guardrail note above
vite 5.4.21
Bundle 428.21 kB, CSS 39.28 kB, measured on the accepted build after the repair; the build before it was 428.19 kB / 128.21 kB gzipped. Dev server pinned to port 5177 with strictPort, so no other app's harness can attach to it
@playwright/test 1.62.0
The product suite at three viewport projects, plus a separate audit harness with its own config
@axe-core/playwright 4.12.1
39 runs, no disabled rule, no excluded selector, no waiver list
storybook 10.5.4
6 suites, 49 stories, 49 play(), run by @storybook/test-runner 0.24.4 in Chromium. The preview loads the product's own tokens.css, global.css and app.css and carries the three audit viewports, so a story is the shipped component on the shipped surface
IBM Plex Sans · IBM Plex Mono
Self-hosted in the product through @fontsource 5.3.0, and again beside this page. A written sentence is never set in the face that carries a measured quantity
Implementation model
claude-opus-4-8, bound per run and verified on the first status event of the run's event log; the execution receipt records one run, one model observed, no fallback and no substitution

Six generated rasters, every one load-bearing in a named state

Five photographs and one texture, 2 candidates generated per role, 1 selected per role, 0 regenerations, $0.48 of a $0.50 cap. All twelve candidates were opened at full size before selection and checked for functional UI, navigation, forms, tables, charts, chart labels, dials, gauges, readable lettering, logos, human figures and branded objects. Every photograph in the product is 4:3, because in the fiction every capture comes off one handheld and one desk archive.

apps/relaygrid/src/data/live.ts excerpt · the offline mark popover
export const OFFLINE_POPOVER = {
  title: "Mira Osgood",
  rows: [
    { label: "Role",     value: "Technician" },
    { label: "Dispatch", value: "RG-4417", mono: true },
    { label: "Handheld", value: "HH-211 · Not reporting", mono: true },
    { label: "Last fix", value: "11:04:37 +00:00", mono: true },
  ],
  positionLabel: "POSITION AT THE LAST FIX",
  positionValue: "E 417 902 · N 109 011",
  closing:
    "This is where the handheld last reported. RelayGrid does not know where she is now.",
  dismissSR: "Close Mira Osgood",
};

There is nowhere in that shape for an estimated position, a confidence figure or a last seen label. Three of the four rows are marked mono because they are machine-produced; the role is not. The restraint is in the shape of the data, not only in the copy.

What is in the package

Product prototype — source, entry apps/relaygrid/src/main.tsx
6 views
Storybook — apps/relaygrid/stories/, outside src/
49 tests
Screen stills, byte-identical copies of Gate C captures
8
Task-flow diagram — the departures at 09:48:22, in HTML
6 + 3
State coverage and accessibility summaries — at 55 / 55
55 renders
Reference-use disclosure — above
structure only
Source excerpts, quoted verbatim with their comments
2
Interaction video — one unbroken take at 1440 × 900, above at EX-09
27.08 s
Full accounting of every path, measurement and its source
PACKAGE.md

The video row was an open item on this page until the recording harness was written. Master Instruction §17 asks for a 20–30 second interaction take alongside the stills, and a playwright.video.config.ts with a tests-video/ spec now produces one. The acceptance count is 186 on both sides of that addition because the two configs cannot see each other's files. No product source was touched to make the recording — the take goes through the same controls, in the same build, that the eight stills above were captured from.

What is still wrong, and was left

  • The prototype keeps nothing across a view change, so T6 has no visible result. App.tsx is a single stateId; every view re-derives from the fixture on mount. Closing RG-4417 therefore returns the board exactly as it opened — TASK_FLOW.md T6 asks for it to re-open with the incident gone and its site drawn cleared-today, and the build does neither. The interaction take stops at the completion dialog because of it, and an earlier cut of the caption track asserted the T6 sentence from the spec instead of from the frame. The fix is a state model the shell owns rather than a caption, and it was not attempted here.
  • The radius policy says the site mark is the only circle in the product, and the build has six more. TOKENS.json's radius_policy reads "no control, no chip, no avatar, no panel and no card is circular", and SCREEN_SPEC.md repeats it. app.css disagrees at six places, all border-radius: 999px: the queue's severity dot (8 px), the plane key's swatch (20 px), the triage wizard's radio and its checked centre (20 / 10 px), the stream's entry dot (8 px) and the Close checklist's mark (20 px). The radio is a circular control, which is the one thing the policy names outright. This page carried the spec's sentence at EX-04 without opening app.css; the sentence is corrected there now. Making the build obey would mean re-drawing a radio group and a checklist as squares, which is a design change and was out of scope — the honest reading is that the spec over-reached and the build never matched it.
  • The Dispatch plane at 1440 is thin. Two or three marks and a route in a large pane. It earns its place operationally — the crew marks and the route are the answer to where is everyone — but it carries little for the eye, and the review named it the weakest composition in the build.
  • The NOW rule on Live Response overruns the crew lane strip and terminates inside the note composer. Measured in Chromium at all three audited widths: the rule runs 52 px past the bottom of .lanestrip at 1440, 1280 and 768 alike, and its lower terminus falls inside the composer's own <textarea> at each. It is authored — .lanestrip__now is bottom: -46px — so it reads as intended rather than as broken, but a rule that ends in the middle of an empty text field is doing less than it appears to. The round-2 review named 1280 and 768 only; the overrun is the same at 1440, which is the primary viewport.
  • A target authored at exactly the bar can always fall a ten-thousandth under it. An intermediate version of the repair modelled the legend strip at 56 px rather than the 55 px it measures, which put a mark's centre at cy 81.71052631578947 — and because the browser's SVG transform matrix is float32, its 44 × 44 hit circle measured 43.99998474121094 px and tripped touch-target. It was fixed by making the constant correct, not by touching the detector or an exclusion list. The fragility is latent, and only the audit would catch it.
  • findingTotals in the audit's JSON counts the primary viewport only. It reports 13 dead-space findings; the all-render figure is 16. A reader trusting the top-level total would have missed the 768 renders entirely — which is where the defect that failed round 1 lived. The field should be labelled or the harness should emit both.
  • Digest equality cannot be used as a regression check on this project. Captures and DOM digests drift slightly between runs from a one-second tick in the elapsed clock and from a timing-dependent scroll offset on one triage capture. It was confirmed as pre-existing by re-running the unmodified build, so it is not repair-induced — but it means all 55 digests can differ between two runs of the same bytes, and two consecutive runs agreed on 54 of 55.
  • Three places where the build pack cannot be satisfied as written, recorded and not reconciled. The 768 paragraph of the screen spec asks for a legend along the strip's whole bottom edge and for the scale bar to keep its position, which are mutually exclusive; its 712 × 28 single-row legend is arithmetically unreachable, because eight keys measure past 712 px on one line, so the strip is two wrapped rows at 55 px. The object header renders 101 px against the pack's 88, which takes 13 px off the body of every object-header view. And the queue's scroll region is 768 px rather than 792, because the build paints a 24 px filter note the spec's arithmetic does not account for. All three are measured in the harness report; none was resolved by editing the spec after the fact.