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
privatecode 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
- Floating
- 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
- Read once · don't keep active button posts to
- Left — Letter card (accent border, paper background):
States#
- Loading —
getRetraction()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 oncewrite 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_foundfor the same reader and the private letter does not re-appear on reload - Error (recoverable) —
getRetraction()rejects → nearesterror.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/storyis 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 - Gated —
robots: { 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-throughonly) - Floating
privatechip — non-interactive
Before/after card (right)#
- "read on
" block — struck-through copy fromdata.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 viap.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 (
VeritasRetractionActionsbutton)- 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, returnsstate: "acknowledged", and the next personal-letter read for that reader returns 404. - UI states:
data-veritas-retraction-acknowledgement-stateexposesidle/saving/acknowledged/error; success rendersdata-veritas-retraction-acknowledgement-status="acknowledged". - Telemetry: no analytics event yet; the BFF acknowledgement response is the durable post-condition.
- Function: POSTs to the same-origin web API proxy
- 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 inveritas-retraction-cascade.spec.ts. - Telemetry: verify event fires (
veritas_retraction_reopened?)
- Function: links to
Data & contracts#
- Reads:
getRetraction()from@/lib/lilith-data/veritas-depthcalls the authenticated BFF route/v1/veritas/retractions/personal-letterand returnsRetractionData({ recipientName, letter.{dateRead, oldValue, newValue, reason}, before, after, promise[] }) ornullfor 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.tsx—VeritasRetraction(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/veritashub and the other depth views, which all use Library (no Explore inconsistency)