Skip to content

Warm Meridian Design System Audit Report

Date: 7 March 2026 (Session 63) Scope: All 9 functional areas of Knowledge Hub Design system: Warm Meridian (OKLCH, Instrument Sans, stone/amber palette) Method: Parallel code review agents scanning 50+ component and page files


The Warm Meridian migration (Session 62) was exceptionally thorough. Across all 9 functional areas and 50+ files audited:

  • Zero raw Tailwind colour class violations (e.g. text-green-600, bg-amber-50) — the semantic token system is fully adopted
  • Zero dark: colour prefixes — dark mode is handled automatically by the CSS variable layer
  • Comprehensive accessibility patterns — keyboard navigation, ARIA labels, multi-signal indicators (colour + icon + text)
  • Consistent 4pt spacing grid and typography hierarchy

Overall grade: B+ → A- (after quick wins applied in this session)

SeverityFoundFixed (This Session)Remaining
Critical00
Major440
Minor291019

#AreaIssueFix
1Codebase-widetext-white used on 8 components + 2 UI primitivesReplaced with text-primary-foreground everywhere
2Codebase-wideMissing --destructive-foreground token in globals.cssAdded to :root, .dark, and @theme inline
3Loginalert() used for password reset confirmationReplaced with toast.success()
4Item DetailRaw <button> in error.tsx instead of <Button> componentReplaced with <Button>
#AreaIssueFix
1SettingsSidebar width w-[220px] not on 4pt gridChanged to w-56 (224px)
2ReviewEmoji icons (✅ ✨) in empty statesReplaced with Lucide CheckCircle2 / Sparkles
3ReviewPanel size props using string valuesConverted to numbers
4Reviewtext-destructive for flagged items (error semantic)Changed to text-status-warning (governance)
5ReviewMissing overscroll-contain on main panelAdded
6BidConfidenceDot using pale *-bg token for dotAdded dot field using vivid foreground token
7Login”Forgot password?” button missing focus ringAdded focus-visible:ring-2 styles
8UI primitivesdark: prefixes on destructive button/badge variantsRemoved (CSS variables handle dark mode)
9Settingstext-white on team deactivate buttontext-primary-foreground
10Settingstext-white on taxonomy deactivate buttontext-primary-foreground

Remaining Issues (Not Fixed — Complete Inventory)

Section titled “Remaining Issues (Not Fixed — Complete Inventory)”

Tier 1: Accessibility Quick Wins (Agent-Safe — No Investigation Needed)

Section titled “Tier 1: Accessibility Quick Wins (Agent-Safe — No Investigation Needed)”

All items below have specific file locations, line numbers, and exact fixes documented in the raw batch reports (/tmp/audit-batch-{1,2,3}.md). Agents can execute these directly.

#AreaIssueFileFixEffort
1Q&A LibraryBulk operation progress lacks aria-live regionbulk-action-toolbar.tsx:161Add aria-live="polite" to progress text container5 min
2Q&A LibraryFilter count change not announced to screen readerslibrary-content.tsx:162Wrap count text in aria-live="polite" region5 min
3Q&A LibraryBadge filter chips not keyboard-accessiblecontent-library-drawer.tsx:204Use <button> styled as badges, or add role="button" tabIndex={0} onKeyDown15 min
4Item DetailContentTabs textarea lacks aria-labelcontent-tabs.tsx:269Add aria-label={Edit ${field}}5 min
5Item DetailQAAnswerDisplay textareas lack aria-labelqa-answer-display.tsx:51,83Add aria-label="Standard answer" / aria-label="Advanced answer"5 min
6CoverageStatCard icons missing aria-hiddencoverage-summary-cards.tsxAdd aria-hidden="true" to Icon component5 min
7CoverageRefreshCw icon missing aria-hiddencoverage-content.tsx:182Add aria-hidden="true"2 min
8CoverageGap cells lack contextual aria-labelcoverage-gap-cell.tsxAdd aria-label="[subtopic name] — no content"10 min
9HomeActivity feed missing role="feed" / role="article"dashboard-activity-feed.tsxAdd role="feed" to container, role="article" to items10 min
10HomeAttentionCard link text not descriptive enough for ATattention-card.tsxMake actionLabel include count context for screen readers15 min
11SettingsGovernance config list items lack semantic structuregovernance-section.tsxAdd role="list" and role="listitem"15 min
Total~92 min

Tier 2: Visual/Semantic Quick Wins (Agent-Safe)

Section titled “Tier 2: Visual/Semantic Quick Wins (Agent-Safe)”
#AreaIssueFileFixEffort
12HomeQuickStatsStrip freshness values lack colour-coded dotsquick-stats-strip.tsxAdd coloured dots using --color-freshness-* tokens15 min
13HomeRollback activity icon could use text-status-warningdashboard-activity-feed.tsxChange rollback icon class5 min
14Home”Knowledge Hub” heading could use text-fluid-2xlapp/page.tsxChange text-xl to text-fluid-2xl2 min
15SettingsOrphaned <Label> on read-only role displayprofile-section.tsxReplace <Label> with <p> or <span>5 min
16Item DetailEditing banner could add pencil iconitem-detail-client.tsx:532Add <Pencil className="size-3.5" /> before text5 min
17Item DetailLoading skeleton max-w-4xl vs content max-w-7xlapp/item/[id]/loading.tsxChange to max-w-7xl2 min
18BidTwo raw <a> elements should use Button asChild + Linkapp/bid/[id]/page.tsx:165,270Replace with <Button asChild><Link>...</Link></Button>10 min
19BrowseDate input format hint may conflict with browser localefilter-panel.tsxAdd helper text clarifying format10 min
20BrowseGrid vs list view missing domain-coloured left bordercontent-row.tsxAdd borderLeftColor inline style matching ContentCard pattern20 min
21ReviewInline shadow should be extracted to CSS variablereview-action-bar.tsx:91Define --shadow-review-bar in globals.css15 min
22LoginShare email state between password and magic link formsapp/login/page.tsxLift email state to share between both forms10 min
Total~99 min

Tier 3: Larger Improvements (Agent-Safe With Coordination)

Section titled “Tier 3: Larger Improvements (Agent-Safe With Coordination)”

Reconcile status (S397, ID-124): all 6 items already satisfied or gone — 0 executors dispatched. #23 QualityFlagBadge already extracted (content-card.tsx:66); #24 FilterPanel already uses shadcn Checkbox; #26 governance list already role=list/listitem; #27 BidContextSync removed (bid relocated to app/procurement/); #28 CopilotKit retired. Lone optional residual: #25 — the team-member “grid divs” gap is gone (now an accessible role=list with per-control aria-labels); converting to a true semantic <table> is an optional design judgment, not a defect.

#AreaIssueFile(s)Effort
23BrowseExtract QualityFlagBadge from 3x duplication in ContentCardcontent-card.tsx20 min
24BrowseReplace native checkboxes in FilterPanel with shadcn Checkboxfilter-panel.tsx20 min
25SettingsTeam member list: grid divs → semantic <table>team-section.tsx45 min
26SettingsGovernance config list add semantic role structuregovernance-section.tsx15 min
27BidReplace BidContextSync setInterval(500ms) with callback refapp/bid/[id]/session/page.tsx30 min
28BidCopilotKit sidebar Warm Meridian theming overridesglobals.css + bid-copilot-sidebar.tsx60-90 min
Total~3-4 hrs

Tier 4: Design Decisions (Need User Input)

Section titled “Tier 4: Design Decisions (Need User Input)”

Status (S397): not yet actioned — needs Liam design input. #29 login brand identity is blocked on the Canonical wordmark asset (Thread C /design-sync). #32 (item-detail 874-line refactor) is likely stale — item-detail was already split into ReaderView/EditorView; reconcile before any work.

These require design direction, not just code changes.

#AreaIssueNotesEffort
29LoginPage lacks brand personality (generic template look)Needs logo/brand mark asset, warm background treatment1-2 hrs
30Q&A LibrarySix filter dropdowns create visual density overloadDesign decision: overflow pattern vs current inline layout1-2 hrs
31BrowseContentCard 448 lines with duplication — refactor?Decompose into CardHeader/CardFooter/QACard sub-components60+ min
32Item Detailitem-detail-client.tsx 874 lines (mostly JSX)Extract provenance + draft toggle sections60+ min
33LoginEmail-first authentication flow (enter once, choose method)UX decision: restructure login flow2-3 hrs

These were noted during audit but require no fix.

#AreaObservation
AQ&A LibraryQARow expand/collapse: full-row button is correct, chevron visual affordance is subtle but functional
BItem DetailMetadata sidebar hide-on-desktop edit pencils: acceptable power-user pattern, keyboard-discoverable
CReviewAction bar flash animation briefly competes with focus ring (150ms, minimal impact)
DQ&A LibrarySelect triggers rely on placeholder for screen reader identification (acceptable per ARIA patterns)

Grade: A-

Semantic tokens: 100% compliant. Dark mode: fully ready. Accessibility: strong (section landmarks, heading hierarchy, icon labelling). Layout: well-balanced two-column dashboard. The Active Bids section now uses BidListCard (updated this session).

Remaining: QuickStatsStrip freshness colour-coding, activity feed ARIA roles.

Grade: A-

Semantic tokens: 100% compliant. Dark mode: fully ready. Grid/list view consistency is strong. Filter controls have proper ARIA. Infinite scroll with IntersectionObserver works well. ContentCard at 448 lines is the largest component but functions correctly.

Remaining: Quality flag badge extraction, native checkbox styling.

Grade: B+

Semantic tokens: 100% compliant. Dark mode: fully ready. Keyboard navigation with arrow keys is excellent. Six filter dropdowns create visual density on narrow screens. Badge filter chips in content library drawer need keyboard accessibility.

Remaining: aria-live regions, keyboard-accessible badge filters.

Grade: A- (after error.tsx fix)

Semantic tokens: 100% compliant. Dark mode: fully ready. Content layout and readability are strong. Editing mode transitions are well-handled with beforeunload guard. Metadata sidebar has good show-on-hover edit pattern.

Remaining: Textarea aria-label attributes, editing banner icon.

Grade: A

Semantic tokens: 100% compliant (60+ bid-specific tokens). Dark mode: fully ready. Accessibility is comprehensive — state badge uses colour + text + dot (triple signal), confidence badge has WCAG comment. Keyboard focus on cards is proper. ConfidenceDot now uses vivid foreground token.

Remaining: Two raw <a> elements, CopilotKit sidebar theming.

Grade: A- (after text-white fix)

Semantic tokens: 100% compliant. Dark mode: fully ready. Data visualisation is accessible (coloured dots + text labels, not colour-only). Information density is well-balanced with collapsible domain sections.

Remaining: Icon aria-hidden attributes, gap cell context labels.

Grade: A (after emoji/panel/semantic fixes)

Semantic tokens: 100% compliant. Dark mode: fully ready. Accessibility is the strongest of all areas — full keyboard shortcuts (Enter, F, arrows, E, L, Esc, ?), role="toolbar", role="article", aria-live announcements, screen reader-friendly action labels. Speed-review UX is polished.

Remaining: Inline shadow extraction to CSS variable.

Grade: B+ (after text-white and sidebar fixes)

Semantic tokens: 100% compliant (after fixes). Dark mode: fully ready. Tab navigation is well-implemented with URL persistence. Forms are consistently structured. Team member list using grid divs instead of semantic <table> is the main accessibility gap.

Remaining: Semantic table, governance list structure.

Grade: B (after alert() and focus ring fixes)

Semantic tokens: 100% compliant. Dark mode: fully ready. Form accessibility is correct (labels, htmlFor, autoComplete, role="alert"). The main gap is brand personality — the page uses correct tokens but looks generic. This is a design decision rather than a code issue.

Remaining: Brand mark/logo, warm background treatment.


1. Semantic Token System — Fully Adopted

Section titled “1. Semantic Token System — Fully Adopted”

The @theme inline registration pattern in Tailwind v4 works well. All 60+ semantic tokens (freshness, confidence, bid state, governance, streaming phases, template review, quality scoring) are consistently used. No raw Tailwind colour classes were found in any audited file.

The CSS variable approach means zero dark: prefixes are needed in components. Light and dark modes are handled entirely by the :root / .dark token redefinitions. This is a significant maintenance win.

WCAG 2.1 AA compliance is strong across the platform. Key patterns:

  • Section landmarks with aria-label
  • Heading hierarchy (h1h2h3)
  • Focus indicators (ring-2 ring-ring ring-offset-2)
  • Multi-signal status indicators (never colour alone)
  • 44x44px minimum touch targets
  • Keyboard navigation where appropriate

Gaps are minor (aria-live regions, a few missing aria-label attributes).

Components are well-structured with consistent patterns. The largest files (ContentCard 448 lines, item-detail-client 874 lines) have been partially decomposed with extracted hooks and sub-components. Further extraction is possible but not urgent.

PrincipleScoreNotes
Signal, not noise9/10Amber reserved for CTAs. Status colours are semantic.
Orientation without instruction8/10Section headers and spatial structure guide well.
Semantic over arbitrary10/10Zero violations after fixes.
Invisible craft9/10Clean, undecorated. Cards and typography do structural work.
Accessible by default8/10Strong foundation. Minor gaps in live regions and labels.

Priority 1: Quick Accessibility Wins (30 min total)

Section titled “Priority 1: Quick Accessibility Wins (30 min total)”

Apply the 7 “High Value” remaining items above — all are aria-live, aria-label, or aria-hidden additions. No visual changes, purely assistive technology improvements.

Priority 2: Login Page Brand Identity (1-2 hrs)

Section titled “Priority 2: Login Page Brand Identity (1-2 hrs)”

The login page is the first impression. Adding a brand mark and warm background treatment would significantly improve the perceived quality of the product.

Priority 3: CopilotKit Sidebar Theming (1-2 hrs)

Section titled “Priority 3: CopilotKit Sidebar Theming (1-2 hrs)”

The CopilotKit sidebar in the bid session likely renders with default theming that may clash with the Warm Meridian palette. CSS overrides targeting the .bid-copilot-sidebar class should align it.


Audit completed 7 March 2026, Session 63. All findings verified against codebase. Quick wins applied and verified with build, test, and lint.


Audit Closeout — Session 390 (22/06/2026)

Section titled “Audit Closeout — Session 390 (22/06/2026)”

A reconciliation pass against the live codebase ahead of ID-122 found that roughly 90% of the Tier 1 + Tier 2 remainder had already been satisfied by intervening work — most notably the ID-119 design-system config-readiness wave (functional ramps, core/domain token split, naming normalisation) and the S198 follow-up refactors. The three items below were the only live gaps; all are resolved in the source-code PR that triggered this update.

#Original areaOriginal entryFix landed (ID-122)
8CoverageGap cells lack contextual aria-labelcomponents/coverage/coverage-gap-cell.tsx — gap-cell root carries role="group" plus a contextual aria-label so the cell announces “{subtopic} — no content” as a single unit to assistive technology.
10HomeAttentionCard link text not descriptive enough for ATcomponents/dashboard/unified-attention-section.tsx — action-link aria-label is now count-aware and action-led, e.g. “Review items (1 item) — {title}”. (The audit’s attention-card.tsx is now this file.)
#Original areaOriginal entryFix landed (ID-122)
19BrowseDate input format hint may conflict with browser localecomponents/browse/filter-panel.tsx — a <p> hint with id filter-date-format-hint now reads “Format follows your browser locale (e.g. DD/MM/YYYY)” and both date inputs reference it via aria-describedby, surfacing the format to AT users.

Tier 1 + Tier 2 — Already-satisfied (no diff)

Section titled “Tier 1 + Tier 2 — Already-satisfied (no diff)”

The remaining items in the Tier 1 + Tier 2 tables above were verified already satisfied by prior work (ID-119, S198, and later refactors) at the time of the ID-122 reconciliation pass. No further code changes were required for those rows.

  • Tier 3, Tier 4 and Tier 5 items remain tracked in their respective tables. ID-122 was scoped to the Tier 1 + Tier 2 remainder only.
  • The Canonical product-name flip (default branding routed through BRANDING.productName) shipped alongside ID-122 on the source side and the live-prose sweep on the docs-site side landed in a separate commit on main. The audit-table heading reference at row #14 is intentionally not rewritten here — it is preserved as the historical record of the audited surface.