Refinement Functional Fixes — Cycle 6

Source of truth: refinement-state/refinement-functional-cycle-6.md.

Cycle 6 test report totals (pre-fix):

PASS FAIL BLOCKED PARTIAL REGRESSION MISSING UNTESTED Health
79 0 0 0 0 32 0 100.0%

No FAIL, BLOCKED, PARTIAL, or REGRESSION features exist in cycle 6 — every tested feature is PASS and three consecutive full-suite runs are 567/567 (run 1), 567/567 (run 2), and 566/567 (run 3; one known parallel-load flake on shadcn-smoke > closes the dialog when Done is activated, isolated rerun 16/16 PASS, within the existing ≤33% flake-with-isolated-clean criterion).

npm run typecheck, npm run build, and npm run lint each exited 0 in cycle 6 evidence (refinement-state/functional-cycle-6-artifacts/*.log).

Scope of this fix cycle therefore reduces to the MISSING inventory. Following the established cadence —

  • Cycle 4 (refinement-functional-fixes-4.md) landed F035 files:listIncoming as its single well-scoped MISSING pickup.
  • Cycle 5 (refinement-functional-fixes-5.md) landed F008 Windows launcher scripts (launcher.bat, launcher.vbs, create-shortcut.ps1).

— cycle 6 implements exactly one low-risk MISSING item: F090 Designed Empty States (spec §23.9). The remaining 31 MISSING items continue to be queued as multi-session deliverables (renderer feature work behind new stores, Phase 2 monitor/AI/email/dashboard scope in spec §§20, 23, 24, and binary assets in F009). F090 is chosen because it (a) touches only the renderer presentation layer, (b) has no IPC, service, database, or new npm dependency surface, (c) has exact copy dictated by spec §23.9 removing interpretation risk, and (d) unblocks the future wiring of empty-state CTAs when F030–F034, F045, and F093 land.


F090 — MISSING → PASS (Designed Empty States for Drafts, Storms, History)

Root cause. Spec §23.9 requires every primary view to render a designed empty state with title, description, and “Quick onboarding links”. Before this cycle, DraftsPlaceholder, StormsPlaceholder, HistoryPlaceholder (and MonitorPlaceholder) in src/renderer/routes/Placeholders.tsx rendered through a generic PlaceholderPage helper that only produced a heading + one-line description with no icon, no CTA, and no onboarding affordances. The master-list code-path field captured the gap precisely: “MISSING/partial — current routes use plain placeholder text and the Publish view shows only scaffold controls.” The Publish route already satisfies §23.9 through DropZone (spec §8A), so the fix surface is the three remaining routes.

Spec justification.

  • §23.9 (Empty States), verbatim copy:
    • “Drafts (no pending): No drafts awaiting review. Monitor is {state}.
    • “Storms (no active): No active storms in the Atlantic basin right now.
    • “History (new season): No publishes this season yet.
    • “Quick onboarding links in each empty state”.
  • F090 master-list entry: “Show polished empty states for Publish, Drafts, Storms, and History instead of generic placeholder text.”

Files changed / added.

  • src/renderer/components/EmptyState.tsx (new, 98 LOC) — reusable presentation primitive. Props: icon: LucideIcon, title: string, description: string, optional cta: { label, onClick }, optional onboardingLinks: { to: AppPath, label: string }[], optional children for future insert-here wiring, optional testId for integration tests. Renders: framed icon chip (animated via framer-motion with useReducedMotioncoerceReducedMotionFlag ternary that short-circuits to opacity:1, scale:1 when reduced-motion is on), semantic <section role="region" aria-labelledby={…}> wrapper, <h1> title, description <p>, optional CTA <Button>, and a labelled <nav aria-label="Onboarding links"> list of <Link>s targeting AppPath values from navConfig.ts. Links have min-h-11 (WCAG 2.5.5 touch target) and focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 (mirrors the existing primary-nav focus contract asserted in appShellRouting.test.tsx:119-121). The motion wrapper deliberately animates only the icon chip, not the whole card — wrapping the card in an initial-opacity motion section would break toBeVisible() assertions in jsdom since framer-motion has no animation loop under happy-dom / jsdom and leaves the element at opacity: 0. This is documented with an inline comment in the source.
  • src/renderer/routes/Placeholders.tsx (edited) — three placeholders rewired:
    • DraftsPlaceholder now renders <EmptyState icon={FileText} title="Drafts" description={No drafts awaiting review. Monitor is ${monitorState}.} …/> with a local const monitorState = 'idle' slot until F093 (NHC monitor) lands. The {state} placeholder from §23.9 is interpolated live, not hard-coded, so the future monitor store can replace the literal with its reactive state without changing the spec copy.
    • StormsPlaceholder renders <EmptyState icon={CloudLightning} title="Storms" description="No active storms in the Atlantic basin right now." …/>.
    • HistoryPlaceholder renders <EmptyState icon={HistoryIcon} title="History" description="No publishes this season yet." …/>.
    • Each includes two route-scoped onboarding links (Drafts → Monitor + Publish; Storms → Monitor + History; History → Publish + Storms).
    • The generic PlaceholderPage helper is retained for MonitorPlaceholder (§23.9 does not enumerate Monitor, so the existing scaffold copy stays; converting it would exceed spec scope).
  • tests/renderer/emptyState.test.tsx (new, 7 tests) — covers the primitive (4 tests: renders title/description/icon region, CTA click, onboarding link targets, absent-CTA/nav when unspecified) and the three route consumers (3 tests: assert §23.9 copy verbatim per route, plus data-testid + onboarding nav presence). Uses <MemoryRouter> so the empty-state <Link>s resolve without depending on the app shell HashRouter.
  • refinement-state/refinement-functional-master-list.md — F090 header updated from [MISSING] Designed Empty States to Designed Empty States; Code path field now cites the new component + three consumer functions; Status transitioned MISSING → PASS; Evidence cites tests/renderer/emptyState.test.tsx and this fix log.

Ambiguities noted (per prompt rule 6).

  • Spec §23.9 writes “Monitor is {state}” as a template string without enumerating which literal values the {state} slot may take. A reasonable interpretation is one of the NHC monitor’s internal states (idle, polling, offline, paused). F093 is MISSING, so there is no live monitor state to read today. I chose the literal 'idle' for the no-data default because (a) it is the most neutral word for “the monitor isn’t doing anything interesting right now — which is consistent with the drafts list being empty”, and (b) it keeps the copy truthful without asserting a polling cadence that does not yet exist. The value lives in a const monitorState at the top of DraftsPlaceholder so F093’s author can swap it for a live subscription in one line. Inline code comment in Placeholders.tsx notes the interpretation + future-wiring path.
  • Spec §23.9 says “Quick onboarding links in each empty state” without specifying which routes each empty state should link to. I chose two intent-aligned destinations per route (Drafts → Monitor/Publish — i.e., both sources of work to review; Storms → Monitor/History — both places to look when no active storm exists; History → Publish/Storms — both entrypoints into this-season activity). Each pair passes through the AppPath union from navConfig.ts so a future enum edit would surface as a compile error instead of a dead link.

What’s now done.

  • Empty-state parity across the three no-data views the spec enumerates (Drafts, Storms, History). Publish already had its illustration-plus-CTA empty state via DropZone.
  • Reusable primitive so F093/F100/F108 et al. can adopt the same presentation without rewriting the card chrome.
  • Accessibility contract: each empty state exposes a region landmark labelled by its <h1>, and the onboarding-link group is a navigation landmark labelled “Onboarding links” — both discoverable via getByRole(…, { name: … }) as the existing route tests already use for headings.

Verification.

  • npm run typecheck → exit 0 (main + preload + renderer tsconfigs, post-edit).
  • npm run lint → exit 0 (eslint on .ts/.tsx, post-edit).
  • npm run build → exit 0 (Vite renderer bundle rebuilt to dist/renderer/assets/index-zjArCC28.js + CSS index-DOffPx65.css; tsc main + preload clean).
  • npm test (post-fix) → 574/574 tests PASS across 55 test files (baseline was 567/567 across 54 files; delta = +1 test file, +7 tests, all new in tests/renderer/emptyState.test.tsx, 0 regressions in any pre-existing test).
  • Isolated run of the new spec: npx vitest run tests/renderer/emptyState.test.tsx → 7/7 PASS in 907ms (see terminal transcript during this cycle).
  • Existing appShellRouting.test.tsx which asserts getByRole('heading', { name: 'Drafts'|'Storms'|'History' }) on each route continues to PASS because the EmptyState primitive renders the title inside an <h1> with the exact text supplied, matching the getByRole('heading') query semantics these tests depend on.
  • Route dependencies F016/F017/F018/F021 all remain PASS — the EmptyState rewrite is additive (new file + call-site swap inside existing exported functions) and preserves every public export signature the shell and the routing tests consume.

Risk. Low. Purely renderer-layer presentation work; no IPC, no service, no DB, no new npm dependency, no preload surface change. The only behavior change end-users would see is polished copy + iconography on three routes they currently see a plain text line on. The MonitorPlaceholder is intentionally left on the legacy PlaceholderPage because §23.9 does not enumerate Monitor; converting it would exceed spec scope. Future F093 can swap it onto EmptyState trivially when the monitor copy is defined.


Out of scope for Cycle 6

The remaining 31 MISSING features in the cycle-6 report are queued for dedicated implement-cycle work (rationale unchanged from cycle 5 with one line removed for F090):

Feature ID(s) Gap Why deferred
F009 assets/ directory with WMB_Logo.png, icon.ico, asset docs Binary assets (PNG/ICO) cannot be generated from text-only fix pass. create-shortcut.ps1 already handles the absent-icon case gracefully.
F030–F034 Publish queue rows, commit-message input, per-file YouTube UI, status log, destination footer Substantial React renderer work with new Zustand stores, TanStack Query wiring, accessibility review (spec §23.5). Cycle-4/5 fix logs both flagged these as multi-session; that reasoning still holds.
F039 Document Creator renderer section (spec §6.2) Full form UI: live filename preview, storm dropdown integration, date/time pickers, IPC wiring.
F045 Quick Browse renderer section (spec §6.3) Backend services + IPC already PASS (F046–F049); renderer UI is the gap.
F050 Header logo + git-status indicator Depends on F009 assets and a new widget in AppShell.
F087–F089 Command palette, keyboard shortcut wiring, context menus (spec §§23.7, 23.8, 23.12) Each requires new renderer infrastructure (react-hotkeys-hook, Radix ContextMenu, command registry) outside single-cycle scope.
F093–F111 Phase 2: NHC monitor, AI drafter, email review, draft queue, rich dashboard, historical views, notifications, QoL, audit/compliance (spec §§20, 23, 24) Explicitly multi-session per spec §20 + §24; every prior cycle flagged these as deferred.

None of these gaps block any currently PASSing feature. The tool’s core publish pipeline (F001–F007, F010–F029, F035–F038, F040–F044, F046–F049, F051–F086, F091–F092) plus the now-landed F008 (cycle 5) and F090 (this cycle) remain fully functional.


Final verification (this cycle)

Check Result
npm run typecheck PASS (main+preload+renderer)
npm run lint PASS (zero warnings, zero errors)
npm run build PASS (vite renderer + tsc main + tsc preload)
npm test (post-fix) PASS — 574/574 tests across 55 test files
F090 delta MISSING → PASS (EmptyState primitive + Drafts/Storms/History consumers + 7 new tests)
Pre-existing tests 567/567 → 567/567 unchanged (0 regressions); +7 new F090 tests
Master-list audit PASS 80, MISSING 31, FAIL/BLOCKED/PARTIAL/UNTESTED 0; 80+31=111 ✓
Other MISSING inventory Unchanged (31 remaining; queued for dedicated implement cycles)

IMPLEMENTATION_COMPLETE