---
path: /legal/terms
surface: customer
domain: onboarding-public
auth: anon
source: apps/oshun/web/src/app/legal/terms/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; manual
  screen-reader and real telemetry-delivery checks pending. Evidence:
  WALKTHROUGH/results/runtime-sweep-2026-05-29.md; 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.'
---

# Legal · Terms of Service

## Purpose

OSHUN's Terms of Service. Thirteen titled sections rendered through the shared
`LegalPageShell` (cream paper, terracotta accent, Cormorant Garamond serif) with
sticky cross-legal nav and in-page TOC.

## Entry points

- Footer link "Terms of Service" on `/welcome` and `/landing`
- Sidebar nav inside any other `/legal/*` page
- Linked from sign-up "By creating an account, you agree to…" copy
- Direct URL / bookmark — yes (anon)

## Layout regions

`page.tsx` defines `SECTIONS` (13 entries) and renders the article inside
`<LegalPageShell sections={SECTIONS}>`. Section ids: `acceptance`,
`description`, `account-registration`, `acceptable-use`,
`intellectual-property`, `user-content`, `subscription`, `disclaimer`,
`limitation`, `termination`, `governing-law`, `changes`, `contact`.

- **Header (shell)**: minimal brand + back link
- **Skip link**: jumps to main
- **Sticky sidebar**: cross-legal nav (7 entries) + in-page TOC
- **Main article**: h1 "Terms of Service", `data-legal-date` "Last updated:
  February 21, 2026", then 13 numbered `<section>` blocks
- **Floating control**: back-to-top button

## States

- [x] **Loading** — N/A; static server component
- [x] **Default** — all sections render
- [x] **TOC click / active section** — clicking a TOC button scrolls to that
      section and marks it `data-active="true"`; passive scroll-spy remains
      `IntersectionObserver`-driven
- [x] **Back-to-top visible** — the shared 44 px control appears after scrolling
      and returns the user near the document start
- [x] **Narrow viewport (≤ 768px)** — sidebar collapses; TOC hidden
- [x] **Print** — shell `@media print` rules hide sidebar/header/back-to-top and
      append anchor hrefs after each link
- [x] **Offline after load** — once loaded, the URL, heading, section inventory,
      and active legal nav state remain stable when the browser context goes
      offline
- [x] **Gated** — N/A; anonymous-readable

## Interactions

### Cross-legal sidebar nav

- [x] **Each `LEGAL_NAV` link** (link) — navigates to sibling legal page; active
      when `pathname === href`; hover applies paper background; mobile target is
      at least 44 px square

### On-this-page TOC (13 buttons)

- [x] **Each TOC button** (button)
  - Function: `scrollIntoView` for that section id
  - Active styling: terracotta + 2px left border after click and via
    `IntersectionObserver`
  - Hidden on ≤ 768px viewport

### Back-to-top

- [x] **"↑"** (button, `aria-label="Back to top"`)
  - Function: smooth-scroll to top
  - Visible only when `scrollY > 300`

### Body content

The Description-of-Service section enumerates the six domain pillars (Tara,
Veritas, Nyx, Arete, Nisaba, Metis) but as plain `<strong>` text, not links.
Verify whether these should deep-link into `/welcome/domains#*`.

## Data & contracts

- **Reads**: none — static text
- **Writes**: none
- **Caching**: SSR static; loaded-route offline stability is asserted. Cold
  offline navigation / per-legal SW path caching is not asserted, and current
  `sw.js` does not path-cache `/legal/*`.
- **Auth**: anon (in middleware `PUBLIC_PREFIXES`)
- **Metadata**: `title: 'Terms of Service'`

## Cross-references

- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Sibling legal pages: [`legal-privacy.md`](./legal-privacy.md),
  [`legal-cookies.md`](./legal-cookies.md),
  [`legal-accessibility.md`](./legal-accessibility.md),
  [`legal-ccpa.md`](./legal-ccpa.md), [`legal-dpa.md`](./legal-dpa.md),
  [`legal-lilith.md`](./legal-lilith.md),
  [`legal-lilith-privacy.md`](./legal-lilith-privacy.md)
- Component sources:
  - `apps/oshun/web/src/components/legal/LegalPageShell.tsx`

## E2E coverage

- Backed by
  [`apps/oshun/web/e2e/public-legal-pages.spec.ts`](../../../apps/oshun/web/e2e/public-legal-pages.spec.ts)
  — covers anonymous access, exact 13-section inventory, legal nav
  inventory/active state, shared TOC/back-to-top/print shell behavior, 390 px
  no-overflow, 44 px touch 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.js` does not path-cache `/legal/*`; PWA shell cache coverage remains
      in the offline-first / PWA walkthroughs.
- [ ] Domain names listed in `description` section are not hyperlinked — confirm
      intent
- [ ] No "I accept" tracking on this page; confirm acceptance is captured at
      sign-up rather than via inline checkbox
- [ ] `LAST_UPDATED` is hard-coded — document the change-control flow
- [ ] Hard-coded en-US copy; no localisation hook
