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

155 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```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 <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)
```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:<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)