v2.8.0: sidebar virtualization for massive vaults + settings redesign#269
Merged
Conversation
Keep ZenNotes fast and light on massive vaults (5,000+ notes) with no behavior change. - Sidebar: window large flat note lists via inert, same-height placeholders that keep the data-* contract keyboard-nav / range-select / cursor read from the DOM. At 5,000 notes: DOM nodes 35,645 -> 5,687 (-84%), JS heap 80 -> 25 MB (-69%), folder expansion 188 -> 33 ms (-82%), longest task 106 -> 0 ms. - StatusBar: stop recomputing an O(n) backlinks scan on every keystroke (key the memo on note.path, not the whole note object). Same count, off the typing hot path. - Watcher: coalesce vault-change events so a bulk git pull / sync collapses N full rescans into one. - VimNav: guard a window-dispatched key event hitting .closest on a non-Element target (fixes a console error). - Boot: halve note-list IPC round-trips (page size 250 -> 500). - Add tooling/scripts/sidebar-vim-smoke.mjs + 'npm run test:sidebar-vim': a CDP smoke test that drives the real app and asserts windowing + j/k/G/gg + range-select data + click-to-open + no console errors. Verified: typecheck (7/7 workspaces), app-core tests (580), the 5k-note perf benchmark, and the vim smoke test all pass.
Reorganize the settings modal so it stops feeling overwhelming — without removing any setting or changing how anything persists. - Rail: group the 9 categories into Look & feel / Editing / Vault / System, each with an icon + short label; descriptions move out of the rail onto the page header. Empty query shows the grouped rail; typing switches to flat search results. - Dense pages -> focused in-page sub-tabs: Vault (Location / Notes / System) and Editor (Vim / Search / Writing / Quick capture). Existing sections are spliced into sub-tabs with no control moved or rewritten. - Search now opens the sub-tab that owns a result before scrolling to and highlighting it. - Keymap left as-is: its sticky live-filter + grouped list already suits 50+ shortcuts; sub-tabs would fragment that search. Verified: control / search-anchor / onChange counts are identical to before, so no setting was dropped and every store + config.toml binding is preserved. Typecheck + app-core tests pass.
| } | ||
| function pressKey(client, key, shift = false) { | ||
| const code = key.length === 1 ? `Key${key.toUpperCase()}` : key | ||
| return evaluate(client, `(() => { window.dispatchEvent(new KeyboardEvent('keydown', { key: ${JSON.stringify(key)}, code: ${JSON.stringify(code)}, shiftKey: ${shift}, bubbles: true, cancelable: true })); return true; })()`) |
| } | ||
| function pressKey(client, key, shift = false) { | ||
| const code = key.length === 1 ? `Key${key.toUpperCase()}` : key | ||
| return evaluate(client, `(() => { window.dispatchEvent(new KeyboardEvent('keydown', { key: ${JSON.stringify(key)}, code: ${JSON.stringify(code)}, shiftKey: ${shift}, bubbles: true, cancelable: true })); return true; })()`) |
Search auto-selected a sub-tabbed category but rendered its default sub-tab, so a matched setting (e.g. a Daily Notes field, now under Vault > Notes) wasn't shown until the result was clicked. Auto-open the owning sub-tab whenever search surfaces a setting, so the control is rendered directly (mirrors the on-click search jump). Fixes the SettingsModal date-note-directory tests that broke under the sub-tab redesign and keeps every setting reachable via search.
Open an Obsidian Excalidraw drawing (*.excalidraw.md, or a .md carrying the excalidraw-plugin frontmatter) and convert it into a native .excalidraw that renders in the drawing editor, instead of showing raw markdown. - Recover the embedded scene from the '## Drawing' code block, handling both plain ```json and LZString-compressed ```compressed-json — the exact codec Obsidian's Excalidraw plugin uses (LZString.compressToBase64). Newlines are stripped and the scene is validated before conversion. - In-editor 'Convert to ZenNotes drawing' prompt shown when an Obsidian drawing is opened. Non-destructive: the original file is kept. - New vault op + IPC (convertObsidianExcalidraw); detection + extractor live in shared-domain with unit tests (incl. the compressed-json round-trip). Verified end-to-end by driving the real app over CDP (compressed drawing -> prompt -> convert -> renders in the Excalidraw editor). Closes #266
A rendered image is an inline (side:1) widget at end-of-line with the raw source hidden, so its host .cm-line still reserved a full text line-box (~one line-height) above and below the block figure — a phantom blank row, most visible beneath the image. Stamp a cm-image-embed-line line decoration only while the source is hidden and collapse that line's strut to line-height:0 (caption line-height restored); trim the figure margins. All scoped to .cm-editor, so Preview (.prose-zen) is unaffected. When the cursor reveals the source the class is absent, so the editable markdown keeps its normal line-height. Adds a live-preview test asserting the class toggles with source visibility.
A standalone local image is rendered as a .local-image-embed figure whose <img> carries width:100% (correct for on-screen preview, where it fills the frame). In the export window that stretches even a tiny image to the full content width, and the max-height:9.3in cap added in #231 then blows it up to a full page — a regression surfaced by that fix. Override the embed in export so images size to their intrinsic dimensions, only ever scaling DOWN to fit the content width or page height, and shrink the frame/caption to hug the image. The .prose-zen-prefixed selectors beat the shared width:100% rule on specificity regardless of stylesheet order. Tall images still scale to fit the page (#231 preserved); wide images still cap to the content width. Verified via real printToPDF for small/wide/tall images.
Replaces the blank 'No note selected' screen (outside Zen mode) with a light landing page: a greeting, quick-create actions (new note / database / drawing, plus daily / weekly when those are enabled), the few most recently edited notes, and today's open tasks with an overdue badge. Rows open on click; ↑/↓ (and j/k in vim mode) rove between them, Enter opens. Tasks are scanned lazily on first show. VimNav yields to the view via a data-home-nav guard.
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.
Integration branch for the v2.8.0 release. Two independent efforts, each its own commit:
1. ⚡ Performance — fast & light on massive vaults (
perf(sidebar))Make ZenNotes stay smooth with 5,000+ note vaults, with no behavior change.
NoteLeafs; the rest render as inert, same-height placeholders that keep the exactdata-*attributes keyboard-nav / range-select / cursor read from the DOM. Because every row stays in the DOM (just cheap when off-screen), none of that logic changes.note.path).git pull/ cloud sync collapses N full vault rescans into one..closeston a non-Element.Measured at 5,000 notes:
Adds
tooling/scripts/sidebar-vim-smoke.mjs(npm run test:sidebar-vim) — a CDP smoke test that drives the real app and asserts windowing +j/k/G/gg+ range-select + click-to-open + no console errors.2. 🎛️ Settings redesign — less overwhelming (
feat(settings))Reorganize the settings modal without removing a single setting or changing how anything persists.
Guardrails verified: identical control / search-anchor /
onChangecounts vsmain→ no setting removed, every store +config.tomlbinding preserved.Verification
Supersedes #268 (the settings-only PR), now folded into this release branch.