plan/01-shared-components.md
01 · Shared components: ModeUntil + BlastRadius (Wave 1)
TL;DR: two components every Wave-2 surface uses. ModeUntil is the one Off · Observe · Enforce control with an optional Until, the same on the policy and in the exception editor (Logseq screenshot ../sources/logseq-mode-until.png). BlastRadius says, simply and visually, what a change does: who, which teams, who drops out of visibility, what tightened (blue), what loosened (red), which findings resolve or reopen.
Owns: new app/data/governance-impact.ts (the Impact interface, README §4, verbatim + a Direction helper), new app/components/governance/mock/ModeUntil.vue, new app/components/governance/mock/BlastRadius.vue, demo rows in app/pages/pages/phase4/mock/parts.vue, parts registry entries in app/data/governance-parts.ts (registry only; 06 later edits the rail in the same file).
Assemble first (CLAUDE.md: design is assemblage): layer UxTabGroup (black/white, c110) for the mode, UxInput/native date like today's sheet, UxTooltip, DataMetric, UxAvatar/name chips, ComplianceTag-style pills; look at the qdash live inventory tiles and DataHistoryGraph before drawing anything new. Check ../q-nuxt-layer/CLAUDE.md for the catalog. No arbitrary-value Tailwind; rose-* not red-* (gotcha); --qp-* vars are RGB triplets.
ModeUntil
- Props:
modelValue: { mode, until? },enforceable(hide Enforce),replaces?: Mode(to show "then Enforce" and decide loosening),compact. Emitsupdate:modelValue. - Layout per the screenshot: label column "Mode" / "Until"; Until is a date input with a trailing hint: "optional: it holds until revoked" when tightening/equal; when loosening (D7) a rose hint "No end date: this stays open until someone revokes it" — never blocks.
- When
untilset andreplacesgiven: a quiet line "then {replaces}" (D6). - Optional
#reasonslot under it so editors can attach the required reason field (D1) without the control owning it.
BlastRadius
- Props:
impact: Impact,variant: 'full' | 'inline',label?. No data fetching — pure presentation. full(exception editor, draft review): headline sentence ("Loosens P-MCP for 3 people in Finance. Small blast radius."), then rows: People (avatars/names with from → to), Teams (team · affected of total, a thin bar), Out of visibility (D9, only when non-empty), Tightened (blue-ish chips) / Loosened (rose chips), Findings (N resolve · M reopen · K resolve as Enforced, ids linking to S1).inline(one line for list rows / tray): direction dot + "3 people · 1 team · 2 findings reopen".- Size words: ≤5 people "small", ≤25 "medium", else "large" — one constant, documented.
- Empty: "No one's mode changes." (still show findings if any).
Steps
- Write
governance-impact.tsfirst and tell 00 it exists (00 imports it). - Build both components against hand-written
Impactfixtures inparts.vue(minimal / typical / large / mixed tighten+loosen / empty) — the demo is the test. - Register them in the parts registry so
/pages/phase4/mock/partslists them.
Done when
/pages/phase4/mock/parts shows both with all five scenarios, light screenshots in shots/01-*.png; no edits outside owned files.
Look for (review)
Is the blue/red legible without the legend? Does "small blast radius" read as the confidence moment Rob described ("This is 3 people … I'm comfortable")?