Files
supersanta 654033fba3 Add Phase 0 test harness (vitest + ESLint) and fix build config
- Add vitest unit tests for Zustand app store state transitions (6 tests)
- Add mock speech-engine WebSocket protocol integration test (spawns server.py --mock on a free port)
- Add ESLint 10 flat config with typescript-eslint; add typecheck/lint/test scripts to package.json
- Fix pre-existing tsc -b breakage via noEmit in tsconfig.node.json; add dedicated tsconfig.tests.json project for Node-side tests
- Track src-tauri/Cargo.lock for reproducible Rust builds; enable protocol-asset Tauri feature
- Ignore generated artifacts (*.tsbuildinfo, src-tauri/gen/, emitted vite.config.js/.d.ts); remove stale emitted vite config copies
- Update AGENTS.md agent guide (demo scope lock, roadmap, verification commands)
- Add HANDOFF.md documenting Phase 0 state and remaining steps for the next agent
2026-10-03 22:33:47 +07:00

14 KiB
Raw Permalink Blame History

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<null, Readable, Readable> 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 <free-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.

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 <free>, 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<null, Readable, Readable> | 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)

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:<port>/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)