Bits UI Map Table (v0.1)
Evalium frontend shared component mapping: Interface pattern → Evalium reusable component → Bits UI primitives → API/data → contracts → build rules.
This doc is intended to be expanded page-by-page. It aligns to:
- COMPONENT-INTERACTION-MAP.md (URL-as-state, shallow routing, drawer semantics, focus restore, param preservation).
- UX-SHARED-COMPONENTS-CONTRACT.md (standard hub/table/drawer patterns, import, bulk, sidekick, etc.).
Key doctrine:
- Calm by default: show essentials; push depth into drawers/Explain.
- Two worlds: authoring can feel “editable”; Execution truth is append-only and must not look overwritable.
- Capability-first gating: UI gates by capabilities, not role names.
0) Power Upgrades (to keep this table enforceable)
To make this table a real build contract, we include:
- Mutability Class:
authoring (Save Lie OK)vsExecution (NO Save Lie)vsProjection/Queue. - Risk Tier: Safe / Destructive / Irreversible / Legal.
- Capability Gates: the capabilities that control visibility/enabled state of actions.
- URL State Contract: which URL params represent state and what must be preserved.
- Freshness/Caching: expected staleness behavior + “Updated X ago”.
- Redaction/Safety: anti-leak rules, especially for Glass Box or portal-scoped surfaces.
- Telemetry Hooks: suggested instrumentation events.
- Loading/Error/Empty: standard states for consistency.
- Props Contract: standardized props to force reusability.
- Do Not Do: regression prevention.
- Testing Contract: minimum unit/e2e expectations.
1) Terms
- Bits UI primitives: accessible building blocks (Dialog, DropdownMenu, Tabs, Combobox, etc.).
- Evalium components: app-level reusable components built using Bits primitives and Evalium contracts.
- Drawer-supported entity types: types that can be opened as a right-side ContextDrawer without full navigation (see Interaction Map baseline).
2) Table A — Pattern Map (high-level)
| Pattern ID | Interface type | Evalium component (reusable) | Mutability class | Bits UI primitives | Example endpoints/data sources | Capability gates (examples) | URL state contract | Risk tier | Freshness/caching | A11y notes |
|---|---|---|---|---|---|---|---|---|---|---|
| NAV-01 | Link & navigation | EntityLink | n/a | Tooltip (optional) | Any entity reference | *.read caps | Must be real <a>; intercept only drawer-supported types; preserve params | Safe | n/a | Don’t break Cmd/Ctrl click; focus visible; SR reads target |
| DRW-01 | Drawer state orchestration | DrawerCoordinator / DrawerService | n/a | n/a | Derived from URL + fetches | n/a | drawer=type:id, drawerTab=; shallow routing; preserve all inventory params | Safe | cache optional | Back closes drawer; abort fetch on close; restore focus |
| DRW-02 | Context surface | ContextDrawer (right sheet) | Mixed (mostly read) | Dialog (sheet-style) + ScrollArea + Tabs + Tooltip | Drawer read models per entity type | Gate actions inside drawer | Deep-linkable; always include “Open full page” | Safe→Destructive | Cache keyed by type:id (SHOULD) | Focus trap; ESC closes; permission-safe notfound/forbidden |
| HUB-01 | Page shell | StandardHub | Depends on page | Tabs + DropdownMenu + Separator + Toolbar (layout) | Any hub page | Gate CTAs | Tabs must preserve drawer params | Safe | Live-ish banners | Consistent tab naming; SR-friendly headings |
| INV-01 | Dense inventory list | DataTable / InventoryTable | Projection/Queue | Checkbox + DropdownMenu + ContextMenu + Popover + Combobox + Select + Pagination + ScrollArea | Users/Groups lists; Questions/Evals inventories | Gate bulk+row actions | viewId/q/sort/page preserved on drawer open/close | Safe | Show “Updated X ago”; stale threshold | Stable row keys; keyboard access to row actions |
| INV-02 | Saved views | SavedViewsDrawer | authoring-lite | Dialog or Popover + Tabs | /questions/views, /evaluations/views | Gate create/delete/share | viewId= canonical; view switching must not lose other params unexpectedly | Destructive (delete) | Live | Dialog focus; SR announces changes |
| FIL-01 | Filtering | FilterBar | n/a | Combobox + Select + Popover + ToggleGroup/Switch | Inventory query params | n/a | Filters round-trip in URL | Safe | Cache option lists | Labels + clear actions; chips keyboard removable |
| CHIP-01 | Status semantics | StatusChip | n/a | Tooltip | Backend status codes | n/a | n/a | Safe | n/a | Tooltip explains meaning; avoid color-only signaling |
| BULK-01 | Bulk operations | BulkActionBar + BulkActionDrawer | authoring / Projection | Dialog (sheet) + Progress + Tabs + Tooltip | Bulk endpoints (e.g., /questions/bulk) | Gate by capability + object state | Must not break inventory params; selection must persist | Destructive | Live | Confirmations accessible; progress announced |
| IMP-01 | Import wizard | ImportWizard | authoring | Tabs-as-steps + Progress + Dialog confirm + Tooltip | Import preview/commit endpoints | Gate import by capability | Optional step=; protect against accidental navigation loss | Destructive-ish | Live | Preview table navigable; clear error reporting |
| VAL-01 | Validation gating | ValidationSidekick | authoring | Accordion/Collapsible + ScrollArea + Tooltip | Validation codes + preview/validate flows | Gate publish action | Anchor/focus registry; deep-link where possible | Safe | Live | “Go to” focuses the exact control reliably |
| MOD-01 | Risk confirmation | ImpactSummaryModal | authoring | AlertDialog (preferred) + Tabs/ToggleGroup (optional) | Usage/dependency data | Gate destructive actions | n/a | Irreversible-ish | Live | Alert semantics for destructive; explicit wording |
| INS-01 | Report block | InsightBlock | Projection | Tooltip + Popover + “Explain” link → Drawer | Summary endpoints (e.g., evaluation-summary) | Gate reporting access | Explain opens drawer/page; deep-linkable | Safe | Must show freshness + “insufficient data” | Explain must be keyboard reachable |
| AUD-01 | Durable history | LedgerTable / Timeline | Execution read | ScrollArea + Separator + Tooltip | Ledger-projected records | Gate ledger view | Deep links per event where possible | Legal | Snapshot/truth | Read-only semantics; expandable details accessible |
| GBX-01 | Glass Box viewer | GlassBoxViewerShell | Execution (NO Save Lie) | Tabs + ScrollArea + Tooltip (sparingly) | Glass Box endpoints (submission/programme/engagement) | Gate access + redaction by scope | Canonical full-page; shareable URLs | Legal | Snapshot-oriented | No edit affordances; “truth projector” posture |
| EXC-01 | Ops queue + triage | ExceptionsQueueTable + TriageDrawer | Projection/Queue | DataTable primitives + Dialog(sheet) | Defensibility exception endpoints | Gate triage | Filters preserved; triage deep-linkable | Destructive-ish | Live-ish + manual refresh CTA | State transitions SR-announced |
3) Table B — Build Contract Add-ons (props, states, telemetry, redaction, do-not-do, tests)
These rows define the minimum contract required to keep components reusable and consistent.
| Pattern ID | Standard props contract (v0) | Loading/Error/Empty states | Telemetry hooks (suggested) | Redaction / safety | Do Not Do | Minimum tests |
|---|---|---|---|---|---|---|
| NAV-01 | href, entityType, entityId, `mode=drawer | page | auto, disabledReasonKey?` | n/a | ui.link.click | Permission-safe tooltip copy |
| DRW-01 | open(type,id,tab?), close(), replace(type,id,tab?), cacheKey? | Must: loading, ready, forbidden, notfound, error+retry | ui.drawer.open/close/replace | Don’t leak raw errors; show request id if available | Don’t stack/nest drawers; replace semantics only | e2e: back closes drawer; fetch aborted on close; params preserved |
| DRW-02 | params={type,id,tab}, prefetchModel?, onCloseFocusId? | Must: skeleton + retry + permission safe | ui.drawer.tab.change | Gate actions inside drawer | Don’t show actions that user cannot perform | e2e: focus trap + restore; ESC closes; open full page link exists |
| INV-01 | queryState, columns, rowKey, rowActions, bulkActions?, lastUpdatedAt?, staleAfterMs? | Must: loading, empty, error+retry | ui.table.sort/filter/page/select | Avoid leaking PII in telemetry | Don’t churn rows; stable keys required | e2e: filter round-trip; selection persists; drawer open/close preserves state |
| INV-02 | views[], activeViewId, onCreate/onDelete/onShare | Must: empty + error | ui.views.switch/create/delete | View sharing obeys capability | Don’t reset unrelated query params | e2e: switching view updates URL + table; back restores |
| FIL-01 | filtersSchema, value, onChange, optionsProviders | Must: option loading states | ui.filters.open/clear/change | Don’t put PII in query string without purpose | Don’t hide active filters | unit: serialize/deserialize filters; e2e: chips clear correctly |
| BULK-01 | actionDefs[], selectedIds[], `dryRun=true | false, onApply(result)` | Must: dryRun preview + partial success | ui.bulk.open/dryrun/apply | Require explicit confirmation for large blast radius | Don’t treat partial success as full failure |
| IMP-01 | steps=[upload,preview,commit], templateLink?, onCommit(result) | Must: preview error rows + commit results | ui.import.preview/commit | Don’t allow commit when preview invalid | Don’t hide row-level errors | e2e: invalid blocks commit; commit results durable |
| VAL-01 | issues[], focusRegistry, onGoTo(issue) | Must: grouped issues + clear severity | ui.validation.goto | Map backend codes → i18n keys | Don’t implement GoTo without stable focus registry | e2e: go-to focuses correct field; publish blocked correctly |
| MOD-01 | title, summary, impactList[], confirmLabel, danger=true | Must: no silent failure | ui.confirm.open/confirm/cancel | Avoid vague “are you sure” | Don’t use plain Dialog for destructive without reason | unit: confirm logic; e2e: enter/esc behaviour |
| INS-01 | title, kpis[], explainHref, freshness | Must: insufficient data state | ui.insight.view/explain | Explain must not leak internal-only signals | Don’t show chart without drilldown | e2e: explain opens drawer and preserves inventory params |
| AUD-01 | events[], expandableFields, grouping? | Must: empty + paging if needed | ui.ledger.expand | Redact internal-only fields by capability | Don’t present edits as overwrites | e2e: expand/collapse accessible; stable ordering |
| GBX-01 | lensModel, tabs, openInNewTabHref, lang | Must: forbidden/notfound safe | ui.glassbox.view/tab | Strict redaction; portal scope safe | Don’t add edit controls; don’t reframe as dashboard | e2e: shareable URL; correct redaction; content is read-only |
| EXC-01 | filters, triageModel, onTriageSave, refresh() | Must: live-ish refresh states | ui.exceptions.refresh/triage/save | Triage comments treated as audit context | Don’t imply triage mutates execution truth | e2e: refresh updates; triage persists; suppressedUntil works |
4) Bits UI Configuration Baseline (recommended)
To avoid drift, define an app baseline for Bits UI behavior:
- Dialog defaults (escape closes, focus trap on, scroll lock consistent).
- Dropdown/Menu alignment + collision behavior consistent.
- Tooltip delays consistent.
Use a single configuration layer (BitsConfig) to standardize this.
5) Mutability Rules (Save Lie allowed vs forbidden)
Save Lie allowed (authoring)
- Questions authoring
- Passage/stimulus authoring
- Evaluation authoring
- Saved views, library organisation
- Import workflows (authoring creates new content/versions)
Save Lie forbidden (Execution/Truth)
- Glass Box pages (truth projector)
- Submission truth, evidence lifecycle, approvals/ratifications
- Ledger timelines and audit record displays
- Anything presented as “what happened” must be append-only and show history