V1 Web PWA · Surface walkthrough

Veritas · topic hub

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections5 minread

On this page

Context. surface customer · domain veritas · route /veritas/topic · auth signed-in · source apps/oshun/web/src/app/veritas/topic/page.tsx

Last walked. 2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data (2xx), console/page-errors, expected content, screenshot verified; live screen-reader, touch, offline, and telemetry-delivery checks pending a manual AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content re-verified 2026-06-03 against current source; 2026-06-29 Playwright re-walk added row/cell-level topic anchors and direct route assertions

Purpose#

A topic hub: what is settled with high confidence, what is open / contested / moving, what the reader has read so far on this topic, and what gaps Lilith has named. A separate column lists primary sources for the topic and the reader's "follow" subscriptions. Thin server component awaits getTopicHub() and renders VeritasTopic.

Entry points#

  • Sub-nav "Topic hubs" tab on /veritas — verify this is the target
  • Linked from /veritas evidence rail — when a claim belongs to a topic with a hub (verify)
  • Linked from /explore — discovery of grounded topics (verify)
  • Direct URL / bookmark — yes (auth required)
  • Internal namespace alternate/domains/veritas/topics and /domains/veritas/topics/[topicId] render a different (workspace-style) view; see those files

Layout regions#

page.tsx awaits getTopicHub()<VeritasTopic>. The component mounts LWebShell with LCustomerNav active="library".

  • Header: Lilith customer nav (Library tab active)
  • Masthead (VrMast): Veritas · the topic | energy · norway · winter 2024–25 | <storyCount> stories · <sourceCount> sources · <openDisputeCount> open disputes, display title "The topic, in one place.", italic lede
  • Two-column grid (1.4fr / 1fr):
    • Left — Settled / Open / What you've read:
      • VrHead: I · Settled, broadly · What we believe with high confidence.
      • Per data.settled[i]: solid-border card with italic statement (16px), conf. <row.confidence> code on the right, eyebrow <row.sourceCount> below
      • VrHead: II · Open · contested · moving · What is not yet decided.
      • Per data.open[i]: dashed-border card with italic statement, italic subline, and medium-confidence whenLabel code
      • VrHead: III · What you've read · From this topic, on Veritas. · "14 of 47 stories · last 30 days" (right text is hardcoded — verify)
      • Reading heatmap: 7-column CSS grid of square cells; cell colour derived from data.readingHistory.readPattern[i] value (accent / #d99777 / paper / L.rule2) — count is data.readingHistory.totalCells
      • Heatmap legend: "Each cell, one day. Solid · read; hollow · skipped."
    • Right — Primary sources / Gaps / Follow:
      • VrHead: — · Primary sources · What we go to first.
      • Per data.primarySources[i]: italic name + description, Nx cited code, VrBand chip
      • VrHead: — · Gaps Lilith named · Where the topic is thin.
      • Paper-bg card with italic copy from data.gapsLines[] (one line per gap, separated by <br />)
      • VrHead: — · Follow · Tell me when this moves.
      • Per data.follows[i]: italic label + ● <on> accent code (looks like a status pill — verify whether it should be a toggle)

States#

  • LoadinggetTopicHub() awaited server-side; no client skeleton
  • Populated — fixture renders all sections; verified through data-veritas-topic-page, settled/open/source/follow row anchors, gaps line anchors, and the reading-history cell grid
  • No open itemsdata.open[] empty; the section still renders header but maps over an empty array (verify graceful behaviour)
  • No reading historydata.readingHistory.readPattern[] of all zeros yields all-accent cells (verify the colour ramp; value 0 maps to L.accent per the ternary)
  • No followsdata.follows[] empty; verify rendering
  • Error (recoverable)getTopicHub() rejects → nearest error.tsx
  • Offline — cached HTML; nothing client-side here
  • Standalone PWA — 1.4fr/1fr grid; verify collapse on narrow screens
  • Empty — N/A; route always loads one topic
  • Gated — none coded

Interactions#

Section I — Settled#

  • Settled card — non-interactive; Playwright asserts every fixture row through data-veritas-topic-settled-row attributes for statement, confidence, and source-count; verify whether each statement should link to its canonical story / claim
  • conf. <N> code — text only

Section II — Open#

  • Open card (dashed border) — non-interactive; Playwright asserts row attributes for statement and whenLabel
  • whenLabel code — text only; e.g., "decision expected by …"

Section III — Reading heatmap#

  • Cell (per day) — non-interactive; Playwright asserts 28 data-veritas-topic-reading-cell nodes and the first five fixture values. Verify whether hovering should reveal what was read that day (no tooltip in code)
  • Legend — text only

Right column — Primary sources#

  • Source row (per source)
    • Function: not a link in code; Playwright asserts the static row contract via data-veritas-topic-primary-source-row, name, band, and cited-count attrs. Verify whether each row should target /veritas/source for that source
    • VrBand chip — non-interactive

Right column — Gaps#

  • Gaps card — non-interactive; multiple lines separated by <br />; Playwright asserts data-veritas-topic-gap-line count and card copy

Right column — Follow#

  • Follow row (per subscription)
    • Function: appears to be a status pill (● on); no toggle in code; Playwright asserts data-veritas-topic-follow-row state attrs and confirms the polished row has no nested button. Verify whether it should become a button that flips the subscription
    • The internal namespace alternate (VeritasTopicHubWorkspace) does have a real Follow topic toggle backed by the Veritas store

Data & contracts#

  • Reads: getTopicHub() from @/lib/lilith-data/veritas-depth — returns TopicHubData ({ storyCount, sourceCount, openDisputeCount, settled[], open[], readingHistory.{totalCells, readPattern}, primarySources[].{name, description, citedCount, band}, gapsLines[], follows[].{label, on} })
  • Writes: none in this view; the internal-namespace workspace does write via the Veritas store (followTopic, unfollowTopic, updateTopicAlert)
  • Realtime: none in this view; consider whether "follow" should be realtime
  • Caching: server fetch; revalidation per fixture policy; note that readingHistory is per-user
  • Auth/role check: shell middleware (verify); per-user cache key required because of reading history
  • Page metadata: title: "Veritas · topic hub", description "What is settled, what is still moving, what you have read so far, and what gaps remain.", alternates.canonical: /veritas/topic

E2E coverage#

  • apps/oshun/web/e2e/veritas-evidence-trail.spec.ts — step 8 now opens the shipped /veritas/topic route and verifies the masthead counts, settled/open row attrs, 28-cell reading-history grid, primary-source rows, gaps lines, and static follow-status rows against real dev web+BFF infra

Cross-references#

Open questions / known gaps#

  • The "14 of 47 stories · last 30 days" right-side text on Section III is hardcoded in the JSX, not derived from data — confirm intent
  • Follow rows look like toggles but are static in code; cross-reference VeritasTopicHubWorkspace (which has the real toggle) and decide whether this surface should align
  • No dynamic route segment in the path (/veritas/topic); the fixture describes one topic. Confirm the production plan
  • Reading-history cell colours: v < 2 maps to L.accent, which means "unread" (value 0) renders the same colour as the most-read cell; verify the colour ramp polarity
  • LCustomerNav active="library" — consistent with the /veritas hub and the other depth views, which all use Library (no Explore inconsistency)