Covers routes/activities that enter the journal without BRouter waytags (imports, uploads, pre-existing rows): - overpass-ways.server.ts: server-side Overpass client (way[highway] + geom in a bbox, configurable OVERPASS_URLS, timeout + oversized-bbox guard). - surface-match.server.ts: pure nearest-way map-matcher → per-segment surface/highway (unmatched → unknown). Unit-tested. - surface-backfill pg-boss job: load geom → skip if breakdown exists → Overpass → match → computeSurfaceBreakdown → store → emit `surface_breakdown` SSE to the owner. Idempotent, retry-safe, best-effort; registered in server.ts. - Enqueued from createActivity (imports/uploads) + owner-on-open in the route & activity detail loaders (non-Planner routes, old rows), deduped via singletonKey. - useSurfaceBackfillUpdates: detail pages subscribe to /api/events and revalidate() when their row's backfill lands (live bars, no reload). - Privacy manifest updated (DE + EN) for the Overpass bbox lookup. Tests: matchSurfaces unit; surface-backfill job (mocked Overpass/db/events: store + emit, skip-if-present, skip-if-no-ways). Verified the real Overpass→match→breakdown pipeline against a live Berlin bbox (509 ways → plausible asphalt/paving_stones/footway mix). typecheck + lint + unit (journal 333) green. Note: the worker runs via server.ts (prod/staging), not `react-router dev`, so the live job + SSE exercise on a deployed instance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useRevalidator } from "react-router";
|
|
|
|
/**
|
|
* Live-updates a route/activity detail page when its async surface backfill
|
|
* completes. Subscribes to `/api/events` and, on a `surface_breakdown` event
|
|
* matching this row, re-runs the loader (so the bars appear without a reload).
|
|
*
|
|
* Enable only while it's worth it — i.e. the viewer is the owner (the backfill
|
|
* emits to the owner's user stream) and the breakdown isn't present yet. Once
|
|
* the breakdown lands, the loader revalidates, `enabled` flips false, and the
|
|
* connection is torn down.
|
|
*/
|
|
export function useSurfaceBackfillUpdates(
|
|
kind: "route" | "activity",
|
|
id: string,
|
|
enabled: boolean,
|
|
): void {
|
|
const revalidator = useRevalidator();
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
if (typeof EventSource === "undefined") return;
|
|
const es = new EventSource("/api/events");
|
|
const onEvent = (e: MessageEvent) => {
|
|
try {
|
|
const parsed = JSON.parse(e.data) as { kind?: string; id?: string };
|
|
if (parsed.kind === kind && parsed.id === id) revalidator.revalidate();
|
|
} catch {
|
|
// Malformed payload — ignore.
|
|
}
|
|
};
|
|
es.addEventListener("surface_breakdown", onEvent as EventListener);
|
|
return () => {
|
|
es.removeEventListener("surface_breakdown", onEvent as EventListener);
|
|
es.close();
|
|
};
|
|
}, [kind, id, enabled, revalidator]);
|
|
}
|