Skip to content

Revert "feat(desktop): workspace pane store registry (v2 PR 3)" (#3940)#4017

Merged
Kitenite merged 1 commit intomainfrom
debug-pane-registry-prese
May 3, 2026
Merged

Revert "feat(desktop): workspace pane store registry (v2 PR 3)" (#3940)#4017
Kitenite merged 1 commit intomainfrom
debug-pane-registry-prese

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 3, 2026

Summary

Reverts #3940 (f7db05ce8).

The pane-store registry / bidirectional persistence sync introduced regressions that aren't worth its current scope:

  • Preset / pending-launch initial commands intermittently don't run because <Pane> remounts when the layout tree restructures (single-pane → split, split-depth growing) and useRef(paneData.initialCommand) reinitializes against already-cleared data.
  • Per-mount sync via subscribeChanges + 3-way reconciliation against lastSyncedSnapshot is hard to reason about and traces revealed it was firing many redundant writes per pane operation.
  • Cold-boot direct-to-workspace seeding races with collection sync (separately fixed in 266c746, now also reverted here).

Going back to per-mount useState(() => createWorkspaceStore(...)) + useLiveQuery-driven seeding restores the pre-PR3 behavior, which was reliable. PR4's workspace.create() flow can re-add a singleton later with a different design that doesn't put initialCommand on persisted pane data.

Test plan

  • bun test src/renderer — 927/927 pass
  • bunx tsc --noEmit — no new errors
  • Smoke: open existing workspace, click presets, verify pane appears AND command runs
  • Smoke: split-pane preset, verify both panes' commands run
  • Smoke: workspace switch back-and-forth, layouts persist

Summary by CodeRabbit

  • Bug Fixes

    • Improved workspace restoration on cold start so persisted layouts and active tabs are reliably preserved.
  • Behavior Change

    • Switching tabs may now reuse existing pane instances in the same layout slot, preserving internal pane state across tab switches.
  • Tests

    • Added regression tests covering cold-start restoration and adding tabs during initialization.
  • Refactor

    • Workspace pane initialization and launch-handling logic reworked/streamlined (internal plumbing changes).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 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: fc65f364-3473-407a-a8c6-44abcab05b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 266c746 and 7e78255.

📒 Files selected for processing (10)
  • apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.test.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/index.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.test.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspacePaneLayout/useV2WorkspacePaneLayout.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx
  • packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx
  • plans/20260430-pane-store-registry-pr3.md
💤 Files with no reviewable changes (8)
  • packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx
  • apps/desktop/src/renderer/lib/workspace-pane-registry/index.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.test.ts
  • plans/20260430-pane-store-registry-pr3.md
  • apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.test.ts
  • apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.ts

📝 Walkthrough

Walkthrough

The workspace pane store initialization was changed to start from an EMPTY_STATE and be seeded via v2WorkspaceLocalState.subscribeChanges (includeInitialState). Registry initialization/wiring was removed from the CollectionsProvider; several tests/files and re-exports were removed, and hooks were updated to create local stores and synchronize with collections via snapshot-guarded subscribe/writebacks.

Changes

Workspace pane store initialization & sync

Layer / File(s) Summary
Data Shape / Defaults
apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.ts
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/.../useV2WorkspacePaneLayout.ts
Stores now start from EMPTY_STATE and lastSyncedSnapshot is seeded from that empty snapshot instead of synchronously reading initialRow?.paneLayout.
Core Reconciliation / Subscription
apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.ts
Row→store seeding and updates are driven through v2WorkspaceLocalState.subscribeChanges(..., { includeInitialState: true }), using the same reconciliation and snapshot-guard logic for initial and subsequent events; comments updated to reflect ordering and row-appearance-after-mutation cases.
Hook-level Store Management
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/.../useV2WorkspacePaneLayout.ts
Hook now creates its own WorkspaceState via createWorkspaceStore(EMPTY_STATE), reads persisted paneLayout with useLiveQuery, applies persisted state into store when snapshots differ, and subscribes to store to write back version/tabs/activeTabId into v2WorkspaceLocalState with snapshot guarding.
Provider Wiring
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx
Synchronous initialization/rewiring of the workspace-pane-registry was removed from the provider’s useMemo; provider now only derives collections via getCollections(activeOrganizationId).
Runtime Cleanup
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
Removing a workspace no longer calls dropWorkspacePaneStore; it now runs cleanupWorkspacePaneRuntimes([workspace]) and deletes the v2WorkspaceLocalState row.
Re-exports removed
apps/desktop/src/renderer/lib/workspace-pane-registry/index.ts
Removed re-exports for addLaunchPanes, LaunchPaneInput, dropWorkspacePaneStore, getOrCreateWorkspacePaneStore, initWorkspacePaneRegistry, and WorkspacePaneRegistryDeps.
Feature removal (helper + tests)
apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.ts
apps/desktop/src/renderer/lib/workspace-pane-registry/addLaunchPanes.test.ts
addLaunchPanes and its LaunchPaneInput type were removed, along with the corresponding test suite.
Regression tests added
apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.test.ts
Two tests added to validate cold-start behavior: (1) registry store seeded from persisted paneLayout (including activeTabId) when initialized before sync; (2) adding a tab on cold-start preserves existing persisted tab and merges rather than clobbering.
UI reconciliation change
packages/panes/src/react/components/Workspace/components/Tab/Tab.tsx
Removed key={pane.id} from rendered Pane nodes, changing React reconciliation to position-based for panes in the same layout slot.
Docs / Plans removed
plans/20260430-pane-store-registry-pr3.md
Planning document removed.

Sequence Diagram(s)

sequenceDiagram
  participant Hook as useV2WorkspacePaneLayout (UI Hook)
  participant Store as Workspace Store
  participant Col as v2WorkspaceLocalState (Collections)
  participant LS as localStorage

  Note over Hook,Store: Initialization (cold-start)
  Hook->>Store: createWorkspaceStore(EMPTY_STATE)
  Hook->>Col: useLiveQuery/get(workspaceId)
  Col->>LS: read persisted row
  LS-->>Col: persisted paneLayout (initial)
  Col-->>Hook: includeInitialState event (subscribeChanges)
  Hook->>Store: replaceState(persistedPaneLayout) [if snapshot differs]

  Note over Store,Col: Ongoing sync
  Store->>Col: update row with version/tabs/activeTabId (on store change)
  Col->>LS: write persisted row
  LS-->>Col: persisted row saved
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
Empty states awake at dawn,
Subscriptions hum, old layouts drawn,
Tabs keep company, not replaced,
Persistence and runtime interlaced—
A quiet hop where state lives on.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Title check ✅ Passed The PR title accurately summarizes the main change: reverting a previous feature (PR #3940) due to regressions.
Description check ✅ Passed The description covers the key sections: clear rationale for the revert, test plan results, and explains the regressions that motivated the revert decision.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 debug-pane-registry-prese

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 3, 2026

Greptile Summary

This PR fixes a cold-boot race where getOrCreateWorkspacePaneStore called .get() synchronously on a collection before its first subscriber had triggered sync, receiving undefined and seeding the store with EMPTY_STATE — silently discarding the persisted layout on the next user action. The fix replaces the one-shot .get() with subscribeChanges({ includeInitialState: true }), so the persisted row is delivered to the existing 3-way reconciliation handler as its first event; startSyncImmediate() is additionally moved into CollectionsProvider's useMemo as insurance for other consumers that still read via .get().

Confidence Score: 4/5

Safe to merge; fix is well-reasoned, all 944 tests pass, and the only concern is a P2 style issue in CollectionsProvider.

No P0 or P1 issues found. The core fix (replacing a racing .get() with subscribeChanges({ includeInitialState: true })) is structurally correct and validated by two targeted regression tests. The three-way reconciliation handles all seeding and echo-prevention edge cases. The sole concern is startSyncImmediate() being called as a side effect inside useMemo, which is non-idiomatic React and can double-fire in Strict Mode, but is acknowledged in comments and the call appears idempotent.

CollectionsProvider.tsx — startSyncImmediate() side effect in useMemo should be monitored if startSyncImmediate ever becomes non-idempotent.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.ts Core fix: removes the racing synchronous .get() seed, replaces it with subscribeChanges({ includeInitialState: true }) delivering the persisted row as the first event; three-way reconciliation and all sync paths preserved correctly.
apps/desktop/src/renderer/lib/workspace-pane-registry/workspace-pane-registry.test.ts Adds two targeted regression tests (cold-boot seeding and cold-boot tab-stacking) using isolated Storage mocks and real createCollection; both verify the fix end-to-end without async workarounds, confirming includeInitialState fires synchronously.
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx Moves startSyncImmediate() into the useMemo as belt-and-suspenders for non-registry consumers; the side effect in useMemo is intentional and acknowledged but remains non-idiomatic React and could cause double-invocations in Strict Mode.

Sequence Diagram

sequenceDiagram
    participant CP as CollectionsProvider (useMemo)
    participant Reg as WorkspacePaneRegistry
    participant TDB as TanStack DB Collection
    participant Store as Zustand Store

    CP->>TDB: startSyncImmediate()
    Note over TDB: Collection loads localStorage data synchronously
    CP->>Reg: initWorkspacePaneRegistry(deps)
    Note over CP: Later — workspace route mounts
    CP->>Reg: getOrCreateWorkspacePaneStore(workspaceId)
    Reg->>Store: createWorkspaceStore(EMPTY_STATE)
    Reg->>TDB: subscribeChanges({ includeInitialState: true })
    TDB-->>Reg: initial-state event (persisted row)
    Note over Reg: 3-way reconciliation:<br/>incoming ≠ storeSnapshot (EMPTY)<br/>storeSnapshot === lastSyncedSnapshot (EMPTY)<br/>→ replaceState(persistedLayout)
    Reg->>Store: replaceState(persistedLayout)
    Note over Store: Store now holds persisted layout ✓
    Note over Store: User action (e.g. addTab)
    Store-->>Reg: store subscriber fires
    Reg->>TDB: update(workspaceId, draft.paneLayout = newState)
    Note over Reg: lastSyncedSnapshot = newSnapshot
    TDB-->>Reg: change event (echo of store→row write)
    Note over Reg: incoming === storeSnapshot → no-op
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx:85
**Side effect inside `useMemo` is non-idiomatic**

`startSyncImmediate()` is a side effect placed directly in `useMemo`. React reserves the right to discard and recompute memo work (and in Strict Mode explicitly does so in development), which means `startSyncImmediate()` can be called multiple times per `activeOrganizationId`. The comment acknowledges this, and the mitigation (idempotency of `startSyncImmediate`) makes this tolerable in practice, but it's worth noting that if `startSyncImmediate` is ever not idempotent (e.g., triggers duplicate event listeners), the double-invoke in Strict Mode could surface subtle bugs. Consider whether a `useEffect` with a dedicated synchrony workaround or a `useRef` guard would be a safer long-term home for this call.

Reviews (1): Last reviewed commit: "fix(desktop): seed pane store from initi..." | Re-trigger Greptile

const collections = useMemo(() => {
if (!activeOrganizationId) return null;
const next = getCollections(activeOrganizationId);
next.v2WorkspaceLocalState.startSyncImmediate();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Side effect inside useMemo is non-idiomatic

startSyncImmediate() is a side effect placed directly in useMemo. React reserves the right to discard and recompute memo work (and in Strict Mode explicitly does so in development), which means startSyncImmediate() can be called multiple times per activeOrganizationId. The comment acknowledges this, and the mitigation (idempotency of startSyncImmediate) makes this tolerable in practice, but it's worth noting that if startSyncImmediate is ever not idempotent (e.g., triggers duplicate event listeners), the double-invoke in Strict Mode could surface subtle bugs. Consider whether a useEffect with a dedicated synchrony workaround or a useRef guard would be a safer long-term home for this call.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx
Line: 85

Comment:
**Side effect inside `useMemo` is non-idiomatic**

`startSyncImmediate()` is a side effect placed directly in `useMemo`. React reserves the right to discard and recompute memo work (and in Strict Mode explicitly does so in development), which means `startSyncImmediate()` can be called multiple times per `activeOrganizationId`. The comment acknowledges this, and the mitigation (idempotency of `startSyncImmediate`) makes this tolerable in practice, but it's worth noting that if `startSyncImmediate` is ever not idempotent (e.g., triggers duplicate event listeners), the double-invoke in Strict Mode could surface subtle bugs. Consider whether a `useEffect` with a dedicated synchrony workaround or a `useRef` guard would be a safer long-term home for this call.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

@Kitenite Kitenite force-pushed the debug-pane-registry-prese branch from c5c4956 to 266c746 Compare May 3, 2026 21:50
@Kitenite Kitenite force-pushed the debug-pane-registry-prese branch from 266c746 to 7e78255 Compare May 3, 2026 22:30
@Kitenite Kitenite changed the title fix(desktop): seed v2 pane store from initial-state event Revert "feat(desktop): workspace pane store registry (v2 PR 3)" (#3940) May 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@Kitenite Kitenite merged commit 06bbdb2 into main May 3, 2026
14 checks passed
@Kitenite Kitenite deleted the debug-pane-registry-prese branch May 3, 2026 23:00
MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request May 8, 2026
Recorded as integrated via -s ours after batch PRs #455-#464.

Taken via individual PRs:
- PR  1 (#455): v2 polish 前半 safe set (9 commits)
- PR  2 (#456): v2/host-service polish 中盤 (12 commits)
- PR  3 (#457): sidebar polish + jwt API (5 commits)
- PR  4 (#458): host-service tRPC retry/cache/timeout (3 commits)
- PR  5 (#459): v2 diff pane / file pane polish (2 commits)
- PR  7 (#462): host-service v2 canonical workspace.create + attachment store (PR1 superset-sh#3893 + PR2 superset-sh#3916)
- PR 11 (#463): agents API + onboarding (7 commits + 1 cleanup)
- PR 12 (#464): v1→v2 import flow rewrite (11 commits + 2 follow-ups)
- PR 13 (#460): host-service shell env probe + typo (2 commits)
- PR 16 (#461): marketplace 19 themes (1 commit)

Skipped / deferred (recorded as integrated for behind=0):
- PR  6: CLI v1 launch (superset-sh#3898 + 30+ CLI/SDK followups) — defer to dedicated migration
- PR  9: v2 PR3 (superset-sh#3940) + revert (superset-sh#4017) — net-zero pair
- PR 10: pty-daemon (superset-sh#3896, superset-sh#3971, superset-sh#4054) — fork keeps its terminal-host
- PR 14: Slack MCP-v2 (superset-sh#4197, superset-sh#4208) — depends on mcp-v2/sdk divergence
- PR 15: onboarding remaining (superset-sh#4115, superset-sh#4125, superset-sh#4214, superset-sh#4213, superset-sh#4222, superset-sh#4225) — depends on fork's deleted setup pages

Behind: 0 after this merge.
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