Skip to content

v1.7.2

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Apr 20:57

Bugfix release — React #300 crash on close-all-tabs

The bug

Packaged viewmd crashed with React minified error #300 ("Rendered fewer hooks than expected") when the user closed all open markdown tabs. The crash was only visible in the signed/notarized DMG — dev mode only warns about hook-order violations, so the 10+ close-all E2E tests shipped in v1.7.1 silently missed it.

Root cause

LinksPanel called 4 useAppStore hooks, had an early return when !linkGraph || !selectedFile, then called 2 more subscriptions after the return. When the last tab closed, hook count flipped from 6 to 4 and React 19 production threw.

The E2E scenario that should have caught this had a broken selector — .segmented-btn:text-is("Links") targeted the Settings segmented controls, not the outline Links button (.outline-segment). The if (count > 0) guard silently skipped the click, so LinksPanel was never actually mounted during close-all. A green bar had been lying about coverage.

Fix

Moved the two trailing subscriptions above the early return so the hook count is invariant across renders.

Guardrails (why this class of bug won't recur)

  • ESLint gate — new flat config with eslint-plugin-react-hooks. The rules-of-hooks rule catches conditional hook calls statically; exhaustive-deps is also error. Verified to fire on the pre-fix LinksPanel.
  • CI workflow — runs lint + typecheck/build + unit tests on every PR and push to main. Blocks merges that reintroduce this class of bug.
  • LinksPanel regression unit test — verified to hard-fail on the pre-fix code via React's "Rendered more hooks than during the previous render" invariant.
  • Focused E2E regression test — dedicated scenario named React #300 regression: open Links panel then close all tabs.
  • E2E selector fix — removed the silent-skip guard; added toBeVisible assertions so future class renames fail loudly.
  • Console listeners in close-all E2E tests now also collect React hook warnings (not just errors).

Incidental fixes surfaced by the new exhaustive-deps rule

  • CollapsiblePreview.toggle was missing searchExpanded in its deps — a real stale-closure bug that allowed fold-state mutation during search-expanded mode (which the comment explicitly forbids).
  • CollapsiblePreview.links || {} fallback allocated a fresh object every render, invalidating the preambleHtml useMemo deps. Wrapped in its own useMemo.
  • FileTree.handleDrop, MarkdownPreview.handleExport{HTML,DOCX}, Sidebar search-results click: minor dep-array corrections.

Multi-AI review

Fix and follow-ups reviewed through Gestalt across two rounds: codex SHIP (both rounds), gemini SHIP (round 2), TDD Critic RED → GREEN, AHA Moment Detector surfaced two reusable lessons about silent E2E selector failures and React dev-mode vs production divergence.

Verification

  • npm run lint → 0 errors, 0 warnings
  • npm test → 240 passed (was 238 + 2 new)
  • npm run test:e2e → 55 passed (was 54 + 1 new)
  • npm run build → clean

Downloads

  • macOS (Apple Silicon)viewmd-1.7.2-arm64.dmg (signed with Developer ID, notarized by Apple). No Intel Mac build in this release.
  • Linuxviewmd-1.7.2.AppImage or markdown-viewer_1.7.2_amd64.deb.

Full changelog in CHANGELOG.md.