Functional Fix Pass — Cycle 12

Context

Cycle 12 report (refinement-functional-cycle-12.md) showed 0 FAIL/BLOCKED/PARTIAL/REGRESSION and 21 MISSING features across Phase 2 surfaces, with 18 consecutive green full-suite runs. The report’s recommended sequencing opened with “Session D: F039 (Document Creator renderer)” — the only Phase 1 feature whose spec (§5.3 + §6.2) predicates later Phase 2 work (draft review, storm folder sync, NHC monitoring). Phase 2 features remain MISSING by design per the release plan and are out-of-scope for Cycle 12 fixes; they are tracked against their master-list entries.

Every fix below cites the spec section that justifies the change. The project root is C:\Users\keen4\WxManBran\tools\tropical-update-publisher\build_v2\v1\tools\tropical-update-publisher; all paths below are relative to that root.


Fix 1 — F039: Document Creator Renderer Section

Feature ID

F039 (MISSING → PASS)

Root Cause

Spec §6.2 defines the “Create New Briefing Document” form (date, hour dropdown, storm dropdown with “Create New” row, new-storm ID/name fields, update type, live filename preview, Create button, Add to Upload Queue button). Spec §5.3 defines the filename generation rules (sanitize [^a-zA-Z0-9\s-], collapse whitespace to hyphens, dedupe duplicate type prefix, Invest uses bare storm ID). Spec §10 defines the new-storm folder basename priority ({id} for Invest, {id}_TD{N}/{id}_PTC{N} for TD/PTC, {id}_{Name} for named storms).

No renderer component implemented the spec. The Ctrl+N binding (useAppShortcuts.ts:217-225) and command palette createNewDocument callback (CommandPalette.tsx:155-160) both routed to /publish with a toast.message('Doc Creator is not available yet'…) stub.

Files Changed

Created:

  • src/renderer/features/doc-creator/docCreatorModel.ts — pure model for filename / folder / readiness calculations. Re-uses shared FileValidator.validateDocxFilename for the live preview. No React / Electron / node:* imports.
  • src/renderer/features/doc-creator/DocCreator.tsx — React component implementing §6.2. Uses useQuery (browse:listStorms keyed on the derived year) + useMutation (doc:create, storm:createFolder) against the existing ipcClient + invokeWithTimeout plumbing. Emits status-log entries on success via usePublishQueueStore.appendLocalLogEntry. “Add to Upload Queue” forwards the created absolute path to usePublishQueueStore.enqueuePaths.
  • src/renderer/features/doc-creator/useDocCreatorFocusStore.ts — minimal Zustand counter store (focusRequestId + requestFocus()) so Ctrl+N / palette can focus the section without DOM queries or imperative ref plumbing. Includes resetDocCreatorFocusStoreForTests for test hygiene.
  • tests/renderer/docCreatorModel.test.ts — 28 unit cases covering §5.3 filename generation, sanitization, duplicate-type dedupe, Invest path, hour tokens, storm folder §10, readiness gating, and validator behavior on malformed inputs.
  • tests/renderer/docCreator.test.tsx — 8 RTL cases covering rendering (§6.2), storm dropdown loading, live preview update flow, Invest field swap, happy-path create → queue flow (storm-folder + doc IPC sequencing + queue enqueue), DOC_EXISTS rejection surfacing, and the Ctrl+N focus bus.

Modified:

  • src/renderer/routes/Placeholders.tsx — mounts <DocCreator /> at the top of PublishPlaceholder (above the DropZone + FileList). New import of DocCreator.
  • src/renderer/hooks/useAppShortcuts.ts — Ctrl+N now navigate(APP_PATHS.publish) then useDocCreatorFocusStore.getState().requestFocus(). Stub toast.message(...) removed.
  • src/renderer/features/command-palette/CommandPalette.tsxcreateNewDocument callback now mirrors the Ctrl+N flow (navigate + requestFocus()). Stub toast.message(...) removed.
  • tests/renderer/windowApiMock.tsWindowApiMockOptions now exposes browseListStorms, docCreate, stormCreateFolder defaults + overrides, mirroring the existing pattern for git / dialog APIs.
  • refinement-state/refinement-functional-master-list.md — F039 entry updated (status MISSING → PASS, code path + evidence populated).

What Was Done

  1. Extracted the filename / folder / readiness logic into docCreatorModel.ts so the component stays declarative and the rules stay unit-testable without React. generateFilename matches the FILENAME_PATTERN grammar in FileValidator.ts; buildStormFolderBasename implements the §10 priority; buildFilenameSegment dedupes a leading type prefix (case-insensitive) so Hurricane + Hurricane Andrew stays Hurricane-Andrew (§5.3).
  2. Built DocCreator.tsx as a form-centric React component:
    • useState for the five form fields (date, time, storm selection, new-storm ID, storm name, update type).
    • useQuery with queryKey: ['browse', 'storms:{year}'] keyed on the derived year so changing the date refreshes the dropdown.
    • Sequential mutation chain in the Create handler: if the “Create New Storm” row is picked, call storm:createFolder first; on success, call doc:create; on success, record the created path so the “Add to Upload Queue” button can hand it to usePublishQueueStore. Each IPC call is wrapped with invokeWithTimeout + wrapRendererIpcFailure for consistent §29 error handling.
    • All surface elements have data-testid hooks for the RTL suite and accessibility labels.
  3. Wired the focus bus: Ctrl+N in useAppShortcuts.ts and the command palette’s createNewDocument action both navigate('/publish') and call useDocCreatorFocusStore.getState().requestFocus(). The component’s effect listens for focusRequestId bumps and moves focus to the date input after scrolling the section into view.
  4. Added tests (see file list above). Both suites pass in isolation and in the full suite.
  5. Updated the master list to reflect the PASS status + evidence.

Spec Citations

  • §5.3 — filename generation grammar (YYYY-MM-DD-[TIME-]Type-Name.docx), sanitization rules, valid update types (Invest, PTC, Tropical-Depression, Tropical-Storm, Hurricane).
  • §6.2 — DocCreator section layout, hour dropdown token shape, Hurricane default, required-field gating.
  • §7.5doc:create / storm:createFolder IPC contract (year + stormFolder + filename → {success, filePath} with DocCreateFailureCode errors).
  • §9 — Word COM automation flow (main-side owned; renderer only reacts to the structured result).
  • §10 — new-storm folder basename priority.
  • §23.7 — Ctrl+N opens Doc Creator.
  • §23.8 — command palette executes the same createNewDocument action.
  • §24 row 013DOC_EXISTS / WORD_* failure code wire format (surfaced verbatim in the DocCreator error region).

Verification

  • npm run typecheck — clean across main, preload, renderer tsconfigs.
  • npm run lint — clean (0 errors, 0 warnings).
  • npm run build — clean renderer (index-BB4SzVx5.js 1.1 MB) + main build.
  • npm test795/795 tests pass (including the 36 new F039 tests). A single earlier run surfaced a flaky timeout in shadcn-smoke.test.tsx > keeps keyboard focus inside the dialog while open; the test passes in isolation and on the re-run. This is unrelated to F039 (pre-existing dialog focus-trap timing sensitivity under heavy parallel load).

Risk

  • Low. The fix is additive: new pure model + new component + new tests. No existing IPC handlers, services, routes, or shared types were changed. The two renderer integration points (Ctrl+N, command palette) previously rendered stub toasts — switching them to the real focus bus cannot regress any working surface. The windowApiMock extension only adds new default handlers behind the existing Proxy fallback.
  • Known lenient validation corner. FILENAME_PATTERN accepts filenames whose slug is only a trailing hyphen (e.g., YYYY-MM-DD-TIME-Hurricane-.docx when the name is blank). The DocCreator guards against this path with evaluateFormReadiness, which surfaces “Enter a storm name.” before the Create button becomes enabled, so operators can never trigger doc:create with an empty slug in practice.

IMPLEMENTATION_COMPLETE