From c4d5d3d0cf5c1710b20865e618543a4cd8d461e3 Mon Sep 17 00:00:00 2001 From: Lalit Sharma Date: Sun, 22 Feb 2026 13:21:22 +0000 Subject: [PATCH] chore(release): bump mobile version to 1.1.11 --- CHANGELOG.md | 7 +++ apps/mobile/package.json | 2 +- apps/mobile/src/navigation/RootNavigator.tsx | 4 ++ .../src/screens/EclipsePreviewScreen.tsx | 22 +++++++++- apps/mobile/src/utils/previewGeometry.ts | 43 ++++++++++++++++++- apps/mobile/tests/preview-geometry.test.ts | 41 +++++++++++++++++- 6 files changed, 115 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc109cc..dea04b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.11] — 2026-02-22 + +### Fixed +- Updated preview mode moon motion so left/right travel direction follows local contact bearing progression when bearing data is available. +- Wired contact bearing values into preview payload construction so direction-aware geometry has access to C1/C2/C3/C4 bearing inputs. +- Added regression tests for bearing-based travel direction selection and fallback behavior when bearing pairs are incomplete. + ## [1.1.10] — 2026-02-22 ### Fixed diff --git a/apps/mobile/package.json b/apps/mobile/package.json index be09ebb..66443ba 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@eclipse-timer/mobile", - "version": "1.1.10", + "version": "1.1.11", "private": true, "main": "index.js", "scripts": { diff --git a/apps/mobile/src/navigation/RootNavigator.tsx b/apps/mobile/src/navigation/RootNavigator.tsx index c216f75..02ca1dc 100644 --- a/apps/mobile/src/navigation/RootNavigator.tsx +++ b/apps/mobile/src/navigation/RootNavigator.tsx @@ -353,6 +353,10 @@ function TimerRoute({ navigation, catalog, onOpenMenu }: TimerRouteProps) { maxUtc: result.maxUtc, c3Utc: result.c3Utc, c4Utc: result.c4Utc, + c1BearingDeg: result.c1BearingDeg, + c2BearingDeg: result.c2BearingDeg, + c3BearingDeg: result.c3BearingDeg, + c4BearingDeg: result.c4BearingDeg, }; navigation.navigate("Preview", { payload }); diff --git a/apps/mobile/src/screens/EclipsePreviewScreen.tsx b/apps/mobile/src/screens/EclipsePreviewScreen.tsx index b23a866..10ad490 100644 --- a/apps/mobile/src/screens/EclipsePreviewScreen.tsx +++ b/apps/mobile/src/screens/EclipsePreviewScreen.tsx @@ -16,6 +16,7 @@ import { colorForContactKey } from "../utils/contactTheme"; import { fmtLocalHuman, fmtUtcHuman } from "../utils/date"; import { calculatePreviewMoonGeometry, + determinePreviewTravelDirection, PREVIEW_STAGE_SIZE, PREVIEW_SUN_RADIUS, } from "../utils/previewGeometry"; @@ -46,6 +47,10 @@ export type PreviewPayload = { maxUtc?: string; c3Utc?: string; c4Utc?: string; + c1BearingDeg?: number; + c2BearingDeg?: number; + c3BearingDeg?: number; + c4BearingDeg?: number; }; type EclipsePreviewScreenProps = { @@ -310,8 +315,23 @@ export default function EclipsePreviewScreen({ contacts: contactProgress, stageSize: SIM_STAGE_SIZE, sunRadius: SUN_RADIUS, + travelDirection: determinePreviewTravelDirection({ + c1BearingDeg: payload.c1BearingDeg, + c2BearingDeg: payload.c2BearingDeg, + c3BearingDeg: payload.c3BearingDeg, + c4BearingDeg: payload.c4BearingDeg, + }), }), - [contactProgress, payload.kindAtLocation, payload.magnitude, progress], + [ + contactProgress, + payload.c1BearingDeg, + payload.c2BearingDeg, + payload.c3BearingDeg, + payload.c4BearingDeg, + payload.kindAtLocation, + payload.magnitude, + progress, + ], ); const phaseLabel = useMemo( diff --git a/apps/mobile/src/utils/previewGeometry.ts b/apps/mobile/src/utils/previewGeometry.ts index fd9e42c..66f0670 100644 --- a/apps/mobile/src/utils/previewGeometry.ts +++ b/apps/mobile/src/utils/previewGeometry.ts @@ -20,10 +20,49 @@ export type PreviewMoonGeometry = { moonTravelHalfSpan: number; }; +export type PreviewDirectionBearings = { + c1BearingDeg?: number; + c2BearingDeg?: number; + c3BearingDeg?: number; + c4BearingDeg?: number; +}; + function clamp01(v: number) { return Math.max(0, Math.min(1, v)); } +function normalizeSignedDeltaDeg(fromDeg: number, toDeg: number) { + const delta = ((toDeg - fromDeg + 540) % 360) - 180; + return delta === -180 ? 180 : delta; +} + +function resolveDirectionalBearingPair(bearings: PreviewDirectionBearings) { + const pairs: Array<[number | undefined, number | undefined]> = [ + [bearings.c1BearingDeg, bearings.c4BearingDeg], + [bearings.c2BearingDeg, bearings.c3BearingDeg], + [bearings.c1BearingDeg, bearings.c3BearingDeg], + [bearings.c2BearingDeg, bearings.c4BearingDeg], + ]; + + for (const [start, end] of pairs) { + if (typeof start !== "number" || !Number.isFinite(start)) continue; + if (typeof end !== "number" || !Number.isFinite(end)) continue; + return { start, end }; + } + + return null; +} + +export function determinePreviewTravelDirection( + bearings: PreviewDirectionBearings | undefined, +): 1 | -1 { + if (!bearings) return 1; + const pair = resolveDirectionalBearingPair(bearings); + if (!pair) return 1; + const delta = normalizeSignedDeltaDeg(pair.start, pair.end); + return delta >= 0 ? 1 : -1; +} + export function determineMoonRadius(kindAtLocation: EclipseKindAtLocation) { if (kindAtLocation === "annular") return 58; if (kindAtLocation === "total") return 76; @@ -109,6 +148,7 @@ export function calculatePreviewMoonGeometry(params: { contacts?: PreviewMotionContacts; stageSize?: number; sunRadius?: number; + travelDirection?: 1 | -1; }): PreviewMoonGeometry { const stageSize = params.stageSize ?? PREVIEW_STAGE_SIZE; const sunRadius = params.sunRadius ?? PREVIEW_SUN_RADIUS; @@ -121,7 +161,8 @@ export function calculatePreviewMoonGeometry(params: { ); const anchors = buildMotionAnchors(params.contacts ?? {}, sunRadius, moonRadius); - const moonOffsetX = interpolateOffsetX(params.progress, anchors); + const travelDirection = params.travelDirection ?? 1; + const moonOffsetX = interpolateOffsetX(params.progress, anchors) * travelDirection; const moonCenterX = stageSize / 2 + moonOffsetX; const moonCenterY = stageSize / 2 + moonClosestOffset; diff --git a/apps/mobile/tests/preview-geometry.test.ts b/apps/mobile/tests/preview-geometry.test.ts index 14888f0..deddd02 100644 --- a/apps/mobile/tests/preview-geometry.test.ts +++ b/apps/mobile/tests/preview-geometry.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; -import { calculatePreviewMoonGeometry, PREVIEW_SUN_RADIUS } from "../src/utils/previewGeometry"; +import { + calculatePreviewMoonGeometry, + determinePreviewTravelDirection, + PREVIEW_SUN_RADIUS, +} from "../src/utils/previewGeometry"; describe("preview moon geometry", () => { it("places C1 at exact outer tangency", () => { @@ -49,4 +53,39 @@ describe("preview moon geometry", () => { expect(c3Distance).toBeCloseTo(Math.abs(PREVIEW_SUN_RADIUS - c3Geometry.moonRadius), 6); expect(postC3Distance).toBeGreaterThan(c3Distance); }); + + it("uses contact bearings to keep moon travel direction accurate", () => { + const leftToRightDirection = determinePreviewTravelDirection({ + c1BearingDeg: 100, + c4BearingDeg: 140, + }); + const rightToLeftDirection = determinePreviewTravelDirection({ + c1BearingDeg: 140, + c4BearingDeg: 100, + }); + + const baseParams = { + progress: 0.25, + kindAtLocation: "total" as const, + contacts: { c1: 0, c2: 0.25, max: 0.5, c3: 0.75, c4: 1 }, + }; + + const leftToRight = calculatePreviewMoonGeometry({ + ...baseParams, + travelDirection: leftToRightDirection, + }); + const rightToLeft = calculatePreviewMoonGeometry({ + ...baseParams, + travelDirection: rightToLeftDirection, + }); + + expect(leftToRight.moonOffsetX).toBeLessThan(0); + expect(rightToLeft.moonOffsetX).toBeGreaterThan(0); + expect(Math.abs(leftToRight.moonOffsetX)).toBeCloseTo(Math.abs(rightToLeft.moonOffsetX), 6); + }); + + it("falls back to default travel direction when bearings are missing", () => { + expect(determinePreviewTravelDirection(undefined)).toBe(1); + expect(determinePreviewTravelDirection({ c2BearingDeg: 120 })).toBe(1); + }); });