Waypoint drag: - Use observeDeep instead of observe to detect Y.Map property changes Host election: - Extracted to host-election.ts with 8 unit tests - Simple rule: lowest real client ID is always host, deterministic - Always sets role (host or participant), no missing roles - Filters server docs (empty awareness state) from election BRouter routing: - Route segment-by-segment like bikerouter.de (one request per waypoint pair), guarantees route passes through every waypoint - Merge GeoJSON segments with accumulated stats - 8 unit tests for segment merging and waypoint splitting Awareness cleanup: - Track client IDs per WebSocket connection - Immediately remove awareness on WebSocket close Debug panel: - Visibility + expanded state persisted in localStorage - Hotkey (Shift+Cmd+Option+T) toggles visibility - Labels server docs as (server) - All logic self-contained in YjsDebugPanel.tsx User identity: - Color and name persisted in localStorage across reloads Tests: 32 total (16 existing + 8 host election + 8 routing) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
26 lines
764 B
TypeScript
26 lines
764 B
TypeScript
/**
|
|
* Determines which client should be the routing host.
|
|
*
|
|
* Simple rule: the real client (has user state) with the lowest
|
|
* client ID is always the host. Deterministic, no race conditions.
|
|
*/
|
|
export function electHost(
|
|
states: Map<number, Record<string, unknown>>,
|
|
localId: number,
|
|
): { isHost: boolean; role: "host" | "participant" } {
|
|
let lowestRealId = Infinity;
|
|
|
|
states.forEach((state, clientId) => {
|
|
if (state.user) {
|
|
if (clientId < lowestRealId) lowestRealId = clientId;
|
|
}
|
|
});
|
|
|
|
// No real clients found (shouldn't happen, but defensive)
|
|
if (lowestRealId === Infinity) {
|
|
return { isHost: true, role: "host" };
|
|
}
|
|
|
|
const isHost = localId === lowestRealId;
|
|
return { isHost, role: isHost ? "host" : "participant" };
|
|
}
|