---
path: /veritas/story
surface: customer
domain: veritas
auth: signed-in
source: apps/oshun/web/src/app/veritas/story/page.tsx
status: walked
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.'
---

# Veritas · the story over time

## 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

- [ ] **Loading** — `getStoryTimeline()` awaited server-side; no client skeleton
- [x] **Populated** — fixture renders multiple entries with kinds (event,
      update, contested, retracted) and at least one struck entry with a
      correction note
- [x] **Today entry** — `entry.tag === 'today'` makes the dot solid accent and
      surfaces a "today" eyebrow (verify visually)
- [x] **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 → nearest
      `error.tsx`
- [ ] **Offline** — cached HTML; nothing client-side
- [ ] **Standalone PWA** — verify the 1fr/280px grid collapses on narrow screens
- [x] **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)

- [x] **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

- 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`](./domains-veritas-story.md) — fixed story id
  - [`domains-veritas-story-id.md`](./domains-veritas-story-id.md) — dynamic
    `[id]` route
- Sibling routes:
  - [`veritas.md`](./veritas.md), [`veritas-claim.md`](./veritas-claim.md),
    [`veritas-counterclaims.md`](./veritas-counterclaims.md),
    [`veritas-retraction.md`](./veritas-retraction.md),
    [`veritas-topic.md`](./veritas-topic.md)
- Feature spec: [`V1/features.md`](../../../V1/features.md#veritas)

## Open questions / known gaps

- [x] 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)
