Skip to content

Conversation

@jeffbowen
Copy link

@jeffbowen jeffbowen commented Dec 2, 2025

Description

  • Implemented saving of left sidebar width to user preferences in the Redux store.
  • Added functionality to restore sidebar width from preferences on application load.
  • Updated preferences schema to include leftSidebarWidth with validation.
  • Set default leftSidebarWidth in the preferences to 222.
  • FWIW, Copilot came up with this implementation. I've tested it on my machine and it works.

Fixes #635

Contribution Checklist:

  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features
    • Sidebar width is now persisted when you finish resizing it and automatically restored on app launch.
    • App initializes renderer with a stored UI snapshot so sidebar settings are applied reliably at startup.
    • Added a sensible default width and enforced acceptable width range to prevent extreme layouts.

✏️ Tip: You can customize this high-level summary in your review settings.

- Implemented saving of left sidebar width to user preferences in the Redux store.
- Added functionality to restore sidebar width from preferences on application load.
- Updated preferences schema to include leftSidebarWidth with validation.
- Set default leftSidebarWidth in the preferences to 222.
@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Walkthrough

Persist sidebar width: dragging the app sidebar now persists the width to the persisted UI snapshot via a new Redux thunk and IPC; startup/hydration flows were extended to read and restore the saved sidebar width into app state.

Changes

Cohort / File(s) Summary
Sidebar resize handler
packages/bruno-app/src/components/Sidebar/index.js
Import saveSidebarWidth and dispatch it in handleMouseUp when dragging ends and width changed, alongside existing updates to leftSidebarWidth and isDragging.
Redux persistence thunk
packages/bruno-app/src/providers/ReduxStore/slices/app.js
Added exported thunk saveSidebarWidth(leftSidebarWidth) which reads state and invokes ipcRenderer to persist a UI state snapshot with { type: 'SIDEBAR', data: { width } }.
Preferences defaults & schema
packages/bruno-electron/src/store/preferences.js
Added layout.leftSidebarWidth default 222 and validation entry (number between 221–600, nullable) to defaults and Yup schema.
UI snapshot store
packages/bruno-electron/src/store/ui-state-snapshot.js
Added getSidebar, saveSidebar, getSidebarWidth, updateSidebarWidth, and extended update({ type, data }) to handle SIDEBAR.
IPC preferences & hydration
packages/bruno-electron/src/ipc/preferences.js
On renderer readiness, instantiate UiStateSnapshot, read sidebar data and emit main:hydrate-app-with-ui-state-snapshot with { sidebar: ... }; added imports and try/catch around hydration.
Collection hydration adjustments
packages/bruno-electron/src/app/collection-watcher.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
collection-watcher now emits a wrapped UI snapshot { collections: ... }; collection hydration reads uiStateSnapshotData and dispatches updateLeftSidebarWidth when uiStateSnapshotData.sidebar.width is present.
Minor formatting
packages/bruno-app/src/providers/App/useIpcEvents.js
Trailing comma added to import list (no behavioral change).

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Sidebar as Sidebar Component
    participant Redux as Renderer Redux (thunks/slices)
    participant IPC as Renderer<->Main IPC (ipcRenderer/ipcMain)
    participant Snapshot as Electron UiStateSnapshot Store

    User->>Sidebar: drag resize -> mouseup
    Sidebar->>Redux: dispatch saveSidebarWidth(asideWidth)
    Redux->>IPC: ipcRenderer.invoke('renderer:save-ui-state-snapshot', { type: "SIDEBAR", data: { width } })
    IPC->>Snapshot: updateSidebarWidth(width) / persist
    Snapshot-->>IPC: persist success
    IPC-->>Redux: resolve thunk

    Note over Snapshot,Redux: On app start / renderer readiness
    Snapshot->>IPC: main:hydrate-app-with-ui-state-snapshot({ sidebar: { width }, collections: { ... } })
    IPC->>Redux: hydrate actions (updateLeftSidebarWidth, collection hydrate)
    Redux->>Sidebar: state updated -> re-render with persisted width
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to check:
    • IPC channel names and error handling in the new thunk and main IPC handlers.
    • That preference merging preserves other layout keys and follows existing save patterns.
    • Validation bounds (221–600) vs UI constraints and default (222).
    • Consistency of the new main:hydrate-app-with-ui-state-snapshot payload shape across listeners.

Suggested labels

size/XXL

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

A handle nudged, a width set right,
Saved in snapshots through the night.
Restart, return — the pane stays true,
No tiny tweaks, the view renews. ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding sidebar width persistence and restoration across application restarts.
Linked Issues check ✅ Passed All objectives from issue #635 are met: sidebar width is persisted when resized and restored on application start.
Out of Scope Changes check ✅ Passed All changes directly support sidebar width persistence and restoration; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f96be5 and 3f0e311.

📒 Files selected for processing (3)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2 hunks)
  • packages/bruno-electron/src/ipc/preferences.js (2 hunks)
  • packages/bruno-electron/src/store/ui-state-snapshot.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-electron/src/store/ui-state-snapshot.js
  • packages/bruno-electron/src/ipc/preferences.js

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

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

🧹 Nitpick comments (4)
packages/bruno-electron/src/store/preferences.js (1)

41-44: Layout default and validation look consistent; consider centralizing bounds

The new layout.leftSidebarWidth default (222) and schema range (221–600) align with the UI’s min/max and the app slice initial state. This is good for keeping persisted values sane.

To avoid future drift, it might be worth documenting or centralizing these bounds so any change to sidebar limits is updated in both the Electron schema and the React/Redux side together.

Also applies to: 88-91

packages/bruno-app/src/providers/ReduxStore/slices/app.js (1)

144-163: Persistence flow is correct; you can reuse savePreferences to avoid duplication

The thunk correctly patches layout.leftSidebarWidth, persists via IPC, and syncs state.app.preferences, so behavior is sound.

Since the logic closely matches savePreferences, you could simplify to:

export const saveSidebarWidth = (leftSidebarWidth) => (dispatch, getState) => {
  const currentPreferences = getState().app.preferences;
  const updatedPreferences = {
    ...currentPreferences,
    layout: {
      ...currentPreferences.layout,
      leftSidebarWidth
    }
  };

  return dispatch(savePreferences(updatedPreferences));
};

This keeps all IPC + validation handling in savePreferences and reduces maintenance surface.

packages/bruno-app/src/providers/App/useIpcEvents.js (1)

6-8: Startup restoration works; consider a stricter check than truthiness

Importing updateLeftSidebarWidth and restoring it on main:load-preferences correctly wires persisted layout into the UI state.

To make this a bit more robust against future changes (e.g., if 0 or null becomes meaningful), you might prefer an explicit check over a truthy test:

if (val?.layout && val.layout.leftSidebarWidth != null) {
  dispatch(updateLeftSidebarWidth({
    leftSidebarWidth: val.layout.leftSidebarWidth
  }));
}

This still ignores null/undefined but doesn’t silently couple behavior to “truthy”.

Also applies to: 164-171

packages/bruno-app/src/components/Sidebar/index.js (1)

7-7: Drag-end persistence is wired correctly; optional optimization for redundant saves

Importing saveSidebarWidth and dispatching it on handleMouseUp after updateLeftSidebarWidth cleanly persists the final, clamped width and matches the intended UX.

If you want to reduce unnecessary preference writes, you could guard the save:

if (asideWidth !== leftSidebarWidth) {
  dispatch(saveSidebarWidth(asideWidth));
}

Not required for correctness, but it avoids IPC + disk I/O when the width hasn’t actually changed.

Also applies to: 35-51

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d56e4f6 and f4b6cc0.

📒 Files selected for processing (4)
  • packages/bruno-app/src/components/Sidebar/index.js (2 hunks)
  • packages/bruno-app/src/providers/App/useIpcEvents.js (2 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js (1 hunks)
  • packages/bruno-electron/src/store/preferences.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses. Keep 'em tight
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Avoid single line abstractions where all that's being done is increasing the call stack with one additional function
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/providers/ReduxStore/slices/app.js
  • packages/bruno-app/src/components/Sidebar/index.js
  • packages/bruno-electron/src/store/preferences.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/Sidebar/index.js (1)
packages/bruno-app/src/providers/ReduxStore/slices/app.js (2)
  • saveSidebarWidth (144-163)
  • saveSidebarWidth (144-163)

- reuse savePreferences to avoid duplication
- consider a stricter check than truthiness on main:load-preferences
- only saveSidebarWidth if it's changed

TODO: Consider centralizing sidebar width/bounds.

> To avoid future drift, it might be worth documenting or centralizing these bounds so any change to sidebar limits is updated in both the Electron schema and the React/Redux side together.

This is outside the scope of this change as other values also do not use constants. They should match `MIN_LEFT_SIDEBAR_WIDTH` / `MAX_LEFT_SIDEBAR_WIDTH` defined in `packages/bruno-app/src/components/Sidebar/index.js` though.
@helloanoop
Copy link
Contributor

Hey @jeffbowen

Thanks for the PR. We'd want to save the state of the sidebar width in ui-snapshot-store

Can you update your PR to add it there so that the final store structure looks like this.

{
  collections: //existing
  sidebar: {
    width: <sidebarWidth>
  }
}

Copy link

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

🧹 Nitpick comments (2)
packages/bruno-electron/src/ipc/preferences.js (1)

31-40: Consider reusing UiStateSnapshotStore instance and aligning error log with existing style

The sidebar hydration block is functionally fine, but two minor improvements:

  • You’re instantiating new UiStateSnapshot() inside the renderer:ready handler every time. Given this is a simple electron-store wrapper it’s not a big deal, but you could instantiate it once at module scope and reuse it to avoid repeated construction.

  • The error message 'Error loading UI state snapshot' is good; consider including the stack or using a consistent prefix format (similar to the global-environments catch above) if you want logs to be easy to grep.

No change required if you’re happy with the current pattern; just something to keep in mind.

packages/bruno-electron/src/app/collection-watcher.js (1)

656-665: Tighten “no collections snapshot” handling when hydrating UI state

This wiring looks consistent with the renderer’s hydrateCollectionWithUiStateSnapshot consumer, but there are two small polish points:

  • When collectionSnapshotState is falsy, you currently send { collections: {} }. On the renderer side, {} is truthy and triggers hydration logic with no pathname. It’s harmless but slightly wasteful. You could instead skip sending in that case:
-  const uiSnapshotState = { collections: collectionSnapshotState || {} };
-  win.webContents.send('main:hydrate-app-with-ui-state-snapshot', uiSnapshotState);
+  if (collectionSnapshotState) {
+    const uiSnapshotState = { collections: collectionSnapshotState };
+    win.webContents.send('main:hydrate-app-with-ui-state-snapshot', uiSnapshotState);
+  }
  • As with preferences.js, you might eventually want a shared UiStateSnapshotStore instance instead of instantiating per call, though it’s not performance‑critical here.

Functionally this is fine; above is optional cleanup.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d02ab0 and 0f96be5.

📒 Files selected for processing (6)
  • packages/bruno-app/src/providers/App/useIpcEvents.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js (2 hunks)
  • packages/bruno-electron/src/app/collection-watcher.js (1 hunks)
  • packages/bruno-electron/src/ipc/preferences.js (2 hunks)
  • packages/bruno-electron/src/store/ui-state-snapshot.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/providers/ReduxStore/slices/app.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single parameters
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses. Keep 'em tight
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Avoid single line abstractions where all that's being done is increasing the call stack with one additional function
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-electron/src/store/ui-state-snapshot.js
  • packages/bruno-electron/src/app/collection-watcher.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-electron/src/ipc/preferences.js
🧬 Code graph analysis (1)
packages/bruno-electron/src/ipc/preferences.js (1)
packages/bruno-electron/src/app/collection-watcher.js (14)
  • UiStateSnapshot (27-27)
  • require (5-11)
  • require (12-18)
  • require (19-19)
  • require (21-21)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (25-25)
  • require (28-28)
  • require (29-29)
  • require (30-30)
  • UiStateSnapshotStore (660-660)
  • uiSnapshotState (663-663)
🪛 Biome (2.1.2)
packages/bruno-electron/src/store/ui-state-snapshot.js

[error] 68-68: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 72-72: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

@jeffbowen
Copy link
Author

@helloanoop Gotcha. I moved to using ui-state-snapshot to store the sidebar width.

To keep this pull request limited to just one feature, I avoided messing with existing collections stuff and tried to mirror the way collections ui-state-snapshot already works. But it seems like there could be a separate PR down the road to unify them a bit.

I was unable to get the sidebar width to update from onWatcherSetupComplete in packages/bruno-electron/src/app/collection-watcher.js and had to do it separately in packages/bruno-electron/src/ipc/preferences.js. Let me know if that is a fine place for you.

Tested and working.

@jeffbowen
Copy link
Author

Looks like #6264 introduced some merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[User Experience] Persist sidebar width to preserve application restarts

2 participants