Skip to content

fix(app): keep desktop session tabs and selected-server sync consistent - #30672

Closed
Hona wants to merge 9 commits into
anomalyco:devfrom
Hona:fix/desktop-session-tabs-ddd
Closed

fix(app): keep desktop session tabs and selected-server sync consistent#30672
Hona wants to merge 9 commits into
anomalyco:devfrom
Hona:fix/desktop-session-tabs-ddd

Conversation

@Hona

@HonaHona commented Jun 4, 2026

Copy link
Copy Markdown
Member

Changelog

Fixed

  • Subagent routes now stay attached to their primary session desktop tab instead of opening separate tabs.
  • Deep-linked nested subagents now attach to the correct primary session tab after their parent sessions load.
  • Renaming a session updates its desktop tab title without requiring a reload.
  • Archiving a primary session while viewing one of its subagents now closes the correct desktop tab and navigates away safely.
  • Archive and delete actions immediately remove affected loaded sessions, cached data, and desktop tabs even while the live event stream is reconnecting.
  • Remote archive and delete events now remove affected desktop tabs and loaded session data consistently.
  • Closing an inactive desktop tab no longer switches away from the current tab, including while the New Session page is open.
  • Active and hovered tabs reserve room for the close button instead of covering the session title.
  • Numbered tab shortcuts such as Cmd+2 or Ctrl+2 now enable based on open desktop tabs instead of project count.
  • Provider and model settings for a selected server now read sync data from that selected server instead of the active app server.

Improved

  • Desktop tabs now model a primary session and its subagents as one navigational unit.
  • Loaded session trees share one traversal path for tab routing, cleanup, permission lookup, and request lookup.

Comparison

Alternative implementation of #30644 for side-by-side review.

CopilotAI review requested due to automatic review settings June 4, 2026 03:51
@Hona
Hona requested a review from adamdotdevin as a code ownerJune 4, 2026 03:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors desktop session tab handling to be rooted at the “root session” (so subagent/child routes stay attached to a single tab), centralizes lineage traversal into a reusable session graph, and introduces neutral “sessions unavailable” lifecycle events to coordinate archive/delete behavior across local and server-driven flows.

Changes:

  • Introduces src/session/tabs to model desktop tabs as root-session state with deterministic open/close/remove behavior.
  • Adds src/session/graph and rewires multiple call sites to use shared lineage/subtree traversal utilities.
  • Adds src/session/lifecycle + events for archive/delete propagation and updates global sync + UI wiring to publish/consume those events and track session_unavailable.

Reviewed changes

Copilot reviewed 32 out of 34 changed files in this pull request and generated 4 comments.

Show a summary per file
FileDescription
packages/app/src/session/tabs/index.tsAdds pure tab-state utilities keyed by root session and route-to-tab bindings.
packages/app/src/session/tabs/index.test.tsAdds unit tests for root-tab binding, removals, and navigation effects.
packages/app/src/session/lifecycle/index.tsDefines neutral session availability types for archive/delete.
packages/app/src/session/lifecycle/events.tsAdds publish/read helpers for the new sessions-unavailable browser event.
packages/app/src/session/lifecycle/events.test.tsTests validation/parsing behavior for sessions-unavailable events.
packages/app/src/session/graph/index.tsIntroduces reusable lineage/root/subtree traversal helpers.
packages/app/src/session/graph/index.test.tsAdds tests for root resolution, cycles, subtree collection, and path child selection.
packages/app/src/pages/session/message-timeline.tsxSwitches archive/delete behavior to subtree-aware eviction + sessions-unavailable publishing.
packages/app/src/pages/session/composer/session-request-tree.tsUses shared graph traversal for request selection within a subtree.
packages/app/src/pages/session.tsxUpdates import path for session prefetch module after re-org.
packages/app/src/pages/layout/helpers.tsUses shared graph traversal to find the child session on an active path.
packages/app/src/pages/layout.tsxMakes archive behavior subtree-aware and publishes sessions-unavailable events.
packages/app/src/context/server-sync.tsxWires a sessions-unavailable hook into directory event handling.
packages/app/src/context/server-sync.test.tsUpdates imports after session infrastructure re-org.
packages/app/src/context/permission-auto-respond.tsUses shared graph traversal for ancestor checks when auto-accepting permissions.
packages/app/src/context/global-sync/types.tsAdds session_unavailable state for archived/deleted tracking.
packages/app/src/context/global-sync/session/trim.tsFixes imports after moving session utilities under session/.
packages/app/src/context/global-sync/session/trim.test.tsUpdates test imports after module move.
packages/app/src/context/global-sync/session/store.tsAdds upsertSession helper for stable binary-search insertion/update.
packages/app/src/context/global-sync/session/prefetch.tsMoves/introduces session prefetch cache logic under session/.
packages/app/src/context/global-sync/session/prefetch.test.tsUpdates test imports after module move.
packages/app/src/context/global-sync/session/load.tsFixes types import path after moving session utilities under session/.
packages/app/src/context/global-sync/session/lineage.tsAdds lineage hydration helper that loads missing ancestors while respecting unavailability.
packages/app/src/context/global-sync/session/lineage.test.tsTests lineage hydration behavior (resolved, unavailable, cycle).
packages/app/src/context/global-sync/session/cache.tsMoves/introduces session cache drop + eviction selection under session/.
packages/app/src/context/global-sync/session/cache.test.tsUpdates test imports after module move.
packages/app/src/context/global-sync/event-reducer.tsAdds session_unavailable tracking and publishes subtree-unavailable changes.
packages/app/src/context/global-sync/event-reducer.test.tsExtends tests for subtree unavailability + idempotent archive transitions.
packages/app/src/context/global-sync/child-store.tsInitializes session_unavailable in per-directory store state.
packages/app/src/context/global-sync/bootstrap.test.tsUpdates bootstrap state to include session_unavailable.
packages/app/src/context/directory-sync.tsAdds lineage hydration during sync, blocks re-inserting unavailable/archived sessions, and uses upsertSession.
packages/app/src/components/titlebar.tsxReplaces ad-hoc tab store with session/tabs + listens for sessions-unavailable events.
packages/app/src/components/titlebar-session-events.tsRemoves deprecated session-tabs-removed event mechanism.
packages/app/src/components/titlebar-session-events.test.tsRemoves tests for deprecated session-tabs-removed event mechanism.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (!tracked(directory, sessionID)) return
const data = response?.data
if (!data) return
if (data.time.archived || store.session_unavailable[ancestorID]) return data
@@ -135,10 +140,12 @@ export function applyDirectoryEvent(input: {
)
}
cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo)
@@ -162,6 +171,7 @@ export function applyDirectoryEvent(input: {
)
}
cleanupSessionCaches(input.setStore, info.id, input.setSessionTodo)
input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
break
}
input.setStore("session_unavailable", info.id, undefined)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 35 out of 37 changed files in this pull request and generated 4 comments.

Comment on lines +35 to +48
export function loadedSessionTreeIDs(sessions: SessionNode[], rootID: string) {
const ids = new Set([rootID])
const queue = [rootID]

for (const id of queue) {
for (const session of sessions) {
if (session.parentID !== id || ids.has(session.id)) continue
ids.add(session.id)
queue.push(session.id)
}
}

return ids
}
@@ -881,6 +878,7 @@ export function MessageTimeline(props: {
const sessions = (sync.data.session ?? []).filter((s) => !s.parentID && !s.time?.archived)
expect(store.session_status.ses_1).toBeUndefined()
})

test("reports loaded routes but only marks the archived session unavailable", () => {
<div class="flex min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden">
<div class="flex min-w-0 flex-row items-center gap-1.5 overflow-hidden">
<For each={tabsEnriched()}>
<For each={tabsEnriched().map((resolve) => resolve())}>
@HonaHona changed the title fix(app): model desktop session tabs as root sessionsfix(app): keep desktop session tabs consistent across subagents and removalsJun 4, 2026
@HonaHona changed the title fix(app): keep desktop session tabs consistent across subagents and removalsfix(app): keep desktop session tabs and selected-server sync consistentJun 4, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Hona