chore: dev-stack switches, shared e2e harness, and desktop shell rules

- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine
  suspend; the reload client otherwise destroys the session under test.
- `dev-peer.sh` plus a separate userdata dir runs a second local peer.
- `tools/voice-probe.js` samples peer state and RTP counters from a live
  window, persisting to localStorage so a renderer reload cannot erase it.
- e2e helpers for voice pairs, peer-role election, and a TURN relay.
- Electron single-instance and dev-client-load decisions move into rules
  files with colocated specs.
This commit is contained in:
2026-08-14 03:19:29 +02:00
parent d71e3a98da
commit e49b3ec112
41 changed files with 35947 additions and 50 deletions
+49
View File
@@ -0,0 +1,49 @@
export const DEV_CLIENT_LOAD_ATTEMPTS = 10;
export const DEV_CLIENT_RETRY_DELAY_MS = 500;
export type DevClientLoadOutcome = 'loaded' | 'aborted' | 'failed';
export interface DevClientLoadDeps {
load: () => Promise<void>;
isAborted: () => boolean;
wait: (delayMs: number) => Promise<void>;
onRetry: (attempt: number, error: unknown) => void;
onGiveUp: (attempts: number, error: unknown) => void;
}
/**
* The dev client is served by a watch-mode build, so a load can fail for
* reasons that resolve on their own: a rebuild in flight, or a shutdown that
* aborted the navigation. Failing hard skipped every window listener
* registered after the load and left a blank window with no message, so this
* retries and always reports instead of throwing.
*/
export async function loadDevelopmentClientWithRetry(deps: DevClientLoadDeps): Promise<DevClientLoadOutcome> {
for (let attempt = 1; attempt <= DEV_CLIENT_LOAD_ATTEMPTS; attempt += 1) {
if (deps.isAborted()) {
return 'aborted';
}
try {
await deps.load();
return 'loaded';
} catch (error) {
if (deps.isAborted()) {
return 'aborted';
}
if (attempt === DEV_CLIENT_LOAD_ATTEMPTS) {
deps.onGiveUp(attempt, error);
return 'failed';
}
deps.onRetry(attempt, error);
await deps.wait(DEV_CLIENT_RETRY_DELAY_MS);
}
}
return 'failed';
}