# Operator UX Foundation (M7) Working journal for the Milestone 7 effort. Research findings, IA sketches, design-system decisions, audit results, and convention drift land here as they happen. Authoritative milestone tracking lives in `milestones.md`; this file is the long-form companion. ## Why This Milestone Exists The operator panel grew organically across M1–M6 as new tabs were added per capability. The "six tabs" framing undercounts what is actually there — Persons, Organizations, Sites, Org Types, Products (with nested Prices), Entitlement Sets (with nested Rules), Plan Ladders (with nested Tiers), Grants, and four Billing surfaces (accounts, subscriptions, invoices, payments) are all distinct surfaces flattened into a single tab strip. The seams are visible: - Cross-tab state synced via custom HTMX events (`productMutation`, `entitlementMutation`, `planLadderMutation`) — fragile and easy to forget when adding a new tab. - Stale-dropdown bugs recur (e.g., creating a plan ladder doesn't refresh the Org Types default-plan dropdown until the page is refreshed). The symptom was patched via the `HX-Trigger` event mesh; the architectural cause (flat tab IA + DOM-cached `revealed` partials) is what M7 retires. - URL state shoehorned into a `?tab=` query param; no real route per capability. Specific entities (an org, a product, a ladder, a grant) never appear in URLs — nothing is bookmarkable, shareable, or reload-stable. - `data-switch-tab` attribute pattern + `operator-tabs.js` to make cross-tab navigation work under strict CSP. - Form, confirmation, and feedback patterns vary tab to tab — there's no shared design vocabulary. - Accessibility has not been audited; keyboard navigation is patchy. - FedWiki sites sit at the same IA level as catalog/runtime tabs, entrenching the assumption that FedWiki is the only integration the member-console will ever have. NextCloud and Discourse are on the horizon; the IA must not bake FedWiki in. These are SPA-pattern symptoms in a tool whose mental model is a navigation hierarchy with three orthogonal axes — *catalog* (org types, products, entitlement sets, plan ladders), *runtime* (per-org enrollment and billing), and *integrations* (FedWiki today; more later). M7 reshapes the panel to match that grain. The integration contract itself is its own scope (proposed M9); M7 only needs to *not entrench* the FedWiki-first-class assumption. ## Phase Tracker See `milestones.md` for the canonical scope. Brief status here: | Phase | Status | Notes | |-------|--------|-------| | 7a — Operator surface inventory & walkthrough | Done | Three sub-tracks completed. **7a.1 Code archaeology** — `docs/operator-ux-research.md`; surfaced four cross-cutting findings handed to 7b: catalog/runtime/integrations as the three IA axes, the per-org composite view as the missing route, integrations carve-out into proposed M9, and read-only-operator role considerations. **7a.2 Deterministic walkthrough capture** — chrome-devtools-mcp single pass over every operator screen; outputs in `docs/operator-ux-walkthrough-evidence/`. **7a.3 Subjective walkthrough (multi-LLM)** — briefing doc plus per-agent outputs against a fixed rubric. | | 7b — Information architecture | Done | OpenSpec change `operator-ia` landed (archived `2026-05-12-operator-ia`); modifies `operator-panel-navigation`. `docs/operator-ia.md` is the canonical IA. | | 7c — Design system foundation | Done | `docs/design-system.md` documents the Bootstrap-derived primitive palette + custom toast/modal/error-handler primitives. Decisions captured in §7c below. | | 7d — SPA → MPA conversion | Done | OpenSpec changes `operator-mpa-conversion` + `operator-composite-expansion` archived 2026-05-16. Success toast survives hx-boost navigation because `.toast-container` is outside the swap target. | | 7e — Form & action patterns | Done | `docs/operator-ux-conventions.md` is the canonical convention layer (HTMX request shape, success/error feedback, modal coverage, validation, status codes, anti-patterns). | | 7f — Audit & integrity guards | Done | All four sub-phases landed. **7f.1** `member-console lint` (Cobra subcommand at `cmd/lint.go`, package `internal/lint/`) — five rules: dead routes, dead swap targets, stale `?tab=` refs, banned interpolated literals, route↔walkthrough coverage. Helper `web.RouteURL` registered on operator/fedwiki/workspace FuncMaps; 28 URL refs migrated. **7f.2** 11 rod walkthroughs in `test/e2e/operator-walkthroughs/`; 10 pass against the running stack, 1 skips gracefully on a seed-data quirk (non-plan grant products with no entitlement_set_id — pre-existing). **7f.3** `status/operator-a11y-baseline.md` covers all 14 top-level operator routes via Lighthouse snapshot mode (scores 89–96; recurring color-contrast on the global nav lifts every route by ~1 point when fixed). **7f.4** this update + the milestones row. | ## Personas (refined in 7a) - **Operator (primary):** internal staff administering people, orgs, plans, entitlements, and billing. Integrations (FedWiki today) are administered through a separate axis — see "Why This Milestone Exists" above. Frequency varies sharply by task: runtime work (enrollment lookups, billing reconciliation) is potentially daily; catalog work (creating products, editing ladders) is weeks-to-months apart. Needs efficiency on hot paths and recoverability on cold paths. - **Read-only operator:** someone who needs to inspect operator state without being able to mutate. Useful for support handoffs and for keeping principle-of-least-authority intact when the team grows past one person. Same screens, action affordances suppressed. - **Hard constraint on 7b:** URL structure must not bake the operator role. A read-only operator viewing `/operator/organizations/{orgID}` and a full operator viewing the same URL land on the same route — what differs is which actions render. ## Design Principles (placeholder — fleshed out in 7c) Working list, refined as research and design-system work proceeds: 1. **URL is the source of truth.** Every operator capability is bookmarkable, refreshable, shareable. 2. **Frequency-weighted IA.** High-frequency tasks get top-level placement; rare tasks live deeper without cluttering the surface. 3. **No surprise destruction.** Destructive actions are confirmed; confirmation copy names the affected entities; nothing is hidden in dropdowns. 4. **Server-rendered first.** HTMX enhances; JavaScript is for the small set of interactions that genuinely need it. Strict CSP stays strict. 5. **Accessible by default.** Keyboard parity with mouse; color is not the sole channel; WCAG 2.1 AA is the floor. 6. **Conventions over configuration.** Forms, tables, modals, and feedback follow shared patterns so operators learn the system once. ## 7a.1 Findings Handed to Downstream Phases From the code-archaeology sub-track only. 7a.2 (deterministic capture) and 7a.3 (multi-LLM walkthrough) will append their own findings sections when they land. Full 7a.1 detail in `docs/operator-ux-research.md`. - **The IA has three axes, not one tab strip.** *Catalog* (org types, products, entitlement sets, plan ladders) → *runtime* (per-org enrollment + billing) is one-way and dependency-shaped. *Integrations* is orthogonal to both. 7b owns the IA call but starts from this finding. - **The per-org composite view is the single biggest unmet need.** "What's the state of org X?" is a daily lookup that today requires threading Organizations → click → Billing tab → scan. A bookmarkable `/operator/organizations/{orgID}` route is the highest-leverage screen for 7b. - **Two grant paths are intentional, not accidental.** Plan grants (ladder-aware, via `IssueGrant`) and non-plan grants (legacy, via `CreateGrant`) handle different `product_type` values. 7b should keep both but make the distinction visible — and consider co-locating them on the per-org page. - **Tier rank reorder is the highest-criticality recurring catalog action.** Sole-writer invariants in `entitlements.Transition`; per-affected-org `pool_provision_transitions` audit row. Worth the most-careful screen design in M7. 7e should require cascade-preview on this and on entitlement-set / product edits at Crit ≥ 4. - **Product retirement, not delete.** Local products are 1:1 mapped to Stripe via `stripe.product_mappings`; M6a already plans `lifecycle_status` (`draft`/`published`/`retired`). 7b/7c surface the Stripe mapping; 7e exposes "retire" rather than "delete." See `status/issues.md` "Product retirement and Stripe-mapping visibility in operator UI." - **Org type CRUD deferred.** Schema supports it; UI deliberately doesn't expose it. Will need to grow when non-personal org types arrive. See `status/issues.md` "Org type CRUD when non-personal types arrive." - **Integrations carve-out (proposed M9).** FedWiki must not be at the same IA level as Products / Org Types / Billing. 7b reserves the URL real estate (`/operator/integrations/...` or similar); the integration contract itself is M9's job. See `status/issues.md` "Integration architecture (and operator IA placement)" and the proposed M9 in `milestones.md`. - **No usage telemetry exists.** All frequency / criticality annotations in 7a.1 are code-introspection estimates. An action-counter middleware spike was considered and deferred — folded into proposed M11 phase 11b (auto-instrumentation middleware) since there is no immediate consumer between now and M11. 7b should not block on telemetry; if any IA decision ends up load-bearing on a specific cell, revisit then. ## 7c Pre-implementation Decisions Decisions made during pre-implementation exploration (2026-05-13). These feed directly into what 7c builds and documents. ### Bootstrap foundation The codebase is already on Bootstrap 5.3.6 (latest stable). The design system is Bootstrap-first: `app.css` supplements Bootstrap for the small set of things Bootstrap doesn't cover (HTMX loading indicator hook, toast container positioning, cross-browser `` element fixes). Everything else uses Bootstrap utilities and components directly — no custom token layer, no parallel utility system. `app.css` carries a comment block at the top stating this contract. All hardcoded color values (e.g. `#0d6efd`) are replaced with Bootstrap CSS variable references (`var(--bs-primary)`) so they stay in sync if Bootstrap's defaults ever change. ### Button palette — two variants retired WCAG 2.1 AA audit surfaced two Bootstrap 5 outline variants that fail against a white background: | Variant | Color | Contrast on white | Verdict | |---------|-------|-------------------|---------| | `btn-outline-info` | `#0dcaf0` (cyan) | ~1.8:1 | Fails (need 4.5:1) | | `btn-outline-warning` | `#ffc107` (yellow) | ~1.28:1 | Fails (need 4.5:1) | Both are **retired** from the approved button palette. Existing uses (e.g. "Tiers" and "Validate Structure" buttons in the plan-ladders partial) are replaced during 7c. Replacement: `btn-outline-secondary` for neutral secondary actions; `btn-outline-primary` for emphasis secondary actions. `design-system.md` documents the full approved palette with contrast ratios. ### Success feedback — HX-Trigger toast 7c adds a success toast primitive (HTML element in the base layout + JS listener for `showSuccessToast` HX-Trigger event) to complement the existing error toast. Rationale: after 7d ships hx-boost, the `.toast-container` is outside the hx-boost swap target and survives page navigation; inline `alert-success` in a POST response body is replaced by the redirect target and never seen. Existing templates are not migrated in 7c — 7e owns that convention and timing. Server-side API: ```go // Signal success to the client — works for both partial swaps and hx-boost navigations. w.Header().Set("HX-Trigger", `{"showSuccessToast": "Product created"}`) ``` ### design-system.md scope `docs/design-system.md` is a Bootstrap usage guide + custom primitives reference, not a standalone design system. Structure: 1. **Foundation** — role of `app.css`, Bootstrap CSS variable conventions, future theming hook (see backlog) 2. **Approved component palette** — which Bootstrap variants are sanctioned and why (buttons with contrast ratios, badge colors, alert types) 3. **Custom primitives** — confirm modal (`data-*` API + ARIA spec), error toast, success toast (HX-Trigger API), loading states, empty states 4. **WCAG baseline** — `aria-live` requirements for HTMX swap targets, focus management rules, contrast checklist ## 7d Landed Decisions Decisions taken during the SPA → MPA conversion (change `operator-mpa-conversion`, archived alongside 7d's completion). ### Runtime above catalog in sidebar render order The 7b IA grouped capabilities into Runtime, Catalog, and Integration. 7d picked the top-to-bottom render order: Runtime → Catalog → Integration. Rationale is the same frequency-weighted IA principle behind 7a's research — runtime is the hot path, so it sits at the top. Group headings (`

` labels above each block) were dropped on the same grounds: naming "Members and Billing" or "Catalog" visibly adds cognitive load that isn't paid back by the disambiguation it provides at this scale. ### Persons is lookup-only, no flat browse A top-level `/operator/persons` browse route would duplicate organization-member context, encourage "find a person" workflows that should route through the owning organization, and inflate the sidebar with a low-frequency affordance. Persons survives only as `/operator/persons/{personID}` (resolved-detail) reachable via the landing-surface lookup affordance or an org member-row link. The legacy `?tab=people` redirects to the landing surface. ### No required group headings The sidebar renders a single flat `