Skip to content

Viewer Information Architecture

Status: Active Lives in: docs/uxs/VIEWER_IA.md Scope: Structural layer only — where things live, what each area is for, and how navigation works. This document does not cover visual tokens (UXS-001), per-surface layout detail (UXS-002 through UXS-010), or behavioral rules such as animation timing and debounce intervals (RFC-062).

Canonical shell IA: This file is the single source for regions, axes, persistence, and clearing after the shell restructure (GitHub #606). Feature UXSs (UXS-002–010) link here for “where in the shell” context; they own surface density and controls. Timing, debounce, and keyboard implementation details live in RFC-062: GI/KG viewer v2.

Single canonical IA: For the GI/KG viewer, information-architecture decisions (regions, axes, persistence, clearing, first-run) live only in this document. UXS-001 and UXS-002 through UXS-010 defer here for shell questions and own tokens plus per-surface visual contracts.

No placeholder UI: The shipped app must not show controls or sections for capabilities that are not implemented. First-run copy and disabled states describe only what exists today; speculative “attention” chrome (e.g. pulsing borders) is out of scope until a dedicated implementation ships.

UXS files reference this document for shell structure. RFC-062 references it for the navigation model that behavioral rules are built on top of.


Mental model

The viewer is a query-driven intelligence tool. The left panel is always available for querying the corpus. The main area provides four perspectives on the same corpus. The right rail shows detail about whatever entity is currently in focus. The status bar keeps the operational context visible at all times.

Subject panel as single context layer: The right rail is the one context column. Every entity type (episode, topic, person, graph node) surfaces there the same way, no matter which tab or query opened it. There is no parallel “detail drawer” per tab — the subject store drives the rail.


Shell layout

┌─────────────────────────────────────────────────────────────────────┐
│  HEADER: App title · version · main tab buttons · theme toggle       │
├────────────────┬────────────────────────────────┬────────────────────┤
│                │                                │                    │
│  LEFT PANEL    │         MAIN AREA              │   RIGHT RAIL       │
│                │                                │                    │
│  Search        │  Digest | Library |            │  Subject Panel     │
│  (default)     │  Graph  | Dashboard            │                    │
│  ↔ Explore     │                                │  (empty when       │
│  mode          │  (tab content)                 │   nothing          │
│                │                                │   selected)        │
│                │                                │                    │
├────────────────┴────────────────────────────────┴────────────────────┤
│  STATUS BAR: [ corpus path _________________________ ]  ● OK  ⚡     │
└─────────────────────────────────────────────────────────────────────┘

Four independent areas. Each has one job:

Area Job
Left panel Query the corpus (Search default; Explore as a deliberate mode)
Main area Browse four perspectives on the corpus (tabs)
Right rail Understand the current subject (entity detail)
Status bar Operational context (corpus path + server health)

The app has no URL routing. Navigation is tab and store state.

Three independent axes operate simultaneously:

Axis Values Who owns it
Main tab digest / library / search / graph / dashboard App.vue local ref (Search v3 — RFC-107)
Subject (right rail) episode / topic / person / graph-node / none subjectStore
Left panel surface launcher (compact query field + recent, non-Search tabs only; hidden on Search) shellStore.leftPanelSurface + searchStore (Explore mode retired — RFC-107 §5)

Changing one axis does not reset the others. Switching from Library to Graph does not clear the subject rail. Running a search does not change the active tab.

Library tab modes (UXS-015 / RFC-104). The library main tab has two browse modes, toggled at the tab head and persisted per session:

  • Episodes (episode-first, default — status quo, UXS-003) — the flat, paginated episode list with the Feed filter chip.
  • Shows (shows-first, opt-in) — a grid of the corpus's shows (GET /api/corpus/feeds). Selecting a card opens that show in the right subject rail (subject.focusShow → the 'show' subject kind → ShowRailPanel) — not in-panel; the grid stays put so you can open another. The show rail mirrors the episode rail (cover + title + "N episodes · RSS" meta, then the show's episodes).

Both modes cross-link an episode into the graph via the shared subject.focusEpisode (metadata_relative_path) path — a Shows-mode episode behaves identically to an Episodes-mode row. Clicking an episode inside the show rail opens it in the same rail (with a ‹ Back to the show, via the subject history stack).


Left panel — Query interface

Always visible. Permanent on the left regardless of which main tab is active.

One surface, one column width (w-72):

  • Search: semantic corpus search — query form, run controls, scrollable results. See UXS-005 — Semantic Search.
  • Explore mode retired. The slide mode-switch, shell.leftPanelSurface = 'explore', and the left-panel-enter-explore / left-panel-back-search / left-panel-slide-host test IDs were removed when Explore's filters folded into the Workspace filter chip bar (UXS-016 §Header). See UXS-005 for the full retirement note.

Keyboard: / expands the column if collapsed, sets surface to Search, then focuses #search-q (useViewerKeyboard + LeftPanel focusQuery). In Explore, Enter in Topic contains or Speaker contains runs Explore; Limit / Min confidence use the same EnterExplore behavior inside the Advanced explore dialog. Enter in Quick question runs Run quick question; Shift+Enter there inserts a newline (IME composition does not trigger submit).

Purpose: Search stays the default mental model (like a map app’s search field). Explore is a deliberate “browse corpus” mode, not a second always-visible stack.

Visual spec: UXS-005 (Search), UXS-001 (tokens)

Collapse (Option B)

The left column may be collapsed to a narrow strip so the main area gains width (especially on the Graph tab). Behaviour:

  • Control: A single collapse/expand control on the top edge of the left column (first row of that column), not inside SearchPanel.
  • Expanded: Column width w-72 (288px) — Search or Explore mode (one visible at a time).
  • Collapsed: Column width w-8 (32px) — strip shows the toggle plus a vertical Search affordance; activating it expands the column to Search mode and focuses the query field. Explore is opened only from the expanded column (Explore corpus →), or via graph hand-offs that set Explore mode programmatically (App.vue + shell.setLeftPanelSurface('explore')).
  • Persistence: Preferred open/closed state is stored under localStorage key ps_left_panel_open ("true" / "false"). Default when the key is missing: expanded.
  • Slash (/): When focus is not in an editable control, / expands the left column if it was collapsed, switches to Search mode, then focuses the semantic search query field (#search-q) — see useViewerKeyboard + App.vue + LeftPanel.vue.

Implementation map: web/gi-kg-viewer/src/App.vue (column chrome, leftOpen), LeftPanel.vue (Search + Explore body). Broader module map: VIEWER_FRONTEND_ARCHITECTURE.md.


Right rail — Subject panel

Context-driven. Shows detail about the current subject. Empty when nothing is selected.

Subject types

Subject Store kind Entry points Component
Episode episode Library row click, Library row G button (#674), Digest row, Search L button EpisodeDetailPanel
Show show Library Shows-mode grid card click (focusShow); episode-in-show → episode subject with ‹ Back (UXS-015 / RFC-104) ShowRailPanel
Topic / Entity topic Digest topic-band title (#674); programmatic focusTopic / focusEntity TopicEntityView (UXS-007)
Person person Search supporting-quote speaker name (#674), Explore Top speakers rollup (#674) PersonLandingView (UXS-010)
Graph node graph-node Any graph node click (Topic / Insight / Quote / Person / Entity / Episode in graph) GraphNodeRailPanel (uses NodeDetail)
(empty) null Nothing selected, corpus not loaded Empty state

Topic vs Graph-node split: A graph click routes through focusGraphNodeGraphNodeRailPanel (rich graph-aware detail). focusTopic / focusEntity are higher-level subject-overview handoffs from non-graph surfaces (Digest, Search, Explore) that open the same TopicEntityView overview rail panel — name + aliases + description + monthly mentions timeline + linked insight/quote list.

Speaker handoffs auto-load the corpus graph baseline — a Person clicked from Search / Explore without a prior Graph visit triggers corpusGraphBaselineLoaderKey so the panel has data to render (mirrors the Digest topic-band auto-load).

Unified node view (target)

There is one subject surface: the polymorphic node view (NodeDetail). No separate Topic / Person / Entity overview panels, and no "Open full … profile" links — clicking a subject from any surface lands directly in the node view. The rail routes every non-episode kind (topic / person / graph-node) through the node view; the store API (focusTopic, focusPerson, focusEntity, focusGraphNode) is unchanged so all handoff call-sites keep working.

Tabs are polymorphic by node type:

  • Topic / EntityDetails | Neighbourhood
  • PersonDetails | Position Tracker | Neighbourhood

Details (first tab) carries the full type-specific detail — the content previously split into TopicEntityView / PersonLandingView folds in here:

  • Topic / Entity — name · aliases · cluster identity (Theme / Similar) · a compact mentions timeline (topic-level; each episode is a collapsible row — collapsed shows the episode's own title, our generated summary_title, and date; expanding reveals the summary_text paragraph, never the KEY POINTS bullets) · Across shows · Related topics · Key voices · Entities involved · inline enrichment (velocity, co-occurs-above-chance).
  • Person — name · role · aliases · description · ranked topics · insights voiced · episodes appeared · connections (co-speakers / topics) · enrichment (grounding %, often-appears-with, contradicts) · stated positions.

Retired: TopicEntityView and PersonLandingView as standalone rail routes; the generic entity overview (same component as topic) folds in too. No Signals tab for now — may be reintroduced later to offload enrichment / diagnostics. Episodes keep EpisodeDetailPanel for now; a follow-up trims its Details to summary + insights and moves the bridge partition off Details into a renamed enrichment tab.

Supersedes the per-type routing described in UXS-007 (Topic Entity View) and UXS-010 (Person Profile).

Persistence rule

The subject persists when switching main tabs. If an episode is open in the rail and the user switches from Library to Graph, the episode rail stays open. On the Graph tab, the corresponding episode node is highlighted and centred in the canvas if it exists in the merged graph.

Clearing rule

The subject clears on:

  1. Explicit close — user clicks the × button in the rail header
  2. Selecting a different subject — new subject replaces the current one
  3. Corpus path change — path change invalidates subject context

Tab switching does not clear the subject.

Close button

Every subject panel mode has a × button in the rail header.

  • Position: right-aligned in the rail header row
  • Token: muted, hover surface-foreground
  • aria-label: "Close [subject type] detail" (e.g. "Close episode detail")
  • data-testid: subject-rail-close

Empty state

When no subject is selected: centred muted text — "Select an episode, topic, or graph node to see details here."

Visual spec: UXS-001 (tokens, shared component rules), per-type detail in UXS-003, UXS-004, UXS-007, UXS-009, UXS-010


Main tabs

Five tabs. Each answers a different question about the same corpus.

Tab Question it answers Default?
Digest What's new in my corpus recently? ✓ Yes (first load)
Library What episodes and feeds have been processed?
Search What am I trying to find out? (Search v3 — RFC-107)
Graph How do entities, topics, and insights connect?
Dashboard Is my corpus healthy, and what should I do?

Tab order: Digest · Library · Search · Graph · Dashboard (left to right)

Default on first load: Digest (per-user override lands in a future USERPREFS-1 pref — RFC-107 OQ1).

Search v3 (RFC-107, in flight). The Search tab is the operator's first-class query surface — a full-width Query Workspace (UXS-016). The former left-column Explore mode is retired; its filters (topic contains / speaker contains / limit / min confidence / grounded only / presets) fold into the Workspace filter chip bar. The LeftPanel Search column is retained as a compact launcher on non-Search tabs (see UXS-005). A shell-wide Cmd-K command palette is summonable from any tab.

Digest

Discovery surface. Rolling-window recent episodes and semantic topic bands. Shares a date lens (corpusLens) with Library. See UXS-002.

Library

Catalog. Answers "what's in the corpus?" with full filtering. Feed list + paginated episode list + episode subject panel. See UXS-003.

Graph

Topology exploration. Cytoscape canvas with GI/KG merged graph. Loads a time-bounded episode slice by default (see graphLens in Viewer graph spec). The graph is another perspective on the same entities that Library and Digest surface — not a separate information domain. See UXS-004.

Chrome (where controls live): A stats strip at the top of the graph card (episode / node / component counts, plus Gestures when a full merged graph is loaded), optional search-highlight chip row, then a compact Types row and filters popover. A collapsible bottom bar under the canvas holds minimap / re-layout / layout cycle, the graph time lens (when applicable), Fit / zoom / PNG, and Gestures only when the stats strip is absent. Details: UXS-004 — Graph chrome.

Dashboard

Operational intelligence. Permanent briefing card (last run, corpus health, and action items) sits above three sub-tabs: Coverage, Intelligence, Pipeline.

See UXS-006 (includes the full dashboard implementation specification).


Status bar

Always visible. Permanent at the bottom of the shell, below the main content area. Single row, ~36px tall.

Contents (left to right):

  • Corpus path input: always-visible editable text field. Placeholder "Set corpus path…". Changing the value triggers artifact listing + graph auto-load cascade. Value persisted in localStorage key ps_corpus_path and restored on next session.
  • Folder picker button [📁]: appears in offline mode (no server). Opens local file picker for loading GI/KG artifacts without a server.
  • Health dot : 8px circle. success = server OK, warning = degraded, danger = unreachable. Clicking opens an anchored popover with capability rows from GET /api/health (including feeds_api, operator_config_api, jobs_api when present), Retry health button, and last error message.
  • Feeds / Operator YAML (aka Config; when feeds_api / operator_config_api are true on health): compact triggers on the status bar open the shared Corpus sources modal (data-testid="status-bar-sources-dialog") with tabs Feeds (canonical feeds.spec.yaml — JSON editor plus optional one-line-per-URL merge into feeds) and Operator YAML (packaged profile: picker from available_profiles; GET may seed profile: cloud_balanced when the operator file is missing/empty if that preset exists — plus monospace overrides YAML; not a second place to edit feed URLs). Tokens, data-testids, and field layout: UXS-001 — Corpus sources dialog.
  • Rebuild indicator : appears only when reindex_recommended is true from index stats. warning token. Clicking opens health popover scrolled to the index section.

Visual spec: UXS-001 — Status bar (tokens, height, data-testid)


Viewport — three-column widths (1024px baseline)

The viewer assumes a minimum ~1024px width (UXS-001). With default Tailwind shell widths at that viewport (both side columns expanded):

Region Approx. width Notes
Left column 288px (w-72) Collapsed: 32px (w-8)
Main area ~400px Remainder after header padding, borders, and both side columns — tight for Library filters + list; acceptable for Graph + Digest
Right subject column 384px (w-96) Collapsed: 32px (w-8)

Right rail collapse: Expanded/collapsed is toggled in App.vue (rightOpen) for more canvas width on Graph. Persistence: stored under localStorage key ps_right_panel_open ("true" / "false", default "true" when missing) — symmetric with the left column's ps_left_panel_open (#673). The rail also auto-opens when a new subject is focused (focusEpisode / focusGraphNode / focusTopic / focusPerson) so a click in Digest / Library / Search / Explore is always immediately visible even if the user had collapsed the rail.

If usability testing shows the main strip is too narrow at 1024px, tune w-72 / w-96 in App.vue and record new numbers here (see also UXS-001 breakpoints / tunables).


First-time / empty corpus state

When no corpus path has been set (first session, or localStorage cleared):

  • Status bar: corpus path field shows placeholder only; health dot shows danger (server may be reachable but no corpus is configured)
  • Briefing card: "Set a corpus path in the status bar below to begin."
  • All main tabs: consistent empty states — short muted instruction per tab
  • Left panel search: input disabled, tooltip "Set corpus path to enable search"
  • Right rail: standard empty state

The viewer should feel intentionally empty — clear next steps — not broken.

Do not add speculative motion (e.g. pulsing border on the path field) or other “draw attention” chrome until that behaviour is implemented in the same change as its spec.


Key cross-surface flows

These flows show how the navigation axes interact in common workflows.

Open an episode from any surface

Any entry point (Library row / Digest row / Search L / Graph episode node)
  → subjectStore.focusEpisode(metadataPath)
  → Right rail: EpisodeDetailPanel
  → If mainTab === 'graph': episode node highlighted + centred in canvas
  → Main tab: unchanged

Open a topic from any surface

Any entry point (Graph topic node / Search result / Digest band / Dashboard)
  → subjectStore.focusTopic(topicId)
  → Right rail: TopicEntityView
  → If mainTab === 'graph': topic node highlighted + centred
  → Main tab: unchanged

Search result → subject

Search result: user clicks G (graph focus)
  → subjectStore.focusGraphNode(cyId)
  → mainTab switches to 'graph'
  → Right rail: GraphNodeRailPanel for that node

Search result: user clicks L (library)
  → subjectStore.focusEpisode(path)
  → Right rail: EpisodeDetailPanel
  → Main tab: unchanged (user stays on current tab)

Corpus path change

User edits corpus path in status bar
  → shellStore.setCorpusPath(newPath)  [writes localStorage]
  → subjectStore.clearSubject()        [path change invalidates context]
  → Cascade: artifacts / library / digest / dashboard / index refresh

Boundaries — what this document does not cover

Topic Where it lives
Visual tokens, colours, typography UXS-001
Per-surface layout and component detail UXS-002 through UXS-010
Animation timing, debounce intervals RFC-062
Graph-specific interaction model RFC-062, UXS-004
API endpoints and data contracts Server guide, RFC files
Implementation component tree VIEWER_FRONTEND_ARCHITECTURE.md

Revision history

Date Change
2026-04-19 Initial draft; shell restructure; subject rules; status bar; flows
2026-04-19 Opening principle (single context layer); collapse Option B + ps_left_panel_open; 1024px width table; no-placeholder / first-run policy
2026-04-19 Single canonical IA policy; visual spec link to UXS-001 #status-bar
2026-04-19 Boundaries table: link Server guide path (../guides/SERVER_GUIDE.md)
2026-04-20 Status bar: Feeds / Operator YAML open Corpus sources modal; profile + feeds split (RFC-077 / UXS-001 #corpus-sources-dialog)
2026-04-21 Operator YAML aka Config; empty available_profiles note; preset list cwd+repo union (RFC-077)
2026-04-21 Corpus sources Feeds: optional one-URL-per-line merge; operator GET may seed profile: cloud_balanced when file missing/empty
2026-04-21 Left panel: Search default vs Explore mode (slide); shell.leftPanelSurface; collapsed strip Search only; / → Search + focus
2026-04-21 Explore: Advanced explore dialog (Limit, Sort, Min confidence, checkboxes); Enter on main topic/speaker + in dialog
2026-04-21 Explore: Enter on filter fields → Explore; Enter on quick question → Run quick question; Shift+Enter newline
2026-04-21 Graph subsection: Chrome paragraph (stats strip, Types + , bottom bar) links UXS-004 graph chrome section