Skip to content

iOS postbuild font/scheme cleanup + favicon swap#1

Merged
lukeslp merged 31 commits into
mainfrom
cursor/ios-postbuild-font-scheme-cleanup
Jun 10, 2026
Merged

iOS postbuild font/scheme cleanup + favicon swap#1
lukeslp merged 31 commits into
mainfrom
cursor/ios-postbuild-font-scheme-cleanup

Conversation

@lukeslp

@lukeslp lukeslp commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

The cursor working branch from beast (last commits 2026-05-21 plus the favicon svg-to-png swap committed during the 2026-06-09 audit). Main is ~3 weeks behind this branch. Orrery is live and working, so merge deliberately - nothing here is deployed until merged and pulled.

Ensure Capacitor builds remain self-contained by stripping Google Fonts links from iOS output, and remove the duplicate shared Xcode scheme.
Consolidate Stargazer into one prominent Sky control with aligned keyboard/a11y hints, and reduce the initial deep-space cinematic linger so zoom-in starts sooner.
Position top controls with safe-area insets for iOS; enable Sky mode when
cinematic lands on Earth; show a dismissible once-per-install intro modal
with tour tips and how to turn Sky mode off.
Remove the once-per-install Sky intro sheet; keep a short post-exit hint only.
Hide drawer/planet/NEO HUD while cinematic; gate Inner Planets constellation pulse
and delayed Sky-toggle blink. Sync iOS builds from dist after pull.
Expose window.__ORRERY_BUILD_STAMP__ so Safari Web Inspector can prove which
build is running. Send no-store on dev/preview. Note: pnpm build:ios overwrites dist.
Regenerate the app branding with a constellation plus sun/planet motif and track iOS asset-catalog PNGs so the new visuals ship with the branch.
Add a complete App Store metadata and submission runbook set for the iOS launch so store prep is repeatable and aligned with the current Capacitor workflow.
Update the shipped icon artwork, anchor sidebar controls to safe-area insets on iPad, and decouple Deep Sky from Deep Space while disabling the Milky Way backdrop streak source.
Remove the Milky Way runtime/prefetch path to eliminate iPad startup streak artifacts, tighten iPad panel close behavior, and update launch docs to reflect the current deep-sky/deep-space and QA expectations.
Mobile users could not reach Asterisms or Satellites — the layers filter
hid both keys behind a desktop-only check while the help panel still
advertised them. The mobile "More" accordion duplicated the desktop Theme
picker with a separate 24px-circle control that fell below HIG 44pt and
dropped the radio role.

- Add Asterisms (A) and Satellites (I) to the layer arrays; remove the
  mobile filter at Panels.tsx so the full Layers list is reachable on
  touch. Destructure the missing showAsterisms/showSatellites props.
- Delete the mobile-only "More" accordion. Theme + Settings + NEO + Info
  panels now render on both modes. Unguard the Status accordion so
  mobile keeps the Moon/View/Rate/Solar Wind stats.
- Extract the About overlay into an AboutDialog component with
  role="dialog", aria-modal, aria-labelledby, focus restoration, Escape
  key handling, and a Tab/Shift-Tab focus trap. Close button now meets
  the 44pt minimum.
- Theme picker rows are 44pt on both mobile and desktop, with 14px
  swatches (was 12px) — eliminates the duplicate 24px-circle picker.
- Unify the accent-border alpha for active states to 0.3 (was 0.25
  and 0.35).
- Replace the stale scrollbar selectors in index.css that targeted
  non-existent aria-labels with the actual "Display layers" and
  "Celestial bodies" labels.
Followup to the mobile-parity pass. The Panels module had drifted into
half a dozen ad-hoc style vocabularies for the same things: 21 distinct
blur values, 11 z-indices with no rationale (Sky toggle silently sat
under the drawer), close-X buttons reinvented at every call site,
accordion headers split into two components with different fonts and
chevron behavior, and the right-edge chrome offset by 4px between
neighbouring elements.

Tokens (src/ui/styles.ts):
- Z (canvasOverlay/hud/mobileNav/controls/drawer/drawerTab/dialog/modal/loading)
- BLUR (chip/card/drawer/modal/bokeh)
- ALPHA + ACTIVE_ALPHA + TIMING reserved for future sweeps

Sweeps and consolidations:
- Replace every inline zIndex in Panels with a Z member; only the
  drawer's local close-button stacking (zIndex 1) remains.
- Replace every inline blur(Npx) with BLUR.chip; drawer-tab was 14px
  for no reason — now matches the rest of the chip family.
- Drop the duplicate close-X implementations: InfoPanel close and
  SideDrawer close now use the existing Btn helper.
- MiniAccordion takes an accent prop and transitions its chevron color
  to match AccordionSection.
- Drawer right edge, drawer-tab, Zoom controls, and Sky toggle all
  share safeAreaRight(14); recent iPad-chrome commit had aligned only
  half of these.

Sky toggle (src/ui/Panels.tsx):
- Now sits at Z.controls and shifts left to safeAreaRight(340) when
  the drawer is open on desktop, so it no longer disappears behind
  the panel.

Theme bootstrap (index.html):
- Inline <script> in <head> reads localStorage('orrery-theme') and sets
  --accent / --accent-rgb / --panel-border on :root before React mounts.
  Drops the var(--accent, #00ffcc) and var(--accent-rgb, 0,255,204)
  fallbacks from index.css since the script always sets a value, even
  in private browsing (falls through to Brass).

Stale-text cleanup:
- Remove "Milky Way" from OBSERVATORY_STATS — commit 6719301
  hard-disabled the runtime backdrop but the loading-screen text stuck.
- SCALE_LANDMARKS "Stellar" moved from distance 20000 to 200000 so it
  renders at the top of the ladder instead of 86% up.

iOS bundle:
- B10 (font gate) was a false alarm: scripts/postbuild-ios-html.mjs
  already strips the Google Fonts links from dist/index.html for iOS.
- B8 (zoom -> --panel-font-scale) deferred. The CSS-var migration
  would require sweeping every font-size in the drawer; left for a
  Tier C pass. The zoom hack works in WebKit (iOS) and Chromium;
  Firefox web users get default font size, no crash.

Pre-existing react-hooks lint error at the canHover effect is
unchanged and predates this branch.
Two follow-ups from real-device screenshots:

Mobile preset strip — INNER/SYSTEM/OUTER/OORT — was 30pt tall with
3px radius and tiny 10px text. Sat right next to the 44pt Sky toggle
and looked like a separate design language. Bumped to 44pt height,
6px radius, 12px text with letter-spacing, accent-bg on active using
ACTIVE_ALPHA tokens, blur via BLUR.chip. Now matches Sky's chip
recipe. Top inset 14 (was 32) to free vertical space; Sky toggle
stays at top 84 mobile so they don't collide.

Side drawer (desktop) gained a defensive width clamp:
- minWidth: 320 (was implicit)
- maxWidth: calc(100vw - 28px) — drawer can't exceed viewport
- boxSizing: border-box — paddings don't push width past 320
- borderLeft: none — clears the stale borderLeft from drawerPanel
  base that was a holdover from the flush-right design

One screenshot showed the drawer clipped to ~30px wide on iPad
landscape with a planet selected. Couldn't reproduce in code paths;
the same screenshot's Layers list lacked Asterisms and Satellites,
which my Tier A1 commit added — so the screenshot predated my
commits reaching the simulator. Defensive clamps land regardless.
Three bugs from real-device iPhone 14 Plus landscape screenshot:

1. Side drawer was clipped to ~50px on the right edge — only the first
   2-3 characters of each accordion header visible. Root cause: the
   `zoom: panelFontScale` non-standard CSS property interacts badly
   with `position: fixed; right: 14; width: 320` under WKWebView,
   pushing the drawer hundreds of pixels past the viewport. Replaced
   with `fontSize: ${13 * panelFontScale}px` on the drawer container,
   which sets the inherited default but does not break layout. Inner
   accordion text still uses explicit pixel sizes, so the font-scale
   feature is partially regressed for those values — but the panel
   renders correctly. Full em-unit migration deferred to Tier C.

2. Drawer tab (sliders pull-handle, right edge center) was rendering
   alongside the open drawer, looking like a misplaced second element.
   Now hidden when `panelVisible` — the close-X inside the drawer is
   the close affordance when the drawer is open.

3. Zoom +/- buttons dispatched a single wheel tick (delta 120) which
   barely moved the camera. Now they jump between scale-level camera
   presets: Sun → Inner → System → Outer → Kuiper → Oort → Stellar.
   The current level is computed by finding the preset whose distance
   is logarithmically closest to the current camera distance; +/-
   walks one preset in each direction. Buttons disable at the ends.
Deleted ~1100 lines of side-drawer chrome at the user's request. What
remains is the minimum needed to explore the scene:

- Sky toggle button — switches solar-system view ↔ observatory mode
- +/- zoom controls — jumps between scale-level presets (Sun, Inner,
  System, Outer, Kuiper, Oort, Stellar)
- Info button + AboutDialog — credits, data sources, links
- Click any stellar object — floating bokehCard with the data we have:
  * Sun, planets, moons, dwarfs → distance, period, surface temp,
    gravity, moons (existing planet card)
  * Constellation → name, season, mythology, notable objects
  * Asterism → name, star count, description
  * Deep sky → name, type, magnitude, catalog id
  * Spacecraft → name, status, launch year, distance, speed
  * NEO → live JPL/NASA orbital params (existing)
- Cinematic intro tour on first load

Removed:
- SideDrawer component (~1100 lines, the whole accordion stack)
- Drawer pull-tab
- ScaleIndicator (left-edge Inner/Outer/Kuiper/Oort/Stellar labels)
- Mobile preset strip (INNER/SYSTEM/OUTER/OORT chips)
- AccordionSection, MiniAccordion, SectionHeader, BodyTreeItem,
  TonightsSky helpers
- panelPeek / panelNudge / canHover hover-behavior infrastructure
- panelFontScale + custom-event plumbing (settings UI is gone;
  state still accepted as prop but unused)

Tokens kept (still useful for any new chrome):
- bokehCard — used by every floating info card
- Z (canvasOverlay/hud/controls/dialog/modal/loading) — stripped
  unused drawer/drawerTab/mobileNav layers
- BLUR, ALPHA, ACTIVE_ALPHA, TIMING — semantic constants stand

Layer state machinery (showStars, showConstellations, showAsterisms,
etc.) is still threaded as props so Sky toggle and keyboard
shortcuts work as before. Power users can still toggle individual
layers via keys (S/L/A/K/O/D/N/C/R/I/M). Touch users get an
all-on default until we add a settings affordance back.

The InfoPanel component was rewritten to render as a floating
bokehCard at bottom (mobile) or bottom-right (desktop), with a
section-tag header and a 44pt close-X via the Btn helper.

src/ui/Panels.tsx: 1872 → 879 lines (−993).
src/ui/styles.ts: 138 → 79 lines (−59).
Align Sky mode with compact corner controls, add persistent zoom buttons, and condense the planet info card so mobile and desktop overlays use space more efficiently. Strengthen immersive sky rendering by boosting star/deep-sky emphasis and dimming only when Sky mode is off or a single constellation is selected.
Unify Sky mode entry behavior across keyboard and button interactions, improve HUD accessibility with larger touch targets and safe-area anchoring, and prevent zoom overlay hit-area collisions with the canvas. Tighten keyboard semantics for details and breadcrumb navigation to keep overlay interactions consistent.
- Move Sky toggle to upper-right with inline constellation SVG; keep info (i)
  upper-left; preserve area labels and pulse behavior.
- Replace About dialog text Close with icon-style x and surface linked
  catalog/live data sources.
- Add billboard pick proxies for spacecraft, nearby stars, and Local Group
  galaxies; thread selections through Orrery/Scene/Panels with non-overlapping
  left/right info-card placement.
- Make Sol selectable via enlarged invisible tap target; allow moon picks across
  all parents.
- Replace random R button with dice-icon toggle that runs an ongoing tour over
  presets, planets, moons, constellations, spacecraft, nearby stars, and
  galaxies (~7s cadence). Double-click triggers a single jump; cinematic mode
  auto-stops the tour.
…e next to sky toggle

- Add `src/lib/constellationCentroids.ts` to fetch and cache constellation
  Point features from `public/data/constellations.json`. Average duplicate
  ids (e.g. Serpens Caput + Cauda for `Ser`) via 3D Cartesian midpoint then
  renormalise to avoid Map.set overwriting one half with the other.
- Add `aimAtSphere` prop to `CamCtrl` in `Scene.tsx`. When set, the camera
  lerps to a vantage 8 units inside the celestial sphere along the line
  origin -> target so the user actually faces the rolled constellation.
- Wire `aimAtSphere` through `Orrery.tsx`. Cleared on planet/moon/sun/preset
  selection, Sky-mode `g` entry, escape and pointer-missed in observatory,
  and at cinematic enter/exit. The latter also invalidates
  `lastTourPickRef` so a slow constellation Promise resolving after
  cinematic exit can no longer pull the camera to a stale aim.
- Move the dice button out of the zoom column and into the top-right HUD
  cluster next to the Sky toggle. Single click triggers an immediate
  transition; works mid-flight (no toggle, no interval).
…oint centroids

Address review findings on the random-tour dice button:

- Drop M045 (Pleiades) from FAMOUS_DSO. `prebake-deepsky.ts` filters it out of
  `public/data/deepsky.json` via NGC mag/size cutoffs, so the info card was
  silently failing to resolve on ~10% of DSO rolls.
- Add a `name` field to FAMOUS_DSO and use it in the breadcrumb. The previous
  nav stack showed the catalog id ("M042") instead of the friendly name.
- Enable `showDwarf` when a dwarf-planet index (>=8) is rolled. Without this
  Eris/Pluto/Ceres are filtered out of `visibleBodies` and the camera lands in
  empty space at the right coordinates with no body to focus.
- Move FAMOUS_DSO + CELESTIAL_SPHERE_RADIUS below the import block so module
  imports stay contiguous.
- Add a 'Deep Sky' branch to currentAreaLabel so the scale label matches the
  active selection instead of falling through to 'Full System'.

Also folds in the previously uncommitted constellation-centroid and mobile
zoom-control improvements:

- Centroid math now prefers averaging line-endpoints from
  `constellations.lines.json` over the Point features (which are label
  positions, often offset for legibility). The 3D Cartesian average handles
  the RA 0/360 wrap and multi-segment figures (Serpens) in one pass.
- Mobile zoom column repositioned to safeAreaTop(64) at z-index Z.hud so the
  +/- buttons sit directly under the dice/sky chip cluster.
…Earth landing

The cinematic tour was strobing constellations on/off mid-flight: Deep Space
turned them on, Solar System back off, Inner Planets on with a tour-pulse
shimmer, Earth focus off again, and exit-on-Earth turned them on once more.
Three transitions in five seconds is visual noise.

Now Deep Space, Solar System and Inner Planets all leave constellations off,
and `exitCinematic` (Earth landing) is the single moment they appear, paired
with the existing `constellationRevealTick` fade-in and the Sky-toggle pulse.

Removes the now-dead pulse machinery: `constellationTourPulse` state, the
`innerPlanetsPulseTimerRef` timer ref + cleanup, and
`constellationPulseUntilRef` (the Sky-toggle wait collapses to a fixed 200ms
grace). Drops the `constellationTourPulse` prop wiring through Scene.tsx.
ConstellationLines/Labels still accept the optional prop with a default of
false, so future use is unaffected.
…hlight

Three regressions stacked on top of each other made objects feel un-clickable
and showed iOS tap highlights everywhere. Root causes were independent.

- src/scene/Stars.tsx: every visible constellation label was wrapped in a
  210x180 invisible click box (minWidth/minHeight defaulted on, only the
  zodiac-glyph focus mode actually needed the room). Across the celestial
  sphere this layered hundreds of giant DOM hit zones over the canvas, eating
  taps on planets/moons that happened to project behind them and triggering
  the iOS tap highlight on every miss. Now the box only enforces minWidth /
  minHeight when a zodiac glyph is rendered; otherwise the label auto-sizes
  to the text and the alignment falls back to centered.

- src/scene/DeepSpace.tsx: spacecraft / nearby-star / galaxy pick proxies
  used `<meshBasicMaterial transparent opacity={0} depthWrite={false}/>`,
  which still renders to the alpha buffer. Switched to `visible={false}`,
  matching the planet/moon hit-target pattern in Bodies.tsx — the geometry
  still raycasts but writes zero pixels.

- src/index.css: explicit `-webkit-tap-highlight-color: transparent` on the
  page and on `<canvas>` so iOS WebKit no longer flashes its default gray
  rectangle on every tap.

Random tour: cap constellations to a curated list of 8 iconic figures (Orion,
Ursa Major, Cassiopeia, Cygnus, Leo, Scorpius, Taurus, Crux). Drawing
uniformly from the full 88-IAU bag had constellations winning ~60% of dice
rolls and drowning out planets / DSO / spacecraft / moons / presets. With
the cap they're now ~12% of rolls.
iPad/desktop placement at bottom-right was occluded by the InfoPanel detail
card, which pins itself to [bottom: 16, right: 16] with full-height width
whenever a planet/constellation/DSO is selected. The +/- buttons disappeared
behind it the moment any object was tapped.

Drop the mobile-vs-desktop branch and always render the zoom column directly
under the dice/sky chip cluster at top-right (the placement phones already
used). Removes the now-dead `mobile` prop on ZoomControls.
Improve distant planet selection by enforcing a minimum on-screen pick radius, so clicks near far planets (e.g. Jupiter while focused on Ceres) register reliably.
Clamp distance-scaled planet pick proxies so they never envelop the camera at close zoom levels, while keeping distant planets easy to select. Rework the planet facts card into a denser 6-item grid with period and gravity normalized to Earth-relative units and distance deprioritized.
Camera + jitter (M0–M1):
- Extract CamCtrl from Scene.tsx with phased state machine
  (idle / settling / tracking / cinematic / observing / aiming)
- Memoize positions on T to stop per-frame Map churn; throttle
  parent notify to centennial-fraction T deltas
- Init prevTrackPos at settle to kill first-frame post-settle kick
- Dice sky targets (constellations / DSOs) skip Stargazer staging;
  single intent fires per click
- Camera near 0.005 → 0.02 to relieve WebKit logarithmicDepthBuffer
- Mobile star catalog (mag ≤ 6.5, ~9K stars) loaded via useIsMobile
- prebake-stars.ts now writes both desktop and mobile catalogs

Reliability (M3):
- LoadingScreen 8s watchdog with 2 retries + Reload affordance
- webglcontextlost / restored handlers re-mount Canvas and reset tasks
- Per-feature Suspense boundaries in Scene.tsx so one failing fetch
  no longer blanks the entire scene
- visibilitychange pauses sim tick + cinematic poll; resumes cleanly
- CelesTrak TLE first, bundled stations.tle as fallback only
- prebake-tle.ts refreshes stations.tle in the prebake pipeline
- VITE_NEO_FEED_URL / VITE_NASA_API_KEY env support; .env.example
- Cinematic exits gracefully on user OrbitControls drag
- Bodies.tsx textures reactive via useIsMobile() (fixes iPad rotation)
- mw.json (524 KB unused) removed from public/data/
- neoStatus='error' surfaces as in-app banner

App Store (M2):
- public/privacy.html enumerating all third-party fetches
- About dialog adds Licenses section + Privacy policy link
  (CC BY-SA / CC BY / BSD-3 attribution required by upstreams)
- LICENSE re-scoped to source code; THIRD_PARTY_NOTICES.md added
- NEO synthetic orbit flagged in lib/kepler.ts and shown as
  "Approximate orbit — JPL SBDB unavailable" in info card
- app-store/REJECTION_NOTES.md template for Resolution Center

Architecture (M4):
- Cinematic steps reference preset labels via camIndex(), not array
  indices — reordering CAMS no longer silently breaks the tour
- CAM_PRESET_LAYER_EFFECTS map replaces inline preset side-effect
  cascade in handlePresetSelect

Tests + CI (M5):
- Vitest setup with 15 tests:
  kepler golden vectors (Earth/Mars/Jupiter ±0.005 AU)
  CAMS label-order integrity, observe-mode preset contract
  raDecTo3D round-trip, mode URL contract, iOS font-strip regex
- GitHub Actions CI runs lint + test + build on push and PR

Diagnostics:
- __ORRERY_DIAG__ overlay (window flag or localStorage orrery-diag=1)
  shows FPS, R/s, camera distance, settling phase, positions Δ/s
Snap ctrl.target to the look point on every target change (focus,
preset, aim-at-sphere) so the camera orients to its destination
immediately, then make subsequent look-at lerp 5x faster than the
position lerp. Cinematography rule: orient first, dolly second.

Before: posAlpha and lookAlpha were nearly identical, so for distant
dice rolls (constellations, DSOs at ~292 AU) the camera would slide
toward the target with the destination still off-frame, only catching
up to actually look at it once nearly arrived.

After: camera whips to face the destination instantly, then drifts
into position with the target stably centered the entire travel.
…destinations

Telemetry showed dice rolls landing the camera in empty space for these
categories: their rendering scheme (camera-pinned celestial sphere or fixed
beyond any preset's reach) doesn't agree with the dice's "fly toward target"
model. The fix:

* Strip dso, nearStar, galaxy, spacecraft from the dice destination pool.
* Remove the DeepSky data layer entirely (Field component, deepsky.json,
  prebake-deepsky.ts, preload tag, About-dialog mention, layer toggle, and
  selDeepSky state) — sphere-pinned shader points are unreadable at any
  alpha when you can't fly closer.
* Remove the broken "Jump to Oort/Stellar view" buttons from the
  spacecraft / nearby-star / galaxy info cards (they jumped to presets that
  framed empty space, not the object).
* Keep DeepSpace + spacecraft + nearby-star + galaxy layers as decoration —
  still toggleable, still selectable via direct click, just not destinations.
* Document the long-term Stellar Neighborhood Mode in PROJECT_PLAN.md
  (de-pin selected stars, render real solar-analog suns at fixed cosmic
  position, switch the camera coordinate system) as the right way to make
  "fly to Sirius" feel like a destination.

Lint, vitest, and pnpm build all green; bundle saved 35 KB on the deleted
catalog.
…s layer toggles

Constellation rolls had a one-frame flash where CamCtrl rendered with
focusTarget/camPreset/aimAtSphere all null and snapped the camera target
to the origin (Sun) before the async getConstellationCentroid promise
resolved and re-snapped to the centroid. Visible as a target whip toward
the Sun and then back out to the constellation.

Expose getConstellationCentroidCached as a sync lookup against the cache
already warmed by prefetchConstellationCentroids on mount. Dice path uses
it first and falls back to the async path (with the existing aimTicket
staleness gate) on cache miss, so the first dice roll after a cold start
still works correctly if the prefetch hasn't completed yet.

Audited the dice pool at the same time: confirmed it contains only
solar-system targets (6 presets, 8 planets, 3 dwarfs, up to 8 moons) plus
the 8 famous constellations from FAMOUS_CONSTELLATIONS. No galaxies,
near-stars, spacecraft, or DSOs — those were already cut by 43602cf and
753f3a9.
…ates

Snapshot of in-flight work; committed on the existing working branch, main untouched.
@lukeslp lukeslp merged commit 0796e7c into main Jun 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants