Skip to content

Persist workspace state across app restarts#6573

Closed
chirag-bruno wants to merge 7 commits intousebruno:mainfrom
chirag-bruno:feature/workspace-persistence
Closed

Persist workspace state across app restarts#6573
chirag-bruno wants to merge 7 commits intousebruno:mainfrom
chirag-bruno:feature/workspace-persistence

Conversation

@chirag-bruno
Copy link
Copy Markdown
Collaborator

@chirag-bruno chirag-bruno commented Dec 30, 2025

Description

This PR implements workspace state persistence using redux-persist, ensuring the active workspace selection is maintained across application restarts. The implementation includes enhanced loading state management with a dedicated LoadingScreen component, improved workspace activation logic that waits for collections to load before switching, and comprehensive test coverage for persistence behavior.

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • 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.

Summary by CodeRabbit

  • New Features

    • Loading screen shown during app startup.
    • Workspace state persistence added so previously opened workspaces and active selection are preserved across restarts.
  • Tests

    • Added end-to-end tests covering workspace persistence and supporting workspace fixtures.
  • Chores

    • Added redux-persist dependency and test helper to restart the app with persistent state.

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

…reen component

- Added redux-persist to manage state persistence across sessions.
- Implemented a LoadingScreen component to enhance user experience during data loading.
- Updated Main component to utilize PersistGate for handling loading state.
- Modified ReduxStore to include persisted reducers for workspaces.
- Updated package.json to include redux-persist as a dependency.
…ogic

- Updated Main component to ensure renderer is ready after persistence is complete.
- Improved LoadingScreen integration during the loading state of the PersistGate.
- Refactored ReduxStore to remove persisted reducer for workspaces.
- Added ensureActiveWorkspace function to manage active workspace switching after collections are loaded.
- Introduced a new test suite to verify that the active workspace selection persists across application restarts.
- Implemented helper functions for creating and selecting workspaces, as well as retrieving the current active workspace name.
- Utilized a singleton pattern for workspace setup to optimize test execution and resource management.
- Removed outdated comments in Main.js and actions.js to enhance code clarity.
- Streamlined the ensureActiveWorkspace function to focus on workspace activation logic without unnecessary comments.
- Ensured that workspace collections are loaded before switching to the active workspace.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 30, 2025

Walkthrough

Integrates redux-persist for workspace state persistence, adds a LoadingScreen, wraps app initialization with PersistGate to wait for rehydration, moves the renderer-ready IPC handshake to post-bootstrap, centralizes workspace activation logic, and adds Playwright fixtures/tests and workspace YAML fixtures.

Changes

Cohort / File(s) Summary
Package & deps
packages/bruno-app/package.json
Added dependency redux-persist (^6.0.0).
Redux store & persistence
packages/bruno-app/src/providers/ReduxStore/index.js
Introduced persistStore/persistReducer and storage; created rootReducer via combineReducers; wrapped workspaces reducer with persist config; ignored persist actions in serializable middleware; exported persistor.
App init & IPC
packages/bruno-app/src/pages/Main.js, packages/bruno-app/src/providers/App/useIpcEvents.js
Main now uses PersistGate to suspend rendering until rehydration; renderer:ready IPC invoke moved to post-bootstrap with one-time guard; removed initial renderer-ready call from useIpcEvents.
UI: Loading overlay
packages/bruno-app/src/components/LoadingScreen/index.js
New full-screen LoadingScreen component with centered loader and "Loading Bruno" text.
Workspace actions
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
Added ensureActiveWorkspace helper and replaced inline active-workspace selection in workspaceOpenedEvent to centralize post-load activation logic.
Playwright fixture & helpers
playwright/index.ts
Added restartAppWithPersistentState() fixture to restart Electron app reusing a persistent userDataPath; added template processing and copy-with-replacement helpers.
E2E fixtures & tests
tests/workspace/persistence/init-user-data/preferences.json, tests/workspace/persistence/fixtures/workspaces/.../workspace.yml, tests/workspace/persistence/workspace-persistence.spec.ts
Added preferences.json with templated workspace paths; added five workspace YAML fixtures (workspace-1workspace-5); added Playwright test workspace-persistence.spec.ts that runs 5 iterations validating workspace selection persists across restarts.

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main.js
    participant PG as PersistGate
    participant Persistor as Redux Persistor
    participant Store as Redux Store
    participant Providers as App Providers
    participant IPC as IPC Renderer

    rect rgba(200,220,240,0.9)
    Note over Main,PG: App start — wrap providers with PersistGate
    Main->>PG: Render PersistGate with persistor
    end

    rect rgba(240,220,200,0.9)
    Note over PG,Persistor: Rehydration phase
    PG->>Persistor: bootstrap()
    Persistor->>Store: rehydrate persisted slices (workspaces)
    Store-->>Persistor: rehydration complete
    end

    rect rgba(240,240,220,0.9)
    Note over Main,Providers: Loading UI during bootstrap
    Main->>Main: Render LoadingScreen until bootstrapped
    end

    rect rgba(200,240,200,0.9)
    Note over PG,IPC: Post-bootstrap initialization
    Persistor-->>PG: bootstrapped
    PG->>Providers: mount provider tree
    Providers->>Main: providers mounted
    Main->>IPC: invoke('renderer:ready')  %% one-time call
    IPC-->>Main: ack
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

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

Poem

✨ A gate that waits, a loader bright,
Redux remembers through the night,
Workspaces wake where they once stood,
IPC nods when state is good. 🚀

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Persist workspace state across app restarts' accurately summarizes the main objective of the changeset, which implements redux-persist integration and related infrastructure to maintain active workspace selection across application restarts.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

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

🧹 Nitpick comments (7)
packages/bruno-app/src/components/LoadingScreen/index.js (1)

3-17: Consider adding accessibility attributes.

The component looks clean. For screen reader users, consider adding role="status" and aria-live="polite" to announce the loading state, plus aria-hidden="true" on the decorative spinner.

🔎 Suggested accessibility enhancement
 const LoadingScreen = () => {
   return (
-    <div className="fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-zinc-900 transition-all duration-300">
+    <div className="fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-zinc-900 transition-all duration-300" role="status" aria-live="polite">
       <div className="flex flex-col items-center">
         <IconLoader2
           className="animate-spin h-12 w-12 mb-4 text-zinc-600 dark:text-zinc-400"
           strokeWidth={1.5}
+          aria-hidden="true"
         />
         <h3 className="text-xl font-medium text-zinc-900 dark:text-zinc-50">
           Loading Bruno
         </h3>
       </div>
     </div>
   );
 };
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)

402-408: Empty catch block swallows errors silently.

The catch block on lines 404-405 swallows errors from loadWorkspaceCollections. Consider at minimum logging the error for debugging.

🔎 Proposed fix
     try {
       await dispatch(loadWorkspaceCollections(workspaceUid));
     } catch (error) {
+      console.error('Failed to load workspace collections:', error);
     }
packages/bruno-app/src/pages/Main.js (1)

52-52: Minor: Redundant ipcRenderer check.

The window.ipcRenderer check here is technically redundant since the early return on line 34 would have already bailed out. Harmless, but could be simplified.

🔎 Simplified condition
-            if (bootstrapped && window.ipcRenderer && !rendererReadyCalled.current) {
+            if (bootstrapped && !rendererReadyCalled.current) {
tests/workspace/workspace-persistence.spec.ts (3)

62-62: Avoid waitForTimeout — prefer assertion-based waits.

Per coding guidelines, reduce usage of page.waitForTimeout(). The workspace switch completion might be better detected via a locator assertion.

🔎 Alternative approach
-    // Wait a bit for workspace to be created and switched to
-    await page.waitForTimeout(500);
-
     // Verify workspace name appears in title bar
     await expect(page.locator('.workspace-name')).toContainText(workspaceName);
+    // The assertion above already waits for the element, additional timeout likely unnecessary

200-203: Multiple hardcoded waits reduce test reliability.

Lines 202 and 222 use waitForTimeout(2000). Consider replacing with condition-based waits where possible — e.g., waiting for a specific element state or network idle.

-        await page.waitForTimeout(2000);
+        // If waiting for persistence, consider polling localStorage or waiting for a UI indicator

128-136: Non-null assertion on workspaceSetupPromise.

Lines 129 and 134 use ! non-null assertion. While the fixture dependency chain should guarantee initialization, consider adding a guard for safety.

🔎 Defensive check
   workspaceNames: async ({ userDataPath }, use) => {
-    const setup = await workspaceSetupPromise!;
+    if (!workspaceSetupPromise) {
+      throw new Error('workspaceSetupPromise not initialized');
+    }
+    const setup = await workspaceSetupPromise;
     await use(setup.workspaceNames);
   },
packages/bruno-app/src/providers/ReduxStore/index.js (1)

51-56: Add missing redux-persist actions to serializableCheck.

The ignored actions list is incomplete. Redux Toolkit and redux-persist documentation recommend ignoring all six persist actions to prevent serialization warnings.

🔎 Proposed fix
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware({
       serializableCheck: {
-        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE', 'persist/PURGE']
+        ignoredActions: [
+          'persist/PERSIST',
+          'persist/REHYDRATE',
+          'persist/PURGE',
+          'persist/FLUSH',
+          'persist/PAUSE',
+          'persist/REGISTER'
+        ]
       }
     }).concat(middleware)
📜 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 ab04850 and d14d331.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • packages/bruno-app/package.json
  • packages/bruno-app/src/components/LoadingScreen/index.js
  • packages/bruno-app/src/pages/Main.js
  • packages/bruno-app/src/providers/App/useIpcEvents.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • tests/workspace/workspace-persistence.spec.ts
💤 Files with no reviewable changes (1)
  • packages/bruno-app/src/providers/App/useIpcEvents.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
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
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/LoadingScreen/index.js
  • tests/workspace/workspace-persistence.spec.ts
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • packages/bruno-app/src/pages/Main.js
tests/**/**.*

⚙️ CodeRabbit configuration file

tests/**/**.*: Review the following e2e test code written using the Playwright test library. Ensure that:

  • Follow best practices for Playwright code and e2e automation

  • Try to reduce usage of page.waitForTimeout(); in code unless absolutely necessary and the locator cannot be found using existing expect() playwright calls

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

  • Promote the use of test.step as much as possible so the generated reports are easier to read

  • Ensure that the fixtures like the collections are nested inside the fixtures folder

    Fixture Example*: Here's an example of possible fixture and test pair

    .
    ├── fixtures
    │   └── collection
    │       ├── base.bru
    │       ├── bruno.json
    │       ├── collection.bru
    │       ├── ws-test-request-with-headers.bru
    │       ├── ws-test-request-with-subproto.bru
    │       └── ws-test-request.bru
    ├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
    ├── headers.spec.ts
    ├── persistence.spec.ts
    ├── variable-interpolation
    │   ├── fixtures
    │   │   └── collection
    │   │       ├── environments
    │   │       ├── bruno.json
    │   │       └── ws-interpolation-test.bru
    │   ├── init-user-data
    │   └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
    └── subproto.spec.ts
    

Files:

  • tests/workspace/workspace-persistence.spec.ts
🧠 Learnings (3)
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/components/LoadingScreen/index.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • packages/bruno-app/src/pages/Main.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/workspace/workspace-persistence.spec.ts
📚 Learning: 2025-12-16T07:16:23.647Z
Learnt from: sanish-bruno
Repo: usebruno/bruno PR: 6090
File: tests/scripting/hooks/init-user-data/ui-state-snapshot.json:1-8
Timestamp: 2025-12-16T07:16:23.647Z
Learning: For e2e tests in the bruno repository: Collections that are shared between CLI and UI tests (comprehensive test suites testing core functionality) should be placed in `packages/bruno-tests/` to avoid duplication. The `tests/**/fixtures/collection` pattern should be used for test-specific collections that test particular UI behaviors or are specific to a single test file.

Applied to files:

  • tests/workspace/workspace-persistence.spec.ts
🧬 Code graph analysis (1)
tests/workspace/workspace-persistence.spec.ts (1)
packages/bruno-electron/src/utils/workspace-config.js (1)
  • workspaceName (217-217)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (6)
packages/bruno-app/package.json (1)

87-87: LGTM!

The redux-persist dependency addition is appropriate for the workspace persistence feature. Version ^6.0.0 is stable and well-maintained.

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

28-34: LGTM on persistence configuration.

The whitelist approach for activeWorkspaceUid is correct — only persisting the minimal state needed for workspace restoration.

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

358-392: Solid helper for workspace activation.

The logic correctly waits for all workspaces with pathname to finish loading before selecting the active one. The fallback chain (current active → default → first) is sensible.

One note: switchWorkspace is async but called without await. This is likely intentional (fire-and-forget), but worth confirming this won't cause race conditions in edge cases.

packages/bruno-app/src/pages/Main.js (1)

50-77: LGTM on PersistGate integration.

The render prop pattern with useRef to ensure single IPC invocation is correct. The renderer:ready signal is properly gated until persistence bootstrapping completes. Clean implementation.

tests/workspace/workspace-persistence.spec.ts (2)

69-74: Singleton pattern note.

The singleton workspaceSetupPromise is a smart optimization to avoid recreating workspaces for each test. Be aware this introduces test coupling — if the shared state gets corrupted, all subsequent tests fail.


187-234: Good test coverage for persistence.

The test properly validates that workspace selection survives multiple app restarts. The iteration approach with random workspace selection provides decent coverage.

…leware configuration

- Added role and aria-live attributes to the LoadingScreen for better accessibility.
- Removed unnecessary window.ipcRenderer check in Main component to simplify logic.
- Expanded ignored actions in Redux middleware to include additional persistence actions.
- Added error logging for failed workspace collection loading in actions.js.
- Improved test stability by ensuring UI elements are ready before interaction in workspace persistence tests.
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: 0

🧹 Nitpick comments (5)
tests/workspace/workspace-persistence.spec.ts (5)

75-79: Consider removing the waitForTimeout here.

The waitForTimeout(500) on line 76 precedes the assertion on line 79. Since expect(page.locator('.workspace-name')).toContainText(workspaceName) already has built-in auto-waiting, the explicit timeout may be unnecessary.

As per coding guidelines, reduce usage of waitForTimeout unless absolutely necessary and the locator cannot be found using existing expect() calls.

🔎 Suggested refactor
-    // Wait a bit for workspace to be created and switched to
-    await page.waitForTimeout(500);
-
     // Verify workspace name appears in title bar
     await expect(page.locator('.workspace-name')).toContainText(workspaceName);

142-149: Non-null assertions on workspaceSetupPromise assume fixture ordering.

Lines 143 and 148 use workspaceSetupPromise! which assumes userDataPath fixture always runs first to initialize the promise. If fixture dependencies change or tests are reorganized, this could cause runtime errors.

The current setup works because userDataPath is a dependency in the fixture chain, but adding a comment explaining this dependency would improve maintainability.


214-217: Unnecessary waitForTimeout after getting workspace name.

Line 216 adds a 2000ms delay after getActiveWorkspaceName, which already awaits the element's visibility. This timeout appears superfluous and slows down test execution.

As per coding guidelines, avoid waitForTimeout unless absolutely necessary.

🔎 Suggested refactor
         await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
         const activeWorkspaceName = await getActiveWorkspaceName(page);
-        await page.waitForTimeout(2000);
         expect(activeWorkspaceName.toLowerCase()).toContain(previousWorkspaceName!.toLowerCase());

204-205: Initial previousWorkspaceName assumes 'Workspace5' is the last created workspace.

The hardcoded 'Workspace5' relies on the singleton setup creating workspaces in order and ending on Workspace5. If the setup logic changes, this test could fail unexpectedly. Consider adding a comment explaining this assumption or deriving it from the fixture.


234-245: Consider documenting why these delays are necessary for persistence.

The 2000ms delays (lines 236 and 244) appear to be necessary for redux-persist to flush state to storage before app close. A brief comment explaining this would help future maintainers understand these aren't arbitrary waits.

         // Wait before closing to ensure state is saved
+        // redux-persist needs time to flush state to localStorage
         await new Promise((resolve) => setTimeout(resolve, 2000));
📜 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 d14d331 and 5b0e330.

📒 Files selected for processing (5)
  • packages/bruno-app/src/components/LoadingScreen/index.js
  • packages/bruno-app/src/pages/Main.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • tests/workspace/workspace-persistence.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/components/LoadingScreen/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
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
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/pages/Main.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
  • tests/workspace/workspace-persistence.spec.ts
tests/**/**.*

⚙️ CodeRabbit configuration file

tests/**/**.*: Review the following e2e test code written using the Playwright test library. Ensure that:

  • Follow best practices for Playwright code and e2e automation

  • Try to reduce usage of page.waitForTimeout(); in code unless absolutely necessary and the locator cannot be found using existing expect() playwright calls

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

  • Promote the use of test.step as much as possible so the generated reports are easier to read

  • Ensure that the fixtures like the collections are nested inside the fixtures folder

    Fixture Example*: Here's an example of possible fixture and test pair

    .
    ├── fixtures
    │   └── collection
    │       ├── base.bru
    │       ├── bruno.json
    │       ├── collection.bru
    │       ├── ws-test-request-with-headers.bru
    │       ├── ws-test-request-with-subproto.bru
    │       └── ws-test-request.bru
    ├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
    ├── headers.spec.ts
    ├── persistence.spec.ts
    ├── variable-interpolation
    │   ├── fixtures
    │   │   └── collection
    │   │       ├── environments
    │   │       ├── bruno.json
    │   │       └── ws-interpolation-test.bru
    │   ├── init-user-data
    │   └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
    └── subproto.spec.ts
    

Files:

  • tests/workspace/workspace-persistence.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/pages/Main.js
  • packages/bruno-app/src/providers/ReduxStore/index.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/workspace/workspace-persistence.spec.ts
🧬 Code graph analysis (2)
packages/bruno-app/src/pages/Main.js (3)
packages/bruno-app/src/providers/ReduxStore/index.js (2)
  • persistor (66-66)
  • persistor (66-66)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)
  • window (19-19)
packages/bruno-app/src/components/LoadingScreen/index.js (1)
  • LoadingScreen (3-18)
tests/workspace/workspace-persistence.spec.ts (3)
packages/bruno-electron/src/utils/workspace-config.js (1)
  • workspaceName (217-217)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
  • workspaceNameInput (22-22)
tests/workspace/default-workspace/default-workspace.spec.ts (1)
  • test (5-230)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
🔇 Additional comments (11)
tests/workspace/workspace-persistence.spec.ts (4)

1-4: LGTM!

Imports are correctly structured with Playwright types and Node.js fs/path modules.


83-88: Module-level singleton may cause issues with parallel test execution.

The workspaceSetupPromise singleton at module scope works for serial execution but could cause race conditions or unexpected state sharing if Playwright runs tests in parallel across workers. Each worker would share this module state.

Consider documenting that this test file must run serially or use Playwright's test.describe.serial if parallel execution becomes an issue.


171-190: LGTM!

The selectWorkspace helper follows Playwright best practices: uses test.step for reporting, proper locator variables, and waitFor states instead of arbitrary timeouts.


195-199: LGTM!

Clean helper function with appropriate visibility assertion.

packages/bruno-app/src/pages/Main.js (3)

1-3: LGTM!

Imports are correctly structured with useRef for the one-time guard and PersistGate for redux-persist integration.


31-33: LGTM!

The useRef guard is the correct pattern to ensure renderer:ready is called exactly once, preventing duplicate IPC calls on re-renders.


50-77: LGTM!

The PersistGate implementation is well-structured:

  • Uses render prop pattern correctly for conditional rendering
  • One-time IPC guard prevents duplicate renderer:ready calls
  • Error handling with .catch() logs failures appropriately
  • Clean separation between loading state and bootstrapped state
packages/bruno-app/src/providers/ReduxStore/index.js (4)

1-3: LGTM!

Imports are correctly structured for redux-persist integration.


28-34: LGTM!

The persist configuration is appropriately scoped:

  • Uses whitelist to persist only activeWorkspaceUid, avoiding bloating storage with unnecessary state
  • Key is namespaced correctly as 'workspaces'

36-47: LGTM!

The rootReducer correctly combines all slices, with persistedWorkspacesReducer wired for the workspaces slice.


49-66: LGTM!

Store configuration is correct:

  • All redux-persist actions (PERSIST, REHYDRATE, PURGE, FLUSH, PAUSE, REGISTER) are ignored in serializable checks
  • Custom middleware is properly concatenated
  • persistor is exported for use with PersistGate

…tence tests

- Introduced a new function, restartAppWithPersistentState, to manage app restarts while preserving user data and workspace state.
- Implemented helper functions for workspace selection and retrieval of the current active workspace name.
- Created a new test suite to verify that active workspace selections persist across application restarts, enhancing test coverage for workspace management.
- Added fixture files for workspace configurations to support the new tests.
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: 0

🧹 Nitpick comments (2)
playwright/index.ts (1)

216-293: Implementation looks solid, with a few minor refinements.

The fixture correctly maintains persistent state across restarts by reusing the same userDataPath. Template replacement logic is well-structured with clear helper functions.

Consider these optional improvements:

  1. Line 262: fs.cpSync is synchronous. Since the rest of the file uses async patterns, consider fs.promises.cp(src, dest, { recursive: true }) for consistency.

  2. Lines 247-253: The silent catch on line 252 may hide legitimate errors beyond binary files. Consider logging skipped files or being more specific about which errors to ignore:

    -            } catch (err) {
    -              // Skip files that can't be read as text (e.g., binary files)
    -            }
    +            } catch (err) {
    +              // Skip binary files that can't be read as UTF-8
    +              if (err instanceof Error && !err.message.includes('invalid UTF-8')) {
    +                console.warn(`Failed to process ${fullPath}:`, err.message);
    +              }
    +            }
  3. Line 244: The textExtensions list is hardcoded. If you anticipate needing to process other text-based config formats, consider making it a parameter or constant at the module level.

tests/workspace/persistence/workspace-persistence.spec.ts (1)

44-90: Replace arbitrary timeouts with deterministic waiting mechanisms.

The test logic is sound, but using setTimeout (lines 77, 85) introduces non-deterministic waits that can make tests flaky. Per coding guidelines, avoid page.waitForTimeout() unless absolutely necessary.

Recommended alternatives for timeouts

Line 77: Instead of waiting 2 seconds for state to save, consider:

  • Listening for a persistence-complete event from the app
  • Checking for a specific DOM state or attribute that indicates save completion
  • Using IPC to confirm state was written to disk

Example:

-        // Wait before closing to ensure state is saved
-        await new Promise((resolve) => setTimeout(resolve, 2000));
+        // Wait for workspace state to persist
+        await expect(page.locator('[data-persistence-state="synced"]')).toBeVisible({ timeout: 5000 });

Line 85: For app close confirmation, consider:

  • Relying on the fixture cleanup (which already closes apps properly)
  • Or checking that the process has actually terminated
-        // Wait a bit for the app to fully close and cleanup
-        if (iteration < 5) {
-          await new Promise((resolve) => setTimeout(resolve, 2000));
-        }
+        // Fixture cleanup handles app closure properly
+        // No additional wait needed

If you must keep the timeouts for now, document why they're necessary with specific reasoning.

📜 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 5b0e330 and d671459.

📒 Files selected for processing (8)
  • playwright/index.ts
  • tests/workspace/persistence/fixtures/workspaces/workspace-1/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-2/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-3/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-4/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-5/workspace.yml
  • tests/workspace/persistence/init-user-data/preferences.json
  • tests/workspace/persistence/workspace-persistence.spec.ts
✅ Files skipped from review due to trivial changes (5)
  • tests/workspace/persistence/fixtures/workspaces/workspace-5/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-4/workspace.yml
  • tests/workspace/persistence/init-user-data/preferences.json
  • tests/workspace/persistence/fixtures/workspaces/workspace-1/workspace.yml
  • tests/workspace/persistence/fixtures/workspaces/workspace-3/workspace.yml
🧰 Additional context used
📓 Path-based instructions (2)
tests/**/**.*

⚙️ CodeRabbit configuration file

tests/**/**.*: Review the following e2e test code written using the Playwright test library. Ensure that:

  • Follow best practices for Playwright code and e2e automation

  • Try to reduce usage of page.waitForTimeout(); in code unless absolutely necessary and the locator cannot be found using existing expect() playwright calls

  • Avoid using page.pause() in code

  • Use locator variables for locators

  • Avoid using test.only

  • Use multiple assertions

  • Promote the use of test.step as much as possible so the generated reports are easier to read

  • Ensure that the fixtures like the collections are nested inside the fixtures folder

    Fixture Example*: Here's an example of possible fixture and test pair

    .
    ├── fixtures
    │   └── collection
    │       ├── base.bru
    │       ├── bruno.json
    │       ├── collection.bru
    │       ├── ws-test-request-with-headers.bru
    │       ├── ws-test-request-with-subproto.bru
    │       └── ws-test-request.bru
    ├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
    ├── headers.spec.ts
    ├── persistence.spec.ts
    ├── variable-interpolation
    │   ├── fixtures
    │   │   └── collection
    │   │       ├── environments
    │   │       ├── bruno.json
    │   │       └── ws-interpolation-test.bru
    │   ├── init-user-data
    │   └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
    └── subproto.spec.ts
    

Files:

  • tests/workspace/persistence/fixtures/workspaces/workspace-2/workspace.yml
  • tests/workspace/persistence/workspace-persistence.spec.ts
**/*.{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. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
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
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • playwright/index.ts
  • tests/workspace/persistence/workspace-persistence.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • tests/workspace/persistence/workspace-persistence.spec.ts
🧬 Code graph analysis (1)
tests/workspace/persistence/workspace-persistence.spec.ts (1)
playwright/index.ts (1)
  • test (28-327)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (3)
tests/workspace/persistence/fixtures/workspaces/workspace-2/workspace.yml (1)

1-11: LGTM!

The fixture structure is valid and properly nested in the fixtures folder, following the documented pattern for test fixtures.

tests/workspace/persistence/workspace-persistence.spec.ts (2)

8-27: Helper function is well-structured.

Good use of test.step for better test reporting, proper use of locators, and multiple assertions.


32-36: LGTM!

Helper function correctly waits for visibility before retrieving text content.

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

🧹 Nitpick comments (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)

358-392: LGTM! Solid centralization of workspace selection logic.

The ensureActiveWorkspace helper correctly waits for all workspace collections to load before determining the active workspace, preventing race conditions during app initialization. The fallback logic (active → default → first workspace) handles edge cases like deleted or invalid workspaces gracefully.

The synchronous dispatch of setActiveWorkspace on line 233 in switchWorkspace ensures that multiple rapid calls to ensureActiveWorkspace won't trigger duplicate switches, which is a nice touch.

Optional: Add clarifying comment about race condition prevention

Consider adding a brief comment explaining that switchWorkspace updates activeWorkspaceUid synchronously, preventing duplicate switches:

 const ensureActiveWorkspace = (dispatch, getState) => {
   const state = getState();
   const { workspaces, activeWorkspaceUid } = state.workspaces;
 
+  // Note: switchWorkspace dispatches setActiveWorkspace synchronously,
+  // so multiple calls to ensureActiveWorkspace won't cause duplicate switches
   if (targetWorkspaceUid && targetWorkspaceUid !== activeWorkspaceUid) {
     dispatch(switchWorkspace(targetWorkspaceUid));
   }
 };

This would help future maintainers understand the design intent.

📜 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 d671459 and 2bbe59b.

📒 Files selected for processing (1)
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.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. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
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
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
🧠 Learnings (1)
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
🧬 Code graph analysis (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/index.js (5)
  • workspace (20-20)
  • workspace (42-42)
  • workspace (50-50)
  • workspace (63-63)
  • workspace (75-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
🔇 Additional comments (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1)

402-409: LGTM! Good error handling and delegation.

The addition of console.error on line 405 improves debugging visibility for workspace loading failures. Calling ensureActiveWorkspace after the try-catch ensures the app always attempts to establish a valid active workspace, even when collection loading fails—this aligns well with the PR's goal of robust workspace persistence across restarts.

@slootjes
Copy link
Copy Markdown

@chirag-bruno Why was this closed, this seems like something that would be very useful as constantly needing to open the same folders/requests when switching workspaces is quite annoying. It looks like this would solve that and it was mostly done.

@chirag-bruno
Copy link
Copy Markdown
Collaborator Author

Hey @slootjes, after reviewing the implementation, we realized this feature would be more valuable if it worked across Workspaces → Collections → Tabs.
Specifically, instead of only restoring the last workspace, we want to support restoring the last workspace, last collection, and last active tabs across app restarts. The current PR only handles workspace restoration.
To make this more robust, we’ve taken some time to design a schema that can support multiple tab types. You can track the progress here: #7494

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.

2 participants