Context. surface customer · domain veritas · route /veritas/story · auth signed-in · source apps/oshun/web/src/app/veritas/story/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-30 addendum: trajectory chart now derives its path, point count, current marker, retraction marker, and labels from StoryTimelineData.entries and is covered by veritas-evidence-trail.spec.ts.
Purpose#
How a story changed week by week. A single thread that preserves the entire
update + correction history: events, updates, contested entries, and retractions
are all shown — retractions are struck through, never erased. Beside the
timeline, a small "picture moved" chart and change notes summarise the
trajectory. Thin server component awaits getStoryTimeline() and renders
VeritasTimeline.
Entry points#
- Sub-nav "Story updates" tab on
/veritas— verify the tab links here - Linked from
/veritas/mobile— the bottom "Story · over time" primary button (currently unwired) implies this route as the target - Linked from
/veritas/retraction— the letter copy says "The piece carries a struck-through line and a correction note. If you re-read it…" - Linked from
/veritas/topic— settled/open rows could open the canonical story - Direct URL / bookmark — yes (auth required)
Layout regions#
page.tsx awaits getStoryTimeline() → <VeritasTimeline>. The component
mounts LWebShell with LCustomerNav active="library".
- Header: Lilith customer nav (Library tab active)
- Masthead (
VrMast):Veritas · the story | <data.windowLabel> | <data.meta or "N updates · M retractions">, display title "The story, over time.", italic lede ("…retractions are struck through, never erased. What was once read remains readable.") - Two-column grid (1fr / 280px):
- Left — Timeline rail (relative, paddingLeft: 26):
- Vertical 2px rule on the left
- One entry per
data.entries[i]:- 12×12 dot positioned absolute on the rule, filled accent when
entry.tag === 'today'else hollow, bordered with the entry's kind colour (retracted=VR_LOW, contested=VR_MED, event=accent, else=ink) - Header line:
LCodedate +LEyebrowkind + optionalLEyebrowaccent tag ('today') - Title (22px italic serif), struck through when
entry.struck - Body (15px serif), muted+struck when
entry.struck - Optional retraction notice card with medium-confidence border: eyebrow "retraction notice" + italic correction note
- 12×12 dot positioned absolute on the rule, filled accent when
- Right — Trajectory column (280px):
VrHead:— · The picture, moved · What changed in how readers saw this.- Inline SVG chart (280×240 viewBox, decorative —
aria-hidden="true",data-veritas-story-trajectory):- X-axis rule at y=120
- "CONFIDENCE · PEAK FIGURE" mono label top-left
- Accent line path derived from
data.entries[]in chronological order; each mapped point carriesdata-veritas-story-trajectory-point=<date>,data-veritas-story-trajectory-kind, confidence, and current-marker attributes - Labelled points come from entry data: the first-reading retraction
label, later retraction label, and current
today · <confidence>label
LRule- "What changed about the story" eyebrow + italic list of
data.changeNotes[] LRule- "If a claim you read becomes false" accent eyebrow + italic copy linking
to "the retraction board ›" (text only; not a
Link)
- Left — Timeline rail (relative, paddingLeft: 26):
States#
- Loading —
getStoryTimeline()awaited server-side; no client skeleton - Populated — fixture renders multiple entries with kinds (event, update, contested, retracted) and at least one struck entry with a correction note
- Today entry —
entry.tag === 'today'makes the dot solid accent and surfaces a "today" eyebrow (verify visually) - Retraction notice on a struck entry — only shown when
entry.struck && entry.correctionNote - No change notes —
data.changeNotes[]empty; the block maps unconditionally - Error (recoverable) —
getStoryTimeline()rejects → nearesterror.tsx - Offline — cached HTML; nothing client-side
- Standalone PWA — verify the 1fr/280px grid collapses on narrow screens
- Empty — N/A; the route always loads one story timeline
- Gated — none coded
Interactions#
Timeline rail (left)#
- Timeline entry — non-interactive container; verify whether each entry should be clickable to the canonical story snapshot at that point
-
LCodedate — text only; not a link -
LEyebrowkind / tag — text only - Title — italic serif, struck when
entry.struck; non-link - Body — serif copy, struck when
entry.struck; non-link - Retraction notice card — eyebrow + italic correction note; not a link;
verify whether it should jump to
/veritas/retractionfor that claim
Trajectory column (right)#
- SVG chart — decorative (
aria-hidden="true") but data-derived:data.entries[]maps to the line path and point metadata, with the current and retracted labels asserted by Playwright - Change-notes list — non-interactive
- "See the retraction board ›" — text only in code (not a
<Link>or<a>); confirm where the link belongs (likely/veritas/retraction)
Data & contracts#
- Reads:
getStoryTimeline()from@/lib/lilith-data/veritas-depth— returnsStoryTimelineData({ windowLabel, meta, entries[].{date, kind, tag?, title, body, struck?, correctionNote?, confidence?, trajectoryLabel?}, changeNotes[] }) - Writes: none
- Realtime: none in this view (a real "story over time" likely subscribes to new updates; verify the V1 plan)
- Caching: server fetch; revalidation per fixture policy
- Auth/role check: shell middleware (verify); the chart's current point
reflects
entry.tag === 'today'andentry.confidence - Page metadata:
title: "Veritas · the story over time", description "How a story changed week by week. Retractions are struck through, never erased.",alternates.canonical: /veritas/story
Cross-references#
- Component:
apps/oshun/web/src/components/lilith/veritas.tsx—VeritasTimeline(lines 849–1072) - Data:
apps/oshun/web/src/lib/lilith-data/veritas-depth.ts(getStoryTimeline) - Internal namespace counterparts:
domains-veritas-story.md— fixed story iddomains-veritas-story-id.md— dynamic[id]route
- Sibling routes:
- Feature spec:
V1/features.md
Open questions / known gaps#
- The trajectory SVG chart's data is derived from
data.entries[]; fixed 2026-06-30 with E2E coverage for the line path, current marker, retracted marker, confidence attributes, and visible labels - "See the retraction board ›" is text only — not a hyperlink in code
- No dynamic route segment in the path; the fixture models a single story.
The
/domains/veritas/story/[id]route does take anid— confirm the production plan - Timeline entries are not clickable; if each should link to the canonical story snapshot at that date, file as a wiring gap
-
LCustomerNav active="library"— consistent with the/veritashub and the other depth views, which all use Library (no Explore inconsistency)