Define the v1 component system and visual identity #36

Closed
opened 2026-08-03 20:52:58 +00:00 by christian · 2 comments
Owner

Question

Brand settled by author direction: this is a HelmDocs product, a sibling to the existing
bidder-tools product rather than its own brand. So this ticket extends an existing design
system
rather than authoring one - which makes it considerably cheaper than originally scoped.

Where the system already lives

  • packages/config/shadcn.css in novelpad-desktop - :root[data-brand="helmdocs"] and
    .dark[data-brand="helmdocs"] token blocks already exist, light and dark, with
    --primary: 24.6 95% 53.1% (orange). Multi-brand is real infrastructure, not a plan.
  • packages/config/tailwind.config.ts - the shared config.
  • .interface-design/reference/design-system - existing reference material.

What this ticket resolves

1. The capability distinction, re-derived inside the HelmDocs palette. The surface map
prototype coded Issuing as pine and Bidding as indigo. That was a proposal and it does not survive
an orange primary. The two capabilities still need to be distinguishable at a glance - a user must
always know which authorization context they are standing in - but the mechanism has to come from
the existing tokens rather than from two invented accents.

2. Density, designed against the evaluation matrix first. It is the hardest surface on the map:
column pinning, horizontal scroll, fractional scores that cannot round (a real award separated
71.81 from 70.89), and it must stay legible at 200 requirements. A system that works on the vendor
home and falls over here is the likely failure mode, and it is discovered late unless the matrix
leads.

3. State encodings, which are most of the actual work. These are information design, not
decoration, and they must read at a glance in a dense grid:

  • three coverage states - answered / not answered / indeterminate
  • gate pass/fail, which disqualifies rather than scores
  • locked-Pro affordances, where visible-but-locked is the default and absent is a bug
  • cells dimmed by release level, which change as the solicitation advances

Semantic state colour must stay separate from the brand accent, or a gate failure and a primary
action will compete.

4. The component inventory the six surfaces actually need, and which existing shadcn primitives
carry over unchanged.

Carried consequence of the brand decision

Branding the venue as the bidder-tools company means the neutrality argument can no longer be
made by the brand, so it must be made by the product
. A retailer may reasonably ask why the
company selling bid-writing AI is running their evaluation. The answers are mechanisms this map
already decided - sealed bidding until the deadline, the human commit that creates every score, the
bar on the model deciding gates or arithmetic - and they are demonstrable rather than rhetorical.

Design consequence: those mechanisms need to be visible in the retailer surfaces rather than
merely true. This is a design requirement, not a marketing note, and it is carried into
the POC prototype.

Prior art: the surface map prototype - https://claude.ai/code/artifact/91357d1c-96a3-43e0-8a3b-d2da6296b01a


Parent: #1

## Question **Brand settled by author direction: this is a HelmDocs product**, a sibling to the existing bidder-tools product rather than its own brand. So this ticket **extends an existing design system** rather than authoring one - which makes it considerably cheaper than originally scoped. ### Where the system already lives - `packages/config/shadcn.css` in `novelpad-desktop` - `:root[data-brand="helmdocs"]` and `.dark[data-brand="helmdocs"]` token blocks already exist, light and dark, with `--primary: 24.6 95% 53.1%` (orange). Multi-brand is real infrastructure, not a plan. - `packages/config/tailwind.config.ts` - the shared config. - `.interface-design/reference/design-system` - existing reference material. ### What this ticket resolves **1. The capability distinction, re-derived inside the HelmDocs palette.** The surface map prototype coded Issuing as pine and Bidding as indigo. That was a proposal and it does not survive an orange primary. The two capabilities still need to be distinguishable at a glance - a user must always know which authorization context they are standing in - but the mechanism has to come from the existing tokens rather than from two invented accents. **2. Density, designed against the evaluation matrix first.** It is the hardest surface on the map: column pinning, horizontal scroll, fractional scores that cannot round (a real award separated 71.81 from 70.89), and it must stay legible at 200 requirements. A system that works on the vendor home and falls over here is the likely failure mode, and it is discovered late unless the matrix leads. **3. State encodings, which are most of the actual work.** These are information design, not decoration, and they must read at a glance in a dense grid: - three coverage states - answered / not answered / indeterminate - gate pass/fail, which disqualifies rather than scores - locked-Pro affordances, where **visible-but-locked is the default and absent is a bug** - cells dimmed by release level, which change as the solicitation advances Semantic state colour must stay separate from the brand accent, or a gate failure and a primary action will compete. **4. The component inventory** the six surfaces actually need, and which existing shadcn primitives carry over unchanged. ### Carried consequence of the brand decision Branding the venue as the bidder-tools company means **the neutrality argument can no longer be made by the brand, so it must be made by the product**. A retailer may reasonably ask why the company selling bid-writing AI is running their evaluation. The answers are mechanisms this map already decided - sealed bidding until the deadline, the human commit that creates every score, the bar on the model deciding gates or arithmetic - and they are demonstrable rather than rhetorical. **Design consequence:** those mechanisms need to be *visible* in the retailer surfaces rather than merely true. This is a design requirement, not a marketing note, and it is carried into [the POC prototype](https://gitea.stephenmann.io/christian/helmdocs-proposal-system/issues/37). Prior art: the surface map prototype - https://claude.ai/code/artifact/91357d1c-96a3-43e0-8a3b-d2da6296b01a --- Parent: #1
christian added the
wayfinder:prototype
wayfinder:ticket
labels 2026-08-03 20:52:58 +00:00
christian added a new dependency 2026-08-03 20:52:59 +00:00
christian added a new dependency 2026-08-03 20:52:59 +00:00
Author
Owner

Audit: what the HelmDocs system already covers

Run against novelpad-desktop. The gap is three items, one of which is a promotion rather than a
design job.
Almost everything the six surfaces need already exists.

Carries over untouched

67 UI primitives in packages/app/src/components/ui/. Every piece of ordinary furniture the six
surfaces need is present: sidebar (the shell), command + combobox + virtualized-combobox
(the org x capability picker), resizable (the two-pane compose surface), scroll-area, badge,
tooltip, progress, skeleton, empty, card, tabs, separator, sheet, dialog.

Table foundation exists. @tanstack/react-table@8.21.3 and @tanstack/react-virtual@3.13.18
are already dependencies, with a 390-line packages/app/src/components/table/data-table.tsx.

Brand tokens exist, light and dark, at packages/config/shadcn.css:181
(:root[data-brand="helmdocs"]).

The reference system already publishes in Claude Design's format.
.interface-design/reference/design-system/preview/*.html are standalone preview pages, and there
are two kits (ui_kits/app, ui_kits/marketing). This is the DesignSync card shape already.

Gap 1 — semantic tokens are designed but not shipped, and warning collides with the brand

The reference system defines them, in colors_and_type.css:

--np-success: #10B981    --np-warning: #F59E0B
--np-danger:  #EF4444    --np-info:    #06B6D4

But packages/config/shadcn.css ships only --destructive. None of the other three are
tokenized. So promoting them is a small PR, not a design exercise.

The genuine problem: --np-warning is amber, hue ~38. The HelmDocs --primary is
24.6 95% 53.1%, hue ~25. Thirteen degrees apart. At status-dot size, a warning and a primary
action are near-indistinguishable — which is exactly the "semantic state must stay clear of the
accent" constraint this ticket named, arriving as a concrete collision rather than a principle.

The eligibility panel's expiring in 11 days warning and a Submit bid button would read as the
same colour. This is the one real colour decision in the ticket.

Note also that --tag-{amber,coral,cyan,dusk,pink,plum,sage} exists but is identity colour for
tags. Reusing it for state would collide with tag semantics.

Gap 2 — nothing in the codebase pins a column

grep -rn "sticky" packages/app/src/components/table/*.tsx returns nothing. data-table.tsx is a
conventional sortable, paginated list table.

The evaluation matrix is transposed — requirements down, vendors across — and needs a pinned
first column with horizontal scroll. TanStack supports columnPinning natively, so it is buildable
on dependencies already present, but it is a new component rather than a variant of data-table.

Gap 3 — health-indicator is a false friend

packages/app/src/components/common/health-indicator.tsx sounds like the eligibility panel and is
not: it reports db-worker, websocket and auth connection status. Nothing reusable. Flagging it
because the name will mislead whoever picks this up.

Revised scope

  1. Promote the semantic palette into shadcn.css, and resolve the amber/orange collision.
    The only real colour decision.
  2. Build the pinned matrix grid. One new component on existing deps.
  3. Compose the eligibility panel from existing primitives plus (1).

Capability wayfinding may need nothing new. sidebar and command exist, and #23 decided the
nav tree swaps wholesale on context switch — so the mechanism can be structural rather than
chromatic, which sidesteps the palette problem entirely.

Consequence for the demo chain: this is roughly one colour decision, one component, and a
promotion PR. #36 is no longer the long pole — the corpus is.

## Audit: what the HelmDocs system already covers Run against `novelpad-desktop`. **The gap is three items, one of which is a promotion rather than a design job.** Almost everything the six surfaces need already exists. ### Carries over untouched **67 UI primitives** in `packages/app/src/components/ui/`. Every piece of ordinary furniture the six surfaces need is present: `sidebar` (the shell), `command` + `combobox` + `virtualized-combobox` (the org x capability picker), `resizable` (the two-pane compose surface), `scroll-area`, `badge`, `tooltip`, `progress`, `skeleton`, `empty`, `card`, `tabs`, `separator`, `sheet`, `dialog`. **Table foundation exists.** `@tanstack/react-table@8.21.3` and `@tanstack/react-virtual@3.13.18` are already dependencies, with a 390-line `packages/app/src/components/table/data-table.tsx`. **Brand tokens exist**, light and dark, at `packages/config/shadcn.css:181` (`:root[data-brand="helmdocs"]`). **The reference system already publishes in Claude Design's format.** `.interface-design/reference/design-system/preview/*.html` are standalone preview pages, and there are two kits (`ui_kits/app`, `ui_kits/marketing`). This is the DesignSync card shape already. ### Gap 1 — semantic tokens are designed but not shipped, and warning collides with the brand The reference system defines them, in `colors_and_type.css`: ``` --np-success: #10B981 --np-warning: #F59E0B --np-danger: #EF4444 --np-info: #06B6D4 ``` **But `packages/config/shadcn.css` ships only `--destructive`.** None of the other three are tokenized. So promoting them is a small PR, not a design exercise. **The genuine problem:** `--np-warning` is amber, hue ~38. The HelmDocs `--primary` is `24.6 95% 53.1%`, hue ~25. **Thirteen degrees apart.** At status-dot size, a warning and a primary action are near-indistinguishable — which is exactly the "semantic state must stay clear of the accent" constraint this ticket named, arriving as a concrete collision rather than a principle. The eligibility panel's `expiring in 11 days` warning and a `Submit bid` button would read as the same colour. **This is the one real colour decision in the ticket.** Note also that `--tag-{amber,coral,cyan,dusk,pink,plum,sage}` exists but is *identity* colour for tags. Reusing it for state would collide with tag semantics. ### Gap 2 — nothing in the codebase pins a column `grep -rn "sticky" packages/app/src/components/table/*.tsx` returns nothing. `data-table.tsx` is a conventional sortable, paginated **list** table. The evaluation matrix is **transposed** — requirements down, vendors across — and needs a pinned first column with horizontal scroll. TanStack supports `columnPinning` natively, so it is buildable on dependencies already present, but it is a **new component rather than a variant** of data-table. ### Gap 3 — `health-indicator` is a false friend `packages/app/src/components/common/health-indicator.tsx` sounds like the eligibility panel and is not: it reports db-worker, websocket and auth connection status. Nothing reusable. Flagging it because the name will mislead whoever picks this up. ### Revised scope 1. **Promote the semantic palette into `shadcn.css`, and resolve the amber/orange collision.** The only real colour decision. 2. **Build the pinned matrix grid.** One new component on existing deps. 3. **Compose the eligibility panel** from existing primitives plus (1). **Capability wayfinding may need nothing new.** `sidebar` and `command` exist, and #23 decided the nav tree swaps wholesale on context switch — so the mechanism can be structural rather than chromatic, which sidesteps the palette problem entirely. **Consequence for the demo chain:** this is roughly one colour decision, one component, and a promotion PR. #36 is no longer the long pole — the corpus is.
christian self-assigned this 2026-08-03 23:43:32 +00:00
Author
Owner

Resolution

Prototype, rendered at shipping size on the Friendship PCS corpus: https://claude.ai/code/artifact/c69c2fa3-3b4d-42b8-902c-a1b62259e5ec

The audit's headline held up: the gap is small, and most of it is furniture that already ships.
What did not hold up was the framing. The audit called the amber/orange collision "the one real
colour decision in the ticket." It is not a colour decision at all — it is a boundary decision, and
the actual design problem is elsewhere: HelmDocs has no way to say "the document cannot tell
you,"
which is the state this product exists to represent.


Decision 1 — the brand accent does not enter data surfaces

The collision is real: semantic amber at hue 38, the HelmDocs primary at hue 24.6. Thirteen degrees.

Repainting amber was tried on paper and abandoned. Every hue with real distance from an orange
accent is already spoken for — red is --destructive, green is success, and the gap between them
is an olive yellow that fails contrast on white and reads cheap at any size. There is no good
caution hue available to a brand whose accent is orange. Accept that rather than fight it.

So the boundary moves instead of the colour:

Orange is chrome. Primary buttons, the active nav rail, the focus ring. Inside any matrix,
grid or score cell the palette is neutral plus verdict, and the accent does not appear.

All four semantic tokens promote into packages/config/shadcn.css unchanged — no hue moves.
This is a strictly smaller change than the audit scoped.

And the rule that makes 13° survivable everywhere else:

Warning never encodes alone. There is no bare amber status dot in this product. Warning always
carries words — "Insurance certificate expires in 11 days" — so hue is reinforcement, never the
signal.

That is checkable in review, which a palette you have to trust is not. It also means the eligibility
panel's expiry warning and a Submit bid button can share a screen without competing: the button is
the only orange thing on it.


Decision 2 — verdicts carry hue, unknowns carry none

This is the encoding, and it is the substantive half of the ticket.

The axis is not good-versus-bad. It is settled versus unsettled — because that is this map's
thesis: the model may locate but never decide (#15), a human commit creates every score (#15), an
ambiguous gate escalates rather than fails (#16). If the encoding is built on good/bad, all of that
becomes invisible in the one surface where it matters.

State Treatment Why
answered filled green a question got answered
not_answered outlined red read, and absent — a finding, not a gap in the reading
indeterminate achromatic, dashed text exists, does not settle it; routes to a human
not evaluable achromatic, dashed + hatched no document could establish it
gate failed filled red ends the evaluation rather than scoring it — filled, not outlined
locked (Pro) neutral + lock, full opacity entitlement, not visibility. Absent would be a bug (#20, #34)
release-dimmed reduced opacity, never a hue change dimming is about when, not about whether it is good

An evaluator scanning two hundred rows sees colour where there is a finding and grey where a human
is still owed. Triage falls out of the palette instead of needing a filter.

indeterminate and "not evaluable" deliberately share a family. They are the same idea arriving
twice — the artifact cannot say. One is recoverable by a human reading harder, the other never is,
so they take different glyph and fill inside one achromatic family: the eye groups them, the hand
can still separate them.


Decision 3 — density, designed on the matrix first

31 requirements, 12 criteria of which 10 are scoreable, three bidders across the binding gradient.
Four calls are load-bearing, and the corpus forced each one rather than taste choosing it:

Gates render as a pinned band above the scored body. Chesapeake is the cheapest bid in the field
and disqualified on two missing health documents. At row 40 of 200 you would miss that, and the
demo's whole point is that a price-ranked leaderboard puts a disqualified bid on top.

Roll-up parents get no committable cell. METHOD (30) and EXPERIENCE (20) render as bands
showing a derived total; their weighted children carry the cells. ORG_CAPACITY and COMMUNITY
stay scoreable, because their sub-bullets carry no points. This is isScoreable() from
src/scoring/routing.ts appearing on screen, and it is why the rule keys on weight rather than on
nesting — Friendship carries both shapes in one document.

Totals are tabular and never rounded. 82.15 against 81.90. A display that rounded to whole
numbers would show a tie in a contest decided by a quarter point.

A disqualified column keeps its scores, struck and greyed, and shows no total. Hiding them would
be easier and worse — the evaluator has to be able to see what was thrown away, and the absent total
is #10's ban on one composite number doing its job in the one place it is tempting to break it.

Geometry: pinned requirement column 360px, vendor columns 132px, base row height 34px with
single-line clamp and click-to-expand, sticky header and sticky footer total.


Decision 4 — capability is stated in words, not in hue

The surface map coded Issuing as pine and Bidding as indigo. That does not survive an orange primary
— and it was the wrong mechanism regardless.

This is an authorization boundary. A dual-role company's vendor vault is invisible to its own
issuing side, so a user who is confused about which context they are standing in reads a boundary as
missing data and files a bug. Colour alone cannot carry a boundary that consequential, and it
excludes anyone who cannot separate the two hues.

Two signals, neither chromatic:

  • A persistent Acting as {Org} — {Capability} strip at the top of the sidebar. "Acting as"
    phrases it as an authorization statement rather than a label: you are not viewing an org, you
    are acting as one.
  • The sidebar ground shifts one step in tone between contexts — peripheral cue, no second accent.

#23 already swaps the nav tree wholesale. What was missing was a persistent statement of the actor,
because a nav that quietly changed is a nav you do not notice changed.

The audit's guess was right for the wrong reason: capability wayfinding needs no new colour, but
it does need a new (composed) component.


Build list

Four components. <EvaluationMatrix> (TanStack columnPinning + react-virtual, both already
dependencies — a new component, not a data-table variant), <CoverageMark> (the seven-state
vocabulary above; one component, one enum), <GateBand>, <ContextStrip>.

One promotion PR: --np-success, --np-warning, --np-info into shadcn.css beside the
--destructive that already ships. No hue changes.

67 primitives carry over untouchedsidebar, command, combobox, resizable, badge,
tooltip, progress, skeleton, empty, card, tabs, sheet, dialog, separator,
scroll-area, and data-table for list surfaces.

Do not reach for: health-indicator (false friend — it reports db-worker/websocket/auth
connection status), --tag-* (identity colour; reusing it for state collides with tag semantics),
or a second accent.


Carried into #37

The brand decision means neutrality cannot be argued by the brand, so it must be argued by
mechanism. Three of the four decisions above are where those mechanisms become visible rather
than merely true, and they are the ones to put in front of a retailer:

  • the sealed field opening at once, stated in the matrix chrome
  • Commit scores as the primary action — the human commit is the loudest control on the surface
  • gates as a pinned band the model never decides, and indeterminate marks that route to a person
    instead of resolving themselves

Side effect: one drift closed

Pulling corpus data into the prototype surfaced a name drift — the truth files used
determinableFromResponseDocument while the schema column and gateEvaluableFromResponse() use
determinableFromResponse. Read through the default, R-B6C ("two bid copies submitted") would have
started failing bidders over a fact no response document can establish. Renamed, and
validate-truth.py now mirrors the gate rule so it cannot recur silently — a36956b:

gates 13, evaluable from the response 12, excluded 1
  excluded: R-B6C  Two bid copies submitted.

What this does not settle

No component is built. This ticket fixes the vocabulary and the geometry; the first real
<EvaluationMatrix> will find things a static page cannot — column resize behaviour under 10+
vendors, virtualisation against a sticky footer, and how the expand-in-place row interacts with
horizontal scroll. Those belong to #37, not here.

## Resolution Prototype, rendered at shipping size on the Friendship PCS corpus: https://claude.ai/code/artifact/c69c2fa3-3b4d-42b8-902c-a1b62259e5ec The audit's headline held up: the gap is small, and most of it is furniture that already ships. What did **not** hold up was the framing. The audit called the amber/orange collision "the one real colour decision in the ticket." It is not a colour decision at all — it is a boundary decision, and the actual design problem is elsewhere: **HelmDocs has no way to say "the document cannot tell you,"** which is the state this product exists to represent. --- ### Decision 1 — the brand accent does not enter data surfaces The collision is real: semantic amber at hue 38, the HelmDocs primary at hue 24.6. Thirteen degrees. **Repainting amber was tried on paper and abandoned.** Every hue with real distance from an orange accent is already spoken for — red is `--destructive`, green is success, and the gap between them is an olive yellow that fails contrast on white and reads cheap at any size. There is no good caution hue available to a brand whose accent *is* orange. Accept that rather than fight it. So the boundary moves instead of the colour: > **Orange is chrome.** Primary buttons, the active nav rail, the focus ring. Inside any matrix, > grid or score cell the palette is neutral plus verdict, and the accent does not appear. All four semantic tokens promote into `packages/config/shadcn.css` **unchanged** — no hue moves. This is a strictly smaller change than the audit scoped. And the rule that makes 13° survivable everywhere else: > **Warning never encodes alone.** There is no bare amber status dot in this product. Warning always > carries words — *"Insurance certificate expires in 11 days"* — so hue is reinforcement, never the > signal. That is checkable in review, which a palette you have to trust is not. It also means the eligibility panel's expiry warning and a `Submit bid` button can share a screen without competing: the button is the only orange thing on it. --- ### Decision 2 — verdicts carry hue, unknowns carry none This is the encoding, and it is the substantive half of the ticket. The axis is **not** good-versus-bad. It is **settled versus unsettled** — because that is this map's thesis: the model may locate but never decide (#15), a human commit creates every score (#15), an ambiguous gate escalates rather than fails (#16). If the encoding is built on good/bad, all of that becomes invisible in the one surface where it matters. | State | Treatment | Why | |---|---|---| | `answered` | filled green | a question got answered | | `not_answered` | outlined red | **read, and absent** — a finding, not a gap in the reading | | `indeterminate` | achromatic, dashed | text exists, does not settle it; routes to a human | | not evaluable | achromatic, dashed + hatched | no document could establish it | | gate failed | **filled** red | ends the evaluation rather than scoring it — filled, not outlined | | locked (Pro) | neutral + lock, full opacity | entitlement, not visibility. Absent would be a bug (#20, #34) | | release-dimmed | reduced opacity, **never** a hue change | dimming is about *when*, not about *whether it is good* | An evaluator scanning two hundred rows sees colour where there is a finding and grey where a human is still owed. Triage falls out of the palette instead of needing a filter. **`indeterminate` and "not evaluable" deliberately share a family.** They are the same idea arriving twice — the artifact cannot say. One is recoverable by a human reading harder, the other never is, so they take different glyph and fill inside one achromatic family: the eye groups them, the hand can still separate them. --- ### Decision 3 — density, designed on the matrix first 31 requirements, 12 criteria of which 10 are scoreable, three bidders across the binding gradient. Four calls are load-bearing, and **the corpus forced each one** rather than taste choosing it: **Gates render as a pinned band above the scored body.** Chesapeake is the cheapest bid in the field and disqualified on two missing health documents. At row 40 of 200 you would miss that, and the demo's whole point is that a price-ranked leaderboard puts a disqualified bid on top. **Roll-up parents get no committable cell.** `METHOD` (30) and `EXPERIENCE` (20) render as bands showing a derived total; their weighted children carry the cells. `ORG_CAPACITY` and `COMMUNITY` stay scoreable, because their sub-bullets carry no points. This is `isScoreable()` from `src/scoring/routing.ts` appearing on screen, and it is why the rule keys on *weight* rather than on nesting — Friendship carries both shapes in one document. **Totals are tabular and never rounded.** 82.15 against 81.90. A display that rounded to whole numbers would show a tie in a contest decided by a quarter point. **A disqualified column keeps its scores, struck and greyed, and shows no total.** Hiding them would be easier and worse — the evaluator has to be able to see what was thrown away, and the absent total is #10's ban on one composite number doing its job in the one place it is tempting to break it. Geometry: pinned requirement column 360px, vendor columns 132px, base row height 34px with single-line clamp and click-to-expand, sticky header and sticky footer total. --- ### Decision 4 — capability is stated in words, not in hue The surface map coded Issuing as pine and Bidding as indigo. That does not survive an orange primary — and it was the wrong mechanism regardless. This is an **authorization boundary**. A dual-role company's vendor vault is invisible to its own issuing side, so a user who is confused about which context they are standing in reads a boundary as missing data and files a bug. Colour alone cannot carry a boundary that consequential, and it excludes anyone who cannot separate the two hues. Two signals, neither chromatic: - A persistent **`Acting as {Org} — {Capability}`** strip at the top of the sidebar. "Acting as" phrases it as an authorization statement rather than a label: you are not *viewing* an org, you are acting as one. - The sidebar ground shifts one step in tone between contexts — peripheral cue, no second accent. #23 already swaps the nav tree wholesale. What was missing was a persistent statement of the actor, because a nav that quietly changed is a nav you do not notice changed. **The audit's guess was right for the wrong reason:** capability wayfinding needs no new colour, but it does need a new (composed) component. --- ### Build list **Four components.** `<EvaluationMatrix>` (TanStack `columnPinning` + `react-virtual`, both already dependencies — a new component, not a `data-table` variant), `<CoverageMark>` (the seven-state vocabulary above; one component, one enum), `<GateBand>`, `<ContextStrip>`. **One promotion PR:** `--np-success`, `--np-warning`, `--np-info` into `shadcn.css` beside the `--destructive` that already ships. No hue changes. **67 primitives carry over untouched** — `sidebar`, `command`, `combobox`, `resizable`, `badge`, `tooltip`, `progress`, `skeleton`, `empty`, `card`, `tabs`, `sheet`, `dialog`, `separator`, `scroll-area`, and `data-table` for list surfaces. **Do not reach for:** `health-indicator` (false friend — it reports db-worker/websocket/auth connection status), `--tag-*` (identity colour; reusing it for state collides with tag semantics), or a second accent. --- ### Carried into #37 The brand decision means neutrality cannot be argued by the brand, so it must be argued by mechanism. Three of the four decisions above are where those mechanisms become **visible** rather than merely true, and they are the ones to put in front of a retailer: - the sealed field opening at once, stated in the matrix chrome - **`Commit scores`** as the primary action — the human commit is the loudest control on the surface - gates as a pinned band the model never decides, and `indeterminate` marks that route to a person instead of resolving themselves ### Side effect: one drift closed Pulling corpus data into the prototype surfaced a name drift — the truth files used `determinableFromResponseDocument` while the schema column and `gateEvaluableFromResponse()` use `determinableFromResponse`. Read through the default, R-B6C ("two bid copies submitted") would have started failing bidders over a fact no response document can establish. Renamed, and `validate-truth.py` now mirrors the gate rule so it cannot recur silently — `a36956b`: ``` gates 13, evaluable from the response 12, excluded 1 excluded: R-B6C Two bid copies submitted. ``` ### What this does not settle No component is built. This ticket fixes the vocabulary and the geometry; the first real `<EvaluationMatrix>` will find things a static page cannot — column resize behaviour under 10+ vendors, virtualisation against a sticky footer, and how the expand-in-place row interacts with horizontal scroll. Those belong to #37, not here.
Sign in to join this conversation.
No description provided.