Skip to content

feat: add keybinds settings modal, auto-delete expired timers, and bump version to v0.5.6#76

Merged
VariableThe merged 2 commits into
mainfrom
feat/keybinds-and-timers-v0.5.6
Jun 28, 2026
Merged

feat: add keybinds settings modal, auto-delete expired timers, and bump version to v0.5.6#76
VariableThe merged 2 commits into
mainfrom
feat/keybinds-and-timers-v0.5.6

Conversation

@VariableThe

@VariableThe VariableThe commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Description

This release bumps the application version to v0.5.6 and introduces key workflow and UX enhancements:

  1. Dedicated Keybinds Settings Modal: Accessible via Settings (Cmd+Shift+S) to view and remap all application shortcuts with live recording. Designed with centered 3D keycaps and layout structure unified with the main Settings window.
  2. Updated Default Shortcuts: Shifted Reminders/Tasks view shortcut to Cmd+R and Timers panel to Cmd+T.
  3. Automatic Timer Cleanup: Completed countdown timers are automatically deleted 5 seconds after completing. Global backend event listener ensures auto-cleanup operates even when the Timers panel is closed.
  4. Graph View Link Parsing & Stability: Added support for standard markdown links ([Note](Note.md)) and wikilinks ([[Note]]).

Pre-PR Verification

  • Checked with npm run lintPassed (0 errors, 0 warnings).
  • Checked with npx vitest runPassed (35 tests passed across 8 suites).

Performance Reporting (Vite Build Output)

dist/index.html                                 0.51 kB │ gzip:   0.31 kB
dist/assets/index-Ckvloptw.css                 21.60 kB │ gzip:   4.96 kB
dist/assets/GraphView-GCnN-aum.js               9.18 kB │ gzip:   3.83 kB
dist/assets/quadtree-B3F59LyK.js              569.99 kB │ gzip: 142.76 kB
dist/assets/react-force-graph-3d-BbAl33Xt.js  774.70 kB │ gzip: 219.15 kB
dist/assets/index-B0Iraufj.js                 932.69 kB │ gzip: 306.49 kB
✓ built in 209ms

Summary by CodeRabbit

  • New Features

    • Added a dedicated Keybinds settings panel with live shortcut recording and reset-to-default controls.
    • Updated default shortcuts for Reminders/Tasks and Timers.
    • Expanded graph link detection to recognize standard markdown links and wiki links, not just file-style links.
  • Bug Fixes

    • Timers now auto-remove shortly after completion, even if the Timers view is closed.
    • Improved graph stability and centering behavior for a smoother 3D layout.
  • Chores

    • Updated the app version to v0.5.6.

@coderabbitai

coderabbitai Bot commented Jun 28, 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 34 minutes and 8 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 review availability.

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, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a0bb0c7-0ab8-4f33-98b5-c26002146ca3

📥 Commits

Reviewing files that changed from the base of the PR and between a59360a and 0e28a95.

📒 Files selected for processing (8)
  • AUDIT_LOG.md
  • src/App.tsx
  • src/GraphView.tsx
  • src/Settings.tsx
  • src/components/KeybindsModal.tsx
  • src/components/ShortcutInput.tsx
  • src/hooks/useGlobalHotkey.ts
  • src/lib/settingsKeys.ts
📝 Walkthrough

Walkthrough

PaperCache v0.5.6 adds a dedicated KeybindsModal with live shortcut recording via a new ShortcutInput component, refactors useGlobalHotkey to read configurable shortcuts from localStorage, moves timer-complete event handling from TimersPage to App.tsx with 5-second auto-deletion in useTimerStore, and expands 3D graph link parsing to support markdown links and wikilinks.

Changes

Keybinds Settings Modal & Configurable Hotkeys

Layer / File(s) Summary
SETTINGS_KEYS extension and useAppStore keybind state
src/lib/settingsKeys.ts, src/store/useAppStore.ts
Nine new shortcut storage keys added to SETTINGS_KEYS; showKeybindsModal boolean state and setShowKeybindsModal action added to useAppStore.
ShortcutInput component
src/components/ShortcutInput.tsx
New component manages recording state, pauses/resumes Electron global shortcuts, renders key "pill" display, and captures keydown to build shortcut strings.
KeybindsModal: init, save, reset, render
src/components/KeybindsModal.tsx
Initializes shortcut states from localStorage, handles Escape via capture-phase listener, saves via electronAPI and localStorage, resets to platform-appropriate defaults, renders grouped shortcut rows.
useGlobalHotkey refactored to configurable shortcuts
src/hooks/useGlobalHotkey.ts
Adds matchShortcut helper; replaces hardcoded key checks with localStorage-backed SETTINGS_KEYS lookups with platform defaults; adds Timers view shortcut; updates ESC guard and shortcuts reference text.
Settings.tsx and App.tsx modal wiring
src/Settings.tsx, src/App.tsx
Settings.tsx imports ShortcutInput from the new file and adds an "Open Keybinds Settings Panel" button. App.tsx imports and renders KeybindsModal overlay wired to showKeybindsModal state.

Timer Auto-Deletion and Event Handling Consolidation

Layer / File(s) Summary
useTimerStore: tickTimer refactor and auto-deletion
src/store/useTimerStore.ts
tickTimer reads current store state directly; completeTimer now schedules removeTimer after 5 seconds; new cleanExpiredTimers filters stale completed timers.
timer-complete event moved to App.tsx; TimersPage cleanup
src/App.tsx, src/components/TimersPage.tsx
App.tsx registers the Tauri timer-complete listener, calls completeTimer, and shows a toast. TimersPage removes its own redundant listener and related imports.

3D Graph Link Parsing and Z-Axis Centroids

Layer / File(s) Summary
buildFolderCentroids cz and multi-syntax link parsing
src/GraphView.tsx
buildFolderCentroids gains a cz component. Link parsing collects targets from /file, .md, and [[wikilink]] syntaxes; emits links only for valid, non-self targets present in nodeIds.

Version Bump to 0.5.6

Layer / File(s) Summary
Version bump across manifests, docs, and mocks
package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json, CHANGELOG.md, AUDIT_LOG.md, notes/New Features in v0.5.6.md, src/setupTests.ts
Version field updated to 0.5.6 in all manifests; changelog and audit log entries added; release notes file introduced; test mock updated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • VariableThe/PaperCache#5: Implements the Tasks/Reminders view and its initial Cmd/Ctrl+T shortcut binding, directly related to this PR's hotkey remapping of that action to Cmd+R.
  • VariableThe/PaperCache#49: Adds isHyprland detection and Alt-conditional shortcut behavior that KeybindsModal and useGlobalHotkey now depend on for platform-based modifier key selection.
  • VariableThe/PaperCache#56: Established the 3D graph foundation including buildFolderCentroids and the original /file link parsing that this PR extends with cz and multi-syntax link support.

Poem

🐇 Hop, hop, I bound my keys with care,
Each shortcut pill displayed with flair!
Timers fade away in five,
Graph links now help wikis thrive,
Version five-point-six is here — oh my!
The rabbit typed Cmd+R and gave a sigh. 🎉

🚥 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 clearly summarizes the main additions: the keybinds modal, timer cleanup, and version bump.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/keybinds-and-timers-v0.5.6

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.

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useGlobalHotkey.ts (1)

92-180: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bypass all global hotkeys while recording a shortcut.

ShortcutInput depends on its own onKeyDown, but this window capture handler runs first. While isRecordingShortcut is true, pressing an existing shortcut like Cmd+P can trigger the app action and prevent the new binding from being captured.

Proposed fix
     const handleGlobalKeyDown = async (e: KeyboardEvent) => {
       const state = useAppStore.getState()
       const defaultMod = isHyprland ? 'Alt' : 'CommandOrControl'
+
+      if (state.isRecordingShortcut) {
+        return
+      }
 
       if (e.key === 'Escape') {
         const isRenaming = useAppStore.getState().isRenaming
         const actionMenuIndex = useAppStore.getState().actionMenuIndex
-        const isRecordingShortcut = useAppStore.getState().isRecordingShortcut
-
-        if (isRecordingShortcut) return // Do not close app while recording shortcut
 
         // Dismiss overlays in priority order — highest-level first
🤖 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/hooks/useGlobalHotkey.ts` around lines 92 - 180, The global hotkey
handler in useGlobalHotkey should not run while ShortcutInput is recording a new
binding. Add an early guard in the window keydown capture logic, using the
existing isRecordingShortcut state, so matchShortcut checks and app actions are
skipped during recording. Keep the change localized to useGlobalHotkey and its
shortcut handler block so ShortcutInput can receive keys without
Cmd+P/Cmd+T/etc. triggering.
🧹 Nitpick comments (2)
src/GraphView.tsx (2)

35-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

cz centroid is computed but never used; the Z-axis layout has no effect.

The centroid map now carries cz, but the force setup only registers folderX/folderY (Lines 206–225) — there is no folderZ force consuming cz. Additionally, ForceGraph3D is configured with numDimensions={2} (Line 476), so the simulation is constrained to a plane and any Z positioning would be ignored anyway. As written, the "Z-Axis Centroids" change is dead code.

Either wire up a folderZ force and switch to numDimensions={3}, or drop cz to avoid misleading dead state.

♻️ Option A — wire up the Z force (3D)
+      fg.d3Force(
+        'folderZ',
+        d3
+          .forceZ<GraphNode>((node) => {
+            const c = centroids.get(node.folder)
+            return c ? c.cz : 0
+          })
+          .strength((node) => (node.folder && !draggedNodesRef.current.has(node.id) ? 0.008 : 0))
+      )

And set numDimensions={3} on ForceGraph3D.

♻️ Option B — drop the unused field
-): Map<string, { cx: number; cy: number; cz: number }> {
-  const centroids = new Map<string, { cx: number; cy: number; cz: number }>()
+): Map<string, { cx: number; cy: number }> {
+  const centroids = new Map<string, { cx: number; cy: number }>()
       cy: radius * Math.sin(angle),
-      cz: (i % 2 === 0 ? 1 : -1) * (15 * (i % 3)),
     })
🤖 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/GraphView.tsx` around lines 35 - 51, The `cz` value in
`buildFolderCentroids` is currently unused because the graph only applies
`folderX` and `folderY` forces and `ForceGraph3D` is still set to
`numDimensions={2}`. Update the `GraphView` setup to either add a `folderZ`
force that consumes the centroid `cz` values and switch `ForceGraph3D` to 3
dimensions, or remove `cz` from `buildFolderCentroids` and related centroid data
to keep the layout logic consistent.

158-165: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Optional: wikilink aliases and redundant matches.

The new parsing is correct and the nodeIds + self-link guards safely discard junk, so this is non-blocking. Two minor notes:

  • reMd (/\]\(([^)]+\.md)\)/g) also matches the ](/file …) syntax already handled by reFile, and external URLs ending in .md. These produce targets that get filtered out, so behavior is fine — just redundant work.
  • Wikilink aliases like [[Note|Display]] aren't handled; targetId becomes Note|Display.md and won't resolve. If aliases are expected, split on | before appending .md.
♻️ Optional alias handling
       while ((match = reWiki.exec(note.content)) !== null) {
-        let targetId = match[1].trim().replace(/\\/g, '/')
+        let targetId = match[1].split('|')[0].trim().replace(/\\/g, '/')
         if (!targetId.endsWith('.md')) targetId += '.md'
         targets.add(targetId)
       }
🤖 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/GraphView.tsx` around lines 158 - 165, The GraphView note target parsing
in the markdown-link branch is still missing wikilink alias handling: when
processing `note.content`, `targetId` should strip any `|display text` portion
from `[[Note|Display]]` before normalizing and appending `.md`, so the resolved
ID matches the actual node. Update the parsing logic alongside the existing
`reMd`/`targets.add` flow in `GraphView` to preserve current guards while
correctly resolving aliased wikilinks.
🤖 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/App.tsx`:
- Around line 76-90: The timer listener setup in App.tsx is missing cleanup for
the case where listen() resolves after the effect has already unmounted, so the
unlisten callback can be lost and the listener remain registered. Update the
effect around listen<string>('timer-complete', ...) to track the pending promise
and ensure the resolved unlisten function is invoked during cleanup even if it
arrives late; use the existing unlistenTimer/cleanup logic in the same effect to
cover both immediate and delayed resolution.

In `@src/components/KeybindsModal.tsx`:
- Around line 15-49: The shortcut state initializers in KeybindsModal are using
getItem(...) || fallback, which turns a saved empty string back into the default
shortcut. Update these reads to preserve cleared bindings by distinguishing null
from '', and apply the same pattern consistently across the shortcut state setup
in KeybindsModal and the related hotkey hydration path so empty values remain
persisted.

In `@src/components/ShortcutInput.tsx`:
- Around line 19-25: The ShortcutInput recording lifecycle only pauses/resumes
shortcuts during local state changes, so if the component unmounts while
recording is still true the paused global shortcut state can leak. Update the
ShortcutInput useEffect to return a cleanup that resumes shortcuts on unmount,
and also clear the recording flag/state that is set when starting shortcut
capture so the modal Escape guard does not remain stuck in recording mode. Use
the existing pauseShortcuts, resumeShortcuts, and setIsRecordingShortcut flow to
locate the fix.

In `@src/hooks/useGlobalHotkey.ts`:
- Around line 194-195: The reference note in useGlobalHotkey is hardcoding the
Tasks/Reminders and Timers Panel shortcuts, which breaks when the user remaps
them. Update the note generation to read from the configured shortcut values
used by useGlobalHotkey rather than embedding Cmd+R and Cmd+T directly. Make
sure the same source of truth that drives the hotkey bindings is also used when
composing the reference text so it stays accurate after remapping.

In `@src/Settings.tsx`:
- Around line 264-270: The edited Toggle App Visibility shortcut is not being
persisted, so it can revert after restart. Update the Settings save flow in
Settings.tsx, specifically the saveSettings logic that already handles shortcut
registration updates, to also write shortcutToggle into
SETTINGS_KEYS.SHORTCUT_TOGGLE before saving. Make sure the same source of truth
used by ShortcutInput for shortcutToggle is included alongside the existing
shortcut fields so the backend and stored settings stay in sync.

---

Outside diff comments:
In `@src/hooks/useGlobalHotkey.ts`:
- Around line 92-180: The global hotkey handler in useGlobalHotkey should not
run while ShortcutInput is recording a new binding. Add an early guard in the
window keydown capture logic, using the existing isRecordingShortcut state, so
matchShortcut checks and app actions are skipped during recording. Keep the
change localized to useGlobalHotkey and its shortcut handler block so
ShortcutInput can receive keys without Cmd+P/Cmd+T/etc. triggering.

---

Nitpick comments:
In `@src/GraphView.tsx`:
- Around line 35-51: The `cz` value in `buildFolderCentroids` is currently
unused because the graph only applies `folderX` and `folderY` forces and
`ForceGraph3D` is still set to `numDimensions={2}`. Update the `GraphView` setup
to either add a `folderZ` force that consumes the centroid `cz` values and
switch `ForceGraph3D` to 3 dimensions, or remove `cz` from
`buildFolderCentroids` and related centroid data to keep the layout logic
consistent.
- Around line 158-165: The GraphView note target parsing in the markdown-link
branch is still missing wikilink alias handling: when processing `note.content`,
`targetId` should strip any `|display text` portion from `[[Note|Display]]`
before normalizing and appending `.md`, so the resolved ID matches the actual
node. Update the parsing logic alongside the existing `reMd`/`targets.add` flow
in `GraphView` to preserve current guards while correctly resolving aliased
wikilinks.
🪄 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: 4069dcc3-1a4b-430f-965e-8d09e6a125aa

📥 Commits

Reviewing files that changed from the base of the PR and between 1c3908f and a59360a.

📒 Files selected for processing (17)
  • AUDIT_LOG.md
  • CHANGELOG.md
  • notes/New Features in v0.5.6.md
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/tauri.conf.json
  • src/App.tsx
  • src/GraphView.tsx
  • src/Settings.tsx
  • src/components/KeybindsModal.tsx
  • src/components/ShortcutInput.tsx
  • src/components/TimersPage.tsx
  • src/hooks/useGlobalHotkey.ts
  • src/lib/settingsKeys.ts
  • src/setupTests.ts
  • src/store/useAppStore.ts
  • src/store/useTimerStore.ts
💤 Files with no reviewable changes (1)
  • src/components/TimersPage.tsx

Comment thread src/App.tsx
Comment thread src/components/KeybindsModal.tsx
Comment thread src/components/ShortcutInput.tsx Outdated
Comment thread src/hooks/useGlobalHotkey.ts Outdated
Comment thread src/Settings.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant