The drawer already knew how to upload. This Story makes it say whose — loudly enough to see from across the desk, and honestly enough that a counsel who has picked nothing is never staring at a dead dropzone with no explanation.
An armed drawer reads as its side. Pick "Opponent's" and the drop target turns crimson and says Drop opponent documents; pick nothing and it is visibly, explicably closed.
The armed-dropzone flow shipped in Story-2111: the five source chips (Client's / Opponent's / Other / Strategy / Privileged), the presign+finalize upload mechanics, the background-upload footer, the per-row side chips, and the privileged refusal. None of that moved. This ticket is colours, states, and copy — a surgical S.
| # | Frame said | Drawer did | Now |
|---|---|---|---|
| 1 | An eyebrow above the sources: SELECT SOURCE TO BEGIN, settling to SOURCE SELECTED once armed. |
No eyebrow. The ask lived inside the dropzone, so the disabled zone had to carry two jobs at once. | The eyebrow states the ask up front; the zone is freed to describe itself. |
| 2 | Disabled zone reads Drag files here, greyed. | Read "Select a source to begin" — the instruction, in the place where the invitation belongs. | dragFilesHere, in ink-meta-muted. The zone now looks like what it is: a dropzone that is currently shut. |
| 3 | The armed zone takes the side's ink — label and icon, not just border and fill. | Border and fill recoloured; the label stayed ink-strong for every side. |
Each armed source carries an ink class. Green-8 / crimson / ink-strong / blue-3. |
| 4 | A Done affordance, bottom-right of the footer. | Missing entirely. The only exit was the ✕. | A subtle SecondaryButton that calls onClose. |
Client's is green, Opponent's is crimson — those are the two poles of the
adversarial axis. "Other" is not a third pole; it is the absence of a
claim about the axis. Giving it a colour of its own would imply a side it does
not have, so it stays neutral: surface-soft fill,
ink-strong label. The register's side badges already read this
way; the drawer now agrees with them.
Every colour resolves through design/v1/colors_and_type.css. The
frame's hexes map onto stops that already exist — no token proposal was
needed:
| Frame | Token | Used for |
|---|---|---|
#1D9E75 | --green-9 | Client's zone border (vivid accent — the selection has to read as on) |
#085041 | --green-8 | Client's zone ink |
#A83A4A | --crimson-13 | Opponent's zone border |
#8B2332 | --crimson | Opponent's zone ink |
#F5F5F5 / #1A1A1A | --surface-soft / ink-strong | Other (neutral) |
Semantic green and crimson here are carrying side, not status — which is exactly the axis the Record register already colours by, so the drawer and the register now speak one language rather than two.
Two new keys under Matters.import, added to
messages/en.json and messages/hi.json in the
same change: sourceSelected and dragFilesHere. The
per-side drop copy (dropClient / dropOpponent /
dropOther) and the trust line already existed in both locales and
were reused, not re-authored. Done reuses Common.done.
src/components/matters/import-into-matter-sheet-v2.stories.tsx:
| Story | State |
|---|---|
Empty | No source — dropzone visibly disabled, eyebrow asks |
ArmedClient | Green accent, "Drop client documents" |
ArmedOpponent (new) | Crimson accent, "Drop opponent documents" |
ArmedOther (new) | Neutral, "Drop other documents" |
Importing | Uploading / reading / registered / failed rows + background-upload footer |
ArmedStrategy, Privileged, ReadOnly, AllRegistered, RegisteredExit, FromTheRail | Pre-existing, unchanged |
pnpm lint (0 errors) ·
pnpm test (1158 passed) · pnpm storybook:check
(drift + build green) · pnpm build green. All five states
screenshotted from the built Storybook and attached to the PR.
SecondaryButton and re-hardcoding brand
classes on a raw button — the exact thing the button doctrine forbids. The
zone's border, fill, icon and label already carry the side unmistakably; the
buttons stay on the shared primitive. If founder review wants the tint, the
right move is an accent variant on the primitive, not a local one-off.
4 file(s), +57 / -14 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +2 | -0 |
| messages/hi.json | Config | +2 | -0 |
| src/components/matters/import-into-matter-sheet-v2.stories.tsx | Component | +14 | -0 |
| src/components/matters/import-into-matter-sheet-v2.tsx | Component | +39 | -14 |
Pick a side and the drawer becomes that side; pick nothing and it says so.