# Phase 0 Handoff — MeetVault Desktop Demo Session handoff for the **Phase 0 (test harness + runtime verification)** work defined in `AGENTS.md` §14. Read `PROJECT_SCOPE.md` and `AGENTS.md` first; this file is a continuation note, not a substitute. **Delete or replace this file when Phase 0 is complete.** --- ## 1. Current verification status (verified 2026-10-03) | Check | Command | Status | Detail | |---|---|---|---| | Typecheck | `npm run typecheck` | **RED** | 3 errors, all in `tests/speechProtocol.mock.test.ts`: TS2322 line 111 (`ChildProcessByStdio` not assignable to `ChildProcessWithoutNullStreams`), TS18048 lines 115/116 (`server` possibly undefined) | | Lint | `npm run lint` | **RED** | 3 errors: `react-hooks/exhaustive-deps` rule definition not found ×2 (`src/pages/LiveMeeting.tsx` lines 51, 70 — pre-existing disable comments; plugin installed but NOT wired into config), and `preserve-caught-error` at test line ~120 (rethrow without `{ cause }`) | | Tests | `npm test` | **RED** | Unit: `src/store/useAppStore.test.ts` **6/6 pass** (with stderr noise — see §4.3). Integration: `tests/speechProtocol.mock.test.ts` **2/5 pass**; failures: close code got 1006 expected 1000; "rejects unsupported sample rates" and "reports malformed config as an error event" both time out waiting for the error event | | Rust | `cargo check --manifest-path src-tauri/Cargo.toml` | **RED** | All deps compile (MSVC toolchain works). Fails only in tauri-build: `` `src-tauri/icons/icon.ico` not found; required for generating a Windows Resource file during tauri-build`` | | Python | `.venv\Scripts\python.exe -m py_compile speech-engine/server.py` | **GREEN** | No changes to server.py planned in Phase 0 | --- ## 2. Completed work this session (done and verified) 1. **Removed `speech-engine/fresh_venv/`** — it was Python 3.14, incompatible with diart. The working venv is `speech-engine/.venv` (Python 3.12). 2. **Fixed pre-existing build breakage**: added `"noEmit": true` to `tsconfig.node.json`. `tsc -b` now works; the composite+noEmit combo was verified. 3. **Installed test/lint devDeps** (lockfile updated): vitest 3.2.7, eslint 10.12, @eslint/js ^10, typescript-eslint 8.71, @types/node ^26, **eslint-plugin-react-hooks ^7.1.1**. 4. **Created the harness**: - `eslint.config.js` — ESLint 10 flat config: `@eslint/js` recommended + `typescript-eslint` recommended, scoped to `**/*.{ts,tsx}` via `.map()`; ignores dist/node_modules/target/.venv. (react-hooks plugin NOT yet wired in.) - `vitest.config.ts`, `tsconfig.tests.json` (ES2022, no DOM lib, `types: ["node"]`; includes `tests/**/*.ts` + `vitest.config.ts`) referenced from root `tsconfig.json`. - `src/test/setup.ts` — in-memory localStorage stub for zustand persist under Node. **Not working as intended; see §4.3.** - `src/store/useAppStore.test.ts` — 6 unit tests (createMeeting, appendSegment add/replace, renameSpeaker trim/fallback, finishMeeting commit, setSettings merge). All pass. - `tests/speechProtocol.mock.test.ts` — 5 integration tests; spawns `.venv\Scripts\python.exe speech-engine/server.py --mock --port ` (skips if venv missing), drives it with Node's native WebSocket + fetch. 5. **package.json scripts added**: `typecheck` (`tsc -b`), `lint` (`eslint .`), `test` (`vitest run`), `test:watch` (`vitest`). 6. **Toolchain unblocked**: installed VS C++ BuildTools workload; MSVC 14.51.36231 at `C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools`. cargo check compiles all dependencies successfully — only the icon step fails. 7. **Verified `.venv` versions** (within requirements pins): fastapi 0.141.1, starlette **1.7.0**, uvicorn 0.54.0, pydantic 2.13.5. --- ## 3. Remaining work — do in this order ### Step 1 — Create the placeholder icon (unblocks cargo check) The archive shipped without `src-tauri/icons/`; git confirms it was never tracked (`git ls-files src-tauri/icons/*` is empty). `tauri.conf.json` has no explicit icon array, so tauri-build requires the default `icons/icon.ico` on Windows. ```powershell New-Item -ItemType Directory "src-tauri\icons" # Node writeFileSync does NOT create parent dirs node C:\Users\supersanta\AppData\Local\Temp\opencode\make-ico.cjs "D:\workspace\MeetVault\Wireframes\Desktop\MeetVault-Desktop-Demo\src-tauri\icons\icon.ico" ``` `make-ico.cjs` is a one-off generator in the temp dir (writes a 1150-byte solid-blue 16×16 32bpp ICO; takes output path as `argv[2]`). If it's gone, recreate it or hand-write any valid minimal ICO. **Commit the generated `icon.ico`** — the build requires it and it was never tracked. Then re-run `cargo check --manifest-path src-tauri/Cargo.toml` → expect GREEN. ### Step 2 — Settle ground truth for WS teardown before touching test assertions Facts verified by reading `speech-engine/server.py` (ws handler, lines ~318–415): - **No explicit `websocket.close()` anywhere in the handler.** Both error paths do `send_json({"type":"error", ...})` then `return`; starlette tears down when the handler returns. Observed close code is **1006** (abnormal, no close frame) — consistent with starlette 1.7.0 behavior. - Bad sample rate → error message exactly: `"MeetVault speech engine currently requires 16 kHz mono PCM."` then return. - Malformed JSON hits the generic `except Exception` (~line 391): tries `send_json({"type":"error","message":str(exc)})`, swallows send failures, then `session.close()`. - Yet in tests those error events **never arrive** (client times out). Hypothesis: starlette drops/loses the pre-close send during teardown. Unconfirmed. Do one of these to confirm before editing assertions: 1. Read `.venv\Lib\site-packages\starlette\websockets.py` (line 39+) for what happens on handler return, or 2. Manual probe: start `server.py --mock --port `, connect with a small Node WebSocket script, send (a) bad sample_rate config, (b) malformed JSON, (c) valid config + stop after finals; log every frame and the close code for each. Then align the three failing assertions to actual behavior. If 1006/no-close-frame is confirmed, accept it in tests **with a comment** documenting that an explicit close/ready handshake is Phase 3 protocol debt (AGENTS.md §9 P1 "speech readiness/protocol"). Do NOT change `server.py` behavior in Phase 0 — tests only. ### Step 3 — Fix `tests/speechProtocol.mock.test.ts` - Line ~6: import `ChildProcessByStdio` from `node:child_process` and `Readable` from `node:stream`. - Line ~104: `let server: ChildProcessByStdio | undefined;` — fixes TS2322 (line 111) and both TS18048s (lines 115/116). - Line ~120: attach `{ cause: error }` to the rethrown `Error(...)` in `beforeAll` (ESLint 10 `preserve-caught-error`). - Close-code assertion per Step 2 findings. ### Step 4 — Wire eslint-plugin-react-hooks into `eslint.config.js` Plugin is installed (`^7.1.1`) but absent from the config, so the pre-existing disable comments in `src/pages/LiveMeeting.tsx` (lines 51, 70) fail with "Definition for rule ... was not found". Check the v7 export shape first (`node_modules/eslint-plugin-react-hooks/package.json` + main entry — v7 is ESM), then add a flat-config entry registering the plugin with `rules-of-hooks: error` and `exhaustive-deps: warn` (or error). Keep the disable comments in LiveMeeting.tsx as-is; once the rule exists they become valid. ### Step 5 — Fix `src/test/setup.ts` localStorage stub Current state: gated on `typeof globalThis.localStorage === 'undefined'`. Under vitest workers the gate passes (Node 26's getter returns undefined while emitting an ExperimentalWarning) yet zustand persist still logs `[zustand persist middleware] Unable to update item 'meetvault-demo-store-v1', the given storage is currently unavailable.` per state update. **Root cause undiagnosed.** Planned fix: drop the gate — unconditionally `Object.defineProperty(globalThis, 'localStorage', { value: new MemoryStorage(), configurable: true, writable: true })`, then add a functional probe (setItem/getItem roundtrip) so a silent no-op is impossible. Verify success by running `npm test` and confirming BOTH the ExperimentalWarning lines AND all "storage is currently unavailable" messages are gone from output. ### Step 6 — Clean up stray artifacts - **Delete `vite.config.js` and `vite.config.d.ts`.** Verified: `vite.config.js` is a tsc-emitted duplicate of `vite.config.ts` (identical config values, 4-space indent). Vite resolves `.js` before `.ts`, so the stale file can shadow the real one. The real config is `vite.config.ts`; keep it. - **`.gitignore`**: add `*.tsbuildinfo` (3 stray files at repo root: tsconfig.app/node/tests.tsbuildinfo) and `src-tauri/gen/` (tauri-build generated). Keep `src-tauri/Cargo.lock` tracked — this is an application, lockfile should be committed. ### Step 7 — Full verification (all four must be green) ```powershell npm run typecheck npm run lint npm test cargo check --manifest-path src-tauri/Cargo.toml ``` Plus the mock-demo smoke path still works: `python speech-engine/server.py --mock` + `npm run desktop` (or at minimum `npm run dev`). AGENTS.md §15: do not claim completion without stating which checks actually ran. ### Step 8 — Documentation - **Create `docs/SMOKE_TEST.md`**: the manual Tauri acceptance walkthrough from AGENTS.md §7 as a checklist (mock mode first; mic permission, levels, transcript segments, speaker rename main↔overlay, pause/resume, finish → Meeting Details playback + transcript, exact placeholder text on backend buttons, restart persistence). - **Update `README.md`**: add the new commands (`typecheck`, `lint`, `test`) and normalize the Python version note to 3.12 (README still says 3.10/3.11 — documented drift per AGENTS.md §6.1; the working venv is 3.12). - **Update `AGENTS.md`** (at workspace root `D:\workspace\MeetVault\AGENTS.md`, tracked in git): §6.6 verification commands now include typecheck/lint/test; repo map gains `tests/`, `src/test/`, `eslint.config.js`, `vitest.config.ts`, `tsconfig.tests.json`; note the placeholder icon situation if relevant. - **Delete this HANDOFF.md** (or replace with a one-line "Phase 0 complete" note). --- ## 4. Verified ground truth & environment notes ### 4.1 Mock speech protocol (`speech-engine/server.py`) - `/health` → `{ok: true, mode: "mock", sample_rate: 16000}` (real mode reports `"real-capable"`). - WS endpoint `ws://127.0.0.1:/ws/live`; default port 8765; test picks a free port via net server on port 0. - Flow: accept → first text frame is JSON config (`model`, `compute`, `language`, `diarization`, `sample_rate`) → `ready` event. Mock ready payload: `{"type":"ready","engine":"mock-whisper","diarization":"mock-diart"}` (real mode: engine `"Whisper {model}"`). - Mock emits one **final** segment per 38400 samples (2.4 s of 16 kHz int16); speaker cycle `speaker_1 → speaker_2 → speaker_1 → speaker_3`. Test streams 5 s of silence in 3200-byte chunks and expects finals at ~[0.3–2.4] and ~[2.7–4.8]. - Client sends `{"type":"stop"}` to end; handler has no explicit close (see Step 2). ### 4.2 Toolchain / environment - Node **v26.10.0**: global `WebSocket` + `fetch` available natively (used by the integration test — no extra deps). - MSVC **14.51.36231** at `C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools` (VS BuildTools install from this session). cargo check works with it. - `.venv`: Python 3.12, fastapi 0.141.1, starlette 1.7.0, uvicorn 0.54.0, pydantic 2.13.5. - **PowerShell quirks** (this shell is Windows PowerShell 5.x): no `&&` separator — use separate commands or `;`; call operator `& "path"` for paths with spaces; run vitest via `node_modules\.bin\vitest.cmd`; esbuild binary lives at `node_modules\@esbuild\win32-x64\esbuild.exe`, NOT in `.bin`; piping stderr (`*>&1`) produces cosmetic NativeCommandError noise — the command still ran fine. - **ESLint 10**: flat config only; recommended set includes `preserve-caught-error` (rethrows must attach `{ cause }`). ### 4.3 Known test-output noise (to be fixed in Step 5) Unit tests pass but emit, per worker: `(node:...) ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.` and one `[zustand persist middleware] Unable to update item 'meetvault-demo-store-v1', the given storage is currently unavailable.` line per state update. Both should disappear once the setup.ts stub actually takes effect. ### 4.4 Working-tree changes that predate or accompany this session (keep, don't revert) - `src-tauri/Cargo.toml`: tauri feature list changed to `["protocol-asset"]` (from earlier work; needed for asset protocol). - Workspace-root `AGENTS.md` (`D:\workspace\MeetVault\AGENTS.md`): rewritten agent guide (82 → 944 lines); it is the current operating guide. --- ## 5. Constraints (from AGENTS.md / PROJECT_SCOPE.md — do not violate) - Keep mock-mode desktop operation working at every checkpoint; no half-finished architecture states. - Placeholder text must remain exactly: `Processing (The job). Please wait a moment` - `tsconfig.app.json` has `strict: true` — never weaken TypeScript strictness to silence an error. - No new **runtime** dependencies (test devDeps are fine and already installed). - Do not invent backend endpoints, credentials, auth behavior, or billing rules; keep `src/lib/backend.ts` a placeholder. - Do not edit generated/dependency dirs: `node_modules/`, `speech-engine/.venv/`, `dist/`, `src-tauri/target/`. - **No commits** — all changes stay uncommitted unless the user asks otherwise. ## 6. Git snapshot (as of handoff, branch `main`) Modified: `AGENTS.md` (workspace root), `package.json`, `package-lock.json`, `src-tauri/Cargo.toml`, `tsconfig.json` (+tests project reference), `tsconfig.node.json` (+noEmit) Untracked: `eslint.config.js`, `vitest.config.ts`, `tsconfig.tests.json`, `src/test/`, `tests/`, `src/store/useAppStore.test.ts`, `HANDOFF.md` (this file), stray `vite.config.js` + `vite.config.d.ts` (delete in Step 6), `*.tsbuildinfo` ×3, `src-tauri/Cargo.lock` (keep tracked), `src-tauri/gen/` (gitignore)