---
path: /domains/nyx/star-chart
surface: customer
domain: nyx
auth: signed-in
source: apps/oshun/web/src/app/domains/nyx/star-chart/page.tsx
status: walked
last_walked:
  '2026-05-29 automated runtime walk (Playwright) — hydration fix verified: 0
  page/console errors, render+data OK; live SR/touch/offline/telemetry pending
  manual AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md'
---

# Nyx · Star Chart

## Purpose

A full-screen interactive horizon planisphere — rendered as a circular sky map
with stars, constellation lines, and planets at the user's default observer
location. Magnitude limit, time offset, and layer toggles let the user explore
what's currently above the local horizon. Wired via
`apps/oshun/web/src/app/domains/nyx/star-chart/page.tsx` ('use client') which
mounts `<NyxStarChart onClose={() => router.back()} />`.

## Entry points

- **Direct URL / bookmark** — yes (signed-in)
- **In-app navigation** — verify whether the Nyx domain hub or
  `EventCalendarOverlay` link routes here (cross-route: focused sky chart links
  from `NyxEventDetailWorkspace` use `event.skyChartHref`)
- **Browser back affordance** — the page mounts `onClose={router.back()}` so the
  in-component back button leaves this surface

## Layout regions

`NyxStarChart` (`apps/oshun/web/src/components/domains/nyx/NyxStarChart.tsx`)
renders a top-to-bottom flow inside one container.

- **Header**: back button (`aria-label="Go back"`), Lucide `Star` icon,
  `<h1> Star Chart</h1>`, right-aligned location chip (`DEFAULT_OBSERVER.name`
  from `nyx-simulation-data`)
- **Controls strip**: magnitude limit range slider (`Mag limit: <value>`), time
  offset range slider (`Time: Now` / `+Nh`), four toggle buttons
  (Constellations, Planets, Grid, Labels)
- **Chart container**: SVG planisphere with radial `skyGrad` background, horizon
  circle, optional altitude grid (30°/60°), azimuth radials, and generated stars
  / constellation lines / planets
- **Selected-body popup**: appears when a star or planet is tapped — shows RA /
  Dec, Magnitude, Spectral Class (stars) or Magnitude / Distance / Elongation /
  Illumination (planets), with close button

## States

- [ ] **Loading** — no async data; client component hydrates with
      `generateStars` / `generateConstellations` / `generatePlanets`
      synchronously
- [ ] **Populated (default)** — magnitude limit 6.0, time offset 0,
      Constellations + Planets + Grid + Labels all on
- [ ] **Faint-stars hidden** — slider at 0.5 limits visible stars to the
      brightest
- [ ] **Time-shifted** — slider at +12h / −12h re-projects the sky for that
      offset
- [ ] **Below horizon** — bodies with altitude ≤ 0 are filtered out; confirm
      chart is not empty for `DEFAULT_OBSERVER`
- [ ] **Star selected** — tapping a star surfaces the popup; tapping the SVG
      background clears it (`onClick={() => setSelected(null)}`)
- [ ] **Planet selected** — same with planet-specific fields
- [ ] **Error (recoverable)** — N/A — pure client computation; verify no throw
      paths in `equatorialToHorizontal` / `computeGMST`
- [ ] **Offline** — view is static client code with no fetch; renders offline
      once cached by SW
- [ ] **Reduced motion** — no animations declared; cursor is `crosshair` on the
      SVG

## Interactions

### Header

- [ ] **"Go back" button** (icon button, `ArrowLeft`)
  - Function: calls `onClose` → `router.back()`
  - Keyboard: Enter activates; focusable
  - Screen reader: announces "Go back"

### Controls strip

- [ ] **Magnitude limit slider** (range input, min 0.5 max 6 step 0.5)
  - Function: filters stars by `magnitude <= maxMagnitude`
  - Screen reader: `aria-label="Magnitude limit, currently <value>"`
- [ ] **Time offset slider** (range input, min −12 max 12 step 0.5)
  - Function: shifts `computeGMST` reference time; chart re-projects
  - Screen reader: `aria-label="Time offset, <now|±Nh hours>"`
- [ ] **Constellations toggle** (button, `Layers` icon)
  - Function: toggles `showConstellations`; constellation lines on/off
- [ ] **Planets toggle** (button, `Eye`/`EyeOff` icon)
  - Function: toggles `showPlanets`; planet markers on/off
- [ ] **Grid toggle** (button)
  - Function: toggles `showGrid`; altitude rings + azimuth radials on/off
- [ ] **Labels toggle** (button)
  - Function: toggles `showLabels`; star names / planet names on/off

### Chart

- [ ] **SVG sky** (`cursor: 'crosshair'`)
  - Function: tap empty area clears `selected` body
  - Touch target: full chart area
- [ ] **Star marker tap** — sets `selected` to the star; popup appears with its
      data
- [ ] **Planet marker tap** — sets `selected` to the planet; popup appears

### Selected-body popup

- [ ] **Close button** (icon button at top of popup)
  - Function: clears `selected`
- [ ] **RA / Dec row** — formatted via `formatRA` / `formatDec`
- [ ] **Magnitude row**
- [ ] **Spectral Class row** (stars only) — color-coded via `SPECTRAL_COLORS`
- [ ] **Distance row** (stars: parsec; planets: au)
- [ ] **Temperature row** (stars only)
- [ ] **Constellation row** (stars only)
- [ ] **Elongation row** (planets only)
- [ ] **Illumination row** (planets only)

## Data & contracts

- **Reads**: none from network — `generateStars`, `generateConstellations`,
  `generatePlanets`, `DEFAULT_OBSERVER` from
  `apps/oshun/web/src/lib/nyx/nyx-simulation-data`; types from
  `apps/oshun/web/src/lib/nyx/nyx-types` (`StarData`, `PlanetData`)
- **Writes**: none
- **Realtime**: none
- **Caching**: static client bundle; SW caches the JS and renders offline
- **Auth/role check**: middleware enforces signed-in for `/domains/*`

## Cross-references

- Domain hub: [`nyx.md`](./nyx.md)
- Sibling Nyx tools: [`domains-nyx-telescope.md`](./domains-nyx-telescope.md),
  [`domains-nyx-coordinates.md`](./domains-nyx-coordinates.md),
  [`domains-nyx-time-travel.md`](./domains-nyx-time-travel.md)
- Component source: `apps/oshun/web/src/components/domains/nyx/NyxStarChart.tsx`
- Simulation data source: `apps/oshun/web/src/lib/nyx/nyx-simulation-data.ts`

## Open questions / known gaps

- [x] **Runtime walk (2026-05-29) — defect FIXED & verified:** React 418
      hydration mismatch resolved (time-derived values now deferred to a
      post-mount effect via `useClientNow`/`useIsMounted` from
      `@/hooks/useClientTime`; for `/coordinates` the missed `RiseSetCalculator`
      site was gated). Re-walked against a fresh build: 0 page errors, 0 console
      errors, HTTP 200. React error #418; visit https://react.dev/errors/418?a.
      React error 418 is a hydration mismatch — server-rendered HTML differs
      from the client (this view renders time/position-dependent content, e.g.
      `new Date()`/`toLocale*`, without `suppressHydrationWarning`). Fix:
      compute time-dependent values in an effect or gate with
      `suppressHydrationWarning`. Status kept `drafted` until fixed.
- [ ] Confirm whether the magnitude/time sliders expose discrete step keyboard
      arrows correctly (default browser range behavior)
- [ ] Document where `DEFAULT_OBSERVER` is overridden for per-user lat/lng —
      page never passes a custom observer prop
- [ ] Confirm whether the chart supports pinch-zoom on touch or only the
      decorative `crosshair` cursor
- [ ] Verify whether the `event.skyChartHref` from event detail pages targets
      `/domains/nyx/star-chart` with query params, and how those params are
      consumed (the component takes no props beyond `onClose`)
