Skip to content

feat: settings manager with two-scope UI and keybinding system#11

Merged
Cheezeiii365 merged 7 commits into
mainfrom
feat/settings-manager
Mar 27, 2026
Merged

feat: settings manager with two-scope UI and keybinding system#11
Cheezeiii365 merged 7 commits into
mainfrom
feat/settings-manager

Conversation

@Cheezeiii365
Copy link
Copy Markdown
Owner

@Cheezeiii365 Cheezeiii365 commented Mar 27, 2026

Summary

  • Settings manager with schema-driven categories, two-scope (User/Workspace) toggle, and search/filter UI
  • Keybinding system refactor: separated commands from keybindings (VS Code-style), added KeybindingService, keybinding recorder, and keyboard shortcuts table
  • New settings pane with category sidebar, setting controls, and override indicators

Test plan

  • Open Settings pane from command palette or menu
  • Toggle between User and Workspace scopes — verify settings persist independently
  • Search/filter settings by keyword
  • Navigate categories in the sidebar
  • Open Keyboard Shortcuts tab, record a new keybinding, verify it takes effect
  • Verify existing commands still work with the refactored keybinding system

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Settings panel with User and Workspace scopes, searchable categories, per-setting modified indicators, reset-to-default, and theme picker.
    • Keyboard shortcuts UI: inline recorder with chord support, conflict detection, sortable/filterable shortcuts table, and persistent user overrides.
  • Refactor

    • Redesigned keyboard shortcut system to VS Code-style ordered rule dispatch with when-clause contexts and last-match-wins priority.

Cheezeiii365 and others added 2 commits March 27, 2026 12:10
Add a VS Code-style settings panel (Cmd+,) with User and Workspace scopes.
Schema-driven architecture makes adding new settings a one-line change.
Includes IPC handlers for per-layer read/write, theme picker, search,
modified indicators, and placeholder categories for future features.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Commands are now pure {id, label, category} with no keybinding knowledge.
Keybindings are a separate ordered rule list evaluated with last-match-wins
priority, enabling context-dependent dispatch via `when` clauses, multiple
bindings per command, and negative rules to suppress defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edef2539-06ae-4082-abae-56c904fc367f

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8eb43 and d7d8a69.

📒 Files selected for processing (7)
  • packages/main/src/settingsResolver.ts
  • packages/renderer/src/components/AppShell.tsx
  • packages/renderer/src/components/settings/KeybindingRecorder.tsx
  • packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx
  • packages/renderer/src/hooks/useKeybindingOverrides.ts
  • packages/renderer/src/lib/KeybindingService.ts
  • packages/renderer/src/styles/settings-pane.css

📝 Walkthrough

Walkthrough

This PR adds a VS Code-style KeybindingService, migrates keybinding storage to ordered rule overrides, and implements a two-scope (user/workspace) settings system with IPC persistence, preload APIs, renderer hooks, and a searchable Settings pane with keybinding editing and recording.

Changes

Cohort / File(s) Summary
Build Plan
IDE_BUILD_PLAN.md
Updated milestones: added 2.6c (when-aware dispatch) and expanded 6.1 to a two-scope, schema-driven settings UI and keyboard-shortcut manager details.
Main process IPC & persistence
packages/main/src/index.ts
Added IPC handlers for settings and keybinding overrides; persists user settings via electron-store and workspace settings to .aide/settings.json; broadcasts SETTINGS_CHANGED / KEYBINDINGS_CHANGED.
Preload bridge
packages/main/src/preload.ts
Exposed new WindowApi methods/events for user/workspace settings, built-in defaults, and keybinding override get/set + change subscriptions.
Settings resolution
packages/main/src/settingsResolver.ts
Exported BUILT_IN_DEFAULTS and simplified resolve logic to use projectOverrides ?? appDefaults for scalar fields.
Shared types & IPC channels
packages/shared/src/commands.ts, packages/shared/src/index.ts
Moved keybinding/when out of CommandDefinition; added KeybindingRule type, SettingsScope, and new IPC channel names; extended WindowApi types.
Keybinding core
packages/renderer/src/lib/KeybindingService.ts, packages/renderer/src/lib/defaultKeybindings.ts, packages/renderer/src/lib/formatKeybinding.ts
New KeybindingService: ordered rules, negative rules, chords, when evaluation, last-match-wins dispatch; centralized defaultKeybindings and formatting util.
Removed legacy shortcut manager
packages/renderer/src/lib/ShortcutManager.ts, packages/renderer/src/lib/ShortcutManager.ts removals
Removed old ShortcutManager and its register/unregister/use hooks; keybinding handling fully replaced by KeybindingService.
Command registry & defaults
packages/renderer/src/lib/CommandRegistry.ts, packages/renderer/src/lib/defaultCommands.ts
Decoupled keybinding/when from CommandRegistry (no longer registers shortcuts or gates execution by when); removed inline keybindings from default command metadata.
Renderer integration
packages/renderer/src/components/AppShell.tsx, packages/renderer/src/components/DockviewContainer.tsx, packages/renderer/src/main.tsx
Init keybindings on API ready (load overrides), register SettingsPane in Dockview, add settings stylesheet import and settings.open command.
Settings UI components
packages/renderer/src/components/panes/SettingsPane.tsx, packages/renderer/src/components/settings/*
Added SettingsPane and modular components: SettingsHeader, SettingsCategorySidebar, SettingsContent, SettingControl, SettingRow, KeyboardShortcutsTable, KeybindingRecorder; includes theme control and workspace-aware UI.
Hooks
packages/renderer/src/hooks/useSettings.ts, packages/renderer/src/hooks/useKeybindingOverrides.ts
Added useSettings (multi-layer state, live subscriptions, scope-aware mutations) and useKeybindingOverrides (load/save overrides, apply at runtime).
Settings schema & descriptors
packages/renderer/src/lib/settingsSchema.ts
Introduced setting descriptor/category types, SETTINGS_CATEGORIES and SETTINGS_DESCRIPTORS, and helpers for category/descriptor traversal.
Styles
packages/renderer/src/styles/settings-pane.css
Added full styling for Settings Pane layout, controls, shortcuts table, and keybinding recorder.
Command palette display
packages/renderer/src/components/CommandPalette.tsx
Display keybindings by querying KeybindingService (getKeybindingsForCommand), uses shared formatKeybinding util; removed reliance on cmd.keybinding.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Settings UI
    participant Hook as useSettings Hook
    participant IPC as Renderer IPC
    participant Main as Main Process
    participant Store as Electron Store / .aide

    UI->>Hook: setValue(key, value) / setScope(scope)
    Hook->>Hook: optimistic update (user/workspace layer)
    Hook->>IPC: window.api.setUserSetting / setWorkspaceSetting
    IPC->>Main: IPC_SETTINGS_SET_USER / IPC_SETTINGS_SET_WORKSPACE
    Main->>Store: write user (electron-store) or workspace (.aide/settings.json)
    Main->>Main: recompute resolved settings
    Main->>IPC: broadcast SETTINGS_CHANGED(resolved)
    IPC->>Hook: onSettingsChanged callback
    Hook->>UI: update resolved and re-render
Loading
sequenceDiagram
    participant Recorder as KeybindingRecorder
    participant UI as Settings UI
    participant Hook as useKeybindingOverrides
    participant Service as KeybindingService
    participant IPC as Renderer IPC
    participant Main as Main Process

    Recorder->>Service: getAllKeybindingRules()
    Service-->>Recorder: rules + suppressed metadata
    Recorder->>Recorder: detect conflicts, show hints
    UI->>Recorder: onRecord(newKey)
    Recorder->>Hook: addOverride({key, command, when})
    Hook->>IPC: window.api.setKeybindingOverrides(updated)
    IPC->>Main: IPC_KEYBINDINGS_SET
    Main->>Main: persist to electron-store
    Main->>IPC: broadcast KEYBINDINGS_CHANGED
    IPC->>Service: loadKeybindings(defaults, updated)
    Service->>Service: parse, suppress negatives, cache, apply
    Recorder->>UI: updated binding shown
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 In the burrow of keys I softly tread,

Rules and whens weave a ribbon of thread,
User and workspace each find their tune,
Chords hum at dusk and defaults at noon,
Hoppity hops — shortcuts now neatly fed. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: a settings manager with two-scope UI and a refactored keybinding system.

✏️ 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 feat/settings-manager

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[bot]
coderabbitai Bot previously requested changes Mar 27, 2026
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: 13

🧹 Nitpick comments (4)
packages/main/src/settingsResolver.ts (1)

90-103: Minor redundancy in merge logic.

userDefaults is fetched again at line 90, but appDefaults (from resolveAppDefaults) already incorporates user defaults. The fallback chain projectSettings.X ?? userDefaults.X ?? appDefaults.X is equivalent to projectSettings.X ?? appDefaults.X since appDefaults already contains userDefaults ?? BUILT_IN_DEFAULTS.

This doesn't cause incorrect behavior (the same value is selected either way), but the intermediate userDefaults lookup is redundant.

♻️ Simplified merge (optional)
 export async function resolveSettings(
   rootPath: string,
   store: Store<AppSettings>,
 ): Promise<ResolvedSettings> {
   const projectSettings = await readProjectSettings(rootPath)
   const appDefaults = resolveAppDefaults(store)
-  const userDefaults = (store.get('editorDefaults') ?? {}) as Partial<AideProjectSettings>

   // Merge: project > user > built-in
   return {
-    tabSize: projectSettings.tabSize ?? userDefaults.tabSize ?? appDefaults.tabSize,
+    tabSize: projectSettings.tabSize ?? appDefaults.tabSize,
-    insertSpaces:
-      projectSettings.insertSpaces ?? userDefaults.insertSpaces ?? appDefaults.insertSpaces,
+    insertSpaces: projectSettings.insertSpaces ?? appDefaults.insertSpaces,
     // ... apply same pattern to other scalar fields
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/main/src/settingsResolver.ts` around lines 90 - 103, The merge is
redundant because appDefaults (from resolveAppDefaults) already includes
userDefaults, so remove the intermediate userDefaults fallback: stop reading
store.get('editorDefaults') into userDefaults and change the merge expressions
in settingsResolver.ts that use projectSettings.* ?? userDefaults.* ??
appDefaults.* to simply projectSettings.* ?? appDefaults.* (update all
occurrences like tabSize, insertSpaces, wordWrap, rulers, fontSize, fontFamily,
formatOnSave) and remove the unused userDefaults variable.
packages/renderer/src/components/CommandPalette.tsx (1)

33-42: Consider adding dependencies to useMemo for dynamic keybinding updates.

The useMemo has an empty dependency array, so items are computed once on mount. If keybindings can change at runtime (e.g., user modifies a keybinding in settings while the palette is open), the displayed shortcuts won't update.

If keybinding changes are expected to require reopening the palette, this is fine. Otherwise, consider exposing a signal or version counter from KeybindingService to invalidate the memo.

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

In `@packages/renderer/src/components/CommandPalette.tsx` around lines 33 - 42,
The memoized mapping that builds palette items (inside useMemo) ignores changes
to keybindings, so update the useMemo dependency array to include a reactive
keybinding-change signal/version from your KeybindingService (or a prop/state
that updates when bindings change) so getKeybindingsForCommand and
formatKeybinding are re-run when keybindings change; locate the useMemo that
maps sorted -> { id, label, description } and add the KeybindingService
signal/version (or a keybindingsVersion state) to its dependencies, or subscribe
to keybinding updates and force an update that invalidates the memo.
packages/renderer/src/components/settings/SettingControl.tsx (1)

31-34: Clearing number input sets value to 0 instead of allowing empty state.

Number('') evaluates to 0, not NaN. When a user clears the input field, the value becomes 0 instead of remaining empty or becoming undefined. This might interfere with the "reset to default" workflow if defaults differ from 0.

Consider allowing empty values to represent "unset":

♻️ Optional: Allow empty values
         onChange={(e) => {
+          if (e.target.value === '') {
+            onChange(undefined)
+            return
+          }
           const num = Number(e.target.value)
           if (!Number.isNaN(num)) onChange(num)
         }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/components/settings/SettingControl.tsx` around lines 31
- 34, The current onChange handler uses Number(e.target.value) which turns an
empty string into 0; change the handler in SettingControl's onChange so it
explicitly checks for an empty string first and treats that as "unset" (e.g.,
call onChange(undefined) or onChange(null) per your app conventions), otherwise
parse the value to a number and call onChange(num) only when Number.isNaN(num)
is false; update any related prop typings if necessary to accept the unset
value.
packages/renderer/src/styles/settings-pane.css (1)

556-560: Use CSS variable for conflict color to support theming.

The conflict message uses a hardcoded color #e5534b instead of a CSS variable token. This won't adapt to theme changes.

♻️ Proposed fix
 .keybinding-recorder__conflict {
   font-size: 11px;
-  color: `#e5534b`;
+  color: var(--text-error);
   padding: 0 2px;
 }

As per coding guidelines: "Use CSS variable token system with data-theme attribute for theming, supporting dark and light mode"

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

In `@packages/renderer/src/styles/settings-pane.css` around lines 556 - 560,
Replace the hardcoded color in the .keybinding-recorder__conflict rule with the
theming CSS variable token (e.g. color: var(--color-error, `#e5534b`)); update the
rule so it uses var(...) instead of `#e5534b` and ensure the token is provided by
the existing data-theme variable system (so dark/light themes override it);
modify the .keybinding-recorder__conflict selector only to reference the CSS
variable while keeping font-size and padding intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/renderer/src/components/AppShell.tsx`:
- Around line 489-492: The IPC call window.api.getKeybindingOverrides() lacks
error handling so failures are swallowed; update the initialization to handle
rejection by adding a .catch() that logs the error (e.g., console.error or the
app's logger) and then calls loadKeybindings(defaultKeybindings, {}) or
loadKeybindings(defaultKeybindings, undefined) to ensure defaults still load.
Keep the existing success path (loadKeybindings(defaultKeybindings, overrides))
and reference window.api.getKeybindingOverrides, loadKeybindings, and
defaultKeybindings when making the change.

In `@packages/renderer/src/components/panes/SettingsPane.tsx`:
- Around line 21-24: The check in the useEffect that sets workspace availability
only treats null as "no workspace" but getWorkspaceRoot() can return undefined;
update the predicate used in the window.api.getWorkspaceRoot().then callback
(where setWorkspaceAvailable is called) to treat both null and undefined as "no
workspace" (e.g., use a null/undefined-safe check such as root != null or
Boolean(root)) so the Workspace scope won't show as available when
getWorkspaceRoot() is unset.

In `@packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx`:
- Around line 135-143: The current recording state uses recordingCommandId and
compares it to row.commandId, causing the KeybindingRecorder to appear for all
rows sharing the same commandId; change the recording state to a composite
identifier (e.g., recordingKey = `${row.commandId}-${row.key}-${row.when}` or
use the row index) and update all uses: replace recordingCommandId with the
composite (both where you render KeybindingRecorder and where you call
setRecordingCommandId) so each row has its own independent recording ID; ensure
onRecord calls addOverride({ key: kb, command: row.commandId, when: row.when })
as needed and reset the composite state to null with setRecordingCommandId(null)
(or setRecordingKey(null)) after recording or canceling.

In `@packages/renderer/src/components/settings/SettingsContent.tsx`:
- Around line 24-27: The code switches to search mode when searchQuery is truthy
even if it’s only whitespace; update the logic in SettingsContent to trim the
query first (e.g., const trimmed = searchQuery?.trim()) and use that trimmed
value to decide whether to call getSearchResults(trimmed) or fall back to
getCategorySections(activeCategory), so whitespace-only queries are treated as
empty and show categories instead of "No settings found."
- Around line 37-61: Special sections (the Color Theme row and
KeyboardShortcutsTable) are rendering even when a search is active and the
special UI blocks themselves don't match the query; update the render guards so
these blocks are only shown when search is not active or when the special item
explicitly matches the query. Concretely, wrap the existing checks for
section.categoryId === 'workbench.appearance' (the Theme select using theme and
onThemeChange) and section.categoryId === 'keyboardShortcuts'
(KeyboardShortcutsTable) with an additional condition such as !isSearchActive ||
matchesSpecialItem(section, searchQuery) (or wire in the existing
searchQuery/isSearchActive state) so the theme row and KeyboardShortcutsTable
only render when appropriate. Ensure to implement a small helper
matchesSpecialItem that tests the query against the special block
labels/descriptions if you choose explicit matching.

In `@packages/renderer/src/hooks/useKeybindingOverrides.ts`:
- Around line 36-44: addOverride and removeOverride build their new lists from
the render-time overrides array which can race and clobber concurrent updates;
change them to compute the new list from the latest persisted state instead of
the captured `overrides`. Specifically, in `addOverride` and `removeOverride`
(currently referencing `overrides` and calling `persist(updated)`), first fetch
the current persisted overrides (or use `persist`'s updater/callback form if
available) and then derive the new list (e.g., current.concat(rule) for add, or
filter the current list for remove) before calling `persist` so updates are
applied against the latest state rather than the stale render snapshot. Ensure
you still keep `useCallback` and its dependencies accurate (remove `overrides`
if you no longer reference it inside the callback).
- Around line 18-23: The initial keybinding load in the useEffect using
window.api.getKeybindingOverrides() can reject and leave loading true; wrap the
async call in a try/finally so setLoading(false) always runs, and in the catch
fallback to defaults by calling loadKeybindings(defaultKeybindings, {}) (or
similar) and setOverrides to an empty/default value; update the useEffect (and
its inner async function) around getKeybindingOverrides, setOverrides,
loadKeybindings, and setLoading to ensure failures are caught and defaults are
applied.

In `@packages/renderer/src/hooks/useSettings.ts`:
- Around line 31-58: The initial load() and the onSettingsChanged callback don't
handle rejected IPC promises, which can leave loading stuck true and produce
unhandled rejections; wrap the Promise.all call inside load() in a
try/catch/finally (check cancelled after await as now) so you
setBuiltInDefaults/setUserSettings/setWorkspaceSettings/setResolved only on
success, log the error in catch, and always setLoading(false) in finally; for
the subscription handler (window.api.onSettingsChanged) ensure the downstream
fetches (window.api.getUserSettings and window.api.getWorkspaceSettings) are
handled with .catch(...) or async/try-catch so failures are caught and logged
instead of causing unhandled rejections while still updating resolved via
setResolved(newResolved).
- Around line 62-91: The optimistic updates in setValue and resetToDefault
mutate local state via setUserSettings/setWorkspaceSettings before awaiting
window.api.setUserSetting/setWorkspaceSetting, but there is no reconciliation on
failure; wrap the API calls in try/catch and on error revert the optimistic
state to the previous value (capture prev inside the updater or store a snapshot
before updating), restore that snapshot in the catch block, and propagate or
surface the error; update both setValue and resetToDefault to follow this
pattern so local state stays consistent with persisted state.

In `@packages/renderer/src/lib/KeybindingService.ts`:
- Around line 219-228: The chord prefix branch is swallowing the first key even
when all matching chord rules are disabled; before calling
e.preventDefault()/e.stopPropagation() and setting pendingChord, evaluate the
rule's activation condition (the `when`/context for the rule) so you only enter
chord mode for rules that are actually enabled. Concretely, in the loop over
`rules` (inspect `entry`, `entry.parts`, and `partsMatch`) add a check that the
rule's `when`/context is satisfied (use the existing context-evaluation helper
or method used elsewhere in KeybindingService) and skip entries whose `when` is
false before assigning `pendingChord` and touching the event. Ensure you still
respect `entry.suppressed` and `entry.parts.length === 2` logic.
- Around line 135-157: loadKeybindings currently overwrites the shared rules
array which drops component-scoped/live rules added by useKeybinding; change
loadKeybindings to merge resolved rules with existing rules instead of replacing
them: filter the existing global rules (the variable rules) to keep any entries
with the component-scoped/source marker used by useKeybinding (e.g., source ===
'component' or a live flag), merge them with the newly computed resolved array
while deduplicating by rule.key (or rule id) so defaults/user overrides still
supersede, then run processRemovals and assign the merged array back to rules;
update logic around parse(rule.key) and processRemovals to operate on the merged
set to preserve lifecycle-scoped shortcuts.
- Around line 55-65: parseSingle currently collapses Cmd and Ctrl into a single
meta flag and leaves key strings unnormalized; update parseSingle (and the
corresponding multi-segment parsing at the other block) to return separate
booleans for cmd (metaKey) and ctrl (ctrlKey) instead of a single meta, and
normalize the parsed key token to the browser KeyboardEvent.key canonical values
(e.g., use " " for Space, "ArrowUp"/"ArrowDown"/"ArrowLeft"/"ArrowRight" for
arrows, map aliases like "enter" -> "Enter", "esc" -> "Escape", and ensure
correct casing) so later comparisons against event.key succeed; keep shift and
alt detection but include opt alias for alt, and ensure the returned
ShortcutParts shape is updated to include ctrl and cmd fields used by the rest
of the code.

In `@packages/renderer/src/lib/settingsSchema.ts`:
- Around line 126-127: getCategoriesWithSettings currently builds categories
only from SETTINGS_DESCRIPTORS, omitting special non-descriptor pages; update
the function to also include category values from your non-descriptor pages
collection (e.g., SETTINGS_PAGES, SETTINGS_NON_DESCRIPTOR_PAGES, or whatever
array/object holds the special pages) by iterating that collection and adding
each page.category into the same Set before returning it so the sidebar treats
those sections as non-empty; keep getCategoriesWithSettings and
SETTINGS_DESCRIPTORS as the primary anchors when making the change.

---

Nitpick comments:
In `@packages/main/src/settingsResolver.ts`:
- Around line 90-103: The merge is redundant because appDefaults (from
resolveAppDefaults) already includes userDefaults, so remove the intermediate
userDefaults fallback: stop reading store.get('editorDefaults') into
userDefaults and change the merge expressions in settingsResolver.ts that use
projectSettings.* ?? userDefaults.* ?? appDefaults.* to simply projectSettings.*
?? appDefaults.* (update all occurrences like tabSize, insertSpaces, wordWrap,
rulers, fontSize, fontFamily, formatOnSave) and remove the unused userDefaults
variable.

In `@packages/renderer/src/components/CommandPalette.tsx`:
- Around line 33-42: The memoized mapping that builds palette items (inside
useMemo) ignores changes to keybindings, so update the useMemo dependency array
to include a reactive keybinding-change signal/version from your
KeybindingService (or a prop/state that updates when bindings change) so
getKeybindingsForCommand and formatKeybinding are re-run when keybindings
change; locate the useMemo that maps sorted -> { id, label, description } and
add the KeybindingService signal/version (or a keybindingsVersion state) to its
dependencies, or subscribe to keybinding updates and force an update that
invalidates the memo.

In `@packages/renderer/src/components/settings/SettingControl.tsx`:
- Around line 31-34: The current onChange handler uses Number(e.target.value)
which turns an empty string into 0; change the handler in SettingControl's
onChange so it explicitly checks for an empty string first and treats that as
"unset" (e.g., call onChange(undefined) or onChange(null) per your app
conventions), otherwise parse the value to a number and call onChange(num) only
when Number.isNaN(num) is false; update any related prop typings if necessary to
accept the unset value.

In `@packages/renderer/src/styles/settings-pane.css`:
- Around line 556-560: Replace the hardcoded color in the
.keybinding-recorder__conflict rule with the theming CSS variable token (e.g.
color: var(--color-error, `#e5534b`)); update the rule so it uses var(...) instead
of `#e5534b` and ensure the token is provided by the existing data-theme variable
system (so dark/light themes override it); modify the
.keybinding-recorder__conflict selector only to reference the CSS variable while
keeping font-size and padding intact.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8e5a219-62d1-484d-8cf6-ca0bbf9543d5

📥 Commits

Reviewing files that changed from the base of the PR and between 2957d20 and 5e8eb43.

📒 Files selected for processing (28)
  • IDE_BUILD_PLAN.md
  • packages/main/src/index.ts
  • packages/main/src/preload.ts
  • packages/main/src/settingsResolver.ts
  • packages/renderer/src/components/AppShell.tsx
  • packages/renderer/src/components/CommandPalette.tsx
  • packages/renderer/src/components/DockviewContainer.tsx
  • packages/renderer/src/components/panes/SettingsPane.tsx
  • packages/renderer/src/components/settings/KeybindingRecorder.tsx
  • packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx
  • packages/renderer/src/components/settings/SettingControl.tsx
  • packages/renderer/src/components/settings/SettingRow.tsx
  • packages/renderer/src/components/settings/SettingsCategorySidebar.tsx
  • packages/renderer/src/components/settings/SettingsContent.tsx
  • packages/renderer/src/components/settings/SettingsHeader.tsx
  • packages/renderer/src/hooks/useKeybindingOverrides.ts
  • packages/renderer/src/hooks/useSettings.ts
  • packages/renderer/src/lib/CommandRegistry.ts
  • packages/renderer/src/lib/KeybindingService.ts
  • packages/renderer/src/lib/ShortcutManager.ts
  • packages/renderer/src/lib/defaultCommands.ts
  • packages/renderer/src/lib/defaultKeybindings.ts
  • packages/renderer/src/lib/formatKeybinding.ts
  • packages/renderer/src/lib/settingsSchema.ts
  • packages/renderer/src/main.tsx
  • packages/renderer/src/styles/settings-pane.css
  • packages/shared/src/commands.ts
  • packages/shared/src/index.ts
💤 Files with no reviewable changes (1)
  • packages/renderer/src/lib/ShortcutManager.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{css,scss,tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Use CSS variable token system with data-theme attribute for theming, supporting dark and light mode

Files:

  • packages/renderer/src/main.tsx
  • packages/renderer/src/components/DockviewContainer.tsx
  • packages/main/src/settingsResolver.ts
  • packages/shared/src/commands.ts
  • packages/renderer/src/lib/formatKeybinding.ts
  • packages/renderer/src/components/CommandPalette.tsx
  • packages/renderer/src/styles/settings-pane.css
  • packages/renderer/src/lib/defaultCommands.ts
  • packages/renderer/src/lib/defaultKeybindings.ts
  • packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx
  • packages/renderer/src/components/AppShell.tsx
  • packages/renderer/src/components/settings/SettingsHeader.tsx
  • packages/renderer/src/components/panes/SettingsPane.tsx
  • packages/renderer/src/components/settings/SettingRow.tsx
  • packages/main/src/index.ts
  • packages/renderer/src/components/settings/SettingControl.tsx
  • packages/renderer/src/lib/settingsSchema.ts
  • packages/renderer/src/lib/KeybindingService.ts
  • packages/renderer/src/components/settings/KeybindingRecorder.tsx
  • packages/renderer/src/components/settings/SettingsContent.tsx
  • packages/renderer/src/lib/CommandRegistry.ts
  • packages/main/src/preload.ts
  • packages/renderer/src/components/settings/SettingsCategorySidebar.tsx
  • packages/shared/src/index.ts
  • packages/renderer/src/hooks/useSettings.ts
  • packages/renderer/src/hooks/useKeybindingOverrides.ts
🧠 Learnings (4)
📚 Learning: 2026-03-25T11:00:59.388Z
Learnt from: Cheezeiii365
Repo: Cheezeiii365/aIDE PR: 4
File: packages/renderer/src/components/FileTree/ContextMenu.tsx:16-20
Timestamp: 2026-03-25T11:00:59.388Z
Learning: Since aIDE does not support Windows, path-handling utilities and related code should assume POSIX-style separators (`/`) (e.g., helpers like `dirname`/`basename` that split on `/`). During review, do not require Windows-style backslash (`\\`) support or `path.sep`/cross-platform normalization unless the project explicitly adds Windows support.

Applied to files:

  • packages/renderer/src/main.tsx
  • packages/renderer/src/components/DockviewContainer.tsx
  • packages/main/src/settingsResolver.ts
  • packages/shared/src/commands.ts
  • packages/renderer/src/lib/formatKeybinding.ts
  • packages/renderer/src/components/CommandPalette.tsx
  • packages/renderer/src/lib/defaultCommands.ts
  • packages/renderer/src/lib/defaultKeybindings.ts
  • packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx
  • packages/renderer/src/components/AppShell.tsx
  • packages/renderer/src/components/settings/SettingsHeader.tsx
  • packages/renderer/src/components/panes/SettingsPane.tsx
  • packages/renderer/src/components/settings/SettingRow.tsx
  • packages/main/src/index.ts
  • packages/renderer/src/components/settings/SettingControl.tsx
  • packages/renderer/src/lib/settingsSchema.ts
  • packages/renderer/src/lib/KeybindingService.ts
  • packages/renderer/src/components/settings/KeybindingRecorder.tsx
  • packages/renderer/src/components/settings/SettingsContent.tsx
  • packages/renderer/src/lib/CommandRegistry.ts
  • packages/main/src/preload.ts
  • packages/renderer/src/components/settings/SettingsCategorySidebar.tsx
  • packages/shared/src/index.ts
  • packages/renderer/src/hooks/useSettings.ts
  • packages/renderer/src/hooks/useKeybindingOverrides.ts
📚 Learning: 2026-03-25T11:11:52.630Z
Learnt from: CR
Repo: Cheezeiii365/aIDE PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T11:11:52.630Z
Learning: Applies to **/*layout*.{ts,tsx} : Use Dockview 5.x for layout and tiling panes

Applied to files:

  • packages/renderer/src/components/DockviewContainer.tsx
  • packages/renderer/src/components/AppShell.tsx
  • packages/renderer/src/components/panes/SettingsPane.tsx
📚 Learning: 2026-03-25T11:11:52.630Z
Learnt from: CR
Repo: Cheezeiii365/aIDE PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T11:11:52.630Z
Learning: Always update IDE_BUILD_PLAN.md when making changes to the codebase

Applied to files:

  • IDE_BUILD_PLAN.md
📚 Learning: 2026-03-25T11:11:52.630Z
Learnt from: CR
Repo: Cheezeiii365/aIDE PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-25T11:11:52.630Z
Learning: Applies to **/*.{css,scss,tsx,ts} : Use CSS variable token system with `data-theme` attribute for theming, supporting dark and light mode

Applied to files:

  • packages/shared/src/index.ts
🔇 Additional comments (18)
packages/renderer/src/lib/formatKeybinding.ts (1)

7-15: LGTM!

Clean utility function that correctly normalizes keybinding strings for macOS display. The case-insensitive replacements and collapsing of separators work well for the intended use case.

packages/renderer/src/main.tsx (1)

19-19: LGTM!

The CSS import for the settings pane is appropriately placed with other style imports.

packages/renderer/src/components/DockviewContainer.tsx (1)

11-11: LGTM!

The SettingsPane import and registration follow the established pattern, and the component key 'settingsPane' correctly matches the addPanel call in AppShell.tsx.

Also applies to: 23-23

packages/main/src/settingsResolver.ts (1)

16-16: LGTM!

Exporting BUILT_IN_DEFAULTS enables the renderer to access built-in defaults through IPC without duplicating the values.

packages/renderer/src/lib/defaultKeybindings.ts (2)

7-49: LGTM! Well-organized keybinding rules.

The keybinding rules are cleanly categorized, and the "last match wins" documentation correctly reflects the KeybindingService implementation (per context snippet showing loadKeybindings concatenates defaults before overrides).


36-45: Verify that all referenced commands are registered.

Several command IDs referenced in lines 36-45 (terminal.new, view.toggleSidebar, panel.close, markdown.togglePreview, commandPalette.open, quickOpen.open, search.findInFiles, settings.open) require verification. Confirm these commands are registered in defaultCommands.ts or elsewhere (likely in AppShell.tsx via useCommand hooks).

packages/renderer/src/lib/defaultCommands.ts (1)

11-12: LGTM! Clean separation of commands from keybindings.

The removal of keybinding properties from command definitions aligns with the new architecture where defaultKeybindings.ts serves as the single source of truth for key mappings. Command definitions are now properly focused on identity and labeling.

Also applies to: 30-31, 50-51, 57-58, 63-64, 69-70, 75-76, 80-81, 86-87, 136-137, 155-156, 163-164

packages/shared/src/commands.ts (1)

5-21: LGTM! Well-designed type separation.

The refactored CommandDefinition (pure identity) and new KeybindingRule (binding + context) interfaces cleanly model the VS Code-style separation. The documentation accurately describes the "last-match-wins" semantics and negative rule convention.

packages/renderer/src/components/AppShell.tsx (1)

669-689: LGTM!

The settings.open command correctly follows the pattern used for other Dockview panels—activating existing or creating new. Clean implementation.

packages/renderer/src/components/settings/SettingRow.tsx (1)

1-36: LGTM!

Clean component with well-structured props, proper conditional rendering for modified state, and accessible reset button with title attribute.

packages/main/src/index.ts (1)

519-609: LGTM!

The settings and keybinding IPC handlers are well-structured:

  • Proper null/undefined handling for value deletion
  • Correct error recovery with empty object fallback
  • Migration logic correctly handles the legacy Record<string, string> format
  • Resolved settings are broadcast after both user and workspace updates
packages/renderer/src/components/settings/SettingsHeader.tsx (1)

1-56: LGTM!

Well-implemented header with proper disabled state handling and helpful tooltip for unavailable workspace scope. The autoFocus on the search input is appropriate UX for a settings pane.

packages/renderer/src/styles/settings-pane.css (1)

1-100: LGTM on theming implementation.

The stylesheet correctly uses CSS variable tokens throughout for backgrounds, text colors, borders, and accents, ensuring proper dark/light theme support.

packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx (1)

23-68: LGTM on data transformation logic.

The rows computation correctly combines keybinding rules with unbound commands, determines override indices for user-sourced rules, and sorts bound commands before unbound ones.

IDE_BUILD_PLAN.md (1)

987-997: LGTM!

The build plan accurately documents the completed settings system (two-scope UI, schema-driven settings, keybinding recorder/manager) and correctly updates milestone statuses. Based on learnings: documentation is being kept in sync with codebase changes.

Also applies to: 1210-1210, 1247-1247

packages/renderer/src/components/settings/SettingControl.tsx (1)

9-66: LGTM on control type handling.

The switch-based rendering correctly handles all setting types with appropriate HTML controls, proper optional chaining for enumValues, and sensible defaults for nullish values.

packages/renderer/src/components/settings/KeybindingRecorder.tsx (1)

63-65: The recorder cannot intercept the global shortcut handler with stopPropagation().

KeybindingService already attached a capture-phase window listener. Using stopPropagation() in another capture listener cannot prevent that earlier same-target listener from firing first—handlers on the same element execute in registration order, regardless of stopPropagation(). The global shortcuts will still trigger while recording new keybindings.

Also applies to: 107-109

packages/shared/src/index.ts (1)

153-165: Good shared-contract expansion for settings/keybinding IPC.

These additions keep channel names and preload API types centralized, which lowers drift risk between main/renderer implementations.

Also applies to: 595-607

Comment thread packages/renderer/src/components/AppShell.tsx Outdated
Comment thread packages/renderer/src/components/panes/SettingsPane.tsx
Comment thread packages/renderer/src/components/settings/KeyboardShortcutsTable.tsx Outdated
Comment thread packages/renderer/src/components/settings/SettingsContent.tsx
Comment thread packages/renderer/src/components/settings/SettingsContent.tsx
Comment on lines +62 to +91
const setValue = useCallback(async (key: string, value: unknown) => {
if (scope === 'user') {
// Optimistic update
setUserSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
await window.api.setUserSetting(key, value)
} else {
setWorkspaceSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
await window.api.setWorkspaceSetting(key, value)
}
}, [scope])

const resetToDefault = useCallback(async (key: string) => {
if (scope === 'user') {
setUserSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
await window.api.setUserSetting(key, undefined)
} else {
setWorkspaceSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
await window.api.setWorkspaceSetting(key, undefined)
}
}, [scope])
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

Optimistic writes need failure reconciliation.

When setUserSetting / setWorkspaceSetting fails, local state stays mutated and diverges from persisted state.

Proposed fix
   const setValue = useCallback(async (key: string, value: unknown) => {
-    if (scope === 'user') {
-      // Optimistic update
-      setUserSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
-      await window.api.setUserSetting(key, value)
-    } else {
-      setWorkspaceSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
-      await window.api.setWorkspaceSetting(key, value)
-    }
+    if (scope === 'user') {
+      await window.api.setUserSetting(key, value)
+      setUserSettings((prev) => (prev ? { ...prev, [key]: value } : { [key]: value }))
+    } else {
+      await window.api.setWorkspaceSetting(key, value)
+      setWorkspaceSettings((prev) => (prev ? { ...prev, [key]: value } : { [key]: value }))
+    }
   }, [scope])

   const resetToDefault = useCallback(async (key: string) => {
     if (scope === 'user') {
-      setUserSettings((prev) => {
-        if (!prev) return prev
-        const next = { ...prev }
-        delete (next as Record<string, unknown>)[key]
-        return next
-      })
       await window.api.setUserSetting(key, undefined)
+      setUserSettings((prev) => {
+        if (!prev) return prev
+        const next = { ...prev }
+        delete (next as Record<string, unknown>)[key]
+        return next
+      })
     } else {
-      setWorkspaceSettings((prev) => {
-        if (!prev) return prev
-        const next = { ...prev }
-        delete (next as Record<string, unknown>)[key]
-        return next
-      })
       await window.api.setWorkspaceSetting(key, undefined)
+      setWorkspaceSettings((prev) => {
+        if (!prev) return prev
+        const next = { ...prev }
+        delete (next as Record<string, unknown>)[key]
+        return next
+      })
     }
   }, [scope])
📝 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
const setValue = useCallback(async (key: string, value: unknown) => {
if (scope === 'user') {
// Optimistic update
setUserSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
await window.api.setUserSetting(key, value)
} else {
setWorkspaceSettings((prev) => prev ? { ...prev, [key]: value } : { [key]: value })
await window.api.setWorkspaceSetting(key, value)
}
}, [scope])
const resetToDefault = useCallback(async (key: string) => {
if (scope === 'user') {
setUserSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
await window.api.setUserSetting(key, undefined)
} else {
setWorkspaceSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
await window.api.setWorkspaceSetting(key, undefined)
}
}, [scope])
const setValue = useCallback(async (key: string, value: unknown) => {
if (scope === 'user') {
await window.api.setUserSetting(key, value)
setUserSettings((prev) => (prev ? { ...prev, [key]: value } : { [key]: value }))
} else {
await window.api.setWorkspaceSetting(key, value)
setWorkspaceSettings((prev) => (prev ? { ...prev, [key]: value } : { [key]: value }))
}
}, [scope])
const resetToDefault = useCallback(async (key: string) => {
if (scope === 'user') {
await window.api.setUserSetting(key, undefined)
setUserSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
} else {
await window.api.setWorkspaceSetting(key, undefined)
setWorkspaceSettings((prev) => {
if (!prev) return prev
const next = { ...prev }
delete (next as Record<string, unknown>)[key]
return next
})
}
}, [scope])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/hooks/useSettings.ts` around lines 62 - 91, The
optimistic updates in setValue and resetToDefault mutate local state via
setUserSettings/setWorkspaceSettings before awaiting
window.api.setUserSetting/setWorkspaceSetting, but there is no reconciliation on
failure; wrap the API calls in try/catch and on error revert the optimistic
state to the previous value (capture prev inside the updater or store a snapshot
before updating), restore that snapshot in the catch block, and propagate or
surface the error; update both setValue and resetToDefault to follow this
pattern so local state stays consistent with persisted state.

Comment thread packages/renderer/src/lib/KeybindingService.ts
Comment on lines +135 to +157
export function loadKeybindings(
defaults: KeybindingRule[],
userOverrides: KeybindingRule[] = [],
): void {
ensureListener()

const resolved: ResolvedRule[] = [
...defaults.map((rule) => ({
rule,
parts: parse(rule.key),
source: 'default' as RuleSource,
suppressed: false,
})),
...userOverrides.map((rule) => ({
rule,
parts: parse(rule.key),
source: 'user' as RuleSource,
suppressed: false,
})),
]

processRemovals(resolved)
rules = resolved
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

Reloading overrides drops component-scoped bindings.

useKeybinding() appends live rules into the shared rules array, but loadKeybindings() replaces that array wholesale. Any mounted lifecycle-scoped shortcut disappears the next time defaults/overrides are reloaded.

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

In `@packages/renderer/src/lib/KeybindingService.ts` around lines 135 - 157,
loadKeybindings currently overwrites the shared rules array which drops
component-scoped/live rules added by useKeybinding; change loadKeybindings to
merge resolved rules with existing rules instead of replacing them: filter the
existing global rules (the variable rules) to keep any entries with the
component-scoped/source marker used by useKeybinding (e.g., source ===
'component' or a live flag), merge them with the newly computed resolved array
while deduplicating by rule.key (or rule id) so defaults/user overrides still
supersede, then run processRemovals and assign the merged array back to rules;
update logic around parse(rule.key) and processRemovals to operate on the merged
set to preserve lifecycle-scoped shortcuts.

Comment thread packages/renderer/src/lib/KeybindingService.ts
Comment on lines +126 to +127
export function getCategoriesWithSettings(): Set<string> {
return new Set(SETTINGS_DESCRIPTORS.map((d) => d.category))
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

Include non-descriptor pages in categoriesWithSettings.

This helper only returns descriptor-backed categories, but the settings UI also renders special sections without descriptors. Those pages will be styled as empty in the sidebar even though they have content.

Suggested fix
+const NON_DESCRIPTOR_CATEGORIES_WITH_CONTENT = [
+  'workbench.appearance',
+  'keyboardShortcuts',
+]
+
 /** Get all category IDs that have at least one setting descriptor */
 export function getCategoriesWithSettings(): Set<string> {
-  return new Set(SETTINGS_DESCRIPTORS.map((d) => d.category))
+  return new Set([
+    ...SETTINGS_DESCRIPTORS.map((d) => d.category),
+    ...NON_DESCRIPTOR_CATEGORIES_WITH_CONTENT,
+  ])
 }
📝 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
export function getCategoriesWithSettings(): Set<string> {
return new Set(SETTINGS_DESCRIPTORS.map((d) => d.category))
const NON_DESCRIPTOR_CATEGORIES_WITH_CONTENT = [
'workbench.appearance',
'keyboardShortcuts',
]
/** Get all category IDs that have at least one setting descriptor */
export function getCategoriesWithSettings(): Set<string> {
return new Set([
...SETTINGS_DESCRIPTORS.map((d) => d.category),
...NON_DESCRIPTOR_CATEGORIES_WITH_CONTENT,
])
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderer/src/lib/settingsSchema.ts` around lines 126 - 127,
getCategoriesWithSettings currently builds categories only from
SETTINGS_DESCRIPTORS, omitting special non-descriptor pages; update the function
to also include category values from your non-descriptor pages collection (e.g.,
SETTINGS_PAGES, SETTINGS_NON_DESCRIPTOR_PAGES, or whatever array/object holds
the special pages) by iterating that collection and adding each page.category
into the same Set before returning it so the sidebar treats those sections as
non-empty; keep getCategoriesWithSettings and SETTINGS_DESCRIPTORS as the
primary anchors when making the change.

Cheezeiii365 and others added 5 commits March 27, 2026 15:08
Replace hardcoded #e5534b with var(--text-error) so the conflict
message adapts to dark/light theme switches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recording state previously used only commandId, causing all rows sharing
the same command to show the recorder simultaneously. Now uses a composite
key (commandId-key-idx) so each row is independently recordable. Also
preserves the when clause in addOverride.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove redundant userDefaults layer in resolveSettings since appDefaults
already incorporates user defaults. Add error handling for keybinding
overrides loading to fall back to defaults on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
addOverride and removeOverride now fetch current state from the main
process instead of closing over the render-time overrides array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cmd and Ctrl were conflated into a single `meta` flag, making it
impossible to bind Ctrl+key shortcuts on macOS independently from
Cmd+key. Additionally, key aliases emitted by the recorder (Space,
Up, Down, etc.) did not match browser KeyboardEvent.key values,
so recorded shortcuts with those keys silently never fired.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Cheezeiii365 Cheezeiii365 merged commit 21f1f51 into main Mar 27, 2026
1 check was pending
@Cheezeiii365 Cheezeiii365 deleted the feat/settings-manager branch March 27, 2026 18:48
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