Skip to content

feat: add opt-in auto-focus for new note titles#87

Open
n00ki wants to merge 2 commits intoerictli:mainfrom
n00ki:feat/focus-new-note-title
Open

feat: add opt-in auto-focus for new note titles#87
n00ki wants to merge 2 commits intoerictli:mainfrom
n00ki:feat/focus-new-note-title

Conversation

@n00ki
Copy link

@n00ki n00ki commented Mar 5, 2026

  • adds a new per-folder setting, autoFocusNewNoteTitle, defaulting to off
  • adds an On/Off toggle in Settings → General
  • when enabled, auto-focuses the new note title after creation:
    • selects the title text for static templates (quick replace)
    • places the caret at the end for dynamic templates with tags like {date} / {counter} (quick append)
  • keeps focus behavior one-shot and scoped to newly created notes only
  • adds stale async selection guards in NotesContext to avoid older note loads overriding current selection state

Why

Auto-focusing title on note creation is useful for fast naming, but can feel intrusive as a default.
This keeps the workflow available for power users while preserving the current non-intrusive default experience.

Summary by CodeRabbit

  • New Features
    • New General Settings toggle to auto-focus the title when creating a new note.
    • Editor now respects the configured title-focus mode (e.g., append or replace) when a new note is created.
  • Bug Fixes
    • Improved reliability of title auto-focus so it applies at the right time and won’t interfere with existing note behavior.

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds an optional setting to auto-focus new note titles, implements pending-title-focus tracking in NotesContext, and integrates editor logic to focus the top-level title when opening newly created notes. Also exposes the new setting in the general settings UI and persists it in backend types.

Changes

Cohort / File(s) Summary
Settings Definition
src-tauri/src/lib.rs, src/types/note.ts
Added optional boolean auto_focus_new_note_title / autoFocusNewNoteTitle to persisted Settings (Rust serde name) and TypeScript Settings type.
Context Management
src/context/NotesContext.tsx
Added exported NewNoteTitleFocusMode type, pending-title-focus tracking (pendingTitleFocusRef), consumePendingTitleFocus(id) action, request-id guarding for select/create flows, and logic to initialize pending focus on create/duplicate.
Editor Integration
src/components/editor/Editor.tsx
Added focusNewNoteTitle(editor, mode) helper; consume pending title-focus on note load and fall back to default focus behavior.
Settings UI
src/components/settings/GeneralSettingsSection.tsx
Added local state, optimistic toggle handler, loading guard, and UI block to turn "Auto-focus title on new note" On/Off.

Sequence Diagram

sequenceDiagram
    autonumber
    actor User
    participant UI as Settings UI
    participant Backend as Tauri/Storage
    participant Notes as NotesContext
    participant Editor

    User->>UI: Toggle auto-focus setting
    UI->>Backend: Persist setting
    Backend-->>UI: Ack

    User->>Notes: createNote(template)
    Notes->>Notes: fetch settings (including autoFocusNewNoteTitle)
    Notes->>Notes: set pendingTitleFocusRef (mode) if enabled
    Notes-->>Editor: open note (noteId)
    Editor->>Notes: consumePendingTitleFocus(noteId)
    Notes-->>Editor: return mode | null
    alt mode returned
        Editor->>Editor: focusNewNoteTitle(mode) -> position cursor at H1
    else no mode
        Editor->>Editor: default focus behavior
    end
    Editor-->>User: Note displayed (cursor positioned)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop in code where new titles gleam,
A tiny flag to fulfill the dream.
Pending hops set the cursor's cue,
Replace or append — the choice is you.
New notes awake with a rabbit's cheer!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 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 summarizes the main feature: adding an optional auto-focus behavior for new note titles, which aligns with all changes across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/settings/GeneralSettingsSection.tsx`:
- Around line 121-140: Rapid toggles can spawn concurrent
invoke("update_settings") calls causing out-of-order persistence; modify
handleToggleAutoFocusNewNoteTitle to serialize updates by adding an in-flight
guard (e.g., isUpdatingAutoFocusRef) plus a pendingDesiredRef: if a call is
in-flight, set pendingDesiredRef.current = enabled and return (but keep
optimistic UI via setAutoFocusNewNoteTitle); when the in-flight call finishes
(success or error) clear isUpdatingAutoFocusRef and, if pendingDesiredRef
differs from the last-applied value, call the same update logic again to apply
the latest desired state; reference handleToggleAutoFocusNewNoteTitle,
autoFocusNewNoteTitle, setAutoFocusNewNoteTitle, and invoke("update_settings")
when making these changes.

In `@src/context/NotesContext.tsx`:
- Around line 178-186: The consumePendingTitleFocus function leaves
pendingTitleFocusRef.current set when the passed id doesn't match, allowing
stale focus to apply later; update consumePendingTitleFocus (and references to
pendingTitleFocusRef) so that if pendingTitleFocusRef.current is falsy or its id
!== id you explicitly set pendingTitleFocusRef.current = null and then return
null, otherwise capture mode, clear pendingTitleFocusRef.current = null and
return mode — ensuring the pending focus is always consumed/cleared as a strict
one-shot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8d47178-8439-4881-9ccc-f86b8797aeb6

📥 Commits

Reviewing files that changed from the base of the PR and between 3331c1e and bb29093.

📒 Files selected for processing (5)
  • src-tauri/src/lib.rs
  • src/components/editor/Editor.tsx
  • src/components/settings/GeneralSettingsSection.tsx
  • src/context/NotesContext.tsx
  • src/types/note.ts

Comment on lines +121 to +140
const handleToggleAutoFocusNewNoteTitle = async (enabled: boolean) => {
if (enabled === autoFocusNewNoteTitle) return;

const previous = autoFocusNewNoteTitle;
setAutoFocusNewNoteTitle(enabled);

try {
const settings = await invoke<Settings>("get_settings");
await invoke("update_settings", {
newSettings: {
...settings,
autoFocusNewNoteTitle: enabled ? true : undefined,
},
});
} catch (error) {
console.error("Failed to update title focus setting:", error);
setAutoFocusNewNoteTitle(previous);
toast.error("Failed to update setting");
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent overlapping toggle writes to avoid out-of-order persistence.

Rapid On/Off clicks can run concurrent update_settings calls; slower earlier responses can overwrite the user’s latest choice.

💡 Proposed fix (serialize toggle updates with in-flight guard)
@@
-  const [autoFocusNewNoteTitle, setAutoFocusNewNoteTitle] =
-    useState<boolean>(false);
+  const [autoFocusNewNoteTitle, setAutoFocusNewNoteTitle] =
+    useState<boolean>(false);
+  const [isUpdatingAutoFocus, setIsUpdatingAutoFocus] = useState(false);
@@
   const handleToggleAutoFocusNewNoteTitle = async (enabled: boolean) => {
-    if (enabled === autoFocusNewNoteTitle) return;
+    if (isUpdatingAutoFocus || enabled === autoFocusNewNoteTitle) return;
 
     const previous = autoFocusNewNoteTitle;
     setAutoFocusNewNoteTitle(enabled);
+    setIsUpdatingAutoFocus(true);
 
     try {
       const settings = await invoke<Settings>("get_settings");
       await invoke("update_settings", {
         newSettings: {
           ...settings,
           autoFocusNewNoteTitle: enabled ? true : undefined,
         },
       });
     } catch (error) {
       console.error("Failed to update title focus setting:", error);
       setAutoFocusNewNoteTitle(previous);
       toast.error("Failed to update setting");
+    } finally {
+      setIsUpdatingAutoFocus(false);
     }
   };
@@
               <Button
                 onClick={() => handleToggleAutoFocusNewNoteTitle(false)}
                 variant={!autoFocusNewNoteTitle ? "primary" : "ghost"}
                 size="sm"
+                disabled={isUpdatingAutoFocus}
               >
                 Off
               </Button>
               <Button
                 onClick={() => handleToggleAutoFocusNewNoteTitle(true)}
                 variant={autoFocusNewNoteTitle ? "primary" : "ghost"}
                 size="sm"
+                disabled={isUpdatingAutoFocus}
               >
                 On
               </Button>

Also applies to: 577-590

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/GeneralSettingsSection.tsx` around lines 121 - 140,
Rapid toggles can spawn concurrent invoke("update_settings") calls causing
out-of-order persistence; modify handleToggleAutoFocusNewNoteTitle to serialize
updates by adding an in-flight guard (e.g., isUpdatingAutoFocusRef) plus a
pendingDesiredRef: if a call is in-flight, set pendingDesiredRef.current =
enabled and return (but keep optimistic UI via setAutoFocusNewNoteTitle); when
the in-flight call finishes (success or error) clear isUpdatingAutoFocusRef and,
if pendingDesiredRef differs from the last-applied value, call the same update
logic again to apply the latest desired state; reference
handleToggleAutoFocusNewNoteTitle, autoFocusNewNoteTitle,
setAutoFocusNewNoteTitle, and invoke("update_settings") when making these
changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/settings/GeneralSettingsSection.tsx (1)

122-144: Good implementation with in-flight guard and optimistic UI.

The handler correctly implements:

  1. Guard against concurrent updates (isUpdatingAutoFocus)
  2. Optimistic UI update with rollback on error
  3. Proper cleanup in finally block

One minor observation: writing undefined instead of false (line 134) when disabled means the setting will be omitted from the persisted JSON rather than explicitly set to false. This works correctly since the read path uses ?? false, but consider using enabled directly for consistency.

💡 Optional: Use boolean value directly
       await invoke("update_settings", {
         newSettings: {
           ...settings,
-          autoFocusNewNoteTitle: enabled ? true : undefined,
+          autoFocusNewNoteTitle: enabled,
         },
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/GeneralSettingsSection.tsx` around lines 122 - 144,
The toggle handler handleToggleAutoFocusNewNoteTitle currently writes undefined
when disabling (using autoFocusNewNoteTitle: enabled ? true : undefined) which
omits the key from persisted JSON; change the payload to set
autoFocusNewNoteTitle: enabled (a boolean) when calling
invoke("update_settings", { newSettings: { ...settings, autoFocusNewNoteTitle:
enabled } }) so the setting is explicitly true/false and remains consistent with
the read path and optimistic UI/rollback logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/settings/GeneralSettingsSection.tsx`:
- Around line 122-144: The toggle handler handleToggleAutoFocusNewNoteTitle
currently writes undefined when disabling (using autoFocusNewNoteTitle: enabled
? true : undefined) which omits the key from persisted JSON; change the payload
to set autoFocusNewNoteTitle: enabled (a boolean) when calling
invoke("update_settings", { newSettings: { ...settings, autoFocusNewNoteTitle:
enabled } }) so the setting is explicitly true/false and remains consistent with
the read path and optimistic UI/rollback logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef628094-0b85-43d3-aade-593fe0571f29

📥 Commits

Reviewing files that changed from the base of the PR and between bb29093 and 5d6e7e2.

📒 Files selected for processing (2)
  • src/components/settings/GeneralSettingsSection.tsx
  • src/context/NotesContext.tsx

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