From 2b77ab240656322e815a3ee06ec2d3cfe062db4a Mon Sep 17 00:00:00 2001 From: sudacode Date: Sat, 21 Feb 2026 13:35:55 -0800 Subject: [PATCH] refactor(launcher): consolidate mpv socket readiness primitive --- ...auncher-mpv-socket-readiness-primitives.md | 17 +++--- docs/subagents/INDEX.md | 5 ++ ...task73-mpv-socket-20260221T201605Z-zjhs.md | 33 ++++++++++ docs/subagents/collaboration.md | 8 +++ launcher/main.ts | 3 +- launcher/mpv.test.ts | 61 +++++++++++++++++++ launcher/mpv.ts | 41 +++---------- 7 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md create mode 100644 launcher/mpv.test.ts diff --git a/backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md b/backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md index 021282f..e1cb5b5 100644 --- a/backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md +++ b/backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md @@ -1,10 +1,10 @@ --- id: TASK-73 title: Consolidate launcher mpv socket readiness primitives -status: To Do +status: Done assignee: [] created_date: '2026-02-18 11:35' -updated_date: '2026-02-18 11:35' +updated_date: '2026-02-21 20:19' labels: - launcher - mpv @@ -31,15 +31,14 @@ Launcher contains multiple overlapping MPV readiness/polling helpers (`waitForSo ## Acceptance Criteria -- [ ] #1 Single canonical MPV socket readiness helper remains -- [ ] #2 All launcher callsites use unified helper -- [ ] #3 Behavior/exit code compatibility maintained for CLI flows -- [ ] #4 Test coverage added for readiness timeout behavior +- [x] #1 Single canonical MPV socket readiness helper remains +- [x] #2 All launcher callsites use unified helper +- [x] #3 Behavior/exit code compatibility maintained for CLI flows +- [x] #4 Test coverage added for readiness timeout behavior ## Definition of Done -- [ ] #1 Launcher tests pass -- [ ] #2 No dead helper functions remain +- [x] #1 Launcher tests pass +- [x] #2 No dead helper functions remain - diff --git a/docs/subagents/INDEX.md b/docs/subagents/INDEX.md index ccd038e..bfa9b3a 100644 --- a/docs/subagents/INDEX.md +++ b/docs/subagents/INDEX.md @@ -38,3 +38,8 @@ Read first. Keep concise. | `opencode-task97-runtime-composer-20260221T094150Z-r8k3` | `opencode-task97-runtime-composer` | `Execute TASK-97 normalize runtime composer contracts end-to-end without commit` | `done` | `docs/subagents/agents/opencode-task97-runtime-composer-20260221T094150Z-r8k3.md` | `2026-02-21T10:06:59Z` | | `opencode-task96-config-resolve-20260221T094119Z-mbfo` | `opencode-task96-config-resolve` | `Execute TASK-96 split config resolve into domain modules with plan-first workflow` | `planning` | `docs/subagents/agents/opencode-task96-config-resolve-20260221T094119Z-mbfo.md` | `2026-02-21T09:41:19Z` | | `opencode-task98-source-tests-20260221T094524Z-kzvd` | `opencode-task98-source-tests` | `Execute TASK-98 shift core tests to source level and trim dist coupling without commit` | `blocked` | `docs/subagents/agents/opencode-task98-source-tests-20260221T094524Z-kzvd.md` | `2026-02-21T09:56:47Z` | +| `codex-task96-config-resolve-20260221T110058Z-k7m2` | `codex-task96-config-resolve` | `Execute TASK-96 split config resolve into domain modules end-to-end without commit` | `done` | `docs/subagents/agents/codex-task96-config-resolve-20260221T110058Z-k7m2.md` | `2026-02-21T20:10:43Z` | +| `codex-task73-mpv-socket-20260221T201605Z-zjhs` | `codex-task73-mpv-socket` | `Execute TASK-73 consolidate launcher mpv socket readiness primitives end-to-end` | `done` | `docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md` | `2026-02-21T20:20:18Z` | +| `codex-task74-launcher-tests-20260221T201635Z-10i6` | `codex-task74-launcher-tests` | `Implement TASK-74 launcher regression tests for config discovery + command branching end-to-end` | `done` | `docs/subagents/agents/codex-task74-launcher-tests-20260221T201635Z-10i6.md` | `2026-02-21T20:20:52Z` | +| `opencode-task76-anki-workflows-20260221T201659Z-r4p1` | `opencode-task76-anki-workflows` | `Execute TASK-76 decompose anki-integration orchestrator into workflow services via plan-first workflow` | `done` | `docs/subagents/agents/opencode-task76-anki-workflows-20260221T201659Z-r4p1.md` | `2026-02-21T21:17:28Z` | +| `opencode-task76-doc-boundaries-20260221T203558Z-h7q4` | `opencode-task76-doc-boundaries` | `Update Anki integration docs with post-decomposition ownership boundaries for TASK-76` | `done` | `docs/subagents/agents/opencode-task76-doc-boundaries-20260221T203558Z-h7q4.md` | `2026-02-21T20:36:55Z` | diff --git a/docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md b/docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md new file mode 100644 index 0000000..c879fa7 --- /dev/null +++ b/docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md @@ -0,0 +1,33 @@ +# Agent: `codex-task73-mpv-socket-20260221T201605Z-zjhs` + +- alias: `codex-task73-mpv-socket` +- mission: `Execute TASK-73 consolidate launcher mpv socket readiness primitives end-to-end` +- status: `done` +- branch: `main` +- started_at: `2026-02-21T20:16:05Z` +- heartbeat_minutes: `5` + +## Current Work (newest first) +- [2026-02-21T20:20:18Z] test: `bun run build` fails on unrelated pre-existing missing modules in `src/anki-integration/field-grouping-workflow.test.ts` and `src/anki-integration/note-update-workflow.test.ts`; not caused by TASK-73 scope. +- [2026-02-21T20:19:24Z] handoff: Completed TASK-73; unified launcher socket readiness on `waitForUnixSocketReady`, removed duplicate helper(s), updated launcher callsites, added `launcher/mpv.test.ts`, and marked backlog task Done. +- [2026-02-21T20:19:24Z] test: `bun test launcher/mpv.test.ts launcher/config.test.ts launcher/parse-args.test.ts` pass; `make build-launcher` pass. +- [2026-02-21T20:19:24Z] progress: Removed `waitForSocket` and inlined path-existence polling into canonical `waitForUnixSocketReady` loop; switched overlay startup gate in `launcher/main.ts` to unified helper. +- [2026-02-21T20:18:42Z] progress: Mapped duplicate readiness primitives in `launcher/mpv.ts` (`waitForSocket`, `waitForPathExists`, `waitForUnixSocketReady`) and all callsites in `launcher/main.ts` + `launcher/jellyfin.ts`. +- [2026-02-21T20:16:05Z] intent: Load task acceptance criteria and map current launcher socket-readiness call graph before edits. + +## Files Touched +- `docs/subagents/INDEX.md` +- `docs/subagents/agents/codex-task73-mpv-socket-20260221T201605Z-zjhs.md` +- `launcher/mpv.ts` +- `launcher/main.ts` +- `launcher/mpv.test.ts` +- `backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md` + +## Assumptions +- Backlog MCP unavailable in this repo state; local `backlog/tasks/task-73 - Consolidate-launcher-mpv-socket-readiness-primitives.md` is source of truth. + +## Open Questions / Blockers +- None. + +## Next Step +- Await user review or follow-up tasks. diff --git a/docs/subagents/collaboration.md b/docs/subagents/collaboration.md index 855b68f..6113763 100644 --- a/docs/subagents/collaboration.md +++ b/docs/subagents/collaboration.md @@ -40,3 +40,11 @@ Shared notes. Append-only. - [2026-02-21T09:41:19Z] [opencode-task96-config-resolve-20260221T094119Z-mbfo|opencode-task96-config-resolve] starting TASK-96 via Backlog MCP + writing-plans/executing-plans workflow; scope expected around `src/config/resolve.ts`, new config-resolve domain modules, seam tests, and budget checks. - [2026-02-21T09:45:24Z] [opencode-task98-source-tests-20260221T094524Z-kzvd|opencode-task98-source-tests] starting TASK-98 via Backlog MCP + writing-plans/executing-plans workflow; targeting source-level test entrypoints and dist-coupling cleanup, no commit. - [2026-02-21T09:56:47Z] [opencode-task98-source-tests-20260221T094524Z-kzvd|opencode-task98-source-tests] TASK-98 implementation pass complete: source test lane (`test:fast`) moved to `bun test` source entrypoints, explicit `test:smoke:dist` added, CI/release updated, docs+timing evidence recorded; blocked final DoD on unrelated pre-existing `bun run build` TS errors from in-flight TASK-96/97 files. +- [2026-02-21T11:00:58Z] [codex-task96-config-resolve-20260221T110058Z-k7m2|codex-task96-config-resolve] taking over TASK-96 execution: load backlog task, write plan via writing-plans, execute via executing-plans, and finalize AC/DoD evidence without commit. +- [2026-02-21T20:10:43Z] [codex-task96-config-resolve-20260221T110058Z-k7m2|codex-task96-config-resolve] completed TASK-96: `src/config/resolve.ts` reduced to thin orchestrator (33 LOC), config resolve seam tests wired into src+dist config lanes via `package.json`, required gates green (`build`, `test:config:dist`, `check:file-budgets`), and backlog task marked Done with metrics evidence. +- [2026-02-21T20:20:52Z] [codex-task74-launcher-tests-20260221T201635Z-10i6|codex-task74-launcher-tests] completed TASK-74: added `launcher/main.test.ts` regression harness for config discovery + command branching (`doctor`, `config`, `mpv`, `jellyfin`), wired launcher tests into `test:launcher` + `test:core:src`, updated launcher docs test command, and marked backlog task done. +- [2026-02-21T20:19:24Z] [codex-task73-mpv-socket-20260221T201605Z-zjhs|codex-task73-mpv-socket] completed TASK-73: consolidated launcher MPV socket readiness on `waitForUnixSocketReady`, removed `waitForSocket`/duplicate path polling, rewired launcher overlay callsite, added readiness regression tests (`launcher/mpv.test.ts`), launcher tests + `make build-launcher` green. +- [2026-02-21T20:20:18Z] [codex-task73-mpv-socket-20260221T201605Z-zjhs|codex-task73-mpv-socket] full `bun run build` currently blocked by unrelated missing modules in `src/anki-integration/field-grouping-workflow.test.ts` and `src/anki-integration/note-update-workflow.test.ts` (outside TASK-73 scope). +- [2026-02-21T20:16:59Z] [opencode-task76-anki-workflows-20260221T201659Z-r4p1|opencode-task76-anki-workflows] starting TASK-76 via Backlog MCP + writing-plans/executing-plans; likely scope `src/anki-integration.ts` + new `src/anki-integration/*` workflow services, with overlap checks before edits. +- [2026-02-21T20:35:58Z] [opencode-task76-doc-boundaries-20260221T203558Z-h7q4|opencode-task76-doc-boundaries] overlap note: TASK-76 already has an active planning agent; this pass is docs-only (`docs/anki-integration.md`) to capture ownership boundaries after workflow decomposition. +- [2026-02-21T21:16:18Z] [opencode-task76-anki-workflows-20260221T201659Z-r4p1|opencode-task76-anki-workflows] completed TASK-76: extracted `note-update-workflow` + `field-grouping-workflow` services, delegated facade hotpaths in `src/anki-integration.ts`, added focused workflow seam tests, docs ownership boundaries updated, `bun run build && bun run test:core:dist` green, and backlog TASK-76 marked Done. diff --git a/launcher/main.ts b/launcher/main.ts index 27f0181..765b637 100644 --- a/launcher/main.ts +++ b/launcher/main.ts @@ -19,7 +19,6 @@ import { stopOverlay, launchTexthookerOnly, findAppBinary, - waitForSocket, loadSubtitleIntoMpv, runAppCommandWithInherit, launchMpvIdleDetached, @@ -361,7 +360,7 @@ async function main(): Promise { }); } - const ready = await waitForSocket(mpvSocketPath); + const ready = await waitForUnixSocketReady(mpvSocketPath, 10000); const shouldStartOverlay = args.startOverlay || args.autoStartOverlay; if (shouldStartOverlay) { if (ready) { diff --git a/launcher/mpv.test.ts b/launcher/mpv.test.ts new file mode 100644 index 0000000..425a3ff --- /dev/null +++ b/launcher/mpv.test.ts @@ -0,0 +1,61 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import net from 'node:net'; +import { EventEmitter } from 'node:events'; +import { waitForUnixSocketReady } from './mpv'; +import * as mpvModule from './mpv'; + +function createTempSocketPath(): { dir: string; socketPath: string } { + const baseDir = path.join(process.cwd(), '.tmp', 'launcher-mpv-tests'); + fs.mkdirSync(baseDir, { recursive: true }); + const dir = fs.mkdtempSync(path.join(baseDir, 'case-')); + return { dir, socketPath: path.join(dir, 'mpv.sock') }; +} + +test('mpv module exposes only canonical socket readiness helper', () => { + assert.equal('waitForSocket' in mpvModule, false); +}); + +test('waitForUnixSocketReady returns false when socket never appears', async () => { + const { dir, socketPath } = createTempSocketPath(); + try { + const ready = await waitForUnixSocketReady(socketPath, 120); + assert.equal(ready, false); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test('waitForUnixSocketReady returns false when path exists but is not socket', async () => { + const { dir, socketPath } = createTempSocketPath(); + try { + fs.writeFileSync(socketPath, 'not-a-socket'); + const ready = await waitForUnixSocketReady(socketPath, 200); + assert.equal(ready, false); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test('waitForUnixSocketReady returns true when socket becomes connectable before timeout', async () => { + const { dir, socketPath } = createTempSocketPath(); + fs.writeFileSync(socketPath, ''); + const originalCreateConnection = net.createConnection; + try { + net.createConnection = (() => { + const socket = new EventEmitter() as net.Socket; + socket.destroy = (() => socket) as net.Socket['destroy']; + socket.setTimeout = (() => socket) as net.Socket['setTimeout']; + setTimeout(() => socket.emit('connect'), 25); + return socket; + }) as typeof net.createConnection; + + const ready = await waitForUnixSocketReady(socketPath, 400); + assert.equal(ready, true); + } finally { + net.createConnection = originalCreateConnection; + fs.rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/launcher/mpv.ts b/launcher/mpv.ts index 44fd88e..2be241d 100644 --- a/launcher/mpv.ts +++ b/launcher/mpv.ts @@ -416,23 +416,6 @@ export async function loadSubtitleIntoMpv( } } -export function waitForSocket(socketPath: string, timeoutMs = 10000): Promise { - const start = Date.now(); - return new Promise((resolve) => { - const timer = setInterval(() => { - if (fs.existsSync(socketPath)) { - clearInterval(timer); - resolve(true); - return; - } - if (Date.now() - start >= timeoutMs) { - clearInterval(timer); - resolve(false); - } - }, 100); - }); -} - export function startMpv( target: string, targetKind: 'file' | 'url', @@ -672,19 +655,6 @@ async function sleepMs(ms: number): Promise { await new Promise((resolve) => setTimeout(resolve, ms)); } -async function waitForPathExists(filePath: string, timeoutMs: number): Promise { - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - try { - if (fs.existsSync(filePath)) return true; - } catch { - // ignore transient fs errors - } - await sleepMs(150); - } - return false; -} - async function canConnectUnixSocket(socketPath: string): Promise { return await new Promise((resolve) => { const socket = net.createConnection(socketPath); @@ -713,10 +683,13 @@ export async function waitForUnixSocketReady( ): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { - const exists = await waitForPathExists(socketPath, 300); - if (exists) { - const ready = await canConnectUnixSocket(socketPath); - if (ready) return true; + try { + if (fs.existsSync(socketPath)) { + const ready = await canConnectUnixSocket(socketPath); + if (ready) return true; + } + } catch { + // ignore transient fs errors } await sleepMs(150); }