Source: qdash /teams — c67 state machine + component mapping, 2026-08-14
Two clarity moves. First, v5's four states become one template with a switcher — proof the page is a state machine with conditional sections, not four designs that happen to rhyme. Second, an honest audit of the composition against the layer: what maps to existing components, what qdash already wraps, and the three things that exist nowhere. A vocabulary fix rides along: assign is the act, mapped is the state, attributed is the money.
One template, four states
Everything below is a single markup tree driven by a state config — switch states and watch sections appear, collapse, and change rank. The switcher itself is the layer's UxTabGroup; in the product these states are derived from data, never chosen.
| Person | Team | Risk story | Identity | Activity | Spend · 30d |
|---|---|---|---|---|---|
f felixalias | Data | 1 Critical — undecided egress · Sun | consumer key | 2 agents · 31m ago | $70 |
s sofia | Platform | 1 Elevated — secrets.yaml read · Fri | org ✓ | 2 agents · 2h ago | $98 |
p priya | Payments | ok | org ✓ | 3 agents · 12m ago | $169 |
c chennew | ok | consumer key | 1 agent · 19m ago | $4 | |
| + 10 more, all ok — show → | $693 | ||||
Teams
Section × state
The whole page in one table — what renders when, and what triggers each transition. This is the spec the teams.vue rewrite implements as conditionals.
| Section | First run | All clear | Attention | At scale (~30+) |
|---|---|---|---|---|
| Verdict line | onboarding instruction | "Nothing needs you" | names the work + $ | counts + workspace link |
| Summary strip | observed/0%/spend/— | all-green subs | mapped % amber | same, bigger numbers |
| Credential note | hidden | hidden (0 consumer keys) | neutral, → /identity | folds into /identity link |
| Search + filters | hidden | hidden < 30 people | hidden < 30 people | primary, above roster |
| Roster | hidden — queue is the page | spend-sorted ledger, no story col | risk-sorted, story + identity cols | exceptions only, story col, tail → search |
| Expand row | — | agents + contexts (v5 design) | agents + contexts (v5 design) | same |
| Workspace | expanded hero + progress | one quiet IdP line | band: assign here ▾ / open workspace → | band: escalation only (queue > ~8) |
| Teams | empty state | chips, no amber | chips + unassigned chip | top 5 + unassigned, tail → Cost Center |
One verb per concept
v5 mixed "map", "assign", and "attribute" freely. Fixed grammar, applied throughout the machine above — v5's "map here ▾" is now "assign here ▾".
A human puts an identity on a team. Buttons say it: "assign →", "assign here ▾", "Assign to Data". The only write verb on the page.
An identity that has an owner. The coverage stat ("71% mapped"), the workspace queue ("needs assigning"), the IdP line ("100% mapped").
Spend that landed on a mapped identity. The strip, the band ("$306 unattributed"), and Cost Center all use it — never "assigned spend".
The composition against the pantry
Audited against the component registry (151 components) and qdash's locals. v5 hand-rolled nearly everything — only UxPageTitle and NavJumpPills were real. The good news: the table, chips, bars, and search all map cleanly. Three things exist nowhere.
| Element | Maps to | Verdict | Notes |
|---|---|---|---|
| Page title + note | qdash PageHeader → UxPageTitle | use | PageHeader (qdash local) wraps UxPageTitle and adds the subtitle + #note slot the layer lacks. Promote to layer as-is. |
| Verdict line | — nothing exists | new | Zero "verdict" hits in the layer. Build on UxLink for verbs. Shared need with c68 index landing — extract once, use twice. |
| Stat tiles | qdash StatTile → DataMetric_Base | extend | StatTile's sub-line is always muted; the machine needs toned subs (amber/green/red). Add subTone or a sub slot, then promote. |
| Merged caveat | ProseCallout (partial) | extend | ProseCallout is left-border only, tones grape/grey/leaf. The caveat needs a boxed sunken variant; qdash attention/Strip is the chip-row cousin. |
| Roster table + expand | DataTable (#details, search, sort) | use | Already in the layer ("unifies qdash DataTable + PermissionTable"). Column search covers the scale state. Expand body is the redesigned agent list. |
| Risk-story cell | — cell pattern | new | severity + sentence + verb-link in one cell. Candidate for a #story column slot component; derivation fn lives beside userRollups. |
| Identity chip | UxPill tone=grey | use | Neutral per the v5 grammar. AgentAvatar/AgentInline cover the agent side in expand rows. |
| Filter chips (✕) | UxTag | use | UxTag has the remove affordance; qdash AgentFilterChip shows the routed variant. Wire to useUrlState. |
| Context chips | UxCode | use | Mono chip bg, exactly the top_context treatment in the expand row. |
| Amber band | — nothing exists | new | Hand-rolled 3× in v5 with identical classes. Strongest extraction on the page — but amber must enter the tone vocabulary first (UxPill stops at warn). |
| Team chip + share bar | UxPercentBar (partial) | extend | UxPercentBar renders fill behind a label — no standalone track. Add a bare track variant, or extract the chip whole (DataSurfacePanel covers the ranked-list cousin). |
| Onboarding progress | UxPercentBar / DataPercentBar | use | The first-run "3 of 8 assigned" line is a compact UxPercentBar + caption. |
| Adoption deltas | qdash Spark / DataMetricTrend | use | Folded into stat subs and chip pills (v5 call) — Spark covers it if a trend cell ever returns. |
| State switcher (mock only) | UxTabGroup | use | Used live in the machine above. Product derives state from data — the switcher never ships. |
| Avatars | UxAvatar | use | Used live in the machine above (initials, small). pending prop covers SCIM-day invited rows later. |
What the layer gains — post-checkpoint
Ranked by reuse breadth. One prerequisite for all of them: amber enters the layer's tone vocabulary — today UxPill stops at warn, ProseCallout at grape/grey/leaf, and the attribution grammar has no token to stand on.