Skip to content

fix: persist workspace panel visibility across restarts#69

Merged
gnoviawan merged 3 commits intodevfrom
fix/persist-workspace-panel-visibility-40
Mar 10, 2026
Merged

fix: persist workspace panel visibility across restarts#69
gnoviawan merged 3 commits intodevfrom
fix/persist-workspace-panel-visibility-40

Conversation

@gnoviawan
Copy link
Owner

@gnoviawan gnoviawan commented Mar 9, 2026

Summary

  • persist sidebar and file explorer visibility in global app settings so panel state survives restarts and project switches
  • preserve Ctrl+B for the file explorer and add a dedicated configurable sidebar shortcut
  • harden panel persistence with serialized immediate writes, rollback on failure, and close-flow synchronization
  • remove project-scoped explorer visibility persistence and add regression coverage for click, keyboard, failure, and legacy restore paths

Test plan

  • src/renderer/hooks/use-app-settings.test.ts
  • src/renderer/components/TitleBar.test.tsx
  • src/renderer/hooks/use-editor-persistence.test.ts
  • src/renderer/layouts/WorkspaceLayout.test.tsx
  • src/renderer/stores/keyboard-shortcuts-store.test.ts
  • src/renderer/stores/sidebar-store.test.ts
  • src/renderer/stores/app-settings-store.test.ts
  • src/renderer/stores/file-explorer-store.test.ts
  • bun run typecheck
  • bunx lint-staged

Closes #40.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Panel visibility (sidebar and file explorer) persisted across sessions.
    • Added keyboard shortcut Ctrl+Shift+B to toggle the sidebar.
  • Improvements

    • Close flow now waits for pending settings to finish before exiting.
    • Visibility changes use unified update calls with toast error notifications on failure.
  • Tests

    • Extensive new and updated tests covering panel persistence, shortcuts, and close-flow behavior.

Move sidebar and file explorer visibility into global app settings so panel state survives restarts and project switches. Serialize immediate writes, add rollback on failure, and wait on close so the latest toggle is not lost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47548ef9-4f1a-42b2-b419-5faceb6a360e

📥 Commits

Reviewing files that changed from the base of the PR and between 173ce0a and 22c77cc.

📒 Files selected for processing (10)
  • src/renderer/hooks/use-app-settings.test.ts
  • src/renderer/hooks/use-app-settings.ts
  • src/renderer/hooks/use-editor-persistence.test.ts
  • src/renderer/hooks/use-editor-persistence.ts
  • src/renderer/layouts/WorkspaceLayout.close-persistence.test.tsx
  • src/renderer/layouts/WorkspaceLayout.test.tsx
  • src/renderer/layouts/WorkspaceLayout.tsx
  • src/renderer/lib/tauri-keyboard-api.ts
  • src/renderer/lib/tauri-window-api.ts
  • src/shared/types/ipc.types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/hooks/use-editor-persistence.test.ts

📝 Walkthrough

Walkthrough

Adds persistence-backed panel visibility for sidebar and file explorer, a concurrency-managed update API (useUpdatePanelVisibility), integrates persistence into TitleBar and WorkspaceLayout (including keyboard shortcut sidebarToggle), migrates file explorer visibility out of editor persistence, and updates close-flow to await pending app-settings writes.

Changes

Cohort / File(s) Summary
App-settings persistence core
src/renderer/hooks/use-app-settings.ts, src/renderer/hooks/use-app-settings.test.ts
Adds concurrency-managed panel visibility persistence, write queue with per-panel request IDs, rollback on failure, and public APIs: useUpdatePanelVisibility(), waitForPendingAppSettingsPersistence(), resetAppSettingsPersistenceQueueForTests(). Tests cover hydration, debouncing, errors, and synchronization.
Types & settings store
src/renderer/types/settings.ts, src/renderer/stores/app-settings-store.ts, src/renderer/stores/app-settings-store.test.ts
Adds sidebarVisible and fileExplorerVisible to AppSettings/DEFAULT_APP_SETTINGS and selector hooks (useSidebarVisibilitySetting, useFileExplorerVisibilitySetting); tests updated for defaults and updates.
Component integrations
src/renderer/components/TitleBar.tsx, src/renderer/components/TitleBar.test.tsx, src/renderer/layouts/WorkspaceLayout.tsx, src/renderer/layouts/WorkspaceLayout.test.tsx, src/renderer/layouts/WorkspaceLayout.close-persistence.test.tsx
Replaces direct store toggles with useUpdatePanelVisibility handlers, surfaces toast error handling, integrates persistence into keyboard shortcuts (sidebarToggle) and close-flow (await pending writes). Tests updated/mocked to exercise new contracts and close-flow coordination.
Editor persistence migration
src/renderer/hooks/use-editor-persistence.ts, src/renderer/hooks/use-editor-persistence.test.ts
Removes fileExplorerVisible from persisted editor state and writes; restore now only replays expandedDirs. Tests updated to assert legacy payloads are ignored.
IPC / window / keyboard API changes
src/shared/types/ipc.types.ts, src/renderer/lib/tauri-keyboard-api.ts, src/renderer/lib/tauri-window-api.ts
Adds sidebarToggle to KeyboardShortcutCallback; changes AppCloseRequestedCallback to return Promise<boolean> and wires onCloseRequested to await the callback result. Keyboard API now accepts typed callback payloads.
Store & tests additions
src/renderer/stores/sidebar-store.test.ts, src/renderer/stores/keyboard-shortcuts-store.test.ts, src/renderer/components/TitleBar.test.tsx
New/updated tests: sidebar store tests, keyboard shortcut default includes sidebarToggle, TitleBar tests validate persistence-aware toggles and error toasts.

Sequence Diagram(s)

sequenceDiagram
    participant Component as rgba(100,149,237,0.5) Component (TitleBar/WorkspaceLayout)
    participant Hook as rgba(60,179,113,0.5) useUpdatePanelVisibility
    participant Store as rgba(255,165,0,0.5) UI Store (Sidebar/FileExplorer)
    participant Persist as rgba(199,21,133,0.5) Persistence API
    participant Toast as rgba(128,128,128,0.5) Toast

    Component->>Hook: updatePanelVisibility(panel, visible)
    Hook->>Store: Optimistically set visibility
    Hook->>Persist: enqueue/write new visibility
    alt persist success
        Persist-->>Hook: write OK
        Hook-->>Component: resolve
    else persist failure
        Persist-->>Hook: error
        Hook->>Store: rollback visibility
        Hook->>Toast: show error message
        Hook-->>Component: reject
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I nudged a setting, soft and light,

Sidebar sleeps — saved for the night.
Ctrl+Shift+B, I twiddle and hum,
Now panels remember where they’ve come from.
Hoppity hops — persistence done!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: persist workspace panel visibility across restarts' accurately describes the main change: adding persistence for sidebar and file explorer visibility state.
Linked Issues check ✅ Passed All coding requirements from issue #40 are met: persistence layer implemented via use-app-settings hooks, visibility loaded at startup, saved on change, survives restarts, and works with keyboard shortcuts.
Out of Scope Changes check ✅ Passed All changes are directly related to persisting panel visibility: new persistence hooks, state updates, UI integration, type definitions, and comprehensive tests. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/persist-workspace-panel-visibility-40

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

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

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/layouts/WorkspaceLayout.tsx (1)

243-253: ⚠️ Potential issue | 🔴 Critical

Return the Promise<boolean> that onCloseRequested() expects.

Line 243 currently passes a void callback into windowApi.onCloseRequested(), but the real API awaits a Promise<boolean>. That is off-contract for typecheck, and the test mock in WorkspaceLayout.test.tsx also using () => void means it will not catch the regression. Return Promise.resolve(false) after kicking off the dialog/respondToClose flow so the close is explicitly prevented while the async persistence wait finishes.

Suggested fix
   useEffect(() => {
     return windowApi.onCloseRequested(() => {
       const dirtyCount = useEditorStore.getState().getDirtyFileCount()
       if (dirtyCount > 0) {
         setAppCloseDirtyCount(dirtyCount)
         setIsAppCloseDialogOpen(true)
       } else {
         void waitForPendingAppSettingsPersistence().then(() => {
           windowApi.respondToClose('close')
         })
       }
+      return Promise.resolve(false)
     })
   }, [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/layouts/WorkspaceLayout.tsx` around lines 243 - 253, The
onCloseRequested callback currently returns void but must return a
Promise<boolean>; update the callback passed to windowApi.onCloseRequested so it
returns Promise.resolve(false) after initiating the close logic (i.e., after
calling useEditorStore.getState().getDirtyFileCount(), setAppCloseDirtyCount,
setIsAppCloseDialogOpen or kicking off
waitForPendingAppSettingsPersistence().then(() =>
windowApi.respondToClose('close'))). Ensure the callback signature remains
async-compatible and always returns a Promise<boolean> (false to prevent
immediate close while async persistence runs).
🧹 Nitpick comments (1)
src/renderer/lib/tauri-keyboard-api.ts (1)

53-72: Use KeyboardShortcutCallback from shared types to eliminate duplication.

The shortcut union is repeated in both the callback parameter (line 53) and the type cast (lines 66-71). Since KeyboardShortcutCallback is already defined in src/shared/types/ipc.types.ts and used in the KeyboardApi interface, import and use it here instead of repeating the literal union.

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

In `@src/renderer/lib/tauri-keyboard-api.ts` around lines 53 - 72, Replace the
duplicated literal union with the shared type: import the
KeyboardShortcutCallback type and use it as the callback parameter type in
onShortcut and for the payload cast inside the listen handler; update the
signature onShortcut(callback: KeyboardShortcutCallback): () => void and cast
payload as KeyboardShortcutCallback (while keeping the existing
listen/UnlistenFn logic and IPC_EVENTS.SHORTCUT reference) so the union is not
duplicated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/hooks/use-app-settings.ts`:
- Around line 11-16: The queued panel-write logic (panelWriteChain,
panelWriteRequestIds, pendingPanelWriteCount) currently snapshots nothing and
reads live state when the async task runs, causing stale writes and incorrect
rollbacks; change the queue to capture each request’s specific panel key and
intended visible value in the queued payload (persist the panel/visible pair
with the task) and add a global write revision/check (e.g., increment a
writeRevision or include a requestId in the payload) that rollback logic must
compare against so only the most recent successful write can undo UI state;
update the corresponding enqueue/dequeue code paths (the functions that push
tasks onto panelWriteChain and the rollback code around the final write
success/failure) to use the captured payload and the revision guard—apply the
same pattern to the other similar blocks referenced (around the other panel
write variables at the other occurrences).
- Around line 19-24: The lint rule lint/suspicious/useIterableCallbackReturn is
triggered by the implicit-return arrow in notifyPanelWriteSettled; change the
callback passed to pendingPanelWriteWaiters.forEach/waiters.forEach to use a
block body so it has an explicit statement (e.g., replace (resolve) => resolve()
with (resolve) => { resolve(); }) or use an equivalent explicit loop, ensuring
you still clear pendingPanelWriteWaiters and call each resolve.

---

Outside diff comments:
In `@src/renderer/layouts/WorkspaceLayout.tsx`:
- Around line 243-253: The onCloseRequested callback currently returns void but
must return a Promise<boolean>; update the callback passed to
windowApi.onCloseRequested so it returns Promise.resolve(false) after initiating
the close logic (i.e., after calling
useEditorStore.getState().getDirtyFileCount(), setAppCloseDirtyCount,
setIsAppCloseDialogOpen or kicking off
waitForPendingAppSettingsPersistence().then(() =>
windowApi.respondToClose('close'))). Ensure the callback signature remains
async-compatible and always returns a Promise<boolean> (false to prevent
immediate close while async persistence runs).

---

Nitpick comments:
In `@src/renderer/lib/tauri-keyboard-api.ts`:
- Around line 53-72: Replace the duplicated literal union with the shared type:
import the KeyboardShortcutCallback type and use it as the callback parameter
type in onShortcut and for the payload cast inside the listen handler; update
the signature onShortcut(callback: KeyboardShortcutCallback): () => void and
cast payload as KeyboardShortcutCallback (while keeping the existing
listen/UnlistenFn logic and IPC_EVENTS.SHORTCUT reference) so the union is not
duplicated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff9aef49-133b-4a14-a43c-01356fc3213e

📥 Commits

Reviewing files that changed from the base of the PR and between 5f0bc22 and 173ce0a.

📒 Files selected for processing (15)
  • src/renderer/components/TitleBar.test.tsx
  • src/renderer/components/TitleBar.tsx
  • src/renderer/hooks/use-app-settings.test.ts
  • src/renderer/hooks/use-app-settings.ts
  • src/renderer/hooks/use-editor-persistence.test.ts
  • src/renderer/hooks/use-editor-persistence.ts
  • src/renderer/layouts/WorkspaceLayout.test.tsx
  • src/renderer/layouts/WorkspaceLayout.tsx
  • src/renderer/lib/tauri-keyboard-api.ts
  • src/renderer/stores/app-settings-store.test.ts
  • src/renderer/stores/app-settings-store.ts
  • src/renderer/stores/keyboard-shortcuts-store.test.ts
  • src/renderer/stores/sidebar-store.test.ts
  • src/renderer/types/settings.ts
  • src/shared/types/ipc.types.ts

@gnoviawan gnoviawan added phase-1 Phase 1: Stability & Polish (v0.3.1) v0.3.1 labels Mar 9, 2026
@gnoviawan gnoviawan changed the title fix: persist workspace panel visibility across restarts (#40) fix: persist workspace panel visibility across restarts Mar 10, 2026
gnoviawan and others added 2 commits March 10, 2026 07:25
Capture panel write payloads in the persistence queue, tighten close-request typing, and reuse shared shortcut callback types. This keeps rollback behavior deterministic and aligns the Tauri window API with async close coordination.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bring the branch up to date with dev while preserving global workspace panel persistence and close-flow synchronization. Align the merged persistence, terminal restore, and WorkspaceLayout tests so the #40 behavior remains covered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnoviawan gnoviawan merged commit 80fe705 into dev Mar 10, 2026
10 checks passed
@gnoviawan gnoviawan mentioned this pull request Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

phase-1 Phase 1: Stability & Polish (v0.3.1) v0.3.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P0-BUG] Sidebar Visibility State Not Persisted

1 participant