Case file TL-02 · SaaS · qualitative research workspace

TraceLab

Where a claim meets the evidence it rests on.

A researcher opens a session transcript, marks the passages that matter, groups them into themes, writes an insight that cites them, and publishes it — but only if the claims survive two named reviewers. This file documents the critical slice of that arc: six views, three edge states, built as a running prototype and examined against its own captures rather than described.

Role
Product design and front-end build
Surface
SaaS · 1440 × 900 primary, with 1280 × 800 and 768 × 1024
Component system
Radix UI — 11 installed, 10 imported directly
Slice
6 views · 3 edge states · 40 public states · 16 QA-only
Examination
Two rounds. Round 1 failed on responsive quality; one repair; round 2 passed
Exhibits
7 screen stills · 3 details at 1:1 · 1 interaction take — every one from the running build
Folio A Position and problem sheet 1 of 8

EX-01 Project Library · 1440 × 900

TASK_FLOW §0 canonical fiction — Corveth Research, 14 sources

STATE_INVENTORY §5 what the public UI must never expose

Analysis software is good either side of the moment that matters

Tools for qualitative research collect well and present well. The part between — where a sentence somebody said becomes a claim a team acts on — is where the provenance is either kept or quietly lost.

TraceLab's slice is built entirely around that middle. Everything in it exists to keep a claim tied to the excerpt it came from, and to make the tie legible to somebody who did not do the coding. Three commitments were fixed before any screen was drawn, and every later decision in this file follows from one of them.

  1. i

    A machine may propose. It may not assert. No confidence percentage appears anywhere in the product; the machine's output is named, provisional, and unusable as evidence until a person confirms it.

  2. ii

    Contradiction is data. Two excerpts that disagree are both kept. Publishing an unresolved disagreement is a first-class outcome, not a failure to tidy up.

  3. iii

    A quote is only evidence in the company of what surrounded it. Citations open inside the turn they were taken from, one at a time, with a route back to the source.

Scope was fixed before pixels too: six views, three edge states, no marketing surface. The public build exposes no states menu, no help overlay, no reduced-motion toggle, no debug panel, no inspector and no flow-step list — reduced motion is honoured through prefers-reduced-motion alone, because this concept has no genuine product need for a switch. That is asserted at runtime on every view, not only in the source.

The one study the prototype ships

Corveth Research, a four-person studio (fictional). Project Workshop Handover — how two-shift repair workshops pass an unfinished job across the change of shift. 14 sources S-01S-14, one session transcript each; six codes; six themes; three insights; two named reviewers. Every person, session, quote and timecode is invented, and no outcome metric is claimed anywhere in the product. The counts are the structure of the fiction, not results.

EX-01 Project Library — the workspace at rest project-library-default · 1440 × 900 · capture 1 of 58
TraceLab Project Library: a left navigation listing Sources 14, Themes 6, Insights 3, Review with a badge of 1, and Codebook 6; a centre column with three recent project cards and a table of session rows, each with an Open in coding button; and an activity rail on the right.
  1. 1 The navigation carries the only badge in the product: Review · 1, in oxblood. It counts unresolved contradictions, and it lives beside the work rather than in a toast that expires.
  2. 2 Every source row's primary action is Open in coding — a session opens where it can be worked on, never in a read-only preview.
  3. 3 The activity rail is written in the study's own vocabulary: Nadia Restrepo coded 9 excerpts on S-09, Tomas Ekwall marked evidence on claim 2 as contradicted. Provenance is the product's idea of news.
Folio B The treatment of machine assistance sheet 2 of 8

EX-02 transcript-coding-suggestions-needs-review

EX-03 theme-canvas-suggestion-provisional

EX-04 insight-composer-ai-unavailable

Guardrail a test asserts the suggestion band contains no % anywhere

The machine may propose. It may not assert

Machine help appears exactly twice in six views, as two named, bounded operations: Suggest codes in the transcript, and Draft with evidence in the composer. There is no prompt box, no message list and no assistant turn anywhere in the product.

That is a decision about where the confidence lives. An assistant that talks invites the researcher to argue with it; an operation that returns a provisional object invites the researcher to inspect it. TraceLab does the second, and it holds four rules while doing it.

1 · No number stands in for judgement

The suggestion band is headed Needs your review · 4 suggested codes, and there is no percentage on the screen. The band's body says what was read and what the reader owes it: TraceLab read S-09 and proposed these codes. They are a starting point. Confirm each one before it becomes evidence. Its footer restates the duty rather than assuming the heading was read: Suggested codes are not evidence until you confirm them.

Each suggestion carries a per-item Rationale disclosure — a collapsible on the row, not one switch for the screen — and each rationale argues from the corpus rather than from itself: This sentence lists three physical places the card is kept and names no digital system. Twelve excerpts already coded paper-first describe the card's physical position. Confirming four suggestions moves the rail's count from 9 to 13 and stamps each new card Suggested by TraceLab · confirmed by NR just now, so the provenance of a machine-originated excerpt survives its promotion.

EX-02 Transcript Coding — four suggested codes, awaiting a person transcript-coding-suggestions-needs-review · 1440 × 900 · edge state 2
TraceLab Transcript Coding with the suggestion band open: the transcript of S-09 in the centre with two coded passages underlined in blue and oxblood, and an evidence rail on the right headed Needs your review, four suggested codes, each with a Rationale disclosure and Confirm and Not this one buttons.
  1. 1 The band names the operation and the amount of work it created — four items to review — and nothing about how sure it is.
  2. 2 The staleness line, in oxblood, carries its own remedy on the same line: Two excerpts changed since these were suggested. Refresh suggestions.
  3. 3 Per item: a Rationale disclosure, then Confirm and Not this one. The decision is per suggestion, never a single accept-all that hides the weakest one behind the strongest.
  4. 4 Confirmed excerpts are hue-coded in the transcript itself, so the rail and the reading column always describe the same object.

Detail the band, at actual size

Cropped from EX-02 at 1:1 — no reflow, no retouch

Fragment 1 · EX-02 at 1090, 1861436, 382, shown at 1:1. Four lines of type do the work a percentage is usually asked to do, and they say who decides.

2 · A proposal is provisional until a person keeps it

On the Theme Canvas, five clusters are the researcher's own. The sixth — Whatever gets said at the door — was proposed by the machine, and it is drawn differently: a dashed border, a Suggested tag on its name, and a bar beneath it reading TraceLab grouped 3 excerpts it read as the same idea. Nothing is saved until you keep it. with Undo and Keep theme. Keeping it makes the border solid and leaves the attribution on the cluster header, permanently. The canvas is a canvas and not a kanban: clusters sit at free positions on a dotted field, with no columns, no lanes and no fixed order.

EX-03 Theme Canvas — a machine proposal, dashed and undecided theme-canvas-suggestion-provisional · 1440 × 900
TraceLab Theme Canvas: six theme clusters placed freely on a dotted field, each holding excerpt chips labelled with source and timecode. The cluster named Whatever gets said at the door has a dashed border and a Suggested tag, with a bar below it offering Undo and Keep theme.
  1. 1 Dashed border and a Suggested tag. The difference between a proposal and a decision is visible from across the room.
  2. 2 Nothing is saved until you keep it. — the state of the object, stated where the object is.
  3. 3 Undo and Keep theme, with a thumbs pair for feedback that does not change the outcome either way.
  4. 4 The researcher's own clusters carry no attribution and no dashes. Only the machine's work is marked, and only until it is adopted.

Edge 1 lives on view 4 only — no view carries the whole failure surface

COPY_DECK §4.6 cause, recovery and consequence, in that order

3 · The machine notices when its own input has gone stale

Suggestions were computed against a corpus that has since moved. Rather than serving them silently or invalidating them without asking, the band says Two excerpts changed since these were suggested. and puts Refresh suggestions on the same line. A warning whose remedy is elsewhere is a way of transferring blame; a warning that carries its own action is a state.

4 · A failure is scoped to the step that failed, and says what survived

Edge 1 is the drafting service being unreachable. It renders inside the draft step card and nowhere else: an oxblood cause line (TraceLab could not reach the drafting service.), then the sentence that actually matters — Your excerpts, themes and notes are saved. Nothing was lost. — then two real exits, Try drafting again and Write it myself, directly above the slot the draft would have filled. The footnote names what still works: The evidence rail still works. Citations can be added by hand from any excerpt.

The context bar, the title, the editor and all six evidence cards stay live behind it, and a test asserts that they do. The same discipline applies when the operation succeeds: the step card reports Step 2 of 3 · grouping claims with a Stop drafting control, because an operation a person cannot stop is an operation that is happening to them.

EX-04 Insight Composer — drafting unavailable, everything else alive insight-composer-ai-unavailable · 1440 × 900 · edge state 1
TraceLab Insight Composer with a failure card at the top of the draft column reading Drafting is unavailable, TraceLab could not reach the drafting service, followed by a recovery sentence and the buttons Try drafting again and Write it myself. Below it the insight title, standfirst and three claims are editable, and the evidence rail on the right lists six cited excerpts.
  1. 1 Cause first, in oxblood, in one sentence — then the state of the user's work, which is the thing they actually want to know.
  2. 2 Recovery sits above the empty slot, not in a banner at the top of the page and not behind a dismissal.
  3. 3 The evidence rail still works. A failure that names its own boundary is smaller than one that does not.
  4. 4 And the boundary is true: the six cited excerpts, their codes and their citation numbers are untouched, and the draft can be written by hand from them.
Folio C Contradiction, and the refusal to delete sheet 3 of 8

EX-05 review-conflict-open

S-11 · 22:05 and S-04 · 08:50 — both coded app-as-record, both cited on claim 2

Edge 3 exits: review-conflict-resolved, review-conflict-left-unresolved

Two excerpts, opposite readings

The prototype's happy path is rigged to reach a disagreement. Claim 2 of the draft carries two excerpts that contradict each other, so reconciliation is exercised rather than skipped — the hardest screen in the product is on the main line, not on a branch.

Both excerpts are coded app-as-record. One opening lead says I do not open the app until lunch. The card tells me where to start. Another says The app is what I open first. The card is just what is left over from the night before, and half the time it is not filled in. They cannot both be summarised away, and the reconciliation card does not try: Decide what each one does to the claim. Nothing is deleted either way.

Each excerpt gets its own three-way answer — Supports this claim, Contradicts this claim, Not related to this claim — so the researcher's judgement is recorded per source rather than as a verdict on the pair. Saving keeps the loser: Counter-example · S-04 · 08:50 — one opening lead reads the app first. is published beneath the claim, and the nav badge clears.

Declining is a first-class outcome. Leave unresolved stamps the claim Unresolved · 2 excerpts disagree. This will publish with the claim., holds the navigation badge at 1, and writes the decision into the activity log with a name against it. Publishing a disagreement is a research position; a tool that only permits tidy claims is quietly asking for false ones.

The terminal action is not the machine's to take

Publish is rendered disabled and visible with Pending approval immediately beside it — the block is explained at the point of refusal, not in a tooltip. It unlocks only when both named reviewers have submitted an approval; one reviewer requesting changes does not unlock it, and a test asserts exactly that. The confirmation dialog then states what publishing means in the study's own terms: It becomes readable by everyone in Corveth Research, with its 6 citations and 1 counter-example.

EX-05 Review — the conflict opened on claim 2 review-conflict-open · 1440 × 900 · edge state 3
TraceLab Review: the insight and its three claims on the left with a Conflict, 1 chip on claim 2; a contradicting-evidence pane in the centre showing the two disagreeing excerpts side by side, each with radio choices Supports, Contradicts and Not related, and Save resolution and Leave unresolved buttons; a review rail on the right with the ask, two reviewers and an activity log.
  1. 1 The claim carries the mark, in oxblood: Conflict · 1. It is a button — the disagreement is opened from the sentence it damages.
  2. 2 Nothing is deleted either way. stated before the choice is offered, so the choice is not read as a choice about which excerpt to lose.
  3. 3 Save resolution and Leave unresolved as siblings, with the consequence of declining written under them rather than hidden in a confirm dialog.
  4. 4 Publish, disabled and visible, with Pending approval beside it and both reviewers' states in the rail.
  5. 5 The badge in the navigation is the same count, in the same colour, carried across every view until somebody deals with it.

Detail the promise, at actual size

Cropped from EX-05 at 1:1

Fragment 2 · EX-05 at 730, 2061134, 284, shown at 1:1. Twenty-eight words, of which the last five are the design decision.
Folio D Citation, shown in context sheet 4 of 8

EX-06 published-insight-citation-open

T6 the audit return — view 6 back into view 2, at the cited excerpt

Asset the paper-fragment plate is the only image inside the product

A quote is not evidence on its own

A citation in a research insight is a promise that a real person said something in a real context. Reproducing the sentence keeps half of that promise. TraceLab keeps the other half.

Citations are inline typographic badges in the prose — never chat turns, never a footnote pile at the bottom. Activating one opens the rail on the quote inside its surrounding turn, under the label SHOWN IN CONTEXT: the cited sentence is tinted, and the sentences either side of it are shown at full strength, including the ones that complicate it. The footnote says why: This quote is shown inside the surrounding turn, so you can judge how it was taken.

Support arrives one quote at a time behind a 1 of 4 pager, rather than as a wall of eleven excerpts that nobody reads. The end of a claim's support is stated rather than hidden — at 4 of 4 the Next control is disabled and visible. And every quote offers Open S-09 in coding, which re-enters the transcript view with that excerpt focused. It is the audit path: from a published claim back to the passage, in one action, without leaving the product.

The published page keeps its own provenance: byline, Approved by Tomas Ekwall and June Bakhshi · version 1, a View history popover with two versions, and a source index listing each cited session with its excerpt count. Nothing about the page pretends the insight arrived without hands on it.

EX-06 Published Insight — citation 1 of 4, inside its turn published-insight-citation-open · 1440 × 900
TraceLab Published Insight: a masthead plate of torn paper fragments joined by fine ink lines, the insight title and standfirst, the byline with approvals and a View history link, and a summary section. On the right, a rail headed Supporting quote from S-09 with a 1 of 4 pager, the cited sentence tinted inside the surrounding turn, and an Open S-09 in coding button.
  1. 1 The rail names the source and the speaker's role before the quote: Closing handover · four bays · P-09 · 04:12.
  2. 2 1 of 4, with Previous and Next. One piece of support at a time, and a stated end.
  3. 3 The tinted sentence sits inside the rest of the turn — including That last line is the one that matters., which is the part a pull-quote would have thrown away.
  4. 4 Open S-09 in coding — the return path from a published claim to the transcript it came from.
  5. 5 The masthead plate is the only image inside the product, commissioned for this state and load-bearing in it.
Folio E Chain of custody sheet 5 of 8

TASK_FLOW §1 six views, one decided trigger each

TASK_FLOW §2 three edge states, one per view, never stacked

EX-08 the same chain, walked once and recorded

One trigger per transition, decided in advance

Every step below advances on a single named control that 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 is evidence that a researcher can complete the arc rather than evidence that a script can.

The critical flow · T1 – T6

  1. 1 Project Library

    T1 Sources tab active, row S-09Open in coding

  2. 2 Transcript Coding

    T2 rail footer reads 9 excerpts codedGroup into themes

  3. 3 Theme Canvas

    T3 exactly one cluster selected → Draft insight from this theme

  4. 4 Insight Composer

    T4 every claim cited, so Request review is enabled

  5. 5 Review

    T5 both reviewers approved, so Publish stops being disabled

  6. 6 Published Insight

    end of the slice — the insight, its 6 citations and its counter-example

T6 Audit return · Open S-09 in coding in the citation rail re-enters view 2 with the cited excerpt focused. It is a return, not an advance, and it is the only edge in the graph that runs backwards.

Departures · three edge states, each on a different view

Departs view 2 · Transcript Coding

E2 — low confidence

Suggest codes returns four unconfirmed suggestions under a Needs your review band. No percentage exists anywhere in the state.

Returns Confirm 4 codes → rail count 9 → 13 · Dismiss suggestions → back to 9 confirmed

Departs view 4 · Insight Composer

E1 — AI unavailable

The drafting service is unreachable. The failure renders inside the draft step card only; the bar, the editor and the six evidence cards stay live.

Returns Try drafting again → the draft lands · Write it myself → empty editor, rail intact

Departs view 5 · Review

E3 — conflicting evidence

A reviewer marked the evidence on claim 2 as contradicted. The two excerpts are laid side by side and answered one at a time.

Returns Save resolution → S-04 kept as counter-evidence · Leave unresolved → badge holds at 1

EX-08 The chain walked once, in one take recorded by Playwright against the real Vite build · 1440 × 900
Length
27.84 s, measured with ffprobe
Frame
1440 × 900 · VP8 / WebM · 2.4 MB
Captions
The take is silent; the caption track describes the action and was written by the run that recorded it, off the same clock
Driven by
Accessible names only — the same controls the end-to-end suite uses
Covers
Library → coding → suggested codes confirmed → themes → draft → conflict opened and resolved → both approvals → publish → citation opened in its turn
Editing
None. One take, no cuts, no speed change; the pauses exist so the screens can be read
Folio F Record of examination — the round this build failed sheet 6 of 8

Round 1 responsive quality 78 · threshold 80 · FAIL

Repair budget one upstream pass, and it was spent

Round 2 responsive quality 84 · PASS

EX-07 transcript-coding-rail-empty, after

It failed the first review, on one axis, and the failure stood

Gate C scores ten axes against fixed thresholds, and no average may rescue a failed one. Round 1 met nine of them, several by a wide margin — and scored 78 on responsive quality against a threshold of 80. That is the whole verdict: FAIL.

The failing viewport was 768 × 1024, one of the three the standard requires for a SaaS build, and it failed on what a reader meets there rather than on a metric. Two findings carried it.

F1 — the 768 composition

On review-conflict-open@768 the breadcrumb wrapped to three lines and the action row was laid alongside it with every label broken across two lines. Four elements painted outside a 64 px bar. Surveying the shared component across all six views found worse: review-changes-requested wrapped its breadcrumb to seven lines and painted from y0.5 to y158.5, over the product bar above it and the work area below.

Worse still, the seam between the two stacked panes cut a line of body text horizontally through the glyphsEvery workshop in the study has a spoken exchange at the door, and none of them, sliced at y532 while its ink ran y522 → y544. No detector fired, because a scroll boundary is legitimate; it simply read as a rendering fault. And the review rail was a 72 px box holding 608 px of content, an item that had been open in the QA report since week 4.

F2 — a state that rendered as a blank column

transcript-coding-rail-empty painted the session header for S-12 — Apprentice shift · six bays · recorded 26 min — over 722 px of blank paper, with the only empty-state copy sitting in the rail beside it. The build pack ships no S-12 transcript and the implementation rendered that gap faithfully, so the fault was upstream of the code. It did not matter: a source that is recorded and blank looks broken rather than empty.

One repair, spent at composition and hierarchy — not on spacing

The standard permits exactly one upstream repair per project, and orders it: reference selection, task flow, screen composition, information hierarchy, component system, asset plan, visual system, prompt exactness. Starting at shadows or spacing when composition is what failed is the named anti-pattern. This repair started at composition.

Measured before and after, at 768 × 1024, on review-conflict-open
MeasurementRound 1Round 2
Breadcrumb lines31
Breadcrumb lines, worst state in the build71
Action labels wrapped to 2 lines30
Elements painting outside the 64 px bar40
Bar scrollHeight in a 64 px bar67 px63 px
Lines of type crossed by a pane boundary10
Review rail box, around 608 px of content72 px609 px
S-12 transcript column, empty surface722 px602 px
Transcript turns invented to close that void00

The context bar fix hides ancestor breadcrumb segments below 820 px and shows an elision marker, because at that width the workspace name is already in the product bar and the project is already in the navigation. They are display: none rather than visually hidden, so the accessibility tree says exactly what the screen says. The stacked panes became a flex column with a 420 px floor instead of a 420 px clip, so a band grows to its content rather than guillotining a line — and the measured cut-line count went to zero at every scroll position, verified by walking every text node in the band against the pane edge.

Detail the repaired bar, at actual size, at 768

Cropped from review-conflict-open@768 at 1:1

Fragment 3 · review-conflict-open@768 at 52, 46768, 118, shown at 1:1. The whole bar, one line, nothing painting outside it — the same controls that needed three lines before.

The empty state that was allowed to stay empty

F2 had an obvious fix and a correct one. The obvious fix was to write P-12 some transcript turns; the void would have closed and the screen would have looked finished. That is precisely the fabrication the acceptance standard exists to prevent, so the column got an empty state of its own instead — two sentences that add no fact the build pack did not already carry.

No transcript for S-12 yet. The 26-minute recording is stored. There is nothing to select until it has been transcribed.

The dead-space finding did not disappear; it went from 722 px to 602 px and is still on the record, accepted, because the void is the emptiness the state is reporting. Zero P-12 turns exist in the fixture before this change or after it.

EX-07 Transcript Coding — S-12, recorded and not yet transcribed transcript-coding-rail-empty · 1440 × 900 · after the repair
TraceLab Transcript Coding on source S-12: the session header reads Apprentice shift, six bays, recorded 26 min, and the reading column below carries its own empty state — No transcript for S-12 yet, the 26-minute recording is stored, there is nothing to select until it has been transcribed. The evidence rail on the right reads Evidence 0 with its own empty copy.
  1. 1 The column now explains its own blankness, in the column. The two facts it states — the source exists, the recording is 26 minutes — were already in the build pack.
  2. 2 The rail states the same thing from its side: Evidence 0, with the Suggest codes remedy where the first card would be.
  3. 3 The source list keeps S-12 selected and legible. Nothing was hidden to make the state look better than it is.

Scores are per axis. No average rescues a failed axis

Repair count 1 of 1 permitted — a second failure would have blocked the project, not sealed it

The two rounds, scored

Gate C, ten axes, both rounds. Thresholds were not touched between them.
Axis Bar R1 R2
Product realism≥ 858990
Visual hierarchy≥ 858989
Composition≥ 848586
Typography≥ 829191
Information density8989
Interaction clarity9090
Reference pattern fidelity≥ 809292
Brand specificity≥ 808989
Responsive quality≥ 807884
Generic-template risk≤ 201010
Hard failures000

What is still wrong, stated rather than closed

  • review-changes-requested at 768 still wraps its control labels. Five controls plus a 124.9 px chip need 625 px against the 420.1 px left after the collapsed breadcrumb; Back to draft takes three lines. Much better than it was — 7 breadcrumb lines to 1, 111 px of bar to 63 px, 6 out-of-bar elements to 0 — and not closed. Closing it needs a control fewer or a taller bar, and both are spec decisions.
  • At 768 the breadcrumb tail and the status chip both read In review. The duplication is in the copy deck, not in the repair, and keeping one more segment does not fit: … / Insights / In review measures 155.8 px against 116.8 px of room.
  • The 768 review rail now begins below the fold. Giving 608 px of content a 609 px box, under bands of 484 px and 770 px, puts it at y1366 on a 1024 px screen. Nothing is unreachable — the page scrolls — but that capture no longer shows the rail at all. It is the honest consequence of the alternative, which was a 72 px sliver.
  • The spec says the 768 panes are 712 × 420; the build treats 420 as a floor. Recorded as a spec/implementation divergence rather than resolved by editing the spec after the fact.
  • Two pairs of inventory rows render the same frame. transcript-coding-suggestions-stale is byte-identical to transcript-coding-suggestions-needs-review, because the build paints the stale line and its inline remedy as a permanent part of the band rather than as a state the band enters — the one DOM-digest alias the audit records. published-insight-source-index is likewise the same frame as published-insight-default at 1440 and 1280, where the source index is the rail's resting content; the two genuinely differ at 768. Both are recorded in the state registry rather than smoothed over.
  • The 768 rail overlay ships without the scrim and focus containment the screen spec describes. It is painted over the context bar, so those actions are unreachable while it is open — which reads as modal without being marked as modal. Adding the scrim would move the dead-space measurement on twelve renders, so it is recorded for a design decision rather than changed inside a repair scoped to something else.
  • A detector defect was found by this repair and fixed in the harness. Dead space measured its region from the raw bounding rect while measuring content after clipping, so a region running past the capture reported its own off-screen part as void — a phantom 350 px. The region is now taken from the same painted-edge function that filters the content. No threshold moved, and the neighbouring project was re-audited afterwards: its report differs by the generatedAt line alone.
Folio G What was measured, and on what sheet 7 of 8

40 / 40 public states reached · 0 unreachable

58 renders · 10 contact sheets

27 axe scans · 0 violations · no waiver list

280 contrast runs measured on composited pixels

Coverage is walked, not claimed

Every state is filed on one of two surfaces before it is built. Public states are the ones a researcher reaches by using the product. QA-only states — skeletons, a 240-turn session, a cluster holding 24 chips, a validation reset, the print stylesheet — live in Storybook and the test suite and never appear in the shipped bundle.

State inventory, counted from the screen × state matrix
View ● public ○ QA-only total
1 · Project Library527
2 · Transcript Coding11314
3 · Theme Canvas549
4 · Insight Composer639
5 · Review8210
6 · Published Insight527
56 states inventoried401656

Eight of the 40 public states belong to the three edge states, because the edges are part of the research experience and are held to the same finish as the happy path. A permanent audit harness drives all 40 through product controls, photographs each one and runs six DOM detectors over it, at 1440 × 900 with six states repeated at 1280 × 800 and twelve at 768 × 1024: 58 renders, 40 / 40 reached, 0 unreachable.

overlap
0
overflow & clipping
0
truncation
0
touch target
0
contrast
0
dead space
9

The nine dead-space findings are held rather than hidden, each with its own measurement: four are empty or short data (a no-results search, a three-row tab, and S-12 twice), four are geometry the screen spec fixes — the 768 drawer is a drawer, not a column — and one is a three-claim document shorter than its window, 15 px over the limit. Not one is closable by composition, and every one of them is closable by fabrication. No threshold was moved and no selector was added to an exclusion list to reach the other five zeros.

The suites, and what each one is for

Playwright99 passed — 33 tests × 3 viewports: critical flow 3, edge states 9, public guardrails 11, axe 9, QA print state 1
Storybook30 / 30 passed across 6 suites, every play() executed; the a11y addon runs at test: 'error' and caught four defects the app-level scan had not
Guardrailssrc/** and dist/** are read from disk and searched for a states menu, help overlay, reduced-motion toggle, debug panel, inspector, flow-step list, QA toolbar, all 56 state IDs, this page's disclosure sentence, and the strings .stories., storybook, @playwright0 hits in both trees
Audit58 renders, six detectors each, plus 10 contact sheets; the run reports what it finds rather than asserting the build is clean

Accessibility, measured rather than asserted

axe-core ran over the six views and the three edge states at all three viewports — 27 scans — with the WCAG 2.0 / 2.1 / 2.2 A and AA tag sets, scoped to the product surface, with no disableRules() and no waiver list anywhere in the spec. Result: 0 violations of any impact, not merely zero serious and critical.

The scan waits for fonts painted, images decoded and the 160 ms route fade 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 reported a serious color-contrast on a frame nobody sees.

Contrast, read off composited pixels

The audit measures contrast on the pixels as painted — 280 runs, 0 indeterminate — which is how it caught the one finding a DOM walk cannot. text-muted clears AA on paper at 5.02:1, but the product tints the current navigation item and the open source row with 6 % blue, and tints the failure card with 6 % oxblood. Composited, the same colour reads 4.2:1 and 4.49:1.

The same ink, measured on three grounds. Ratios as reported by the audit run.
Ink on groundMeasuredAgainst 4.5:1
text-muted on paper5.02:1passes
text-muted composited over a current nav item's 6 % blue tint4.2:1fails
text-muted composited over the failure card's 6 % oxblood tint4.49:1fails
Those three runs, moved one rung down the ink scale to text-secondarypasses

The palette did not change. Three runs took a darker ink; blue, oxblood and paper kept every tint, chip, border and accent role they already held. A brand colour that fails in one composite is not a brand problem, it is a composite problem.

Keyboard operation, and what is not claimed

Radix carries the dialog, popover, dropdown, tabs and radio-group semantics — focus trapping, Escape, roving focus — and four keyboard and AT defects were found and repaired rather than waived: a role="button" on an <article> that wrapped its own focusable chips; a control nested inside a control on the focused evidence card; a Radix modal dropdown that made the whole app aria-hidden with focusable content inside it, fixed with modal={false} rather than by hiding the rule; and two scroll regions — the transcript and the cited-evidence list — that a keyboard user could not scroll at all, because every coded span is a <mark> and nothing inside them was focusable. The reconciliation radios were given real 24 / 44 px targets while keeping the 16 px painted mark.

What is not claimed: this suite has no keyboard-only walk of the critical flow. Every state the audit reaches, it reaches through a control resolved by role and accessible name, which is a strong signal and not the same proof. It is recorded here as an absence rather than dressed up as a presence.

Folio H Sources, method and stack sheet 8 of 8

Reference material is private, gitignored, and excluded from this package

Every image on this page is a capture of the TraceLab build or an asset commissioned for it

Sources, and what was not taken

On the use of references

Before any screen was drawn, shipped research, annotation and document tools were studied at the level of structure: how an annotation made in a document materialises as a card in a rail and stays bound to its span; how machine suggestions are presented as a reviewable queue rather than as applied changes; how a citation is opened without leaving the reading position; how an approval gate states its own precondition at the point of refusal; how a conflict between two annotations is surfaced without either being discarded.

Nothing else was taken. No source brand, wordmark, colour, photograph, illustration, copy, taxonomy, participant, dataset or proprietary business rule appears anywhere in this build. The reference material is held privately, is excluded from this repository and from this package, and no reference application is named or screenshotted here — every image on this page is a capture of the TraceLab prototype or an asset commissioned for it. The one image inside the product itself is a paper-fragment plate made for the published insight's masthead, and it is load-bearing in that state.

The stack, as installed

React18.3.1
TypeScript5.9.3
Vite5.4.21
Radix UI10 imported directly — Dialog, AlertDialog, Popover, DropdownMenu, Tabs, Collapsible, RadioGroup, ScrollArea, Progress, Tooltip. VisuallyHidden is installed as an eleventh but reaches the UI through Dialog rather than a direct import
Storybook10.5.4 — 6 suites, 30 stories with play()
Playwright1.62.0 — three viewport projects
axe-core4.12.1, via @axe-core/playwright
Source Serif 4transcript, claims and evidence prose
Geistproduct chrome, controls and labels
IBM Plex Monosource IDs, timecodes and counts

The three-role type system is the brand: anything a participant said is set in a serif, anything the product says about itself is set in Geist, and anything that identifies a piece of evidence — S-09 · 04:12 — is set in mono, so a source ID is recognisable as an object even when it appears in running text.

Specimen — the fixture that refused to invent a transcript

apps/tracelab/src/data/fiction.ts excerpt · comment verbatim
/* rail-empty (S-12) — COPY_DECK §2.10. No S-12 transcript body exists in the
 * deck; the reading column is kept present but no P-12 turns are fabricated.
 *
 * The reading column carries its OWN empty state. COPY_DECK §2.10 gives this
 * state a header, the rail's empty copy and nothing else, so the column rendered
 * as 722 px of blank paper under a header reading `recorded 26 min`: a source
 * that is recorded and blank looks broken rather than empty, and the rail's
 * `Select a passage in the transcript to code it.` asks for something the column
 * cannot offer. The two lines below add no fact the deck does not already carry
 * — S-12 exists, its recording is 26 minutes (`headerSub`), and the source index
 * lists it as `0 excerpts` / `Uncoded`. No P-12 turn is invented to close the
 * void; that is the fabrication VISUAL_ACCEPTANCE_STANDARD §4⑥ exists to stop. */
export const CODING_EMPTY = {
  headerTitle: 'Apprentice shift · six bays',
  headerSub: 'S-12 · P-12 · apprentice · 8 months · recorded 26 min',
  railTab: 'Evidence 0',
  emptyLine: 'No excerpts on S-12 yet.',
  emptyBody: 'Select a passage in the transcript to code it.',
  emptyControl: 'Suggest codes',
  footerCount: '0 excerpts coded',
  transcriptEmptyLine: 'No transcript for S-12 yet.',
  transcriptEmptyBody:
    'The 26-minute recording is stored. There is nothing to select until it has been transcribed.',
};

Specimen — both exits from a contradiction, written the same way

apps/tracelab/src/views/Review.tsx excerpt
function resolveConflict() {
  setConflict('resolved');
  setNavBadge(0);
  announce(CONFLICT.resolvedStatus);
  setExtraActivity({ text: CONFLICT.resolvedActivity.replace(' — just now', ''), time: 'just now' });
}
function leaveUnresolved() {
  setConflict('unresolved');
  setNavBadge(1);
  announce(CONFLICT.unresolvedStatus);
  setExtraActivity({ text: CONFLICT.unresolvedActivity.replace(' — just now', ''), time: 'just now' });
}

Neither branch removes an excerpt from anything. They differ in the badge count, the announcement and the line written into the activity log — which is the whole claim of Folio C, in the only place where a claim like that can be checked.