diff --git a/apps/journal/app/components/RouteMapThumbnail.client.tsx b/apps/journal/app/components/RouteMapThumbnail.client.tsx index 3ea476d..5a4591d 100644 --- a/apps/journal/app/components/RouteMapThumbnail.client.tsx +++ b/apps/journal/app/components/RouteMapThumbnail.client.tsx @@ -142,7 +142,7 @@ function DayColoredRoute({ data, dayBreaks, highlightedDay }: { data: GeoJsonObj type: "Feature", geometry: { type: "LineString", coordinates: seg.coords }, properties: {}, - } as unknown as GeoJsonObject; + } as GeoJsonObject; return ( { - void (_creds as unknown as OAuthCredentials); + const buffer = await withFreshCredentials(service.id, async () => { const resp = await fetchWithTimeout(event.fileUrl!); if (!resp.ok) throw new Error(`Wahoo file download failed: ${resp.status}`); return Buffer.from(await resp.arrayBuffer()); diff --git a/apps/planner/app/components/MapHelpers.tsx b/apps/planner/app/components/MapHelpers.tsx index 73753d4..e75ba34 100644 --- a/apps/planner/app/components/MapHelpers.tsx +++ b/apps/planner/app/components/MapHelpers.tsx @@ -11,7 +11,7 @@ export function MapExposer() { const map = useMap(); useEffect(() => { if (typeof window !== "undefined") { - (window as unknown as Record).__leafletMap = map; + window.__leafletMap = map; } }, [map]); return null; diff --git a/apps/planner/app/components/NoGoAreaLayer.tsx b/apps/planner/app/components/NoGoAreaLayer.tsx index b6508ee..9179890 100644 --- a/apps/planner/app/components/NoGoAreaLayer.tsx +++ b/apps/planner/app/components/NoGoAreaLayer.tsx @@ -45,7 +45,7 @@ export function NoGoAreaLayer({ noGoAreas, doc, enabled, onToggle }: NoGoAreaLay const polygon = L.polygon(latLngs, NO_GO_STYLE); polygon.on("contextmenu", (e) => { - L.DomEvent.preventDefault(e as unknown as Event); + L.DomEvent.preventDefault(e.originalEvent); suppressObserverRef.current = true; doc.transact(() => noGoAreas.delete(i, 1), "local"); suppressObserverRef.current = false; diff --git a/apps/planner/app/components/PlannerMap.tsx b/apps/planner/app/components/PlannerMap.tsx index 76c9de5..0a135d3 100644 --- a/apps/planner/app/components/PlannerMap.tsx +++ b/apps/planner/app/components/PlannerMap.tsx @@ -210,7 +210,7 @@ export function PlannerMap({ yjs, sessionId, onRouteRequest, highlightPosition, moveWaypoint(i, lat, lng); }, contextmenu: (e) => { - L.DomEvent.preventDefault(e as unknown as Event); + L.DomEvent.preventDefault(e.originalEvent); const orig = e.originalEvent as MouseEvent; setContextMenu({ x: orig.clientX, y: orig.clientY, index: i }); }, diff --git a/apps/planner/app/components/PoiPanel.tsx b/apps/planner/app/components/PoiPanel.tsx index 1f55dc0..9c31617 100644 --- a/apps/planner/app/components/PoiPanel.tsx +++ b/apps/planner/app/components/PoiPanel.tsx @@ -90,8 +90,9 @@ export function PoiMarkers({ poiState, onAddWaypoint }: PoiPanelProps) { // Dynamic import to avoid bundling markercluster when POIs aren't used import("leaflet.markercluster").then(() => { if (!mounted) return; - // After import, L.markerClusterGroup is available - const cluster = (L as unknown as { markerClusterGroup: (opts?: object) => L.LayerGroup }).markerClusterGroup({ + // After import, L.markerClusterGroup is available (typed via + // module augmentation in app/types/global.d.ts). + const cluster = L.markerClusterGroup({ maxClusterRadius: 40, disableClusteringAtZoom: 15, showCoverageOnHover: false, diff --git a/apps/planner/app/components/RouteInteraction.tsx b/apps/planner/app/components/RouteInteraction.tsx index 36375f9..205da20 100644 --- a/apps/planner/app/components/RouteInteraction.tsx +++ b/apps/planner/app/components/RouteInteraction.tsx @@ -194,7 +194,7 @@ export function RouteInteraction({ // Prevent double-click zoom when clicking ghost marker marker.on("dblclick", (e) => { - L.DomEvent.stop(e as unknown as Event); + L.DomEvent.stop(e.originalEvent); }); map.on("mousemove", onMouseMove); diff --git a/apps/planner/app/components/SessionView.tsx b/apps/planner/app/components/SessionView.tsx index c393489..7a9e170 100644 --- a/apps/planner/app/components/SessionView.tsx +++ b/apps/planner/app/components/SessionView.tsx @@ -195,18 +195,18 @@ export function SessionView({ sessionId, hasJournalCallback, returnUrl, initialW }, []); const handleChartClick = useCallback((position: [number, number]) => { - const map = (window as unknown as Record).__leafletMap as L.Map | undefined; + const map = window.__leafletMap; map?.panTo(position); }, []); const handleChartDragSelect = useCallback((bounds: [[number, number], [number, number]]) => { - const map = (window as unknown as Record).__leafletMap as L.Map | undefined; + const map = window.__leafletMap; map?.fitBounds(bounds, { padding: [30, 30] }); setIsZoomedByChart(true); }, []); const handleResetZoom = useCallback(() => { - const map = (window as unknown as Record).__leafletMap as L.Map | undefined; + const map = window.__leafletMap; if (!map || !yjs) return; const coordsJson = yjs.routeData.get("coordinates") as string | undefined; if (!coordsJson) return; diff --git a/apps/planner/app/lib/use-yjs.ts b/apps/planner/app/lib/use-yjs.ts index a9637f6..1dfb235 100644 --- a/apps/planner/app/lib/use-yjs.ts +++ b/apps/planner/app/lib/use-yjs.ts @@ -99,6 +99,10 @@ export function useYjs( // Initialize waypoints once after first sync if (initialWaypoints?.length && !initializedWaypoints.current) { + // y-websocket emits both "sync" and "synced" (legacy alias). We + // keep "synced" for backwards-compat with older versions; the + // typed `.on` signature in the current lib version omits it + // even though the runtime still fires it, so we coerce. (provider as unknown as { on(event: string, cb: () => void): void }).on("synced", () => { // Only add if the doc is empty (avoid duplicating on reconnect) if (waypoints.length === 0 && !initializedWaypoints.current) { diff --git a/apps/planner/app/types/global.d.ts b/apps/planner/app/types/global.d.ts new file mode 100644 index 0000000..3b9e179 --- /dev/null +++ b/apps/planner/app/types/global.d.ts @@ -0,0 +1,34 @@ +// Ambient type augmentations for the planner client. +// +// Picked up by tsconfig's default `include` (every `**/*.ts(x)` inside +// the project root). Keeps the `as unknown as Record` +// dance out of every site that needs to read or write a globally- +// exposed value. + +import type { Map as LeafletMap, LayerGroup } from "leaflet"; + +declare global { + // Exposed by MapHelpers.tsx::MapExposer for E2E tests and external + // integrations. Optional because it's only present after the map + // mounts. + interface Window { + __leafletMap?: LeafletMap; + } +} + +// Module augmentation for leaflet.markercluster. The plugin extends +// the global `L` namespace at runtime but ships no TypeScript types, +// so without this declaration callers fall back to `as unknown as +// {...}` shims. +declare module "leaflet" { + function markerClusterGroup(options?: MarkerClusterGroupOptions): LayerGroup; + interface MarkerClusterGroupOptions { + showCoverageOnHover?: boolean; + spiderfyOnMaxZoom?: boolean; + zoomToBoundsOnClick?: boolean; + maxClusterRadius?: number; + [key: string]: unknown; + } +} + +export {};