Skip to content

feat: Production readiness & security hardening#15

Merged
VariableThe merged 10 commits into
mainfrom
feat/production-hardening
Jun 21, 2026
Merged

feat: Production readiness & security hardening#15
VariableThe merged 10 commits into
mainfrom
feat/production-hardening

Conversation

@VariableThe

@VariableThe VariableThe commented Jun 21, 2026

Copy link
Copy Markdown
Owner

This PR completes a comprehensive security and architecture audit to harden PaperCache for production.

1. Build Pipeline & Repository Health

  • Switched CI & Release workflows to use npm ci for deterministic builds.
  • Converted release triggers to workflow_dispatch.
  • Updated package.json with proper node engines and private flags.
  • Cleaned up obsolete dependencies (marked).
  • Added SECURITY.md and CONTRIBUTING.md.

2. API Key Security & Hardening

  • Main Process Migration: The API Key is no longer stored in localStorage. It's encrypted at rest via Electron's safeStorage and held in the main process memory.
  • Renderer IPC: The UI now communicates exclusively via narrow IPC channels.
  • Background Migration: Added a shim to transparently upgrade legacy plaintext keys to secure storage.

3. Main Process Hardening

  • Implemented a strict Content-Security-Policy (CSP) allowing unsafe-eval specifically for mathjs, and tightening all other sources.
  • Protected default documentation commands via fs.existsSync.
  • Added electron-updater with MacOS tray checking integration.

4. UI/UX Improvements & Code Organization

  • Wrapped the application tree in an <ErrorBoundary> to prevent white-screen crashes.
  • Extracted Editor.tsx and extensions.ts out of App.tsx for a massively cleaner entry point.

5. Automated Tests

  • Extracted pure functions from MathEvaluator and wrote targeted tests bypassing the DOM.
  • Added tests verifying the safeStorage IPC bindings.
  • All test suites passing.

Summary by CodeRabbit

  • New Features
    • Added in-app update checking from the tray (and on startup).
    • API key management now runs through Electron IPC (set/status) and works with the editor’s AI commands.
  • Bug Fixes
    • Improved resilience with an ErrorBoundary and friendlier “Reload App” recovery UI.
  • Documentation
    • Added contribution and security policy documentation.
  • Chores
    • Updated CI to run across macOS/Ubuntu/Windows using npm ci, and adjusted release workflow/assets.
    • Added/updated tests for math evaluation and secure storage behavior.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@VariableThe, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 10 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 486f2409-9cdb-4ded-a14b-c07631d7395b

📥 Commits

Reviewing files that changed from the base of the PR and between e28566e and a2f80e7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • .gitignore
  • CONTRIBUTING.md
  • SECURITY.md
  • electron/main.ts
  • package.json
  • src/App.css
  • src/App.tsx
  • src/GraphView.tsx
  • src/Settings.css
  • src/Settings.test.tsx
  • src/Settings.tsx
  • src/components/Editor.tsx
  • src/components/ErrorBoundary.tsx
  • src/components/RemindersPage.tsx
  • src/lib/editor/MathEvaluator.ts
  • src/lib/editor/extensions.ts
  • src/main.tsx
  • src/setupTests.ts
  • vite.config.ts
📝 Walkthrough

Walkthrough

This PR migrates OpenAI API key storage from the renderer (localStorage/safeStorage) to the Electron main process via new IPC handlers, extracts the CodeMirror editor into a standalone Editor component with a useEditorExtensions hook, adds electron-updater auto-update support, introduces an ErrorBoundary, and expands CI to a multi-OS matrix with manual-dispatch releases.

Changes

App feature changes

Layer / File(s) Summary
ElectronAPI contract and AI store cleanup
src/types.d.ts, src/store/useAIStore.ts, electron/preload.ts
ElectronAPI gains setApiKey, getApiKeyStatus, checkForUpdates and removes apiKey from openAIChat args; AIState drops apiKey/setApiKey; preload exposes the updated surface.
Electron main: IPC handlers, CSP, auto-updater, command file helper
electron/main.ts
Adds memoryApiKey loaded from config.enc via safeStorage decryption, IPC handlers set-api-key/get-api-key-status/check-for-updates, updates openai-chat to use memoryApiKey, installs a CSP via onHeadersReceived, adds a writeCommandFile helper replacing inline fs.writeFileSync calls, and extends the tray menu with a Check for Updates item.
Editor component, useEditorExtensions hook, MathEvaluator refactor
src/components/Editor.tsx, src/lib/editor/extensions.ts, src/lib/editor/MathEvaluator.ts, tests/MathEvaluator.test.ts
New Editor component exposes EditorRef (dispatch/focus) via forwardRef and wires onChange to save notes and trigger math evaluation. useEditorExtensions builds the full CodeMirror extension set including keybindings, /ai//ctx//context Enter handler, and mousedown link routing. MathEvaluator extracts evaluateMathChanges as a testable static async helper.
App.tsx refactor, Settings IPC migration, ErrorBoundary, main entry
src/App.tsx, src/Settings.tsx, src/Settings.test.tsx, src/components/ErrorBoundary.tsx, src/main.tsx
App.tsx drops in-file CodeMirror setup and uses <Editor ref={editorRef} />. Settings switches from safeStorage to getApiKeyStatus/setApiKey IPC. ErrorBoundary class component added with reload fallback. main.tsx replaces the legacy migration helper with inline async key migration and wraps the root render in ErrorBoundary.
Packaging, auto-updater dependency, test infrastructure
package.json, src/setupTests.ts, tests/safeStorage.test.ts
package.json adds electron-updater, removes marked, adds engines >=22 and GitHub publish config. setupTests.ts conditionally installs window.electronAPI mock with vi.fn() spies. safeStorage renderer-flow test suite added.

CI/CD and repository docs

Layer / File(s) Summary
CI OS matrix and release workflow changes
.github/workflows/ci.yml, .github/workflows/release.yml
ci.yml adds a macOS/Ubuntu/Windows OS matrix and switches to npm ci. release.yml removes push trigger, adds workflow_dispatch, switches to npm ci, and extends asset upload globs to include *.yml and *.blockmap.
CONTRIBUTING.md and SECURITY.md
CONTRIBUTING.md, SECURITY.md
CONTRIBUTING.md added with setup, dev server, and PR contribution guidelines. SECURITY.md added with supported-version policy, private reporting instructions, and 48-hour acknowledgment timeline.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over Renderer,configEnc: API Key Setup Flow
    Renderer->>Preload: setApiKey(key)
    Preload->>MainProcess: IPC set-api-key
    MainProcess->>MainProcess: safeStorage.encryptString(key)
    MainProcess->>configEnc: fs.writeFileSync config.enc
    MainProcess-->>Renderer: true
  end
  rect rgba(144, 238, 144, 0.5)
    Note over Editor,ElectronAPI: /ai Command Flow
    Editor->>useEditorExtensions: Enter keypress on /ai line
    useEditorExtensions->>ElectronAPI: getApiKeyStatus()
    ElectronAPI-->>useEditorExtensions: true
    useEditorExtensions->>ElectronAPI: openAIChat(model, messages, baseURL)
    ElectronAPI->>MainProcess: IPC openai-chat (uses memoryApiKey)
    MainProcess-->>ElectronAPI: completion text
    ElectronAPI-->>useEditorExtensions: response
    useEditorExtensions->>Editor: replace thinking marker with completion
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • VariableThe/PaperCache#2: Both PRs modify .github/workflows/ci.yml to expand the OS matrix via strategy.matrix changes on runs-on.
  • VariableThe/PaperCache#7: Both PRs modify API key handling and OpenAI settings flow in src/Settings.tsx and src/store/useAIStore.ts.
  • VariableThe/PaperCache#10: Both PRs modify the openai-chat IPC handler in electron/main.ts and the MathEvaluator flow in src/lib/editor/MathEvaluator.ts.

Poem

🐰 Hop, hop! The key hides safe in main,
No more localStorage to explain!
The editor leaps from App's embrace,
A component now, with its own space.
ErrorBoundary catches every fall—
The rabbit built a safer hall! 🏠✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat: Production readiness & security hardening' directly summarizes the main objective of the changeset, which encompasses security hardening, API key encryption, CSP implementation, and production-readiness improvements across the codebase.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/production-hardening

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 (2)
src/App.tsx (2)

69-78: ⚠️ Potential issue | 🔴 Critical

Add missing useAIStore import.

Line 70 calls useAIStore.setState(...) but useAIStore is not imported. This will cause a ReferenceError at runtime. Add the import:

import { useAIStore } from './store/useAIStore'
🤖 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/App.tsx` around lines 69 - 78, The useAIStore store is being accessed via
useAIStore.setState() in the App.tsx file but the import statement is missing,
which will cause a ReferenceError at runtime. Add the missing import for
useAIStore from the store directory at the top of the App.tsx file so that the
useAIStore reference is properly available when the setState call is executed.

107-107: ⚠️ Potential issue | 🔴 Critical

Add missing variables to useSettingsStore destructuring at line 32.

themePreset, showRulings, textColor, and numColor are used at lines 107, 116, 153, and 155, but are not destructured from useSettingsStore. Update the destructuring:

Current code (line 32)
const { fontFamily, bgType, bgColor, bgImage } = useSettingsStore()

Should include: themePreset, showRulings, textColor, numColor.

🤖 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/App.tsx` at line 107, The variables themePreset, showRulings, textColor,
and numColor are being used throughout the component but are not being
destructured from the useSettingsStore hook call at line 32. Update the
destructuring assignment from useSettingsStore to include these four missing
variables alongside the existing fontFamily, bgType, bgColor, and bgImage
variables. This will ensure these variables are properly defined and available
for use in the className attribute and other locations where they appear.
🧹 Nitpick comments (6)
SECURITY.md (1)

9-12: ⚡ Quick win

Use a role-based security contact instead of a personal inbox.

Publishing a personal Gmail address makes the reporting channel harder to rotate and exposes an individual inbox unnecessarily. A dedicated security@ alias (or similar intake) fits the private-reporting policy better.

🤖 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 `@SECURITY.md` around lines 9 - 12, Replace the personal email address
adityasharma.variable@gmail.com in the SECURITY.md file with a dedicated
role-based security contact email address such as security@yourdomain.com or a
similar alias. This provides better security practices by creating a
team-managed inbox rather than exposing an individual's personal email, and
allows for easier rotation and management of the security reporting channel.
src/components/Editor.tsx (2)

10-10: 💤 Low value

Consider typing dispatch parameter and editorRef more strictly.

Using any for the transaction parameter and editor ref loses type safety. The CodeMirror types could provide better autocomplete and catch errors.

♻️ Optional type improvements
+import type { TransactionSpec } from '`@codemirror/state`'
+import type { ReactCodeMirrorRef } from '`@uiw/react-codemirror`'

 export interface EditorRef {
-  dispatch: (tx: any) => void
+  dispatch: (tx: TransactionSpec) => void
   focus: () => void
 }

-const editorRef = useRef<any>(null)
+const editorRef = useRef<ReactCodeMirrorRef>(null)

Also applies to: 22-22

🤖 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.tsx` at line 10, The dispatch function parameter uses
any for the transaction type and the editorRef also lacks proper typing, which
reduces type safety and IDE autocomplete support. Replace the any type in the
dispatch function signature with the appropriate CodeMirror transaction type,
and similarly update the editorRef type annotation to use the proper CodeMirror
editor reference type instead of any to ensure better type safety and
development experience.

37-53: ⚡ Quick win

Stale closure risk: notes array captured in callback may be outdated during async operations.

The handleEditorChange callback closes over notes, but between the callback creation and execution, the notes array could change (e.g., from external updates). Since the callback spreads and mutates based on notes[currentNoteIndex], you may overwrite changes made by other sources.

Consider using the functional update pattern with setNotes or reading fresh state via useAppStore.getState() inside the callback, similar to how extensions.ts does it.

♻️ Suggested approach
 const handleEditorChange = useCallback(
   (val: string, viewUpdate?: ViewUpdate) => {
-    const updatedNotes = [...notes]
-    if (updatedNotes[currentNoteIndex]) {
-      updatedNotes[currentNoteIndex].content = val
-      setNotes(updatedNotes)
-      window.electronAPI.saveNote(activeNote.id, val)
+    const currentNotes = useAppStore.getState().notes
+    const currentIndex = useAppStore.getState().currentNoteIndex
+    const note = currentNotes[currentIndex]
+    if (note) {
+      setNotes((prev) => {
+        const updated = [...prev]
+        if (updated[currentIndex]) {
+          updated[currentIndex] = { ...updated[currentIndex], content: val }
+        }
+        return updated
+      })
+      window.electronAPI.saveNote(note.id, val)
     }
🤖 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.tsx` around lines 37 - 53, The handleEditorChange
callback captures the notes array from its closure, which can become stale
during async operations or external state updates, causing potential loss of
concurrent changes. Instead of spreading the notes array directly in the
callback, refactor it to use a functional update pattern by passing a callback
function to setNotes that receives the current state as a parameter, ensuring
you always work with the most up-to-date notes array. This way, you read fresh
state inside the callback execution rather than relying on the captured notes
value, and you can remove notes and currentNoteIndex from the dependency array
since the updater function will have access to current values.
src/lib/editor/extensions.ts (1)

56-99: ⚡ Quick win

Duplicate code: Mod-Backspace and Mod-Delete handlers are identical.

Both handlers perform the exact same logic. Extract to a shared helper function to reduce duplication and maintenance burden.

♻️ Extract shared delete handler
+const deleteCurrentNote = (setNotes: typeof useAppStore.getState().setNotes, setCurrentNoteIndex: typeof useAppStore.getState().setCurrentNoteIndex) => {
+  const note = useAppStore.getState().notes[useAppStore.getState().currentNoteIndex]
+  if (!note) return true
+  if (note.id.startsWith('commands/')) {
+    alert('Files in the commands folder cannot be deleted.')
+    return true
+  }
+  if (confirm('Delete this note?')) {
+    window.electronAPI.deleteNote(note.id)
+    setNotes((prev) => prev.filter((n) => n.id !== note.id))
+    if (useAppStore.getState().currentNoteIndex >= useAppStore.getState().notes.length - 1) {
+      setCurrentNoteIndex(Math.max(0, useAppStore.getState().notes.length - 2))
+    }
+  }
+  return true
+}
+
 // Then in the keymap:
 { key: 'Mod-Backspace', run: () => deleteCurrentNote(setNotes, setCurrentNoteIndex) },
 { key: 'Mod-Delete', run: () => deleteCurrentNote(setNotes, setCurrentNoteIndex) },
🤖 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/lib/editor/extensions.ts` around lines 56 - 99, Extract the duplicate
note deletion logic from both the Mod-Backspace and Mod-Delete keyboard event
handlers into a single shared helper function. The helper function should
encapsulate all the logic that currently appears in both run functions including
the note retrieval, the commands folder check with alert, the deletion
confirmation dialog, the electronAPI.deleteNote call, the setNotes state update
to filter the deleted note, and the setCurrentNoteIndex logic to adjust the
index if needed. Then replace the identical code blocks in both the
Mod-Backspace and Mod-Delete handlers with calls to this new shared helper
function.
src/lib/editor/MathEvaluator.ts (1)

71-82: ⚡ Quick win

Potential use-after-destroy: accessing view.state in async callback after view may be unmounted.

If the editor component unmounts between scheduling the timeout and it firing, view.state.doc.toString() could throw or access stale state.

🛡️ Add a guard for destroyed view
   static triggerMathEvaluation(view: EditorView) {
     if (this.evalTimeout) window.clearTimeout(this.evalTimeout)
     this.evalTimeout = window.setTimeout(async () => {
+      // Guard against destroyed view
+      if (view.destroyed) return
       const docStr = view.state.doc.toString()
       const scope = VariableScope.getScope()
       const changes = await this.evaluateMathChanges(docStr, scope)
🤖 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/lib/editor/MathEvaluator.ts` around lines 71 - 82, The
triggerMathEvaluation method has a potential use-after-destroy issue where the
async callback accesses view.state without verifying the view is still valid. If
the editor component unmounts during the 300ms timeout period, accessing
view.state.doc.toString() could fail. Add a guard check at the beginning of the
setTimeout callback (after the arrow function opens) to verify the view is still
mounted and valid before attempting to access view.state. Check if the view has
a valid state property or use an appropriate method to determine if the
EditorView is still active before proceeding with the docStr and scope
initialization.
src/Settings.tsx (1)

75-77: 💤 Low value

Missing error handling for setApiKey failure and no way to clear the key.

Two concerns:

  1. If setApiKey returns false, the user receives no feedback that the save failed.
  2. Users cannot clear an existing API key - they can only replace it. If apiKey is empty, the key persists unchanged.

Consider handling the failure case and optionally allowing key removal:

Proposed improvement
   if (apiKey) {
-    await window.electronAPI.setApiKey(apiKey)
+    const success = await window.electronAPI.setApiKey(apiKey)
+    if (!success) {
+      console.error('Failed to save API key')
+      // Optionally show user feedback here
+      return
+    }
   }
🤖 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/Settings.tsx` around lines 75 - 77, The setApiKey call in the Settings
component is missing error handling and cannot clear existing API keys. Modify
the logic to handle both cases: when apiKey has a value, call setApiKey and
check if it returns false, then show error feedback to the user; when apiKey is
empty, still call setApiKey with the empty value to clear the stored key instead
of skipping the operation entirely. This ensures users get feedback on save
failures and can remove API keys when needed.
🤖 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 @.github/workflows/ci.yml:
- Around line 14-17: The GitHub Actions workflow references use moving version
tags (`@v4`) which poses a supply-chain security risk. Update the
actions/checkout@v4 reference to pin it to a specific commit SHA instead of the
moving tag, and similarly update the actions/setup-node@v4 reference to use a
pinned commit SHA. Additionally, add persist-credentials: false to the checkout
action configuration to prevent GitHub from persisting credentials in the
workflow, reducing credential exposure risk.

In `@CONTRIBUTING.md`:
- Line 26: The Pre-PR Checks section in the CONTRIBUTING.md file is incomplete
and only mentions npm run lint and npm run test, but CI also enforces npm run
typecheck and npm run format:check. Update the Pre-PR Checks bullet point to
include all four commands that contributors must run before opening a PR: lint,
test, typecheck, and format:check, so that contributors are aware of all
required checks and can avoid opening PRs with failing required checks.

In `@electron/main.ts`:
- Around line 656-667: In the set-api-key IPC handler, add error logging to the
catch block before returning false. Instead of silently catching and returning
false when an exception occurs during encryption or file writing, log the actual
error details from the caught exception using an appropriate logger so that disk
write failures, permission issues, or encryption problems become visible for
troubleshooting and debugging purposes.
- Around line 646-692: Remove the unused error variables from the catch blocks
in this section. In the initial try-catch block that reads from the config.enc
file and in the set-api-key IPC handler's try-catch block, change the catch
clauses from catching an unused error variable to using empty catch blocks
(remove the parameter). Additionally, review the code for any formatting issues
flagged by prettier and adjust indentation, spacing, or line breaks accordingly
to match the project's formatting standards.
- Around line 358-375: The session.defaultSession.webRequest.onHeadersReceived
callback and its CSP configuration have formatting inconsistencies that need to
be addressed. Reformat the onHeadersReceived callback to improve readability,
particularly the long CSP policy strings in the ternary operator. Break the
Content-Security-Policy header value across multiple lines if needed, ensure
consistent indentation throughout the block, and verify that spacing around
operators and function parameters follows ESLint/Prettier standards. Apply
automatic formatting tools to align the entire callback structure with the
project's linting configuration.

In `@src/lib/editor/extensions.ts`:
- Around line 120-206: The code inserts a thinking marker
(`\n\u200B...\u200C\n`) using `view.dispatch()` and later replaces it via
`docStr.replace()`, but this is fragile because if the user edits the document
while the async openAIChat call is pending, the marker position shifts and the
replacement fails silently. Instead of relying on string replacement, capture
the exact position where the marker was inserted (the `line.to` value), then use
`view.dispatch()` with a targeted change that replaces the specific range
containing the marker rather than searching for it in the modified document
string. Apply this approach consistently in all three locations where the marker
replacement currently occurs: in the response handler, the catch handler, and
the outer try-catch block.

In `@src/main.tsx`:
- Around line 36-44: The safeStorageDecrypt method returns the original
base64-encoded value on decryption failure rather than throwing or returning
empty, so the if (decrypted) check will pass even with invalid data. Add
validation logic after the safeStorageDecrypt call to ensure the decrypted value
looks like a valid API key before attempting to set it with setApiKey. Validate
characteristics such as expected prefix or reasonable length to distinguish
between actual decrypted API keys and garbage base64 data that would indicate
decryption failure.

---

Outside diff comments:
In `@src/App.tsx`:
- Around line 69-78: The useAIStore store is being accessed via
useAIStore.setState() in the App.tsx file but the import statement is missing,
which will cause a ReferenceError at runtime. Add the missing import for
useAIStore from the store directory at the top of the App.tsx file so that the
useAIStore reference is properly available when the setState call is executed.
- Line 107: The variables themePreset, showRulings, textColor, and numColor are
being used throughout the component but are not being destructured from the
useSettingsStore hook call at line 32. Update the destructuring assignment from
useSettingsStore to include these four missing variables alongside the existing
fontFamily, bgType, bgColor, and bgImage variables. This will ensure these
variables are properly defined and available for use in the className attribute
and other locations where they appear.

---

Nitpick comments:
In `@SECURITY.md`:
- Around line 9-12: Replace the personal email address
adityasharma.variable@gmail.com in the SECURITY.md file with a dedicated
role-based security contact email address such as security@yourdomain.com or a
similar alias. This provides better security practices by creating a
team-managed inbox rather than exposing an individual's personal email, and
allows for easier rotation and management of the security reporting channel.

In `@src/components/Editor.tsx`:
- Line 10: The dispatch function parameter uses any for the transaction type and
the editorRef also lacks proper typing, which reduces type safety and IDE
autocomplete support. Replace the any type in the dispatch function signature
with the appropriate CodeMirror transaction type, and similarly update the
editorRef type annotation to use the proper CodeMirror editor reference type
instead of any to ensure better type safety and development experience.
- Around line 37-53: The handleEditorChange callback captures the notes array
from its closure, which can become stale during async operations or external
state updates, causing potential loss of concurrent changes. Instead of
spreading the notes array directly in the callback, refactor it to use a
functional update pattern by passing a callback function to setNotes that
receives the current state as a parameter, ensuring you always work with the
most up-to-date notes array. This way, you read fresh state inside the callback
execution rather than relying on the captured notes value, and you can remove
notes and currentNoteIndex from the dependency array since the updater function
will have access to current values.

In `@src/lib/editor/extensions.ts`:
- Around line 56-99: Extract the duplicate note deletion logic from both the
Mod-Backspace and Mod-Delete keyboard event handlers into a single shared helper
function. The helper function should encapsulate all the logic that currently
appears in both run functions including the note retrieval, the commands folder
check with alert, the deletion confirmation dialog, the electronAPI.deleteNote
call, the setNotes state update to filter the deleted note, and the
setCurrentNoteIndex logic to adjust the index if needed. Then replace the
identical code blocks in both the Mod-Backspace and Mod-Delete handlers with
calls to this new shared helper function.

In `@src/lib/editor/MathEvaluator.ts`:
- Around line 71-82: The triggerMathEvaluation method has a potential
use-after-destroy issue where the async callback accesses view.state without
verifying the view is still valid. If the editor component unmounts during the
300ms timeout period, accessing view.state.doc.toString() could fail. Add a
guard check at the beginning of the setTimeout callback (after the arrow
function opens) to verify the view is still mounted and valid before attempting
to access view.state. Check if the view has a valid state property or use an
appropriate method to determine if the EditorView is still active before
proceeding with the docStr and scope initialization.

In `@src/Settings.tsx`:
- Around line 75-77: The setApiKey call in the Settings component is missing
error handling and cannot clear existing API keys. Modify the logic to handle
both cases: when apiKey has a value, call setApiKey and check if it returns
false, then show error feedback to the user; when apiKey is empty, still call
setApiKey with the empty value to clear the stored key instead of skipping the
operation entirely. This ensures users get feedback on save failures and can
remove API keys when needed.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0121f43a-08d6-4c04-9b0f-bc88790b9fd2

📥 Commits

Reviewing files that changed from the base of the PR and between ecdf678 and cf9770d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • CONTRIBUTING.md
  • SECURITY.md
  • electron/main.ts
  • electron/preload.ts
  • package.json
  • src/App.tsx
  • src/Settings.test.tsx
  • src/Settings.tsx
  • src/components/Editor.tsx
  • src/components/ErrorBoundary.tsx
  • src/lib/editor/MathEvaluator.ts
  • src/lib/editor/extensions.ts
  • src/main.tsx
  • src/setupTests.ts
  • src/store/useAIStore.ts
  • src/types.d.ts
  • tests/MathEvaluator.test.ts
  • tests/safeStorage.test.ts
💤 Files with no reviewable changes (1)
  • src/store/useAIStore.ts

Comment thread .github/workflows/ci.yml Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread electron/main.ts
Comment thread electron/main.ts
Comment thread electron/main.ts
Comment thread src/lib/editor/extensions.ts
Comment thread src/main.tsx
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 5 file(s) based on 7 unresolved review comments.

Files modified:

  • CONTRIBUTING.md
  • electron/main.ts
  • package-lock.json
  • src/lib/editor/extensions.ts
  • src/main.tsx

Commit: e28566e9cb989a4c0eb49a6896c39782d01e1305

The changes have been pushed to the feat/production-hardening branch.

Time taken: 3m 43s


⚠️ 1 file(s) could not be committed — the agent does not have permission to push to .github/workflows/. Please apply these changes manually:

.github/workflows/ci.yml — 1 change:

Lines 11–18
         os: [macos-latest, ubuntu-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-node@v4
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          persist-credentials: false
+      - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
         with:
           node-version: '22'
       - run: npm ci

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