Skip to content

fix(desktop): reopen a chat on the browser tab the user left it on - #7793

Merged
waleedlatif1 merged 8 commits into
stagingfrom
fix/browser-tab-restore-on-chat-switch
Sep 13, 2026
Merged

waleedlatif1 merged 8 commits into
stagingfrom
fix/browser-tab-restore-on-chat-switch

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Reopening a chat landed on the strip's last browser tab instead of the one the user left it on, because the strip treated its own fallback as a selection and pushed it to the desktop app.
  • Which resource the panel shows is now a pure rule: an explicit selection wins while it is on screen, otherwise the last resource, except that a browser or terminal last resource defers to the tab the desktop app is currently showing. That tab is what the desktop remembers per chat, and it survives relaunch.
  • Nothing is written back into the selection, so the arrival order of the tab list and the chat history can no longer decide what a chat opens on.
  • A native tab switch still claims the selection as the user's, measured against the tab the desktop app was showing rather than the one the strip shows, since with no explicit selection those are now the same tab.
  • Closing the shown tab moves to a neighbour of its own kind, so the strip and the desktop app agree on what comes next instead of the strip falling back to its last tab.

Why it is a pure rule rather than a write

An earlier revision adopted the remembered tab by writing it into the selection from an effect, gated on the chat history having loaded. An instrumented Electron run with the history held back 2.5s showed the strip on the wrong page for 2164ms before it corrected. Deriving the value instead removes the gate, the passive setter and the effect, and the same run now shows the remembered page immediately.

Testing

  • Unit: 887 tests across the chat surface, including a dedicated suite for the resolver. Every guard in the change was mutation-tested in both directions; two tests that originally passed with their guard deleted were rewritten until they failed.
  • Full apps/sim suite: 3,543 files, 50,872 tests, zero failures. Type-check across all workspaces, lint, 46 audits, migration safety and docs manifest all pass.
  • Instrumented Electron runs against a local build, sampling the strip's selected tab every 8ms plus a mutation observer, covering: reopening a chat, reopening with the chat history delayed 2.5s, clicking between pages, opening a page, and closing the shown page. No perceptible wrong-tab window in any case. Opening a page shows the previous last page for a single frame (5-15ms) while the new page is still being projected; everything else is clean.

Known limits (unchanged by this PR)

  • A chat that also holds a stored file or workflow opens on that stored resource, not on the browser tab the user left on. Nothing records which kind of resource was last shown.
  • With both browser and terminal tabs, only the kind of the last resource is consulted.

Type of Change

  • Bug fix

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

🤖 Generated with Claude Code

https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ

The resource strip pushed its own last-tab fallback onto the desktop app
whenever a chat opened without an explicit selection, overriding the tab
the desktop remembers the user was on. The shared desktop-tab hook now
switches the native tab only for an explicit selection, adopts the
desktop's active tab when the strip is on its fallback, and defers a
selected tab that has not landed yet until it does. Chat hydration no
longer writes a browser or terminal tab into the URL as a fallback.
@vercel

vercel Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Sep 13, 2026 2:35am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the previously reported restoration and late-selection failures are resolved without a newly established defect.

Summary

  • Separates explicit resource selection from the effective resource shown by the panel.
  • Resolves desktop-backed fallbacks against each native session’s active tab.
  • Preserves explicit selections made before native tabs arrive.
  • Tracks genuine native tab switches while ignoring first reports and hook-requested switches.
  • Selects a same-kind neighboring tab when closing the currently shown desktop tab.
  • Avoids writing browser or terminal fallback tabs into the URL during chat hydration.
  • Adds focused browser, terminal, hydration, close-liveness, and resource-policy coverage.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Open or reopen chat] --> B[Hydrate persisted resources]
  A --> C[Read desktop browser and terminal state]
  B --> D{Live explicit selection?}
  C --> D
  D -->|Yes| E[Show explicit resource]
  D -->|No| F{Fallback is desktop-backed?}
  F -->|Yes and remembered tab is live| G[Show native remembered tab passively]
  F -->|No| H[Show last resource fallback]
  G --> I{Later native tab change?}
  I -->|First report or requested switch| J[Do not claim selection]
  I -->|User switch while viewing that kind| K[Record explicit user selection]
Loading

Reviews (6) · Last reviewed commit: "Merge remote-tracking branch 'origin/sta..."

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-desktop-tab-resources.ts Outdated
…lection

Review round on the reopen fix.

A late first report of the desktop app's active tab carries the tab it
remembers, not a switch the user made, so it is adopted rather than
claimed and agent activity can still take the view on chat open. A move
away from a tab the desktop was already showing stays the user's own.

Adoption now waits for the chat history to be applied, so the arrival
order of the tab list and the history no longer decides which resource a
chat opens on, and it skips a tab the strip has already dropped, so
closing the shown tab cannot write the closed id back. Closing the shown
tab selects its neighbour the way the desktop app picks the next native
tab, instead of flashing through the strip's last tab.

The two wrapper hooks now share one options type with the strip, and the
adopt rule lives in a single helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment thread apps/sim/app/workspace/[workspaceId]/home/hooks/use-desktop-tab-resources.ts Outdated
The effect that shows an explicitly selected tab was keyed on the
selection alone, so a selection made before the desktop app published
its tab list was dropped rather than applied when the tab arrived. It is
now keyed on that tab being live as well, which covers the late arrival
without a retry ref to arm and disarm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

waleedlatif1 and others added 2 commits September 12, 2026 09:36
…mbing

Quality pass on the reopen fix.

The late-arrival adoption now carries the same guards as the hydrated
one, so a first report of the desktop app's active tab can no longer
override a selection the user made before the tab list arrived. Both
guards are pinned by tests that fail when either is removed.

The predicate the adopt and claim paths share moved into one helper, so
the single difference between them — adoption needs the tab to still be
in the strip, following the user does not — is stated once. Removes a
ref nothing read and an options interface with no second consumer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

The test hosts took their props through a type alias derived from the
hook signature. The repo asks for an interface, and the hook already
exports one that is exactly this shape, so the hosts use it directly
instead of restating it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Reopening a chat could show the wrong page for as long as the chat
history took to load. The strip wrote the desktop app's remembered tab
into the selection from an effect, and that write had to wait for the
history or the arrival order would decide what the chat opened on. With
the history held back 2.5s, an instrumented run showed the wrong page
for 2164ms before it corrected.

The rule is now a pure function: an explicit selection wins, otherwise
the last resource, except that a desktop-backed last resource defers to
the tab the desktop app is showing. Nothing is written back, so the
gate, the passive setter and the effect behind them are gone, and the
same run now shows the remembered page immediately.

A native switch is claimed as the user's against the tab the desktop app
was showing rather than the one the strip shows, since with no explicit
selection those are now the same tab. Closing the shown tab prefers a
neighbour of its own kind, so the strip and the desktop app agree on
what comes next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139YonWmiZUnPMTHoH4PtAJ
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@waleedlatif1
waleedlatif1 merged commit 2799994 into staging Sep 13, 2026
34 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/browser-tab-restore-on-chat-switch branch September 13, 2026 02:53
waleedlatif1 added a commit that referenced this pull request Sep 24, 2026
Organization Home passed projectsDesktopTabs: false to useChat in Assistant
mode, which only nulls the native active-tab ids used for the strip's
fallback. ChatResourcePanel still projects the chat scope's browser and
terminal tabs in every mode, and chat link clicks in the desktop app open
those tabs through the same projection, so Assistant chats showed desktop
tabs while ignoring which one the desktop app remembers (the #7793 reopen
behavior). Build Home's options with getMothershipUseChatOptions, like
workspace Home, so tabs project consistently in every mode. Assistant
requests still attach no resources.
waleedlatif1 added a commit that referenced this pull request Sep 24, 2026
Organization Home passed projectsDesktopTabs: false to useChat in Assistant
mode, which only nulls the native active-tab ids used for the strip's
fallback. ChatResourcePanel still projects the chat scope's browser and
terminal tabs in every mode, and chat link clicks in the desktop app open
those tabs through the same projection, so Assistant chats showed desktop
tabs while ignoring which one the desktop app remembers (the #7793 reopen
behavior). Build Home's options with getMothershipUseChatOptions, like
workspace Home, so tabs project consistently in every mode. Assistant
requests still attach no resources.
waleedlatif1 added a commit that referenced this pull request Sep 24, 2026
* fix(desktop): admit browser download saves before reading the body

PUT /api/desktop/tool/file buffered up to 100 MiB of the request body
before the use case checked that the tool call exists, is a running
browser_save_download claimed by the desktop, and belongs to the caller's
run, so any signed-in user could force large reads with bogus toolCallIds.

Add admitBrowserDownloadSave (same operation and binding resolution as
saveBrowserDownload, no claim or audit), following the existing
admitCreateWorkspaceFile pattern, and run it before the body read. The
save use case still re-validates and claims atomically after the read,
so a failed or oversized upload never burns the single-use claim.

* fix(desktop): walk cross-realm shadow roots when resolving upload targets

resolveFileInputTarget climbed out of shadow roots with `root instanceof ShadowRoot`.
Elements in same-origin iframes belong to the frame's realm, so the check was false
for their shadow roots and the ancestor walk stopped at the shadow boundary, failing
browser_upload_file with "no nearby file input". Use the file's duck-typed
`'host' in root` idiom like every other shadow-host hop.

* fix(chat): keep an activity's completed title off runs while it still waits on the user

Every run of a main-lane activity segment received the segment-wide activity,
including its completedTitle. A finished multi-call run before a pending
approval or terminal handoff (which splits the segment into runs) therefore
read the past-tense completed title while the activity was still unfinished.

Runs now receive the completed title only once every call in the segment has
finished; until then they keep the activity's in-progress title and summarize
their own calls. Fully finished activities render exactly as before.

* fix(search): keep partial coverage through persisted search compaction

compactRetrievalCitations kept only citations, dropping data.retrieval, and
stripToolResultOutput applies it on both save and load. A reloaded timed-out
search with no matches therefore rendered "No results", which the live UI and
the tool itself deliberately avoid because partial results cannot establish
absence.

Compaction now keeps a bounded retrieval: { status: 'partial' } marker (never
the full retrieval object), and re-compacting compacted output keeps it.
Complete searches compact exactly as before.

* fix(organization): route chat URLs the same way as organization Home

The chat page gated on (mothershipAvailable || memberScoped) while
OrganizationHome renders nothing without copilot.use, or without Build and
Search. A viewer with copilot.use but neither Build nor Search, and a
Search-only viewer opening an assistant chat, got a blank page. Apply
Home's two redirects (Search when copilot.use is denied, workspace
settings when neither Build nor Search is allowed) before loading the
chat, through one getOrganizationHomeRedirect shared by both pages so
they cannot drift again; the later copilot.use redirect becomes
unreachable and is removed.

* fix(organization): keep a custom date range when Home restores Search results

organizationHomeParsers already reads from/to, but OrganizationHomeContent
passed only source and updated to searchFiltersFromParams, so a Home URL
with updated=custom restored an unbounded search. Pass from/to through,
matching the workspace results view, and add them to the effect deps.

* fix(chat): project desktop tabs in organization Assistant chats

Organization Home passed projectsDesktopTabs: false to useChat in Assistant
mode, which only nulls the native active-tab ids used for the strip's
fallback. ChatResourcePanel still projects the chat scope's browser and
terminal tabs in every mode, and chat link clicks in the desktop app open
those tabs through the same projection, so Assistant chats showed desktop
tabs while ignoring which one the desktop app remembers (the #7793 reopen
behavior). Build Home's options with getMothershipUseChatOptions, like
workspace Home, so tabs project consistently in every mode. Assistant
requests still attach no resources.

* fix(home): make the chat/resource panel divider keyboard-adjustable

The resource panel divider (moved into the shared ChatPanelLayout this release,
identical to main's home.tsx) was pointer-only: a separator with no tabIndex,
key handling, or aria-value*, so keyboard users could not focus or resize it.

Mirror the file text-editor split: the divider is now a focusable separator
with ArrowLeft/ArrowRight steps and Home/End. Both separators now read keys
through one readSeparatorKey helper (modifier and IME guard included). Keyboard widths go through the same MIN/max clamps as the drag
(keyboardPanelWidth next to panelWidthAt), land without the width transition
like the window-resize clamp (shared writeWidthInstantly), are ignored during a
live drag, and claim the resource view like a pointer resize. aria-valuenow/min/
max are written imperatively on focus, key, and drag end, preserving the hook's
zero-render resize design. Focus-visible outline matches the text-editor split.

* docs(rules): scope member avatar aria-hidden to rows that show the name

The settings-pages rule said every member avatar renders aria-hidden because
the name is always beside it. MemberRow shows only the email, so its avatar
correctly stays labelled (role=img, aria-label=name), matching the emcn Avatar
TSDoc. Reword the rule and regenerate the Cursor projection.

* docs(skills): clarify service-mode selector guidance in add-connector

The skill said service mode requires shared selectors, but GitLab (service
mode only) uses plain host/project inputs. Align with the live-search README:
verification is mandatory; shared selectors apply only to resource pickers.

* fix(home): keep a focused divider's value current through window resizes

The window-resize clamp updated the panel width but not the focused
divider's aria-valuemax/valuenow, so assistive tech kept the old bounds
until the divider was focused again. The clamp now also reports to the
divider while it holds focus, including when the pinned width stays
within the new bounds.

This branch was previously deployed

1 inactive deployment
Preview — 776b2762 Deployed Sep 13, 2026 by vercel[bot]
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