Context. surface customer · domain nyx · route /nyx/sky-almanac · auth signed-in · source apps/oshun/web/src/app/nyx/sky-almanac/page.tsx
Last walked. 2026-06-29 real-infra Playwright chromium — signed-in shell route, live BFF default + ?lat=51.5&lon=0 contracts, section/row DOM anchors, mobile standalone viewport, shared axe fixture. Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md#198-2026-06-29-nyx-sky-almanac-live-bff-coverage
Purpose#
A power-user "almanac for your sky" depth page: solar-noon altitude,
sunrise/sunset bearings, the Moon's phase and distance, chart angles, and the
night's true darkness window. All astronomy is computed upstream
(@oshun/domain-nyx → buildNyxSkyAlmanacPage over the in-repo ephemeris) and
served by the BFF at GET /v1/nyx/sky-almanac; this page only fetches the
view-model server-side and lays out its titled label/value/detail sections.
Entry points#
- Direct URL / bookmark —
/nyx/sky-almanac - Nyx hub depth nav — power-user complement to the consumer Nyx surfaces
(
/nyx,/nyx/tonight,/nyx/observation,/nyx/events) - Shell nav —
LCustomerNav active="explore"(the page mounts the customer nav with the Explore tab active)
Layout regions#
- Header:
LWebShelltop =LCustomerNav active="explore" - Main: centered responsive column (
data-responsive-page,maxWidth: 880, padding32px 56px 80px,data-nyx-sky-almanac-page,data-route="/nyx/sky-almanac") holdingSkyAlmanacSections(root wrapperdata-testid="sky-almanac",data-nyx-sky-almanac,data-available,data-generated-at,data-section-count,data-row-count)<h1>—data.title(defaults to "Sky Almanac")- Subtitle —
<p data-testid="sky-almanac-subtitle" data-nyx-sky-almanac-subtitle>(only when non-empty) - Sections — one
<section aria-label="<title>">per almanac section, each also carriesdata-nyx-sky-almanac-section,data-section-key,data-section-title, anddata-row-count. Each section is an<h2>+ a<dl>whose rows are<div data-testid="sky-almanac-row" data-nyx-sky-almanac-row data-row-key data-row-label data-has-detail>holding<dt>label</dt><dd>value<span data-testid="sky-almanac-detail" data-nyx-sky-almanac-detail> — detail</span></dd>(thedetailspan renders only when a row has a detail; the expected sections are Sun / Moon / Chart angles / Darkness)
- Aside / Footer: None.
States#
- Loading — N/A in-view: the page is an async server component
(
await getNyxSkyAlmanac()); the route-level Suspense/skeleton applies while the server renders, not an in-component spinner - Empty — N/A: a configured backend always returns at least the Sun /
Moon sections; an empty
sections[]simply renders the<h1>+ subtitle with no<dl>rows - Populated —
data.available === true: real-infra Playwright asserts the live BFF contract and browser DOM agree on section count, row count, section titles, row labels, detail presence, generated-at shape, and the visible subtitle; rows with adetailappend " —" ( data-testid="sky-almanac-detail",data-nyx-sky-almanac-detail) - Error / unavailable (backend down) —
data.available === false:shapeSkyAlmanacfalls back toskyAlmanacUnavailable()— title "Sky Almanac", subtitle "Unavailable", zero rows, and arole="status"note (data-testid="sky-almanac-unavailable") "Sky almanac data is unavailable right now — check back shortly." No fabricated astronomical values are shown (a falsifiable claim must never be invented). NoteshapeSkyAlmanacalso returns the unavailable state whenever the BFF payload lacks an arraysectionsfield (!remote || !Array.isArray(...)); covered bynyx-depth.test.tsandSkyAlmanacSections.test.tsx - Offline — server fetch fails → unavailable state (same as backend down); the SW may serve a cached prior render where applicable. Browser forcing this branch requires an isolated BFF-failure harness because the fetch happens inside the Next server component, not in the browser network stack.
- Signed-in shell context — the route is walked through
primeAuthenticatedShellPage/openAuthenticatedShellPath; Playwright asserts it does not land on/welcomeor/loginand thatLCustomerNavmarks Explore active. The domain-stubs BFF endpoint itself currently has no route pre-handler, so do not treat the BFF route as an authorization boundary. - Standalone PWA — mobile standalone launch is mocked with
(display-mode: standalone), the responsive column renders without horizontal overflow, and the section/row structure remains visible
Interactions#
This is a read-only computed-data page — no forms, buttons, or mutations.
- Section navigation — sections are plain landmarks (
aria-label); Playwright asserts the Sun / Moon / Chart angles / Darkness sections are present with their<h2>headings and row lists, so screen-reader users can jump between them via headings and landmarks -
LCustomerNav— shell navigation (Explore active); this page asserts the active Explore link and the shared shell behavior is covered in../../shell/01-app-shell.md
Data & contracts#
- Reads (server):
getNyxSkyAlmanac()→bffGet('/v1/nyx/sky-almanac')→ normalizedNyxSkyAlmanacData({ title, subtitle, generatedAtIso, available, sections: [{ title, rows: [{ label, value, detail? }] }] }); the shaper filters out malformed sections/rows and stampsavailable: falsewhen the BFF is unreachable - Reads (BFF):
GET /v1/nyx/sky-almanaccomputes the default observer;GET /v1/nyx/sky-almanac?lat=51.5&lon=0computes an explicit observer and returns subtitle51.50°, 0.00°E - Writes: None.
- Realtime: None.
- Caching: SSR per request (async server component); no client fetch
- Auth/role check: page is exercised as a signed-in customer shell route; the current domain-stubs BFF route is not guarded by a pre-handler
- Contracts:
NyxSkyAlmanacData/NyxAlmanacSection/NyxAlmanacRowinapps/oshun/web/src/lib/server/nyx-depth.ts; computation in@oshun/domain-nyx(buildNyxSkyAlmanacPage)
E2E coverage#
The Playwright route spec is the authoritative browser proof for this walkthrough: it runs against the dev Next/BFF stack, verifies the live almanac BFF default and coordinate-specific contracts, opens the signed-in customer route, and checks section landmarks, row/detail DOM anchors, generated-at shape, Explore nav context, standalone mobile containment, and no horizontal overflow. Component and server-shaper Vitest coverage back the same read-only and unavailable-state contracts at narrower layers.
apps/oshun/web/e2e/nyx-sky-almanac.spec.ts— live BFF contract checks for default and explicitlat/lonobserver payloads; signed-in route rendering from the live BFF contract; section/row/detail observability; Explore nav active state; mobile standalone launch; and no horizontal overflow.apps/oshun/web/src/app/nyx/sky-almanac/SkyAlmanacSections.test.tsx— component rendering, empty/unavailable states, and row/detail structure.apps/oshun/web/src/lib/server/nyx-depth.test.ts— server-side BFF shaping and unavailable fallback behavior.
Cross-references#
- Sibling Nyx surfaces:
nyx.md,nyx-tonight.md,nyx-observation.md,nyx-events.md,domains-nyx-solar.md,domains-nyx-moon.md,domains-nyx-star-chart.md - Studio downstream:
../../studio/generation/studio-generation-nyx-3d.md - Journeys:
journeys/nyx-tonight-observation.md,journeys/nyx-event-calendar-sync-reminder.md - Component sources:
apps/oshun/web/src/app/nyx/sky-almanac/SkyAlmanacSections.tsx,apps/oshun/web/src/lib/server/nyx-depth.ts
Known downstream boundaries#
- The BFF accepts explicit
lat/lonquery parameters and otherwise uses the default observer, but the page currently calls/v1/nyx/sky-almanacwithout passing a session, profile, or URL observer. Decide whether/nyx/sky-almanacshould let users choose or persist their observing site, and whether the subtitle should explain the default site. - Browser-driving the backend-down state needs an isolated failure harness for server-component BFF fetches; unit coverage proves the fallback today.