UXS-001: GI / KG Viewer -- Shared Design System¶
- Status: Active
- Authors: Podcast Scraper Team
- Related PRDs:
- PRD-003: User Interfaces & Configuration
- PRD-017: Grounded Insight Layer
- PRD-019: Knowledge Graph Layer
- Related RFCs:
- RFC-062: GI/KG viewer v2
- Shell information architecture (regions, axes, persistence): VIEWER_IA.md
- Playwright / E2E:
- E2E surface map
- Feature UX specs (each viewer surface has its own UXS):
- UXS-002: Corpus Digest
- UXS-003: Corpus Library
- UXS-004: Graph Exploration
- UXS-005: Semantic Search
- UXS-006: Dashboard
- UXS-007: Topic Entity View
- UXS-008: Enriched Search
- Implementation paths:
web/gi-kg-viewer/src/theme/tokens.css-- CSS custom properties (--ps-*)web/gi-kg-viewer/tailwind.config.js-- Tailwind color mapping to CSS varsweb/gi-kg-viewer/src/style.css-- global styles, font stacks, focus ringweb/gi-kg-viewer/src/utils/chartTheme.ts-- Chart.js theme integrationweb/gi-kg-viewer/src/utils/cyGraphStylesheet.ts-- Cytoscape node/edge styles
Summary¶
This is the shared design system for the GI/KG viewer SPA (web/gi-kg-viewer/).
It defines semantic color tokens, typography, layout primitives, key interactive states,
accessibility targets, and component conventions that all feature UXS files reference.
Individual viewer surfaces (search, library, digest, graph, dashboard, topic entity view, enriched search) have their own UXS files for layout, density, and surface-specific rules. This file is the foundation they all build on.
Shell layout (information architecture): The three-column shell (left query column, main tabs, right subject rail, bottom status bar) is defined in VIEWER_IA.md — regions, navigation axes, subject persistence and clearing, status bar contents, and left-panel collapse. This UXS-001 file anchors tokens, typography, tunables, and shared components for those regions; do not duplicate the full IA narrative here.
InsightCard: The reusable InsightCard layout for GIL insight rows (slots, tokens, accessibility) is specified below under Components → InsightCard (shared component). UXS-007, UXS-009, and UXS-010 reference that subsection instead of duplicating card rules.
Principles¶
- Data-first: Maximize space for the graph and evidence; chrome stays quiet.
- Respect system theme: Support light and dark via
prefers-color-scheme(or equivalent) unless a future PRD mandates a manual toggle only. - Semantic coloring: GIL vs KG cues use dedicated domain tokens (
gi,kg), not ad hoc greens and purples in components. UI-level intents (primary,success,warning,danger) are separate from domain tokens. - Intelligence-tool aesthetic: Feel like a serious analytics/intelligence platform (Palantir Blueprint, Grafana, Elastic) -- professional, data-dense, modern but relaxed. Not a marketing site, not a consumer app.
Scope¶
In scope:
- Viewer shell: header (Podcast Intelligence Platform + v2), panels, file/folder pickers, banners
- Semantic color tokens (surface, text, intent, domain, chart series)
- Typography (font stacks, scale, weights)
- Layout primitives (base unit, max width, regions)
- Key interactive states (hover, focus, disabled, loading, empty, error)
- Component conventions (buttons, inputs, banners)
- Chart and graph color rules
- Accessibility targets
Non-goals:
- Brand marketing pages, email templates, or MkDocs theme
- CLI ANSI styling (covered only if a separate UXS is added)
- Feature-specific layout and density (see feature UXS files)
- Full shell information architecture (regions, the three navigation axes, subject persistence and clearing rules, first-run policy, cross-surface pseudocode) — specified only in VIEWER_IA.md; UXS-001 does not replace it
Historical note: The pre–shell-restructure left-panel Corpus | API·Data tabs are obsolete. Current chrome follows VIEWER_IA (status bar corpus path + left Search/Explore); see GitHub #606.
Boundary note: This UXS covers the static visual contract (tokens, layout, component appearance, accessibility targets). Behavioral rules (animation timing, debounce intervals, resize/collapse logic, keyboard shortcuts) belong in RFC-062. See the UXS vs RFC boundary guidance.
Theme support¶
- Mode: both (follows system via
prefers-color-scheme; manual override viadata-themeattribute on:root) - Primary palette: dark -- the viewer is a data tool used in terminal-adjacent contexts; dark mode is the design baseline
- Breakpoints: desktop only (minimum 1024px assumed; no mobile breakpoints). Approximate three-column widths at 1024px are recorded in VIEWER_IA.md — Viewport.
Semantic color tokens¶
Token values are inspired by the Palantir Blueprint gray scale with the
shadcn/ui foreground-pairing convention. Every surface token has a matching
-foreground text token so contrast is validated at the token level.
CSS custom properties use the --ps- prefix (defined in
src/theme/tokens.css). Tailwind maps these via tailwind.config.js.
Design references: Blueprint gray scale (#111418 .. #F6F7F9), shadcn/ui
background/foreground pairs, Grafana layered surfaces, Elastic UI intent/vis
separation.
Surface tokens (gray scale)¶
Five semantic steps for surfaces, from deepest background to lightest border.
| Token | Dark | Light | Usage |
|---|---|---|---|
canvas |
#111418 |
#F6F7F9 |
Page/app background |
canvas-foreground |
#E5E8EB |
#1C2127 |
Primary text on canvas |
surface |
#1C2127 |
#FFFFFF |
Panels, cards, code blocks |
surface-foreground |
#E5E8EB |
#1C2127 |
Primary text on surface |
elevated |
#252A31 |
#F6F7F9 |
Popovers, dropdowns, modals |
elevated-foreground |
#DCE0E5 |
#252A31 |
Text on elevated surfaces |
overlay |
#2F343C |
#EDEFF2 |
Hover overlays, active table rows |
overlay-foreground |
#DCE0E5 |
#252A31 |
Text on overlay surfaces |
border |
#404854 |
#D3D8DE |
Dividers, input borders |
Text tokens¶
| Token | Dark | Light | Usage |
|---|---|---|---|
muted |
#8F99A8 |
#5F6B7C |
Secondary labels, help text, timestamps |
disabled |
#5F6B7C |
#ABB3BF |
Disabled controls and placeholder text |
link |
#6cb3f7 |
#1a6fc4 |
Inline text links (distinct from action) |
Intent tokens (UI actions and feedback)¶
Separate from domain tokens; used for buttons, alerts, and status indicators.
| Token | Dark | Light | Usage |
|---|---|---|---|
primary |
#4C90F0 |
#2D72D2 |
Primary action buttons, links |
primary-foreground |
#111418 |
#FFFFFF |
Text on primary surfaces |
success |
#32A467 |
#238551 |
Positive status, confirmations |
success-foreground |
#111418 |
#FFFFFF |
Text on success surfaces |
warning |
#EC9A3C |
#C87619 |
Caution states, non-critical |
warning-foreground |
#111418 |
#111418 |
Text on warning surfaces |
danger |
#E76A6E |
#CD4246 |
Errors, destructive actions |
danger-foreground |
#111418 |
#FFFFFF |
Text on danger surfaces |
Domain tokens (GIL / KG identity)¶
These are visualization-level cues, not generic UI intents. They stay stable across themes and distinguish GIL from KG content at a glance.
| Token | Dark | Light | Usage |
|---|---|---|---|
gi |
#7dd3a0 |
#1e7a4a |
GIL / insight affordances |
gi-foreground |
#111418 |
#FFFFFF |
Text on gi surfaces |
kg |
#c4a3ff |
#5c3d9e |
KG affordances |
kg-foreground |
#111418 |
#FFFFFF |
Text on kg surfaces |
warning token vs clusters: After CIL digest pill alignment (UXS-002),
warning fill/border for search-hit emphasis on graph nodes (e.g.
Quote / search-highlighted nodes) only. Topic cluster membership and topic
discovery UI use the kg token everywhere — Digest CIL pills, graph
TopicCluster compounds, Dashboard Intelligence cluster cards. Do not use
warning fill for cluster membership or topic grouping.
Chart series tokens¶
Used by Chart.js bar/line/pie charts when more than two series are plotted. Derived from Blueprint extended palette colors that complement the domain and intent tokens.
| Token | Dark | Light | Usage |
|---|---|---|---|
series-1 |
#4C90F0 |
#2D72D2 |
First series (= primary) |
series-2 |
#7dd3a0 |
#1e7a4a |
Second series (= gi) |
series-3 |
#c4a3ff |
#5c3d9e |
Third series (= kg) |
series-4 |
#EC9A3C |
#C87619 |
Fourth series (= warning) |
series-5 |
#3FA6DA |
#147EB3 |
Fifth series (teal) |
Banners may use color-mix against surface / border; new variants should
still derive from the tokens above.
Typography¶
- UI font:
Inter, system-ui, -apple-system, "Segoe UI", sans-serif-- Inter (variable, self-hosted or Google Fonts) provides tabular numerals for data tables and a tall x-height for dense layouts. Falls back to system fonts if Inter is not loaded. - Monospace font:
"JetBrains Mono", ui-monospace, "Cascadia Code", "Fira Code", monospace-- used for transcript snippets, code blocks, JSON, and any fixed-width data. Clear glyph differentiation (0/O, 1/l/I) matters for data review. - Scale: Prefer relative sizes (
rem/ Tailwindtext-sm/text-base); keep body at 1rem (16px) equivalent for long reading; tighten slightly for dense metadata rows. Heading scale:text-xl(h1),text-lg(h2),text-base font-semibold(h3). - Dense UI: Tables and stat rows may use one step smaller (
text-sm, 0.875rem) than body but not below 0.8125rem without an explicit accessibility review. - Font weights: 400 (regular body), 500 (medium emphasis), 600 (semibold headings and labels), 700 (bold, sparingly).
Layout and spacing¶
- Base unit: 4px (0.25rem) -- spacing and gaps should be multiples where practical.
- Max content width: 960px for primary column content; graph area may be full viewport width.
- Left query column (Semantic search default; Explore as a slide-in mode from
LeftPanel.vue): collapsiblew-72rail inApp.vuewhen expanded. - Right subject column (Episode detail, graph node detail, future topic/person): fixed
width
w-96(24rem / 384px at 16px root) when expanded. To tune further, change that class on the same element (desktop-only layout). - Status bar: full-width footer under the three-column row — visual contract in Status bar below (
StatusBar.vue,data-testid="app-status-bar"). - Regions: Header + lede + panel stack; graph pages use full-height canvas with
overlays/panels that respect
surfaceandborder.
Key states¶
Visual treatment for interactive elements. Behavioral timing (debounce, transition duration) belongs in RFC-062.
- Hover: Show
overlaybackground (one step lighter thansurface); underline on links; graph nodes may show a tooltip or highlight ring usingprimaryat reduced opacity - Active / pressed: Show
elevatedbackground (one step darker thanoverlay) - Focus: 2px solid
primaryring with 2px offset; never remove native outline without an equivalent replacement - Disabled: Use
disabledtext color, 40% opacity on controls,cursor: not-allowed - Loading: Skeleton placeholder using
surface/borderstripes; graph area may show a centered spinner usingmuted - Empty state: Centered
mutedtext explaining what to load (e.g. "Select a file or folder to begin"); no decorative illustration required - Error state:
dangerborder on inputs; inline error message indanger; banner variant for page-level errors usingdanger/danger-foreground
Components¶
- Buttons: Primary uses
primary/primary-foreground; secondary usessurface/border; destructive usesdanger/danger-foreground. - File inputs / pickers: Match
surfacebackground; clear label weight (600). - Banners / alerts: Success uses
successbackground tint; warning useswarningtint; error usesdangertint; neutral info usesprimaryormuted. Domain-specific success messages (e.g. "insights loaded") may lean ongiinstead of genericsuccess.
InsightCard (shared component)¶
The InsightCard is a reusable card component used across multiple feature UXSs (UXS-007, UXS-009, UXS-010) to display a GIL Insight with consistent visual treatment. Feature UXSs declare which slots they use; the component renders only populated slots.
Required slots (always present):
- Insight text: the Insight's text content (
text-sm,canvas-foreground). - Grounding badge: "grounded" (
successtoken,text-xs) or "ungrounded" (warningtoken,text-xs). Visual indicator of data quality.
Optional slots (feature UXSs opt in):
| Slot | Token / style | Used by |
|---|---|---|
insight_type badge |
Small pill, text-xs. See token mapping below |
UXS-009, UXS-010 |
position_hint bar |
40px bar, primary on border; UXS-009 tooltip |
UXS-009, UXS-010 |
| Confidence score | muted, text-xs. Hidden when unavailable |
UXS-009, UXS-010 |
| Speaker chip | muted, text-sm. Clickable to Person Landing |
UXS-007 |
| Episode attribution | Episode title + publish date, muted, text-xs |
UXS-007, UXS-010 |
| Supporting quote | Blockquote, border left 3px, italic. With timestamp |
UXS-007, UXS-009 |
insight_type token mapping: claim = primary, recommendation = success,
observation = muted, question = link, other = muted.
"Strongest" variant (UXS-010 only): When a card represents the
strongest_insight for a topic group, it receives a primary left border (3px)
and a "Strongest" label (primary, text-xs).
Accessibility: InsightCard uses role="article" with aria-label constructed
from the insight type and first ~50 characters of text. Badge tokens are
supplemented by text labels (colour is never the sole differentiator).
Charts and graph¶
- Chart.js (and future chart wrappers) must resolve axis/grid/text colors from the
same light/dark logic as the page (CSS variables or the shared
chartTheme.tsmodule). - Multi-series charts use
series-1throughseries-5in order. Single-series charts default toprimary. Domain-specific charts (GIL vs KG breakdowns) usegi/kg. - Cytoscape (v2) node/edge styling consumes the same semantic tokens via
cyGraphStylesheet.tsso the graph matches panels and charts. - Dashboard charts (UXS-006): Every chart on the Dashboard must expose a written takeaway (Tufte-style insight): either a dedicated line below the chart or the chart title itself when it states the quantitative conclusion (e.g. stage timings headline). Parents must not render a chart with data and no takeaway. If the data does not support a clear, honest takeaway, that chart does not belong on the Dashboard surface.
Status bar¶
Visual contract for the permanent bottom row (StatusBar.vue, data-testid="app-status-bar"). What belongs in the bar (corpus path, folder picker, health dot, rebuild indicator, persistence keys) is defined in VIEWER_IA.md — Status bar. Here: tokens and layout only.
- Height: ~36px single row; background
canvas; top borderborder - Corpus path field:
surfacebackground,border; placeholder copy Set corpus path… (muted); full remaining width left of trailing affordances - Health dot: 8px filled circle;
success/warning/dangerintent tokens - Rebuild indicator (⚡):
warningwhen index stats recommend reindex (see VIEWER_IA) - Folder picker (offline): icon control per shipped chrome; match
surface/border
Corpus path field — listing and artifacts¶
Subject clearing and path-change cascade (what invalidates the subject rail, tab handoffs): VIEWER_IA.md — Corpus path change.
- Online (health ok): Typing a corpus path in the status bar field triggers listing via
GET /api/artifactsand loads every listed.gi.json/.kg.jsoninto the merged graph automatically. List opens the Corpus artifacts dialog (data-testid="artifact-list-dialog") with All / None / Load into graph; Load into graph applies the current selection and switches to Graph when ready. - Offline: Files on the status bar (or Choose files… inside the Health dialog)
loads local GI/KG without the server. The value persists in
localStoragekeyps_corpus_path.
Corpus sources dialog (Feeds | Operator YAML)¶
Modal data-testid="status-bar-sources-dialog" (title Corpus sources). Opens from status bar Feeds / Operator YAML when the matching capability is true on GET /api/health.
- Tabs: Feeds (
data-testid="sources-dialog-tab-feeds") | Operator YAML (data-testid="sources-dialog-tab-operator"). Switching tabs lazy-loads only that tab’s API so a broken operator file does not block the Feeds editor. - Feeds tab: Internal sub-tabs Manage (
sources-dialog-feeds-panel-list) vs Raw JSON (sources-dialog-feeds-panel-json). Manage:sources-dialog-feeds-add-url+sources-dialog-feeds-add-btn(Add feed) appends one URL at a time (deduped); per-row Edit / Delete; each change persists viaPUT /api/feeds. Raw JSON:sources-dialog-feeds-textarea+ Apply JSON. Persisted as corpusfeeds.spec.yaml(same as CLI--feeds-spec, RFC-077 / #626). Help copy states feeds are not edited in Operator YAML. - Operator YAML tab:
- Shows resolved file path from
GET /api/operator-config(operator_config_path) in small muted text above the controls. - Profile row: native
<select>data-testid="sources-dialog-profile-select". Options:None(empty value — noprofile:line persisted), then every name inavailable_profilesfrom GET (sorted server list). If the on-disk file references aprofile:name not in that list (custom / stale preset), show an extra option{name} (custom)so the value round-trips until the operator changes it. Whenviewer_operator.yamlis missing or whitespace-only, GET/api/operator-configseedsprofile: cloud_balancedon disk if that stem exists under packagedconfig/profiles(otherwise content stays empty until the user saves). - Overrides editor: monospace
sources-dialog-operator-textarea— YAML for keys other than top-levelprofile:(the<select>is the sole source of truth forprofile:on Save). Anyprofile:line pasted into the textarea is stripped on save when resolving body text; None in the menu therefore always persists without aprofile:key even if the textarea still contained a pastedprofile:line. On load, the client splits file content into select + body (seeoperatorYamlProfile.ts). - Save YAML persists via
PUT /api/operator-configwith JSON{ "content": "..." }. Errors: 422 invalid YAML; 400 withdetail.keysfor forbidden secrets or forbidden feed keys (rss,rss_url,rss_urls,feedsat root). Server validation is shallow (top-level keys only), not a full duplicate ofConfigvalidation. - Help copy: secrets only via environment; packaged preset defaults merge first (#593), explicit keys in the file override; feeds belong in the Feeds tab / list file.
Playwright map: update E2E surface map when data-testids here change.
Shell keyboard shortcuts¶
Implemented in useViewerKeyboard.ts (global handlers, not per-component ad hoc):
- Slash — When focus is not in an editable control: expand the left query column if
needed, switch to Search mode, then focus
#search-q. - Escape — On the Graph main tab, when focus is not in an editable control: clear graph interaction / transient selection state.
- 1 / 2 / 3 / 4 (#674 item 5) — When focus is not in an editable control: switch the main tab to Digest / Library / Graph / Dashboard respectively. Editable-target guard prevents firing while typing in any text input / textarea / contenteditable.
- G / L on semantic search hits + Library episode rows (#674 item 2) — Bound as click targets on each row (Show on graph / open episode in subject panel; Library row also has an S prefill-search button), not as document-level shortcuts, so they do not fight the browser’s find shortcut or OS bindings.
Accessibility¶
- Focus: Visible focus styles on all interactive elements (native or custom); do not remove outline without a replacement that meets contrast.
- Contrast: Aim for WCAG 2.1 AA for normal text against
canvas/surface; verifymutedonsurfacefor small text. Foreground-pairing ensures each surface token has a pre-validated text color. - Motion: Respect
prefers-reduced-motionfor non-essential animations.
Tunable parameters¶
The token architecture (names, pairing convention, intent/domain split) is frozen.
The values below are deliberately open for experimentation during early v2
development. Adjust them in tokens.css (CSS custom properties) or via browser
DevTools; do not hard-code alternatives in component files.
| Parameter | Current value | Status | Notes |
|---|---|---|---|
| UI font family | Inter | Open | Could try Geist, IBM Plex Sans, system stack |
| Monospace font family | JetBrains Mono | Open | Could try Fira Code, Source Code Pro |
| Base font size | 16px (1rem) | Open | 14px more dense; 16px more readable |
| Border radius | 0.375rem (6px) | Open | 0 = sharp/Palantir; 0.5rem = softer |
| Spacing base unit | 4px | Open | Could move to 6px or 8px for more air |
| Shadow depth | None (flat) | Open | Subtle shadow = more depth/elevation cue |
| Surface gray palette | Blueprint-derived #111418..#404854 |
Open | Exact values may shift during contrast review |
| Intent color hues | Blueprint-derived blue/green/orange/red | Open | Hues stable; saturation/lightness may tune |
| Domain color hues (gi/kg) | Green #7dd3a0 / Purple #c4a3ff |
Frozen | Identity colors; do not change w/o UXS rev |
| Token names | canvas, surface, primary, gi, etc. |
Frozen | Names are the API; values are the theme |
| Pairing convention | Every surface gets -foreground |
Frozen | Structural rule; not negotiable |
| Intent/domain separation | Intent for UI; domain for GIL/KG | Frozen | Structural rule; not negotiable |
| Digest similarity strong floor | 0.85 | Open | Strong vs good; constant DIGEST_SIMILARITY_STRONG_MIN |
| Digest similarity good floor | 0.70 | Open | Good vs weak; constant DIGEST_SIMILARITY_GOOD_MIN |
| Recency dot rolling window | 24h from local YYYY-MM-DD midnight | Open | Digest + Library episode rows; date-only API |
| Library feed search threshold | 15 feeds | Open | Constant in LibraryView.vue; default 15 feeds trigger |
| Graph default episode cap (initial) | 15 episodes | Open | GRAPH cap in graphEpisodeSelection.ts; graph-only lens |
| Graph episode load recency score floor | 0.2 | Open | GRAPH_SCORE_RECENCY_MIN; linear mix with max 1.0 |
| Graph episode load topic cluster bonus | 0.4 | Open | TOPIC_CLUSTER_BONUS; additive (graphEpisodeSelection.ts) |
| Graph episode load all-time decay days | 90 | Open | ALL_TIME_DECAY_DAYS; trailing window from newest publish |
| Graph episode load GI density max weight | 0.4 | Open | GI_DENSITY_MAX; future per-episode coverage API cap |
| Graph recency seed default | 7d | Open | Initial graph window; VIEWER_GRAPH_SPEC (init load) |
| Dashboard GI coverage warn threshold | 50% | Open | Briefing + Coverage; UXS-006 §3.4 |
| Dashboard index coverage warn threshold | 60% | Open | Briefing + Coverage; UXS-006 §3.4 |
| Dashboard action items max | 3 | Open | Briefing card triage cap |
| Dashboard Top voices limit | 5 | Open | persons/top default limit |
| COSE ABOUT edge ideal length | 80px | Open | cyCoseLayoutOptions.ts; VIEWER_GRAPH_SPEC (styling) |
| COSE MENTIONS edge ideal length | 150px | Open | cyCoseLayoutOptions.ts; VIEWER_GRAPH_SPEC (styling) |
| Graph recency decay (opacity) | 90 days | Open | VIEWER_GRAPH_SPEC §4.1 node recency vs episode pick |
| Graph recency minimum opacity | 0.4 | Open | VIEWER_GRAPH_SPEC §4.1 |
| Graph degree heat max degree | 30 | Open | GraphCanvas.vue; VIEWER_GRAPH_SPEC §4.3 |
| Graph label zoom (hide all labels) | 0.5 | Open | Below: no labels (GraphCanvas.vue) |
| Graph label zoom (full labels) | 1.0 | Open | Above: full node labels |
| Graph compound fill opacity | 0.06 | Open | TopicCluster compound; VIEWER_GRAPH_SPEC §3.6 |
Code map: Similarity floors live in web/gi-kg-viewer/src/utils/digestRowDisplay.ts; recency parsing and the rolling window live in web/gi-kg-viewer/src/utils/digestRecency.ts; the feed-count threshold constant lives in LibraryView.vue. Corpus graph episode pick (cap, recency + topic-cluster score, tie-break) lives in web/gi-kg-viewer/src/utils/graphEpisodeSelection.ts.
Graph canvas / layout (open, not in table above): They ship in code today but are intentionally easy to tune without a UXS revision — see Viewer graph spec — Graph visual styling and UXS-004.
- COSE semantic
idealEdgeLength: peredgeTypeincyCoseLayoutOptions.ts, applied only after the intra-tc:topic-cluster branch (that branch stays highest priority). - COSE semantic
edgeElasticity: same file; insidetc:clusters the profile default applies. - Label zoom thresholds:
GraphCanvas.vue— no labels when zoom is strictly below 0.5; short labels from 0.5 through 1.0; full label above 1.0 (event-driven node classes, not Cytoscape min-zoom stylesheet selectors). - Topic degree heat
maxDegree: 30 inGraphCanvas.vue(WIP §4.3 cap). - Rail minimap (
GraphNeighborhoodMiniMap.vue): ReusesbuildGiKgCyStylesheetcompact +prefersReducedMotionQuery(), andsyncGraphLabelTierClassesafterlayoutstop/ fit so label-tier rules match that instance’s zoom.
How to experiment¶
- Browser DevTools: Inspect
:root, change any--ps-*variable live. tokens.csspresets: Create alternate files (theme-compact.css,theme-relaxed.css) that override the open parameters. Import one at a time.- Pinia theme store (
stores/theme.ts): Sets CSS variables at runtime, enabling a theme switcher in the UI for side-by-side comparison.
When an open parameter is finalized after experimentation, update its status to Frozen in this table and record the decision in the revision history.
Visual references¶
No wireframes checked in yet. As the v2 UI stabilizes, add annotated screenshots or Figma frames here to lock layout expectations for the main panels, graph canvas, and search overlay.
Acceptance criteria¶
- [ ] New viewer UI uses semantic tokens only (no stray hex in Vue/SFC or JS style objects)
- [ ] Theme support matches declared mode (both, system-driven, dark baseline)
- [ ] Light and dark values match the token table (or the table is updated with rationale)
- [ ] Every surface token uses its matching
-foregroundfor text (no ad hoc text colors) - [ ] Intent tokens (
primary/success/warning/danger) are used for UI feedback; domain tokens (gi/kg) are used only for GIL/KG identity - [ ] Key interactive states match this spec (hover, focus, disabled, error, empty, loading)
- [ ] Focus states visible on buttons, inputs, and graph controls
- [ ] Chart.js series use
series-1throughseries-5; graph colors derive from palette - [ ] Inter and JetBrains Mono load correctly (or system fallbacks render acceptably)
- [ ] RFC-062 implementation checklist references this UXS for theme work
- [ ] Tunable parameters table reflects current status (open values finalized -> frozen)
Revision history¶
| Date | Change |
|---|---|
| 2026-04-20 | Tunables: graph initial-load episode scoring (recency floor/max, topic-cluster bonus, 90d all-time decay, future GI density cap) in graphEpisodeSelection.ts + table rows |
| 2026-04-19 | Tunable: Graph default episode cap (15) for corpus graph initial load (VIEWER_GRAPH_SPEC initial load) |
| 2026-04-19 | Open tunables for digest similarity tiers, recency dot window, Library feed-search threshold (#610) |
| 2026-04-03 | Initial draft |
| 2026-04-03 | Added theme support, key states, boundary note, visual refs section |
| 2026-04-03 | Blueprint gray scale, shadcn pairing, intent/domain split, Inter + JetBrains Mono, chart series tokens, design-reference citations |
| 2026-04-03 | Added Tunable parameters section (frozen vs open knobs); RFC-062 and PRD cross-references updated to document theme preset system |
| 2026-04-06 | Status Active — viewer v2 implements this contract per RFC-062 |
| 2026-04-13 | UXS-001 hub; UXS-002 through UXS-008 for feature specs |
| 2026-04-13 | Added shared InsightCard component (UXS-007/009/010 alignment) |
| 2026-04-19 | Summary pointer to InsightCard subsection for discoverability |
| 2026-04-17 | Open tunables for graph COSE semantics, label zoom tiers, Topic degree heat cap (VIEWER_GRAPH_SPEC) |
| 2026-04-17 | Graph bullets: rail minimap shares reduced-motion + syncGraphLabelTierClasses with main canvas |
| 2026-04-19 | Corpus path List opens status-bar artifact-list-dialog (not Dashboard workspace) |
| 2026-04-19 | Corpus path List/Load wording; shell keyboard shortcuts (slash, Escape, row G/L) |
| 2026-04-19 | Status bar section with #status-bar; scope non-goal + historical note for shell IA; corpus path subsection links VIEWER_IA flows |
| 2026-04-20 | Corpus sources dialog (#corpus-sources-dialog): Feeds vs Operator YAML; profile <select> + overrides textarea; sources-dialog-profile-select; RFC-077 / #593 merge semantics |
| 2026-04-21 | Operator save: profile <select> sole source of truth; None strips pasted profile: in textarea; shallow validation called out |
| 2026-04-21 | Corpus sources Feeds: Add feed one URL at a time + Manage / Raw JSON sub-tabs; operator GET seeds profile: cloud_balanced when viewer_operator.yaml is missing/whitespace-only and preset exists; client parses FastAPI JSON detail for clearer errors |
| 2026-04-19 | Shell IA pointer to VIEWER_IA; warning vs kg clusters; Dashboard charts require a written takeaway (dedicated line or chart title); tunables: graph 7d seed, COSE lengths, recency decay, label tiers, compound opacity, Dashboard thresholds / caps |