feat: local HTTP API, quick capture, mermaid/math plugins + ASAR fix#231
Conversation
…AR crash New features: - Local HTTP API server (port 29168) with bearer token auth GET/POST/PUT /api/notes, /api/notes/search, /api/notes/quick, /api/status - Global Quick Capture (Cmd+Shift+N): frameless floating window for fast notes - Mermaid diagrams: code block renderer with "Open in Mermaid Live" button - Math/LaTeX: code block renderer with styled display - Vim mode: plugin stub with toggle command (ready for @codemirror/vim) Bug fix: - Fix ERR_PACKAGE_PATH_NOT_EXPORTED crash in packaged app electron-vite was externalizing @readied/* workspace packages, but ASAR doesn't have compiled JS for them. Now workspace packages are bundled into the main/preload output instead of externalized. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 37 minutes and 53 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughAdds a local HTTP server with bearer-token auth and IPC handlers, a floating quick-capture window with global shortcut, preload APIs, renderer Quick Capture UI, three new renderer plugins (Mermaid, Math, Vim Mode), and tweaks to Electron Vite build and local CLI permission allowlist. Changes
Sequence Diagram(s)sequenceDiagram
actor Client as HTTP Client
participant LocalServer as LocalServer (main)
participant IPC as Main IPC Handlers
participant Repo as Note Repository
Client->>LocalServer: POST /api/notes (Bearer token + JSON)
LocalServer->>LocalServer: Validate Bearer token (const-time)
alt token valid
LocalServer->>IPC: invoke createNote handler (adapter)
IPC->>Repo: create note in storage
Repo-->>IPC: created note object
IPC-->>LocalServer: adapted response
LocalServer-->>Client: 201 Created + JSON
else token invalid
LocalServer-->>Client: 401 Unauthorized
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51124ab642
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const notes = await repo.list(); | ||
| return notes.filter(n => !n.isDeleted).length; |
There was a problem hiding this comment.
Compute API note count without list() default limit
This count is derived from repo.list(), but SQLiteNoteRepository.list defaults to limit = 50, so /api/status underreports as soon as a user has more than 50 active notes. The status endpoint should return a true total (e.g., via repo.count()), otherwise integrations that rely on this health metadata get incorrect results.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
✅ Fixed: uses repo.count() instead of list().
There was a problem hiding this comment.
Actionable comments posted: 22
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.claude/settings.local.json (1)
1-91:⚠️ Potential issue | 🟠 MajorRemove
.claude/settings.local.jsonfrom version control and add it to.gitignore.This file contains user-specific absolute paths (e.g.,
/Users/tomasmaritano/...) and follows the.local.jsonnaming convention indicating per-developer local overrides—similar to VS Code's workspace-level overrides. Other.claude/subdirectories (.claude/plugins/and.claude/statsig/) are already gitignored.Add
/.claude/settings.local.jsonto.gitignore. If shared Claude permissions are needed, create a committed.claude/settings.jsonwith non-machine-specific paths and keep.settings.local.jsonfor local development only.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/settings.local.json around lines 1 - 91, The committed .claude/settings.local.json contains machine-specific absolute paths and should be removed from version control and ignored; remove the tracked file (e.g., git rm --cached .claude/settings.local.json or delete and commit), add "/.claude/settings.local.json" to .gitignore, and commit that change; if a shared, non-machine-specific config is needed, add a committed .claude/settings.json with sanitized paths (keeping per-developer overrides in .claude/settings.local.json) so .claude/plugins/ and .claude/statsig/ remain ignored as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/settings.local.json:
- Around line 81-88: Remove the now-redundant specific ls allowlist entries
("Bash(ls
/Users/tomasmaritano/Documents/Github/readied/readide/apps/desktop/src/renderer/components/NoteListFilterBar*)"
and "Bash(ls
/Users/tomasmaritano/Documents/Github/readied/readide/apps/desktop/src/renderer/components/*.module.css)")
because the broader "Bash(ls:*)" pattern already covers them; update the
allowlist to keep only the general "Bash(ls:*)" entry and verify there are no
other duplicated specific "Bash(ls ...)" lines to clean up.
In `@apps/desktop/src/main/handlers/localServerHandlers.ts`:
- Around line 60-61: Module-level mutable singletons `server` and `apiToken`
cause state leakage and duplicate ipc handlers; change to encapsulate state in a
factory (e.g., a function that creates and returns {
registerLocalServerHandlers, autoStart, stop }) that holds its own `server` and
`apiToken` instances instead of module globals, and have
`registerLocalServerHandlers` call `ipcMain.removeHandler(channel)` for each
channel before calling `ipcMain.handle(...)` to avoid "second handler
registered" on re-import/hot-reload; reference symbols to change: replace module
`server` and `apiToken`, update `registerLocalServerHandlers`, and use
`LocalServer`/`ipcMain.removeHandler` accordingly.
- Around line 148-151: The stop handler currently awaits server.stop() without
error handling so rejections bubble to the renderer; wrap the
ipcMain.handle('localServer:stop', ...) implementation in a try/catch like the
'localServer:start' handler: try { await server.stop(); return { ok: true }; }
catch (err) { return { ok: false, error: String(err?.message ?? err) }; } so the
renderer always receives a consistent { ok, error } shape and no unhandled IPC
rejection occurs.
- Around line 136-145: Validate and coerce the incoming port before calling
server.start: in the ipcMain.handle('localServer:start') handler, treat the
optional port param as unknown, coerce it to a number (e.g., via Number(...) or
parseInt), check Number.isInteger and that it falls within 1–65535 (allow
undefined to mean "use default"); if invalid, return { ok: false, error:
'invalid port' } (or a descriptive error) instead of passing it through; then
pass the validated port to server.start (functions to locate:
ipcMain.handle('localServer:start'), server.start, server.isRunning,
server.getPort, getOrCreateApiToken).
- Around line 174-179: The function autoStartLocalServer currently only
pre-warms an API token and ignores most of LocalServerHandlerDeps
(noteRepository, noteToSnapshot) and does not actually start the server; also
getOrCreateApiToken can throw and will bubble up to app startup. Rename and
narrow the function to prepareLocalServerToken (or similar) accepting only
DataPaths (or dataPaths.root) instead of LocalServerHandlerDeps, wrap the
getOrCreateApiToken call in a try/catch that logs the error and
returns/continues (so failures don't reject main startup), and keep actual
server startup to the existing localServer:start flow; update any callers in
main/index.ts to call the new prepareLocalServerToken and ensure server.start
remains only invoked via the setting-driven path.
In `@apps/desktop/src/main/index.ts`:
- Around line 892-898: The ipcMain handler "window:closeSelf" currently allows
any renderer to close its BrowserWindow via ipcMain.handle and
BrowserWindow.fromWebContents(event.sender); change this to restrict closing to
the quick-capture window by either renaming the channel to
"window:closeQuickCapture" and only handling that channel, or keep the channel
and add an explicit check comparing event.sender to the stored
quickCaptureWindow.webContents (e.g., if event.sender !==
quickCaptureWindow.webContents then return { ok: false }); only call win.close()
when the sender matches the known quickCaptureWindow and win is not destroyed;
ensure the handler still returns a consistent result object.
- Around line 457-462: The blur handler currently unconditionally closes
quickCaptureWindow and can drop user input; change it so the 'blur' event does
not directly call quickCaptureWindow.close(). Instead, have the renderer expose
its "dirty" state via IPC (e.g., channel names like 'quick-capture-dirty' /
'quick-capture-clean' or a one-time query), and only close on blur when the
renderer reports clean/empty; additionally make Escape keypress the primary
dismiss action (listen for 'keydown' or register an accelerator in the renderer
and send an IPC 'quick-capture-close' to the main process), and optionally gate
blur-close behind a user setting or skip it in dev (check process.env.NODE_ENV
or isPackaged) so DevTools won't trigger automatic close.
- Around line 420-425: The existing quickCaptureWindow may be hidden even when
focused; update createQuickCaptureWindow so that when quickCaptureWindow exists
and is not destroyed you call quickCaptureWindow.show() before
quickCaptureWindow.focus(), mirroring the behavior used for settingsWindow to
ensure an always-on-top/frameless window is made visible as well as focused.
- Around line 880-883: globalShortcut.register for 'CommandOrControl+Shift+N'
can return false if the accelerator is already taken; update the registration
code where globalShortcut.register is called (the block that invokes
createQuickCaptureWindow) to check the boolean return value and, on false, emit
a visible diagnostic: log a warning via the existing main-process logger (or
processLogger) including the accelerator string and that registration failed,
and optionally send an IPC message to renderer(s) or the quick-capture UI to
surface the problem to the user; ensure the new logic references
globalShortcut.register, the accelerator 'CommandOrControl+Shift+N', and
createQuickCaptureWindow so it’s easy to locate and test.
In `@apps/desktop/src/main/services/localServer.ts`:
- Around line 194-222: The dynamic id routes for GET/PUT (/api/notes/:id)
currently capture reserved literal segments like "quick" (and "search"), causing
misrouting; update the routing logic so those literals are handled explicitly:
either add explicit branches before the regex routes for /api/notes/quick and
/api/notes/search that return 405 for unsupported methods (e.g., GET/PUT on
/api/notes/quick) and forward POST/search to their handlers, or keep the dynamic
branch but validate the extracted noteId in handlers.getNote/handlers.updateNote
(or right after const noteId = pathname.split('/').pop()!) and return a 400/405
with sendJson when noteId equals reserved values like "quick" or "search";
reference symbols: handlers.getNote, handlers.updateNote, this.readBody,
this.sendJson.
- Around line 268-282: The readBody function currently accumulates unlimited
chunks causing DoS risk; change it to enforce a max size (e.g., 5 * 1024 * 1024
bytes or make configurable) by tracking total bytes on each 'data' event and
immediately stop processing when exceeded: remove listeners, destroy the
request/stream, and reject the Promise with an error indicating payload too
large (HTTP 413) so the caller can send a 413 response; keep the existing JSON
parse/resolve logic on 'end' and ensure 'error' cleans up listeners too.
- Around line 61-73: In getOrCreateApiToken, ensure the token file is created
with owner-only permissions and the parent directory is restricted: before
writing tokenPath (TOKEN_FILE) call mkdir for the parent directory with mode
0o700 (recursive:true) if needed, write the token using fs.writeFile with the
option { mode: 0o600 }, and if an existing token is read, check and fix its
permissions (fs.chmod tokenPath to 0o600) when they are more permissive;
reference tokenPath, TOKEN_FILE and getOrCreateApiToken when making these
changes.
- Around line 142-147: Replace the direct inequality check on
req.headers.authorization with a constant-time, length-checked comparison: first
verify the header exists and starts with the literal "Bearer " prefix, then
extract the token substring and compare its Buffer to Buffer.from(this.token)
using crypto.timingSafeEqual only if lengths match; if any check fails call
this.sendJson(res, 401, { error: 'Unauthorized' }); also add the required import
for Node's crypto module. Ensure you reference the existing authHeader variable,
this.token, and this.sendJson when making these changes in localServer.ts.
- Around line 97-107: When starting the server in start(), ensure the temporary
server reference is cleared if listen fails and that the 'error' handler is
removed once listen succeeds: attach a named error handler to this.server
(created via createServer and used in this.server!.on('error', ...)) that on
error sets this.server = null and rejects the start() promise; in the listen
callback remove that error listener so later runtime errors don't trigger the
startup rejection, and on successful listen resolve normally. Reference the
this.server field, start() method, the createServer(...) callback
(handleRequest), the server.on('error', reject) usage, and the
server.listen(...) callback when making these changes.
In `@apps/desktop/src/renderer/components/QuickCapture.module.css`:
- Around line 3-14: The .container CSS sets border-radius: 12px but the
BrowserWindow is created with frame: false and backgroundColor: '#0a0b0d' which
prevents rounded corners from being visible; update the BrowserWindow options in
the window creation (the new BrowserWindow({...}) call) to add transparent: true
and remove the opaque backgroundColor (or set backgroundColor to 'transparent')
so the .container border-radius can show, or alternatively remove the
border-radius from QuickCapture.module.css .container if you prefer an opaque
rectangular window.
In `@apps/desktop/src/renderer/components/QuickCapture.tsx`:
- Around line 35-54: The catch block in handleSave silently swallows errors
causing no user feedback or logs when window.readied.notes.create fails; update
handleSave to catch the error as e, call setSaving(false), log the error (e.g.,
console.error or processLogger) and set a user-visible error state (e.g.,
setError or setSaveError) so the UI can display a message, and ensure the
dependency array includes any new state setters; retain handleClose on success
only.
- Around line 20-24: In QuickCapture's useEffect that calls
window.readied.notebooks.list(), add error handling and an unmount guard: wrap
the promise chain with .catch to log or surface the error (using the component's
logging mechanism or window.api) so failures don't silently drop, and use an
isMounted flag (or AbortController) inside the effect to ensure setNotebooks is
only called when the component is still mounted; reference the useEffect,
window.readied.notebooks.list(), and setNotebooks to locate the code to modify.
- Around line 57-75: The keydown handler onKeyDown inside the useEffect should
ignore IME composition events so Enter/Escape don't prematurely submit or close:
at the start of onKeyDown, return early if (e as any).isComposing is true or
e.keyCode === 229; keep the existing Escape and Cmd/Ctrl+Enter branches
unchanged otherwise. Update the window event listener logic in the same
useEffect (where onKeyDown, handleClose and handleSave are referenced) so
composition events are skipped before calling handleClose or handleSave.
In `@apps/desktop/src/renderer/plugins/math.tsx`:
- Around line 1-14: The header comment incorrectly states that inline ($...$)
and display ($$...$$) math are handled by a remark plugin even though the module
only registers fenced code-block renderers via activate(); fix by either
removing that remark-plugin claim from the docstring or by actually implementing
and registering a remark plugin: if removing, edit the top comment to mention
only fenced code-block renderers and the copy UI; if implementing, add a remark
plugin that wraps inline/display math in spans (e.g., producing elements with
classes like "language-math-inline"/"language-math-display") and register it in
the module's activate() PluginManifest so the existing CSS selectors (e.g.,
.markdown-preview code.language-math-inline) become reachable.
In `@apps/desktop/src/renderer/plugins/mermaid.tsx`:
- Around line 13-17: injectMermaidStyles currently sets a module-level flag
(styleInjected) and appends a <style> element but never removes it; change it to
store the created element in a module-level variable (e.g., mermaidStyleEl), add
a removeMermaidStyles() that removes mermaidStyleEl from document.head and
resets styleInjected/mermaidStyleEl, and call removeMermaidStyles() from the
plugin's dispose() so reactivation works and no orphaned nodes remain; apply the
same pattern for injectMathStyles/styleInjected in math.tsx (create a
mathStyleEl, removeMathStyles(), reset flag, and call from dispose()).
- Around line 90-100: The openInLive callback currently uses btoa(state) and
window.open(..., '_blank') which breaks on Unicode, produces non‑URL‑safe
base64, and allows tabnabbing; replace the btoa call in openInLive with a UTF‑8
safe encoder (e.g., UTF‑8 encode the JSON then base64 it), convert the resulting
base64 to URL‑safe alphabet by replacing '+'→'-', '/'→'_' and stripping any '='
padding (matching mermaid.live's serde), and update the window.open call to
include noopener/noreferrer (e.g., window.open(url, '_blank',
'noopener,noreferrer') or use an anchor with rel="noopener noreferrer") so that
code (the variable used in the state) is correctly encoded and opened securely.
In `@apps/desktop/src/renderer/plugins/vimMode.tsx`:
- Around line 34-71: The activation path currently calls enable()
unconditionally when the saved config is true, causing redundant
context.config.set('enabled', true), repeated context.log.warn messages and
showing the VIM status even when the dependency is missing; change activate() so
it separates "apply current state" from "first-time enable" side-effects: detect
whether `@codemirror/vim` is present (e.g., try a dynamic import or a boolean
hasVim flag), then on activation if enabled is true only call a new applyState()
that registers extensions (using unregisterExt) and calls showStatus()
conditionally (show a disabled/unavailable indicator or skip showStatus when
hasVim is false) but do NOT call context.config.set('enabled', true) or
context.log.warn; keep enable() as the explicit user action that sets
context.config.set and emits the warn when attempting to enable without the dep.
---
Outside diff comments:
In @.claude/settings.local.json:
- Around line 1-91: The committed .claude/settings.local.json contains
machine-specific absolute paths and should be removed from version control and
ignored; remove the tracked file (e.g., git rm --cached
.claude/settings.local.json or delete and commit), add
"/.claude/settings.local.json" to .gitignore, and commit that change; if a
shared, non-machine-specific config is needed, add a committed
.claude/settings.json with sanitized paths (keeping per-developer overrides in
.claude/settings.local.json) so .claude/plugins/ and .claude/statsig/ remain
ignored as intended.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 261edb40-3dae-4c44-8afb-b3e574b47f85
📒 Files selected for processing (16)
.claude/settings.local.jsonapps/desktop/electron-vite.config.tsapps/desktop/src/main/handlers/localServerHandlers.tsapps/desktop/src/main/index.tsapps/desktop/src/main/services/localServer.tsapps/desktop/src/preload/api/app.tsapps/desktop/src/preload/api/index.tsapps/desktop/src/preload/api/localServer.tsapps/desktop/src/preload/index.tsapps/desktop/src/renderer/components/QuickCapture.module.cssapps/desktop/src/renderer/components/QuickCapture.tsxapps/desktop/src/renderer/main.tsxapps/desktop/src/renderer/plugins/index.tsapps/desktop/src/renderer/plugins/math.tsxapps/desktop/src/renderer/plugins/mermaid.tsxapps/desktop/src/renderer/plugins/vimMode.tsx
Security: - Token file written with mode 0o600 (owner-only) - Constant-time token comparison via crypto.timingSafeEqual - 5MB request body limit (DoS prevention) - Server state cleared on listen failure (EADDRINUSE) - window:closeSelf rejects calls from main window API: - Note count uses repo.count() not list() with limit - Empty string content accepted in PUT (allows clearing notes) - /api/notes/quick route checked before :id (no ambiguity) - Port validation (1-65535), stop handler error handling Quick Capture: - Removed blur→close (prevented copying from other apps) - IME composition guard on keyboard handlers - Save errors displayed to user - Notebook fetch guarded against unmount - Border-radius removed (needs transparent:true) Plugins: - Mermaid: copy-to-clipboard instead of broken base64 URL - Mermaid/Math: style elements cleaned up on dispose - Vim: don't show indicator when dep missing, don't re-persist config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/main/index.ts (1)
929-937:⚠️ Potential issue | 🟡 Minor
before-quitdoes not await async shutdown.
void stopLocalServer()is fire-and-forget inside thebefore-quithandler; Electron will proceed to close the app regardless of whether the HTTP server has finished closing its sockets or draining in-flight responses. The OS will eventually reap the listener, but any active request gets dropped mid-response. If clean shutdown matters, switch to the async pattern where you callevent.preventDefault(), awaitstopLocalServer(), then callapp.quit()(guarded by a flag to avoid recursion).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/index.ts` around lines 929 - 937, The before-quit handler currently fire-and-forgets stopLocalServer(), so modify the app.on('before-quit') handler to perform an async graceful shutdown: add a module-level boolean (e.g., isQuitting) to guard against recursion, make the handler accept the event, call event.preventDefault() if not already quitting, set isQuitting, await stopLocalServer(), optionally await stopPluginWatcher() if it becomes async, then close db (db.close()) and log via getLogger().info('Database closed'), and finally call app.quit() to complete shutdown; ensure the guard prevents re-entering the handler.
♻️ Duplicate comments (2)
apps/desktop/src/renderer/plugins/math.tsx (1)
19-24:⚠️ Potential issue | 🟠 MajorStyle element still leaks on
dispose()— fix not applied here.The sibling change in
mermaid.tsxnow tracks the<style>node in a module-level variable and removes it on dispose, butmath.tsxstill only uses thestyleInjectedboolean without a reference to the element. Consequentlydispose()(lines 155–160) only unregisters the renderers — the injected<style>stays indocument.headfor the lifetime of the document, and becausestyleInjectedis never reset, a subsequentactivate()becomes a no-op. Please mirror the fix already done inmermaid.tsx.♻️ Proposed fix
-// Inject styles once -let styleInjected = false; +// Inject styles once; keep a ref to remove on dispose +let styleInjected = false; +let styleElement: HTMLStyleElement | null = null; function injectMathStyles() { if (styleInjected) return; styleInjected = true; const style = document.createElement('style'); + styleElement = style; style.textContent = ` @@ document.head.appendChild(style); } @@ return { dispose() { unregisterMath(); unregisterLatex(); + if (styleElement && styleElement.parentNode) { + styleElement.parentNode.removeChild(styleElement); + styleElement = null; + styleInjected = false; + } }, };Also applies to: 103-104, 155-161
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/plugins/math.tsx` around lines 19 - 24, The injected <style> element in math.tsx is only guarded by the boolean styleInjected and never removed on dispose(), causing a leak and preventing re-activation; modify injectMathStyles and module state to store the created <style> node in a module-level variable (e.g., mathStyleEl), set styleInjected when inserted, and in dispose() remove mathStyleEl from document.head (if present) and null it and reset styleInjected so subsequent activate() can re-insert; update any references to styleInjected/injectMathStyles accordingly to use and clear the stored element.apps/desktop/src/renderer/plugins/vimMode.tsx (1)
50-81:⚠️ Potential issue | 🟡 MinorGate VIM badge + warnings by real Vim availability
enable()still shows theVIMstatus unconditionally, and activation still re-warns on every startup whenenabled=true. This keeps signaling “active” behavior while keybindings are unavailable.💡 Proposed fix
activate(context) { let enabled = context.config.get<boolean>('enabled') ?? false; + // TODO: flip to true once `@codemirror/vim` is installed and wired. + const hasVimDependency = false; + let warnedMissingDep = false; const showStatus = () => { context.layout.addComponent('editor-status-bar', { id: 'vim-mode:status', component: VimStatusIndicator, @@ const hideStatus = () => { context.layout.removeComponent('vim-mode:status'); }; + + const applyState = () => { + if (enabled && hasVimDependency) { + showStatus(); + } else { + hideStatus(); + } + }; + + const warnMissingDepOnce = () => { + if (hasVimDependency || warnedMissingDep) return; + warnedMissingDep = true; + context.log.warn( + 'Vim mode enabled but `@codemirror/vim` is not installed. ' + + 'Add it to apps/desktop/package.json to activate Vim keybindings.' + ); + }; const enable = () => { enabled = true; context.config.set('enabled', true); - // TODO: When `@codemirror/vim` is installed, register the extension here: - // unregisterExt = context.registerExtensions('vim-keymap', [vim()]); - context.log.warn( - 'Vim mode enabled but `@codemirror/vim` is not installed. ' + - 'Add it to apps/desktop/package.json to activate Vim keybindings.' - ); - showStatus(); + warnMissingDepOnce(); + applyState(); }; const disable = () => { enabled = false; context.config.set('enabled', false); - hideStatus(); + applyState(); context.log.info('Vim mode disabled'); }; @@ - if (enabled) { - // Don't re-persist: just show the warning and status - context.log.warn( - 'Vim mode enabled but `@codemirror/vim` is not installed. ' + - 'Add it to apps/desktop/package.json to activate Vim keybindings.' - ); - // Don't show VIM indicator when the dependency is missing — - // it would mislead the user into thinking vim mode is active. - } + // Apply persisted state without re-emitting warning noise on startup. + applyState();#!/bin/bash set -euo pipefail # Verify whether Vim deps are present in manifests/lockfiles fd -HI '^package\.json$' . | xargs rg -n '"@codemirror/vim"|"@replit/codemirror-vim"' || true fd -HI 'pnpm-lock.yaml' . | xargs rg -n '"@codemirror/vim"|"@replit/codemirror-vim"' || true # Verify current plugin behavior paths (status + warning) rg -n -C3 'const enable =|showStatus\(|if \(enabled\)|log\.warn\(' apps/desktop/src/renderer/plugins/vimMode.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/plugins/vimMode.tsx` around lines 50 - 81, The plugin currently shows the VIM status and logs a warning on every startup even if `@codemirror/vim` isn't installed; change the flow to detect whether the Vim extension was actually registered (use the existing TODO spot where unregisterExt = context.registerExtensions('vim-keymap', [vim()]) would be set) and gate UI/warnings on that: in enable() only call showStatus() when the registered-extension flag/unregisterExt is truthy and emit context.log.warn('Vim mode enabled but ...') only when a user action enables while the extension is missing; remove or stop re-emitting the startup-wide context.log.warn inside the if (enabled) startup branch and avoid showing the VIM indicator there unless the extension is present (check the same registered-extension flag/unregisterExt).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/main/handlers/localServerHandlers.ts`:
- Around line 168-173: The ipc handler for 'localServer:getToken' can throw an
unhandled rejection because getOrCreateApiToken (which does fs I/O) is called
without try/catch; update the handler defined in localServerHandlers.ts so the
async function wraps the apiToken assignment and
getOrCreateApiToken(dataPaths.root) call in a try/catch and return a consistent
result shape (e.g., { ok: true, value: apiToken } on success and { ok: false,
error: <message or error> } on failure) to match other handlers; ensure you
still cache apiToken on success and include the original error message in the
error field.
In `@apps/desktop/src/main/index.ts`:
- Around line 891-906: The current window:closeSelf handler infers allowed
senders by finding a "main" window which is fragile; instead maintain an
explicit allowlist of webContents for windows you intend to allow closing (e.g.,
quick-capture and settings). Add a Set (e.g., allowedClosableWebContents) when
you create quickCaptureWindow and settingsWindow (in their create functions such
as createQuickCaptureWindow/createSettingsWindow or where quickCaptureWindow and
settingsWindow are assigned) and add their webContents.id (or webContents) to
the Set; remove the entry on the window's 'closed' event. In the
ipcMain.handle('window:closeSelf', ...) handler check event.sender (or
event.sender.id) against allowedClosableWebContents and only call win.close() if
it's explicitly present, otherwise return { ok: false, error: 'Not permitted to
close this window' }. This replaces the BrowserWindow.getAllWindows() find()
logic and ensures only explicitly opened quick-capture/settings windows can
request self-close.
In `@apps/desktop/src/renderer/components/QuickCapture.tsx`:
- Around line 53-67: The call to window.readied.notes.create returns a Result
type and currently handleClose() is invoked unconditionally; change the await
result handling in the QuickCapture component (the window.readied.notes.create
call) to check result.ok before closing: if result.ok is true then call
handleClose(), otherwise do not close, call setError(...) with a message derived
from result.error.type or result.error.error (prefer result.error.error if
present, fallback to result.error.type), and ensure setSaving(false) is called
on failure so the UI exits saving state; keep the existing catch for transport
exceptions but move handleClose() inside the success branch.
In `@apps/desktop/src/renderer/plugins/math.tsx`:
- Around line 109-117: The copySource callback sets setCopied(true) then
schedules setCopied(false) with an unreferenced setTimeout which may run after
unmount; fix by storing the timeout id in a ref (e.g. copyTimeoutRef) and clear
any existing timeout before creating a new one inside copySource, and add a
useEffect cleanup that clears copyTimeoutRef.current on unmount so
setCopied(false) won't run after the component unmounts; reference the
copySource callback, setCopied state setter, and useCallback/useEffect for
locating where to add the ref/cleanup.
- Around line 7-10: Remove the unreachable inline CSS for math from the
docstring and any duplicated blocks in
apps/desktop/src/renderer/plugins/math.tsx: specifically delete the
`.language-math-inline` (and related inline-only rules) CSS that is commented as
unusable without remark-math; if you want to preserve intent, instead add a
short TODO note referencing that these styles should be reintroduced when
`remark-math` is actually registered, or conditionally emit the CSS only when a
runtime flag or plugin-registration (the math plugin registration code in this
file) confirms `remark-math` is present.
In `@apps/desktop/src/renderer/plugins/mermaid.tsx`:
- Around line 93-111: copySource and copyForLive schedule
setCopied(false)/setLiveHint(false) with setTimeout but don't clear timers,
causing potential "state update on unmounted component" warnings; fix by storing
timeout ids in a ref (e.g., copyTimeoutRef) and clearing any existing timeout at
the start of each handler and on unmount via useEffect cleanup, then assign the
new timeout id to the ref so you can call clearTimeout(ref.current) before
setting state or when the component unmounts; update both copySource and
copyForLive to use this pattern and reference setCopied and setLiveHint
respectively.
- Around line 1-7: The plugin header and manifest.description in
apps/desktop/src/renderer/plugins/mermaid.tsx claim an "Open in Mermaid Live"
one‑click action but the UI only provides "Copy" and "Copy for Mermaid Live"
clipboard buttons (see the button labels and the renderer registration in this
file); either update the docstring and manifest.description to accurately state
that the plugin copies content to the clipboard (remove references to opening
mermaid.live) or implement the advertised behavior by changing the "Copy for
Mermaid Live" button handler to open the Mermaid Live editor URL (e.g.,
https://mermaid.live/edit#pako:... with the compressed payload) in a new
window/tab; modify the manifest.description and the top-of-file comment to match
whichever approach you choose and update the click handler for the "Copy for
Mermaid Live" button (or its helper function) accordingly so the code and
description are consistent.
---
Outside diff comments:
In `@apps/desktop/src/main/index.ts`:
- Around line 929-937: The before-quit handler currently fire-and-forgets
stopLocalServer(), so modify the app.on('before-quit') handler to perform an
async graceful shutdown: add a module-level boolean (e.g., isQuitting) to guard
against recursion, make the handler accept the event, call
event.preventDefault() if not already quitting, set isQuitting, await
stopLocalServer(), optionally await stopPluginWatcher() if it becomes async,
then close db (db.close()) and log via getLogger().info('Database closed'), and
finally call app.quit() to complete shutdown; ensure the guard prevents
re-entering the handler.
---
Duplicate comments:
In `@apps/desktop/src/renderer/plugins/math.tsx`:
- Around line 19-24: The injected <style> element in math.tsx is only guarded by
the boolean styleInjected and never removed on dispose(), causing a leak and
preventing re-activation; modify injectMathStyles and module state to store the
created <style> node in a module-level variable (e.g., mathStyleEl), set
styleInjected when inserted, and in dispose() remove mathStyleEl from
document.head (if present) and null it and reset styleInjected so subsequent
activate() can re-insert; update any references to
styleInjected/injectMathStyles accordingly to use and clear the stored element.
In `@apps/desktop/src/renderer/plugins/vimMode.tsx`:
- Around line 50-81: The plugin currently shows the VIM status and logs a
warning on every startup even if `@codemirror/vim` isn't installed; change the
flow to detect whether the Vim extension was actually registered (use the
existing TODO spot where unregisterExt =
context.registerExtensions('vim-keymap', [vim()]) would be set) and gate
UI/warnings on that: in enable() only call showStatus() when the
registered-extension flag/unregisterExt is truthy and emit context.log.warn('Vim
mode enabled but ...') only when a user action enables while the extension is
missing; remove or stop re-emitting the startup-wide context.log.warn inside the
if (enabled) startup branch and avoid showing the VIM indicator there unless the
extension is present (check the same registered-extension flag/unregisterExt).
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: da98e230-15d6-4d5a-b205-b5c1e8f0aaf6
📒 Files selected for processing (8)
apps/desktop/src/main/handlers/localServerHandlers.tsapps/desktop/src/main/index.tsapps/desktop/src/main/services/localServer.tsapps/desktop/src/renderer/components/QuickCapture.module.cssapps/desktop/src/renderer/components/QuickCapture.tsxapps/desktop/src/renderer/plugins/math.tsxapps/desktop/src/renderer/plugins/mermaid.tsxapps/desktop/src/renderer/plugins/vimMode.tsx
CI fixes: - tables.tsx: cast ReactElement with generic params for .props access - localServer.ts: attach cause to re-thrown errors (preserve-caught-error) Review findings: - localServerHandlers: getToken wrapped in try/catch with result shape - index.ts: closeSelf uses explicit allowlist Set instead of fragile find - index.ts: graceful before-quit with async shutdown + isQuitting guard - QuickCapture: check result.ok before closing, show error on failure - math.tsx: timeout ref cleanup, style element cleanup in dispose, removed dead CSS - mermaid.tsx: timeout ref cleanup, description corrected - vimMode.tsx: no VIM indicator when dep missing, no unnecessary startup warn Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All review findings addressed in 52edc7bCI Fixes
Review Findings
Verification: 0 lint errors, 17/17 typecheck pass |
Vercel's pnpm install fails because electron/node-gyp is referenced via SSH URL (git@github.com:) which Vercel can't resolve. The git config rewrite converts to HTTPS before install runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Release PR Merges `develop` into `main` to trigger a new release via semantic-release. ### Highlights since last release - **feat: allow disabling built-in plugins** + MCP server FTS5 fix (#237) - **fix: move @readied/* to devDependencies** — ASAR startup crash (#235) - **fix: exclude @readied/* from ASAR** (#233) - **feat: local HTTP API, quick capture, mermaid/math plugins** (#231) - **fix: replace removed lucide brand icons** (#229) - Multiple CI, type compat, and dependency fixes ### MCP server: sql.js → better-sqlite3 - FTS5 triggers now work (was crashing with `no such module: fts5`) - WAL mode for safe concurrent access with the desktop app - Runtime FTS5 check at startup with descriptive error ### Plugin system fixes (from code review) - Built-in plugins no longer briefly activate before enabled state loads - IPC listener cleanup no longer nukes unrelated listeners 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Built-in plugins can now be toggled on/off in settings; enabled/disabled states persist across sessions * Improved markdown editor URL auto-linking for more reliable link creation * **Bug Fixes** * Fixed URL auto-linking to correctly identify URLs when document content changes * **Tests** * Added comprehensive test suite for database trigger functionality * **Chores** * Migrated database backend for improved performance and stability <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
New Features
Bug Fix
ERR_PACKAGE_PATH_NOT_EXPORTED— workspace packages now bundled by electron-vite instead of externalized to node_modulesVerified
Test plan
pnpm typecheck— 17/17 passpnpm build(desktop) — builds successfully🤖 Generated with Claude Code
Summary by CodeRabbit