← Portfolio

Portfolio Case Study

moondial

An e-ink celestial almanac for iOS and Android, built with React Native for moon phases, sun and moon timing, photo-light windows, Zmanim, and timezone-aware locations

React Native TypeScript Zustand Luxon Astronomy Engine Hebcal tz-lookup iOS & Android

Beta candidate  ·  iOS and Android 1.1 (6)


Origin

moondial began as Astronomical Calendar, a native re-architecture of the web calendar at bachtogauss.com/calendar. The web version is something you sit down with. The app is something you reach for in the moment: outside at night, watching the moon, checking tomorrow's light, or wondering when the next visible event is coming.


Overview

moondial turns the original browser calendar into a Today-first mobile almanac. It keeps the real astronomy, eclipse, photography-light, and Zmanim calculations, but presents them through a quieter interface built for quick reading: a moon-led Today screen, a continuous Calendar view, and Settings for location and display preferences.

The current app is branded around a pencil-on-paper e-ink system: Cormorant Garamond for display text, DM Mono for labels and tabular values, fine linework, muted graphite color, and light-mode-only surfaces. The polish work is deliberately rendering and interaction work; the calculation engines are treated as locked behavior.

The app coordinates independent calculation domains for sun and moon rise/set events, moon phases, eclipses, blue and golden hour windows, and halachic Zmanim. Location, timezone, persisted settings, custom SVG iconography, app startup behavior, and platform-specific release details are handled behind a three-tab interface: Today, Calendar, and Settings.


Today

Today is the front door. It is locked to the live date and reads like a small celestial instrument: location and coordinates at the top, a large animated moon, moon age and illumination, moonrise and moonset, a sky arc for sun and moon positions, a sunrise / solar noon / sunset ledger, photo-light windows, and optional Zmanim.


Feature Set

Today

Calendar

Settings


Architecture

The app is organized around screen-level composition, calculation services, small adapter layers, and a compact global store. The architecture keeps astronomical and halachic calculations independent, then merges partial results so a failure in one domain does not erase usable data from another.

src/
  components/          E-ink UI, MoonDisk, SkyArc, ledgers, controls
  components/icons/    Bottom tab icons
  components/symbols/  Phase and eclipse SVG symbols
  context/             Startup readiness and launch animation
  navigation/          React Navigation bottom tabs
  screens/             Today, Calendar, and Settings screens
  services/            Calendar engine, location, geocoding, moon/time data
  store/               Zustand calendar, location, settings, persistence
  styles/              Shared theme, colors, typography, spacing
  types/               Shared TypeScript interfaces
  utils/               Astronomy, halachic, eclipse, moon, timezone adapters

Calculation pipeline

Calendar navigation is render-first: the visible month and selected date update immediately, while cached or cold data attaches only when its returned key still matches the current view. That keeps interaction responsive even when eclipse or upcoming-event searches are still filling in.

Key files and components

TodayScreen.tsx Main Today composition: live date, moon hero, sky arc, light ledger, Zmanim, replay orchestration
CalendarScreen.tsx Continuous month calendar, selected-day timetable, this-month rows, upcoming board
SettingsScreen.tsx Location, display, calendar marker, section, and Zmanim controls
calendarDataEngine.ts Memory caches, promise dedupe, stale-key guards, and cooperative prefetch scheduling
moonPhaseEventService.ts Canonical month phase lookup and session cache for Astronomy Engine quarter searches
timeService.ts Parallel astronomy and Hebcal/Zmanim orchestration with fault-tolerant merging
astroDataAdapter.ts Adapter from calculation services to screen-readable day data
SkyArc.tsx Sun and moon arc visualization with rendering-only rise/set calibration
MoonDisk.tsx SVG phased moon used by Today, Calendar, and timetable views
LightLedger.tsx Sunrise, solar noon, sunset, and photo-light rows
TimeSettleText.tsx Count-up page-turn animation for primary Today time values
FadeInOnReplay.tsx Shared first-load and tap-replay value reveal treatment
StartupContext.tsx Coordinates launch overlay, store hydration, and Today animation readiness
EInkSwitch.tsx / SegControl.tsx Custom Settings controls for the current e-ink interface

Tech Stack

Mobile framework React Native 0.81.1, React 19.1
Language TypeScript
UI foundation React Native Paper provider, custom e-ink controls, custom styles, custom fonts
Navigation React Navigation bottom tabs
State and persistence Zustand with AsyncStorage-backed location and settings persistence
Date/time handling Luxon plus native Date values from calculation libraries
Astronomy calculations astronomy-engine
Halachic calculations @hebcal/core
Location @react-native-community/geolocation
Geocoding OpenStreetMap Nominatim
Timezone resolution tz-lookup
Graphics react-native-svg
Testing Jest, React Test Renderer, TypeScript checks, lint, standalone calculation diagnostics

Engineering Decisions

Today-first product shape

The month calendar is no longer the only center of gravity. Today leads with the information a user wants while standing outside or planning the light: moon phase, moon age, moonrise, moonset, sun position, blue and golden hour, and Zmanim. Calendar remains the deeper planning view.

Fault-tolerant data merging

Astronomy and halachic data are fetched separately and merged after each path completes. Individual Zmanim values are isolated so a single bad calculation does not blank the card, and missing values return null rather than blocking the entire view.

One moon phase source

Principal moon phases now route through moonPhaseEventService as the canonical phase enumeration path. Calendar markers, moon summaries, and upcoming data all draw from the same service so phase times cannot diverge between parts of the interface.

Responsive Calendar orchestration

The Calendar screen uses memory-only caches, promise dedupe, stale-key guards, and cooperative prefetching. Visible phase and eclipse markers are warmed before below-the-fold sections because calendar cells are what users scan while navigating.

Timezone-aware location handling

GPS, coordinate, and manual search locations are enriched with IANA timezone IDs through offline tz-lookup resolution. Display times, phase and eclipse date assignment, and Hebcal calculations use the selected location timezone when available.

Rendering polish without calculation drift

The current animation and visual work is presentation-only. The sky arc, marker clipping, page-turn time values, launch overlay, bottom-tab clearance, and app icon updates preserve the underlying astronomy, eclipse, photography-light, and Zmanim engine behavior.


Testing and Verification

The latest documented verification snapshot reports the core checks passing cleanly:


Current Caveats


Roadmap


Attribution