fix: force XWayland backend on Linux to fix unresponsive window controls - #182
fix: force XWayland backend on Linux to fix unresponsive window controls#182GihanRathnayake wants to merge 10 commits into
Conversation
Under native Wayland (e.g. KWin) the window's input region is not committed until a resize, leaving the title bar controls unresponsive until the window is maximized. Default GDK_BACKEND to x11 on Linux, unless the user has set it explicitly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: force XWayland backend on Linux to fix unresponsive window controls
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds extension-aware note storage, indexing, search, creation, duplication, and editing. It also adds find-and-replace, Inter typography, persistent sidebar resizing, and platform-specific desktop startup and layout behavior. ChangesMulti-format note workflows
Editor search replacement
Desktop UI and settings
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
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. Comment |
Bundle the Inter variable font (self-hosted via @fontsource-variable/inter, works offline) and expose it as a fourth editor font alongside Sans, Serif, and Mono. - Add "inter" to the FontFamily type, font map, and settings dropdown - Export fontFamilyMap and reuse it for the settings preview (removes a duplicated, now-incomplete font-stack ternary) - Inter-scoped typographic tuning: negative letter-spacing on body and headings, plus font-optical-sizing; other families keep normal tracking - List spacing polish: gap between sibling items, tighter marker-to-text gap, slightly deeper indent Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends note discovery, search indexing, and the editor to recognize a built-in allowlist of code/text extensions (.go, .py, .js, etc.) in addition to .md, so users can keep code snippets alongside their markdown notes in the same folder. Non-markdown files get a syntax-highlighted read view with click-to-edit, reusing the existing raw-textarea "source mode" plumbing, and a new "New File..." action to create them from the sidebar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Feat/inter editor font
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/editor/Editor.tsx (1)
1798-1807: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPrevent copy menu state leak for plain-text notes.
Pressing
Cmd+Shift+Con a plain-text note setscopyMenuOpentotrue. Since the dropdown is hidden for plain-text notes, the menu won't appear immediately, but the state lingers. As a result, the export dropdown will spontaneously pop open the next time the user clicks on a markdown note.🛠️ Proposed fix to ignore the shortcut for plain text
// Keyboard shortcut for Cmd+Shift+C to open copy menu useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "c") { + if (isPlainTextNote) return; e.preventDefault(); setCopyMenuOpen(true); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); - }, []); + }, [isPlainTextNote]);🤖 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/Editor.tsx` around lines 1798 - 1807, Update the keydown handler in the editor’s copy-menu useEffect to ignore Cmd/Ctrl+Shift+C when the current note is plain text, so it never calls setCopyMenuOpen(true) in that mode. Preserve the existing shortcut behavior for markdown notes and the listener cleanup.
🤖 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-tauri/src/lib.rs`:
- Around line 1466-1476: Update the ID-building logic in both create_file and
duplicate_note so extension omission occurs only for the literal "md", matching
id_from_abs_path; preserve "markdown" in generated IDs and filenames. Extract
the duplicated closure logic into a shared build_note_id helper if practical,
and use it at both call sites so .markdown files retain their requested
extension.
- Around line 738-750: Update the extension check in the ID construction flow
around is_supported_note_extension so `.md` matching is case-insensitive. Ensure
any casing of the Markdown extension uses the strip_suffix behavior and produces
an extension-less legacy ID, while preserving extension retention for other
supported note types.
- Around line 1554-1607: Update duplicate_note after extract_title_for computes
title so markdown duplicates receive a distinct title by appending “ (Copy)”.
Apply this only when is_markdown_ext(&ext) is true, while preserving the
existing extracted title for non-markdown files.
In `@src/components/editor/Editor.tsx`:
- Around line 1424-1427: Prevent debounced source-mode edits from being
discarded by tracking the latest sourceContent in a ref and adding a flusher
that persists pending changes immediately. Invoke this flusher before cancelling
sourceTimeoutRef in both plain-text and markdown note-switch paths, and during
component unmount, while preserving the existing timeout cleanup.
---
Outside diff comments:
In `@src/components/editor/Editor.tsx`:
- Around line 1798-1807: Update the keydown handler in the editor’s copy-menu
useEffect to ignore Cmd/Ctrl+Shift+C when the current note is plain text, so it
never calls setCopyMenuOpen(true) in that mode. Preserve the existing shortcut
behavior for markdown notes and the listener cleanup.
🪄 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
Run ID: 60dfcc7d-cf0d-4ea4-b59f-e111f6f385ad
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
package.jsonsrc-tauri/src/lib.rssrc-tauri/src/main.rssrc/App.csssrc/components/editor/Editor.tsxsrc/components/editor/codeHighlight.tssrc/components/editor/lowlight.tssrc/components/layout/Sidebar.tsxsrc/components/notes/FolderNameDialog.tsxsrc/components/notes/FolderTreeView.tsxsrc/components/notes/NoteList.tsxsrc/components/settings/EditorSettingsSection.tsxsrc/context/NotesContext.tsxsrc/context/ThemeContext.tsxsrc/main.tsxsrc/services/notes.tssrc/types/note.ts
| let ext = file_path.extension()?.to_str()?; | ||
| let rel_str = rel.to_str()?; | ||
| let id = rel_str.strip_suffix(".md")?.replace(std::path::MAIN_SEPARATOR, "/"); | ||
|
|
||
| let id = if ext == "md" { | ||
| // Strip .md by converting to string and trimming (avoids with_extension | ||
| // which breaks on stems containing dots like "meeting.2024-01-15.md"). | ||
| rel_str.strip_suffix(".md")?.replace(std::path::MAIN_SEPARATOR, "/") | ||
| } else if is_supported_note_extension(ext) { | ||
| // Every other recognized extension (.markdown, .go, .py, ...) keeps its extension in the ID. | ||
| rel_str.replace(std::path::MAIN_SEPARATOR, "/") | ||
| } else { | ||
| return None; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Case-sensitive .md check breaks the "extension-less legacy ID" invariant for non-lowercase .md files.
ext == "md" is a case-sensitive literal comparison, but is_supported_note_extension (the fallback branch) matches case-insensitively. A file named e.g. NOTES.MD fails the first check and falls into the "keep extension in ID" branch, producing ID "NOTES.MD" instead of the intended legacy extension-less "NOTES".
Concretely, extension_from_id still lowercases this to "md", so NoteMetadata.extension reports "md" while note.id still embeds .MD. Downstream, FolderTreeView.handleCopyFilepath (note.extension.toLowerCase() === "md" ? folder+"/"+note.id+".md" : ...) then appends .md a second time, producing a broken path like .../NOTES.MD.md. This persists until the note is edited and saved (which triggers the markdown rename path).
🐛 Proposed fix
- let id = if ext == "md" {
+ let id = if ext.eq_ignore_ascii_case("md") {
// Strip .md by converting to string and trimming (avoids with_extension
// which breaks on stems containing dots like "meeting.2024-01-15.md").
- rel_str.strip_suffix(".md")?.replace(std::path::MAIN_SEPARATOR, "/")
+ rel_str.strip_suffix(&format!(".{ext}"))?.replace(std::path::MAIN_SEPARATOR, "/")
} else if is_supported_note_extension(ext) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let ext = file_path.extension()?.to_str()?; | |
| let rel_str = rel.to_str()?; | |
| let id = rel_str.strip_suffix(".md")?.replace(std::path::MAIN_SEPARATOR, "/"); | |
| let id = if ext == "md" { | |
| // Strip .md by converting to string and trimming (avoids with_extension | |
| // which breaks on stems containing dots like "meeting.2024-01-15.md"). | |
| rel_str.strip_suffix(".md")?.replace(std::path::MAIN_SEPARATOR, "/") | |
| } else if is_supported_note_extension(ext) { | |
| // Every other recognized extension (.markdown, .go, .py, ...) keeps its extension in the ID. | |
| rel_str.replace(std::path::MAIN_SEPARATOR, "/") | |
| } else { | |
| return None; | |
| }; | |
| let ext = file_path.extension()?.to_str()?; | |
| let rel_str = rel.to_str()?; | |
| let id = if ext.eq_ignore_ascii_case("md") { | |
| // Strip .md by converting to string and trimming (avoids with_extension | |
| // which breaks on stems containing dots like "meeting.2024-01-15.md"). | |
| rel_str.strip_suffix(&format!(".{ext}"))?.replace(std::path::MAIN_SEPARATOR, "/") | |
| } else if is_supported_note_extension(ext) { | |
| // Every other recognized extension (.markdown, .go, .py, ...) keeps its extension in the ID. | |
| rel_str.replace(std::path::MAIN_SEPARATOR, "/") | |
| } else { | |
| return None; | |
| }; |
🤖 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-tauri/src/lib.rs` around lines 738 - 750, Update the extension check in
the ID construction flow around is_supported_note_extension so `.md` matching is
case-insensitive. Ensure any casing of the Markdown extension uses the
strip_suffix behavior and produces an extension-less legacy ID, while preserving
extension retention for other supported note types.
| let build_id = |s: &str| -> String { | ||
| let leaf = if is_markdown_ext(&ext) { | ||
| s.to_string() | ||
| } else { | ||
| format!("{}.{}", s, ext) | ||
| }; | ||
| match target_folder.as_deref() { | ||
| Some(prefix) if !prefix.is_empty() => format!("{}/{}", prefix.trim_end_matches('/'), leaf), | ||
| _ => leaf, | ||
| } | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
is_markdown_ext misused to decide ID-extension omission — .markdown files silently become .md.
Both create_file and duplicate_note use is_markdown_ext(&ext) (true for both "md" and "markdown") to decide whether to omit the extension when building the note ID/leaf. But the ID scheme (see id_from_abs_path) only omits the extension for literal "md" — .markdown files always keep their extension embedded.
Concretely, creating a file named notes.markdown via create_file: build_id omits the extension (since is_markdown_ext("markdown") is true) → the resulting ID has no extension → abs_path_from_id appends .md by default → the file is actually written as notes.md, silently changing the extension the user explicitly requested. The returned Note.extension ("markdown") then no longer matches the real file's extension. The same pattern reproduces in duplicate_note for existing .markdown notes, producing a mangled filename like readme.markdown-copy.md instead of readme-copy.markdown.
🐛 Proposed fix (both sites)
// create_file (~1466-1476)
let build_id = |s: &str| -> String {
- let leaf = if is_markdown_ext(&ext) {
+ let leaf = if ext == "md" {
s.to_string()
} else {
format!("{}.{}", s, ext)
};
match target_folder.as_deref() {
Some(prefix) if !prefix.is_empty() => format!("{}/{}", prefix.trim_end_matches('/'), leaf),
_ => leaf,
}
}; // duplicate_note (~1563-1579)
- let stem = if is_markdown_ext(&ext) {
+ let stem = if ext == "md" {
leaf
} else {
leaf.strip_suffix(&format!(".{ext}")).unwrap_or(&leaf).to_string()
};
let build_id = |s: &str| -> String {
- let leaf = if is_markdown_ext(&ext) {
+ let leaf = if ext == "md" {
s.to_string()
} else {
format!("{}.{}", s, ext)
};
match &dir_prefix {
Some(prefix) => format!("{}/{}", prefix, leaf),
None => leaf,
}
};Consider extracting a shared fn build_note_id(dir_or_folder: Option<&str>, stem: &str, ext: &str) -> String helper used by both commands — the duplicated logic is exactly why this bug exists in two places at once.
Also applies to: 1563-1579
🤖 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-tauri/src/lib.rs` around lines 1466 - 1476, Update the ID-building logic
in both create_file and duplicate_note so extension omission occurs only for the
literal "md", matching id_from_abs_path; preserve "markdown" in generated IDs
and filenames. Extract the duplicated closure logic into a shared build_note_id
helper if practical, and use it at both call sites so .markdown files retain
their requested extension.
| let content = fs::read_to_string(&source_path) | ||
| .await | ||
| .map_err(|e| e.to_string())?; | ||
| let ext = extension_from_id(&id); | ||
|
|
||
| let (dir_prefix, leaf) = match id.rfind('/') { | ||
| Some(pos) => (Some(id[..pos].to_string()), id[pos + 1..].to_string()), | ||
| None => (None, id.clone()), | ||
| }; | ||
| let stem = if is_markdown_ext(&ext) { | ||
| leaf | ||
| } else { | ||
| leaf.strip_suffix(&format!(".{ext}")).unwrap_or(&leaf).to_string() | ||
| }; | ||
|
|
||
| let build_id = |s: &str| -> String { | ||
| let leaf = if is_markdown_ext(&ext) { | ||
| s.to_string() | ||
| } else { | ||
| format!("{}.{}", s, ext) | ||
| }; | ||
| match &dir_prefix { | ||
| Some(prefix) => format!("{}/{}", prefix, leaf), | ||
| None => leaf, | ||
| } | ||
| }; | ||
|
|
||
| let mut new_stem = format!("{}-copy", stem); | ||
| let mut new_id = build_id(&new_stem); | ||
| let mut counter = 1; | ||
| while abs_path_from_id(&folder_path, &new_id) | ||
| .map(|p| p.exists()) | ||
| .unwrap_or(false) | ||
| { | ||
| new_stem = format!("{}-copy-{}", stem, counter); | ||
| new_id = build_id(&new_stem); | ||
| counter += 1; | ||
| } | ||
|
|
||
| let dest_path = abs_path_from_id(&folder_path, &new_id)?; | ||
| fs::write(&dest_path, &content) | ||
| .await | ||
| .map_err(|e| e.to_string())?; | ||
|
|
||
| let metadata = fs::metadata(&dest_path) | ||
| .await | ||
| .map_err(|e| e.to_string())?; | ||
| let modified = metadata | ||
| .modified() | ||
| .ok() | ||
| .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) | ||
| .map(|d| d.as_secs() as i64) | ||
| .unwrap_or(0); | ||
| let title = extract_title_for(&content, &new_id, &ext); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant functions and surrounding code.
rg -n "duplicateNote|extract_title_for|title =|build_id|is_markdown_ext|extension_from_id|abs_path_from_id" src-tauri/src/lib.rs src-tauri -S
# Show the implementation slice around the duplication logic.
sed -n '1500,1645p' src-tauri/src/lib.rs
# Show the title extraction helper if present in this file.
sed -n '1,420p' src-tauri/src/lib.rsRepository: erictli/scratch
Length of output: 28656
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where note titles are displayed / sourced on the frontend if needed.
rg -n "title|duplicateNote|id\.|extract_title|markdown" src-tauri/src frontend src -SRepository: erictli/scratch
Length of output: 39012
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '497,548p' src-tauri/src/lib.rs
sed -n '108,126p' src/components/notes/NoteList.tsx
sed -n '1,40p' src/lib/utils.tsRepository: erictli/scratch
Length of output: 3694
Duplicated markdown notes need a distinct title. duplicate_note copies the markdown content verbatim, and extract_title_for derives the new title from that same content. Since the sidebar renders title, the copy is indistinguishable from the original; append (Copy) when duplicating markdown notes.
🤖 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-tauri/src/lib.rs` around lines 1554 - 1607, Update duplicate_note after
extract_title_for computes title so markdown duplicates receive a distinct title
by appending “ (Copy)”. Apply this only when is_markdown_ext(&ext) is true,
while preserving the existing extracted title for non-markdown files.
| if (sourceTimeoutRef.current) { | ||
| clearTimeout(sourceTimeoutRef.current); | ||
| sourceTimeoutRef.current = null; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Prevent data loss by flushing pending source mode saves.
When switching notes, clearing sourceTimeoutRef.current without saving discards any keystrokes made within the 300ms debounce window. Since plain-text notes exclusively use the textarea (sourceMode), this bug now consistently affects the primary editing path for plain-text files.
Track the latest sourceContent in a ref and create a flusher to persist pending changes before the timeout is cancelled during a note switch or component unmount.
💾 Proposed fix for source mode saves
First, track the source content and add a flush callback (e.g., around line 612 near the other refs):
const notesCtxRef = useRef(notesCtx);
notesCtxRef.current = notesCtx;
+ // Track latest source content for flushing on note switch
+ const sourceContentRef = useRef(sourceContent);
+ useEffect(() => {
+ sourceContentRef.current = sourceContent;
+ }, [sourceContent]);
+
+ const flushPendingSourceSave = useCallback(async () => {
+ if (sourceTimeoutRef.current && loadedNoteIdRef.current) {
+ clearTimeout(sourceTimeoutRef.current);
+ sourceTimeoutRef.current = null;
+ await saveImmediately(loadedNoteIdRef.current, sourceContentRef.current);
+ }
+ }, [saveImmediately]);
+
// Keep ref in sync with current note ID
currentNoteIdRef.current = currentNote?.id ?? null;Then, replace the cancellation blocks in both the plain-text and markdown note-switch paths (lines ~1424 and ~1470):
- if (sourceTimeoutRef.current) {
- clearTimeout(sourceTimeoutRef.current);
- sourceTimeoutRef.current = null;
- }
+ if (sourceTimeoutRef.current) {
+ flushPendingSourceSave();
+ }Finally, flush the save on unmount (around line 1581):
if (needsSaveRef.current && editorRef.current) {
needsSaveRef.current = false;
const manager = editorRef.current.storage.markdown?.manager;
const markdown = manager
? manager.serialize(editorRef.current.getJSON())
: editorRef.current.getText();
// Fire and forget - save will complete in background
saveNote(markdown);
}
+ if (sourceTimeoutRef.current && loadedNoteIdRef.current) {
+ clearTimeout(sourceTimeoutRef.current);
+ sourceTimeoutRef.current = null;
+ saveNote(sourceContentRef.current, loadedNoteIdRef.current);
+ }
if (linkPopupRef.current) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (sourceTimeoutRef.current) { | |
| clearTimeout(sourceTimeoutRef.current); | |
| sourceTimeoutRef.current = null; | |
| } | |
| if (sourceTimeoutRef.current) { | |
| flushPendingSourceSave(); | |
| } |
🤖 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/Editor.tsx` around lines 1424 - 1427, Prevent debounced
source-mode edits from being discarded by tracking the latest sourceContent in a
ref and adding a flusher that persists pending changes immediately. Invoke this
flusher before cancelling sourceTimeoutRef in both plain-text and markdown
note-switch paths, and during component unmount, while preserving the existing
timeout cleanup.
…om upstream v1.0.0 Ports several features and fixes from erictli/scratch's v1.0.0 release that don't conflict with this fork's Linux window-controls fix, Inter font option, or plain-text/code file editing support: - Find & Replace with Replace/Replace All actions, platform-aware shortcut (⌥⌘F on macOS, Ctrl+H elsewhere) (upstream erictli#180) - Drag-to-resize sidebar width, persisted per-folder (upstream erictli#170) - Linux window-controls config (tauri.linux.conf.json, visible:true + show()/set_focus()), complementary to this fork's existing GDK_BACKEND=x11 fix — different root cause, same symptom (upstream erictli#160) - macOS autocorrect/spellcheck support via WebKit defaults (upstream erictli#150) - Windows native titlebar layout fix (upstream erictli#163) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Fedora/Ubuntu repos both ship an unrelated package named "scratch" (MIT's educational Scratch), which Tauri's linux bundler collides with since it derives the deb/rpm package name from productName lowercased. Rename to "Scratch Notes" so local builds install as scratch-notes without conflicting; this also covers any future CI-built releases since they read the same config. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/layout/SidebarResizeHandle.tsx (1)
9-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
--sidebar-widthtoggle logic across two files. Both sites implement the same "null removes the CSS property, otherwise sets it to${px}px" logic independently, risking drift if the variable name or clamping semantics change.
src/components/layout/SidebarResizeHandle.tsx#L9-L16: keepapplyWidthVarbut move it into a shared module (e.g.src/lib/sidebar.ts) so both files import the same implementation.src/context/ThemeContext.tsx#L358-L366: replace the inline null/px branch in this effect with the shared helper fromsrc/lib/sidebar.ts.🤖 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/SidebarResizeHandle.tsx` around lines 9 - 16, The duplicated --sidebar-width toggle logic should use one shared implementation. In src/components/layout/SidebarResizeHandle.tsx lines 9-16, move applyWidthVar into a shared module such as src/lib/sidebar.ts and import it where needed; in src/context/ThemeContext.tsx lines 358-366, replace the inline null/px branch with that shared helper.
🤖 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/editor/SearchToolbar.tsx`:
- Around line 95-105: The replacement UI controls rendered by the SearchToolbar
toggle and the controls around onToggleReplace must be keyboard reachable.
Override IconButton’s default tabIndex of -1 with a non-negative tabIndex for
the toggle and the replace buttons referenced around lines 157-171, preserving
their existing click behavior and labels.
---
Nitpick comments:
In `@src/components/layout/SidebarResizeHandle.tsx`:
- Around line 9-16: The duplicated --sidebar-width toggle logic should use one
shared implementation. In src/components/layout/SidebarResizeHandle.tsx lines
9-16, move applyWidthVar into a shared module such as src/lib/sidebar.ts and
import it where needed; in src/context/ThemeContext.tsx lines 358-366, replace
the inline null/px branch with that shared helper.
🪄 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: 92afe1a1-0f02-45de-960d-f7c8d904b9a3
⛔ Files ignored due to path filters (1)
src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
src-tauri/Cargo.tomlsrc-tauri/src/lib.rssrc-tauri/tauri.linux.conf.jsonsrc/App.csssrc/App.tsxsrc/components/editor/Editor.tsxsrc/components/editor/SearchToolbar.tsxsrc/components/icons/index.tsxsrc/components/layout/FolderPicker.tsxsrc/components/layout/Sidebar.tsxsrc/components/layout/SidebarResizeHandle.tsxsrc/components/settings/SettingsPage.tsxsrc/context/ThemeContext.tsxsrc/lib/platform.tssrc/lib/sidebar.tssrc/types/note.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/layout/Sidebar.tsx
- src-tauri/src/lib.rs
- src/components/editor/Editor.tsx
| <IconButton | ||
| onClick={onPrevious} | ||
| disabled={totalMatches === 0} | ||
| title={`Previous match (${shift}↵)`} | ||
| onClick={onToggleReplace} | ||
| title={isReplaceOpen ? "Hide Replace" : "Show Replace"} | ||
| className="shrink-0" | ||
| > | ||
| <ArrowUpIcon className="w-4.5 h-4.5 stroke-[1.5]" /> | ||
| {isReplaceOpen ? ( | ||
| <ChevronDownIcon className="w-4 h-4 stroke-[1.8]" /> | ||
| ) : ( | ||
| <ChevronRightIcon className="w-4 h-4 stroke-[1.8]" /> | ||
| )} | ||
| </IconButton> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make the replacement controls keyboard reachable.
IconButton defaults to tabIndex={-1}, so the new toggle and replace buttons are skipped by Tab navigation. Keyboard-only users cannot open the replacement UI or activate its buttons.
Proposed fix
<IconButton
onClick={onToggleReplace}
title={isReplaceOpen ? "Hide Replace" : "Show Replace"}
className="shrink-0"
+ tabIndex={0}
+ aria-expanded={isReplaceOpen}
>
...
<IconButton
onClick={onReplace}
disabled={totalMatches === 0}
title="Replace (↵)"
+ tabIndex={0}
>
...
<IconButton
onClick={onReplaceAll}
disabled={totalMatches === 0}
title="Replace All (Ctrl/Cmd+Enter)"
+ tabIndex={0}
>Also applies to: 157-171
🤖 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/SearchToolbar.tsx` around lines 95 - 105, The
replacement UI controls rendered by the SearchToolbar toggle and the controls
around onToggleReplace must be keyboard reachable. Override IconButton’s default
tabIndex of -1 with a non-negative tabIndex for the toggle and the replace
buttons referenced around lines 157-171, preserving their existing click
behavior and labels.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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-tauri/tauri.conf.json`:
- Line 3: Update the preview-window title formatting in the relevant
preview-title logic in lib.rs to replace the remaining “ — Scratch” branding
with “Scratch Notes” or the project’s shared product-name constant. Preserve the
existing title format and behavior otherwise.
🪄 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: 47aa1fa5-ff56-45ff-be83-0d05c37e3260
📒 Files selected for processing (1)
src-tauri/tauri.conf.json
| { | ||
| "$schema": "https://schema.tauri.app/config/2", | ||
| "productName": "Scratch", | ||
| "productName": "Scratch Notes", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Update the remaining preview-window branding.
src-tauri/src/lib.rs:3912-3961 still formats preview titles with " — Scratch", so preview windows will display the old product name after this rename. Use the new product name or a shared branding constant there as well.
🤖 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-tauri/tauri.conf.json` at line 3, Update the preview-window title
formatting in the relevant preview-title logic in lib.rs to replace the
remaining “ — Scratch” branding with “Scratch Notes” or the project’s shared
product-name constant. Preserve the existing title format and behavior
otherwise.
Adds a one-click "Apply Repose Light" preset in the light theme's color editor, based on Monkeytype's Repose Light theme (cream background, muted sage accent). Reuses the existing custom-color override system, so it's fully reversible via the existing "Reset all" control. bg-muted/bg-emphasis/border are derived from the theme's text color at the same opacities the default theme uses; selection reuses the default light theme's highlight color for visual consistency across themes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dark counterpart to the Repose Light preset, based on Monkeytype's Repose Dark theme. Generalizes the color-editor preset button to pick the right preset for the mode being edited instead of only offering one for light. bg-muted/bg-emphasis/border are tinted from the theme's (light) text color at the same opacities the default dark theme uses; selection reuses the default dark theme's highlight color for consistency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a highlighter toolbar button with a 5-color picker (yellow/green/ blue/pink/red) via @tiptap/extension-highlight. Default yellow keeps serializing as clean ==text== markdown; other colors persist through save/reload as theme-adaptive inline <mark style="..."> HTML. Splits the selection color from the highlight color so selected, searched, and highlighted text stay visually distinct. Also fixes right-click Copy/Cut losing the active text selection: the table context-menu handler was unconditionally collapsing the selection to the click point on every right-click, not just table ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Problem
On Linux under native Wayland (reproduced on Fedora 44 / KDE Plasma / KWin), the window's title bar controls (close, minimize, maximize) are unresponsive on first click. The window's input region isn't committed until a resize, so the controls only start working after the window is maximized/restored.
Fix
Default
GDK_BACKENDtox11(XWayland) on Linux before GTK/WebKitGTK initializes. This sidesteps the native-Wayland input-region issue while leaving an explicit user-setGDK_BACKENDuntouched.#[cfg(target_os = "linux")]); macOS/Windows unaffected.GDK_BACKENDoverride.main()beforerun(), so it applies before any GTK init — works in bothtauri devand shippedtauri buildbinaries.Testing
cargo buildpasses.GDK_BACKEND=x11 npm run tauri devmakes the close button respond on first click; this change applies that automatically.Note: PR built and fixed with Claude Code.
Summary by CodeRabbit