Skip to content

Add editor display and title-bar settings - #199

Open
Norkep wants to merge 7 commits into
erictli:mainfrom
Norkep:agent/editor-display-titlebar-settings
Open

Add editor display and title-bar settings#199
Norkep wants to merge 7 commits into
erictli:mainfrom
Norkep:agent/editor-display-titlebar-settings

Conversation

@Norkep

@Norkep Norkep commented Aug 3, 2026

Copy link
Copy Markdown

Summary

  • Add a setting to enable or disable mouse-based editor page-width resizing.
  • Add a setting to show or hide the fixed formatting toolbar.
  • Add mutually exclusive title-bar information modes: modification date, filename, or none.
  • Preserve donor defaults for existing settings files.
  • Reset all new appearance preferences with the existing appearance reset action.

Stack

This is PR 2 of the Scratch 1.0.1 backport stack and depends on #198.

The branch currently includes the PR 1 commit. Merge #198 first; this PR diff will then shrink to the editor display and title-bar settings commit.

Verification

  • npm test -- --run: 9 files, 29 tests passed.
  • npm run build: passed.
  • cargo test --manifest-path src-tauri/Cargo.toml --quiet: 2 tests passed.
  • cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --all-features -- -D warnings: passed.
  • git diff --check HEAD^..HEAD: passed.

Tests cover donor defaults and migration behavior, disabled resize pointer behavior, formatting-toolbar visibility, accessible settings controls, title-bar mode exclusivity, filename formatting, and empty-state handling.

Scope

Standalone Markdown windows, Settings access from standalone windows, multiple workspaces, selection formatting, tables, and drag/drop remain outside this change.

No zero-bug guarantee is possible. Risk is reduced through focused regression tests, the complete frontend suite available at this stack level, production build, Rust serialization tests, Clippy, and a scope-leak audit.

Summary by CodeRabbit

  • New Features
    • Added sidebar sorting by newest or oldest, with the selection saved between sessions.
    • Added a collapsible Folders section with persisted state.
    • Added editor settings for toolbar visibility and adjustable editor-width resizing.
    • Added title-bar options to show the filename, modified date, or neither.
  • Bug Fixes
    • Improved editor-width measurement for more accurate resizing behavior.
  • Tests
    • Expanded coverage for settings, sorting, folder behavior, title-bar information, and editor controls.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR persists editor display preferences and sidebar sorting settings. It adds controls for title-bar metadata, toolbar visibility, editor-width resizing, note sorting, and folder-section collapse. It also adds Vitest configuration and tests.

Changes

Settings contracts and utility behavior

Layer / File(s) Summary
Persisted settings and utility contracts
src-tauri/src/lib.rs, src/types/note.ts, src/lib/*, package.json, vitest.config.ts
Adds persisted settings, default resolvers, title-bar metadata utilities, a serialized settings patch queue, Vitest scripts, configuration, and tests.

Editor preferences

Layer / File(s) Summary
Editor preference state and rendering
src/context/ThemeContext.tsx, src/components/settings/EditorSettingsSection.tsx, src/components/editor/Editor.tsx, src/components/editor/EditorWidthHandle.tsx, src/components/editor/*test*, src/components/settings/*test*
Loads, persists, resets, and exposes editor preferences. Adds settings controls and applies toolbar, title-bar, and width-resize preferences in the editor.

Sidebar navigation

Layer / File(s) Summary
Sidebar sorting and folder-section visibility
src/components/layout/*, src/components/notes/*, src/lib/folderTree.ts, src/lib/folderTree.test.ts
Adds newest-first and oldest-first sorting, deterministic ordering, persisted folder-section collapse state, accessible controls, updated keyboard visibility, and related tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • erictli/scratch#102: Extends the folder-tree implementation with sidebar sorting and collapsible folder-section behavior.
  • erictli/scratch#196: Shares editor settings, sidebar, folder-tree, title-bar, and Vitest changes.
  • erictli/scratch#198: Shares sidebar sorting, folder-collapse, settings, and related utility changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary editor display and title-bar settings added by the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/notes/FolderTreeView.tsx (1)

779-845: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix: Enter on the "Folders" disclosure button is silently blocked.

SidebarFolderSection (rendered at lines 904-934) introduces the first native, Tab-focusable <button> inside the keyboard-nav container. focusedItemKey never tracks this button, so currentIndex is -1 when it has focus. handleKeyDown calls e.preventDefault() unconditionally for Enter once visibleItems.length > 0, before checking currentIndex. This cancels the button's native Enter activation. A keyboard user who tabs to the "Folders" button cannot toggle it with Enter; only a mouse click or Space works.

Move the currentIndex < 0 guard for Enter before the preventDefault()/stopPropagation() calls, so the container only intercepts Enter when a tracked item is focused.

🐛 Proposed fix
       if (visibleItems.length === 0) return;
-      e.preventDefault();
-      e.stopPropagation();
-
-      const currentIndex = visibleItems.findIndex(
+
+      const currentIndex = visibleItems.findIndex(
         (item) => itemKey(item) === focusedItemKey,
       );
+
+      if (e.key === "Enter" && currentIndex < 0) return;
+
+      e.preventDefault();
+      e.stopPropagation();

Add a regression test that renders FolderTreeView, tabs focus to the "Folders" disclosure button, and asserts Enter toggles foldersSectionCollapsed. Do you want me to draft this test?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/notes/FolderTreeView.tsx` around lines 779 - 845, Move the
currentIndex < 0 guard into the Enter branch of handleKeyDown before the
unconditional preventDefault and stopPropagation calls, while preserving
interception for tracked items. Add a regression test for FolderTreeView that
tabs to the Folders disclosure button and verifies Enter toggles
foldersSectionCollapsed.
🧹 Nitpick comments (2)
src/components/editor/EditorWidthHandle.test.tsx (1)

13-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test case for the actual container-width clamp.

The current mock returns width: 526 with clientWidth: 600, so Math.min(renderedWidth, container.clientWidth) never reaches the clamped branch. The test name implies it verifies the cap against an "unconstrained max-width," but no assertion currently proves that a renderedWidth larger than clientWidth gets capped.

Add a case where the mocked getBoundingClientRect width exceeds clientWidth, and assert the result equals clientWidth.

✅ Proposed additional test case
   it("measures the rendered page instead of its unconstrained max-width", () => {
     ...
     expect(getRenderedEditorWidth(container)).toBe(526);
   });
+
+  it("caps the measured width at the container width", () => {
+    const container = document.createElement("div");
+    const editor = document.createElement("div");
+    editor.className = "ProseMirror";
+    editor.getBoundingClientRect = () => ({
+      x: 0,
+      y: 0,
+      left: 0,
+      top: 0,
+      right: 700,
+      bottom: 800,
+      width: 700,
+      height: 800,
+      toJSON: () => ({}),
+    });
+    Object.defineProperty(container, "clientWidth", { value: 600 });
+    container.append(editor);
+
+    expect(getRenderedEditorWidth(container)).toBe(600);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/EditorWidthHandle.test.tsx` around lines 13 - 33, Add a
separate test case alongside the existing “measures the rendered page” test that
mocks getBoundingClientRect in the container setup with a width greater than
clientWidth, then assert getRenderedEditorWidth returns the container’s
clientWidth, covering the clamp branch.
vitest.config.ts (1)

1-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the IS_REACT_ACT_ENVIRONMENT setup in a Vitest setup file. Two test files duplicate the same globalThis.IS_REACT_ACT_ENVIRONMENT = true assignment. The root cause is that vitest.config.ts has no setupFiles entry to set this once for all React-rendering tests.

  • vitest.config.ts#L1-L10: add a test.setupFiles entry pointing to a new setup file that sets globalThis.IS_REACT_ACT_ENVIRONMENT = true once.
  • src/components/editor/EditorWidthHandle.test.tsx#L9-L10: remove the inline assignment now covered by the shared setup file.
  • src/components/settings/EditorSettingsSection.test.tsx#L10-L11: remove the inline assignment now covered by the shared setup file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.ts` around lines 1 - 10, Centralize the React act environment
setup by adding a Vitest setup file referenced through test.setupFiles in
vitest.config.ts, with that file assigning globalThis.IS_REACT_ACT_ENVIRONMENT =
true. Remove the duplicate inline assignments from
src/components/editor/EditorWidthHandle.test.tsx lines 9-10 and
src/components/settings/EditorSettingsSection.test.tsx lines 10-11; no direct
changes are needed elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/layout/Sidebar.tsx`:
- Around line 176-210: Update the settings persistence used by
handleNoteSortOrderChange so changing sidebarSortOrder cannot overwrite
concurrent settings updates: prefer making update_settings merge only supplied
fields into the existing settings, or use an atomic/current-setting update that
rejects stale full-object writes. Preserve the optimistic UI update and rollback
behavior while ensuring concurrent settings changes are retained.

In `@src/context/ThemeContext.tsx`:
- Around line 235-238: Update updateSettingsPatch to serialize read-merge-write
operations through a shared promise queue, ensuring each invocation awaits
completion of the previous updateSettings call before calling getSettings.
Preserve the existing patch merge behavior and ensure the queue remains usable
after both successful and failed writes.

---

Outside diff comments:
In `@src/components/notes/FolderTreeView.tsx`:
- Around line 779-845: Move the currentIndex < 0 guard into the Enter branch of
handleKeyDown before the unconditional preventDefault and stopPropagation calls,
while preserving interception for tracked items. Add a regression test for
FolderTreeView that tabs to the Folders disclosure button and verifies Enter
toggles foldersSectionCollapsed.

---

Nitpick comments:
In `@src/components/editor/EditorWidthHandle.test.tsx`:
- Around line 13-33: Add a separate test case alongside the existing “measures
the rendered page” test that mocks getBoundingClientRect in the container setup
with a width greater than clientWidth, then assert getRenderedEditorWidth
returns the container’s clientWidth, covering the clamp branch.

In `@vitest.config.ts`:
- Around line 1-10: Centralize the React act environment setup by adding a
Vitest setup file referenced through test.setupFiles in vitest.config.ts, with
that file assigning globalThis.IS_REACT_ACT_ENVIRONMENT = true. Remove the
duplicate inline assignments from
src/components/editor/EditorWidthHandle.test.tsx lines 9-10 and
src/components/settings/EditorSettingsSection.test.tsx lines 10-11; no direct
changes are needed elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04d3b667-bc8c-404e-87dd-3daf5c266e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 9126a5a and 661b600.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • package.json
  • src-tauri/src/lib.rs
  • src/components/editor/Editor.tsx
  • src/components/editor/EditorWidthHandle.test.tsx
  • src/components/editor/EditorWidthHandle.tsx
  • src/components/layout/Sidebar.tsx
  • src/components/layout/SidebarControls.test.tsx
  • src/components/layout/SidebarControls.tsx
  • src/components/layout/SidebarFolderSection.test.tsx
  • src/components/layout/SidebarFolderSection.tsx
  • src/components/notes/FolderTreeView.test.tsx
  • src/components/notes/FolderTreeView.tsx
  • src/components/notes/NoteList.tsx
  • src/components/settings/EditorSettingsSection.test.tsx
  • src/components/settings/EditorSettingsSection.tsx
  • src/context/ThemeContext.tsx
  • src/lib/editorToolbar.test.ts
  • src/lib/editorToolbar.ts
  • src/lib/editorWidthResize.test.ts
  • src/lib/editorWidthResize.ts
  • src/lib/folderTree.test.ts
  • src/lib/folderTree.ts
  • src/lib/titleBarNoteInfo.test.ts
  • src/lib/titleBarNoteInfo.ts
  • src/types/note.ts
  • vitest.config.ts

Comment on lines +176 to +210
setNoteSortOrder(
s.sidebarSortOrder === "oldest" ? "oldest" : "newest",
);
}).catch((error) => {
console.error("Failed to load settings:", error);
setFoldersEnabled(false);
});
}, []);

const handleNoteSortOrderChange = useCallback(
(nextSortOrder: NoteSortOrder) => {
if (nextSortOrder === noteSortOrder) return;

const previousSortOrder = noteSortOrder;
setNoteSortOrder(nextSortOrder);

void notesService
.getSettings()
.then((settings) =>
notesService.updateSettings({
...settings,
sidebarSortOrder: nextSortOrder,
}),
)
.catch((error) => {
console.error("Failed to save note sort order:", error);
setNoteSortOrder((current) =>
current === nextSortOrder ? previousSortOrder : current,
);
toast.error("Failed to save note sort order");
});
},
[noteSortOrder],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether update_settings performs a full overwrite or a partial merge.
rg -n -B2 -A 30 'fn update_settings' src-tauri/src/lib.rs

Repository: erictli/scratch

Length of output: 1275


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'notesService|settings|Settings' . | sed 's#^\./##' | head -100

echo
echo "== references to notesService and settings APIs =="
rg -n "notesService\.(getSettings|updateSettings)|updateSettings\(|getSettings\(|update_settings|sidebarSortOrder" . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200

echo
echo "== lib.rs relevant settings functions =="
sed -n '1680,1790p' src-tauri/src/lib.rs

Repository: erictli/scratch

Length of output: 9232


Protect settings updates from lost concurrent writes.

handleNoteSortOrderChange reads the full settings object, then updateSettings sends it to update_settings, where *settings = new_settings. This replaces the in-process settings object. If another settings update runs between the read and write, that update can be lost. Apply a partial merge in update_settings or require the caller/atomic updater to include the current setting and reject stale updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/layout/Sidebar.tsx` around lines 176 - 210, Update the
settings persistence used by handleNoteSortOrderChange so changing
sidebarSortOrder cannot overwrite concurrent settings updates: prefer making
update_settings merge only supplied fields into the existing settings, or use an
atomic/current-setting update that rejects stale full-object writes. Preserve
the optimistic UI update and rollback behavior while ensuring concurrent
settings changes are retained.

Comment thread src/context/ThemeContext.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/context/ThemeContext.tsx (1)

726-798: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Split the provider data and actions into separate contexts.

contextValue combines all settings data and all setter callbacks. A change to one appearance setting changes the provider value and rerenders action-only consumers. Use the repository dual-context pattern to separate data from actions.

As per coding guidelines, src/**/*{Context,context,Notes}.{ts,tsx} must use the NotesContext dual context pattern with data and actions separated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/context/ThemeContext.tsx` around lines 726 - 798, Refactor the
ThemeContext provider around contextValue to follow the repository’s
dual-context pattern: create separate data and actions contexts, placing
theme/settings values in the data context and setter/reset/reload callbacks in
the actions context. Update the provider exports and consumers to use the
appropriate context, and ensure each context has only the dependencies relevant
to its contents.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/context/ThemeContext.tsx`:
- Around line 726-798: Refactor the ThemeContext provider around contextValue to
follow the repository’s dual-context pattern: create separate data and actions
contexts, placing theme/settings values in the data context and
setter/reset/reload callbacks in the actions context. Update the provider
exports and consumers to use the appropriate context, and ensure each context
has only the dependencies relevant to its contents.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ce43b6f-f9d3-4f69-a984-3f5457f38ee4

📥 Commits

Reviewing files that changed from the base of the PR and between 661b600 and ed1b1d2.

📒 Files selected for processing (7)
  • src/components/editor/EditorWidthHandle.test.tsx
  • src/components/layout/SidebarControls.tsx
  • src/context/ThemeContext.test.tsx
  • src/context/ThemeContext.tsx
  • src/lib/settingsUpdateQueue.test.ts
  • src/lib/settingsUpdateQueue.ts
  • src/lib/titleBarNoteInfo.test.ts
💤 Files with no reviewable changes (1)
  • src/components/layout/SidebarControls.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/titleBarNoteInfo.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant