Per-note windows (Apple Notes-style) + multi-window hardening#11
Merged
Conversation
…-style) Desktop only. Entry points: row ⋯/context menu "Open in New Window", ⌘↵ on a focused list row, and ⌘-click on a row. The note-N window opens smaller with both side panels closed, restores its ASSIGNED note (never touching the shared sidecar's last-active pointer), lands focus in the editor body, and titles itself after the note (rename included). Opening the same note again focuses its existing window (per-note focus-if-open, kept current via set_window_note); workspace-level focus-if-open skips note windows. ⌘0 / Window ▸ Main Window (native accelerator) surfaces the full workspace view for the FOCUSED window's workspace — focusing the existing main/ws-N window or creating one — never dragging forward a main window parked on another workspace. Also, since main + a note window now routinely show one folder: the note list re-reads on window focus (throttled); right-click / ⌘-click on a row no longer move selection or steal focus; and new windows open flash-free (no welcome-card flash during bootstrap — delayed spinner instead; shell-assigned windows skip the folder probe; the empty-editor placeholder waits for the initial load). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting, iCloud, window polish Editor correctness: - Markup-mode ⌘Z could walk back into the PREVIOUS note (the one reused CodeMirror instance records replace() in its history, and addToHistory:false only remaps old events). A pristine CM state snapshotted at mount now templates a fresh per-note state on every swap (editor/markupHistory.ts; @codemirror/state+commands become direct deps — single deduped copies). - Wiki tooltip/suggest popups no longer pop on note open (stuck top-left): gated through swappingRef — which now also starts TRUE at mount, since a fresh window never runs the swap path — plus a detached-anchor guard, and a caret at a link's LEFT edge no longer counts as inside it. - WKWebView occasionally left a stale paint of the caret line (a "doubled" ghost line, few px offset): .editor-pane gets its own compositing layer. Live URLs (fuzzy off everywhere — .md is a real TLD; a Notes.md mention must never linkify or be rewritten on disk): - md.linkify in the WYSIWYG editor (bare URLs normalize once to <url> on save), mirrored in the preview transform; preview link clicks route through openExternalUrl instead of navigating the surface. - Typed URLs linkify on the trailing space via an InputRule that sets the editor's raw-link attr — typed links round-trip as the bare url. iCloud Drive: the bulk walks skip dataless (SF_DATALESS) file content instead of blocking on download-on-demand — a non-downloaded folder lists instantly with empty previews; an explicit note open still materializes the file. Windows & workspace UX: - Note windows cascade centered on the opener's monitor (opener+28px glued them onto the main window — read as a doubled editor). - "Open Folder…" honors ⌘ (switcher ⌘↵/⌘-click, orb menu ⌘-click): picks a folder into its OWN window via pickFolderForNewWindow (registry entry persisted before the window spawns). - Read-only preview shows a floating chip (hover: "Exit preview"; click exits). - A folder scope chip names the filter when the rail is closed (click opens the rail, ✕ back to All Notes without switching notes). - Typing a search while the sidebar is collapsed auto-peeks the list without stealing focus from the box; clearing or committing tucks it away. Docs: CLAUDE.md + README caught up (per-note windows, ⌘0, live URLs, chips). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…edUrls
Two items from the note-windows branch review:
- NotePreview's click handler now vets target.getAttribute('href') instead
of target.href, which resolves relative paths and footnote # anchors
against the document base into openable http(s) URLs — so a relative
[x](foo) opened a junk tab instead of being swallowed as the doc comment
promised. Test added.
- linkifyTypedUrls gets the unit test every other new module had: the
regex boundaries (^/\s, explicit scheme only, whitespace terminator) and
the from = end - url.length mark math (URL marked exactly, raw-link attr
set, typed space outside the inclusive:false mark), plus the
link/code/wiki_link skip paths — driven through the same stub-builder +
inputRules harness as the wikiLinkExtension test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eak, linkify/iCloud edges Second xhigh /code-review pass over the note-windows branch (15 verified findings) plus field-test follow-ups. Correctness: - useNotes: a note window's in-memory `active` pointer no longer leaks to the shared sidecar — writeMetadataNow substitutes the live on-disk value at the write boundary, so pins/appearance/navigation/close can't hijack the main window's restore pointer. onCloseRequested keeps a window open when its flush hit an unresolved conflict (instead of destroying it and dropping the edit). - ⌘0 (Window ▸ Main Window): move the menu:main-window listener from workspace-keyed Workspace (remounts on every ⌃R switch → duplicate listeners → ⌘0 opened several workspaces at once) to App (one stable listener per window). window_note_renamed re-keys note-window focus-if-open on cross-window rename/move; all focus paths unminimize before show/focus; openInNewWindow fails loudly when the workspace left the registry; windowNote clears on the first in-place switch. - linkifyTypedUrls: bail when the typed whitespace replaces a selection (offsets would mis-mark and leave the selection undeleted); trim trailing punctuation from the URL. - useNotesStorage: skipProbe excludes the main window (its assignment is stale after a webview reload) and pickFolderForNewWindow probes the folder before touching the registry. - useCorpus: re-read an iCloud-dataless file once its list preview materializes (download leaves mtime unchanged, so the updatedAt-keyed refresh missed it). - NotePreview: middle-click (auxclick) also routes through the vetted external opener. - Search Enter with the sidebar collapsed returns to the editor, not a hidden list. Tooling/docs: - eslint.config.js + .prettierignore: ignore stray root .vite/ cache and .claude/ worktrees (gitignored full-repo copies that made `eslint .` hang / report phantoms). - CLAUDE.md/README caught up; completed backlog items removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From an external review of the note-windows branch: - M3 (blocking): unify the "open in new window" chord. Hoist isOpenInNewWindowChord to shortcuts.ts and use it in TopBar (recents + Open Folder…) and the ⌃R switcher, which previously gated on metaKey alone — so Ctrl-click/Ctrl↵ now works off macOS (Windows/Linux), matching the note-list rows. - M1: notify the shell when a note is trashed or deleted (window_note_removed), the delete-path mirror of window_note_renamed — so a note window left showing an orphan stops answering per-note focus-if-open for a dead id. Pure core unit-tested. - M2: cite the SDK header + verified macOS version for the hand-coded SF_DATALESS. - N1/N3: document the notifyShellNoteRenamed ordering and that writeMetadataNow's diskActiveRef fallback is a load-time snapshot (best-effort, not authoritative). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opens any note in its own desktop window, Apple Notes-style, on top of the existing
per-workspace windows — plus a round of live URLs, editor polish, and a thorough
code-review/field-test hardening pass.
What's new
Per-note windows (desktop).
⌘↵/⌘-clicka list row (or its ⋯ → "Open in NewWindow") opens just that note in a small
note-Nwindow: both side panels closed, focusin the editor, native title tracking the note. Reopening the same note focuses its
existing window;
⌘0(Window ▸ Main Window) brings back the workspace's main window.⌘-click/⌘↵also works on "Open Folder…" to open a picked folder in its own window.Editor & preview.
https://…links render as real links in the editor and preview,typed URLs linkify on the trailing space, and open in the browser via ⌘-click (plain
click in preview). Explicit schemes only — a stray
Notes.mdnever becomes a link orgets rewritten (
.mdis a real TLD).the list when a folder filters it with the rail closed; search auto-peek of the
collapsed sidebar while typing.
iCloud. The Rust walks skip not-yet-downloaded (dataless) files' content instead of
blocking on download — they list by name/mtime with empty previews, and search/preview
recover once macOS materializes the file.
Hardening (xhigh /code-review + field testing)
15 verified findings fixed, plus a ⌘0 double-window bug caught in field testing:
activepointer no longerleaks to the shared sidecar, so it can't hijack the main window's next-launch restore.
menu:main-windowlistener from theworkspace-keyed
Workspace(remounted per ⌃R switch → duplicate listeners → ⌘0 openedseveral workspaces at once) to
App(one stable listener per window). Trace-verified.probe no longer skipped for stale main-window assignments; cross-window rename re-keys
note-window focus-if-open; middle-click in preview routes through the vetted opener; and
more.
.vite/cache and.claude/worktrees(madeeslint .hang).Testing
npm test910/910 ·tscclean ·cargo test22/22 ·npm run lint0 errors · Prettierclean. The per-note-window flows, ⌘0, minimized-window focus, and live URLs were also
verified by hand in the desktop app.
🤖 Generated with Claude Code