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) |
Navigation axes¶
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 theleft-panel-enter-explore/left-panel-back-search/left-panel-slide-hosttest 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 Enter → Explore 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
localStoragekeyps_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) — seeuseViewerKeyboard+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
focusGraphNode → GraphNodeRailPanel (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 / Entity →
Details | Neighbourhood - Person →
Details | 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 thesummary_textparagraph, 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:
- Explicit close — user clicks the × button in the rail header
- Selecting a different subject — new subject replaces the current one
- 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, hoversurface-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
localStoragekeyps_corpus_pathand 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 fromGET /api/health(includingfeeds_api,operator_config_api,jobs_apiwhen present), Retry health button, and last error message. - Feeds / Operator YAML (aka Config; when
feeds_api/operator_config_apiare true on health): compact triggers on the status bar open the shared Corpus sources modal (data-testid="status-bar-sources-dialog") with tabs Feeds (canonicalfeeds.spec.yaml— JSON editor plus optional one-line-per-URL merge intofeeds) and Operator YAML (packagedprofile:picker fromavailable_profiles; GET may seedprofile: cloud_balancedwhen 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 whenreindex_recommendedis true from index stats.warningtoken. 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
mutedinstruction 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 |