Refinement Audit — Cycle 1
Refinement Audit — Cycle 1
Summary
The project is generally in solid shape: the main-process services are typed, most IPC paths have tests, and the renderer is using a reasonably disciplined bridge/query setup. The main issues are edge-case contract gaps where the UI depends on IPC channels that were never wired, plus a couple of desktop-specific behaviors that break on real operator workflows.
Critical Findings
C1: Publish recovery banner calls two IPC channels that are still stubbed
- File:
src/main/ipc/router.ts:229 - Issue: The renderer’s post-timeout recovery flow depends on
config:getPublicandshell:openPath, butregisterAllIpcHandlersnever installs real handlers for either channel, so both fall through to the genericNOT_IMPLEMENTEDstub. - Evidence:
PublishUnknownStateBannerinvokesgetTropicalApi().configGetPublic()and thengetTropicalApi().shellOpenPath(...)atsrc/renderer/components/PublishUnknownStateBanner.tsx:40andsrc/renderer/components/PublishUnknownStateBanner.tsx:45, whilerouter.tsjumps from the dialog/first-run/settings branches to later channel families and finally stubs unmatched channels atsrc/main/ipc/router.ts:361. - Fix: Add typed handlers for
config:getPublicandshell:openPath, wire them inregisterAllIpcHandlers, and cover both channels in router tests so the banner’s “Open repo folder” path is executable in production.
C2: File copy and publish disagree about the configured incoming posts folder
- File:
src/main/index.ts:84 - Issue: Copied documents always go to
PathService’s hard-coded defaultincoming/posts, whilePublishServicestages files from the configurableconfig.incomingPostsPath. If an operator changes that setting, copied files land in one folder and publish looks in another. - Evidence:
createFileCopyServiceis wired withgetIncomingDir: (): string => pathService.getFullIncomingPath()atsrc/main/index.ts:86, but publish derives staging paths fromconfig.incomingPostsPathinsrc/main/services/git/PublishService.ts:150andsrc/main/services/git/PublishService.ts:380. - Fix: Resolve the incoming copy destination from the current config, not from the
PathServicedefault, and add coverage proving copy + publish stay aligned whenincomingPostsPathis customized.
Important Findings
I1: Storm folder renames fail when only the folder casing needs to change
- File:
src/main/services/storms/StormFolderService.ts:538 - Issue:
updateFolderNameandsyncAllFolderNamesstat the target path before renaming and reject withSTORM_FOLDER_TARGET_EXISTSwhenever it exists. On case-insensitive filesystems, a rename like09L_milton→09L_Miltonresolves to the same on-disk directory and is incorrectly blocked. - Evidence:
updateFolderNamerejects as soon asdeps.fs.stat(destAbs)succeeds atsrc/main/services/storms/StormFolderService.ts:538, and the same pattern appears in bulk sync atsrc/main/services/storms/StormFolderService.ts:672. - Fix: Detect case-only renames on case-insensitive platforms, treat the source directory as the same target instead of a collision, and add regression coverage for both single-folder and sync flows.
I2: Second launch does not refocus the first-run window
- File:
src/main/app/lifecycle.ts:43 - Issue: The single-instance handler only asks
mainWindowFactory()for a window, so during the first-run gate, when the main window does not exist yet, a second launch exits without focusing the already-open first-run window. - Evidence:
enforceSingleInstanceresolves onlyconst win = mainWindowFactory()and returns early when it is null atsrc/main/app/lifecycle.ts:43-44;src/main/index.tsonly assignsmainWindowReffor the post-gate main window, not the first-run shell. - Fix: Fall back to any existing non-destroyed
BrowserWindowwhen the factory returns null, and update the single-instance tests to cover the first-run-window case.
Minor Findings
M1: Router tests never assert the renderer-visible config/shell utility channels
- File:
tests/main/ipcRouter.test.ts:78 - Issue: The router suite verifies a generic stub path and several feature families, but it never checks that
config:getPublicandshell:openPathare wired when their dependencies are present. That gap let a user-visible recovery action ship against stubs. - Fix: Add explicit router tests for both channels so future refactors cannot silently regress them back to
NOT_IMPLEMENTED.
Design Improvements
D1: Give renderer-facing utility IPC calls dedicated handlers instead of relying on router special cases
- Scope:
src/main/ipc/router.ts, new utility handler module(s),tests/main/ipcRouter.test.ts - Current: The router contains ad hoc inline branches for a handful of channels, while other renderer-visible utility channels are easy to forget and silently drop to the stub handler.
- Proposed: Extract small typed handlers for utility channels such as public config snapshots and shell path opening, then wire and test them as first-class handler units.
- Benefit: Reduces stub drift, keeps the router easier to audit, and makes renderer-facing contracts harder to break accidentally.
Regressions
None detected.
TOTAL_FINDINGS: 6