V1 Web PWA · Conventions

Conventions

Conventions and reading guide for the V1 Web PWA walkthrough.

7sections4 minread3tables

On this page

How to read, write, and update the files in this folder.

Per-view file template#

Every route gets a file. Every file follows this template exactly. Sections may be empty (write _None._) but must be present, so a reader knows what they're looking at.

markdown
---
path: /exact/route/pattern
surface: customer | studio | operator | workspace
domain: <the controlled domain slug used by the matching routes.csv row>
auth: anon | signed-in | signed-in + role:<role> | signed-in + tenant:<tenant>
source: apps/oshun/web/src/app/<path>/page.tsx
status: stub | drafted | walked | stale
last_walked: 'YYYY-MM-DD by <walker/method>, against commit <sha>'
---

# <Human-readable view name>

## Purpose

One paragraph (≤ 3 sentences). What is this view for, and who uses it. Avoid
restating what's already in `V1/features.md` — link to the relevant anchor
instead.

## Entry points

How a user arrives at this view.

- Linked from `<route>` via `<link element / button label>`
- Deep link from `<email / push / SMS / share target>`
- Search result from `/search` for query `<example>`
- Direct URL (bookmarkable / shareable / role-gated / hidden)
- Shell nav (home / explore / activity / library / profile / domain switcher)

## Layout regions

The named visual regions a user can identify, top to bottom (or left-to-right on
wide breakpoints). Use the actual region names from the page component.

- **Header**: <variant — shell header / domain header / minimal>
- **Hero / above-the-fold**: <what's here>
- **Main**: <primary content panel>
- **Aside / secondary panel**: <if any>
- **Footer / sticky bottom**: <if any>

## States

Every observable visual state of the view. Produce it in local development when
the required system is available; otherwise mark it N/A with a reason or leave
it unchecked with the exact external prerequisite. Never invent reachability.

- [ ] **Loading** — skeleton or spinner matches final layout dimensions (no
      CLS); skeleton respects theme tokens; loading attribute announced to
      screen readers
- [ ] **Empty** — no data; on-brand copy; CTA points to a productive next
      action; not just a "nothing here" placeholder
- [ ] **Populated (short)** — 1–5 items; expected layout
- [ ] **Populated (long)** — 50+ items; pagination/virtualization/infinite
      scroll works; scroll position restored on back-navigation
- [ ] **Error (recoverable)** — failed query; retry button works; original query
      parameters preserved
- [ ] **Error (unrecoverable)** — 500/403; support contact link; navigation away
      still works
- [ ] **Offline** — network unreachable; SW serves cached read where sensible;
      writes are queued or disabled with explanation; offline banner visible
- [ ] **Gated** — auth/role/feature-flag denies; user sees why and how to
      unlock; no information leak
- [ ] **Standalone PWA** — view renders cleanly when launched from home-screen
      icon; URL bar absent; safe-area insets respected

If a state cannot occur for this view, write
`- [x] **<state>** — N/A because <reason>` so the omission is auditable.

## Interactions

One bullet per discrete interactive element. Drill into nested elements (e.g., a
menu's items, a modal's buttons).

Each bullet uses this micro-template:

```
- [ ] **<Element label>** (<element type: button / input / link / tab / etc.>)
  - Function: <what it does>
  - Keyboard: <key activates; tab order position>
  - Screen reader: announces "<text>"
  - Touch target: ≥ 44×44 CSS px on coarse pointers
  - Mobile (≤ 640 px): <any layout/behavior delta>
  - Disabled when: <condition> → tooltip/aria-disabled text "<text>"
  - Offline behavior: <queue / disable / no-op>
  - Telemetry: <event name fired>
```

If a sub-element exists (e.g., menu items, dialog content), nest its bullets one
level deeper.

## Data & contracts

What this view reads, writes, and subscribes to.

- **Reads**: `<BFF route or lib call>` — returns `<contract type>`
- **Writes**: `<BFF route>` — accepts `<contract type>`
- **Realtime**: `<channel/topic if any>` — `<events handled>`
- **Caching**: SSR / SWR / ISR / static / SW-cached (which cache key)
- **Auth/role check**: `<middleware.ts rule or BFF policy>`

Link to the contract in `libs/contracts/` or `libs/openapi/`.

## Cross-references

- Feature spec: [`V1/features.md#<anchor>`](../V1/features.md#anchor)
- Architecture: [`V1/ARCHITECTURE.md#<anchor>`](../V1/ARCHITECTURE.md#anchor)
- Journeys that pass through this view: `journeys/<file>.md`
- Sibling routes: `<list>`
- Component sources: `apps/oshun/web/src/<dir>/<file>.tsx`

## Open questions / known gaps

- [ ] <items the walker noted but couldn't resolve in one pass>

Status legend#

Status Meaning
stub File exists with header; content not written.
drafted Content written from code reading; not yet verified against a running app.
walked Direct runtime evidence (manual or automated) verifies every reachable check; the method and date are recorded.
stale The file is walked but the code under source has changed since last_walked.

Status is the truth in matrix/routes.csv. The status header inside each file mirrors the CSV. When you walk a view, update both.

Cross-reference rules#

  • A bare §N or §N.M resolves to V1/TODOS.md (the execution backlog).
  • features§"<anchor>" resolves to V1/features.md by heading anchor.
  • arch§"<anchor>" resolves to V1/ARCHITECTURE.md by heading anchor.
  • Within the walkthrough, link with relative Markdown paths (for example, [PWA behavior](./shell/03-pwa-behavior.md)).

E2E test coverage (specs ↔ journeys)#

Journeys are executable targets. Automation-backed journeys link one or more Playwright specs under apps/oshun/web/e2e/; journeys without meaningful automation are labelled as manual in the Docs Center registry. Where a spec exists, the link is bidirectional and mechanically checkable so a reviewer can jump from a flow to the test that guards it and back, and drift between the two is visible.

1. Spec → journey (header comment). Every spec that covers a journey opens with a fenced header block naming the journey doc and the steps it exercises:

ts
/**
 * E2E · Journey: WALKTHROUGH/journeys/<journey-name>.md
 * Covers: steps 1–6 (proxy boundary 307/404 → /aaa-upgrade page → AaaGate embed leak).
 * Coverage matrix: WALKTHROUGH/journeys/coverage.md
 *
 * A spec may cover one journey, part of a journey, or steps shared by several
 * journeys — list each journey it backs on its own `Journey:` line.
 */

The Journey: line uses the repo-relative path (greppable, unambiguous). A spec that is purely per-view (not tied to a journey) omits the block — only journey-backing specs carry it.

2. Journey → spec (## E2E coverage section). Every journey doc carries an ## E2E coverage section, placed immediately after Failure modes and before Per-view files touched:

markdown
## E2E coverage

- [`apps/oshun/web/e2e/<file>.spec.ts`](../../apps/oshun/web/e2e/<file>.spec.ts)
  — steps 1–6; drives the proxy boundary, the disclosure page, and the embed
  gate.
- **Coverage depth**: deep | partial | shallow | none — see
  [`coverage.md`](./coverage.md).
- **Uncovered**: list any step/failure-mode no spec exercises yet, with the
  reason (blocked on a missing route, requires a testnet, etc.). `_None._` when
  whole.

3. Master matrix. journeys/coverage.md is the single index: every journey → its primary spec(s) → coverage depth → status. It is the source of truth for "is this flow tested, and how well". Update it in the same change that adds or deepens a spec.

Coverage-depth rubric (used in the matrix and the per-journey section):

Depth Meaning
deep Drives the journey's interactions (click/type/nav), asserts state transitions + post-conditions, and exercises ≥1 failure mode.
partial Exercises some steps but skips key interactions, failure modes, or post-conditions.
shallow Smoke only — asserts a heading/URL renders; does not drive the journey's interactions.
none No spec meaningfully covers the journey.

A shallow spec is the test-equivalent of a stub: it would pass against an almost empty page. Treat shallow like stub in the route matrix — it is not done.

Check authoring rules#

  • One element, one bullet. A "save and continue" button is one bullet, not two — but if the bullet's behavior depends on which mode the form is in, the bullet says so.
  • Verbs, not descriptions. - [ ] **Save** opens confirmation — not - [ ] There is a confirmation.
  • Per-state, not per-pixel. Don't itemize visual styling unless the spec calls out a specific token or animation.
  • No hypotheticals. Only list elements that exist in code today. If the spec says something must exist and the code doesn't have it, log it under "Open questions / known gaps".
  • Use the actual label / placeholder / aria-label text so a reader can use Ctrl+F in the running app to find the element.

Walking discipline#

Walking a file means:

  1. Open the per-view file. Read source:.
  2. Open apps/oshun/web/src/app/<route>/page.tsx. Make sure the file exists and the imports are recognizable.
  3. Run the app locally. Navigate to the route as the auth role specified in the header.
  4. For each state in States: produce that state in the running app (use dev tools / mock data / network throttling / role switcher) and tick the box.
  5. For each bullet in Interactions: trigger it; verify function, keyboard, screen reader, touch target, mobile, disabled, offline, and telemetry. Tick when all sub-bullets pass.
  6. Verify Data & contracts by tracing a network request in DevTools.
  7. Update status: walked and last_walked: <date> by <name>, against commit <sha> in the file header. Update the matching CSV row.

If you can't reach a state or trigger an interaction, leave it unchecked and write what blocked you in Open questions / known gaps. Never tick a box you didn't verify.

File naming#

Files within a folder use kebab-case derived from the route, with the leading slash stripped:

Route File
/ customer/02-home-discovery/home.md
/tara customer/03-tara/tara.md
/arete/coaching customer/04-arete/arete-coaching.md
/domains/arete/journal customer/04-arete/domains-arete-journal.md
/studio/design-language studio/design/studio-design-language.md
/operator/review-queues operator/operator-review-queues.md

A file named README.md inside a domain folder is the domain index — it lists every route in that folder with one-line status and a link.