Skip to content

fix: stability fixes round 2 — cross-tab sync, atomic persistence, state cleanup#254

Merged
mattleaverton merged 6 commits intodanshapiro:mainfrom
mattleaverton:stability-fixes-r2
Mar 30, 2026
Merged

fix: stability fixes round 2 — cross-tab sync, atomic persistence, state cleanup#254
mattleaverton merged 6 commits intodanshapiro:mainfrom
mattleaverton:stability-fixes-r2

Conversation

@mattleaverton
Copy link
Copy Markdown
Collaborator

Summary

Five fixes for structural state management issues, worked serially on a single branch. Closes #253.

  • Dead code cleanup — Remove unused terminal.runtime.updated server broadcast (no client handler) and 3 never-dispatched Redux actions (collapseAll, expandAll, clearClosedTabSnapshot). -226 lines.

  • Tombstone merge for cross-tab sync — Replace full-replacement hydrateTabs with set-union merge. Tabs created in different browser tabs now survive sync instead of being silently overwritten. Per-tab updatedAt resolves property conflicts (newer wins). Tombstone set prevents deleted tabs from resurrecting.

  • Atomic tabs+panes persistence — Merge separate freshell.tabs.v2 and freshell.panes.v2 localStorage keys into a single freshell.layout.v3 key. Eliminates partial-write vulnerability. Cross-tab sync handles one storage event instead of two, removing the readAndHydratePairedKey workaround. Includes v2→v3 migration on first load.

  • Session repair hot-path — Add cache check in waitForSession before falling through to serial queue wait. Sessions discovered at startup are enqueued before cache is consulted; this avoids blocking terminal creation when the cache already has a valid result.

  • Remove tab.terminalId dual source of truth — Remove the legacy terminalId field from the Tab type. All terminal identity now lives exclusively in pane.content.terminalId. Add pane-tree-walking selectors and replace 27 read sites across 16 source files.

Test plan

  • Full test suite passes (3050 tests, 1 pre-existing failure in refresh-context-menu-flow)
  • Server tests pass (175 files)
  • Client and server typecheck clean
  • Production build succeeds
  • Multi-tab behavior: open Freshell in 2+ browser tabs, create tabs in both simultaneously — none lost
  • Reconnect: kill server, restart, all tabs recover
  • Page refresh preserves all tabs and pane layouts
  • Claude terminal creation is fast (no repair blocking)
  • Tab headers show correct metadata after tab.terminalId removal

🤖 Generated with Claude Code

mattleaverton and others added 6 commits March 30, 2026 11:55
… Redux actions

Remove server broadcast `terminal.runtime.updated` (no client handler existed),
unused sessionsSlice actions (collapseAll, expandAll), and unused tabRegistrySlice
action (clearClosedTabSnapshot). Clears noise before structural stability fixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace full-replacement hydrateTabs with set-union merge. Tabs created in
different browser tabs now survive cross-tab sync instead of being silently
overwritten. Per-tab updatedAt resolves property conflicts (newer wins).
Tombstone set prevents deleted tabs from resurrecting via remote sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge separate freshell.tabs.v2 and freshell.panes.v2 localStorage keys
into a single freshell.layout.v3 key. Eliminates partial-write vulnerability
where tabs could persist but panes fail, causing orphaned tabs with picker
content. Cross-tab sync now handles one storage event instead of two,
removing the readAndHydratePairedKey workaround. Includes migration from
v2 keys on first load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When waitForSession finds a session in the queue, check the disk cache
before falling through to queue.waitFor(). Sessions discovered at startup
are enqueued before cache is consulted, so this avoids blocking terminal
creation on queue processing when the cache already has a valid result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the legacy terminalId field from Tab type. All terminal identity
now lives exclusively in pane.content.terminalId. Add pane-tree-walking
selectors (selectTerminalIdsForTab, selectPrimaryTerminalIdForTab,
selectTabIdByTerminalId) and replace 27 read sites across 16 source files.
Migration strips legacy terminalId from persisted data on load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/components/Sidebar.tsx
@mattleaverton mattleaverton merged commit fc1c99f into danshapiro:main Mar 30, 2026
1 check passed
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.

Cross-tab sync silently loses tabs; partial-write persistence; dual source of truth in tab.terminalId

1 participant