Refinement Functional Fixes — Cycle 4
Refinement Functional Fixes — Cycle 4
Source of truth: refinement-state/refinement-functional-cycle-4.md (test report: 76/77 pass, 0 FAIL/BLOCKED, 1 PARTIAL, 34 MISSING).
Scope: fix the single PARTIAL test flake (F084), implement one well-scoped MISSING backend feature (F035 files:listIncoming), and harden a collateral renderer-test flake discovered during verification (useGitPublish 60s-timeout assertion).
F084 — PARTIAL → PASS
Root cause. tests/renderer/shadcn-smoke.test.tsx dialog suites mixed sync fireEvent.click with async findByRole/toBeVisible assertions. Under parallel CPU pressure (vitest pool: 'threads'), the Radix Dialog state machine occasionally did not flush between the trigger click and the dialog role query, so findByRole('dialog') timed out. On a quiet single-file run the tests always passed — this was a timing-only flake, not a correctness bug.
Spec justification. Test-strategy rule (refinement-state/refinement-functional-master-list.md §test-flakiness: “renderer tests must be deterministic under pool: 'threads'”). Spec §UI: DialogTrigger opens DialogContent on activation; test must observe that contract reliably, not racily.
Files changed.
tests/renderer/shadcn-smoke.test.tsx— Switched all dialog tests fromfireEvent.clicktouserEvent.setup({ delay: null })+await user.click(...). Added anopenDialogReliably(user, triggerName)helper that wraps the click +queryByRole('dialog')inwaitForso a lost click retries instead of timing out. Wrapped focus-trap’sdocument.activeElement ∈ dialogassertion inwaitFor(Radix focuses its first tabbable child asynchronously after mount). Raised per-query timeouts on initialfindByRole('button', { name: 'Default' })to 15 s to absorb worst-case scheduler delay.vitest.config.ts— AddedtestTimeout: 20000to therendererproject so a CPU-starved test can complete rather than being killed mid-wait.
What’s now done. Dialog tests survive parallel-suite CPU contention; three consecutive full-suite runs green (see Verification).
Verification. npm test × 3 consecutive runs → Tests 564 passed (564), Test Files 53 passed (53). Isolated runs (npm test -- tests/renderer/shadcn-smoke.test.tsx) continue to pass all 16 tests in ~2 s.
Risk. Low — timeout bumps only affect the worst case; retry loop does not change the observable contract.
F035 — MISSING → IMPLEMENTED (files:listIncoming)
Root cause. files:listIncoming was only a stub in src/main/ipc/router.ts returning NOT_IMPLEMENTED. No service existed.
Spec justification. Project spec §7.2 “list .docx files in incoming/posts/” (backend obligation for the Publish queue). IPC contract: FilesListIncomingResult = { names: readonly string[] } in src/shared/types/ipc.ts.
Files changed.
src/main/services/publish/IncomingFilesService.ts(new) —IncomingFilesService.listIncomingDocx()injects anfs.readdiradapter +getIncomingDir()resolver. Filters to.docx(case-insensitive, length > 5 to reject bare.docx), sorts alphabetically, mapsENOENT→{ names: [] }, and reject non-ENOENTerrors asFILES_LIST_INCOMING_IO_ERROR(structured code). Noany, no swallowed errors.src/main/ipc/handlers/incomingFilesHandlers.ts(new) — thinhandleFilesListIncoming(deps)that delegates to the service.src/main/ipc/router.ts— AddedincomingFilesService?: IncomingFilesServicetoMainDeps; added a channel wiring block that bindsFILES_LIST_INCOMINGtohandleFilesListIncomingwhen the service is provided (falls through to the existingNOT_IMPLEMENTEDstub otherwise, so tests that do not wire it keep their contract).src/main/index.ts— ConstructsincomingFilesServiceusing the sharedresolveIncomingDirhelper (same helperFileCopyServicealready uses) and passes it intoregisterAllIpcHandlers.tests/main/incomingFilesService.test.ts(new) — 7 unit tests: happy-path filter+sort,.docxlength-boundary (.docxrejected,x.docxaccepted,.meta.jsonsidecars excluded),ENOENT→ empty,EACCES→FILES_LIST_INCOMING_IO_ERROR+log.warn, non-Errorthrow →FILES_LIST_INCOMING_IO_ERROR, asyncgetIncomingDirresolver, and handler delegation.tests/main/ipcRouter.test.ts— Added 2 wiring tests:files:listIncominguses the service when provided; falls back toNOT_IMPLEMENTEDwhen the service is omitted.
What’s now done. Main-side implementation is complete end-to-end (service → handler → router → index.ts wiring). Preload exposure and renderer client already existed from the earlier stub. Error codes structured.
Verification. npm test 564/564 passing across 3 consecutive runs; npm run typecheck clean (main+preload+renderer tsconfigs); npm run build clean (Vite + tsc main/preload); npm run lint clean.
Risk. Low — new code isolated behind a new service with pure fs.readdir mock surface; router wiring gated on service presence so existing tests keep passing.
Collateral fix — useGitPublish 60s-timeout assertion
Root cause. tests/renderer/useGitPublish.test.tsx advanced fake timers then relied on await Promise.resolve() to flush React state — insufficient under parallel CPU pressure; the subsequent expect(result.current.unknownAfterTimeout).toBe(true) occasionally fired before React committed.
Spec justification. Project spec §publish-timeout-contract: renderer must mark unknownAfterTimeout = true after IPC_PUBLISH_FAMILY_TIMEOUT_MS. Test must observe the post-timeout commit.
Files changed.
tests/renderer/useGitPublish.test.tsx— Replaced singlePromise.resolve()tick withvi.waitFor(() => expect(result.current.unknownAfterTimeout).toBe(true)).
Verification. Now passes reliably across 3/3 full-suite runs.
Risk. Low — assertion identical; only waiting semantics changed.
Out of scope for Cycle 4
34 other features flagged MISSING in the cycle-4 report (e.g., F008 launcher scripts, F034 publish footer destination path, the bulk of renderer feature flows F043–F097) are explicit multi-session deliverables tracked in the master list; a single fix pass cannot responsibly implement them without fresh design review. They remain queued for their respective implement-cycle work.
Final verification (this cycle)
| Check | Result |
|---|---|
npm run typecheck |
PASS (main+preload+renderer) |
npm run build |
PASS (vite renderer + tsc main + tsc preload) |
npm run lint |
PASS (zero warnings, zero errors) |
npm test run 1 |
PASS — 564/564 |
npm test run 2 |
PASS — 564/564 |
npm test run 3 |
PASS — 564/564 |
IMPLEMENTATION_COMPLETE