control-center-consolidation
Consolidate the overlapping Control Center / Policy & Enforcement / Compliance work — current-state evaluation, ranked consolidation plan, initial-ideas mock
Why
Parallel dash-ui efforts left qdash with three overlapping surfaces: /controls (Control Center), /policy (Policy & Enforcement), and /compliance + /framework. The policy page and Control Center duplicate the control table and its authoring flow with diverging numbers; compliance work overlaps both. Exploration showed the parallelism is temporal, not branch-level — three qcontrol cycles (c3/c4/c5) landed within 5 days of each other around a Control Center that was never reconciled, and the queued component-consolidation cycle (qcontrol c2) never ran. An in-depth design pass follows this cycle; c64's job is a crystal-clear picture of the current state plus low-hanging-fruit consolidation moves.
Objective
Three deliverables: (1) current-state.md — the duplication map, findings numbered F1–F10 so downstream work can cite them; (2) consolidation-plan.md — ranked low-hanging fruit + questions reserved for the deep pass; (3) /pages/control-center-ideas — a c61-style ideas mock with four initial consolidation concepts. Explicitly NOT solving everything — understanding + quick wins only.
Scope
cycles/c64.control-center-consolidation/— current-state.md, consolidation-plan.mdapp/pages/pages/control-center-ideas.vue+ page-registry entry- Read-only inputs: qcontrol
crates/qdash/uipages +docs/plan/qdash-information-architecture.md+rules.rsPACKS; bob-wirec17-governance.ts; design c54/c56/c63 cycles - No qdash code changes in this cycle (the plan is the deliverable; execution is qcontrol-side follow-up)
Key Changes
- Exploration: qdash pages + git history (parallelism is temporal, not branch-level — c3/c4/c5 in 5 days around an unreconciled /controls; qcontrol c2 never ran) and design-site/bob-wire reference inventory (Gen 1 card era vs Gen 2 table era; no Control Center mock existed)
current-state.md— timeline, page inventory, findings F1–F10, mock inventory, and the normalized control vocabulary (14 controls × c17 family × rules.rs packs) that doubles as the mock datasetconsolidation-plan.md— 9 ranked low-hanging-fruit items tagged F# with sequencing, + 5 questions reserved for the deep pass/pages/control-center-ideasmock (app/pages/pages/control-center-ideas.vue, registered in page-registry): Idea 1 "One vocabulary" (normalized table, findings vs would-catch as distinct labeled counts), Idea 2 "One surface, three lenses" (working Controls/Enforcement/Coverage tabs per the IA doc — recommended), Idea 3 "Pack toggle = the bridge" (live pack toggles → provable/attestation/out-of-scope buckets, one server evidence CTA), Idea 4 "Act/Prove split" (compressed alternative for the deep pass to adjudicate)- Iteration direction chosen (2026-08-10): start from the existing controls table and iterate.
/pages/control-center(app/pages/pages/control-center.vue) — 1:1 replica of qdash /controls: template + helper logic copied verbatim (same layer DataTable), Panel/PageHeader chrome inlined, live-data snapshot from /api/controls + /api/security (12 seeded controls, 107 findings, match counts egress 83 · quota 19 · shadow 4 · budget 1), pack enablement as a ref standing in for localStorage; GovernSheet/downloads/cross-links stubbed with titled affordances - Iteration 1 — category sections: controls grouped by GovernSheet category (8 categories, dropdown order, empty ones dropped) using /policy's section-row idiom; section rows carry control count + rolled-up match total; DataTable swapped for its underlying UxTableList to host section rows, all cells + expand details unchanged. Real categories pulled from /api/controls (autonomy ×3, egress ×2, consumption ×2, privilege/posture/data/target/coverage ×1)
- Iteration 2 — two-column grid (non-destructive):
/pages/control-center-v2(control-center-v2.vue, registered) — categories as half-width cards in a lg:grid-cols-2, sunken header bands with count + rolled-up matches, compact control rows (name/condition + severity + matches, pills + edit on line two). Trade-offs vs iteration 1 noted on-page: one-screen catalog and one-control categories stop dominating, but loses the aligned column grid and expand-row details - Iteration 3 — category column (non-destructive):
/pages/control-center-v3(control-center-v3.vue, registered) — the replica's flat DataTable exactly as-is plus one Category column (GovernSheet label as a neutral chip). Lightest touch: no grouping chrome, category becomes scannable/sortable. All three variations + ideas page verified rendering. (Ops note: the dev-server page watcher went stale twice today — new-page routes 404ing until a server restart; restarted twice, qdash :3030 untouched) - Control expand row (two-section):
/pages/control-expand(control-expand.vue, registered) — the controls table with two-section expand cards (inventory-agent-expand-v2 idiom: bare ExpandRow + stacked UxTableListExpandSection, close X on section 1, egress auto-opens per variation). Section 1 = read (name · active findings · framework coverage), section 2 = edit (condition · decisions · actions, sentence-family pills). Three variations: V1 big-name header + three-column workbench, V2 findings-forward (real egress snapshot rows) + SentenceRow-labeled sentence flow with pill-toggle decisions, V3 one-line identity strip + dense two-column edit. Edit state deliberately shared across variations ("one model, three skins"). V4 (2026-08-11): V1's read header + V3's edit as one flex row — condition flex-1 with UxAddBtn for AND clauses (mono summary line dropped), decisions/actions compact columns, save button rule-separated beneath, space-y-8 between sections - V5 (2026-08-11, based on V4): three sections — V4's read header · condition (one-line, UxAddBtn) + clickable severity chips + Category selector (all 8 GovernSheet categories, .ctl-style select with helper caption, forms-plugin chevron) · third section with the GovernSheet's FULL option sets (all 7 decisions with hints, remediation input, all 5 when-it-happens actions incl. require-approval/route with proj marks, all 10 Satisfies packs editable), save beneath. Edit state extended (sev/remediation/approval/route/packs) — shared across variations
- Condition adding wired (2026-08-11): edit model refactored to a clause array; V4 + V5's UxAddBtn appends an AND clause (signal-appropriate default field via a FIELDS-per-signal map, per-clause ✕ remove, 4-clause cap mirroring GovernSheet); first-clause field select upgraded from single-option to the signal's full field list
- V5 refinements (2026-08-11): Category moved from section 2 to section 3 left column (above Remediation, GovernSheet order); Save control right-aligned
- Idea 3 shipped to qdash (2026-08-11):
qcontrol/crates/qdash/ui/app/pages/controls.vue— the packs card grid replaced with the managed-list picker (enabled-only removable chips in qdash's primary tint + SentenceSelect add-pill over the not-yet-enabled remainder). Panel title/count/intro/localStorage persistence unchanged; add flows through togglePack so persistence stays in one place. Uncommitted in the qcontrol working tree. First qdash code change of c64 (scope extended by Mark). Note: still the 6-pack list — expanding to the full 10-pack rules.rs vocabulary is consolidation-plan item 2, deliberately not smuggled into this UI change/pages/framework-picker-ideas(framework-picker-ideas.vue, registered) — four compact alternatives to the /controls pack-card grid, rendered against the current grid as baseline at the TRUE 10-pack catalog size (the shipped panel shows 6 — an F2 drift). One shared enabled-set across all variations. Idea 1 toggle chips (all options visible, ~6× less vertical), Idea 2 popover picker (one-line closed state, enable-all), Idea 3 enabled-only + SentenceSelect add-pill (managed-list steady-state), Idea 4 GovernSheet-style checkbox wrap (panel and sheet would read identically) - Layer change — ExpandSection wraps text (2026-08-11):
UxTableListsetswhitespace-nowrapon the whole table (cell behavior), which expand-card content inherits — long captions then set the column's min-content and overflow the card padding (seen on V5's Satisfies caption). Fix:ExpandSectionroot now carrieswhitespace-normal— expand cards are prose-shaped, not cells. Benefits every ExpandSection consumer - Layer change — SentenceSelect sizes to current selection (2026-08-11): rebuilt
q-nuxt-layer/components/sentence/Select.vueon the mirror-span technique — an invisible in-flow span carrying the selected option's label (same font/padding/border metrics) sets the pill width, with the select overlaidabsolute inset-0 w-full h-full. First attempt used inline-grid +min-w-0and did NOT work: an auto grid track still sizes to the select's max-content (its widest option) — the select must be out of flow entirely. Selected label read fromselectedOptions[0].text(mount/update/change sync; SSR seeds from modelValue). Side-find: the ⌄ chevron on these pills comes from the layer's@tailwindcss/forms(base strategy) background-image, not the component —appearance-nonenever affected it, so the component's appearance-suppression was dead code (removed). Chevron reserve slimmed pr-12 → pr-8 (~8px gap between content and the forms-plugin chevron at right 0.5rem) - Layer change — SentenceText de-greedied (2026-08-11): removed the hardcoded
flex-1 min-w-[200px]fromq-nuxt-layer/components/sentence/Text.vue; the pill now sizes to content via the nativesizeattribute (content length, falling back to placeholder length, floor 4) so it sits inline in a sentence like the other pills. Call sites wanting fill passclass="flex-1". Affects all consumers at next layer release — check NotificationRuleCard-style URL fields when bumping - Control detail page:
/pages/control-detail(control-detail.vue, registered) — the GovernSheet edit modal promoted to a full page (the per-control detail the plan reserved; Figma 12499:26869 territory). Copy, field order, and .ctl/.proj chrome verbatim from qdash GovernSheet.vue; page adds a context header (breadcrumb, name, severity/category chips, mono id, live matches) and a two-column Definition / Response-&-mapping split with bottom save bar. Exemplar = seeded crit control matching Mark's screenshot. Compile-verified (no dev server was running — all servers killed after the .nuxt collision) - V5 shipped to qdash (2026-08-11):
qcontrolcommit376baabbonfeat/control-center—/controlsexpand row is now the three-section V5 card (ControlExpandCard.vue), replacing the link-list details and the edit-via-modal path for existing controls. Faithful to the mock with reality corrections: vocabulary-backed fields/signals/ops/decisions instead of the mock's hardcoded lists, staticand(backend rules are AND-only — the mock's and/or pill was aspirational), GovernSheet's dry-run preview kept under the sentence, section-1 header shows saved values not the draft. Table gains the category column (5 cols, actions kept per Mark). Hygiene rode along: GovernSheet's authoring engine extracted touseControlDraft(modal + card share it; props/emits contract unchanged so policy.vue promote is untouched), shareduseControlVocabcache, realControlinterface + testedsevNorm/hasAlertinlib/control.ts,.projdeduplicated intoProjMark.vue, and the mock's denserowRefscallback-ref pattern dropped entirely (DataTable'sbareDetails+ slot-scopedclosecovers it). Two latent bugs fixed in passing: the dry-run timer now clears on unmount, and the follow-the-rule watchers areimmediate(the shared vocab cache is usually warm before a card mounts, so vocab-change triggers alone never fired — caught via headless-browser verification: New-control category select rendered empty). Layer released as v0.9.7 (the three sentence/ExpandSection changes above); all consumer lockfiles refreshed. Verified end-to-end in headless Chromium against the live backend: expand/dry-run/severity/add-remove clause/close-X/save-as-copy round trip + New-control modal, zero console errors;dist/regenerated via the isolated build.
Visual record
14 captures — heroes, page slices, and full-page renders. Click to view.














Artifacts
- initial-prompt.md
- current-state.md
- initial-prompt.md