Skip to content

fix: resolve architecture rule violations from AGENTS.md#12

Merged
VariableThe merged 1 commit into
mainfrom
fix-agent-rule-violations
Jun 20, 2026
Merged

fix: resolve architecture rule violations from AGENTS.md#12
VariableThe merged 1 commit into
mainfrom
fix-agent-rule-violations

Conversation

@VariableThe

@VariableThe VariableThe commented Jun 20, 2026

Copy link
Copy Markdown
Owner

This PR resolves several architectural rule violations identified in AGENTS.md.

Fixes:

  1. Zustand Slices: Refactored useAppStore() whole-store subscriptions into optimized slice subscriptions to prevent unnecessary re-renders across the React tree.
  2. IPC Listeners: Updated preload.ts event handlers (onSwipeGesture, onTriggerNewNote, onTriggerTasks) to return an unsubscribe cleanup function, preventing memory leaks and conforming to IPC isolation standards. Also updated src/types.d.ts appropriately.
  3. Timers: Replaced the continuous setInterval polling in RemindersPage.tsx with a recursive setTimeout chain to comply with background execution/efficiency guidelines.

Performance Reporting

Vite build output post-changes:

dist/index.html                     0.51 kB │ gzip:   0.31 kB
dist/assets/index-Cf-uibUY.css     15.19 kB │ gzip:   3.69 kB
dist/assets/esm-DV5G4TZQ.js       733.76 kB │ gzip: 200.38 kB
dist/assets/index-yp8qvpuN.js   1,050.85 kB │ gzip: 346.38 kB

Summary by CodeRabbit

  • New Features

    • Event listeners for gestures, notes, and tasks now support unsubscription for improved resource management.
  • Performance Improvements

    • Optimized timer efficiency in reminders functionality.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Three IPC listener registration methods in the Electron preload (onSwipeGesture, onTriggerNewNote, onTriggerTasks) now return cleanup functions via ipcRenderer.removeListener, with the ElectronAPI type updated accordingly. Across six React components and hooks, useAppStore() object destructuring is replaced with per-field selector calls. RemindersPage replaces setInterval with a recursive setTimeout loop.

Changes

IPC Listener Unsubscribe

Layer / File(s) Summary
ElectronAPI unsubscribe type contract
src/types.d.ts
onSwipeGesture return type changed from void to () => void in the ElectronAPI interface.
Preload IPC listener cleanup implementation
electron/preload.ts
onSwipeGesture, onTriggerNewNote, and onTriggerTasks store handlers in named variables, register with ipcRenderer.on, and return ipcRenderer.removeListener cleanup functions.

Zustand Selector Migration and Timer Fix

Layer / File(s) Summary
Per-field useAppStore selectors
src/App.tsx, src/components/MainActionMenu.tsx, src/components/NoteSearch.tsx, src/components/NoteTitleBar.tsx, src/hooks/useGlobalHotkey.ts, src/hooks/useNoteStorage.ts
All six files replace a single useAppStore() destructuring with individual useAppStore((state) => state.field) calls for each consumed state value and setter.
RemindersPage recursive setTimeout scheduler
src/components/RemindersPage.tsx
The useEffect timer changes from setInterval to a self-rescheduling scheduleNext function using setTimeout, with the timeout ID cleared on effect teardown.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 Hop, hop, cleanup's done at last!
No listeners linger from the past.
Each selector slims its call,
One field at a time — not all.
The timeout loops with newfound grace,
A tidy warren, every trace. ✨

🚥 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 directly addresses the main objective of the PR: resolving architecture rule violations from AGENTS.md through refactored store subscriptions, IPC cleanup functions, and optimized polling.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-agent-rule-violations

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: 2

🤖 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 `@electron/preload.ts`:
- Around line 25-33: The preload.ts file correctly implements the disposer
pattern by having onTriggerNewNote and onTriggerTasks return cleanup functions
that call ipcRenderer.removeListener. However, this unsubscribe contract is only
effective if callers actually invoke the returned disposer functions. While the
implementation in this file is correct, ensure the disposer pattern is being
properly consumed by verifying that callers in useGlobalHotkey.ts are capturing
the return values from onTriggerNewNote and onTriggerTasks and invoking them in
useEffect cleanup functions to prevent listener accumulation.

In `@src/components/RemindersPage.tsx`:
- Around line 71-79: The recursive scheduling in the scheduleNext function can
create dangling timers after unmount if the callback fires while teardown is in
progress. Add a boolean flag initialized to true (like isMounted) to track
component mount state, set it to false in the cleanup function return statement,
and check this flag at the beginning of the setTimeout callback before calling
scheduleNext() again. This prevents scheduling new timeouts after the component
has unmounted.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffa96f59-e8ea-4897-b794-da396326de98

📥 Commits

Reviewing files that changed from the base of the PR and between f2aed06 and 369b454.

📒 Files selected for processing (9)
  • electron/preload.ts
  • src/App.tsx
  • src/components/MainActionMenu.tsx
  • src/components/NoteSearch.tsx
  • src/components/NoteTitleBar.tsx
  • src/components/RemindersPage.tsx
  • src/hooks/useGlobalHotkey.ts
  • src/hooks/useNoteStorage.ts
  • src/types.d.ts

Comment thread electron/preload.ts
Comment on lines 25 to +33
onTriggerNewNote: (callback: () => void) => {
ipcRenderer.on('trigger-new-note', () => callback())
const handler = () => callback()
ipcRenderer.on('trigger-new-note', handler)
return () => ipcRenderer.removeListener('trigger-new-note', handler)
},
onTriggerTasks: (callback: () => void) => {
ipcRenderer.on('trigger-tasks', () => callback())
const handler = () => callback()
ipcRenderer.on('trigger-tasks', handler)
return () => ipcRenderer.removeListener('trigger-tasks', handler)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unsubscribe contract is implemented but not consumed by current renderer callers.

onTriggerNewNote/onTriggerTasks now return disposers, but src/hooks/useGlobalHotkey.ts (Lines 124-138 in provided context) does not capture or invoke them in useEffect cleanup, so listeners can still accumulate.

Suggested caller-side fix (outside this file)
// src/hooks/useGlobalHotkey.ts (inside the relevant useEffect)
+ let offNewNote: (() => void) | undefined
+ let offTasks: (() => void) | undefined

if (window.electronAPI.onTriggerNewNote) {
-  window.electronAPI.onTriggerNewNote(() => {
+  offNewNote = window.electronAPI.onTriggerNewNote(() => {
     // ...
   })
}

if (window.electronAPI.onTriggerTasks) {
-  window.electronAPI.onTriggerTasks(() => {
+  offTasks = window.electronAPI.onTriggerTasks(() => {
     // ...
   })
}

+ return () => {
+   offNewNote?.()
+   offTasks?.()
+ }

As per coding guidelines, trace context across related files to ensure cross-file contracts are actually enforced at usage sites.

🤖 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 `@electron/preload.ts` around lines 25 - 33, The preload.ts file correctly
implements the disposer pattern by having onTriggerNewNote and onTriggerTasks
return cleanup functions that call ipcRenderer.removeListener. However, this
unsubscribe contract is only effective if callers actually invoke the returned
disposer functions. While the implementation in this file is correct, ensure the
disposer pattern is being properly consumed by verifying that callers in
useGlobalHotkey.ts are capturing the return values from onTriggerNewNote and
onTriggerTasks and invoking them in useEffect cleanup functions to prevent
listener accumulation.

Comment on lines +71 to +79
let timeoutId: ReturnType<typeof setTimeout>
const scheduleNext = () => {
timeoutId = setTimeout(() => {
setNow(Date.now())
scheduleNext()
}, 10000) // update every 10s for better responsiveness
}
scheduleNext()
return () => clearTimeout(timeoutId)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent post-unmount re-scheduling in the recursive timer.

Line 73 can still schedule the next timeout after unmount if teardown happens while the callback is in flight, which leaves a dangling timer. Add a cancellation flag and nullable timeout guard in cleanup.

Suggested patch
 React.useEffect(() => {
-  let timeoutId: ReturnType<typeof setTimeout>
+  let timeoutId: ReturnType<typeof setTimeout> | null = null
+  let cancelled = false
   const scheduleNext = () => {
+    if (cancelled) return
     timeoutId = setTimeout(() => {
+      if (cancelled) return
       setNow(Date.now())
       scheduleNext()
     }, 10000) // update every 10s for better responsiveness
   }
   scheduleNext()
-  return () => clearTimeout(timeoutId)
+  return () => {
+    cancelled = true
+    if (timeoutId) clearTimeout(timeoutId)
+  }
 }, [])
📝 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.

Suggested change
let timeoutId: ReturnType<typeof setTimeout>
const scheduleNext = () => {
timeoutId = setTimeout(() => {
setNow(Date.now())
scheduleNext()
}, 10000) // update every 10s for better responsiveness
}
scheduleNext()
return () => clearTimeout(timeoutId)
let timeoutId: ReturnType<typeof setTimeout> | null = null
let cancelled = false
const scheduleNext = () => {
if (cancelled) return
timeoutId = setTimeout(() => {
if (cancelled) return
setNow(Date.now())
scheduleNext()
}, 10000) // update every 10s for better responsiveness
}
scheduleNext()
return () => {
cancelled = true
if (timeoutId) clearTimeout(timeoutId)
}
🤖 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/RemindersPage.tsx` around lines 71 - 79, The recursive
scheduling in the scheduleNext function can create dangling timers after unmount
if the callback fires while teardown is in progress. Add a boolean flag
initialized to true (like isMounted) to track component mount state, set it to
false in the cleanup function return statement, and check this flag at the
beginning of the setTimeout callback before calling scheduleNext() again. This
prevents scheduling new timeouts after the component has unmounted.

@coderabbitai

coderabbitai Bot commented Jun 20, 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 3 file(s) based on 2 unresolved review comments.

Files modified:

  • package-lock.json
  • src/components/RemindersPage.tsx
  • src/hooks/useGlobalHotkey.ts

Commit: 375b5a7ed340ba00ce84990dd554e2418aa2c09e

The changes have been pushed to the fix-agent-rule-violations branch.

Time taken: 2m 25s

@VariableThe VariableThe merged commit 8d028e6 into main Jun 20, 2026
2 checks passed
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