Race Timer

Type: Software Status: In Progress Deadline: v1.0 ASAP (target: usable for 2026 Toronto Albacore season). M-2 follows. Created: 2026-06-02

What is this?

A sailboat race-day platform for Apple Watch + iPhone, built class-clean for one-design racing.

  • Watch (sailor surface): pre-start countdown, race-state handling (AP / Recall / Abandon), race flag reference. Free forever. Optional fleet-sync receive — 30-day trial, then $9.99 one-time IAP.
  • iPhone (RC + sailor surface): RC mode broadcasts the start sequence over BLE to every sailor’s watch + can score the regatta. RC features are paid (Individual $10/yr or Fleet Manager $50/yr + 20 seats).

Motivation

Build for a fleet, not just personal use. Pilot is the Toronto Albacore series — an active one-design dinghy class David sails in. The wedge from competitive teardown is fleet sync — no incumbent does it well, every existing app is a solo-racer manual-tap utility.

What does success look like?

  • Reliable pre-start countdown sailors actually trust on the start line
  • Toronto Albacore fleet adopts it during the 2026 season — visible enough on the line that other clubs notice
  • Paid App Store listing with enough adoption beyond the pilot fleet to justify continued work
  • Class-rules-clean: never causes a sailor to be DSQ’d

Technical

  • Stack: SwiftUI, watchOS 10+ / iOS 17+ targets, Core Bluetooth, StoreKit 2. M-2 adds CloudKit + Sign in with Apple.
  • Repo: ~/projects/race-timer/djt53/race-timer (private) on GitHub
  • Build: XcodeGen → RaceTimer.xcodeproj; ./build.sh archives + uploads to App Store Connect
  • Team: 8HZAKZWFY8 (Enigma Studio Inc.)
  • Intended users: One-design dinghy sailors + their RC volunteers; pilot with Toronto Albacore fleet

Spec snapshot (v1.0 — see decisions/, assets/feature-catalog.md)

  • Pre-start sequence: 5-4-1-Go (RRS Rule 26) default; 10-5-1-Go big-boat, 3-2-1-Go dinghy
  • Watch features: countdown with haptic schedule, pre-start flag chip from broadcast, AP / Recall / Abandon, race flag reference, GO splash, no GPS (class-clean, decision 001)
  • Fleet sync: RC iPhone advertises BLE → sailor watches subscribe. Wire format RaceBroadcast (sequence + start timestamp + state + pre-start flag). watchOS can’t be a peripheral, so RC role lives on iPhone (decision 002 → revised in BLE foundation work)
  • Monetization (decision 003 v3): watch free + $9.99 one-time for fleet sync; iPhone RC $10/yr Individual subscription with 1-month intro free, Fleet Manager $50/yr + 20 seats deferred to M-2

Milestones

IDNameStateNotes
M-1Watch + iPhone v1.0 — timer + fleet sync + paywallIn TestFlight (build 14 processing); watch-bugfixes branch pending mergeBuild 14 supersedes builds 12 + 13. Bundles all session-10 changes (new haptic mapping, pause button removed, broadcast CTA, complication, broadcast keepalive) plus a critical reversal: HKWorkoutSession is back (decision 014 supersedes 013) because build 13’s wrist-down hardware test showed haptic/sound cues queue and only fire on wake without it. The workout-started chime is accepted as the cost of reliable race-day cues; AOD-during-countdown comes back too. Haptics Review screen ships at Settings → Debug. Watch-bugfixes branch (Tue Jun 30) waiting for iOS session to quiesce before joint TF push: broadcast Leave button synchronous-flip + top-right X, haptic timing telemetry (HapticTimingLog + Debug view showing scheduled-vs-actual drift + live workout/runtime state), haptic mapping normalized to all-.failure + new 3:00 (3× burst) / 2:00 (2× burst) count cues, and a SmartAlarmProbe spike testing WKExtendedRuntimeSession.start(at:) precision — precursor to a decided path 3 (drop HK entirely, chain smart-alarm sessions per cue) if wrist-down probe drift comes in <200ms. Remaining: broadcast jank polish; hide Debug section before App Store submission; HealthKit nutrition label declaration before App Store submission (becomes moot if path 3 ships); IAP registration deferred to App Store submission.
M-2Regatta management + fleet-first pivot — RegattaTrack interop, identity spine, Sailor tab, ClubSpot mirror, brand refreshIn progress (M-2.2 ClubSpot mirror + iOS read side complete Tue Jun 16; RegattaTrack interop + identity + Sailor + Home redesign + light mode + multi-fleet all shipped Mon Jun 15)14-18 weeks total. Decisions 006, 011, 015, 016. Session-14 (Monday): full RegattaTrack interop (provider abstraction + Connections UI + matrix checkout + publish path built unexercised + ProviderResponseCache + decode robustness), identity spine (SailorIdentity + name-only matcher + swipe-based onboarding wizard + single-admin sync rule), Sailor tab content (stats + recent form + crewmates + races + ParticipantProfileSheet + TeamProfileSheet), Home redesign (renamed from Timer, house.fill, Run a Race segment, broadcast explainer), light mode (adaptive yellow/electric-blue), and multi-fleet scope (FleetScope + FleetScopeStore + FleetScopePill on every tab). Pilot-mode feature flag turns monetization OFF for free fleet testing. M-2.2 ClubSpot mirror complete (session 13 + session 15 polish): backend (daily cron + lazy clubspot_sync_event + deep mirror tables) + iOS (ClubSpotMirror read client + onboarding class multi-select + Events tab fleet event browser + regatta detail with standings/registered-boats/empty-state). Standings rewrite (Tue Jun 16): stopped re-implementing scoring; iOS mirrors ClubSpot’s verbatim computed standings payload (scoresByRegistration + per-race points/letterScore/throwout). Honors per-class throwouts + per-class starts + letter codes + tiebreaks without us touching the math. M-2.4 brand refresh still pending — even more salient after fleet-first + 7-class launch surface (race-timer no longer fits): new product name, icon, marketing surface — must land before App Store submission. M-2.3 series/throwouts, M-2.5 multi-class, M-2.6 Fleet Manager seats unchanged.
M-3Handicap + advanced scoring (PHRF / IRC / ORC)FutureDefer until M-2 ships and we have feedback
M-4Watch-side results visibilityFutureDefer — watch stays focused on race-day during M-2