← Design · c121

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. Emits update: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 until set and replaces given: a quiet line "then {replaces}" (D6).
  • Optional #reason slot 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

  1. Write governance-impact.ts first and tell 00 it exists (00 imports it).
  2. Build both components against hand-written Impact fixtures in parts.vue (minimal / typical / large / mixed tighten+loosen / empty) — the demo is the test.
  3. Register them in the parts registry so /pages/phase4/mock/parts lists 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")?

Qpoint Brand Style Guide