Deepen three architectural seams: FIT consolidation, host election extraction, injectable db
- Extract shared fitToGpx into connected-services/fit.ts (FIT is a Garmin open standard used by Wahoo, Coros, Garmin — not provider-specific) - Add importActivity() to sync/imports.server.ts so providers call one function instead of createActivity + recordImport separately; eliminates direct dependency on activities.server.ts from provider adapters - Update wahoo importer + webhook to use both shared helpers - Extract useHostElection(yjs) hook from useRouting so host election is independently testable without mounting the full routing stack - Add setDb() to journal db.ts for module-level injection in unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
48d97be8f1
commit
e387e1f798
8 changed files with 104 additions and 122 deletions
28
apps/planner/app/lib/use-host-election.ts
Normal file
28
apps/planner/app/lib/use-host-election.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { electHost } from "./host-election.ts";
|
||||
import type { YjsState } from "./use-yjs.ts";
|
||||
|
||||
export function useHostElection(yjs: YjsState | null): boolean {
|
||||
const [isHost, setIsHost] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!yjs) return;
|
||||
|
||||
const checkHost = () => {
|
||||
const states = yjs.awareness.getStates() as Map<number, Record<string, unknown>>;
|
||||
const localId = yjs.awareness.clientID;
|
||||
const { isHost: amHost, role } = electHost(states, localId);
|
||||
setIsHost(amHost);
|
||||
yjs.awareness.setLocalStateField("role", role);
|
||||
};
|
||||
|
||||
yjs.awareness.on("change", checkHost);
|
||||
checkHost();
|
||||
|
||||
return () => {
|
||||
yjs.awareness.off("change", checkHost);
|
||||
};
|
||||
}, [yjs]);
|
||||
|
||||
return isHost;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import * as Y from "yjs";
|
||||
import type { YjsState } from "./use-yjs.ts";
|
||||
import { electHost } from "./host-election.ts";
|
||||
import { useHostElection } from "./use-host-election.ts";
|
||||
import {
|
||||
hashNoGoAreas,
|
||||
mergeGeoJsonSegments,
|
||||
|
|
@ -51,7 +51,7 @@ function restoreWaypoints(yjs: YjsState, snapshot: WaypointData[], restoringRef:
|
|||
export type RouteError = "no_route" | "failed" | "rate_limit" | null;
|
||||
|
||||
export function useRouting(yjs: YjsState | null, sessionId: string) {
|
||||
const [isHost, setIsHost] = useState(false);
|
||||
const isHost = useHostElection(yjs);
|
||||
const [computing, setComputing] = useState(false);
|
||||
const [routeError, setRouteError] = useState<RouteError>(null);
|
||||
const [routeStats, setRouteStats] = useState<RouteStats>({});
|
||||
|
|
@ -67,26 +67,6 @@ export function useRouting(yjs: YjsState | null, sessionId: string) {
|
|||
// instead of N-1 on every recompute.
|
||||
const segmentCacheRef = useRef<SegmentCache>(new SegmentCache());
|
||||
|
||||
// Host election via Yjs awareness
|
||||
useEffect(() => {
|
||||
if (!yjs) return;
|
||||
|
||||
const checkHost = () => {
|
||||
const states = yjs.awareness.getStates() as Map<number, Record<string, unknown>>;
|
||||
const localId = yjs.awareness.clientID;
|
||||
const { isHost: amHost, role } = electHost(states, localId);
|
||||
setIsHost(amHost);
|
||||
yjs.awareness.setLocalStateField("role", role);
|
||||
};
|
||||
|
||||
yjs.awareness.on("change", checkHost);
|
||||
checkHost();
|
||||
|
||||
return () => {
|
||||
yjs.awareness.off("change", checkHost);
|
||||
};
|
||||
}, [yjs]);
|
||||
|
||||
const computeRoute = useCallback(
|
||||
async (waypoints: WaypointData[]) => {
|
||||
if (!yjs || !isHost || waypoints.length < 2) return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue