diff --git a/.gitignore b/.gitignore
index c24ff2f978f..fcfc7d65314 100755
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,8 @@ tmpclaude-*
*.swo
node_modules/
dist/
+dist-proof/
+apps/desktop-tauri/.proof/
nul
# Local scripts and env files
diff --git a/AGENTS.md b/AGENTS.md
index 68579880bd1..81aea8af962 100755
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -136,7 +136,7 @@ pnpm run tauri:build
### UI validation with CUA ([trycua/cua](https://github.com/trycua/cua))
-Unit tests and `local-check` do **not** prove tray, settings, float bar, theme, or WebView2 behavior. For UI / tray / settings / float-bar / visual changes, agents **must** retest on a real Windows desktop build using **Cua Drivers** (background computer-use: click, type, screenshot, UIA) from the open-source [trycua/cua](https://github.com/trycua/cua) project. Docs: [cua.ai/docs](https://cua.ai/docs), driver install: [install guide](https://cua.ai/docs/how-to-guides/driver/install), CLI reference: [cua-driver CLI](https://cua.ai/docs/reference/cua-driver/cli-reference).
+Unit tests and `local-check` do **not** prove tray, settings, float bar, theme, or WebView2 behavior. Changes confined to webview content may use the [browser proof](#browser-proof-pnpm-proofcheck) instead. For every other UI / tray / settings / float-bar / visual change, agents **must** retest on a real Windows desktop build using **Cua Drivers** (background computer-use: click, type, screenshot, UIA) from the open-source [trycua/cua](https://github.com/trycua/cua) project. Docs: [cua.ai/docs](https://cua.ai/docs), driver install: [install guide](https://cua.ai/docs/how-to-guides/driver/install), CLI reference: [cua-driver CLI](https://cua.ai/docs/reference/cua-driver/cli-reference).
**Install (Windows PowerShell, from upstream README):**
@@ -167,12 +167,21 @@ Then follow post-install instructions (permissions / accessibility as prompted).
**Do not** treat Vitest/jsdom or `cargo test` alone as sufficient for tray icon, DWM, WebView2 theme, float bar z-order, or settings chrome. **Do not** open issues/PRs against trycua/cua unless the user explicitly asks; use it as tooling.
+### Browser proof (`pnpm proof:check`)
+
+The React surfaces also run in a plain browser over mocked IPC (`apps/desktop-tauri/proof.html`, fixtures in `src/proof/`). `pnpm proof:check` (in `apps/desktop-tauri`) drives Chrome, Edge, or Chromium over CDP through `scripts/proof-scenarios.json` and prints PASS/FAIL per scenario plus screenshots. It runs on any OS, so subagents and CI can run it without a Windows desktop. Details: `docs/WINDOWS_PROOF.md` §5.
+
+- **Enough on its own** when the change only touches webview content: layout, copy / i18n, rendered data and states, settings pages and controls, provider siloing. Add or extend a scenario that covers the change, run `pnpm proof:check`, and attach the PASS output and screenshots labelled "Chromium, mocked IPC".
+- **Still needs the CUA loop** when the change touches the tray icon or tray menu, window chrome, frame, size or position, DWM / WebView2 theme behavior under `auto`, float bar always-on-top or window behavior, blur-dismiss, or real provider data, cookies, or credential storage. When unsure, run both.
+- **Mocks follow the bridge:** a new `invoke("...")` command needs a fixture handler or an `INERT_ACTIONS` entry in `src/proof/router.ts`; `src/proof/coverage.test.ts` fails otherwise. Fixtures stay synthetic (`example.com` identities, no real settings, cookies, tokens, or provider responses).
+- **Headed runs** (`pnpm proof:check -- --headed`) use the runner's own isolated browser profile. Do not attach automation to the user's browser profile.
+
### Off-machine proof (Cloudflare)
Every Cloudflare runtime (Workers, Containers, Sandbox, Browser Rendering) is Linux. None can run the Tauri shell, WebView2, the tray, or DWM. Off-machine evidence supplements the CUA loop above; it never replaces it for tray, theme, float-bar, or window-chrome changes.
- **What it can prove:** React surface layout, copy / i18n, rendered data, provider siloing, DOM and accessibility tree, and console errors (Browser Rendering's headless Chromium against a static frontend build); tray icon pixels from `codexbar::tray::{render_bar_icon_rgba, render_percent_icon_rgba}` exported without a desktop.
-- **Frontend adapter:** mock the bridge with `mockIPC(handler, { shouldMockEvents: true })` and `mockWindows` from `@tauri-apps/api/mocks` (ships with `@tauri-apps/api`; no new dependency). The handler answers by command name from fixtures whose shape matches that command's return type in `types/bridge.ts`; usage-snapshot fixtures use the `CODEXBAR_SEED_USAGE_JSON` shape. Keep the mock out of the production bundle.
+- **Frontend adapter:** the [browser proof](#browser-proof-pnpm-proofcheck) build. `pnpm build:proof` emits a static `dist-proof/`; `pnpm proof:check -- --url ` or `--cdp ` runs the same scenarios against a hosted copy or a remote browser. The mock stays out of the production bundle (`src/proof/isolation.test.ts`).
- **Fixtures only:** never upload real settings, cookies, tokens, account emails, or provider responses.
- **Labeling:** mark these screenshots in PRs as "Chromium (Linux), mocked IPC". Fonts, scrollbars, and DPI differ from WebView2.
- **Windows-only checks away from the dev machine:** use a Windows host (CircleCI `windows` orb or a disposable Windows VM) running the same rebuild → proof mode → CDP + CUA loop. Cloudflare may front that host (`cloudflared` tunnel behind Cloudflare Access) and store its artifacts, but the proof itself comes from Windows.
@@ -189,7 +198,8 @@ Every Cloudflare runtime (Workers, Containers, Sandbox, Browser Rendering) is Li
- Screenshots / GIFs for UI changes (Windows)
- Linked issue / reference when relevant
- Hosted PR check exists: `ci/circleci: pr-check` is the primary Windows gate; `.github/workflows/pr-check.yml` is manual Blacksmith backup. Porting micro PRs targeting `port/upstream-*` use focused local evidence and intentionally skip automatic hosted Windows CI; same-repository `main`-bound PRs always get the hosted gate; fork PRs never do (see Fork PRs and reviews).
-- UI / tray / settings / float-bar / visual PRs: **hybrid proof is the default** — CDP for actions and state assertions, CUA Driver ([trycua/cua](https://github.com/trycua/cua)) for real-pixel screenshots and native checks — after a **fresh local rebuild** — see [UI validation with CUA](#ui-validation-with-cua-trycuacua). If CUA cannot be used, explain why and attach equivalent manual proof (PR template checkboxes).
+- Webview-only UI PRs may attach `pnpm proof:check` output and screenshots instead (see [Browser proof](#browser-proof-pnpm-proofcheck)).
+- Other UI / tray / settings / float-bar / visual PRs: **hybrid proof is the default** — CDP for actions and state assertions, CUA Driver ([trycua/cua](https://github.com/trycua/cua)) for real-pixel screenshots and native checks — after a **fresh local rebuild** — see [UI validation with CUA](#ui-validation-with-cua-trycuacua). If CUA cannot be used, explain why and attach equivalent manual proof (PR template checkboxes).
- Before non-trivial merge: thermo-nuclear structure review when the project process requires it.
diff --git a/apps/desktop-tauri/package.json b/apps/desktop-tauri/package.json
index 3bbaacadbad..c6e5efb2926 100644
--- a/apps/desktop-tauri/package.json
+++ b/apps/desktop-tauri/package.json
@@ -6,10 +6,13 @@
"type": "module",
"scripts": {
"dev": "vite",
+ "dev:proof": "vite --mode proof",
"check-locale": "node scripts/check-locale-drift.mjs",
"lint": "oxlint src",
"prebuild": "pnpm run check-locale",
"build": "tsc --noEmit && vite build",
+ "build:proof": "tsc --noEmit && vite build --mode proof",
+ "proof:check": "node scripts/proof-check.mjs",
"preview": "vite preview",
"check:anti-slop": "tsc --noEmit -p tsconfig.anti-slop.json",
"test": "vitest run",
diff --git a/apps/desktop-tauri/proof.html b/apps/desktop-tauri/proof.html
new file mode 100644
index 00000000000..2da0e80b9ce
--- /dev/null
+++ b/apps/desktop-tauri/proof.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ CodexBar Desktop (proof)
+
+
+
+
+
+
diff --git a/apps/desktop-tauri/scripts/proof-check.mjs b/apps/desktop-tauri/scripts/proof-check.mjs
new file mode 100644
index 00000000000..69e6bf1eb7b
--- /dev/null
+++ b/apps/desktop-tauri/scripts/proof-check.mjs
@@ -0,0 +1,459 @@
+#!/usr/bin/env node
+// Browser UI proof over the mocked-IPC proof build (docs/WINDOWS_PROOF.md §5).
+// Starts `vite --mode proof` (or uses --url), drives Chrome/Edge/Chromium over
+// CDP through scripts/proof-scenarios.json, prints PASS/FAIL per scenario and
+// saves screenshots. Node built-ins only: fetch, WebSocket, child_process.
+//
+// pnpm proof:check headless, all scenarios
+// pnpm proof:check -- --headed visible window, pauses on each screenshot
+// pnpm proof:check -- --only tray scenarios whose name contains "tray"
+// pnpm proof:check -- --url an already-served proof build (dev or dist-proof)
+// pnpm proof:check -- --cdp attach to a running browser instead of launching one
+// pnpm proof:check -- --browser browser binary (or env PROOF_BROWSER)
+// pnpm proof:check -- --out screenshot folder (default .proof/screens)
+//
+// Exit codes: 0 all scenarios passed, 1 a scenario failed, 2 setup failed.
+
+import { spawn } from "node:child_process";
+import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { createServer } from "node:net";
+import { dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const appDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const STEP_TIMEOUT_MS = 8_000;
+const CDP_TIMEOUT_MS = 30_000;
+const PROOF_PORT = 1430;
+
+const READY_BY_WINDOW = {
+ main: ".tray-panel-reveal--ready",
+ flyout: ".tray-panel-reveal--ready",
+ settings: ".settings-surface",
+ floatbar: ".floatbar__pill, .floatbar__empty",
+};
+
+function parseArgs(argv) {
+ const opts = { headed: false, only: null, url: null, cdp: null, browser: null, out: null, hold: null };
+ for (let i = 0; i < argv.length; i++) {
+ const arg = argv[i];
+ const value = () => {
+ const next = argv[++i];
+ if (next === undefined) fail(2, `${arg} needs a value`);
+ return next;
+ };
+ if (arg === "--") continue;
+ else if (arg === "--headed") opts.headed = true;
+ else if (arg === "--only") opts.only = value();
+ else if (arg === "--url") opts.url = value().replace(/\/$/, "");
+ else if (arg === "--cdp") opts.cdp = value();
+ else if (arg === "--browser") opts.browser = value();
+ else if (arg === "--out") opts.out = value();
+ else if (arg === "--hold") opts.hold = Number(value());
+ else fail(2, `unknown option ${arg}`);
+ }
+ opts.hold ??= opts.headed ? 1_500 : 0;
+ opts.out = resolve(opts.out ?? join(appDir, ".proof", "screens"));
+ return opts;
+}
+
+// Mirrors rust/src/core/redactor.rs SecretRedactor. Output can carry a
+// caller-supplied CDP URL (remote bridges often take ?token=) or page text.
+const SECRET_PATTERNS = [
+ [/(Bearer\s+)[^\s,;]+/gi, "$1[REDACTED]"],
+ [/((?:cookie|set-cookie)\s*:\s*)[^\r\n]+/gi, "$1[REDACTED]"],
+ [
+ /([?&](?:token|code|device_code|verification_code|authorization_code|client_secret|api_key|access_token|refresh_token)=)[^\s]+/gi,
+ "$1[REDACTED]",
+ ],
+ [
+ /("?(?:api_key|apiKey|token|access_token|refresh_token|id_token|oauth_token|device_code|verification_code|authorization_code|client_secret)"?\s*[:=]\s*")[^"]+"/gi,
+ '$1[REDACTED]"',
+ ],
+ [
+ /(\b(?:api_key|access_token|refresh_token|id_token|oauth_token|device_code|verification_code|authorization_code|client_secret|secret|password)\b\s*[:=]\s*)[^\s,;}\]]+/gi,
+ "$1[REDACTED]",
+ ],
+ [/\b(?:sk|ghp|gho|github_pat|zai|nanogpt|openrouter|fk)-[A-Za-z0-9_-]{8,}\b/gi, "[REDACTED]"],
+ [/(\/\/)[^/@\s]+@/g, "$1[REDACTED]@"],
+];
+
+function redact(text) {
+ return SECRET_PATTERNS.reduce((out, [pattern, replacement]) => out.replace(pattern, replacement), String(text));
+}
+
+function fail(code, message) {
+ console.error(`[proof-check] ${redact(message)}`);
+ process.exit(code);
+}
+
+const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
+
+function findBrowser(explicit) {
+ const candidates = [
+ explicit,
+ process.env.PROOF_BROWSER,
+ ...(process.platform === "win32"
+ ? [
+ `${process.env.ProgramFiles}\\Google\\Chrome\\Application\\chrome.exe`,
+ `${process.env["ProgramFiles(x86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
+ `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
+ `${process.env["ProgramFiles(x86)"]}\\Microsoft\\Edge\\Application\\msedge.exe`,
+ `${process.env.ProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
+ ]
+ : process.platform === "darwin"
+ ? [
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
+ ]
+ : ["/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/microsoft-edge"]),
+ ];
+ return candidates.find((path) => path && existsSync(path)) ?? null;
+}
+
+async function waitForHttp(url, timeoutMs) {
+ const end = Date.now() + timeoutMs;
+ while (Date.now() < end) {
+ try {
+ if ((await fetch(url)).ok) return true;
+ } catch {
+ // not up yet
+ }
+ await sleep(250);
+ }
+ return false;
+}
+
+async function startProofServer() {
+ const vite = join(appDir, "node_modules", "vite", "bin", "vite.js");
+ if (!existsSync(vite)) fail(2, "vite is not installed; run pnpm install");
+ const child = spawn(
+ process.execPath,
+ [vite, "--mode", "proof", "--port", String(PROOF_PORT), "--strictPort", "--host", "127.0.0.1"],
+ { cwd: appDir, stdio: ["ignore", "pipe", "pipe"] },
+ );
+ child.once("error", (error) => fail(2, `proof server failed to start: ${error.message}`));
+ // fail() exits the process directly; do not leave the server behind.
+ process.once("exit", () => child.kill());
+ let log = "";
+ child.stdout.on("data", (chunk) => (log += chunk));
+ child.stderr.on("data", (chunk) => (log += chunk));
+ const base = `http://127.0.0.1:${PROOF_PORT}`;
+ if (!(await waitForHttp(`${base}/proof.html`, 30_000))) {
+ child.kill();
+ fail(2, `proof server did not start on port ${PROOF_PORT}:\n${log}`);
+ }
+ return { base, stop: () => child.kill() };
+}
+
+async function launchBrowser(opts) {
+ const binary = findBrowser(opts.browser);
+ if (!binary) fail(2, "no Chrome, Edge or Chromium found; pass --browser or set PROOF_BROWSER");
+ // Reused across runs: an isolated profile with no logins, never the user's.
+ const profile = join(appDir, ".proof", "browser-profile");
+ mkdirSync(profile, { recursive: true });
+ // A fixed free port rather than port 0 + DevToolsActivePort: that file
+ // outlives the browser, so a stale copy could point at an older instance.
+ const port = await freePort();
+ const args = [
+ `--user-data-dir=${profile}`,
+ `--remote-debugging-port=${port}`,
+ "--no-first-run",
+ "--no-default-browser-check",
+ // A headed window behind other windows would otherwise stop painting,
+ // and the tray panel waits on requestAnimationFrame before it reveals.
+ "--disable-backgrounding-occluded-windows",
+ "--disable-renderer-backgrounding",
+ "--disable-background-timer-throttling",
+ "--window-size=1000,1080",
+ ...(opts.headed ? [] : ["--headless=new", "--disable-gpu"]),
+ "about:blank",
+ ];
+ // A binary that cannot run makes spawn() throw (Windows: EFTYPE) or emit
+ // "error" rather than "exit"; both must reach the setup-error path.
+ let child;
+ try {
+ child = spawn(binary, args, { stdio: "ignore" });
+ } catch (error) {
+ fail(2, `could not launch browser ${binary}: ${error.message}`);
+ }
+ let exited = false;
+ child.once("exit", () => (exited = true));
+ let launchError = null;
+ child.once("error", (error) => (launchError = error));
+ const end = Date.now() + 20_000;
+ while (Date.now() < end) {
+ await sleep(200);
+ if (launchError) fail(2, `could not launch browser ${binary}: ${launchError.message}`);
+ try {
+ const version = await (await fetch(`http://127.0.0.1:${port}/json/version`)).json();
+ if (version.webSocketDebuggerUrl) return { ws: version.webSocketDebuggerUrl, child };
+ } catch {
+ // not listening yet
+ }
+ if (exited) {
+ fail(
+ 2,
+ `browser exited before opening its debugging port. Another browser may still be using ${profile}; close it and retry.`,
+ );
+ }
+ }
+ child.kill();
+ fail(2, `browser did not open a debugging port: ${binary}`);
+}
+
+function freePort() {
+ return new Promise((done, reject) => {
+ const server = createServer();
+ server.once("error", reject);
+ server.listen(0, "127.0.0.1", () => {
+ const { port } = server.address();
+ server.close(() => done(port));
+ });
+ });
+}
+
+class Cdp {
+ constructor(socket) {
+ this.socket = socket;
+ this.nextId = 0;
+ this.pending = new Map();
+ this.listeners = [];
+ socket.addEventListener("message", (event) => {
+ const msg = JSON.parse(event.data);
+ if (msg.id !== undefined && this.pending.has(msg.id)) {
+ const { resolve: done, reject } = this.pending.get(msg.id);
+ this.pending.delete(msg.id);
+ if (msg.error) reject(new Error(`${msg.error.message} (${msg.error.code})`));
+ else done(msg.result);
+ } else if (msg.method) {
+ for (const listener of this.listeners) listener(msg);
+ }
+ });
+ socket.addEventListener("close", () => {
+ for (const { reject } of this.pending.values()) reject(new Error("browser connection closed"));
+ this.pending.clear();
+ });
+ }
+
+ static async connect(url) {
+ const socket = new WebSocket(url);
+ await new Promise((done, reject) => {
+ socket.addEventListener("open", done, { once: true });
+ socket.addEventListener("error", () => reject(new Error(`cannot connect to ${url}`)), { once: true });
+ });
+ return new Cdp(socket);
+ }
+
+ send(method, params = {}, sessionId) {
+ // A closed socket drops sends silently; fail now rather than at the timeout.
+ if (this.socket.readyState !== WebSocket.OPEN) {
+ return Promise.reject(new Error(`CDP ${method}: browser connection closed`));
+ }
+ const id = ++this.nextId;
+ this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
+ return new Promise((done, reject) => {
+ const timer = setTimeout(() => {
+ this.pending.delete(id);
+ reject(new Error(`CDP ${method} timed out after ${CDP_TIMEOUT_MS} ms`));
+ }, CDP_TIMEOUT_MS);
+ this.pending.set(id, {
+ resolve: (value) => {
+ clearTimeout(timer);
+ done(value);
+ },
+ reject: (error) => {
+ clearTimeout(timer);
+ reject(error);
+ },
+ });
+ });
+ }
+
+ on(listener) {
+ this.listeners.push(listener);
+ }
+
+ close() {
+ this.socket.close();
+ }
+}
+
+class Page {
+ constructor(cdp, sessionId) {
+ this.cdp = cdp;
+ this.sessionId = sessionId;
+ this.problems = [];
+ cdp.on((msg) => {
+ if (msg.sessionId !== sessionId) return;
+ if (msg.method === "Runtime.consoleAPICalled" && ["error", "warning"].includes(msg.params.type)) {
+ const text = msg.params.args.map((arg) => arg.value ?? arg.description ?? "").join(" ");
+ this.problems.push(`console.${msg.params.type}: ${text.slice(0, 300)}`);
+ } else if (msg.method === "Runtime.exceptionThrown") {
+ const detail = msg.params.exceptionDetails;
+ this.problems.push(`exception: ${(detail.exception?.description ?? detail.text).slice(0, 300)}`);
+ }
+ });
+ }
+
+ send(method, params) {
+ return this.cdp.send(method, params, this.sessionId);
+ }
+
+ async eval(expression) {
+ const result = await this.send("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true });
+ if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description ?? "evaluate failed");
+ return result.result.value;
+ }
+
+ async poll(expression, timeoutMs = STEP_TIMEOUT_MS) {
+ const end = Date.now() + timeoutMs;
+ for (;;) {
+ if (await this.eval(expression)) return true;
+ if (Date.now() > end) return false;
+ await sleep(150);
+ }
+ }
+}
+
+const js = (value) => JSON.stringify(value);
+const asList = (value) => (Array.isArray(value) ? value : [value]);
+
+// textContent rather than innerText: the app splits emails across elements,
+// which innerText reads back with layout spaces in between.
+const hasText = (text) => `document.body.textContent.includes(${js(text)})`;
+
+async function runStep(page, step, ctx) {
+ if (step.expectText !== undefined) {
+ for (const text of asList(step.expectText)) {
+ if (!(await page.poll(hasText(text)))) throw new Error(`text not found: ${js(text)}`);
+ }
+ } else if (step.expectNoText !== undefined) {
+ for (const text of asList(step.expectNoText)) {
+ if (!(await page.poll(`!${hasText(text)}`))) throw new Error(`text still present: ${js(text)}`);
+ }
+ } else if (step.expectSelector !== undefined) {
+ if (!(await page.poll(`!!document.querySelector(${js(step.expectSelector)})`))) {
+ throw new Error(`selector not found: ${step.expectSelector}`);
+ }
+ } else if (step.expectTheme !== undefined) {
+ const theme = await page.eval("document.documentElement.dataset.theme");
+ if (theme !== step.expectTheme) throw new Error(`theme is ${js(theme)}, expected ${js(step.expectTheme)}`);
+ } else if (step.click !== undefined) {
+ // `near` picks, among same-label matches, the one whose closest ancestor
+ // containing that text is nearest (e.g. the Switch button of one account row).
+ const { selector, label, near = null } = step.click;
+ const point = await page.eval(`(() => {
+ const near = ${js(near)};
+ const depth = (e) => {
+ let d = 0;
+ for (let n = e; n; n = n.parentElement, d++) if (n.textContent.includes(near)) return d;
+ return Infinity;
+ };
+ const matches = [...document.querySelectorAll(${js(selector)})].filter(
+ (e) => (e.getAttribute("aria-label") || e.textContent || "").trim() === ${js(label)});
+ const el = near === null
+ ? matches[0]
+ : matches.filter((e) => depth(e) < Infinity).sort((a, b) => depth(a) - depth(b))[0];
+ if (!el) return null;
+ el.scrollIntoView({ block: "center" });
+ const r = el.getBoundingClientRect();
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
+ })()`);
+ if (!point) throw new Error(`nothing to click: ${selector} labelled ${js(label)}`);
+ for (const type of ["mouseMoved", "mousePressed", "mouseReleased"]) {
+ await page.send("Input.dispatchMouseEvent", { type, ...point, button: "left", clickCount: 1 });
+ }
+ } else if (step.expectCall !== undefined) {
+ const { cmd, args = {} } = step.expectCall;
+ const found = await page.poll(`(window.__CODEXBAR_PROOF__?.calls() ?? []).some((call) =>
+ call.cmd === ${js(cmd)} && Object.entries(${js(args)}).every(([k, v]) => JSON.stringify(call.args[k]) === JSON.stringify(v)))`);
+ if (!found) throw new Error(`backend call not made: ${cmd} ${js(args)}`);
+ } else if (step.screenshot !== undefined) {
+ await sleep(300);
+ let clip;
+ if (step.clip === "panel") {
+ // The browser window cannot resize, so crop to the height the tray
+ // panel committed for its window.
+ const height = await page.eval(
+ `parseFloat(document.querySelector(".menu-surface--tray")?.style.maxHeight || "0")`,
+ );
+ if (height > 0) clip = { x: 0, y: 0, width: ctx.width, height: Math.ceil(height), scale: 1 };
+ }
+ const shot = await page.send("Page.captureScreenshot", { format: "png", ...(clip ? { clip } : {}) });
+ const file = join(ctx.out, `${step.screenshot}.png`);
+ writeFileSync(file, Buffer.from(shot.data, "base64"));
+ ctx.screenshots.push(file);
+ if (ctx.hold) await sleep(ctx.hold);
+ } else if (step.wait !== undefined) {
+ await sleep(step.wait);
+ } else {
+ throw new Error(`unknown step ${js(step)}`);
+ }
+}
+
+async function runScenario(page, base, scenario, opts) {
+ const [width, height] = scenario.size;
+ const window = new URLSearchParams(scenario.query).get("window") ?? "main";
+ const ready = READY_BY_WINDOW[window];
+ if (!ready) throw new Error(`unknown window ${window}`);
+ page.problems = [];
+ await page.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false });
+ await page.send("Page.navigate", { url: `${base}/proof.html?${scenario.query}` });
+ if (!(await page.poll(`!!document.querySelector(${js(ready)})`, 20_000))) {
+ throw new Error(`surface never became ready (${ready})`);
+ }
+ const ctx = { out: opts.out, hold: opts.hold, width, screenshots: [] };
+ for (const step of scenario.steps) await runStep(page, step, ctx);
+ const unhandled = await page.eval("window.__CODEXBAR_PROOF__?.unhandledCommands() ?? []");
+ if (unhandled.length) throw new Error(`unhandled backend commands: ${unhandled.join(", ")}`);
+ const problems = page.problems.filter((line) => !/Download the React DevTools/.test(line));
+ if (problems.length) throw new Error(problems.join("\n "));
+ return ctx.screenshots;
+}
+
+async function main() {
+ const opts = parseArgs(process.argv.slice(2));
+ const { scenarios } = JSON.parse(readFileSync(join(appDir, "scripts", "proof-scenarios.json"), "utf8"));
+ const selected = scenarios.filter((s) => !opts.only || s.name.includes(opts.only));
+ if (!selected.length) fail(2, `no scenario matches ${js(opts.only)}`);
+ mkdirSync(opts.out, { recursive: true });
+
+ const server = opts.url ? null : await startProofServer();
+ const base = opts.url ?? server.base;
+ const browser = opts.cdp ? null : await launchBrowser(opts);
+ let cdp;
+ let targetId;
+ let failed = 0;
+ try {
+ cdp = await Cdp.connect(opts.cdp ?? browser.ws);
+ ({ targetId } = await cdp.send("Target.createTarget", { url: "about:blank" }));
+ const { sessionId } = await cdp.send("Target.attachToTarget", { targetId, flatten: true });
+ const page = new Page(cdp, sessionId);
+ await page.send("Runtime.enable");
+ await page.send("Page.enable");
+ if (opts.headed) await page.send("Page.bringToFront");
+
+ for (const scenario of selected) {
+ try {
+ const shots = await runScenario(page, base, scenario, opts);
+ console.log(`PASS ${scenario.name}${shots.length ? ` (${shots.length} screenshot${shots.length > 1 ? "s" : ""})` : ""}`);
+ } catch (error) {
+ failed++;
+ console.log(`FAIL ${scenario.name}\n ${redact(error.message)}`);
+ }
+ }
+ } finally {
+ if (cdp && targetId) await cdp.send("Target.closeTarget", { targetId }).catch(() => {});
+ if (browser) {
+ if (cdp) await cdp.send("Browser.close").catch(() => browser.child.kill());
+ else browser.child.kill();
+ }
+ cdp?.close();
+ server?.stop();
+ }
+ console.log(`\n${selected.length - failed}/${selected.length} scenarios passed. Screenshots: ${opts.out}`);
+ process.exit(failed ? 1 : 0);
+}
+
+main().catch((error) => fail(2, error.stack ?? String(error)));
diff --git a/apps/desktop-tauri/scripts/proof-scenarios.json b/apps/desktop-tauri/scripts/proof-scenarios.json
new file mode 100644
index 00000000000..a2a2c5ff820
--- /dev/null
+++ b/apps/desktop-tauri/scripts/proof-scenarios.json
@@ -0,0 +1,98 @@
+{
+ "scenarios": [
+ {
+ "name": "tray-default",
+ "query": "window=main&fixture=default",
+ "size": [310, 1032],
+ "steps": [
+ { "expectText": ["Usage & Spend", "dev@example.com", "claude.user@example.com"] },
+ { "screenshot": "tray-default", "clip": "panel" },
+ { "click": { "selector": ".provider-grid__item", "label": "Claude" } },
+ { "expectNoText": "dev@example.com" },
+ { "expectText": ["claude.user@example.com", "Switch Account..."] },
+ { "screenshot": "tray-claude-detail", "clip": "panel" }
+ ]
+ },
+ {
+ "name": "tray-errors",
+ "query": "window=main&fixture=errors",
+ "size": [310, 1032],
+ "steps": [
+ { "expectText": ["Your Claude session has expired.", "cursor.user@example.com"] },
+ { "screenshot": "tray-errors", "clip": "panel" }
+ ]
+ },
+ {
+ "name": "tray-empty",
+ "query": "window=main&fixture=empty",
+ "size": [310, 1032],
+ "steps": [
+ { "expectText": "No providers configured" },
+ { "expectNoText": "@example.com" },
+ { "screenshot": "tray-empty", "clip": "panel" }
+ ]
+ },
+ {
+ "name": "flyout-default",
+ "query": "window=flyout&fixture=default",
+ "size": [310, 1032],
+ "steps": [
+ { "expectText": "dev@example.com" },
+ { "screenshot": "flyout-default", "clip": "panel" }
+ ]
+ },
+ {
+ "name": "settings-general-dark",
+ "query": "window=settings&tab=general&theme=dark",
+ "size": [880, 620],
+ "steps": [
+ { "expectTheme": "dark" },
+ { "expectText": ["Refresh interval", "Start at login"] },
+ { "screenshot": "settings-general-dark" },
+ { "click": { "selector": "button", "label": "Menu" } },
+ { "expectText": "Panel scale" },
+ { "screenshot": "settings-menu-after-click" }
+ ]
+ },
+ {
+ "name": "settings-general-light",
+ "query": "window=settings&tab=general&theme=light",
+ "size": [880, 620],
+ "steps": [
+ { "expectTheme": "light" },
+ { "expectText": "Refresh interval" },
+ { "screenshot": "settings-general-light" }
+ ]
+ },
+ {
+ "name": "settings-providers",
+ "query": "window=settings&tab=providers&theme=dark",
+ "size": [880, 620],
+ "steps": [
+ { "expectText": ["Codex", "Claude"] },
+ { "screenshot": "settings-providers" }
+ ]
+ },
+ {
+ "name": "settings-codex-two-accounts",
+ "query": "window=settings&tab=providers:codex&fixture=two-codex-accounts&theme=dark",
+ "size": [880, 620],
+ "steps": [
+ { "expectText": ["work@example.com", "personal@example.com"] },
+ { "screenshot": "settings-codex-two-accounts" },
+ { "click": { "selector": "button", "label": "Switch", "near": "personal@example.com" } },
+ { "expectCall": { "cmd": "codex_account_switch", "args": { "id": "proof-codex-personal" } } }
+ ]
+ },
+ {
+ "name": "floatbar",
+ "query": "window=floatbar",
+ "size": [520, 120],
+ "steps": [
+ { "expectSelector": ".floatbar__pill" },
+ { "expectText": "%" },
+ { "screenshot": "floatbar" }
+ ]
+ }
+ ]
+}
diff --git a/apps/desktop-tauri/src/proof/coverage.test.ts b/apps/desktop-tauri/src/proof/coverage.test.ts
new file mode 100644
index 00000000000..4b20e97bfc8
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/coverage.test.ts
@@ -0,0 +1,67 @@
+import { readdirSync, readFileSync } from "node:fs";
+import { describe, expect, it, vi } from "vitest";
+import { buildProofScenario } from "./fixtures/scenarios";
+import { INERT_ACTIONS, createProofRouter } from "./router";
+
+const SRC = `${import.meta.dirname}/..`;
+const INVOKE_CALL = /\binvoke\s*(?:<[^>]*>)?\(\s*["']([A-Za-z0-9_]+)["']/g;
+
+function productionSources(dir: string): string[] {
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
+ const path = `${dir}/${entry.name}`;
+ if (entry.isDirectory()) return entry.name === "proof" ? [] : productionSources(path);
+ return /\.tsx?$/.test(entry.name) && !/\.(test|spec)\.tsx?$/.test(entry.name) ? [path] : [];
+ });
+}
+
+function invokedCommands(): Map {
+ const commands = new Map();
+ for (const file of productionSources(SRC)) {
+ for (const match of readFileSync(file, "utf8").matchAll(INVOKE_CALL)) {
+ if (!commands.has(match[1])) commands.set(match[1], file.slice(SRC.length + 1));
+ }
+ }
+ return commands;
+}
+
+describe("proof router coverage", () => {
+ it("finds the frontend's invoke calls", () => {
+ expect(invokedCommands().size).toBeGreaterThan(50);
+ });
+
+ // A new backend command must either get a fixture handler or be declared an
+ // inert action, so the proof build never silently answers it with null.
+ it("handles or explicitly declares every command the frontend invokes", () => {
+ const known = new Set([...handledCommands(), ...INERT_ACTIONS]);
+ const missing = [...invokedCommands()]
+ .filter(([command]) => !known.has(command))
+ .map(([command, file]) => `${command} (${file})`);
+ expect(missing).toEqual([]);
+ });
+
+ it("declares no inert action that also has a handler or is no longer invoked", () => {
+ const handled = new Set(handledCommands());
+ const invoked = invokedCommands();
+ expect(INERT_ACTIONS.filter((command) => handled.has(command))).toEqual([]);
+ expect(INERT_ACTIONS.filter((command) => !invoked.has(command))).toEqual([]);
+ });
+
+ it("records inert actions without warning", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const router = createProofRouter(buildProofScenario("default", new Date()), {
+ window: "main",
+ tab: "general",
+ });
+ expect(router.handle("codex_account_switch", { id: "work" })).toBeNull();
+ expect(router.calls()).toEqual([{ cmd: "codex_account_switch", args: { id: "work" } }]);
+ expect(warn).not.toHaveBeenCalled();
+ warn.mockRestore();
+ });
+});
+
+function handledCommands(): readonly string[] {
+ return createProofRouter(buildProofScenario("default", new Date()), {
+ window: "main",
+ tab: "general",
+ }).handledCommands;
+}
diff --git a/apps/desktop-tauri/src/proof/fixtures/bridgeData.ts b/apps/desktop-tauri/src/proof/fixtures/bridgeData.ts
new file mode 100644
index 00000000000..3cb5341aabc
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/fixtures/bridgeData.ts
@@ -0,0 +1,228 @@
+import type {
+ AppInfoBridge,
+ CostCoverageCounts,
+ CurrencyRatesSnapshot,
+ HooksConfigSnapshot,
+ ProviderChartData,
+ ProviderDetail,
+ ProviderSummary,
+ ProviderUsageSnapshot,
+ SpendContract,
+ UpdateStatePayload,
+ UsageSpendRow,
+ UsageSpendSummary,
+} from "../../types/bridge";
+import type { LocalSpendFixture, ProofScenario } from "./scenarios";
+import { dayKey } from "./common";
+
+const EMPTY_COVERAGE: CostCoverageCounts = { priced: 0, unpriced: 0, unmetered: 0, estimated: 0 };
+
+export function proofUpdateState(): UpdateStatePayload {
+ return {
+ status: "idle",
+ version: null,
+ error: null,
+ progress: null,
+ releaseUrl: null,
+ canDownload: false,
+ canApply: false,
+ lastCheckedAt: null,
+ };
+}
+
+export function proofAppInfo(version: string): AppInfoBridge {
+ return {
+ name: "CodexBar",
+ version,
+ buildNumber: "proof",
+ buildDate: "2026-01-01",
+ updateChannel: "stable",
+ tagline: "May your tokens never run out.",
+ };
+}
+
+export function proofCurrencyRates(preferredCode: string): CurrencyRatesSnapshot {
+ return {
+ rates: { USD: 1, EUR: 0.92, GBP: 0.79, JPY: 151.4 },
+ supportedCodes: ["USD", "EUR", "GBP", "JPY"],
+ preferredCode,
+ };
+}
+
+export function proofHooksConfig(): HooksConfigSnapshot {
+ return {
+ enabled: false,
+ settingsEnabled: false,
+ fileEnabled: true,
+ rules: [],
+ path: "C:\\Users\\proof\\AppData\\Roaming\\CodexBar\\hooks.json",
+ error: null,
+ maxRules: 32,
+ maxArguments: 16,
+ };
+}
+
+export function providerSummaries(scenario: ProofScenario): ProviderSummary[] {
+ const enabled = new Set(scenario.settings.enabledProviders);
+ return scenario.catalog.map((entry, order) => ({
+ id: entry.id,
+ displayName: entry.displayName,
+ enabled: enabled.has(entry.id),
+ order,
+ }));
+}
+
+export function providerDetail(
+ scenario: ProofScenario,
+ providerId: string,
+): ProviderDetail {
+ const entry = scenario.catalog.find((candidate) => candidate.id === providerId);
+ const snapshot: ProviderUsageSnapshot | undefined = scenario.providers.find(
+ (candidate) => candidate.providerId === providerId,
+ );
+ return {
+ id: providerId,
+ displayName: entry?.displayName ?? snapshot?.displayName ?? providerId,
+ enabled: scenario.settings.enabledProviders.includes(providerId),
+ autoResumeAfterQuotaReset: false,
+ autoResumeSupported: false,
+ optionalDetailsSupported: false,
+ optionalDetailsEnabled: false,
+ email: snapshot?.accountEmail ?? null,
+ plan: snapshot?.planName ?? null,
+ authType: snapshot ? snapshot.sourceLabel : null,
+ sourceLabel: snapshot?.sourceLabel ?? null,
+ organization: snapshot?.accountOrganization ?? null,
+ lastUpdated: snapshot?.updatedAt ?? null,
+ session: snapshot?.primary ?? null,
+ weekly: snapshot?.secondary ?? null,
+ primaryLabel: snapshot?.primaryLabel ?? null,
+ secondaryLabel: snapshot?.secondaryLabel ?? null,
+ modelSpecific: snapshot?.modelSpecific ?? null,
+ tertiary: snapshot?.tertiary ?? null,
+ extraRateWindows: snapshot?.extraRateWindows ?? [],
+ usageItems: [],
+ hiddenUsageItemIds: [],
+ cost: snapshot?.cost ?? null,
+ pace: snapshot?.pace ?? null,
+ lastError: snapshot?.error ?? null,
+ errorState: snapshot?.errorState ?? null,
+ dashboardUrl: `https://${providerId}.example.com/usage`,
+ statusPageUrl: null,
+ buyCreditsUrl: null,
+ hasSnapshot: snapshot !== undefined,
+ usageSource: "auto",
+ cookieSource: entry?.cookieDomain ? "auto" : null,
+ region: null,
+ };
+}
+
+function localSpend(scenario: ProofScenario, providerId: string): LocalSpendFixture | undefined {
+ if (!scenario.settings.enabledProviders.includes(providerId)) return undefined;
+ return scenario.localSpend.find((candidate) => candidate.providerId === providerId);
+}
+
+export function providerChartData(
+ scenario: ProofScenario,
+ providerId: string,
+ now: Date,
+): ProviderChartData {
+ const spend = localSpend(scenario, providerId);
+ const days = spend ? Array.from({ length: 14 }, (_, index) => 13 - index) : [];
+ // A fixed wave keeps the bars varied but identical across reloads.
+ const factor = (daysAgo: number) => 0.55 + ((daysAgo * 37) % 10) / 10;
+ return {
+ providerId,
+ costHistory: days.map((daysAgo) => ({
+ date: dayKey(now, daysAgo),
+ value: spend ? Number((spend.daily * factor(daysAgo)).toFixed(2)) : null,
+ })),
+ creditsHistory: [],
+ usageBreakdown: [],
+ localUsage: spend
+ ? {
+ todayCost: spend.daily,
+ thirtyDayCost: Number((spend.daily * 30).toFixed(2)),
+ thirtyDayTokens: spend.tokensPerDay * 30,
+ periodCost: Number((spend.daily * 30).toFixed(2)),
+ periodTokens: spend.tokensPerDay * 30,
+ reportingPeriod: scenario.settings.costReportingPeriod ?? "rolling:30",
+ latestTokens: spend.tokensPerDay,
+ topModel: spend.model,
+ estimateNote: "Estimated from local logs",
+ tokenCostUpdatedAtMs: now.getTime(),
+ }
+ : null,
+ tokensHistory: days.map((daysAgo) => ({
+ date: dayKey(now, daysAgo),
+ tokens: spend ? Math.round(spend.tokensPerDay * factor(daysAgo)) : 0,
+ })),
+ tokensIncomplete: false,
+ };
+}
+
+export function spendContract(providerId: string, reportingPeriod: string): SpendContract {
+ return {
+ providerId,
+ historyDays: 30,
+ reportingPeriod,
+ knownCostUsd: null,
+ knownZero: false,
+ provenance: "listPriceEstimate",
+ priceCoverage: EMPTY_COVERAGE,
+ priceCoverageRatio: null,
+ historyCoverageEstablished: true,
+ tokenMix: {
+ inputTokens: null,
+ outputTokens: null,
+ cacheReadTokens: null,
+ cacheCreationTokens: null,
+ reasoningTokens: null,
+ },
+ conversationCount: 0,
+ models: [],
+ projects: [],
+ conversations: [],
+ daily: [],
+ hourlyActivity: [],
+ projectSourceStatus: null,
+ customPricingActive: false,
+ imports: [],
+ };
+}
+
+export function usageSpendSummary(scenario: ProofScenario, now: Date): UsageSpendSummary {
+ const reportingPeriod = scenario.settings.spendDashboardPeriod ?? "rolling:30";
+ const rows: UsageSpendRow[] = scenario.catalog.flatMap((entry) => {
+ const spend = localSpend(scenario, entry.id);
+ if (!spend) return [];
+ return [
+ {
+ providerId: entry.id,
+ displayName: entry.displayName,
+ sevenDay: Number((spend.daily * 7).toFixed(2)),
+ thirtyDay: Number((spend.daily * 30).toFixed(2)),
+ sevenDayTokens: spend.tokensPerDay * 7,
+ thirtyDayTokens: spend.tokensPerDay * 30,
+ periodCost: Number((spend.daily * 30).toFixed(2)),
+ periodTokens: spend.tokensPerDay * 30,
+ currency: "USD",
+ source: "local",
+ includedInOverview: true,
+ daily: Array.from({ length: 7 }, (_, index) => ({
+ day: dayKey(now, 6 - index),
+ amount: spend.daily,
+ })),
+ },
+ ];
+ });
+ return {
+ rows,
+ contract: spendContract("all", reportingPeriod),
+ reportingPeriod,
+ reportingDay: dayKey(now, 0),
+ dashboardTimezone: "UTC",
+ costTrackingEnabled: scenario.settings.costUsageEnabled ?? true,
+ tokenActivity: [],
+ };
+}
diff --git a/apps/desktop-tauri/src/proof/fixtures/common.ts b/apps/desktop-tauri/src/proof/fixtures/common.ts
new file mode 100644
index 00000000000..3cfeaa00c52
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/fixtures/common.ts
@@ -0,0 +1,218 @@
+import type {
+ ProviderCatalogEntry,
+ ProviderUsageSnapshot,
+ RateWindowSnapshot,
+ SettingsSnapshot,
+} from "../../types/bridge";
+
+const MINUTE_MS = 60_000;
+
+export function isoAfter(now: Date, minutes: number): string {
+ return new Date(now.getTime() + minutes * MINUTE_MS).toISOString();
+}
+
+export function isoBefore(now: Date, minutes: number): string {
+ return isoAfter(now, -minutes);
+}
+
+/** Local `YYYY-MM-DD` for the day `daysAgo` before `now`. */
+export function dayKey(now: Date, daysAgo: number): string {
+ const day = new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysAgo);
+ const month = String(day.getMonth() + 1).padStart(2, "0");
+ const date = String(day.getDate()).padStart(2, "0");
+ return `${day.getFullYear()}-${month}-${date}`;
+}
+
+export function rateWindow(
+ now: Date,
+ usedPercent: number,
+ windowMinutes: number | null,
+ resetsInMinutes: number | null,
+): RateWindowSnapshot {
+ return {
+ usedPercent,
+ remainingPercent: Math.max(0, 100 - usedPercent),
+ windowMinutes,
+ resetsAt: resetsInMinutes === null ? null : isoAfter(now, resetsInMinutes),
+ resetDescription: null,
+ isExhausted: usedPercent >= 100,
+ reservePercent: null,
+ reserveDescription: null,
+ };
+}
+
+export interface SnapshotInput {
+ providerId: string;
+ displayName: string;
+ primary: RateWindowSnapshot;
+ primaryLabel: string;
+ updatedAt: string;
+}
+
+export function providerSnapshot(
+ input: SnapshotInput,
+ overrides: Partial = {},
+): ProviderUsageSnapshot {
+ return {
+ providerId: input.providerId,
+ displayName: input.displayName,
+ primary: input.primary,
+ selectedMetric: input.primary,
+ primaryLabel: input.primaryLabel,
+ secondary: null,
+ modelSpecific: null,
+ tertiary: null,
+ extraRateWindows: [],
+ cost: null,
+ planName: null,
+ accountEmail: null,
+ sourceLabel: "auto",
+ updatedAt: input.updatedAt,
+ error: null,
+ errorState: "ready",
+ pace: null,
+ accountOrganization: null,
+ trayStatusLabel: null,
+ fetchDurationMs: 420,
+ ...overrides,
+ };
+}
+
+/** A subset of the shipped catalog: enough for sidebars without listing every provider. */
+export const PROOF_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
+ { id: "codex", displayName: "Codex", cookieDomain: "chatgpt.com", cliAliases: ["openai"] },
+ { id: "claude", displayName: "Claude", cookieDomain: "claude.ai" },
+ { id: "cursor", displayName: "Cursor", cookieDomain: "cursor.com" },
+ { id: "gemini", displayName: "Gemini", cookieDomain: null },
+ { id: "copilot", displayName: "Copilot", cookieDomain: null },
+ { id: "openrouter", displayName: "OpenRouter", cookieDomain: null },
+ { id: "factory", displayName: "Factory", cookieDomain: "app.factory.ai" },
+ { id: "kiro", displayName: "Kiro", cookieDomain: null },
+ { id: "zai", displayName: "z.ai", cookieDomain: null },
+ { id: "mistral", displayName: "Mistral", cookieDomain: null },
+];
+
+export function catalogOrder(): string[] {
+ return PROOF_PROVIDER_CATALOG.map((entry) => entry.id);
+}
+
+export function baseSettings(overrides: Partial = {}): SettingsSnapshot {
+ return {
+ preferredCurrencyCode: "AUTO",
+ enabledProviders: [],
+ providerOrder: catalogOrder(),
+ refreshIntervalSecs: 300,
+ adaptiveRefresh: false,
+ refreshAllProvidersOnMenuOpen: false,
+ statusChecksEnabled: true,
+ lowPowerMode: false,
+ lowPowerModePreference: "off",
+ startAtLogin: false,
+ startMinimized: false,
+ showNotifications: true,
+ soundEnabled: true,
+ notificationSoundTheme: "windows",
+ notificationSoundPaths: {
+ predictiveWarning: null,
+ highUsage: null,
+ criticalUsage: null,
+ exhausted: null,
+ statusIssue: null,
+ sessionDepleted: null,
+ sessionRestored: null,
+ },
+ highUsageThreshold: 70,
+ criticalUsageThreshold: 90,
+ providerQuotaWarnings: {},
+ predictivePaceWarningEnabled: false,
+ limitResetNotificationsEnabled: false,
+ credentialExpiryNotificationsEnabled: false,
+ sessionQuotaNotificationsEnabled: true,
+ showPace: true,
+ trayIconMode: "single",
+ stackedTrayTopProvider: null,
+ stackedTrayBottomProvider: null,
+ switcherShowsIcons: true,
+ menuBarShowsHighestUsage: false,
+ menuBarShowsPercent: false,
+ menuBarIconStyle: "bars",
+ mergedOverviewSelectedProviders: [],
+ quotaWarningMarkersVisible: true,
+ highlightMenuCardsOnHover: true,
+ workdayTickStyle: "subtle",
+ menuBarColorPace: false,
+ menuBarColorByProvider: false,
+ menuBarPaceReservePositive: false,
+ providersSortedAlphabetically: false,
+ providersEnabledOnly: false,
+ showAsUsed: true,
+ showAllTokenAccountsInMenu: false,
+ enableAnimations: false,
+ resetTimeRelative: true,
+ showResetWhenExhausted: false,
+ menuBarDisplayMode: "detailed",
+ overviewLayout: "detailed",
+ hidePersonalInfo: false,
+ updateChannel: "stable",
+ autoDownloadUpdates: false,
+ installUpdatesOnQuit: false,
+ globalShortcut: "Ctrl+Shift+U",
+ switcherShortcuts: {},
+ codexCustomSessionsDirs: [],
+ antigravityAdditionalProfileHomes: [],
+ agentSessionsEnabled: false,
+ stayAwakeEnabled: false,
+ agentSessionSshHosts: [],
+ hooksEnabled: false,
+ httpProxyEnabled: false,
+ httpProxyUrl: "",
+ httpProxyUsername: "",
+ httpProxyPassword: "",
+ uiLanguage: "english",
+ uiLanguageFollowsSystem: false,
+ theme: "dark",
+ windowScalePercent: 100,
+ trayScalePercent: 100,
+ trayPanelAlwaysOnTop: false,
+ powertoysStatusPipeEnabled: false,
+ claudeAvoidKeychainPrompts: false,
+ claudeSwapEnabled: false,
+ claudeSwapExecutablePath: "",
+ codexSparkUsageVisible: true,
+ codexOpenaiWebExtras: false,
+ codexHistoricalTracking: true,
+ codexExternalOauthSourcesAllowed: false,
+ codexLocalSessionCostLedgerEnabled: true,
+ codexOpenaiWebBatterySaverEnabled: false,
+ openAiLiveUsageEnabled: true,
+ openAiModelRateLimits: [],
+ disableKeychainAccess: false,
+ wayfinderGatewayUrl: "",
+ providerMetrics: {},
+ floatBarEnabled: true,
+ floatBarOpacity: 90,
+ floatBarScale: 100,
+ floatBarOrientation: "horizontal",
+ floatBarStyle: "floating",
+ floatBarClickThrough: false,
+ floatBarProviderIds: [],
+ floatBarDarkText: false,
+ floatBarShowResetInline: true,
+ floatBarShowCost: false,
+ promoteTrayIcon: false,
+ claudeDailyRoutinesUsageVisible: true,
+ claudeAllowReadingClaudeCodeCredentials: true,
+ alibabaTokenPlanRegion: "intl",
+ copilotSeatCreditEntitlement: null,
+ weeklyProgressWorkDays: null,
+ costSummaryDisplayStyle: "compact",
+ openCodexUsageLogsEnabled: false,
+ hideNativeCodexCostWhenOpenCodexPresent: false,
+ costReportingPeriod: "rolling:30",
+ costUsageEnabled: true,
+ spendDashboardPeriod: "rolling:30",
+ spendDashboardHiddenSourceIds: [],
+ providerAccentColors: {},
+ ...overrides,
+ };
+}
diff --git a/apps/desktop-tauri/src/proof/fixtures/scenarios.ts b/apps/desktop-tauri/src/proof/fixtures/scenarios.ts
new file mode 100644
index 00000000000..5316166a55d
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/fixtures/scenarios.ts
@@ -0,0 +1,339 @@
+import type {
+ ClaudeAccount,
+ CodexAccount,
+ CodexAccountsStateBridge,
+ CodexAccountUsageSnapshot,
+ ProviderCatalogEntry,
+ ProviderUsageSnapshot,
+ SettingsSnapshot,
+} from "../../types/bridge";
+import {
+ PROOF_PROVIDER_CATALOG,
+ baseSettings,
+ isoAfter,
+ isoBefore,
+ providerSnapshot,
+ rateWindow,
+} from "./common";
+
+export const PROOF_SCENARIO_NAMES = [
+ "default",
+ "two-codex-accounts",
+ "errors",
+ "empty",
+] as const;
+
+export type ProofScenarioName = (typeof PROOF_SCENARIO_NAMES)[number];
+
+/** Everything the mocked bridge serves for one scenario. All data is synthetic. */
+export interface ProofScenario {
+ name: ProofScenarioName;
+ settings: SettingsSnapshot;
+ catalog: ProviderCatalogEntry[];
+ providers: ProviderUsageSnapshot[];
+ codexAccounts: CodexAccountsStateBridge;
+ claudeAccounts: ClaudeAccount[];
+ /** Local-log spend; providers without an entry have no local logs. */
+ localSpend: LocalSpendFixture[];
+}
+
+export interface LocalSpendFixture {
+ providerId: string;
+ daily: number;
+ tokensPerDay: number;
+ model: string;
+}
+
+function codexSpend(): LocalSpendFixture {
+ return { providerId: "codex", daily: 1.84, tokensPerDay: 412_000, model: "gpt-5-codex" };
+}
+
+function claudeSpend(): LocalSpendFixture {
+ return { providerId: "claude", daily: 2.37, tokensPerDay: 289_000, model: "claude-sonnet-4" };
+}
+
+const SESSION_MINUTES = 300;
+const WEEK_MINUTES = 10_080;
+const MONTH_MINUTES = 43_200;
+const DAY_MINUTES = 1_440;
+
+function codexSnapshot(now: Date, email: string): ProviderUsageSnapshot {
+ return providerSnapshot(
+ {
+ providerId: "codex",
+ displayName: "Codex",
+ primary: rateWindow(now, 34, SESSION_MINUTES, 142),
+ primaryLabel: "Session",
+ updatedAt: isoBefore(now, 1),
+ },
+ {
+ secondary: rateWindow(now, 58, WEEK_MINUTES, 3 * DAY_MINUTES + 190),
+ secondaryLabel: "Weekly",
+ planName: "Plus",
+ accountEmail: email,
+ sourceLabel: "oauth",
+ pace: {
+ stage: "slightly_behind",
+ deltaPercent: 4,
+ willLastToReset: true,
+ etaSeconds: null,
+ expectedUsedPercent: 54,
+ actualUsedPercent: 58,
+ },
+ },
+ );
+}
+
+function claudeSnapshot(now: Date): ProviderUsageSnapshot {
+ return providerSnapshot(
+ {
+ providerId: "claude",
+ displayName: "Claude",
+ primary: rateWindow(now, 22, SESSION_MINUTES, 61),
+ primaryLabel: "Session (5h)",
+ updatedAt: isoBefore(now, 2),
+ },
+ {
+ secondary: rateWindow(now, 41, WEEK_MINUTES, 5 * DAY_MINUTES + 30),
+ secondaryLabel: "Weekly",
+ planName: "Max",
+ accountEmail: "claude.user@example.com",
+ accountOrganization: "Example Studio",
+ sourceLabel: "oauth",
+ },
+ );
+}
+
+function cursorSnapshot(now: Date, updatedMinutesAgo = 3): ProviderUsageSnapshot {
+ return providerSnapshot(
+ {
+ providerId: "cursor",
+ displayName: "Cursor",
+ primary: rateWindow(now, 62, MONTH_MINUTES, 12 * DAY_MINUTES),
+ primaryLabel: "Plan",
+ updatedAt: isoBefore(now, updatedMinutesAgo),
+ },
+ {
+ planName: "Pro",
+ accountEmail: "cursor.user@example.com",
+ sourceLabel: "web",
+ },
+ );
+}
+
+function geminiSnapshot(now: Date): ProviderUsageSnapshot {
+ return providerSnapshot(
+ {
+ providerId: "gemini",
+ displayName: "Gemini",
+ primary: rateWindow(now, 15, DAY_MINUTES, 9 * 60),
+ primaryLabel: "Daily",
+ updatedAt: isoBefore(now, 4),
+ },
+ {
+ planName: "Free",
+ accountEmail: "gemini.user@example.com",
+ sourceLabel: "cli",
+ },
+ );
+}
+
+function copilotSnapshot(now: Date): ProviderUsageSnapshot {
+ return providerSnapshot(
+ {
+ providerId: "copilot",
+ displayName: "Copilot",
+ primary: rateWindow(now, 48, MONTH_MINUTES, 18 * DAY_MINUTES),
+ primaryLabel: "Premium",
+ updatedAt: isoBefore(now, 5),
+ },
+ {
+ planName: "Business",
+ accountEmail: "copilot.user@example.com",
+ sourceLabel: "api",
+ },
+ );
+}
+
+function codexAccount(
+ now: Date,
+ id: string,
+ email: string,
+ source: CodexAccount["source"],
+ nickname: string | null,
+): CodexAccount {
+ return {
+ id,
+ nickname,
+ emailHint: email,
+ authSubject: null,
+ providerAccountId: null,
+ codexHomePath: source === "ambient" ? "C:\\Users\\proof\\.codex" : `C:\\proof\\codex-homes\\${id}`,
+ source,
+ createdAt: isoBefore(now, 30 * DAY_MINUTES),
+ updatedAt: isoBefore(now, 60),
+ lastAuthenticatedAt: isoBefore(now, 2 * DAY_MINUTES),
+ };
+}
+
+function codexAccountUsage(
+ now: Date,
+ email: string,
+ plan: string,
+ sessionUsed: number,
+ weeklyUsed: number,
+): CodexAccountUsageSnapshot {
+ return {
+ email,
+ providerAccountId: null,
+ plan,
+ allowed: true,
+ limitReached: false,
+ primaryWindow: {
+ usedPercent: sessionUsed,
+ resetAt: isoAfter(now, 142),
+ limitWindowSeconds: SESSION_MINUTES * 60,
+ },
+ secondaryWindow: {
+ usedPercent: weeklyUsed,
+ resetAt: isoAfter(now, 3 * DAY_MINUTES + 190),
+ limitWindowSeconds: WEEK_MINUTES * 60,
+ },
+ credits: null,
+ updatedAt: isoBefore(now, 1),
+ };
+}
+
+function singleCodexAccount(now: Date): CodexAccountsStateBridge {
+ const email = "dev@example.com";
+ return {
+ accounts: [codexAccount(now, "proof-codex-ambient", email, "ambient", null)],
+ accountOrdinals: { "proof-codex-ambient": 1 },
+ snapshots: { "proof-codex-ambient": codexAccountUsage(now, email, "Plus", 34, 58) },
+ };
+}
+
+function claudeAccounts(now: Date): ClaudeAccount[] {
+ return [
+ {
+ id: "proof-claude-1",
+ email: "claude.user@example.com",
+ organization: "Example Studio",
+ plan: "Max",
+ isActive: true,
+ isSaved: true,
+ usage: {
+ fiveHour: { usedPercent: 22, resetsAt: isoAfter(now, 61) },
+ sevenDay: { usedPercent: 41, resetsAt: isoAfter(now, 5 * DAY_MINUTES + 30) },
+ updatedAt: isoBefore(now, 2),
+ },
+ },
+ ];
+}
+
+function emptyCodexAccounts(): CodexAccountsStateBridge {
+ return { accounts: [], accountOrdinals: {}, snapshots: {} };
+}
+
+const DEFAULT_ENABLED = ["codex", "claude", "cursor", "gemini", "copilot"];
+
+function defaultScenario(now: Date): ProofScenario {
+ return {
+ name: "default",
+ settings: baseSettings({ enabledProviders: DEFAULT_ENABLED }),
+ catalog: PROOF_PROVIDER_CATALOG,
+ providers: [
+ codexSnapshot(now, "dev@example.com"),
+ claudeSnapshot(now),
+ cursorSnapshot(now),
+ geminiSnapshot(now),
+ copilotSnapshot(now),
+ ],
+ codexAccounts: singleCodexAccount(now),
+ claudeAccounts: claudeAccounts(now),
+ localSpend: [codexSpend(), claudeSpend()],
+ };
+}
+
+function twoCodexAccountsScenario(now: Date): ProofScenario {
+ const work = "work@example.com";
+ const personal = "personal@example.com";
+ return {
+ name: "two-codex-accounts",
+ settings: baseSettings({ enabledProviders: ["codex", "claude"] }),
+ catalog: PROOF_PROVIDER_CATALOG,
+ providers: [codexSnapshot(now, work), claudeSnapshot(now)],
+ codexAccounts: {
+ accounts: [
+ codexAccount(now, "proof-codex-work", work, "ambient", "Work"),
+ codexAccount(now, "proof-codex-personal", personal, "managedByApp", "Personal"),
+ ],
+ accountOrdinals: { "proof-codex-work": 1, "proof-codex-personal": 2 },
+ snapshots: {
+ "proof-codex-work": codexAccountUsage(now, work, "Plus", 34, 58),
+ "proof-codex-personal": codexAccountUsage(now, personal, "Pro", 9, 17),
+ },
+ },
+ claudeAccounts: claudeAccounts(now),
+ localSpend: [codexSpend(), claudeSpend()],
+ };
+}
+
+function errorsScenario(now: Date): ProofScenario {
+ const claude = providerSnapshot(
+ {
+ providerId: "claude",
+ displayName: "Claude",
+ primary: rateWindow(now, 0, SESSION_MINUTES, null),
+ primaryLabel: "Session (5h)",
+ updatedAt: isoBefore(now, 1),
+ },
+ {
+ error: "Your Claude session has expired. Sign in again to resume usage updates.",
+ errorState: "expiredSession",
+ sourceLabel: "oauth",
+ },
+ );
+ return {
+ name: "errors",
+ settings: baseSettings({ enabledProviders: ["codex", "claude", "cursor"] }),
+ catalog: PROOF_PROVIDER_CATALOG,
+ providers: [
+ codexSnapshot(now, "dev@example.com"),
+ claude,
+ // Last good data from three hours ago: older than the 10-minute stale cutoff.
+ cursorSnapshot(now, 180),
+ ],
+ codexAccounts: singleCodexAccount(now),
+ claudeAccounts: [],
+ localSpend: [codexSpend(), claudeSpend()],
+ };
+}
+
+function emptyScenario(): ProofScenario {
+ return {
+ name: "empty",
+ settings: baseSettings({ enabledProviders: [] }),
+ catalog: PROOF_PROVIDER_CATALOG,
+ providers: [],
+ codexAccounts: emptyCodexAccounts(),
+ claudeAccounts: [],
+ localSpend: [],
+ };
+}
+
+const BUILDERS: Record ProofScenario> = {
+ default: defaultScenario,
+ "two-codex-accounts": twoCodexAccountsScenario,
+ errors: errorsScenario,
+ empty: emptyScenario,
+};
+
+export function isProofScenarioName(value: string): value is ProofScenarioName {
+ return (PROOF_SCENARIO_NAMES as readonly string[]).includes(value);
+}
+
+/** Fixture timestamps are relative to `now` so reset countdowns stay plausible. */
+export function buildProofScenario(name: ProofScenarioName, now: Date = new Date()): ProofScenario {
+ return BUILDERS[name](now);
+}
diff --git a/apps/desktop-tauri/src/proof/install.ts b/apps/desktop-tauri/src/proof/install.ts
new file mode 100644
index 00000000000..94e6c9a7bb4
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/install.ts
@@ -0,0 +1,37 @@
+import { mockIPC, mockWindows } from "@tauri-apps/api/mocks";
+import { buildProofScenario } from "./fixtures/scenarios";
+import { PROOF_WINDOWS, windowLabel, type ProofParams } from "./params";
+import { createProofRouter, type ProofCall, type ProofRouter } from "./router";
+
+/** Read by `scripts/proof-check.mjs` over CDP to assert backend calls. */
+export interface ProofProbe {
+ calls: () => ProofCall[];
+ unhandledCommands: () => string[];
+}
+
+declare global {
+ interface Window {
+ __CODEXBAR_PROOF__?: ProofProbe;
+ }
+}
+
+/**
+ * Replace the Tauri IPC with fixture data. Must run before App mounts:
+ * `getCurrentWebviewWindow()` reads the mocked window metadata.
+ */
+export function installProofMocks(params: ProofParams, now: Date = new Date()): ProofRouter {
+ const scenario = buildProofScenario(params.fixture, now);
+ if (params.theme) {
+ scenario.settings = { ...scenario.settings, theme: params.theme };
+ }
+ const router = createProofRouter(scenario, { window: params.window, tab: params.tab, now });
+ const current = windowLabel(params.window);
+ const others = PROOF_WINDOWS.map(windowLabel).filter((label) => label !== current);
+ mockWindows(current, ...others);
+ mockIPC(router.handle, { shouldMockEvents: true });
+ window.__CODEXBAR_PROOF__ = {
+ calls: router.calls,
+ unhandledCommands: router.unhandledCommands,
+ };
+ return router;
+}
diff --git a/apps/desktop-tauri/src/proof/isolation.test.ts b/apps/desktop-tauri/src/proof/isolation.test.ts
new file mode 100644
index 00000000000..fa20f6ef70c
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/isolation.test.ts
@@ -0,0 +1,23 @@
+import { readFileSync } from "node:fs";
+import { describe, expect, it } from "vitest";
+
+const SRC = `${import.meta.dirname}/..`;
+
+function source(relative: string): string {
+ return readFileSync(`${SRC}/${relative}`, "utf8");
+}
+
+describe("production entry isolation", () => {
+ it.each(["main.tsx", "App.tsx", "lib/tauri.ts"])("%s never imports the proof bridge", (file) => {
+ const text = source(file);
+ expect(text).not.toMatch(/from\s+["'][^"']*\/proof(\/|["'])/);
+ expect(text).not.toMatch(/import\(\s*["'][^"']*\/proof(\/|["'])/);
+ expect(text).not.toContain("@tauri-apps/api/mocks");
+ });
+
+ it("index.html still boots the production entry", () => {
+ const html = readFileSync(`${SRC}/../index.html`, "utf8");
+ expect(html).toContain('src="/src/main.tsx"');
+ expect(html).not.toContain("proof");
+ });
+});
diff --git a/apps/desktop-tauri/src/proof/locale.ts b/apps/desktop-tauri/src/proof/locale.ts
new file mode 100644
index 00000000000..5b16ed57151
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/locale.ts
@@ -0,0 +1,31 @@
+import englishFtl from "../../../../rust/src/locale/en-US.ftl?raw";
+import { ALL_LOCALE_KEYS } from "../i18n/keys";
+import type { LanguageOption, LocaleStrings } from "../types/bridge";
+
+/** Parse the shipped English Fluent file the way the Rust bridge flattens it. */
+export function parseFtlEntries(text: string): Record {
+ const entries: Record = {};
+ for (const line of text.split(/\r?\n/)) {
+ const match = /^([A-Za-z][\w-]*)\s*=\s?(.*)$/.exec(line);
+ if (match) entries[match[1]] = match[2].replace(/\{ "\{\}" \}/g, "{}");
+ }
+ return entries;
+}
+
+let cached: LocaleStrings | null = null;
+
+/** English bundle; keys missing from the file fall back to the key name, as the UI does. */
+export function englishLocaleStrings(): LocaleStrings {
+ if (cached) return cached;
+ const parsed = parseFtlEntries(englishFtl);
+ const entries: Record = {};
+ for (const key of ALL_LOCALE_KEYS) {
+ entries[key] = parsed[key] ?? key;
+ }
+ cached = { language: "english", entries: { ...parsed, ...entries } };
+ return cached;
+}
+
+export const PROOF_LANGUAGES: LanguageOption[] = [
+ { value: "english", display: "English" },
+];
diff --git a/apps/desktop-tauri/src/proof/main.tsx b/apps/desktop-tauri/src/proof/main.tsx
new file mode 100644
index 00000000000..f5825b189dd
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/main.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { installProofMocks } from "./install";
+import { parseProofParams } from "./params";
+import "../styles.css";
+import "../surfaces/settings/settings-layout.css";
+import "../surfaces/menu-surface-layout.css";
+import "../surfaces/tray-card.css";
+
+installProofMocks(parseProofParams(window.location.search));
+
+// App is loaded only after the mocks exist so no module sees the bare IPC.
+void import("../App").then(({ default: App }) => {
+ ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+ ,
+ );
+});
diff --git a/apps/desktop-tauri/src/proof/params.ts b/apps/desktop-tauri/src/proof/params.ts
new file mode 100644
index 00000000000..6fccea0dcbe
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/params.ts
@@ -0,0 +1,40 @@
+import type { ThemePreference } from "../types/bridge";
+import { FLOATBAR_WINDOW_LABEL } from "../floatbar/api";
+import { isProofScenarioName, type ProofScenarioName } from "./fixtures/scenarios";
+
+export const PROOF_WINDOWS = ["main", "settings", "flyout", "floatbar"] as const;
+export type ProofWindow = (typeof PROOF_WINDOWS)[number];
+
+export interface ProofParams {
+ window: ProofWindow;
+ /** Raw settings tab request (`general`, `providers:codex`, ...); App parses it. */
+ tab: string;
+ fixture: ProofScenarioName;
+ /** Overrides the fixture's saved theme when present. */
+ theme: ThemePreference | null;
+}
+
+function isProofWindow(value: string): value is ProofWindow {
+ return (PROOF_WINDOWS as readonly string[]).includes(value);
+}
+
+function isThemePreference(value: string): value is ThemePreference {
+ return value === "dark" || value === "light" || value === "auto";
+}
+
+export function parseProofParams(search: string): ProofParams {
+ const params = new URLSearchParams(search);
+ const windowParam = params.get("window") ?? "";
+ const fixture = params.get("fixture") ?? "";
+ const theme = params.get("theme") ?? "";
+ return {
+ window: isProofWindow(windowParam) ? windowParam : "main",
+ tab: params.get("tab") || "general",
+ fixture: isProofScenarioName(fixture) ? fixture : "default",
+ theme: isThemePreference(theme) ? theme : null,
+ };
+}
+
+export function windowLabel(window: ProofWindow): string {
+ return window === "floatbar" ? FLOATBAR_WINDOW_LABEL : window;
+}
diff --git a/apps/desktop-tauri/src/proof/render.test.tsx b/apps/desktop-tauri/src/proof/render.test.tsx
new file mode 100644
index 00000000000..001f3b3d0f4
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/render.test.tsx
@@ -0,0 +1,74 @@
+import { cleanup, configure, render, screen } from "@testing-library/react";
+import { clearMocks } from "@tauri-apps/api/mocks";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { parseProofParams } from "./params";
+import type { ProofRouter } from "./router";
+
+// Each case re-imports the whole App after vi.resetModules(); under a loaded
+// full-suite run that cold import alone can exceed the default timeouts.
+configure({ asyncUtilTimeout: 10_000 });
+
+async function renderProof(search: string): Promise {
+ window.history.replaceState({}, "", `/proof.html${search}`);
+ // Fresh modules per render: surface and settings stores are module singletons.
+ vi.resetModules();
+ const { installProofMocks } = await import("./install");
+ const router = installProofMocks(parseProofParams(search));
+ const { default: App } = await import("../App");
+ render();
+ return router;
+}
+
+afterEach(async () => {
+ cleanup();
+ // Unmount unlistens resolve asynchronously and still need the mocked internals.
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ clearMocks();
+ window.history.replaceState({}, "", "/");
+});
+
+describe("proof build renders App over the mocked bridge", { timeout: 20_000 }, () => {
+ it("shows the tray panel with the default providers in the main window", async () => {
+ const router = await renderProof("?window=main&fixture=default");
+ expect((await screen.findAllByText("Codex")).length).toBeGreaterThan(0);
+ expect((await screen.findAllByText("Claude")).length).toBeGreaterThan(0);
+ expect(screen.queryByText("Bootstrap failed")).not.toBeInTheDocument();
+ expect(router.unhandledCommands()).toEqual([]);
+ });
+
+ it("renders the flyout for the errors scenario", async () => {
+ await renderProof("?window=flyout&fixture=errors");
+ expect((await screen.findAllByText("Cursor")).length).toBeGreaterThan(0);
+ });
+
+ it("renders Settings > General in the settings window", async () => {
+ const router = await renderProof("?window=settings&tab=general&fixture=default");
+ expect(await screen.findByText("Start at login")).toBeInTheDocument();
+ expect(router.unhandledCommands()).toEqual([]);
+ });
+
+ it("renders Settings > Providers with the enabled providers listed", async () => {
+ await renderProof("?window=settings&tab=providers&fixture=default");
+ expect((await screen.findAllByText("Codex")).length).toBeGreaterThan(0);
+ expect((await screen.findAllByText("Claude")).length).toBeGreaterThan(0);
+ });
+
+ it("renders the Codex pane for two accounts", async () => {
+ await renderProof("?window=settings&tab=providers:codex&fixture=two-codex-accounts");
+ expect((await screen.findAllByText(/work@example\.com/)).length).toBeGreaterThan(0);
+ expect((await screen.findAllByText(/personal@example\.com/)).length).toBeGreaterThan(0);
+ });
+
+ it("renders the main window with no providers enabled", async () => {
+ const router = await renderProof("?window=main&fixture=empty");
+ await vi.waitFor(
+ () => {
+ expect(document.body.textContent).not.toBe("");
+ expect(screen.queryByText("Loading shell contract…")).not.toBeInTheDocument();
+ },
+ { timeout: 10_000 },
+ );
+ expect(screen.queryByText("Codex")).not.toBeInTheDocument();
+ expect(router.unhandledCommands()).toEqual([]);
+ });
+});
diff --git a/apps/desktop-tauri/src/proof/router.test.ts b/apps/desktop-tauri/src/proof/router.test.ts
new file mode 100644
index 00000000000..8c38a88dc38
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/router.test.ts
@@ -0,0 +1,151 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import type {
+ BootstrapState,
+ CurrentSurfaceState,
+ LocaleStrings,
+ ProviderDetail,
+ ProviderUsageSnapshot,
+ SettingsSnapshot,
+} from "../types/bridge";
+import { PROOF_SCENARIO_NAMES, buildProofScenario } from "./fixtures/scenarios";
+import { parseProofParams } from "./params";
+import { UNHANDLED_COMMAND_WARNING, createProofRouter } from "./router";
+
+const NOW = new Date("2026-03-10T12:00:00Z");
+
+function routerFor(name: (typeof PROOF_SCENARIO_NAMES)[number], window: "main" | "settings" = "main") {
+ return createProofRouter(buildProofScenario(name, NOW), { window, tab: "providers", now: NOW });
+}
+
+afterEach(() => {
+ vi.restoreAllMocks();
+});
+
+describe("proof router", () => {
+ it.each(PROOF_SCENARIO_NAMES)("serves bootstrap, settings and providers for %s", (name) => {
+ const scenario = buildProofScenario(name, NOW);
+ const router = routerFor(name);
+
+ const bootstrap = router.handle("get_bootstrap_state") as BootstrapState;
+ expect(bootstrap.settings.enabledProviders).toEqual(scenario.settings.enabledProviders);
+ expect(bootstrap.providers.length).toBeGreaterThan(0);
+
+ const settings = router.handle("get_settings_snapshot") as SettingsSnapshot;
+ expect(settings).toEqual(scenario.settings);
+
+ const cached = router.handle("get_cached_providers") as ProviderUsageSnapshot[];
+ expect(cached.map((snapshot) => snapshot.providerId)).toEqual(
+ scenario.providers.map((snapshot) => snapshot.providerId),
+ );
+
+ const locale = router.handle("get_locale_strings", { language: null }) as LocaleStrings;
+ expect(locale.entries.TabGeneral).toBe("General");
+
+ const surface = router.handle("get_current_surface_state") as CurrentSurfaceState;
+ expect(surface.mode).toBe("trayPanel");
+ });
+
+ it("keeps each provider's identity on its own snapshot", () => {
+ const providers = buildProofScenario("default", NOW).providers;
+ const emails = providers.map((snapshot) => snapshot.accountEmail).filter(Boolean);
+ expect(new Set(emails).size).toBe(emails.length);
+ for (const email of emails) expect(email).toMatch(/@example\.com$/);
+ });
+
+ it("covers the scenario shapes the screenshots rely on", () => {
+ expect(buildProofScenario("default", NOW).providers.map((p) => p.providerId)).toEqual(
+ expect.arrayContaining(["codex", "claude"]),
+ );
+ expect(buildProofScenario("two-codex-accounts", NOW).codexAccounts.accounts).toHaveLength(2);
+ const errors = buildProofScenario("errors", NOW).providers;
+ expect(errors.some((snapshot) => snapshot.error !== null)).toBe(true);
+ expect(
+ errors.some((snapshot) => NOW.getTime() - Date.parse(snapshot.updatedAt) > 10 * 60_000),
+ ).toBe(true);
+ expect(buildProofScenario("empty", NOW).settings.enabledProviders).toEqual([]);
+ });
+
+ it("answers provider detail without leaking another provider's identity", () => {
+ const router = routerFor("default");
+ const claude = router.handle("get_provider_detail", { providerId: "claude" }) as ProviderDetail;
+ expect(claude.email).toBe("claude.user@example.com");
+ const openrouter = router.handle("get_provider_detail", { providerId: "openrouter" }) as ProviderDetail;
+ expect(openrouter.hasSnapshot).toBe(false);
+ expect(openrouter.email).toBeNull();
+ });
+
+ it("applies settings patches to later reads", () => {
+ const router = routerFor("default");
+ router.handle("update_settings", { patch: { theme: "light" } });
+ expect((router.handle("get_settings_snapshot") as SettingsSnapshot).theme).toBe("light");
+ });
+
+ it("keeps provider detail in step with patched enabled providers", () => {
+ const router = routerFor("default");
+ const enabled = () =>
+ (router.handle("get_provider_detail", { providerId: "claude" }) as ProviderDetail).enabled;
+ expect(enabled()).toBe(true);
+ const remaining = buildProofScenario("default", NOW).settings.enabledProviders.filter(
+ (id) => id !== "claude",
+ );
+ router.handle("update_settings", { patch: { enabledProviders: remaining } });
+ expect(enabled()).toBe(false);
+ expect(
+ (router.handle("get_cached_providers") as ProviderUsageSnapshot[]).map((s) => s.providerId),
+ ).not.toContain("claude");
+ });
+
+ it("reports the requested settings tab as the shell target", () => {
+ const router = createProofRouter(buildProofScenario("default", NOW), {
+ window: "settings",
+ tab: "providers:claude",
+ now: NOW,
+ });
+ expect(router.handle("get_current_surface_state")).toEqual({
+ mode: "settings",
+ target: { kind: "settings", tab: "providers:claude" },
+ });
+ });
+
+ it("returns null and warns once for an unknown command", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const router = routerFor("default");
+ expect(router.handle("definitely_not_a_command")).toBeNull();
+ expect(router.handle("definitely_not_a_command")).toBeNull();
+ expect(warn).toHaveBeenCalledTimes(1);
+ expect(warn).toHaveBeenCalledWith(`${UNHANDLED_COMMAND_WARNING} definitely_not_a_command`);
+ expect(router.unhandledCommands()).toEqual(["definitely_not_a_command"]);
+ });
+
+ it("answers window and dialog plugin commands without warning", () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const router = routerFor("default");
+ expect(router.handle("plugin:window|set_size", {})).toBeNull();
+ expect(router.handle("plugin:window|inner_size", {})).toEqual({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+ expect(router.handle("plugin:dialog|open", {})).toBeNull();
+ expect(warn).not.toHaveBeenCalled();
+ });
+});
+
+describe("proof URL params", () => {
+ it("falls back to safe defaults", () => {
+ expect(parseProofParams("?window=nope&fixture=nope&theme=nope")).toEqual({
+ window: "main",
+ tab: "general",
+ fixture: "default",
+ theme: null,
+ });
+ });
+
+ it("reads window, tab, fixture and theme", () => {
+ expect(parseProofParams("?window=settings&tab=menu&fixture=errors&theme=light")).toEqual({
+ window: "settings",
+ tab: "menu",
+ fixture: "errors",
+ theme: "light",
+ });
+ });
+});
diff --git a/apps/desktop-tauri/src/proof/router.ts b/apps/desktop-tauri/src/proof/router.ts
new file mode 100644
index 00000000000..c21054dd1d0
--- /dev/null
+++ b/apps/desktop-tauri/src/proof/router.ts
@@ -0,0 +1,332 @@
+import { emit } from "@tauri-apps/api/event";
+import { version as appVersion } from "../../package.json";
+import type {
+ BootstrapState,
+ CurrentSurfaceState,
+ SettingsSnapshot,
+ SettingsUpdate,
+} from "../types/bridge";
+import {
+ parseSettingsTabRequest,
+ providersTabRequest,
+} from "../surfaces/settings/settingsTabs";
+import type { ProofScenario } from "./fixtures/scenarios";
+import {
+ proofAppInfo,
+ proofCurrencyRates,
+ proofHooksConfig,
+ proofUpdateState,
+ providerChartData,
+ providerDetail,
+ providerSummaries,
+ spendContract,
+ usageSpendSummary,
+} from "./fixtures/bridgeData";
+import { PROOF_LANGUAGES, englishLocaleStrings } from "./locale";
+import type { ProofWindow } from "./params";
+
+export const UNHANDLED_COMMAND_WARNING = "[proof-mock] unhandled command:";
+
+type Handler = (args: Record) => unknown;
+
+/** Plugin namespaces whose commands are window chrome or OS dialogs: inert in a browser. */
+const INERT_PLUGIN_PREFIXES = ["plugin:window|", "plugin:webview|", "plugin:dialog|", "plugin:event|"];
+
+/**
+ * User actions (sign-ins, credential writes, OS side effects) the browser
+ * accepts as no-ops. They return null and are recorded in `calls()`, so a
+ * scenario can assert that a click reached the backend. Give one a real
+ * handler when a scenario needs its result.
+ */
+export const INERT_ACTIONS = [
+ "add_token_account",
+ "apply_update",
+ "claude_account_add",
+ "claude_account_cancel_login",
+ "claude_account_remove",
+ "claude_account_save_current",
+ "claude_account_switch",
+ "claude_swap_account_reauthenticate",
+ "claude_swap_account_switch",
+ "codex_account_add",
+ "codex_account_reauthenticate",
+ "codex_account_remove",
+ "codex_account_restart_desktop",
+ "codex_account_switch",
+ "download_update",
+ "export_preferences",
+ "focus_agent_session",
+ "get_openai_live_usage",
+ "get_openai_org_rate_limits",
+ "grok_account_add",
+ "grok_account_cancel_login",
+ "grok_account_fetch",
+ "grok_account_reauthenticate",
+ "grok_account_remove",
+ "grok_account_save_current",
+ "grok_account_switch",
+ "import_browser_cookies",
+ "import_preferences",
+ "open_path",
+ "play_notification_sound",
+ "quit_app",
+ "register_global_shortcut",
+ "remove_api_key",
+ "remove_manual_cookie",
+ "remove_openrouter_management_api_key",
+ "remove_token_account",
+ "revoke_provider_credentials",
+ "save_hooks_config",
+ "set_active_token_account",
+ "set_api_key",
+ "set_jetbrains_ide_path",
+ "set_manual_cookie",
+ "set_openrouter_management_api_key",
+ "set_provider_auto_resume_after_quota_reset",
+ "set_provider_azure_api_version",
+ "set_provider_cookie_source",
+ "set_provider_gateway_url",
+ "set_provider_optional_details",
+ "set_provider_region",
+ "set_provider_usage_source",
+ "set_provider_workspace_id",
+ "trigger_provider_login",
+ "unregister_global_shortcut",
+ "write_usage_spend_export",
+] as const;
+
+export interface ProofCall {
+ cmd: string;
+ args: Record;
+}
+
+export interface ProofRouterOptions {
+ window: ProofWindow;
+ tab: string;
+ now?: Date;
+}
+
+export interface ProofRouter {
+ handle: (cmd: string, payload?: unknown) => unknown;
+ /** Command names with a fixture-backed handler. */
+ handledCommands: readonly string[];
+ /** Commands that reached the fallback since this router was created. */
+ unhandledCommands: () => string[];
+ /** Every non-plugin command handled so far, in order. */
+ calls: () => ProofCall[];
+ settings: () => SettingsSnapshot;
+}
+
+function argsRecord(payload: unknown): Record {
+ return payload !== null && typeof payload === "object" && !Array.isArray(payload)
+ ? (payload as Record)
+ : {};
+}
+
+function stringArg(args: Record, key: string): string {
+ const value = args[key];
+ return typeof value === "string" ? value : "";
+}
+
+function emitSoon(event: string, payload: unknown): void {
+ window.setTimeout(() => {
+ void emit(event, payload).catch(() => {});
+ }, 0);
+}
+
+function windowPluginResult(cmd: string): unknown {
+ switch (cmd) {
+ case "plugin:window|inner_size":
+ case "plugin:window|outer_size":
+ return { width: window.innerWidth, height: window.innerHeight };
+ case "plugin:window|inner_position":
+ case "plugin:window|outer_position":
+ return { x: 0, y: 0 };
+ case "plugin:window|scale_factor":
+ return window.devicePixelRatio || 1;
+ case "plugin:window|is_visible":
+ case "plugin:window|is_focused":
+ return true;
+ default:
+ return null;
+ }
+}
+
+export function createProofRouter(
+ scenario: ProofScenario,
+ options: ProofRouterOptions,
+): ProofRouter {
+ const now = options.now ?? new Date();
+ let settings: SettingsSnapshot = { ...scenario.settings };
+ const warned = new Set();
+
+ const bootstrap = (): BootstrapState => ({
+ contractVersion: "v1",
+ providers: scenario.catalog,
+ settings,
+ });
+
+ const surfaceState = (): CurrentSurfaceState => {
+ if (options.window === "settings") {
+ const request = parseSettingsTabRequest(options.tab);
+ const tab = request?.providerId
+ ? providersTabRequest(request.providerId)
+ : (request?.tab ?? "general");
+ return { mode: "settings", target: { kind: "settings", tab } };
+ }
+ return { mode: "trayPanel", target: { kind: "summary" } };
+ };
+
+ // Fixture builders read settings, so they get the patched copy.
+ const current = (): ProofScenario => ({ ...scenario, settings });
+ const enabledProviders = () =>
+ scenario.providers.filter((snapshot) => settings.enabledProviders.includes(snapshot.providerId));
+
+ const handlers: Record = {
+ get_bootstrap_state: bootstrap,
+ get_settings_snapshot: () => settings,
+ update_settings: (args) => {
+ const patch = argsRecord(args.patch) as SettingsUpdate;
+ settings = { ...settings, ...patch } as SettingsSnapshot;
+ return settings;
+ },
+ get_provider_catalog: () => scenario.catalog,
+ get_provider_statuses: () => [],
+ reorder_providers: () => providerSummaries(current()),
+ reorder_visible_providers: () => providerSummaries(current()),
+ restore_default_provider_order: () => providerSummaries(current()),
+ get_cached_providers: enabledProviders,
+ refresh_providers: () => {
+ const ids = enabledProviders().map((snapshot) => snapshot.providerId);
+ emitSoon("refresh-started", { providerIds: ids });
+ emitSoon("refresh-complete", {
+ providerCount: ids.length,
+ errorCount: enabledProviders().filter((snapshot) => snapshot.error !== null).length,
+ });
+ return null;
+ },
+ refresh_providers_if_stale: () => null,
+ get_current_surface_state: surfaceState,
+ set_surface_mode: (args) => args.mode ?? "trayPanel",
+ get_locale_strings: () => englishLocaleStrings(),
+ set_ui_language: () => null,
+ get_available_languages: () => PROOF_LANGUAGES,
+ get_currency_rates: (args) =>
+ proofCurrencyRates(stringArg(args, "preferredCurrencyCode") || "AUTO"),
+ get_update_state: proofUpdateState,
+ check_for_updates: proofUpdateState,
+ dismiss_update: proofUpdateState,
+ get_app_info: () => proofAppInfo(appVersion),
+ get_deepseek_pricing_status: () => null,
+ get_stay_awake_status: () => false,
+ get_system_accent_color: () => "#4c8bf5",
+ // A fixed 1080p desktop minus the taskbar: headless browsers report a
+ // small `screen`, which would cap the tray panel's auto-fit height.
+ get_work_area_rect: () => ({ x: 0, y: 0, width: 1920, height: 1032 }),
+ tray_visibility_status: () => ({ support: "supported", state: "promoted" }),
+ get_credential_storage_status: () => ({
+ manualCookies: "Windows Credential Manager",
+ apiKeys: "Windows Credential Manager",
+ tokenAccounts: "Windows Credential Manager",
+ }),
+ get_provider_detail: (args) => providerDetail(current(), stringArg(args, "providerId")),
+ get_provider_chart_data: (args) =>
+ providerChartData(current(), stringArg(args, "providerId"), now),
+ get_provider_local_usage_summary: (args) =>
+ providerChartData(current(), stringArg(args, "providerId"), now).localUsage,
+ get_usage_spend_summary: () => usageSpendSummary(current(), now),
+ get_spend_contract: (args) =>
+ spendContract(stringArg(args, "providerId"), settings.spendDashboardPeriod ?? "rolling:30"),
+ get_provider_cookie_source_options: () => [
+ { value: "auto", label: "Automatic" },
+ { value: "manual", label: "Manual" },
+ { value: "off", label: "Off" },
+ ],
+ get_provider_region_options: () => [],
+ get_provider_workspace_id: () => null,
+ get_provider_gateway_url: () => "",
+ get_provider_azure_api_version: () => null,
+ get_api_keys: () => [],
+ get_api_key_providers: () => [],
+ has_openrouter_management_api_key: () => false,
+ get_manual_cookies: () => [],
+ list_detected_browsers: () => [
+ { browserType: "edge", displayName: "Microsoft Edge", profileCount: 1 },
+ { browserType: "chrome", displayName: "Google Chrome", profileCount: 2 },
+ ],
+ get_token_account_providers: () => [],
+ get_token_accounts: (args) => ({
+ providerId: stringArg(args, "providerId"),
+ support: {
+ providerId: stringArg(args, "providerId"),
+ displayName: stringArg(args, "providerId"),
+ title: "Token accounts",
+ subtitle: "",
+ placeholder: "",
+ },
+ accounts: [],
+ activeIndex: 0,
+ }),
+ get_codex_accounts_state: () => scenario.codexAccounts,
+ codex_accounts_list: () => scenario.codexAccounts.accounts,
+ codex_account_snapshots: () => scenario.codexAccounts.snapshots,
+ codex_account_fetch: (args) => scenario.codexAccounts.snapshots[stringArg(args, "id")] ?? null,
+ get_claude_accounts_state: () => scenario.claudeAccounts,
+ claude_reconciliation_state: () => null,
+ claude_swap_accounts_list: () => ({
+ enabled: false,
+ executableConfigured: false,
+ accounts: [],
+ error: null,
+ }),
+ grok_accounts_list: () => [],
+ get_hooks_config: proofHooksConfig,
+ list_agent_sessions: () => ({ status: "disabled" }),
+ get_gemini_cli_signed_in: () => ({ signedIn: true, credentialsPath: null }),
+ get_vertexai_status: () => ({ hasCredentials: false, credentialsPath: null }),
+ list_jetbrains_detected_ides: () => [],
+ get_kiro_status: () => ({ available: false, hint: null }),
+ get_codex_workspaces_snapshot: () => null,
+ get_safe_diagnostics: () => "Proof build: mocked IPC, no diagnostics collected.",
+ // Window and shell side effects are no-ops in a browser.
+ dismiss_tray_panel: () => null,
+ begin_flyout_gesture: () => null,
+ end_flyout_gesture: () => null,
+ open_settings_window: () => null,
+ open_flyout_window: () => null,
+ close_settings_window: () => null,
+ reanchor_tray_panel: () => null,
+ reveal_tray_panel_window: () => null,
+ resize_float_bar: () => null,
+ open_external_url: () => null,
+ open_release_page: () => null,
+ open_provider_dashboard: () => null,
+ open_provider_status_page: () => null,
+ };
+
+ const inertActions = new Set(INERT_ACTIONS);
+ const calls: ProofCall[] = [];
+
+ const handle = (cmd: string, payload?: unknown): unknown => {
+ if (cmd.startsWith("plugin:window|")) return windowPluginResult(cmd);
+ if (INERT_PLUGIN_PREFIXES.some((prefix) => cmd.startsWith(prefix))) return null;
+ const args = argsRecord(payload);
+ calls.push({ cmd, args });
+ const handler = handlers[cmd];
+ if (handler) return handler(args);
+ if (inertActions.has(cmd)) return null;
+ if (!warned.has(cmd)) {
+ warned.add(cmd);
+ console.warn(`${UNHANDLED_COMMAND_WARNING} ${cmd}`);
+ }
+ return null;
+ };
+
+ return {
+ handle,
+ handledCommands: Object.keys(handlers),
+ unhandledCommands: () => [...warned],
+ calls: () => [...calls],
+ settings: () => settings,
+ };
+}
diff --git a/apps/desktop-tauri/src/test/node-builtins.d.ts b/apps/desktop-tauri/src/test/node-builtins.d.ts
index 0f66cc2a636..f2411e0827f 100644
--- a/apps/desktop-tauri/src/test/node-builtins.d.ts
+++ b/apps/desktop-tauri/src/test/node-builtins.d.ts
@@ -5,6 +5,11 @@
// supersede these).
declare module "node:fs" {
export function readFileSync(path: string, encoding: "utf8"): string;
+ export interface Dirent {
+ name: string;
+ isDirectory(): boolean;
+ }
+ export function readdirSync(path: string, options: { withFileTypes: true }): Dirent[];
}
// Node 20.11+ / vite-node inject import.meta.dirname|filename; those types
diff --git a/apps/desktop-tauri/vite.config.ts b/apps/desktop-tauri/vite.config.ts
index caae813b64e..67b38fe60dc 100644
--- a/apps/desktop-tauri/vite.config.ts
+++ b/apps/desktop-tauri/vite.config.ts
@@ -1,19 +1,42 @@
///
-import { defineConfig } from "vite";
+import { fileURLToPath } from "node:url";
+import { defineConfig, searchForWorkspaceRoot } from "vite";
import react from "@vitejs/plugin-react";
-export default defineConfig({
- plugins: [react()],
- server: {
- host: "127.0.0.1",
- port: 1420,
- strictPort: true,
- },
- clearScreen: false,
- test: {
- environment: "jsdom",
- globals: true,
- setupFiles: ["./src/test/setup.ts"],
- include: ["src/**/*.{test,spec}.{ts,tsx}"],
- },
+export default defineConfig(({ mode }) => {
+ // The mocked-IPC proof page is a separate entry so the desktop bundle never
+ // contains the mock bridge or its fixtures.
+ const proof = mode === "proof";
+ // Vitest runs with mode "test" and imports the proof bridge in its tests.
+ const allowLocaleFiles = proof || mode === "test";
+ return {
+ plugins: [react()],
+ server: {
+ host: "127.0.0.1",
+ port: 1420,
+ strictPort: true,
+ fs: allowLocaleFiles
+ ? {
+ // The proof bridge serves the shipped English strings from rust/src/locale.
+ allow: [
+ searchForWorkspaceRoot(process.cwd()),
+ fileURLToPath(new URL("../../rust/src/locale", import.meta.url)),
+ ],
+ }
+ : undefined,
+ },
+ build: proof
+ ? {
+ outDir: "dist-proof",
+ rollupOptions: { input: fileURLToPath(new URL("./proof.html", import.meta.url)) },
+ }
+ : undefined,
+ clearScreen: false,
+ test: {
+ environment: "jsdom",
+ globals: true,
+ setupFiles: ["./src/test/setup.ts"],
+ include: ["src/**/*.{test,spec}.{ts,tsx}"],
+ },
+ };
});
diff --git a/docs/WINDOWS_PROOF.md b/docs/WINDOWS_PROOF.md
index 9de894acd8e..9cebbcd462b 100644
--- a/docs/WINDOWS_PROOF.md
+++ b/docs/WINDOWS_PROOF.md
@@ -176,3 +176,89 @@ Tick each entry as it is verified on the Windows target.
[ ] runtime.update-banner-flow
[ ] runtime.single-instance-mutex
```
+
+---
+
+## 5. Mocked-IPC proof build
+
+`apps/desktop-tauri/proof.html` runs the real React surfaces in any browser.
+The Tauri bridge is replaced by `mockIPC` / `mockWindows` from
+`@tauri-apps/api/mocks`. Commands are answered by synthetic fixtures in
+`src/proof/fixtures/` (typed against `types/bridge.ts`). The desktop bundle
+(`index.html` → `src/main.tsx`) never imports `src/proof`.
+
+```powershell
+cd apps/desktop-tauri
+pnpm run dev:proof # http://127.0.0.1:1420/proof.html
+pnpm run build:proof # static output in dist-proof/
+```
+
+URL parameters:
+
+| Param | Values | Default |
+| --- | --- | --- |
+| `window` | `main`, `settings`, `flyout`, `floatbar` | `main` |
+| `tab` | any settings tab id, or `providers:` | `general` |
+| `fixture` | `default`, `two-codex-accounts`, `errors`, `empty` | `default` |
+| `theme` | `dark`, `light`, `auto` (overrides the fixture) | fixture value |
+
+Example: `proof.html?window=settings&tab=providers:codex&fixture=two-codex-accounts`.
+
+Every command the frontend invokes either has a fixture handler or is listed
+in `INERT_ACTIONS` (`src/proof/router.ts`): user actions such as sign-ins,
+credential writes, and OS side effects that return `null`.
+`src/proof/coverage.test.ts` fails when a new `invoke("...")` has neither, so
+the mocks cannot drift silently. An unknown command still returns `null` and
+logs `[proof-mock] unhandled command: ` once. Window, webview, dialog,
+and event plugin calls are inert.
+
+### Automated check: `pnpm proof:check`
+
+`scripts/proof-check.mjs` runs the scenarios in `scripts/proof-scenarios.json`
+in Chrome, Edge, or Chromium over CDP and prints `PASS` / `FAIL` per scenario.
+It uses Node built-ins only.
+
+```powershell
+cd apps/desktop-tauri
+pnpm proof:check # starts the proof server, headless browser
+pnpm proof:check -- --headed # visible window, pauses on each screenshot
+pnpm proof:check -- --only tray # scenarios whose name contains "tray"
+pnpm proof:check -- --url # an already-served proof build (dev or dist-proof/)
+pnpm proof:check -- --cdp # attach to a running browser on this host
+pnpm proof:check -- --cdp --url # remote browser; must be reachable from it
+```
+
+Without `--url`, the runner serves the proof build on `127.0.0.1`, which a
+browser on another host cannot reach.
+
+With `--cdp`, the runner drives whatever browser it is given, and its isolated
+`.proof/browser-profile/` applies only to browsers it launches itself. Point
+`--cdp` at a browser started with its own empty `--user-data-dir`, never one
+with real logins. CDP has no authentication, so keep a remote browser's
+debugging port bound to loopback and reach it through an SSH tunnel or an
+authenticated bridge, not an open port.
+
+Screenshots go to `apps/desktop-tauri/.proof/screens/` (git-ignored). The
+browser runs on its own profile in `.proof/browser-profile/`, never a user
+profile. Exit code `0` means every scenario passed, `1` means one failed, and
+`2` means setup failed.
+
+A scenario names the URL query and the window size, then runs its steps in
+order. Every scenario also fails on a console error or warning, an uncaught
+exception, or an unhandled backend command.
+
+| Step | Checks |
+| --- | --- |
+| `expectText` / `expectNoText` | page text (string or list), polled up to 8 s |
+| `expectSelector` | an element exists |
+| `expectTheme` | `` |
+| `click` | `{ selector, label, near? }`: a real mouse click on the element whose aria-label or text equals `label`; `near` picks the match closest to that text |
+| `expectCall` | `{ cmd, args? }`: the frontend invoked that backend command (args are a subset match) |
+| `screenshot` | saves `.png`; `"clip": "panel"` crops a tray panel to the height it committed for its window |
+
+Add or extend a scenario for the UI a PR changes.
+
+Screenshots from this build are labelled **"Chromium (Linux), mocked IPC"**
+(or the browser actually used). They support layout, copy, rendered data, and
+provider-siloing review; they do not replace the CUA proof for tray, theme,
+float-bar, DWM, or WebView2 behaviour (see AGENTS.md).