Releases: chipi/orrery
v0.6.3
Mobile UX + reliability pass. Five user-filed mobile polish issues (#125-#129) plus three v0.6.2 follow-ups land together: missions density matches fleet, science-lens overlap on /fly cleared, space-stations black-canvas race fixed, deterministic e2e replacing retry-passes, release tooling made truly one-command, bundle warning silenced, and the encyclopedia gains two curated companion lists.
Added
- Reading list at
/science/reading-list(issue #128) — 8 curated beginner-to-intermediate space books (Cosmos, Pale Blue Dot, A Brief History of Time, The Right Stuff, Packing for Mars, Rocket Propulsion Elements, Fundamentals of Astrodynamics, How to Read the Solar System) + 5 long-form blog/journalism recommendations (Planetary Society, Casey Handmer, Eric Berger / Ars Technica, NASA Spaceflight Forum, Damn Interesting). Audience badges (beginner/intermediate/advanced), explanatory blurbs. - Watch list at
/science/watch-list(issue #129) — 7 sci-fi films (Contact, Interstellar, 2001, The Martian, Apollo 13, Moon, For All Mankind), 4 documentaries (For All Mankind '89, In the Shadow of the Moon, Cosmos: A Personal Voyage, When We Left Earth), 3 podcasts (Off-Nominal, MECO, Are We There Yet?), 4 YouTube channels (Scott Manley, Everyday Astronaut, Veritasium, PBS Space Time). - Both new pages are anchored as tab cards at the bottom of the
/sciencerail nav. New paraglide keysscience_tab_reading_list+science_tab_watch_listin en-US; other 13 locales fall back to JS title-casing via the existingtabLabelhelper (full localisation deferred to the next wave23 batch). scripts/release-rehearsal.ts— interactive version bump + CHANGELOG scaffold (issue #223). When the script detectspackage.json.versiondoesn't match the argument version, it offers to bump it via a[Y/n]prompt. When the CHANGELOG section is missing, it offers to scaffold a stub with today's date + empty Added/Fixed/Changed shells. Non-TTY contexts (CI, scripted) take the "no" default — the fail-closed contract from v0.6.2 #134 holds..github/workflows/release.ymlauto-publishing GH Release on tag push (was v0.6.2 work but properly exercised for the first time on v0.6.3 — confirmed idempotent on the v0.6.2 retag, marked Latest only for stable semver).
Changed
- Mobile mission tiles → 2-per-row (issue #125) —
/missionscard grid usesrepeat(auto-fill, minmax(150px, 1fr))on ≤600 px so the layout matches/fleetdensity (which the user prefers). Both surfaces carry agency logos so the same minimum-width floor works; very narrow viewports (<340 px) degrade gracefully to 1 column. - Equal-height cards in /missions + /fleet grids (issue #225) — within a row, card heights now stretch to match the tallest. Was sizing to content, so a card with a long name + caveat sat taller than its neighbours and broke the visual rhythm. Two-line fix per file:
.card-li { height: 100% }+.card { height: 100% }. - Science layers panel — collapsed-by-default on mobile, raised z-index (issue #126) — the expanded panel was overlapping
/fly's CAPCOM ticker. Now ships as a strip on ≤600 px (tap to expand), max-height 50vh → 40vh, z-index 32 → 37 so the active interaction renders above the CAPCOM panel rather than under it. /sciencewider page + flexible right rail (issue #226) —.page max-width 1200 → 1440 px, right rail track220px → minmax(220px, 320px). On viewports >1200 px the layout was sitting centered with empty margin on the right, and the right-rail section list couldn't grow into that space. Now wider viewports get more usable area and the section list claims some of it (capped so the prose column stays dominant). Mid-range (768-1024 px) behaviour unchanged.vite.config.tschunk-size warning ceiling 500 → 700 kB (issue #224). Identified the two offenders:three.module.js~513 kB (already auto-chunked by Vite, can't tree-shake further at the SvelteKit layer) and Paraglide'smessages.js~665 kB (per-locale split needs anoutputStructure: 'locale-modules'change in inlang/project — deferred follow-up). Both intentional; raising the ceiling stops the build log from carrying a known-known every release. >700 kB is now the real regression flag.
Fixed
- Space stations 3D black-canvas on cold mount (issue #127).
/iss+/tiangongkicked offstartThree()viaqueueMicrotaskafteronMountsetviewMode='3d', but queueMicrotask fires BEFORE the browser's first layout pass on a cold load — socontainer.clientWidthread0and the renderer was sized 0×0, giving a permanent black screen until something else triggered a resize (the workaround the user reported: navigating away and back). Fix: newstartThreeWhenSized()wrapper subscribes a ResizeObserver to the container and starts THREE on the first non-zero size emit. Synchronous when container is already sized (no regression in the happy path). - /moon + /mars: rotate planet to face the deep-linked site (issue #227). When a mission card's "to the surface" cross-link lands on
/moon?site=Xor/mars?site=X, the panel + halo open but previously the planet stayed in its default orientation — the site itself could be on the far side, invisible until the user manually dragged. NewfaceMoonAtSite()/faceMarsAtSite()callbacks (gated on aface: trueoption toselectSite) compute the longitude angle of the site and setmoonMesh.rotation.y/marsMesh.rotation.yso the site lands on the camera-facing +Z hemisphere. autoSpin pauses so it stays put. Latitude isn't adjusted (camera moves would be a heavier change), but the longitude flip alone fixes the "site on far side" case the user reported. - /fly red-dot vs blue-tube split — root-cause rewrite (issue #228). Five prior attempts (v0.6.1 sprite-tube alignment, v0.6.2 future-tube tip snap, plus three intermediate translations) treated symptoms. A debug log on the v0.6.2 build showed the gap between the TubeGeometry cross-section centroid and the spacecraft sprite peaking at 20.33 scene-units mid-arc (Earth orbit is 80 units, so the gap was visually huge). Root cause:
THREE.TubeGeometrysamples its curve viagetPointAt(arc-length uniform u)even when the sourcePolylineCurve3overridesgetPoint(t)to be a uniform-t lerp. For Kepler ellipses sampled at uniform true anomaly (transferEllipseinmission-arc.ts), adjacent-chord lengths differ by ~10× between perihelion and aphelion — so arc-length-uniform u disagreed with uniform-t parameter by exactly the gap we were chasing. Heliocentric tubes rewritten as one mesh per leg with a manual builder that places cross-sections at exactlypts[i]and aShaderMaterialwhose fragment shader paints bright ifvT<uProgress, dim otherwise — boundary lands at the sprite by mathematical identity (proven numerically against a Kepler-shape unit test, zero gap across 100 sampled t values). Same shader-gradient treatment applied to cislunar phase lines (#228b): each phase line carries per-vertexaT, per-frame uProgress derived frommet_daysvs phase's[start_met_days, end_met_days]window. Net delete: outLineFuture/retLineFuture meshes, snapTubeTip vertex-mutation function + tipMutation cache, PolylineCurve3 class (now unused), v0.6.2 debug console.log. ~140 LOC added, ~210 LOC removed. - 5 flaky mobile e2e tests — deterministic waits replacing retry-passes (issue #222). v0.6.2's release rehearsal had 5 mobile-chromium tests passing on retry:
earth.spec(satellite click) added a 150 mspanel.waitFor({state:'visible'})per click instead of bareisVisible();earth.spec(ISS GALLERY thumbnails) bumped timeout 5 s → 10 s for image-load on slow CI;fly.spec(3D/2D toggle) swapped fragile role+name regex for[data-testid="fly-view-toggle"]+not.toHaveText(initialLabel);_helpers/nav.ts.clickNavLinkwaits for the drawer link to be visible before clicking (coversi18n-pt-BR.spec);mars.spec(FULL MISSION CARD cross-link) droppedwaitForLoadState('networkidle')(mars streams canvas textures continuously, never hits the 500 ms quiet window) + bumped cross-link timeout to 10 s.
v0.6.2
Reliability + release-tooling pass on top of v0.6.1. Tightens the e2e suite (deterministic readiness signals, body-text translation coverage, Linux visual baselines), removes a parallel-agent footgun in validate-data, and codifies the AGENTS.md release-readiness checklist as actual tooling — a one-command pre-tag dry run and an auto-publish GH Release workflow on tag push.
Added
npm run release:rehearsal vX.Y.Z(issue #134) — single command that runs preflight + e2e on bothdesktop-chromiumandmobile-chromiumprojects, extracts the matching## [X.Y.Z]block fromCHANGELOG.md, and creates a DRAFT GH Release. Fail-closed on any step. Codifies the AGENTS.md §"Before tagging or releasing" checklist so the rule is verifiable, not just documented..github/workflows/release.yml— auto-publish GH Release on tag push (issue #135). Fires on anyv*tag push. Extracts the CHANGELOG section, creates the Release with that body, marks Latest only for stable semver (pre-releases likev0.6.2-rc1don't bump the homepage marker). Idempotent — re-runs on a re-tagged commit update the existing Release. Eliminates the "tagged but no Release" failure mode that hit v0.6.0 + v0.6.1.- Mobile Cmd-K affordance in
/science(issue #137) — the encyclopedia search trigger is now reachable on ≤640 px viewports via a Search row in the hamburger drawer. Samearia-labelas the desktop rail Search button, sogetByRole('button', {name: /Search the encyclopedia/i})matches in both viewports. Newnav_searchparaglide key in all 14 locales. scripts/regenerate-visual-baselines-linux.sh+npm run regen-visual-baselines-linux(issue #132). Runs the pinned Playwright Docker image with a host-side.linux-node-modules/overlay so Linux visual baselines are generated bit-identical to CI's ubuntu-latest runner, without touching the host's darwin install. Six new*-linux.pngbaselines committed; thetest.skip(process.platform !== 'darwin')guard from v0.6.1 is dropped.docs/guides/visual-regression-baselines.md— full regeneration workflow + commit conventions + three documented failure modes (esbuild arch collision,/.npmEACCES, non-deterministic renders). Cross-linked fromvisual.spec.ts.tests/e2e/i18n-body-text-desktop.spec.ts(issue #131) — desktop-only smoke covering 6 EU-Latin locales (de/es/fr/it/nl/pt-BR) asserting that a translated body-text token (plan_empty_title) actually renders. Closes the coverage gap the v0.6.1html[lang]-only migration left: a silent Paraglide en-US fallback on a translated screen now fails the smoke instead of passing.
Fixed
/fly3D ↔ 2D hash-invariant flake — deterministic readiness signal (issue #133). Replaces the v0.6.1data-out-vertex-hashstability poll with ADR-056 hooks:window.__flyArcHash()+window.__fly2DArcHash()return the stable 11-vertex hash ornullwhile hydrating;window.__flyMissionId()returns the id of the mission most recently committed to page$state. Reading from Svelte's reactive$stateat call time eliminates the microtask gap between framework flush and DOM attribute write. Targeted run (12 cases): 5 consecutive runs, zero flake. Full suite: 62/62 in 1.3 min.validate-datano longer scans untracked PRD/RFC drafts (issue #136). Scoped the doc-gating-sentence check togit ls-files docs/prd docs/rfc docs/adrso a parallel agent's untracked draft can't refuse your unrelatedgit push. Staged-but-uncommitted files still get gated. Falls back to the filesystem walk ifgitisn't accessible (CI shallow-clone edge case). The stash-and-restore workaround from AGENTS.md §"Pre-push hook quirks" + CLAUDE.md §"Untracked PRD/RFC drafts" is dropped.
Changed
v0.6.1 — e2e stability + footer + /fly 3D fix + PWA auto-update + space-themed docs
A patch landing the day after v0.6.0 to stabilise CI / e2e, add a build-date stamp + README + CHANGELOG links in the footer, fix a long-standing /fly 3D sprite-tube alignment bug, switch the service worker to silent auto-update, and ship a space-themed VitePress docs site.
Added
- Footer build date + README + CHANGELOG links — the footer now reads
Gallery | Credits | Library | License | README | v0.6.1 · 2026-05-16. README is a separate link (back from being merged into the version pill); the version + deploy-date pill links toCHANGELOG.mdon GitHub instead of the README. Build date is injected at build time via Vitedefine(same pattern as__APP_VERSION__), so it doubles as a quick-scan "this is the build live on GH Pages today" signal. - VitePress docs site — space theme + guides folder — full
docs/rewrite with a custom theme (docs/.vitepress/theme/custom.css) matching the production app palette (--orrery-bg #04040c, gold#ffc850, teal#4ecdc4, mars#c1440e); Bebas Neue + Space Mono + Crimson Pro from Google Fonts; static starfield + radial-gradient glow on the home hero (pure CSS, no images). Navigation revised: Home / Guides (user + translator) / Decisions (TA / ADR / RFC / PRD indices) / ↗ Live App. Footer + sidebar + tables + code blocks + custom blocks re-tinted; local-search input + outline-on-right + 640 px mobile breakpoint tuned.npm run docs:buildgreen after fixing 15 dead links surfaced by the guide move.
Changed
- PWA service worker → silent auto-update —
vite.config.tsswitchesregisterType: 'prompt'to'autoUpdate'. New SW bundles install silently on the user's next navigation instead of surfacing a "new version · refresh" toast that asked the user a question they couldn't answer with context. Modern PWA default (Twitter / Slack / Discord behaviour). Trade-off: a user with the app open for hours stays on the old version until they navigate — acceptable for an explorer / docs app. Dropslayout_pwa_new_version+layout_pwa_refreshmessage keys across all 14 locales.
Fixed
/fly3D sprite-tube tip alignment — the red dot lagged on outbound and led on return becausespacecraftPos()and the in-framesnapTubeTip()lerp re-derived the spacecraft's position from the same waypoints through two independent code paths; algebraically identical, visually drifty under sustained playback. RefactoredsnapTubeTipto translate the tip cross-section directly tosc.pos × SCALE_3D(single source of truth), so the tube cone tip and the sprite are guaranteed to coincide every frame, on both arcs.- Mobile e2e regressions (hamburger drawer) — 14 i18n locale-chip smoke specs +
fleet.spec(nav exposes the FLEET link) +smoke.spec(nav bar is visible …) targeted the desktop.centernav strip, which isdisplay: noneon ≤640 px viewports since the v0.6.0 mobile-nav overhaul. Newtests/e2e/_helpers/nav.tsexposesclickNavLink()+localeChip()— viewport-aware nav navigation, and the locale-chip locator is scoped to[data-locale-picker]so it doesn't collide withbutton.chipfilter chips on screens like/fly. The remaining 2 affected suites (fleet.spec+smoke.spec) open the menu inline. /scienceCmd-K spec skipped on mobile — the Search button isdisplay: noneon ≤640 px (desktop affordance, seesrc/routes/science/+layout.svelte); the two Cmd-K specs nowtest.skip()on mobile viewports.- Visual-regression baselines skipped on non-darwin —
visual.spec.tsbaselines are committed as*-darwin.png(maintainer's machine); CI Linux looks for*-linux.pngand reports "missing baseline" on every run. Suite nowtest.skip()s on non-darwin until linux baselines are committed too. Local darwin runs still execute the assertions. /libraryaxe scan timeout in CI — 678 outbound-link rows × every axe rule exceeded the per-test 30 s playwright budget on cold Ubuntu runners (35–43 s in failing runs). Bumped to 90 s for/libraryspecifically; other a11y-pilot routes stay on the default.missions.speccount drift — Apollo 13 shipped as the 37th mission (17th Moon entry) in v0.6.0, but the spec was still asserting 36 / 16. Bumped to 37 / 17 to match.fly-render-validation3D ↔ 2D hash-invariant flake — Mariner 4, Apollo 11, and Apollo 17 occasionally failed on the first run and passed on retry. Bumped thedata-view='2d'wait from 5 s to 10 s; swapped the locator to[data-testid="fly-view-toggle"](more stable than a label regex now that the toggle row has five sibling panel-visibility buttons).- e2e workflow timeout 40 → 60 min — the suite is 23–30 min steady-state, but flaky retries plus the
/libraryaxe scan push the worst case past 40. 60 min gives 2× margin without masking a runaway hang.
Internal
@playwright/test1.59.1 → 1.60.0 — dedupplaywright-coreto a single version so svelte-check no longer sees two distinctPagetypes (was blocking CI typecheck after@axe-core/playwrightlanded and pulledplaywright-core@1.60.0).package-lock.jsonregenerated with--cpu=x64 --os=linux --include=optional— keeps the Linux-only native deps (@emnapi/core,@emnapi/runtime,esbuild@0.28.0,yaml@2.9.0) in the lockfile so GH Actions runners'npm cidoesn't reject the sync.
v0.6.0 — cislunar /fly + /fleet + landing page + Observation/Life-in-space science tabs
The cislunar release. /fly learns Moon-mission geometry — every Apollo-class free-return, Artemis hybrid free-return, Chandrayaan-3 spiral, and Chang'e LOR rendered as its own per-profile trajectory in an Earth-centred view. The /science encyclopedia grows two new tabs (Observation + Life in Space). A new top-level /fleet explorer ships. And the visit starts on a real landing page at / instead of an explore-redirect.
Added — cislunar /fly view (ADR-058)
- Per-mission cislunar trajectory machinery —
buildCislunarTrajectory()parses each Moon mission's newflight.cislunar_profile(parking orbit, TLI ∆v + C3, translunar type, lunar arrival altitude / periselene, return ∆v) and produces a typed phase sequence (parking → tli_coast → lunar_arrival → tei_coast → reentry). 17 Moon missions populated (Apollo 11/13/17 + Artemis 2/3 + Luna 9/17/24 + LRO + Clementine + Chandrayaan 1/3 + Chang'e 4/5/6 + SLIM + Blue Moon Mk1). - Earth-Centred Inertial scene at true Earth-Moon scale (
SCALE_CISLUNAR = 1/10 000) — auto-engages for any mission whosedest === 'MOON'. Phase-coloured trajectory lines, ∆v annotation sprites, real Earth + Moon textures, moon-frame group that tracks lunar drift so lunar-orbit / descent / ascent phases anchor to the Moon as it moves. - Auto-zoom across phases — camera close to Earth during parking / spiral_earth / reentry, pulled back across translunar coast, lerped in toward the Moon during lunar_orbit / lunar_flyby / descent / ascent. Mouse-wheel during a phase wins; next phase transition re-arms the lerp.
- Moon-proximity override — flyby-only profiles (Artemis II, Apollo 13) have no explicit
lunar_flybyphase, so the camera now zooms whenever the spacecraft computes to within 80 000 km of the Moon centre, regardless of phase type. - 8 science layers wired into the cislunar scene at parity with heliocentric — hover, soi, gravity, velocity, centripetal, apsides, coast, conics. SoI rings sized to Earth (924 000 km) + Moon (66 100 km). Coast preview integrates a two-body Earth-gravity prediction forward 33 h.
- Heliocentric auto-zoom (interplanetary missions) — DEPARTURE close-up on Earth → wide cruise framing on the Earth↔destination midpoint → APPROACH close-up on the live destination. Mirror sub-phases for round-trips. Tube
drawRangeround-snaps to whole segments so the sprite stays aligned with the trajectory tip under close-up zoom. - 2D cislunar fallback for the
/fly2D toggle — same per-mission geometry rendered to canvas with Earth at centre and an auto-zoom on lunar phases. - Apollo 13 added as the 37th mission — free-return flyby with aborted LOI.
Added — /science Observation + Life in Space tabs
- Observation tab (S1–S3 / issues #80 #81) — 7 sections covering Adaptive Optics · Black Holes · Coronagraphs · Interferometry · Space Photography · Spectroscopy · Wormholes. New ObservatoryShowcase strip on the space-photography section gallery imagery expanded to 96 observatory images across the fleet gallery pipeline.
- Life in Space tab (S2 / issue #80) — sections covering microgravity, radiation, IVA, EVA, lunar suits, long-duration life support, and crew-health topics. Three new suit-family sections (IVA / EVA / lunar suits) cross-link to
/fleet. /sciencenow spans 85 sections across 10 tabs (was 54 / 8). 71 hand-coded SVG diagrams (was 62) — one per section + 10 tab covers, with the fail-closed integrity gate enforcing the count on every build.- Diagram polish pass — S6 wave repaints 50+ existing diagrams across orbits / transfers / propulsion / mission-phases / porkchop / scales-time to match the latest design-system tokens (flag flips, blueprint accents).
photofield wired into section pages — high-value imagery embedded on 18 sections (NASA / ESA observatory + agency photography), all with full image-provenance coverage.
Added — /fleet explorer + space-suit category
- New top-level
/fleetroute — bill-of-materials view across the fleet (rockets, capsules, landers, rovers, orbiters, observatories) and now space-suits. Selection halos, per-planet pill colors on filters, Mars labels. - Space-suit category — 13 suit-family entries (en-US) with full provenance + i18n wave 2/3 across 12 locales + sr-Cyrl manual overlays for the 3 new suit sections. Wired into
validate-data+ schemas. Krechet placeholder + sokol-m fallbacks.linked_missionsuse real mission IDs.
Added — root / landing page (PRD-013 / UXS-013 / Issue #74)
- Real landing replacing the
/ → /explore307 redirect — hero (ORRERY96 px wordmark + tagline + two CTAs), what-is-this section, why-this-exists section, multi-paragraph guided tour, 11-card grid covering every primary nav destination +/fleet, footer block linking to GitHub / README / License / Credits / Library / Technical Authority. Long-form scrollable single column, mobile-first (375 px → 1-col cards). 49 newlanding_*keys translated to all 14 supported locales. Browser-locale auto-detection on first paint. 10 e2e tests on desktop + mobile.
Added — Dutch locale + i18n coverage
- Dutch (
nl) locale — 14th supported language (issue #72). Full UI bundle (711 keys) at 100% parity with the other 13 locales. Native name Nederlands, short tag NL, flag 🇳🇱. LocalePicker entry added between Italiano and Српски. Translations follow the ESA-NL physics/astronomy glossary; mission and agency proper nouns kept in original. - Dutch entity overlay tree — full coverage across missions / planets / sun / rockets / earth-objects / moon-sites / mars-sites / iss-modules / iss-visitors / tiangong-modules / tiangong-visitors / scenarios (161 files).
/scienceoverlay gap closed for es/de/fr/it — 14 files (history/_intro + 6 history sections + space-stations/_intro + 4 space-stations sections + mission-phases/eva + scales-time/long-duration) translated for the four EU locales that already had partial coverage. Brings es/de/fr/it from 49 → 63/sciencefiles each.iss-visitors/overlays for all 13 non-en-US locales — closes the en-US-only gap for cargo_dragon, crew_dragon, cygnus, htv_x, progress_ms, soyuz_ms, starliner. 7 files × 13 locales = 91 new files.- i18n wave 2/3 for 28 new sections + 13 suit entries across the 12 non-en-US locales.
Added — Mobile + Browser-locale ergonomics (ADR-057)
- Browser-locale URL canonicalisation (Issue #73 Gap 1) — auto-detected locales rewrite the URL to the canonical
?lang=deform viareplaceState, so bookmarks and share-links carry the locale. 10 unit + 5 e2e tests. orrery_localecookie for explicit locale overrides (Issue #73 Gap 2 / ADR-057 Accepted) — single functional cookie (SameSite=Lax, 1-year, no PII), written only on explicit LocalePicker click. Sits between URL andnavigator.languagein the resolution chain. Auto-detect paths never write the cookie.- Mobile UX overhaul — hamburger nav, collapsible
/flyHUD on narrow viewports, compact/sciencesection rail, footer-overlay improvements, per-planet pill colors. - Build version in footer —
Credits | Library | v0.6.0strip; version injected via Vitedefinefrompackage.json. - Nav wordmark polish — bumped to 36 px, vertically centred within the 52 px nav bar.
Added — Documentation + tooling
- Tech BOM generator (closes #92) —
scripts/build-tech-bom.tswritesdocs/TECH-BOM.mdlisting every npm package shipped or used at build time, with version + license. License-audited fail-closed in CI. - PRD-014 + RFC-017 — Surface Hotspots (progressive landing-site exploration).
- ADR-055, ADR-056, ADR-057, ADR-058 —
i18ncookie scope, surface-map design, locale persistence cookie scope, cislunar/flyarchitecture.
Changed
/flyleft rail consolidated — identity / navigation / flight-params / systems / spacecraft-state stack flush in one column. Top-controls bar puts FlightDirectorBanner + ScienceLayersPanel side-by-side instead of stacked vertically.- Solar / Cislunar
/flytoggle de-scoped (ADR-058 amendment) — Moon missions render exclusively in cislunar view; heliocentric Moon-mode no longer toggleable. The PiP context inset was also dropped during smoke-testing. - Mars + maps polish — selection halos on
/mars//moon, label rendering for Mars sites, drop redundant README footer link.
Fixed
- CI e2e timeout bumped 25 → 40 min for the v0.6 suite size.
apollo13.jpgsourced + suit-diagram spec-panel fixes;sokol-mplaceholder.- Tech BOM GitHub-shorthand + LICENSE URL — unblocks the deploy.
- Heliocentric tube/sprite alignment under close-up auto-zoom — round-snap fixes the visible offset.
- Artemis II cislunar zoom — Moon-proximity check now fires for hybrid-free-return profiles that skip the explicit
lunar_flybyphase.
Dependencies
- Bulk Dependabot bump — vitest 2 → 4 · svelte 5.16 → 5.55 + 4 minor.
v0.5.0 — encyclopedia + explorers + LEARN-link stewardship
The encyclopedia + explorers release. Three new primary nav routes (
/iss,/tiangong,/science), a layered "Science Lens" of live physics annotations across every 3D scene, and end-to-end outbound-link provenance discipline.
Closes the v0.5.0 milestone (#6) — Tiangong Explorer + the LEARN-link stewardship rollout — plus the Science encyclopedia (#39) and ISS Explorer (#41) bodies of work that were originally on v0.4.0 but read more cleanly here together.
/science — in-app encyclopedia (PRD-008 / RFC-011)
The headline of v0.5.0. 54 sections across 7 tabs: Orbits · Mission Phases · Planets & Bodies · Spaceflight · Space Stations · History · Space-101 landing.
- 48 hand-coded SVG diagrams (engineering blueprint style — white-on-black with teal accents)
- KaTeX server-rendered formulas — client receives static HTML, no JS math library (ADR-034)
- Right-rail navigation, Cmd-K search overlay, narrative_101 lead-ins on every section
- Cross-screen
?-chips deep-linking from/missions,/fly,/explore,/plan,/earth,/moon,/mars,/iss,/tiangongstraight to the relevant chapter
/science integration with the rest of the app:
SCIENCEtab on every detail panel (MissionPanel, PlanetPanel, SunPanel, SmallBodyPanel)- Science Lens toggle gates a layer of in-scene physics annotations across every 3D scene
- Flight Director banner on
/fly— 5-phase narration (Departure · Trans-X Injection · Cruise · Approach · Arrival), each phase deep-linking to the matching/sciencesection - Why? popovers explain individual numeric labels in context across every panel
- Mission Sandbox layered onto
/planporkchop — pin one cell, click another → ΔDEP / ΔTOF / Δ∆v compare panel
Live physics overlays — Science Layers: Eleven sub-toggleable layers behind the lens — spheres of influence, hover info cards, gravity vectors, velocity vectors, centripetal arrows, apsides + true anomaly, engine-off coast preview, conic-section family panel, microgravity axes, atmosphere shells, tidal-lock indicator, ozone holes.
Closes #39.
ISS Explorer (/iss) — PRD-010 / RFC-013
A full station-explorer route built on a shared station-geometry library:
- 32 modules (every USOS + ROS module + visiting craft) with per-module 3D pickability (raycast-driven panel open) plus hover outlines and emissive selection pulse
- Full module panel: OVERVIEW · GALLERY · TECHNICAL · ANATOMY · SCIENCE · LEARN tabs
- Per-module agency badges in the drawer
- Sun-tracking solar-array animation
- Microgravity axes lens-gated overlay (ZENITH/NADIR · PROGRADE/RETROGRADE · PORT/STARBOARD)
- Orbit-regime banner with WhyPopovers on altitude/inclination/period
- Hand-drawn ANATOMY diagrams for 9 visiting spacecraft (Crew Dragon, Soyuz MS, Cygnus, Dragon, Progress, HTV, Starliner, ATV, Shenzhou)
- 2D blueprint views (top + side projections)
Closes #41.
Tiangong Explorer (/tiangong) — PRD-011 / RFC-014
Mirror of /iss for China's Tiangong station, built on the shared station-geometry library:
- Tianhe core + Wentian + Mengtian lab modules with sun-tracking gallium-arsenide arrays
- Three docked-vehicle slots (Shenzhou, Tianzhou, plus visiting spot)
- Polished low-end fallback (auto-switch to list view on under-20 fps hardware)
- ADR-048/049/050 lock the asset pipeline, module pickability rules, and low-end fallback contract
Closes #50.
LEARN-link stewardship — ADR-051
End-to-end discipline for every outbound link in the app:
- L-A — ADR-051 + RFC-015 + audit doc (the policy)
- L-B — Per-link provenance manifest +
LinkCredit.svelte+LearnLink.svelte+ AJV validation - L-C — Agency-portal LEARN-link enrichment (non-US first; native-language priority)
- L-D — Public
/librarypage (bill-of-links across the entire app); Mission Library renamed to Mission Catalog - L-E — Outbound link-checker chained into
npm run fetchwith freshness gating
Closes #51 epic + #52 / #53 / #54 / #55 / #56.
Test infrastructure — flake-free e2e under headless WebGL
The first run of the full e2e suite after the v0.5.0 UX wave landed exposed 46 stacked failures — the previous 25-min CI window kept being cancelled by superseding pushes before ever reaching them. Categorised + fixed in 3 commits:
- 12 i18n locale tests broke when the LocalePicker added flag emojis (
🇩🇪 DEinstead ofDE) — swapped totoContainText. /missionsfilter pills + timeline collapsed by default; tests now click the strip open and the page auto-expands when filter URL params are present./planporkchop selector collided with a ScienceChip aria-label; pinned tests tocanvas.porkchop.PlanetPanelLEARN tab folded into SCIENCE — tests follow.FlightDirectorBannerinner anchor href; tests follow./iss+/tiangongcanvas-click tests replaced their flaky spiral-search with a deterministicwindow.__pickAt()test hook +canvas.click({position})(bypasses overlays).- All 8×
waitForTimeout(<magic>)replaced with deterministic readiness signals:data-objects-count/data-sites-count/data-view/data-sim-day/data-sc-phaseattributes. /iss+/tiangongperf-fallback (FPS<20 → list mode) skipped undernavigator.webdriver— software-rasterizer WebGL on GH Actions was tripping the gate.
Result: e2e job dropped from 25-min timeout to 16-min clean pass.
Stack notes
No new dependencies. Three.js still r128. SvelteKit + Paraglide-js + ajv + KaTeX (build-time only). Static export via adapter-static — still deploys to GitHub Pages with no backend.
🤖 Notes generated with Claude Code
v0.4.0 — Languages + Mars Surface Map + image credit stewardship
The internationalisation + maps + provenance release. All 12 supported locales now at 100% UI parity, full Mars Surface Map, and end-to-end image provenance discipline.
(/iss, /tiangong, and the /science encyclopedia all landed during this development window but are scoped to v0.5.0 — see the v0.5.0 release for those.)
Closes the v0.4.0 milestone (#5): #30 v0.1.10 audit cleanup, #36/#37/#38 i18n Waves 1/2/3, #40 Mars Surface Map, #44/#45 agency-first imagery, #47/#48 image credit rollout. (#39 Science and #41 ISS Explorer were originally also tagged to this milestone but are reframed under v0.5.0 in the release narrative since they form a coherent encyclopedia + explorer story together with Tiangong.)
Languages — 12 locales now fully covered
UI message bundle (684 keys) translated key-for-key across every supported locale: Spanish · French · German · Italian · Portuguese-BR · Mandarin · Japanese · Korean · Hindi · Arabic (RTL) · Russian · Serbian-Cyrillic. Locale picker shows national flags.
Closes #36 (Wave 1: fr/de/pt-BR/it) · #37 (Wave 2 CJK: zh-CN/ja/ko) · #38 (Wave 3: hi/ar-RTL/ru).
Mars Surface Map (/mars) — PRD-009 / RFC-012
A new primary nav route. Equirectangular 2D Mars map + 3D globe with surface-feature panels for landing sites, orbital probes, and major regions. Atmosphere shell layer (~120 km) lens-gated. Per-site GALLERY · TECHNICAL · LEARN tabs. Rover traverses rendered as cross-linked path overlays. 16 surface sites + 11 orbiters at ship.
Closes #40.
Image credit rollout — ADR-046 / ADR-047
End-to-end provenance discipline for every pixel in the app:
- Agency-first build-time imagery sourcing (NASA = fallback library, not the default)
- Per-image provenance manifest (auto-generated by
scripts/build-image-provenance.ts) - Public
/creditspage with all imagery + text + logo attributions - License allowlist + waivers system, fail-closed in
validate-data - Lightbox attribution on every gallery thumbnail
- Honest mixed-source footers per gallery surface
- Non-NASA agency imagery enriched via partnership-credit queries
Closes #44 (agency-first sourcing) · #45 (non-NASA enrichment) · #47 (Milestones A/B/C) · #48 (Milestone D).
/missions UX cleanup
Filter strip + timeline navigator collapsed by default for a clean grid; auto-expand when any filter URL param is present so deep links show their state. Per-mission flight params + caveat banners (RECONSTRUCTED / SPARSE / UNKNOWN). Ships at 36 missions (4 outer-catalogue additions + Artemis II + free-return concepts).
v0.1.10 audit cleanup (#30 carry-over)
Drift fixes from the v0.1.10 audit folded in here.
Tooling + infrastructure
- 4 new ADRs covering the agency-first imagery decision (ADR-046), per-image provenance manifest (ADR-047), and the diagram authoring + KaTeX patterns groundwork (ADR-034, ADR-035) used heavily in v0.5.0.
validate-datafails closed on unknown licenses and missing image provenance.npm run preflightmirrors CI step-for-step locally; pre-push hook self-installs.
Stack notes
No new dependencies. Three.js still r128. SvelteKit + Paraglide-js + ajv. Static export via adapter-static — still deploys to GitHub Pages with no backend.
🤖 Notes generated with Claude Code
v0.3.0 — heliocentric /fly + cislunar reframe + 4 new missions
Heliocentric
/fly, cislunar reframe, and four new missions (32 total).
Major /fly trajectory overhaul
transferEllipse(): true two-point Keplerian arc with the Sun at one focus, pinning both endpoints to live planet positions. Spacecraft start coincides with Earth atdep_day, rendezvous coincides with destination atarr_day— no more drift between rocket and planet.PolylineCurve3forTubeGeometry— the 3D past-tube progression now matches the spacecraft sprite exactly (no centripetal-spline drift).- Svelte 5
$effectdep-tracking fix: refs not yet defined on first run prevented state from being tracked, so marker + arc-rebuild effects silently skipped subsequent mission swaps.
Apollo-class round-trip + Moon-mode reframe
- Moon-mode is now heliocentric like Mars (Sun + Earth orbit visible, Moon orbiting Earth at exaggerated 0.15 AU).
- Camera follows live Earth, zoomed tight (50u). Sim-speed pills swap to
[0.1, 0.5, 1, 3]×for short cislunar missions. - Round-trip timeline math:
arr_day = dep + 2 * transit_daysfor crewed / sample-return so the scrubber maps cleanly across the full mission.
Four new missions (32 total)
- Artemis II (NASA, FLOWN April 2026 — the project's inspiration)
- Inspiration Mars (Tito 2013 free-return concept)
- Starship Mars Crew (SpaceX crewed Mars round-trip concept)
- Blue Moon MK1 (Blue Origin cargo lunar lander)
Other
/explorevisibility layers (PLANETS · DWARFS · COMETS · INTERSTELLAR)/exploreSIZES → REFERENCES with visual planet-size diorama/earthsatellites in real-inclination orbital planes/planrocket roster expansion + launch sites + photos/missionstrajectory preview moves into the card + FLIGHT tab/flyCAPCOM panel: taller, sticky header, events scroll independently- Non-US mission galleries via curated Wikimedia top-up
- PWA install nag suppressed, capability kept
- Dev-server:
vite fs.allow ['static']+ scenario probe gating
🤖 Notes back-filled with Claude Code
v0.2.0 — /fly trajectory math: isolation + per-mission validation
Phase 4 of the multi-phase polish plan — the largest, and the final phase before v0.3.0 outer-planet work begins. Minor version bump because this touches the trajectory core; semver-honest.
ADR-030 locks the pure-function boundary + tolerance philosophy.
Pure-function extraction
Every numeric formula previously inline in `src/routes/fly/+page.svelte` is now in `src/lib/fly-physics.ts`:
| Function | Role |
|---|---|
| `heliocentricSpeed(rAu, aTransferAu)` | Vis-viva on transfer ellipse |
| `distanceBetween(a, b)` | Heliocentric Euclidean distance |
| `auToKm` / `auToMkm` | Unit conversion |
| `signalDelayMin(distAu)` | One-way light-minute delay |
| `missionElapsedDays(simDay, depDay, arrDay, totalMissionDays)` | MET from arc progress |
| `dvRemaining(total, used)` | ∆v ledger (clamped) |
| `moonPositionAtMet(metDays)` | Moon orbital position |
| `moonOutboundArc(moonPos, steps)` | Earth → Moon Bezier |
| `moonReturnArc(moonPos, steps)` | Moon → Earth Bezier |
Constants centralised in `src/lib/fly-physics-constants.ts` — single source of truth for MU_SUN, AU_TO_KM, AU_PER_YR_TO_KMS, C_LIGHT_KM_S, MOON_ORBITAL_PERIOD_DAYS, etc.
The `+page.svelte` file is now a consumer; the math is testable independently of Three.js + Canvas2D rendering.
Per-mission validation harness
`src/lib/fly-physics-validation.test.ts` walks 10 real missions (9 measured-quality + 1 sparse) and asserts peak heliocentric speed along the outbound arc matches `mission.flight.cruise.peak_heliocentric_speed_km_s` within a per-mission tolerance:
| Class | Missions | Peak-speed tolerance |
|---|---|---|
| Energetic (C3 ≳ 10) | Curiosity, Perseverance, InSight, MAVEN, Mars Express, Hope, Tianwen-1 | 1.5 km/s |
| Direct entry / flyby | Mariner 4, Mars Pathfinder | 2.0 km/s |
| Sparse (reconstructed) | Mars 3 | 3.0 km/s |
Tolerances reflect the Hohmann-approximation envelope of the model — /fly visualises a Hohmann transfer ellipse, not a real Lambert solution. The harness validates the visualization is close to reality, not exact. Honest about the limitation in code rather than papering over it.
Coverage gap fixes
- 4 new V∞ shaping tests for `outboundArc()` — previously zero coverage despite the v0.1.10 code path. Tests cover baseline passthrough, energetic bend-out, Hohmann-matched, extreme-V∞ clamp.
- 6 new Moon Bezier tests — previously zero coverage. Tests cover arc start/end positions, period wrap, control-point flip between outbound + return.
New test helper
`src/lib/test-helpers/expect-close.ts` provides:
```ts
expectCloseTo(computed, golden, tolerance, description); // throws on miss
expectInRange(computed, min, max, description);
```
Reusable for any future per-mission validation (e.g. when v0.3.0 outer-planet missions land).
State
- 251 unit tests (was 214, +37)
- 82 e2e tests (1 skipped, all others green)
- No new dependencies
- No breaking API changes for /fly consumers
- /fly `+page.svelte` net diff: ~30 lines smaller after extraction
Multi-phase plan complete
v0.1.10 → v0.1.11 → v0.1.12 → v0.1.13 → v0.2.0 shipped sequentially per the planning file. All four phases done in one session.
| Phase | Tag | Headline |
|---|---|---|
| 1 | v0.1.11 | Hover thumbnails + Earth year scrubber |
| 2 | v0.1.12 | PWA service worker + high-contrast toggle |
| 3 | v0.1.13 | Flight-data re-use across /fly + /missions |
| 4 | v0.2.0 | /fly math isolation + 10-mission validation |
Next track
v0.3.0 — outer-planet implementation. RFC-008 + ADR-028 already deliberated. 4 new destinations (Uranus, Neptune, Pluto, Ceres) + 4 new missions (Voyager 2, Galileo, New Horizons, Dawn). Catalogue grows from 28 → 32. Tracked under issue #27.
v0.1.13 — Flight-data re-use across /fly + /missions
Phase 3 of the multi-phase polish plan. Surfaces the rich flight data populated by issue #31 (mission data normalisation) in two new user-visible places.
/fly CAPCOM ticker reads structural events
New pure helper `src/lib/mission-event-merge.ts#mergeFlightEvents()` fuses editorial overlay events (curated narrative copy) with `mission.flight.events[]` (structural timeline from issue #31). Editorial events take precedence at MET collisions within a 0.05-day tolerance.
Before this release, sparse missions (Mars 3, Luna 9 / 17 / 24, Apollo 17) had bare CAPCOM tickers — their editorial overlays were sparse but their structural flight data was rich. Now those missions show TCMs + EDL + anomalies in the ticker even when their editorial copy is missing.
7 new unit tests cover: empty inputs, editorial-passthrough, structural-fallback, MET collision dedup, sort order, anomaly typing, label-map override (i18n hook).
NEXT EVENT row in /fly FLIGHT PARAMS HUD
A new row in the FLIGHT PARAMS aside shows the next upcoming event from the merged ticker:
```
NEXT T+7d · TRAJECTORY CORRECTION
```
Reads from the same merged events array so the ticker + HUD stay in sync. Displays "—" once all events have passed.
/missions card quality badge
Inline next to the existing status badge in each card head:
| Quality | Colour |
|---|---|
| MEASURED | teal |
| SPARSE | gold |
| RECONSTRUCTED | orange |
| UNKNOWN | grey |
Helps users understand the mission corpus at a glance — the 21 measured + 6 sparse + 1 unknown breakdown from issue #31 is now visible everywhere mission cards appear.
State
- 214 unit tests (was 207, +7)
- 82 e2e tests (was 77, +5)
- 1 skipped, all others green
- No new dependencies; all work uses existing infrastructure
Next phase
Phase 4 (v0.2.0) — `/fly` calculation isolation + per-mission validation harness. Largest of the four phases: extract ~25 numeric formulas inline in `+page.svelte` into pure modules, add a validation harness comparing computed values against the golden truth in each mission's `flight.*` records for 5–10 missions.
v0.1.12 — PWA + a11y polish
Phase 2 of the multi-phase polish plan. Closes Theme C — issue #18.
Service worker
Orrery now installs as a true PWA on Android + iOS, with offline-first behaviour after first visit.
- Plugin: `@vite-pwa/sveltekit` (Workbox-generated SW). New devDependency.
- Cache strategies: shell + textures + fonts + logos + images precached (cache-first, immutable); mission JSON + i18n overlays stale-while-revalidate (instant from cache, refreshed in background); gallery + flight-data manifests network-first with 3 s timeout. Porkchop grids excluded from precache (browser HTTP cache only — they're large + per-route).
- Update toast: when a new SW is waiting, a small REFRESH toast appears at the bottom of the screen so users explicitly opt into the new version.
- Install-prompt deferral: the `beforeinstallprompt` event is held until the user has visited 3+ unique screens. Visit counter held in runtime memory only (per CLAUDE.md no-localStorage rule). Avoids the intrusive first-paint install banner.
- iOS: Safari ignores `beforeinstallprompt`. iOS users get manual install via the share sheet — same as before, now with proper SW-backed offline behaviour once installed.
ADR-029 documents the decision.
Manual high-contrast toggle
A small Aa button in the right side of the nav bar toggles `data-high-contrast="true"` on ``. The CSS hooks (already shipped in v0.1.10's `tokens.css`) bump dim/faint text alphas, thicken borders, and raise panel/HUD/nav backgrounds toward fully-opaque.
- Both OS preference (`prefers-contrast: more`) and the manual override are recognised — manual takes precedence.
- New `src/lib/high-contrast.ts` mirrors the existing `reduced-motion.ts` pattern: `matchMedia` + `MutationObserver` subscription, SSR-safe, cleanup function returned.
- 44 px touch target, `aria-pressed`, localised `aria-label`.
- Active state lit in teal; default outlined in faint grey.
Tests
4 new e2e tests in `tests/e2e/pwa.spec.ts`:
- `manifest.webmanifest` served + parseable
- `/sw.js` served + Workbox-generated
- `<link rel="manifest">` present in HTML head
- High-contrast toggle button flips `data-high-contrast` on ``
State: 207 unit + 77 e2e (1 skipped), all green.
Theme C status
- ✅ C1 — service worker (this release)
- ✅ C2 — manual high-contrast toggle (this release)
- ⏳ C3 — Lighthouse CI gate (deferred; needs separate `lhci` config)
Next phase
Phase 3 (v0.1.13) is re-using the new flight data on /fly + /missions: CAPCOM ticker reads `mission.flight.events[]`, NEXT EVENT row in the FLIGHT PARAMS HUD, data-quality badge on /missions cards.