Skip to content

feat(rpc): /action/enqueueFile + chained reimport E2E#248

Merged
pasrom merged 2 commits into
mainfrom
feat/rpc-enqueue-file
May 12, 2026
Merged

feat(rpc): /action/enqueueFile + chained reimport E2E#248
pasrom merged 2 commits into
mainfrom
feat/rpc-enqueue-file

Conversation

@pasrom
Copy link
Copy Markdown
Owner

@pasrom pasrom commented May 12, 2026

Summary

  • New POST /action/enqueueFile RPC endpoint exposes the menu's "Open from Recording" entry point. Body {"path":"..."}, returns 200/400.
  • e2e-app.sh --reimport-recorded chains a record-only meeting with a re-import: live capture writes a WAV, then that WAV gets POSTed back into the pipeline. Asserts transcript contains the fixture keyword (meeting, case-insensitive).
  • New 3rd step in e2e-app.yml after the existing transcript + record-only lanes (~1 min wall extra on Mini, --no-build reuses the bundle).
  • Shared _poll_for_new_lastjob_terminal helper eliminates ~50 lines of polling duplication between run_one_meeting and run_one_reimport. Single source of truth for the IFS='|' framing rationale.

Commits

  1. feat(rpc): add POST /action/enqueueFile endpoint — Swift code + 3 integration tests (poll loop on the observable to harden against sanitizer-lane flake).
  2. test(e2e-app): chain record-only with re-import + transcript assertion — driver + workflow + shared poll helper + named RECORDER_FINALIZE_WAIT_S constant.

Why

Closes E2E coverage gap #6 (file-import path is only fixture-tested via xctest, never live) AND ratchets up the toothless > 100 bytes transcript check to a content-based assertion. The chain catches three failure classes the single lanes miss:

Failure Transcript lane Record-only lane Reimport lane
Recorder writes malformed WAV doesn't see it (uses fixture directly) passes (size > 64 KB suffices) FAILS at AudioMixer decode
AudioMixer.loadAudioAsFloat32 regresses doesn't exercise it doesn't exercise it FAILS at decode
Live capture is silent / garbage doesn't exercise recorder passes (size > 64 KB suffices) FAILS at content match

Implementation notes

  • RPC closure validates FileManager.default.fileExists(atPath: url.path) on the AppState side; RPC layer just translates false → 400. Keeps the transport thin.
  • recordOnly=true stays on during the reimport phase — only affects WatchLoop.enqueueRecording, not AppState.enqueueFiles. No restart needed.
  • Content keyword is meeting (case-insensitive). The fixture has Projekt Meeting, also Meeting in the simulator window title; robust to Parakeet/WhisperKit casing differences.

Test plan

  • swift test --filter DebugRPCServerIntegrationTests — 12 tests pass incl. 3 new
  • ./scripts/lint.sh — 0 violations
  • bash -n scripts/e2e-app.sh — syntax OK
  • After merge: e2e-app.yml push-trigger fires all 3 lanes:
  • Nightly cron green

@github-actions github-actions Bot added the enhancement New feature or request label May 12, 2026
Base automatically changed from test/record-only-live-e2e to main May 12, 2026 17:21
@pasrom pasrom force-pushed the feat/rpc-enqueue-file branch from 515b90f to 9f8a604 Compare May 12, 2026 17:22
pasrom added 2 commits May 12, 2026 19:24
Exposes the menu's "Open from Recording" entry point over RPC. Body is
`{"path":"..."}`; returns 200 on success, 400 on missing/empty path or
file that doesn't exist. Wires through to the same AppState.enqueueFiles
the NSOpenPanel flow uses, so the import code path is identical.

Motivation: e2e-app.sh wants to feed a recorded WAV back through the
pipeline for a content-based transcript assertion. The fixture-based
xctest E2Es never exercise the file-import path with live-recorded audio.

Three integration tests cover missing path → 400, nonexistent path → 400,
and valid path → 200 with closure invocation. Existence check lives on
the closure side (AppState) so the RPC layer stays a thin transport.
Adds --reimport-recorded mode that runs a record-only meeting (live
capture → mix.wav), then POSTs that WAV to /action/enqueueFile and
asserts the resulting transcript contains the fixture's spoken keyword
("meeting", case-insensitive).

Closes the gap between the existing transcript lane (uses fixture WAV
directly, doesn't exercise recorder output) and the record-only lane
(only checks files exist, doesn't validate content). Catches three
failure classes neither lane sees alone:

  - Recorder writes a malformed WAV → record-only would still pass on
    file size; reimport fails because AudioMixer can't decode it.
  - AudioMixer 3-tier file-load fallback regresses → currently only
    fixture-tested via xctest, never live.
  - Live capture is silent/garbled → transcript lane's fixture-direct
    path never touches the recorder, so a routing regression on
    BlackHole / mic / default-output goes undetected.

recordOnly toggle stays on during the reimport phase but only affects
WatchLoop.enqueueRecording — AppState.enqueueFiles bypasses it.

Wires up a third step in e2e-app.yml after the existing two lanes,
sharing the deployed bundle via --no-build. Adds ~1 min wall on Mini.
@pasrom pasrom force-pushed the feat/rpc-enqueue-file branch from 9f8a604 to 38f148b Compare May 12, 2026 17:24
@pasrom pasrom merged commit 4870bf9 into main May 12, 2026
10 checks passed
@pasrom pasrom deleted the feat/rpc-enqueue-file branch May 12, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant