V1 Web PWA · Surface walkthrough

Veritas · the story over time

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

walked
8sections4 minread

On this page

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: LCode date + LEyebrow kind + optional LEyebrow accent 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
    • 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 carries data-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)

States#

  • LoadinggetStoryTimeline() 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 entryentry.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 notesdata.changeNotes[] empty; the block maps unconditionally
  • Error (recoverable)getStoryTimeline() rejects → nearest error.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
  • LCode date — text only; not a link
  • LEyebrow kind / 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/retraction for 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 — returns StoryTimelineData ({ 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' and entry.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#

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 an id — 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 /veritas hub and the other depth views, which all use Library (no Explore inconsistency)