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
/veritasevidence 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/topicsand/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-confidencewhenLabelcode 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 isdata.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 citedcode,VrBandchip 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)
- Left — Settled / Open / What you've read:
States#
- Loading —
getTopicHub()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 items —
data.open[]empty; the section still renders header but maps over an empty array (verify graceful behaviour) - No reading history —
data.readingHistory.readPattern[]of all zeros yields all-accent cells (verify the colour ramp; value 0 maps toL.accentper the ternary) - No follows —
data.follows[]empty; verify rendering - Error (recoverable) —
getTopicHub()rejects → nearesterror.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-rowattributes 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 -
whenLabelcode — text only; e.g., "decision expected by …"
Section III — Reading heatmap#
- Cell (per day) — non-interactive; Playwright asserts 28
data-veritas-topic-reading-cellnodes 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/sourcefor that source VrBandchip — non-interactive
- Function: not a link in code; Playwright asserts the static row contract via
Right column — Gaps#
- Gaps card — non-interactive; multiple lines separated by
<br />; Playwright assertsdata-veritas-topic-gap-linecount and card copy
Right column — Follow#
- Follow row (per subscription)
- Function: appears to be a status pill (
● on); no toggle in code; Playwright assertsdata-veritas-topic-follow-rowstate 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 realFollow topictoggle backed by the Veritas store
- Function: appears to be a status pill (
Data & contracts#
- Reads:
getTopicHub()from@/lib/lilith-data/veritas-depth— returnsTopicHubData({ 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
readingHistoryis 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/topicroute 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#
- Component:
apps/oshun/web/src/components/lilith/veritas.tsx—VeritasTopic(lines 1353–1597) - Data:
apps/oshun/web/src/lib/lilith-data/veritas-depth.ts(getTopicHub) - Internal namespace counterpart:
- Sibling routes:
- Feature spec:
V1/features.md
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 < 2maps toL.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/veritashub and the other depth views, which all use Library (no Explore inconsistency)