Context. surface customer · domain onboarding-public · route /legal/privacy · auth anon · source apps/oshun/web/src/app/legal/privacy/page.tsx
Last walked. 2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data (2xx), console/page-errors, expected content, screenshot verified; manual screen-reader and real telemetry-delivery checks pending. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content re-verified 2026-06-03 against current source; 2026-06-28 public-legal-pages Playwright pass covered anonymous routing, exact section inventory, legal nav matrix/active states, TOC/back-to-top/print shell controls, 390 px no-overflow, 44 px touch targets, offline-after-load stability, no telemetry hooks, and axe. Manual AT pass and real telemetry delivery remain pending.
Purpose#
OSHUN's full privacy policy, rendered in the Lilith manuscript register (cream
paper, terracotta accent, Cormorant Garamond serif). Fifteen titled sections
delivered as a single <article> inside LegalPageShell, which adds cross-page
legal nav, scroll-spy TOC, back-to-top, and a print stylesheet.
Entry points#
- Footer link "Privacy Policy" on
/welcomeand/landing - Sidebar nav inside any other
/legal/*page (the shell renders a fixedLEGAL_NAVlist) - Direct URL / bookmark — yes (anon)
- Linked from in-product privacy controls (e.g., profile settings, cookie consent banner)
- Email footer "Privacy" link
Layout regions#
page.tsx declares a SECTIONS array of 15 anchor ids and wraps the article
body in <LegalPageShell sections={SECTIONS}>. All visual styling is injected
by the shell as a <style> block; the article itself uses only semantic HTML
and data-legal-date / data-legal-table-wrap / data-legal-mono hooks.
- Header (shell): minimal
[data-legal-header]with brand link and a back link (rendered byLegalPageShell) - Skip link:
[data-legal-skip-link]jumps to main content - Sticky sidebar:
[data-legal-sidebar]— cross-legal nav (7 entries) plus in-page TOC button list ("On this page") - Main article:
[data-legal-content] article— h1 "Privacy Policy",data-legal-date"Last updated: May 26, 2026", then 15<section>anchors (introduction,information-we-collect,how-we-use,data-sharing,data-retention,data-security,your-rights,regional-notices,dsar-companion,data-residency,moderation-reporting,international-transfers,childrens-privacy,changes,contact) - Floating control:
[data-legal-back-to-top]— 44px circular button fixed bottom-right, fades in after 300px scroll
States#
- Loading — N/A; static server component, no async data fetched
- Default (populated) — all 15 sections render with serif body type
- TOC click / active section — clicking a TOC button scrolls to that
section and marks it
data-active="true"; passive scroll-spy remains driven byIntersectionObserverinLegalPageShell - Back-to-top visible — appears once
window.scrollY > 300 - Narrow viewport (≤768px) — sidebar collapses to a static block above
the article; in-page TOC section is hidden via
display: none !important - Print —
@media printremoves sidebar, header, back-to-top; switches background to white, text to black, expands the article to full width, and prints anchor hrefs after each link - Error (unrecoverable) — N/A; no data dependencies, only segment
error.tsxwould catch - Offline after load — once the route is loaded, the URL, heading, section inventory, and active legal nav state remain stable when the browser context goes offline
- Gated — N/A; anonymous-readable
Interactions#
Cross-legal sidebar nav ([data-legal-nav-link])#
Seven <Link> entries hardcoded in LegalPageShell's LEGAL_NAV: Privacy
Policy, Terms of Service, Cookie Policy, Accessibility, CCPA, DPA, Lilith Terms.
- Each sidebar nav link (link)
- Function: navigates to the sibling legal page
- Active styling:
data-activeattribute whenpathname === href(italicised, terracotta, left-border accent) - Hover styling: paper background on
onMouseEnter - Keyboard: Tab focuses; Enter activates
- Touch target: ≥ 44 px height and width on the 390 px mobile viewport
On-this-page TOC ([data-legal-toc-link])#
Fifteen <button> elements, one per SECTIONS entry.
- Each TOC button (button)
- Function: calls
scrollToSection(id)→el.scrollIntoView({ behavior: 'smooth', block: 'start' }) - Active styling: terracotta + left border when clicked immediately or when
IntersectionObserverflags that section as intersecting - Hover: paper background
- Keyboard: Tab into list, Enter / Space activates
- Hidden when viewport ≤ 768px
- Function: calls
Back-to-top button ([data-legal-back-to-top])#
- "↑" back to top (button,
aria-label="Back to top")- Function:
window.scrollTo({ top: 0, behavior: 'smooth' }) - Visibility: opacity 0 with
pointer-events: noneuntilwindow.scrollY > 300, then opacity 1 - Position:
fixed; bottom: 28; right: 28; 44×44
- Function:
Body links#
Anchors inside the article use the shell's serif/terracotta-underline style. The
visible link here is an inline mailto-style reference to privacy@oshun.app
rendered as plain text, not an anchor element — verify during walk whether it
should be clickable.
Data & contracts#
- Reads: none — static text only
- Writes: none
- Realtime: none
- Caching: server-rendered static; loaded-route offline stability is
asserted. Cold offline navigation / per-legal SW path caching is not asserted,
and current
sw.jsdoes not path-cache/legal/*. - Auth/role check: anonymous; each legal page is listed individually in
middleware
PUBLIC_PATHS(e.g./legal/privacy,/legal/terms,/legal/cookies,/legal/accessibility,/legal/ccpa,/legal/dpa) — there is no/legalprefix entry - Metadata:
title: 'Privacy Policy',description: 'How OSHUN collects, uses, and protects your personal information.'
Cross-references#
- Shell:
shell/01-app-shell.md - Sibling legal pages (all share
LegalPageShell):legal-terms.mdlegal-cookies.mdlegal-accessibility.mdlegal-ccpa.mdlegal-dpa.mdlegal-lilith.md(usesLegalPage, notLegalPageShell— different chrome)legal-lilith-privacy.md(also usesLegalPage)
- Component sources:
apps/oshun/web/src/components/legal/LegalPageShell.tsxapps/oshun/web/src/design-system/lilith(tokens:LV,SERIF_L,MONO_L)
E2E coverage#
- Backed by
apps/oshun/web/e2e/public-legal-pages.spec.ts— covers the public legal route matrix with anonymous access, exact section inventory, legal nav inventory/active state, TOC click and active state, back-to-top behavior, print chrome suppression, 390 px no-overflow, 44 px nav/back-to-top targets, offline-after-load stability, no telemetry hooks, and axe.
Open questions / known gaps#
- Cold offline navigation / per-legal SW cache is not asserted and current
sw.jsdoes not path-cache/legal/*; PWA shell cache coverage remains in the offline-first / PWA walkthroughs. - Body contains a plain-text email reference (
privacy@oshun.app) — confirm intent: should it be a mailto link? -
LAST_UPDATEDstring is hard-coded ('May 26, 2026') — confirm the update process when policy changes - No "I accept" or consent affordance on this page; consent UX lives on the cookie banner shell — verify scope expectation
- No localisation; en-US only — confirm whether i18n is in scope