V1 Web PWA · Surface walkthrough

Nisaba · the reading desk

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

walked
9sections5 minread

On this page

Context. surface customer · domain nisaba · route /nisaba · auth signed-in · source apps/oshun/web/src/app/nisaba/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

Purpose#

The Nisaba domain hub. A reading desk for the long texts: passage with sections rail, marginalia, cross-references, and links to the deeper Nisaba sub-surfaces (scholar, lexicon, graph, notebook).

Entry points#

  • Home domain card grid — see ../02-home-discovery/home.md
  • Domain switcher/switcher (see ../02-home-discovery/switcher.md)
  • Domain back-stack from sibling Nisaba routesLSubNav active="desk" keeps the reading desk anchored across /nisaba/*
  • Library + Explore — sky-and-scripture collection and curated routes link here
  • Direct URL / bookmark — yes (auth required via shell middleware)
  • Server fetchgetNisaba() runs on the server before render

Layout regions#

page.tsx is a thin server component: await getNisaba()<NisabaRoom data={data} />.

Inside NisabaRoom (rooms.tsx, line 1114), a Lilith room composition. The desk passage is a Pali / Buddhist text on the five aggregates (anattā / not-self) — the Greek (Marcus Aurelius) text belongs to the compare and scholar views, not the desk:

  • Web shell topLCustomerNav active="library" plus an LSubNav with desk | scholar | lexicon | graph | notebook tabs (desk active)
  • Header / above the fold — passage title (italic muted), citation eyebrow, language buttons (data.passage.languages — Pali / English / Both per activeLanguage)
  • Sections + Marginalia rail (left, 220 px)data.sections.map rows with section number and title (the here section uses ink color and accent number), then an LRule and a Marginalia block (data.marginalia.map label/count rows)
  • Main reading column (centre) — passage body (data.passageBody.map) with a drop-cap first paragraph and an inline "Margin · me" five-aggregates note; below it an LRule and the prev/next section nav + passage actions (see Interactions)
  • Cross-references / concept-graph rail (right, 280 px)Cross-references eyebrow over an LCard with the concept-graph SVG (data.graphNodes) and a Read alongside list of related passages (data.related)

Automation anchors#

This file covers the compact /nisaba reading desk. The search-result destination is the live domain reader at /domains/nisaba, covered by ../../journeys/nisaba-scholarly-read.md through data-nisaba-surface, data-nisaba-workspace, and data-nisaba-reading-* anchors.

  • Root: data-nisaba-desk-page with title/citation/active-language and language/section/marginalia/paragraph/graph-node/related counts
  • Header: data-nisaba-desk-header, data-nisaba-desk-eyebrow, data-nisaba-desk-heading, data-nisaba-desk-citation-text, and per data-nisaba-desk-language={language} button with aria-pressed and active-state attributes
  • Sections + marginalia: data-nisaba-desk-section-rail with per data-nisaba-desk-section={number} row, section title/current attributes, and data-nisaba-desk-marginalia with per-row label/value cells
  • Reading: data-nisaba-desk-reading, per data-nisaba-desk-paragraph={index} paragraph, inline margin note data-nisaba-desk-inline-margin-note, and footer action wrappers
  • Cross-reference rail: data-nisaba-desk-crossrefs, data-nisaba-desk-graph, per graph-node label/color attributes, and data-nisaba-desk-related with per-row code/title cells

States#

  • LoadedgetNisaba() resolves before render; no client loading branch in this view
  • Active language: Pali / English / Bothdata.passage.activeLanguage seeds the selected language button; clicking a language swaps the reading body through passageBodies
  • Active section — exactly one section.here === true
  • Cross-domain bridges — verify any links to Tara / Veritas / Metis that the room data surfaces
  • Reduced motion — composes static Lilith primitives; no animations observed
  • Standalone PWA / Offline — depends on shell + server cache; no Nisaba-specific branch

Interactions#

Top sub-nav#

  • The desk (tab) — active; route /nisaba
  • Scholar mode (tab) — routes to /nisaba/scholar (verify wiring in LSubNav)
  • Lexicon (tab) — routes to /nisaba/lexicon
  • Concept graph (tab) — routes to /nisaba/graph
  • Notebook (tab) — routes to /nisaba/notebook

Header language buttons#

  • Language buttonPali, English, and Both, side-by-side are real buttons when the live desk provides all three bodies; clicking updates the active-language attribute and reading body. The honest unavailable fallback remains English-only.

Sections rail#

  • Section row — navigates to that section anchor; current-section row styled with accent number + ink title

Reading column#

  • Footnote / cross-reference link — opens the corresponding lexicon / graph / scholar entry
  • Highlighted token — opens lexicon for that word (verify)
  • ‹ I. The setting (LBtn kind="ghost" href="/nisaba#section-I") — jumps to the previous section anchor
  • III. On feeling › (LBtn kind="primary" href="/nisaba#section-III") — jumps to the next section anchor
  • NisabaPassageActions (rooms.tsx ~1311-1315 → components/lilith/NisabaPassageActions.tsx) — a 'use client' row:
    • Save passage / ✓ Saved — toggles the passage in the local web library (toggleOshunWebLibraryItem, domain: 'nisaba')
    • Read aloud / Pause / Resume — speaks the passage via window.speechSynthesis; degrades to a "Read-aloud not supported" eyebrow when the API is absent
    • TrustSafetyFlagButton — flags the passage (targetKind="content")

Data & contracts#

  • Reads: getNisaba() from @/lib/lilith-data/nisaba — fetches the live BFF route GET /v1/nisaba/room via bffGet (nisaba.ts ~14-19) and returns NisabaData (passage, sections, languages, activeLanguage, marginalia, graphNodes, related). On an unreachable feed it returns an honest nisabaUnavailable() room (no embedded fixture).
  • Writes: None from the server view; NisabaPassageActions (client) writes to the local web library and to the trust/safety flag flow.
  • Realtime: None.
  • Caching: server fetch; SW caches the rendered HTML for offline reload
  • Auth/role check: shell middleware
  • Route metadata: title "Nisaba · the reading desk", canonical /nisaba

Cross-references#

Open questions / known gaps#

  • NisabaData is served live from GET /v1/nisaba/room; confirm the BFF contract stays in sync with the NisabaData shape
  • Confirm whether language and section controls are interactive in V1 — language buttons swap Pali / English / Both bodies on the live desk; section rows are links to /nisaba#section-*
  • Document the cross-domain bridges Nisaba carries (Tara ritual moments, Veritas grounded evidence, Metis study)
  • Confirm shell sub-nav routing — LSubNav items carry explicit hrefs for desk, scholar, lexicon, graph, and notebook