Release v1.7.1 — QoL bug-fix batch - #855
Conversation
) (#845) gray-matter bundles js-yaml@3.x which carries GHSA-h67p-54hq-rp68. gray-matter was used in exactly one file (src/main/google/sync.ts) for frontmatter parse + stringify during legacy migration and syncAll. Replace with two small local helpers (parseFrontmatter / stringifyFrontmatter) that use the top-level js-yaml (^4.x) already in the dependency graph, using named imports compatible with js-yaml 5.x (PR #819 removes the default export). Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The two toHaveClass assertions in 'theme toggle changes dark class' used a 2_000 ms timeout while the suite norm is 5_000 ms. The theme flip routes through an IPC settings round-trip (appearance.mode persistence) before the renderer applies the dark class, which occasionally exceeds 2s under full-suite load. Aligns both assertions with the suite-wide 5s budget. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…open (#835) When the find bar was already visible, pressing Cmd+F again called setIsFindOpen(true) which was a React no-op — the isOpen prop didn't change so the focus/select useEffect in FindBar never re-fired. Now, Cmd+F dispatches a 'find:focus' CustomEvent when the bar is open; FindBar listens for it and immediately focuses + selects the input, letting the user type a new search term without reaching for the mouse. Closes #827 Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rror wrapping (#838) * chore(tools): fix provenance race, dedup ToolProvenance, wrap plain throws (#790) - Fix documentId read-before-store-update race in executeCreateAndOpenFile: derive newDocumentId from fullPath directly (same derivation as executeOpenFile) instead of reading editorStore before its async update settles, preventing waitForEditorDocumentContent from polling on a stale id and dropping the annotation. - Extract ToolProvenance interface into src/renderer/lib/tools/provenanceTypes.ts to eliminate the byte-identical copy in file.ts (circular import previously prevented importing from index.ts). - Wrap plain-object throws in unwrapSaveFileResult / unwrapReadFileResult with Object.assign(new Error(...), result) so callers get stack traces; isMissingPathFileError still matches via isFileOperationFailure (ok/code/path properties are preserved). - Sanitize toFileOperationFailure message with a stable POSIX-code lookup table instead of the raw OS error string, which can carry extra filesystem metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza * fix(tools): revert Error-wrap in unwrapSaveFileResult / unwrapReadFileResult The Object.assign(new Error(...), result) throw breaks Electron's contextBridge: the structured-clone algorithm preserves only standard Error fields (message/name/stack) and drops custom properties (ok, code, path). Those properties are what isFileOperationFailure and isMissingPathFileError key on, so after crossing the preload→renderer bridge the thrown value is no longer recognised as a missing-path error, and the Save-As relocation path is never taken (confirmed by e2e/electron.stale-paths.spec.ts failure). Plain-object throws cross the bridge without loss because structured clone copies all own enumerable properties on plain objects. The toFileOperationFailure sanitisation is kept — it is safe because isFileOperationFailure checks ok/code/path (not message), and plain objects preserve those fields through the bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…Session (#839) After any modal dismissal (Not now, Escape, overlay click), restore focus to the editor and place the cursor at the end of the pasted range so the user can keep typing without having to re-click into the document. Adds a "Dismiss this Session" ghost button that sets a module-level boolean (pasteAnnotationDismissedThisSession) so the prompt is skipped for the rest of the app session. Follows the same unpersisted, in-memory idiom as useAnnotationStore's createdThisSession Set — no new persistence layer, no settings.json change. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
) Relying on Electron's role-inferred accelerators for zoomIn/zoomOut left ⌘- unresponsive on macOS (⌘+ zoom-in continued to work because macOS normalises ⌘+Shift+= → ⌘= through the OS menu layer, but ⌘- was silently dropped before reaching the menu handler). Fix: pin explicit `accelerator` strings on all three zoom items. Also add two hidden duplicate items—one for CmdOrCtrl+Plus (covers the ⌘++ chord on layouts that need Shift) and one for CmdOrCtrl+numsub (numeric-keypad minus)—so both physical minus keys trigger zoom-out. Privilege-boundary note: this is a src/main/ change; see PR body. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… threads (#830) (#841) Same-type ProseMirror marks are exclusive, so setComment/setMark on a range fully covering an existing comment mark silently replaced it, orphaning the old thread's store entry — the next persistence merge then deleted the thread (replies included). Marks and store diverged in the meantime, so the status bar and chat chips rendered contradictory counts. - setComment now refuses ranges that would fully consume an existing comment mark (partial overlaps still split and survive) and fires a new onCommentBlocked option; Editor.tsx surfaces it as a notification. - add_comment pre-checks the resolved range and returns COMMENT_EXISTS naming the existing thread id, steering the model to reply_to_comment or resolve_comment instead of duplicating. - All count surfaces (status bar, chat chips) now derive from one countOpenThreads selector over the comment store instead of mixing live marks with store entries. - restoreComments warns when legacy same-anchor threads would clobber each other on re-mark. - e2e regression covering the exactly-equal-range add_comment case. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#842) The Comment Review and Quick Review window-level keydown handlers exempted inputs/textareas but not the TipTap contenteditable, so with a review panel open, typing in the document body had its arrows advancing threads, Enter resolving a comment (or accepting a suggestion), and Backspace rejecting. Both handlers now return early when the event target is content-editable — review shortcuts stay active only while focus is outside the editor. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…isk edits (#829) (#844) Session restore hydrated every tab from the IndexedDB snapshot without ever consulting the disk, so files edited outside Prose (another editor, git, an agent) while the app was closed came back stale — and a save from the restored tab silently reverted the external changes. recoverSessionState now stats each tab's file (the existing file:stat IPC) and compares mtime against the session's savedAt: - disk unchanged → snapshot, exactly as before (stat failures fall back too) - disk newer + clean tab → load the disk version (the snapshot has nothing the disk doesn't) - disk newer + dirty tab → genuine conflict: keep the draft (nothing lost yet) and raise a persistent notification with a "Load disk version" action, since silently picking either side guarantees loss Runtime watching of open files (the running-app case) is follow-up #843. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…799) (#846) `normalizeMarkdownFootnotesDom` used selector `sup a.footnote-ref`, but markdown-it-footnote outputs `<sup class="footnote-ref"><a>…</a></sup>` — the class is on `<sup>`, not `<a>`, so the selector never matched. This meant `FootnoteReference.parseHTML()` could not find `a.footnote-ref:first-child` with `data-id`/`data-reference-number`, causing references to fall through to plain `link` + `superscript` marks. On re-serialize they became HTML soup (`[<sup>\[1\]</sup>](#fn1)`) instead of `[^1]`. Fix: change selector to `sup.footnote-ref > a` and stamp `class="footnote-ref"` plus the required attrs onto the anchor element so the tiptap-footnotes parse rule matches. Extend `e2e/electron.footnotes.spec.ts` with three deterministic tests covering the setContent probe, serialization correctness, and the save→reload file-load path. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#798) (#851) Override addInputRules() in FootnoteReference to use an end-anchored regex /\[\^([^\]]+)\]$/ and tr.replaceWith for an atomic replacement, fixing the off-by-one that left a bare '[' when the trigger immediately followed a word character (e.g. 'claim[^1]' rendered as 'claim[¹' instead of 'claim¹'). Also adds deterministic e2e coverage in electron.footnotes.spec.ts for both the abutting ('claim[^1]') and spaced ('claim [^1]') insertion paths. Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…oot switch (#836) * feat(explorer): add Duplicate to file context menu + fully clear selection on root switch (#797) - Add `onFileDuplicate` prop (single-file only, multi-N awaits #796) wired through FileTreeProps → FileTreeItemProps → fileContextMenu; calls getApi().duplicateFile() via handleFileDuplicate in FileListPanel - Add `clearSelectionFully` action to fileListStore that zeroes selectedPaths, selectedPath, and anchorPath; setRootPath now uses it so no residual selection carries over to the new root (clearMultiSelect retains the anchor-collapse behavior for post-delete UX) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza * fix(explorer): surface duplicate failures as a toast, route setRootPath through clearSelectionFully (#797) Review follow-ups on #836: duplicate errors were console-only — now they show via notificationStore like other file operations (file-op failures cross the bridge as plain objects, so the message is extracted defensively). setRootPath now calls clearSelectionFully instead of duplicating its field assignments inline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(editor): add bullet list marker preference (dash vs asterisk) Adds a bulletListMarker setting to Settings → Editor so users can choose between '-' (default, preserving existing behaviour) and '*' (asterisk). The preference wires through the Settings type, default settings, the tiptap-markdown Markdown extension config, and a live useEffect that patches the extension's options object so the change takes effect on the next save without an app restart. Closes #792 * fix(editor): write bulletListMarker to editor.storage.markdown, not extension options (#792) Review follow-up on #837: tiptap-markdown's onBeforeCreate shallow-copies options into editor.storage.markdown, and the bullet-list serializer reads that copy at serialize time (verified in tiptap-markdown source). Mutating the extension's options object was a no-op — live setting changes only took effect after an editor remount. Target the storage copy instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
/review Auto-triggered after E2E tests passed. |
Code ReviewPR #855 — Release v1.7.1 QoL batch. Covers 13 fixes across comments, footnotes, file ops, session restore, and editor UX. Reviewing for correctness, security, and performance. Security
Correctness
The logic in Session snapshot staleness check (#829)
if (!tabDraft.isDirty) {
const disk = await readDocumentFromDisk(tabDraft.path)
if (disk) { restoredContent = disk.content; restoredFrontmatter = disk.frontmatter }
}When the disk read fails, this silently falls through to the snapshot. That is the correct behavior per the comment ("Falls back to the snapshot on read failure"). ✓ Footnote selector fix (#799)
New The override uses
Using
if (e.target instanceof HTMLElement && e.target.isContentEditable) returnAdded to both Minor Observations
SummaryThe implementation is solid. All the bug fixes are targeted and the rollout strategy (dual-check, graceful fallbacks, store-as-source-of-truth for comment counts) reflects good defensive engineering. No blocking issues found. |
✅ Ready to MergeCode review found no significant issues. This PR is ready for human approval and merge. |
Umbrella PR for the v1.7.1 point release: 13 QoL bug fixes and small enhancements from the 2026-08-27 triage run, each landed as an individually reviewed squash commit (green CI + claude review per PR, review-fix rounds included on #836/#837).
Draft until the HITL QA loop passes. Version bump + release notes land here before undrafting.
Closes #801
Closes #827
Closes #790
Closes #832
Closes #806
Closes #830
Closes #826
Closes #829
Closes #804
Closes #799
Closes #798
Closes #797
Closes #792
Also folded in: dependabot #819 was closed in favor of a dedicated dependency pass (45 open alerts, 22 high — separate follow-up).
🤖 Generated with Claude Code
https://claude.ai/code/session_01WqmQeUBBLbtmveh54SMwza