Skip to content

Fix missing Terminals tab while Cloud machine connects - #17326

Merged
austinywang merged 2 commits into
mainfrom
issue-cloud-loading-terminals
Oct 5, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-cloud-loading-terminals

Conversation

@austinywang

@austinywang austinywang commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Changelog

Fixed: keep the Terminals tab visible while a new Cloud machine is connecting.

Problem

A connecting machine with no terminal snapshot omitted its Terminals group, leaving the sidebar with Ports, Displays, and Resources only.

Change

The tree builder now includes the Terminals group during .connecting, so the tab remains available and shows No terminals yet until the machine reports its terminals.

Validation

  • Regression test commit: c060419efb5 (test: keep cloud terminals tab visible while connecting)
  • Fix commit: 8909eeb9c6f (fix: show cloud terminals tab while connecting)
  • git diff --check passed.
  • Local app-host test execution is prohibited by the repository policy; compile admission/build will run through the fleet.

Callsign registration timed out, so this PR is signed unregistered.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes the Cloud sidebar so the Terminals tab stays visible while a machine is connecting, instead of disappearing until the machine reports its first terminal snapshot.

  • The tree builder now includes the Terminals group during the .connecting state, showing "No terminals yet" as a placeholder.
  • Adds a regression test covering the connecting state.

Written for commit 8909eeb. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Cloud machines now show their Terminals group while connecting, including a “No terminals yet” placeholder when no terminals are available.

@cursor

cursor Bot commented Oct 5, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (3)
.github/review-bot-rules/test-determinism.md — configured
.github/review-bot-rules/swift-architectural-rethink.md — configured
.github/review-bot-rules/source-control-artifacts.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: dab4ad91-5630-43a5-ba6a-6ce5f71a2ab8
📥 Commits

Reviewing files that changed from the base of the PR and between 9aefea4 and 8909eeb.

📒 Files selected for processing (2)
  • Sources/Cloud/CloudTreeNode.swift
  • cmuxTests/CloudSidebarSurfaceRegressionTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

Connecting cloud machines with available machine info now show a Terminals group. A regression test checks that a non-desktop machine’s group contains one “No terminals yet” placeholder.

Changes

Cloud terminals visibility

Layer / File(s) Summary
Show terminals while connecting
Sources/Cloud/CloudTreeNode.swift, cmuxTests/CloudSidebarSurfaceRegressionTests.swift
The cloud tree includes the Terminals group while a machine is connecting. The regression test verifies that the group contains exactly one “No terminals yet” placeholder.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: teamleaderleo

Merge Risk: ⚪ Minimal · up to 8909e

Connecting cloud machines can show the Terminals tab before terminals arrive, with a regression test for the empty state. No actionable merge-blocking risk is evident.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: keeping the Terminals tab visible while a Cloud machine connects.
Description check ✅ Passed The description explains the problem, change, regression test, and validation status. It omits the template’s demo video section and checklist, and reports that app-host tests have not run locally.
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.
Cmux Cloud Persistent Session And Early Input ✅ Passed The diff changes only when CloudTreeNodeBuilder adds the Terminals group and adds a regression test for its empty placeholder. terminalsGroupNode builds presentation nodes and the “No terminals ye…
Cmux Swift Actor Isolation ✅ Passed The production diff only adds .connecting to the condition that appends the Terminals group in CloudTreeNodeBuilder.cloudChildren (line 891). It adds no model, service protocol, shared mutable typ…
Cmux Swift Blocking Runtime ✅ Passed The production Swift diff only adds .connecting to the condition that includes the Terminals group. It adds no blocking or timing-based synchronization. The test diff adds deterministic assertions a…
Cmux Browser Automation Off-Main ✅ Passed The diff changes only Sources/Cloud/CloudTreeNode.swift and cmuxTests/CloudSidebarSurfaceRegressionTests.swift. The browser automation rule applies to Sources/TerminalController.swift and `Contr…
Cmux Expensive Synchronous Load ✅ Passed The production diff only adds .connecting to the condition that appends the Terminals group. cloudChildren filters the already supplied resources before that condition, and terminalsGroupNode on…
Cmux Cache Substitution Correctness ✅ Passed The production diff only adds .connecting to the condition that displays the Terminals UI group. The group still uses the existing resources collection, which cloudChildren filters for terminals…
Cmux No Hacky Sleeps ✅ Passed The custom check applies to production changes in TypeScript, JavaScript, shell, and build/runtime scripts. The diff changes Swift files only: a condition in Sources/Cloud/CloudTreeNode.swift and a …
Cmux Algorithmic Complexity ✅ Passed The change adds .connecting to the Terminals-group condition. For connecting machines with terminals, the group already appeared through !terminals.isEmpty; the new path only builds a group for an…
Cmux Swift Concurrency ✅ Passed The diff adds only a .connecting condition to the Terminals-group visibility check and a synchronous regression test. It adds no Dispatch queue, Combine state, completion-handler API, or fire-and-fo…
Cmux Swift @Concurrent ✅ Passed The diff changes a synchronous condition in CloudTreeNodeBuilder.cloudChildren and adds a synchronous regression test. Neither changed function is async or uses @concurrent; the patch adds no as…
Cmux Swift Package Boundaries ✅ Passed The production diff changes one condition in CloudTreeNodeBuilder so the sidebar includes its Terminals UI group while a machine connects. The group builder assembles outline rows and an empty-state…
Cmux Swiftpm Lockfiles ✅ Passed The diff changes only Sources/Cloud/CloudTreeNode.swift and cmuxTests/CloudSidebarSurfaceRegressionTests.swift. It changes cloud tree behavior and adds a regression test. It does not change a Swif…
Cmux Swift Logging ✅ Passed The diff adds only a .connecting condition to the Terminals-group check in Sources/Cloud/CloudTreeNode.swift and adds a regression test in cmuxTests/CloudSidebarSurfaceRegressionTests.swift. It …
Cmux User-Facing Error Privacy ✅ Passed The production diff only adds .connecting to the condition that creates the terminals pool. The app presents that pool through CloudTreeMachineDetailLayout as a Terminals tab in the sidebar. Its e…
Cmux Full Internationalization ✅ Passed The production diff only adds .connecting to the condition that displays the Terminals group. The existing No terminals yet placeholder uses String(localized:defaultValue:), and its `Resources/L…
Cmux Swiftui State Layout ✅ Passed The diff does not introduce a SwiftUI state or layout pattern covered by the check. It changes a condition in CloudTreeNodeBuilder.cloudChildren to include the terminals group while connecting (Sour…
Cmux Architecture Rethink ✅ Passed The diff makes a local visibility correction in CloudTreeNodeBuilder.cloudChildren: it adds .connecting to the existing condition that decides whether to add the Terminals group. The group remains…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR changes only the cloud tree builder’s Terminals-group condition and adds a regression test. It does not add or materially change an NSWindow, NSPanel, NSWindowController, SwiftUI Window, or Win…
Cmux Source Artifacts ✅ Passed The diff changes only Sources/Cloud/CloudTreeNode.swift and cmuxTests/CloudSidebarSurfaceRegressionTests.swift. These are intentional product source and test code, which the source-control-artifac…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS. The only production-source change is in Sources/Cloud/CloudTreeNode.swift: it adds .connecting to the condition that includes the Terminals group. It adds no test/debug accessor, guarded ext…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@austinywang
austinywang merged commit c9bdbd6 into main Oct 5, 2026
78 of 79 checks passed
@austinywang
austinywang deleted the issue-cloud-loading-terminals branch October 5, 2026 02:44
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Merge receipt for 8909eeb9c6: every check was green at merge (16 verified; 20 skipped by policy). Full suite runs on main after merge.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Dogfood tours of 8909eeb9

cloud-machine-author-tour at 8909eeb9, on its merge ded5b328 that CI built: failure (run)

Failed: DogfoodScenarioUITests.swift:115: failed - Dogfood steps failed:

cloud-machine-author-tour at 8909eeb9

Key frames of cloud-machine-author-tour at 8909eeb 02-failed 04-10-right-sidebar-cloud 09-failed 15-30-lab-aero

Tours are picked by the paths globs in dogfood/scenarios/*.json; a Dogfood-tours: a, b line in the description picks them instead (none turns this off). Look at every frame before merging: a green tour only means no step failed.

rustybret pushed a commit to rustybret/bmux that referenced this pull request Oct 5, 2026
7997c55 fix(cloud): update machine rename optimistically (manaflow-ai#17324)
c9bdbd6 Fix missing Terminals tab while Cloud machine connects (manaflow-ai#17326)
b047fa3 Fix Agent Hibernation never selecting live Claude Code sessions (manaflow-ai#17306)
9aefea4 cloud sidebar polish: header refresh, tab switch, empty states, errors and upgrade (manaflow-ai#17074)

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci.yml
austinywang added a commit that referenced this pull request Oct 5, 2026
A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Oct 5, 2026
* test(cloud): display health checks must not black-list websockify viewers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): stop Xvnc black-listing every viewer of additional displays

Xvnc black-lists a peer after five unauthenticated connections, for a
timeout that doubles each time. Every viewer of an additional display is
websockify on 127.0.0.1, and the helper's 30s health check read the RFB
greeting and hung up, so displays soon answered every connection with
"Too many security failures". Restored panes and quiet retries then
failed for minutes.

Start additional displays with -UseBlacklist=0 (SecurityTypes None behind
the private network gives it nothing to protect; Xvnc refuses runtime
changes), complete the None handshake in the health check so it clears
marks instead of adding them, and replace recovery's pgrep patterns, which
pgrep's regex dialect rejected, with exact argv matching.

Each helper request also runs in a plain shell now: the login shell cost
about 0.7s per call and only the long-lived service needs its environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): New Display hands over a running standby display

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(cloud): keep one standby display running per desktop VM

Each New Display started Xvnc, a desktop session and websockify on the
guest (about 0.7s) while the user waited. The guest helper now keeps one
display running outside the catalog, hands it over on create (about 0.1s)
and starts the next in the background. The standby is invisible to list,
does not count against capacity, and a restarted helper adopts it instead
of leaking it. Opening a machine's Displays tab, or restoring it selected,
runs guest discovery once so the standby is warm before the first click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): failed or launching standby displays are never misassigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): serialize standby handover; retire failed standbys; rediscover after wake

Concurrent creates could record a launching standby's number as a normal
display; take the standby under one lock and reserve running numbers. A
standby whose start failed is stopped and replaced instead of handed over
or retried forever. Displays-tab discovery runs once per open and retries
when a sleeping machine wakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): align two sidebar tests with merged behavior

A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): standby never takes a recorded number; failed discovery retries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): address review: standby race and Displays discovery retry

ensure_standby's locked check now also rejects a number a concurrent
create recorded after the unlocked probe, so a display can never be both
a catalog display and the standby.

Displays-tab discovery reports completion; a failed discovery clears the
request and retries, bounded to three attempts per shown machine, instead
of treating task launch as done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): connecting machines keep only the Resources tab (#17139)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a stale Displays discovery cannot retry after the tab reopens

Drive discovery completions explicitly instead of yielding a fixed number of times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): bind each Displays discovery completion to its request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Oct 5, 2026
…#17327)

* test(cloud): display health checks must not black-list websockify viewers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): stop Xvnc black-listing every viewer of additional displays

Xvnc black-lists a peer after five unauthenticated connections, for a
timeout that doubles each time. Every viewer of an additional display is
websockify on 127.0.0.1, and the helper's 30s health check read the RFB
greeting and hung up, so displays soon answered every connection with
"Too many security failures". Restored panes and quiet retries then
failed for minutes.

Start additional displays with -UseBlacklist=0 (SecurityTypes None behind
the private network gives it nothing to protect; Xvnc refuses runtime
changes), complete the None handshake in the health check so it clears
marks instead of adding them, and replace recovery's pgrep patterns, which
pgrep's regex dialect rejected, with exact argv matching.

Each helper request also runs in a plain shell now: the login shell cost
about 0.7s per call and only the long-lived service needs its environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): New Display hands over a running standby display

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(cloud): keep one standby display running per desktop VM

Each New Display started Xvnc, a desktop session and websockify on the
guest (about 0.7s) while the user waited. The guest helper now keeps one
display running outside the catalog, hands it over on create (about 0.1s)
and starts the next in the background. The standby is invisible to list,
does not count against capacity, and a restarted helper adopts it instead
of leaking it. Opening a machine's Displays tab, or restoring it selected,
runs guest discovery once so the standby is warm before the first click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): failed or launching standby displays are never misassigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): serialize standby handover; retire failed standbys; rediscover after wake

Concurrent creates could record a launching standby's number as a normal
display; take the standby under one lock and reserve running numbers. A
standby whose start failed is stopped and replaced instead of handed over
or retried forever. Displays-tab discovery runs once per open and retries
when a sleeping machine wakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a closed display view is not rebuilt from a stale graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): load every workspace display and keep closed displays closed

Displays beyond the first in a Cloud workspace layout appeared late: the
catalog only learns display:N from guest discovery and drops memberships
for displays it does not know, and nothing on open or restore ran
discovery. Publishing a graph whose memberships name unknown displays now
runs discovery once per lifecycle generation.

Closed display panes came back: the membership removal is queued while
reconciliation still reads the graph holding the token, so it rebuilt the
pane under a new panel id and left an orphaned token that resurrected it
after every close. Closing now fences the view until a fetched graph drops
its token, retries a removal that has not landed, and a pane rebuilt from
an orphaned token removes that token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): discover member displays on deltas; bound and scope token cleanup

Membership rows arrive as projection deltas, so discovery also runs from
publishDelta. Orphan cleanup skips other Macs' tokens (a no-op write that
still cost a guest snapshot), removal retries stop after three attempts,
and the client id is read once per close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): prune this Mac's orphaned display memberships in open workspaces

A membership token whose pane is gone (an old close whose removal never
landed, a crash, a pane rebuilt under a new id) showed as a duplicate
display row and resurrected the display after it was closed; the
reconciler treats one live pane as satisfying every token for that
display, so nothing removed it. After reconciling an open Cloud
workspace, remove this Mac's tokens with no live display pane, once the
machine's restore has settled. Tokens of workspaces not open here, and of
other Macs, are kept as their layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): keep display discovery results across machine-list polls

refreshDisplays dropped its result whenever refreshGeneration changed, and
every machine-list poll bumps it, so a ~2s guest discovery that overlapped
a poll was discarded: workspace displays loaded only after a later lucky
discovery. Check the lifecycle generation only; the coordinator already
invalidates on identity or image changes. Member-display discovery also
re-checks after each attempt (bounded to three per lifecycle) because a
launch-time refresh can cancel one, verified live: the first attempt's
exec was cancelled after 1ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): member display listed once; silent desktop probe is not unreachable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): list a workspace display once; stop repairing healthy desktops

The sidebar listed a workspace's display twice: cloudWorkspaceResources
appends a copy of a member display carrying its membership view, and
localWorkspaceMembers deduped against the first copy (the pool resource,
without the view), so the local pane added the display again.

Display 1 sat on "Loading Cloud page" for 20s+: the desktop probe's 2s
deadline never cancelled its connection, so a busy carrier held it to the
proxy's 10s header timeout, and the timeout read as unreachable and ran
the control plane's desktop repair (a guest exec, ~12s) on a healthy
desktop. The deadline now cancels the connection, and repair runs only
when the proxy or service answered with an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a New Display pane and its membership are one sidebar row

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): build workspace rows and local-pane rows from one resource list

The sidebar built workspace rows from cloudWorkspaceResources (which holds
the membership copy of a member display) but deduped local panes against
the pool resources alone, so a New Display pane listed its display twice.
Both passes now read the same list.

Adds debug.cloudtree.rows (DEBUG only), returning the Cloud sidebar's rows
from the same builder, so dogfood can assert listed rows; duplicate rows
were invisible to cloud tree --json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): display names parse from the daemon graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cloud): named displays, display tab titles, remove a display from a workspace

Display panes showed noVNC's page title, and renaming a workspace display
sent a synthetic view id to the daemon's tab rename, which cannot work.
Displays now have one shared name per display, stored as a frontend
projection beside the workspace memberships so every client follows a
rename live. Rows and pane tabs show it ("Display N" until renamed);
renaming the tab or the row renames the display; clearing restores the
number.

Workspace display rows get a hover X and a "Remove from Workspace" menu
item, which close the display's pane on this Mac (the fenced membership
removal) or remove this Mac's membership when no pane is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): list debug.cloudtree.rows as a debug method; fold display names into the membership type

The socket capability guard requires debug methods to be listed as
intentionally unadvertised, and the package conventions lint rejects an
all-static enum, so the display-name constants move onto
CloudVMDisplayMembership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a display row's Rename names the display

The menu now routes display renames to renameDisplay (one name per
display, shared by every row and pane) instead of a view's tab rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): align two sidebar tests with merged behavior

A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): the desktop placeholder is titled Display 1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): title display panes from the click, never Desktop first

The display 1 placeholder (shown before guest discovery) was titled
"Desktop" and renamed "Display 1" seconds later; it now uses the same
numbered title as discovery. A new display's pane is titled "Starting
display…" from the click instead of "New tab", then takes the
display's name when it materializes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): standby never takes a recorded number; failed discovery retries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): address review: standby race and Displays discovery retry

ensure_standby's locked check now also rejects a number a concurrent
create recorded after the unlocked probe, so a display can never be both
a catalog display and the standby.

Displays-tab discovery reports completion; a failed discovery clears the
request and retries, bounded to three attempts per shown machine, instead
of treating task launch as done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): connecting machines keep only the Resources tab (#17139)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a stale Displays discovery cannot retry after the tab reopens

Drive discovery completions explicitly instead of yielding a fixed number of times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): bind each Displays discovery completion to its request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): closing a display pane removes it for every client

Drives the real pane-close path. Another client's token in the same
workspace must not rebuild the pane, including when the close lands before
this pane's own token reaches the graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): closed displays stay closed across clients and instances

- Closing a display pane or clicking X removes the display from that Cloud
  workspace for every client. The fence is keyed by machine, workspace and
  display, set at close from the pane's own workspace, and lifted when this
  Mac opens the display there again.
- Pruning only touches view IDs this process recorded: stable and DEV
  builds on one Mac share a client ID.
- A sleeping machine or an undiscovered display does not spend a removal
  attempt.
- Tab renames run in order, and afterwards every pane shows the display's
  real name, so a cleared or failed rename reverts the tab.
- A failed reserved pane drops its "Starting display…" title.
- A reset or refused connection after the tunnel opens reads as unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a desktop that resets the opened tunnel is unreachable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): read a reset after the desktop tunnel opens as unreachable

The probe's errors arrive as NWConnection.StreamError, so the previous NWError
cast never matched. Only the HEAD stage maps a reset or refusal; before the
tunnel opens the same error belongs to the local proxy and stays unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a queued close cannot undo a reopen; another client's re-add shows again

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): order display removals against reopens; release on another client's re-add

- A reopen bumps the display's generation, so a close or removal retry that
  started before it leaves the reopened display alone.
- Removal retries run on the machine's lane, ordered with the reopen's
  attach; the lane's enqueue is split into a resource-keyed core.
- A removal records the tokens it deleted. A later graph showing any other
  token means another client put the display back, so the fence lifts.
- Attempts are counted per closed display and only while it is fenced.
- A tab rename re-applies the display's name only when it changed nothing or
  failed, so a successful rename no longer flashes the old name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): type the lane failure handler explicitly

The ternary of closures crashed the type checker (failed to produce diagnostic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a no-op display removal still fences older graphs; proxy reset is unknown

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): release a closed display by graph revision, not token sets

A removal now reports the cursor of the graph it was computed from. A graph
no newer than that predates the removal and stays fenced, including when the
removal found nothing to delete. A newer graph that still shows the display
means another client put it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): fence a closed display up to its removal write's own cursor

A graph published between the removal's snapshot read and its write could be
newer than the snapshot yet still hold the removed tokens, releasing the fence
early. The removal now reports the write receipt's cursor (the snapshot's only
for a no-op), and a reply without one keeps the fence until the display is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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