V1 Web PWA · Surface walkthrough

Veritas · soft correction

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

walked
8sections6 minread

On this page

Context. surface customer · domain veritas · route /veritas/retraction · auth signed-in · source apps/oshun/web/src/app/veritas/retraction/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; per-user BFF 404/no-leak verified 2026-06-27 in veritas-retraction-cascade.spec.ts; per-user acknowledgement POST and same-reader reload hidden state verified 2026-06-30 in veritas-retraction-cascade.spec.ts

Purpose#

A private letter shown to a single reader on their next visit when a claim they previously read has been retracted or corrected. The promise is "no notification at midnight" — Veritas surfaces the change in-app, never names the reader outside this notice, and never pushes. Thin server component awaits getRetraction() and renders VeritasRetraction only when the authenticated BFF has a pending notice for that reader.

Entry points#

  • On next visit after a relevant retraction — automatic surface; the triggering logic lives elsewhere (verify which BFF/Sophia signal flags a read claim as retracted)
  • Linked from /veritas/story — the timeline says "Veritas notifies you personally for the claims you have read on the days you read them. The notice is private to you. See the retraction board ›" (verify the link target)
  • Direct URL / bookmark — yes (auth required), but robots: { index: false, follow: false } is set so the page is not indexed
  • Email link — V1/features.md says retractions are surfaced privately on next visit; not via push (verify whether email is even part of the channel)

Layout regions#

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

  • Header: Lilith customer nav (Library tab active)
  • Masthead (VrMast): Veritas · the soft correction | for <recipientName> · <date read> · private | a thing you read became false, display title "A small correction, for you.", italic lede
  • Two-column grid (1fr / 1fr):
    • Left — Letter card (accent border, paper background):
      • Floating private code label in the top-right corner
      • <data.recipientName> — italic greeting
      • Multi-paragraph letter (19px serif, lh 1.7) addressing the reader by name, naming the date they read, showing the old (struck-through) value and the new (italic accent) value, naming the reason, and explaining that the corrected piece will surface the correction at the top on re-read
      • Signed — Lilith, on behalf of Veritas
    • Right column — Detail + Promise + Actions:
      • VrHead: I · The change, in detail · Before · after.
      • 2-column grid: "read on " (struck-through, medium-confidence colour) | "now reads" (accent border, paper background)
      • VrHead: II · The promise · What this notice does — and doesn't.
      • Italic list of data.promise[] lines; the rendering strips any *emphasis* markers (regex *([^*]+)* → group)
      • Action row (VeritasRetractionActions, flex, gap 10):
        • Read once · don't keep active button posts to /api/veritas/retractions/personal-letter/acknowledgement, which forwards to the authenticated BFF /v1/veritas/retractions/personal-letter/acknowledgement; success renders an acknowledgement status and the same reader sees 404 on reload
        • Re-open the piece (href="/veritas/story?origin=retraction", flex 1), verified 2026-06-24

States#

  • LoadinggetRetraction() awaited server-side; no client skeleton
  • Populated — authenticated BFF personal-letter payload renders the letter, the before/after card, the promise list, and the action row
  • First view — the reader has not yet seen this letter (no read once write event from a prior visit)
  • Returning view — after "Read once · don't keep" posts the acknowledgement, the BFF personal-letter read returns 404 veritas_retraction_notice_not_found for the same reader and the private letter does not re-appear on reload
  • Error (recoverable)getRetraction() rejects → nearest error.tsx
  • Offline — cached HTML can render the letter; "Read once · don't keep" fails closed with a network-unavailable acknowledgement status when the POST cannot reach the same-origin API route, while "Re-open the piece" is a normal route link whose offline behaviour depends on whether /veritas/story is already cached
  • Standalone PWA — the 1fr/1fr grid collapses on narrow screens via grid behaviour; verify
  • Empty — when the authenticated BFF returns 404 veritas_retraction_notice_not_found, Next renders the route as a 404 instead of falling back to another reader's letter
  • Gatedrobots: { index: false, follow: false } prevents search indexing, and the BFF gates the route per-user; a different user does not see another reader's letter

Interactions#

Letter card (left)#

  • Letter body — non-interactive serif paragraphs
  • Old value strikethrough — visual; verify SR text announces "was X, now Y" (currently relies on text-decoration: line-through only)
  • Floating private chip — non-interactive

Before/after card (right)#

  • "read on " block — struck-through copy from data.before; non-interactive
  • "now reads" block — accent border, copy from data.after; non-interactive

Promise list#

  • Italic line — non-interactive; the render strips *emphasis* markers via p.replace(/\*([^*]+)\*/g, (_m, w) => w). Verify why the markers are stripped (markdown emphasis from fixture? if so, consider <em> rendering instead)

Action row#

  • Read once · don't keep (VeritasRetractionActions button)
    • Function: POSTs to the same-origin web API proxy /api/veritas/retractions/personal-letter/acknowledgement, which forwards the authenticated customer bearer to the BFF acknowledgement route. The BFF stores a per-user acknowledgement, returns state: "acknowledged", and the next personal-letter read for that reader returns 404.
    • UI states: data-veritas-retraction-acknowledgement-state exposes idle / saving / acknowledged / error; success renders data-veritas-retraction-acknowledgement-status="acknowledged".
    • Telemetry: no analytics event yet; the BFF acknowledgement response is the durable post-condition.
  • Re-open the piece (LBtn kind="primary" size="lg")
    • Function: links to /veritas/story?origin=retraction, the corrected story timeline with retraction notices. Verified 2026-06-24 in veritas-retraction-cascade.spec.ts.
    • Telemetry: verify event fires (veritas_retraction_reopened?)

Data & contracts#

  • Reads: getRetraction() from @/lib/lilith-data/veritas-depth calls the authenticated BFF route /v1/veritas/retractions/personal-letter and returns RetractionData ({ recipientName, letter.{dateRead, oldValue, newValue, reason}, before, after, promise[] }) or null for no pending notice
  • Writes: "Read once · don't keep" writes a per-user acknowledgement through the BFF route /v1/veritas/retractions/personal-letter/acknowledgement; the write is idempotent and hides the same reader's pending personal-letter read on reload. "Re-open the piece" is navigation only and does not write.
  • Realtime: none
  • Caching: server fetch per user (must not be CDN-cached since the letter is private and personalised)
  • Auth/role check: shell middleware enforces signed-in; the BFF route requires Veritas scope and scopes getRetraction() to the requesting reader
  • Page metadata: title: "Veritas · soft correction", description "When a claim you read becomes false: a private letter, on your next visit. No notification at midnight.", alternates.canonical: /veritas/retraction, robots: { index: false, follow: false }

Cross-references#

  • Component: apps/oshun/web/src/components/lilith/veritas.tsxVeritasRetraction (lines 1771–1934)
  • Data: apps/oshun/web/src/lib/lilith-data/veritas-depth.ts (getRetraction)
  • Story timeline reference: VeritasTimeline (lines 1053–1066) names the retraction notification model; this view is what it links to
  • Sibling routes:
  • Feature spec: V1/features.md
  • Cross-domain: privacy commitments / "private to you" are part of the Lilith/Psyche tone policy; verify with V1/features.md#lilith-persona-policy

Open questions / known gaps#

  • Durable acknowledgement for "Read once · don't keep" now writes through the BFF and hides the same reader's private letter on reload; verified in veritas-retraction-cascade.spec.ts
  • Confirm the route is per-user gated at the BFF (getRetraction() returns a 404 for users without a pending retraction, not someone else's letter)
  • Verify whether the "Read once · don't keep" semantics means a hard delete vs a soft acknowledge — current implementation is a soft per-user acknowledgement: the pending private letter no longer re-appears, while the underlying cascade record remains available to audit/history systems
  • No keyboard shortcut documented for dismiss (Esc); verify the shell provides one
  • The promise list strips *emphasis* markers — consider whether surfacing emphasis as <em> would convey the intended typographic hierarchy
  • LCustomerNav active="library" — consistent with the /veritas hub and the other depth views, which all use Library (no Explore inconsistency)