Skip to content

feat: issue #1439#1441

Merged
zerob13 merged 3 commits intodevfrom
codex/issue-1439
Apr 9, 2026
Merged

feat: issue #1439#1441
zerob13 merged 3 commits intodevfrom
codex/issue-1439

Conversation

@zerob13
Copy link
Copy Markdown
Collaborator

@zerob13 zerob13 commented Apr 9, 2026

Summary by CodeRabbit

  • New Features
    • Added keyboard shortcuts to toggle the sidebar (Ctrl/Cmd+B) and workspace panel (Ctrl/Cmd+J) — they only act on the focused chat window.
  • Settings
    • Shortcuts appear in Settings > Shortcuts and can be recorded/cleared.
  • Localization
    • Shortcut labels added across supported languages.
  • Documentation
    • New spec describing shortcut behavior and acceptance criteria.

@zerob13 zerob13 changed the title feat: issue 1439 feat: issue #1439 Apr 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Adds keyboard shortcuts to toggle the left sidebar (Cmd/Ctrl+B) and right workspace panel (Cmd/Ctrl+J), wiring new shortcut events from main global registration through IPC to renderer handlers, introducing a sidebar Pinia store, updating shortcut settings/i18n, and adding tests for main and renderer behaviors.

Changes

Cohort / File(s) Summary
Spec / Docs
docs/specs/sidebar-workspace-shortcuts/spec.md
New specification describing default keybindings, acceptance criteria, customization, focus-scoped behavior, and non-goals.
DevDependencies
package.json
Bumped dev deps: markstream-vue and stream-monaco.
Main events & shortcuts
src/main/events.ts, src/main/presenter/configPresenter/shortcutKeySettings.ts, src/main/presenter/shortcutPresenter.ts
Added shortcut:toggle-sidebar and shortcut:toggle-workspace event names; added ToggleSidebar/ToggleWorkspace default keys; register global shortcuts that validate focused chat window and send IPC events to that window.
Renderer events & IPC runtime
src/renderer/src/events.ts, src/renderer/src/composables/useAppIpcRuntime.ts, src/renderer/src/App.vue
Added renderer event constants; extended useAppIpcRuntime options to accept toggle handlers; App.vue registers handlers that call sidebar/sidepanel store actions (workspace toggle guarded by chat route/session).
UI store
src/renderer/src/stores/ui/sidebar.ts
New Pinia store useSidebarStore with collapsed state, toggleSidebar() and setCollapsed() actions.
UI component & settings
src/renderer/src/components/WindowSideBar.vue, src/renderer/settings/components/ShortcutSettings.vue
WindowSideBar wired to sidebar store (data-testid attributes added); ShortcutSettings shows new ToggleSidebar and ToggleWorkspace entries with icons and i18n labels.
i18n
src/renderer/src/i18n/*/settings.json (da-DK, en-US, fa-IR, fr-FR, he-IL, ja-JP, ko-KR, pt-BR, ru-RU, zh-CN, zh-HK, zh-TW)
Added shortcuts.toggleSidebar and shortcuts.toggleWorkspace localized labels across locales.
Tests
test/main/presenter/shortcutPresenter.test.ts, test/renderer/components/App.startup.test.ts, test/renderer/components/WindowSideBar.test.ts
New/extended tests: verify global shortcut registration and focused-window dispatch in main; renderer startup tests validate IPC handlers calling store actions with/without active chat session; WindowSideBar tests verify store-driven toggle behavior and DOM changes.

Sequence Diagram

sequenceDiagram
    actor User
    participant Main as Main Process
    participant IPC as IPC / Electron
    participant Renderer as Renderer Window
    participant Store as Sidebar/Sidepanel Store
    participant Component as WindowSideBar Component

    User->>Main: Press Cmd/Ctrl+B or Cmd/Ctrl+J
    Main->>Main: globalShortcut handler runs
    Main->>Main: verify focused window ∈ presenter.getAllWindows()
    alt focused chat window
        Main->>IPC: send SHORTCUT_EVENTS.TOGGLE_SIDEBAR / TOGGLE_WORKSPACE
        IPC->>Renderer: deliver shortcut event
        Renderer->>Store: invoke handleToggleSidebar / handleToggleWorkspace
        Store->>Store: toggle collapsed or toggleWorkspace(sessionId)
        Store->>Component: reactive update
        Component->>Component: update UI (width/classes)
    else not focused or non-chat
        Main->>Main: no IPC dispatch
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Poem

🐰 I hopped through code to give keys a dance,

B for the sidebar, J for workspace's chance.
Stores now whisper and components sway,
Translations hum across every display.
Tap the keys — watch panels dart and prance!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'feat: issue #1439' is non-descriptive and fails to convey the actual changes—adding keyboard shortcuts to toggle the sidebar and workspace panel. Use a descriptive title that summarizes the main feature, such as 'feat: add keyboard shortcuts for sidebar and workspace toggle' or similar.
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 (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 codex/issue-1439

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
package.json (1)

160-170: ⚠️ Potential issue | 🟠 Major

Missing peer dependencies for markstream-vue@0.0.12-beta.1.

The package declares peer dependencies on shiki, stream-markdown, and @terrastruct/d2, but only shiki is available transitively via stream-monaco@0.0.22 (which depends on shiki@^3.19.0, satisfying the peer requirement of shiki@^3.13.0). However, stream-markdown and @terrastruct/d2 are genuinely absent from the dependency tree.

Either add these as explicit dependencies/devDependencies if markstream-vue requires them, or verify with the package maintainer whether these peer dependencies are optional and can be safely unmet.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 160 - 170, markstream-vue@0.0.12-beta.1 declares
peerDependencies on shiki, stream-markdown, and `@terrastruct/d2` but only shiki
is present transitively via stream-monaco; add the missing peers by adding
"stream-markdown" and "@terrastruct/d2" to package.json (either "dependencies"
or "devDependencies" depending on runtime need) so markstream-vue's peers are
satisfied, or alternatively confirm with the markstream-vue maintainer that
those peers are optional and document that decision; update package.json
accordingly and run a fresh install to validate the dependency tree.
🧹 Nitpick comments (1)
docs/specs/sidebar-workspace-shortcuts/spec.md (1)

23-25: Optional: vary Non-Goals bullet phrasing for readability.

Lines 23–25 all begin with “No …”. Rewording one bullet would make the section read more naturally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/specs/sidebar-workspace-shortcuts/spec.md` around lines 23 - 25, The
three Non-Goals bullets all start with “No …”, which is repetitive; update one
or two bullets to vary phrasing for better flow — e.g., keep the first as “No
persistence change for sidebar collapsed state.” but change the second to “We
will not add new buttons, menu items, or redesign the layout.” and the third to
“This increment will not include a full localization sweep for every existing
locale.” Edit the three lines shown in the diff to use this varied wording while
keeping the original intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/presenter/shortcutPresenter.ts`:
- Around line 71-93: The shortcut handlers currently send SHORTCUT_EVENTS to any
focused window; restrict them to the chat renderer by verifying the focused
window is the chat window before calling sendToWebContents. In the ToggleSidebar
and ToggleWorkspace handlers, replace the loose focusedWindow?.isFocused() check
with a check against the chat window (e.g., compare focusedWindow to
presenter.windowPresenter.getChatWindow(), or check focusedWindow?.type ===
'chat' / focusedWindow?.webContents.id ===
presenter.windowPresenter.getChatWindow()?.webContents.id) and only call
presenter.windowPresenter.sendToWebContents(...) when that check passes.

---

Outside diff comments:
In `@package.json`:
- Around line 160-170: markstream-vue@0.0.12-beta.1 declares peerDependencies on
shiki, stream-markdown, and `@terrastruct/d2` but only shiki is present
transitively via stream-monaco; add the missing peers by adding
"stream-markdown" and "@terrastruct/d2" to package.json (either "dependencies"
or "devDependencies" depending on runtime need) so markstream-vue's peers are
satisfied, or alternatively confirm with the markstream-vue maintainer that
those peers are optional and document that decision; update package.json
accordingly and run a fresh install to validate the dependency tree.

---

Nitpick comments:
In `@docs/specs/sidebar-workspace-shortcuts/spec.md`:
- Around line 23-25: The three Non-Goals bullets all start with “No …”, which is
repetitive; update one or two bullets to vary phrasing for better flow — e.g.,
keep the first as “No persistence change for sidebar collapsed state.” but
change the second to “We will not add new buttons, menu items, or redesign the
layout.” and the third to “This increment will not include a full localization
sweep for every existing locale.” Edit the three lines shown in the diff to use
this varied wording while keeping the original intent.
🪄 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: eb642cb3-b30a-433e-9491-004bb95a8f87

📥 Commits

Reviewing files that changed from the base of the PR and between 987aa6a and 2d57796.

📒 Files selected for processing (26)
  • docs/specs/sidebar-workspace-shortcuts/spec.md
  • package.json
  • src/main/events.ts
  • src/main/presenter/configPresenter/shortcutKeySettings.ts
  • src/main/presenter/shortcutPresenter.ts
  • src/renderer/settings/components/ShortcutSettings.vue
  • src/renderer/src/App.vue
  • src/renderer/src/components/WindowSideBar.vue
  • src/renderer/src/composables/useAppIpcRuntime.ts
  • src/renderer/src/events.ts
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/stores/ui/sidebar.ts
  • test/main/presenter/shortcutPresenter.test.ts
  • test/renderer/components/App.startup.test.ts
  • test/renderer/components/WindowSideBar.test.ts

Comment on lines +71 to +93
if (this.shortcutKeys.ToggleSidebar) {
globalShortcut.register(this.shortcutKeys.ToggleSidebar, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
void presenter.windowPresenter.sendToWebContents(
focusedWindow.webContents.id,
SHORTCUT_EVENTS.TOGGLE_SIDEBAR
)
}
})
}

if (this.shortcutKeys.ToggleWorkspace) {
globalShortcut.register(this.shortcutKeys.ToggleWorkspace, () => {
const focusedWindow = presenter.windowPresenter.getFocusedWindow()
if (focusedWindow?.isFocused()) {
void presenter.windowPresenter.sendToWebContents(
focusedWindow.webContents.id,
SHORTCUT_EVENTS.TOGGLE_WORKSPACE
)
}
})
}
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

Restrict shortcut dispatch to chat window only.

At Line 73 and Line 85, dispatch currently targets any focused window. If the settings window is focused, TOGGLE_SIDEBAR / TOGGLE_WORKSPACE are sent to the wrong renderer context.

💡 Suggested fix
     if (this.shortcutKeys.ToggleSidebar) {
       globalShortcut.register(this.shortcutKeys.ToggleSidebar, () => {
         const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_SIDEBAR
-          )
-        }
+        const settingsWindowId = presenter.windowPresenter.getSettingsWindowId()
+        if (!focusedWindow?.isFocused() || focusedWindow.id === settingsWindowId) return
+        void presenter.windowPresenter.sendToWebContents(
+          focusedWindow.webContents.id,
+          SHORTCUT_EVENTS.TOGGLE_SIDEBAR
+        )
       })
     }

     if (this.shortcutKeys.ToggleWorkspace) {
       globalShortcut.register(this.shortcutKeys.ToggleWorkspace, () => {
         const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_WORKSPACE
-          )
-        }
+        const settingsWindowId = presenter.windowPresenter.getSettingsWindowId()
+        if (!focusedWindow?.isFocused() || focusedWindow.id === settingsWindowId) return
+        void presenter.windowPresenter.sendToWebContents(
+          focusedWindow.webContents.id,
+          SHORTCUT_EVENTS.TOGGLE_WORKSPACE
+        )
       })
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/shortcutPresenter.ts` around lines 71 - 93, The shortcut
handlers currently send SHORTCUT_EVENTS to any focused window; restrict them to
the chat renderer by verifying the focused window is the chat window before
calling sendToWebContents. In the ToggleSidebar and ToggleWorkspace handlers,
replace the loose focusedWindow?.isFocused() check with a check against the chat
window (e.g., compare focusedWindow to
presenter.windowPresenter.getChatWindow(), or check focusedWindow?.type ===
'chat' / focusedWindow?.webContents.id ===
presenter.windowPresenter.getChatWindow()?.webContents.id) and only call
presenter.windowPresenter.sendToWebContents(...) when that check passes.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/presenter/shortcutPresenter.ts (1)

84-107: Optional: DRY the two toggle shortcut handlers.

Both registrations share the same logic; extracting a tiny helper would reduce repetition.

♻️ Suggested refactor
+    const registerFocusedChatToggle = (accelerator: string | undefined, event: string) => {
+      if (!accelerator) return
+      globalShortcut.register(accelerator, () => {
+        const focusedWindow = getFocusedChatWindow()
+        if (!focusedWindow) return
+        void presenter.windowPresenter.sendToWebContents(focusedWindow.webContents.id, event)
+      })
+    }
+
-    if (this.shortcutKeys.ToggleSidebar) {
-      globalShortcut.register(this.shortcutKeys.ToggleSidebar, () => {
-        const focusedWindow = getFocusedChatWindow()
-        if (focusedWindow) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_SIDEBAR
-          )
-        }
-      })
-    }
-
-    if (this.shortcutKeys.ToggleWorkspace) {
-      globalShortcut.register(this.shortcutKeys.ToggleWorkspace, () => {
-        const focusedWindow = getFocusedChatWindow()
-        if (focusedWindow) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_WORKSPACE
-          )
-        }
-      })
-    }
+    registerFocusedChatToggle(this.shortcutKeys.ToggleSidebar, SHORTCUT_EVENTS.TOGGLE_SIDEBAR)
+    registerFocusedChatToggle(this.shortcutKeys.ToggleWorkspace, SHORTCUT_EVENTS.TOGGLE_WORKSPACE)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/shortcutPresenter.ts` around lines 84 - 107, Both
ToggleSidebar and ToggleWorkspace handlers duplicate the same logic; extract a
small helper to DRY them. Create a helper function (e.g.,
registerToggleShortcut) that accepts a shortcut key and SHORTCUT_EVENTS value,
calls globalShortcut.register(key, () => { const focusedWindow =
getFocusedChatWindow(); if (focusedWindow) void
presenter.windowPresenter.sendToWebContents(focusedWindow.webContents.id,
event); }), then replace the two explicit globalShortcut.register calls for
this.shortcutKeys.ToggleSidebar and this.shortcutKeys.ToggleWorkspace with calls
to registerToggleShortcut(this.shortcutKeys.ToggleSidebar,
SHORTCUT_EVENTS.TOGGLE_SIDEBAR) and
registerToggleShortcut(this.shortcutKeys.ToggleWorkspace,
SHORTCUT_EVENTS.TOGGLE_WORKSPACE).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/presenter/shortcutPresenter.ts`:
- Around line 84-107: Both ToggleSidebar and ToggleWorkspace handlers duplicate
the same logic; extract a small helper to DRY them. Create a helper function
(e.g., registerToggleShortcut) that accepts a shortcut key and SHORTCUT_EVENTS
value, calls globalShortcut.register(key, () => { const focusedWindow =
getFocusedChatWindow(); if (focusedWindow) void
presenter.windowPresenter.sendToWebContents(focusedWindow.webContents.id,
event); }), then replace the two explicit globalShortcut.register calls for
this.shortcutKeys.ToggleSidebar and this.shortcutKeys.ToggleWorkspace with calls
to registerToggleShortcut(this.shortcutKeys.ToggleSidebar,
SHORTCUT_EVENTS.TOGGLE_SIDEBAR) and
registerToggleShortcut(this.shortcutKeys.ToggleWorkspace,
SHORTCUT_EVENTS.TOGGLE_WORKSPACE).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b47eb7a9-3cb9-4e4e-a48f-7a69f7850274

📥 Commits

Reviewing files that changed from the base of the PR and between 2d57796 and 089d8ca.

📒 Files selected for processing (2)
  • src/main/presenter/shortcutPresenter.ts
  • test/main/presenter/shortcutPresenter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/main/presenter/shortcutPresenter.test.ts

@zerob13 zerob13 merged commit c5afc64 into dev Apr 9, 2026
3 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