Evalium Shared Component Contracts (v1.2)
High-leverage primitives for a data-dense, enterprise-proof UI (SvelteKit 2 + Svelte 5)
This document defines the shared component contracts that Evalium uses to enforce:
- Context links everywhere
- Drawer vs full-page navigation consistency
- Ledger/provenance defensibility
- Calm, repeatable data-dense patterns
- First-class i18n (MUST)
These are contracts, not full implementations: each component describes purpose, responsibilities, required behaviours, and key interfaces.
Capability Baseline (Validated 2026-02-25)
Use these contracts against the current backend surface:
- authoring inventory is live for Questions, Evaluations, and Passages.
- Saved inventory views (
personal,shared) are live for Questions/Evaluations. - Taxonomy assignment and taxonomy descendants filters are live for Questions/Evaluations.
- Usage graph endpoints are live with dependency details for Questions/Evaluations.
- Bulk authoring actions are live with
dryRun+ per-item outcomes. - Import preview/commit is live for Questions, Passages, Evaluations.
- Evaluation version policy fields and feedback tag config are live.
Still pending as first-class backend entities:
- Compliance case/timeline orchestration (jobs + ledger exist; case entity is pending).
- Skills/evidence-fact profile hubs (skills inference parked).
- Explicit proctor command endpoints (
pause/resume/terminate). - Granular saved-view sharing ACLs beyond
personal/shared.
0) Global Rules (Apply to Every Shared Component)
0.1 Context Links Everywhere (MUST)
If an entity reference is visible and the user is permitted to view it, it must be clickable in place.
- If not permitted, render as non-link with tooltip: “No permission to view.”
- No dead text rule: chips/badges/names/IDs that look like references must be links or explicitly permission-blocked.
0.2 Link Contract (MUST)
- On drawer-enabled surfaces, normal click opens a read-only right-drawer peek for supported entity references.
- Drawers are browse/review context only; edit and deeper workflow must route to canonical full pages.
- Cmd/Ctrl-click, shift-click, and middle-click always keep native full-page behavior.
- If the entity read model is not drawer-backed on the current surface, normal click must navigate to canonical full page.
- Rollout status (2026-03-06): production drawer-peek behavior is fully wired on
/questions; other surfaces remain canonical full-page until wired.
0.3 URL as State (MUST)
The Right Drawer is a navigation surface driven by URL, not local component state.
- Back/Forward must open/close drawer naturally.
- Drawer must be deep-linkable and shareable.
0.4 Accessibility Baseline (MUST)
- WCAG 2.2 AA baseline
- Keyboard navigable
- Visible focus
- Proper labels/ARIA where relevant
- Drawer focus trap + ESC close + return focus to opener
- No critical interaction depends on hover-only behavior
- Touch target sizing must support mobile/iPad operational usage
0.5 Fetching & Read Models (MUST)
- Prefer server
loadread models for primary pages. - The Right Drawer supports two data modes:
- prefetched (fast open from current screen)
- deep-link fetch fallback (URL opened directly)
0.6 Standard “Live-ish” Freshness (MUST)
Any surface that represents time-sensitive state must expose:
- “Updated X ago”
- “Stale” badge beyond a threshold
- Manual refresh CTA always available (auto-refresh optional and off by default)
0.7 Live Announcements (SHOULD, for all data-dense pages)
Data-dense admin pages should include a consistent, screen-reader friendly “what just happened” channel:
- a single aria-live region (polite) per page/shell
- used for: sort changes, selection count changes, bulk actions applied, errors on optimistic actions
- should be concise and non-spammy (avoid per-row chatter)
0.8 Internationalisation (i18n) Contract (MUST)
Evalium must treat i18n as part of the Definition of Done for frontend implementation.
0.8.1 No Hardcoded UI Strings (MUST) All user-visible text must come from the i18n message layer, including:
- page titles, button labels, placeholders
- empty/loading/error state text
- toast/live-announcements text
- tooltips, helper text
- ARIA labels/descriptions (screen reader strings are user-visible)
Allowed exceptions:
- user-generated content (e.g., evaluation titles, question stems)
- stable identifiers (IDs, codes) where a label is not expected
0.8.2 Plurals + Interpolation (MUST)
Messages that include counts must use plural-aware message functions.
No string concatenation like "Deleted " + count + " items".
0.8.3 Locale-safe Formatting (MUST) Dates, times, numbers, and durations must be formatted via locale-aware formatters:
- dates/times via
Intl.DateTimeFormat - numbers/percentages via
Intl.NumberFormat - “Updated X ago” via a relative-time approach consistent across the app
0.8.4 Directionality + Layout Resilience (SHOULD) Components must not assume English-only text lengths. UI must tolerate longer strings without clipping. (RTL support can be phased, but layout must not hard-code LTR-only constraints.)
0.8.5 Backend Error Localisation (MUST for forms/actions) User-facing error messages should be localisable:
- Prefer backend returning stable error codes / structured field errors.
- Frontend maps codes → i18n messages.
- Raw backend error strings should not be displayed directly except in dev tooling.
0.8.6 Mutation Idempotency (MUST where required) When backend endpoints require/expect idempotency:
- frontend must attach
Idempotency-Key - retries must reuse the same key for the same payload
- changed payloads must mint a new key
- idempotency error codes must map to deterministic user guidance
0.8.7 Paraglide Runtime (MUST) Inlang Paraglide-JS is the required message runtime for frontend surfaces.
Rules:
- all component copy must resolve from Paraglide message functions.
- no raw string literals for user-facing UI text (except user-authored content and stable identifiers).
- shared components must not accept plain text props when a message key/value resolver is available.
0.8.8 Override Resolution Order (MUST) Runtime translation resolution order is:
- org override
- tenant override
- product default (Paraglide base)
This precedence must be identical across page shells, drawers, tables, toasts, and validation messages.
0.8.9 Text Override Guardrails (MUST) If translation overrides are editable in-product:
- only existing keys can be overridden by default (no ad-hoc key creation in v1).
- placeholder/token integrity must be validated before publish.
- override writes must be versioned and auditable.
- failed override lookups must gracefully fallback to lower precedence.
0.9 Status Label Contract (MUST)
All user-facing status chips/filters must use canonical labels from /docs/ux/language-and-tone-guidelines.md.
Minimum required mappings:
- Readiness: backend
ready|needs_review|blocked-> UIReady|Needs review|Blocked - Exception triage: backend
open|acknowledged|resolved-> UIOpen|Acknowledged|Resolved - Assignment monitor: backend
invited|started|completed|expired-> UINot started|In progress|Completed|Expired
Rules:
- Do not display raw backend enum values in primary UI.
- Do not mix synonymous labels in the same workflow.
- Filter chips and table badges must use the same label set.
0.10 Automated Accessibility Guardrails (MUST)
Shared components must be testable and enforced through automation:
- static lint/compile gates for accessibility issues
- automated a11y checks on shared component render paths
- regression checks on critical keyboard and focus behaviors
0.11 Task-Tier and Handoff Contract (MUST)
Shared components must support task-tier behavior:
- mobile-safe interaction defaults for Tier A/Tier B flows
- explicit complex-task handoff patterns for Tier C flows on constrained devices
- no dead-end screens when an action is desktop-optimized
reference docs:
/docs/ux/UX-ACCESSIBILITY-STANDARDS-AND-GUARDRAILS.md/docs/ux/UX-MOBILE-AND-TASK-TIER-POLICY.md
1) EntityLink (Navigation Orchestrator) — Atomic
Purpose
The single, mandatory way to render entity references (text, chip, badge, row cell) with consistent behaviour.
Responsibilities (MUST)
- Enforce Link Contract (drawer peek vs full page) based on surface support + entity type.
- Permission-aware rendering:
- permitted → link
- not permitted → non-link + tooltip
- Preserve working context:
- drawer opens without leaving current workflow
- full page navigation remains the path for edit/deeper workflows and retains standard breadcrumbs/IA (handled by shells)
Native Anchor Rule (MUST)
EntityLink must render a real <a href="..."> element when canView = true to preserve:
- hover shows URL in status bar
- right-click → open in new tab
- Cmd/Ctrl-click → open new tab
For drawer-enabled entity links, normal click intercepts navigation (preventDefault) and updates the drawer URL param.
Read-Only Drawer Rule (MUST)
Peek drawers must be read-only. Any mutating action from the drawer context must hand off to the canonical full page.
Power User Behaviour (MUST)
- Cmd/Ctrl-click always opens the canonical full page in a new tab (even when normal click opens drawer peek).
- Shift-click and middle-click behaviour must remain native.
Prefetch on Hover Intent (SHOULD)
On hover/focus intent, EntityLink may prefetch the minimal read model needed for the drawer.
Constraints:
- capability-aware
- debounced/throttled
- fetch summary data only (not heavy blocks)
i18n Requirements (MUST)
- Tooltip text (e.g., “No permission to view”) must be localised.
- If
labelis absent and we fall back to an ID/code, the surrounding UI (prefixes like “User:”) must be localised.
Inputs (recommended)
type: entity type enum/string (user, group, taxonomy_term, question, passage, evaluation, assignment, submission…)id: entity identifierlabel: optional fallback labelvariant: appearance variant (text/chip/badge/custom)canView: boolean (or capability token resolved upstream)drawerDefaultTab: optional (e.g., “activity”)hrefOverride: optional canonical URL (if known)
Canonical URL Requirement (MUST)
Every entity that can appear in a drawer peek must have a canonical full page URL:
/questions/:id/passages/:id/evaluations/:id/assignments/:id/submissions/:id/users/:id/groups/:id/skills/:id(feature-flagged until skills profiles are backed)
2) DrawerCoordinator (Root Layout Service) — Non-visual
Purpose
Single source of truth for drawer open/close state, driven by shallow-routing state with URL fallback.
Responsibilities (MUST)
- Watch
$page.state.draweras the primary reactive source. - Parse URL drawer params as fallback for deep-link/open-in-new-tab loads.
- Parse and validate drawer params
- Open/close the Right Drawer without full page reload
- Ensure Back/Forward behaves naturally (e.g., Back closes the drawer)
Implementation note:
pushState/replaceStateupdate$page.statebut do not update$page.urlreactively.- Build next URLs from
location.href, preserve existing query params, and mirror drawer state into URL params for shareability.
Replace Semantics (MUST)
If a drawer is already open and another drawer-targeting EntityLink is clicked:
- replace the current drawer content (no stacking)
- update the URL param from
drawer=a:1→drawer=b:2 ContextDrawerreacts and swaps content
Rationale: “Calm UI” for SMBs; avoids nested/stacked drawers.
URL Schema (MUST)
drawer=<type>:<id>Optional:drawerTab=<tab>drawerContext=<encoded>(avoid unless required)
3) ContextDrawer (Right Drawer Shell) — Container
Purpose
Standard “Support Context” surface with consistent behaviour and deep-linking.
Responsibilities (MUST)
- Focus trap, ESC close, return focus to opener
- URL-driven open/close (via
DrawerCoordinator) - Standard header with:
- entity icon/type
- title
- status chips (optional)
- “Open full page” link (always present if permitted)
- Support “replace semantics” smoothly (swap content without tearing underlying page)
Data Modes (MUST)
Mode A — Prefetched
- Parent passes summary/read model when drawer opened from current page.
- Drawer renders immediately; optionally revalidates in background.
Mode B — Deep-link Fetch Fallback
- If user lands on URL with
?drawer=...:- drawer fetches minimal read model required to render
- shows skeleton/loading + error states
Drawer Read Model Cache (SHOULD)
To avoid duplicate fetches:
EntityLinkhover prefetch writes to a shared cache keyed bytype:idContextDrawerchecks cache before fetching
i18n Requirements (MUST)
- Drawer headings, tab labels, action labels, empty/error states must be localised.
- “Open full page” and any explanatory helper text must be localised.
Required States (MUST)
- Loading
- Error (retry)
- Empty/not found (permission-safe messaging)
- Stale indicator (if applicable)
4) StandardHub (Entity Hub Shell) — Page Shell
Purpose
Consistent full-page layout for primary assets and complex workflows.
Responsibilities (MUST)
- Standard header area:
- title
- key status chips
- primary CTA area
- mode banners (Working Draft, Provisional, Hold, etc.)
- Standard tab system (optional but consistent):
- Overview / Activity / Results / Settings
- Breadcrumb placement and consistent IA
- Right-drawer compatibility (should not break underlying hub state)
Mode Banners are Functional (MUST)
Mode banners must include direct contextual links:
- Working Draft banner links to Published Version
- Legal Hold banner links to Compliance Case
- Provisional banner links to what is pending (e.g., grading queue items)
Tab Contract (MUST)
- Tabs are optional, but ordering and naming is consistent when present.
- If Activity exists, it is always named Activity and placed consistently.
i18n Requirements (MUST)
- Tab names, banner labels, CTA labels must be localised.
- Banner helper text (e.g., “This version is a working draft…”) must be localised.
- Breadcrumb labels must be localised (except entity names).
5) DataTable (Generic Dense List Primitive) — Page Primitive
Purpose
Reusable table pattern for data-dense admin surfaces (Questions, Users, Assignments, Grading Queue lists, Cohorts, etc.).
Responsibilities (MUST)
- Sort, filter, pagination (server-driven where needed)
- Stable keyed rows (no row churn)
- Standard row actions surface (buttons/menu)
- Standard states:
- loading
- empty
- error + retry
- Integration: entity references inside cells use
EntityLink
5.1 Keyed Reactivity (MUST)
Rows must use stable keys (row.id) to minimise DOM churn.
5.2 Bulk Selection + Bulk Toolbar (MUST where selection exists)
If the table supports selection:
- “Select all” checkbox supports checked / unchecked / indeterminate
- Selection count displayed consistently
- Bulk actions toolbar appears only when
selectedCount > 0
Keyboard shortcut (SHOULD): Pressing . while focus is inside the table region should move focus to the first enabled bulk action.
- Must not trigger while editing text inputs/textarea/contenteditable
- Scope must be table-region only (not global document-wide)
- Should announce via Live Announcements: “Bulk actions focused”
5.3 Declarative Filter Schema (SHOULD)
Tables should define filters using a consistent schema:
- sections with:
id,title,type,options,initial - supports “instant apply” or “apply/cancel” mode
- supports “clear filters” standard CTA
5.4 Optimistic Bulk Mutations (SHOULD)
For backend-supported bulk actions, use two-stage execution:
- stage 1:
dryRunpreview - stage 2: apply exact confirmed payload
- always show per-item outcomes (
ok,code,message,changed) - never collapse partial success into a binary toast
Optimistic patching is optional and only safe after apply confirmation semantics are preserved.
5.5 Live-ish Freshness Contract (MUST for live-ish tables)
DataTable must support:
lastUpdatedAt+staleAfterMs- header shows “Updated X ago”
- transitions to Stale when threshold passed
- manual refresh CTA always available
Auto-refresh optional and off by default.
i18n Requirements (MUST)
- Column headers, toolbars, filter labels, empty/error states must be localised.
- Live announcements must be localised and plural-aware (e.g., “1 item selected” vs “5 items selected”).
- Date/time cells must be formatted via locale-safe formatting (0.8.3).
5.6 Dependency-Aware Usage Views (MUST where endpoints exist)
When usage endpoints expose dependency blocks, shared table/panel components must render:
- typed dependency chips (assignment/programme/content-pack/evaluation/passage)
- dependency-specific links via
EntityLink - risk cues from usage counts (
activeSessions,submissions) without hiding raw counts
6) StatusChip (Semantic Badge) — Atomic
Purpose
Single source of truth for status semantics across Evalium.
Responsibilities (MUST)
- Map backend statuses → semantic tokens:
- success / info / warning / danger / provisional / disabled
- Provide:
label(human-readable)description(tooltip explaining meaning and implications)- optional icon
- Enforce consistency: developers do not choose arbitrary colours per feature.
i18n Requirements (MUST)
labelanddescriptionmust be localised.- Backend status codes must remain stable; frontend maps code → i18n key.
- No hardcoded English status strings.
7) ValidationSidekick (authoring Sidebar) — authoring Primitive
Purpose
Persistent, actionable error/warning surface for builders.
Responsibilities (MUST)
- Render list of:
- Errors (block publish)
- Warnings (acknowledge on publish)
- Each item provides:
- summary
- optional “learn more”
- Go to action that focuses relevant control
Focus Registry Contract (MUST)
Builders must register focus targets in a stable registry so Sidekick can focus elements reliably across nested components.
i18n Requirements (MUST)
- Error/warning summaries must be localised.
- Parameterised messages must use interpolation (no concatenation).
- If errors originate from backend validation, map structured codes → localised messages (0.8.5).
8) ImpactSummaryModal (Traffic Light Propagation) — authoring Primitive
Purpose
Make propagation decisions explicit and defensible at Publish time.
Responsibilities (MUST)
- Group impacted assets:
- Green: Draft (safe to update)
- Amber: Published (requires new version)
- Red: Active runs (high risk)
- Default selections:
- if any Red exists → default to “Don’t update anything”
- Provide:
- counts + “view all”
- explicit choices
- confirmation summary of what will change
i18n Requirements (MUST)
- All labels, group headers (Green/Amber/Red descriptions), and confirmation text must be localised.
- Counts must be plural-aware.
9) InsightBlock (Reporting Primitive) — Insights Primitive
Purpose
Standard wrapper for dashboard/report blocks.
Responsibilities (MUST)
Each block must include:
- Explain link (opens Evidence/Provenance drawer or page)
- Scope chips (version/timeframe/cohort)
- Freshness indicator (“Updated … ago”)
- Processing state (queued/running) when projections pending
- Data sufficiency guardrails (“Insufficient data”)
i18n Requirements (MUST)
- Explain-link labels, chips, processing states, and empty states must be localised.
- Numeric presentation (percentages, pass rates) must be locale-formatted.
10) LedgerTable (Durable History) — Audit Primitive
Purpose
Consistent audit/ledger rendering for “who did what and why”.
Responsibilities (MUST)
Always includes:
- Timestamp
- Actor (as
EntityLink) - Action (structured label)
- Reason (if required)
- Details (expandable)
i18n Requirements (MUST)
- Action labels and helper text must be localised.
- Date/time formatting must be locale-safe.
- If the backend provides action codes, map codes → localised strings (preferred).
11) BulkActionDrawer (Inventory Primitive) — authoring Primitive
Purpose
Standard two-step bulk action UI for inventory surfaces.
Responsibilities (MUST)
- support action selection (
archive,restore,taxonomyReplace) - support preview (
dryRun) before apply - show summary totals (
total,succeeded,failed,changed) - show per-item outcomes and keep failures inspectable
- preserve selection context after run (unless user explicitly clears)
Error Handling (MUST)
- map backend
codevalues to localised row-level guidance - treat partial success as first-class result, not as a failure of the whole run
12) ImportWizard (Inventory Primitive) — authoring Primitive
Purpose
Reusable preview/commit import workflow for Questions, Passages, Evaluations.
Responsibilities (MUST)
- ingest CSV payload
- run preview and display row-level diagnostics (
valid+errors) - allow commit only after explicit confirmation
- display commit summary with created count and navigation links to resulting assets
Contract Notes (MUST)
- preview and commit must be clearly separated in UI copy and action buttons
- preview errors must be localised without exposing raw backend strings
13) UsageGraphPanel (Dependency Primitive) — authoring Primitive
Purpose
Reusable dependency panel for "where used" contexts in authoring.
Responsibilities (MUST)
- render usage counts and dependency groups
- support Question usage dependencies (Evaluations, Passages)
- support Evaluation usage dependencies (Assignments, Programme Requirements, Content Packs)
- provide deep links for impact review and safe change decisions
Appendix A) UI Archetypes (to help you visualise the whole app)
This is the “mental model library” for Evalium. Most screens are one of these patterns.
A1) Library / Directory (Discovery)
Use when: browsing large sets (Questions, Passages, Evaluations, Users, Groups)
- Top: Search + Filters
- Middle:
DataTable(dense list) - Right:
ContextDrawerpeek (preview, usage, quick actions) - Bulk toolbar for batch actions
Feels like: “Find and understand quickly; edit intentionally.”
A2) Entity Hub (Primary Asset Management)
Use when: managing “real objects” (Evaluation, Programme, Assignment)
StandardHubwith tabs- Activity tab uses
LedgerTable - ContextDrawer still works for support references
Feels like: “This is the home for this asset.”
A3) Builder (Canvas + Sidekick)
Use when: authoring structures (Evaluation Builder, Mapping Set Editor)
- Main canvas (sections/items/rules)
ValidationSidekickalways visible- Publish triggers
ImpactSummaryModal
Feels like: “Create safely; the sidekick prevents foot-guns.”
A4) Queue (Work-inbox)
Use when: humans process items (Grading Queue, Compliance queue)
DataTableas the inbox- Row click opens
ContextDrawer(detail + actions) - Optional split-screen when action is intense (grading)
Feels like: “Process efficiently; keep provenance.”
Current backend note:
- Compliance queue today is Jobs/Ledger-centric until case orchestration lands.
A5) Live Monitor (Pulse)
Use when: live-ish operations (Command Centre)
DataTablewith strong freshness + stale states + manual refresh- Drawer shows session timeline + proctor telemetry/actions Feels like: “Operational calm; nothing jumps unless you ask.”
Current backend note:
- Proctor action controls should be feature-flagged/read-only until explicit command endpoints exist.
A6) Insights Dashboard (Blocks)
Use when: reporting and projections (Reporting, Competence Profiles)
- Grid of
InsightBlockcomponents - Every derived metric has Explain link
- Processing/freshness always visible
Feels like: “Defensible intelligence, not vibes.”
Appendix B) Recommended Build Order (Pragmatic)
EntityLinkDrawerCoordinator+ContextDrawerStandardHubDataTable(with Bulk + Filter schema + Live Announcements + Freshness)StatusChipValidationSidekick+ImpactSummaryModalInsightBlockLedgerTable