docs(spec): amend §8 severity-map + §6 stories-location + §8.X enforcement

- §8 severity table now covers all 21 values of the 5 mirrored enums
  (ShiftAssignment/ArtistEngagement/Payment/Person/MatchStatus); drops
  3 phantom rows (active/inactive/expired, in no enum). Closes the
  silent grey-fallback gap for 11 production values found in the
  Plan 3 brainstorm self-audit.
- §6 stories-placement reworded: custom/wrapper components (incl.
  PrimeVue wrappers) co-locate; only the ~80 PrimeVue catalog +
  Foundations centralize. Plan 2 misread this.
- New §8.X: bidirectional Vitest consistency test
  (apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts),
  added in Plan 3 — fails on any unmapped enum value OR orphan key.

Plan 3 cleanup tasks (tracked in the Plan 3 plan doc, not here):
  (a) migrate Plan 2's 6 centralized stories to co-located.
  (b) refactor AppTopbar to wrap PrimeVue Menubar per RFC AD-3.

Background: discovered during Plan 3 (Tier-1 primitives) brainstorm
self-audit. Fixes spec-vs-reality drift and two Plan 2 deviations
from binding spec/RFC text; prevents recurrence for future enums.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 19:28:03 +02:00
parent 95496ce216
commit ae0bd2da6c

View File

@@ -281,9 +281,28 @@ Custom components are first-class in Storybook exactly like PrimeVue
ones. crewli-starter `ComponentsPage.vue` (~80 PrimeVue components) is ones. crewli-starter `ComponentsPage.vue` (~80 PrimeVue components) is
the **source of truth for the standard-component catalog**. the **source of truth for the standard-component catalog**.
**Story placement:** custom/wrapper stories co-located next to the **Story placement (binding — two disjoint classes):**
`.vue` (moves with the file at cutover); PrimeVue standard catalog and
Foundations centralized under `stories/`. 1. **Custom/wrapper components.** Every component authored in this repo
— shell pieces (`AppSidebar`, `AppTopbar`, `WorkspaceSwitcher`,
`RightDrawer`, …), Tier-1 primitives (`StatusTag`, `StatCard`,
`StateBlock`, `PageHead`, `TagsInput`, `EnergyDots`, `EnergyPicker`),
`DraggableBlock`, and the template layer — **including components
that internally wrap a PrimeVue component**. Their `.stories.ts` is
**co-located next to the `.vue`** so it moves with the component at
cutover and is deleted with it. This is the default for essentially
all `components-v2/**` work.
2. **PrimeVue standard catalog + Foundations.** The ~80-component
pure-PrimeVue demo gallery (source of truth: crewli-starter
`ComponentsPage.vue`) and the Foundations stories
(Color/Typography/Spacing/Icons/Dark/Density) — neither has an owning
`.vue`. These are **centralized under `stories/`**.
"Wraps a PrimeVue component" does **not** reclassify a component into
the catalog — a wrapper is still custom and co-locates. (Plan 2 misread
the prior wording and centralized 6 shell stories under
`src/stories/v2/`; Plan 3 carries a cleanup task to migrate them — see
commit body.)
**Story tree:** `Foundations/` (Color, Typography, Spacing, Icons, Dark **Story tree:** `Foundations/` (Color, Typography, Spacing, Icons, Dark
mode, Density) · `PrimeVue/` (~80, grouped as in ComponentsPage) · mode, Density) · `PrimeVue/` (~80, grouped as in ComponentsPage) ·
@@ -395,18 +414,42 @@ knowledge, not a per-page decision** — it lives in one map
| Status value(s) | `severity` | Semantics | | Status value(s) | `severity` | Semantics |
|---|---|---| |---|---|---|
| `approved`, `confirmed`, `completed`, `active` | `success` | terminal-good / active | | `approved`, `completed`, `confirmed`, `contracted`, `paid_in_full` | `success` | terminal-good / fully settled |
| `pending`, `pending_approval`, `invited` | `warn` | awaiting action | | `pending_approval`, `pending`, `applied`, `option`, `offered`, `reverted` | `warn` | organizer action required |
| `rejected`, `cancelled`, `declined` | `danger` | terminal-bad | | `invited`, `requested`, `deposit_paid` | `info` | awaiting external party / in-progress — no viewer action |
| `draft` | `secondary` | not yet live | | `none`, `draft`, `dismissed` | `secondary` | muted — absent / not-yet-live / archived |
| `inactive`, `expired` | `secondary` (muted) | dormant | | `rejected`, `cancelled`, `declined`, `no_show` | `danger` | terminal-bad |
| (unmapped fallback) | `info` | + dev-warn so gaps surface | | (unmapped at runtime) | renders `info` + dev console warn | unreachable in a passing build — §8.X consistency test fails on any gap |
Rule: **every** backend status enum mirrored into `src/types/` gets a Rule: **every** backend status enum mirrored into `src/types/` gets a
row here in the same PR that adds the enum (extends the existing row here in the same PR that adds the enum (extends the existing
"mirror backend PHP enums" project rule). `StatusTag` never inlines a "mirror backend PHP enums" project rule). `StatusTag` never inlines a
severity; it always resolves through this map. severity; it always resolves through this map.
### 8.X Enforcement (binding)
The §8 severity map is mechanically enforced by a single Vitest unit
test, `apps/app/tests/unit/utils/statusSeverity.consistency.spec.ts`,
added in Plan 3 alongside `statusSeverity.ts`. It imports the live enum
modules (`ShiftAssignmentStatus`, `ArtistEngagementStatus`,
`PaymentStatus`, `PersonStatus`, `MatchStatus`) and asserts **both**
directions:
1. **Completeness** — every value of every listed enum resolves to an
explicit severity in `statusSeverity.ts`, never the dev-fallback.
Guards the failure mode that left 11 values silent-falling to grey
`info`.
2. **No phantoms** — every key in `statusSeverity.ts` corresponds to a
value present in at least one listed enum. Guards the inverse mode
(the original table mapped `active`/`inactive`/`expired`, which exist
in no enum).
Adding a new mirrored enum, or a new value on an existing one, requires
extending **both** `statusSeverity.ts` and this test's enum-list in the
**same commit**. A silent fallback or an orphan key is a test failure
that blocks CI, not a convention. This extends the existing "mirror
backend PHP enums" rule (CLAUDE.md) with a mechanical gate.
**Tier-2 — Smart Filter subsystem (secure generic version now):** **Tier-2 — Smart Filter subsystem (secure generic version now):**
`SmartFilterBar` + `FilterChip` + `FilterPopover` + `AddFilterMenu` + `SmartFilterBar` + `FilterChip` + `FilterPopover` + `AddFilterMenu` +
5 editors (Text / NumberRange / MultiSelect / EnumGrid / Tag) + 5 editors (Text / NumberRange / MultiSelect / EnumGrid / Tag) +