Skip to content

refactor(web-shell): simplify remote workspace flow - #12264

Merged
yiliang114 merged 1 commit into
mainfrom
codex/simplify-remote-workspace-flow
Sep 19, 2026
Merged

yiliang114 merged 1 commit into
mainfrom
codex/simplify-remote-workspace-flow

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

This follow-up simplifies the remote workspace interaction introduced by #12085 without changing the normal Add workspace entry. The directory browser still opens directly for local users, while previously verified computers remain available through the Folder source selector. Remote-add and connection-add navigation now share one explicit continuation intent, ordinary daemon links stay tab-scoped instead of silently becoming saved connections, and remote folders keep their globe marker without a duplicate host label above the workspace list.

Why it's needed

The normal local workflow should remain unsurprising for users who never use remote computers, and persistent remote connections should reflect an explicit user action. The previous implementation also carried parallel flags and duplicate host chrome that were no longer needed once source selection moved into the directory browser.

Reviewer Test Plan

How to verify

  1. With no saved remote computer, click Add workspace and confirm the directory browser opens immediately on This computer without a Local/Remote choice dialog.
  2. Add a computer from Settings > Connections, return to Add workspace, switch Folder source to that computer, choose a remote directory, and confirm the workspace is registered on the selected daemon.
  3. Open an ordinary daemon URL, complete its connection gate, and confirm the daemon remains usable in the current tab but is not added to the persistent Connections catalog.
  4. Submit a daemon from Settings > Connections or Daemon Status and confirm the validated origin is saved.
  5. Confirm remote workspace rows retain the folder-and-globe marker and the Projects header no longer repeats the daemon host.

Evidence (Before & After)

Before: every successful cross-origin gate probe could save the daemon, and the sidebar showed both a host chip and per-folder remote markers. After: only explicit connection forms save the daemon, the host chip is removed, and Add workspace keeps the direct directory-browser interaction with Folder source selection. The dedicated browser suite passed all 6 remote-workspace scenarios, including local default behavior, remote switching, cancellation, source switching, and the no-connection path.

Tested on

OS Status
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local Web Shell with mocked local and remote daemons in Chromium, plus focused unit tests.

Risk & Scope

  • Main risk or tradeoff: remote continuation state crosses a full-page daemon navigation, so this PR keeps that state tab-scoped and one-shot and verifies return/cancel behavior end to end.
  • Not validated / out of scope: Windows and Linux browser runs; desktop integration, managed SSH, daemon discovery, and multi-daemon aggregation remain outside this Web Shell milestone.
  • Breaking changes / migration notes: none.

Design documents: English design · 中文设计

Linked Issues

Follow-up to #12085 for the Web Shell milestone of #11475.

中文说明

本 PR 做了什么

这是 #12085 的后续简化,不改变普通的“添加工作区”入口。本地用户仍会直接进入目录浏览器,已验证的远程计算机则继续通过“目录来源”选择器提供。远程工作区添加和连接添加现在共用一个明确的续接意图;普通 daemon 链接只在当前标签页生效,不再被静默保存为持久连接;远程目录继续保留地球标记,同时移除工作区列表上方重复的主机标签。

为什么需要

对于从不使用远程计算机的用户,普通本地流程应保持直观;持久化远程连接也应来自用户的明确操作。目录来源已经收进目录浏览器后,原实现中的并行标记和重复主机展示都不再需要。

Reviewer Test Plan

如何验证

  1. 没有保存远程计算机时,点击“添加工作区”,确认目录浏览器直接以“这台计算机”打开,不出现“本地/远程”选择对话框。
  2. 从“设置 > 连接”添加一台计算机,回到“添加工作区”,把“目录来源”切换到该计算机,选择远程目录,并确认工作区注册到所选 daemon。
  3. 直接打开一个普通 daemon URL,完成连接页验证,确认该 daemon 可在当前标签页使用,但不会加入持久化连接目录。
  4. 从“设置 > 连接”或 Daemon 状态提交 daemon,确认验证后的 origin 会被保存。
  5. 确认远程工作区行仍显示文件夹加地球标记,并且“项目”标题不再重复显示 daemon 主机。

证据(前后对比)

之前:任何跨 origin 连接页探测成功都可能保存 daemon,侧边栏同时显示主机标签和每个目录的远程标记。之后:只有显式连接表单会保存 daemon;移除主机标签;“添加工作区”继续直接进入目录浏览,并通过“目录来源”切换机器。专用浏览器测试的 6 个远程工作区场景全部通过,覆盖本地默认行为、远程切换、取消、来源切换和无远程连接路径。

测试平台

系统 状态
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

本地 Web Shell、Chromium 中模拟的本地和远程 daemon,以及专项单元测试。

风险与范围

  • 主要风险或权衡:远程续接状态需要跨越一次完整页面导航,因此本 PR 保持它仅限当前标签页、只使用一次,并通过端到端测试验证返回和取消行为。
  • 未验证 / 范围外:未在 Windows 和 Linux 运行浏览器测试;桌面端集成、托管 SSH、daemon 发现和多 daemon 聚合仍不属于当前 Web Shell 里程碑。
  • 破坏性变更 / 迁移说明:无。

设计文档:English design · 中文设计

关联问题

这是 #12085 的后续,属于 #11475 的 Web Shell 里程碑。

@yiliang114

Copy link
Copy Markdown
Collaborator Author

E2E test report

✅ Dedicated Chromium remote-workspace flow passed: 6/6 scenarios.

Covered behaviors:

  • Settings adds and persists a verified computer, then returns to Connections.
  • Add workspace opens the directory browser directly and defaults to This computer.
  • Folder source switches to a verified remote computer and lists only that daemon's directories.
  • Choosing a remote folder registers it on the selected daemon, not the source daemon.
  • Cancel returns to the exact source tab, and Folder source can switch back to This computer.
  • With no connected computer, Add workspace opens directly without a Local/Remote choice.

Also passed locally: root build; Web Shell typecheck, lint, and format check; 1,270 focused unit tests.

@qqqys

qqqys commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

E2E test report — independent execution at 3fdc8e473ec6

The triage review above recorded that the author's 6/6 Chromium run was "the author's claim, not independently re-run here", and named two verification gaps it could not close from a static read. I re-ran the suite myself in an isolated worktree at this exact commit, with a base arm and a mutation arm. No Critical found; on this evidence the PR is mergeable. I am not approving in this pass — this is the first independent report on the PR, so the approval decision is left to a re-read at an unmoved head.

Arm choice

A tmux TUI arm cannot reach this diff: all 14 non-docs files are under packages/web-shell/client/**, which is a browser bundle the terminal shell never mounts. The substitute is the suite the PR already ships — client/e2e/web-shell.remote-workspace-add.spec.ts, unchanged by this PR (git diff --stat <base>...<head> -- packages/web-shell/client/e2e/ → 0 files), which is itself a static confirmation of the review's "the suite is unaffected" reading.

Matched base/head A/B

Both arms on the same port, same host, same hardlinked dependency tree, one minute apart:

arm commit result
base ef16196c08 6 passed (58.7s)
head 3fdc8e473e 6 passed (56.6s)

Unit arm over the six test files this PR changes: Test Files 6 passed (6), Tests 262 passed (262).

Mutation witness (the arm that makes the green mean something)

A green browser run alone proves nothing, so I re-added the one line this PR deletes — rememberRemoteConnection(baseUrl) after confirmDaemonTarget(baseUrl) in StandaloneAuth.tsx, plus its import:

Test Files  1 failed (1)
     Tests  1 failed | 47 passed (48)
   × retries with the typed token, stores it per tab, and creates no connection
     131|   expect(localStorage.getItem('qwen-remote-connections')).toBeNull();

It fails on exactly the headline assertion, and only on it. Restoration was witnessed three ways rather than assumed: git diff --exit-code <head-ref> -- packages/web-shell → rc=0, git status --porcelain -- packages/web-shell → 0 lines, grep -c rememberRemoteConnection StandaloneAuth.tsx → 2 before / 0 after, and a re-run → 48 passed (48).

So "an ordinary daemon link no longer writes to the persistent connection catalog" is now independently pinned, not just asserted.

Dangling-reference census (the real risk surface of a −129-line diff)

Grepped across the whole head tree, not just the patch, because a diff against the merge base cannot show a collision main contributed:

removed symbol remaining references at head
getRemoteWorkspaceAddStep 0
RemoteWorkspaceAddStep (the type) 0 (7 substring hits are all clearRemoteWorkspaceAddStep)
sidebar.workspacesOnHost 0
continueRemoteWorkspaceAdd / continueRemoteConnectionAdd 0 / 0
.projectsHeaderRemote 0
data-testid="remote-workspace-indicator" 0 — no test or spec queries the removed node
GlobeIcon, formatOriginHost in WebShellSidebar.tsx 0 / 0; isPageOriginDaemon retains 4 uses, so its import stays live

Locale parity holds: i18n.tsx declares exactly two Messages objects (EN at 18, ZH at 3998) and the key is dropped from both. Globe2Icon, the per-row badge in WorkspaceSection.tsx:25,106, is untouched — so "remote folders keep their globe marker" is true at the code level and is now also pinned at DOM level by marks each remote workspace folder / leaves local workspace folders unmarked.

No dead switch: main.tsx:464 renders <StandaloneAuth> without an onChangeTarget, so the default at StandaloneAuth.tsx:160 (onChangeTarget = navigateToDaemon) is the migrated function; StandaloneContext.Provider value={true} makes standalone a boolean, so standalone && isRemoteWorkspaceAddActive() is type-sound and behaviourally identical to the old standalone ? getRemoteWorkspaceAddStep() : undefined compared against 'browse'.

The two gaps the review named

"No test pins the fresh-tab re-prompt" — half closed, and the half that is open is not this PR's to close. main.tsx:40-43, which composes UNCONFIRMED_DAEMON_TARGET from !isKnownDaemonTarget(...) and !isRemoteConnectionKnown(...), is byte-identical at base and head (same line numbers, same text). So the fresh-tab re-prompt is caused solely by the catalog no longer being written on a gate pass — it is the intended change, and it is strictly more conservative on the trust axis. The catalog half is pinned and mutation-witnessed above; the main.tsx composition remains untested, but it is unchanged here, so that gap pre-dates this diff.

"Nothing witnesses the chip removal visually" — still true, and I could not close it either. What I can add is that the gap is narrower than it reads: the removal has a DOM-level witness in the two retained sidebar tests, and the visuals suite reporting "no screenshot changed" means no scenario renders a sidebar driven by a cross-origin daemon. That is a coverage gap in the visuals scenarios, not a defect in this diff.

CI at this commit

Lane census complete and per-lane, not read off the rollup: total_count=92 fetched=92 MATCH=True, 37 distinct lane names, zero non-green, zero incomplete. All six product lanes success — Test (ubuntu-latest, Node 22.x), Lint & Static (ubuntu-latest, Node 22.x), Integration Tests (no-AK, No Sandbox), Desktop Shell (ubuntu-22.04), Desktop Shell (windows-2022), web-shell E2E Smoke (ubuntu-latest, Node 22.x). The skipped rows (Test (macos-latest), Test (windows-latest), Integration Tests (CLI)) are event-type skips present on every PR; they are counted as their own bucket here and are not folded into "green".

One non-blocking note on the spec, not on this diff

web-shell.remote-workspace-add.spec.ts:15 hardcodes const REMOTE_ORIGIN = 'http://127.0.0.1:5199', and its own doc comment says any origin works "as long as it differs from the page origin". Running the suite with PLAYWRIGHT_PORT=5199 therefore makes the page origin collide with the mock-daemon origin and fails 5 of 6 scenarios — I hit this myself and it cost a wasted arm before I read line 15. Deriving REMOTE_ORIGIN from the configured port (or asserting the two differ in a beforeAll) would make the suite portable. This is a test-infrastructure robustness point only; it does not affect the PR under review, whose own CI runs on the default port.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving on a re-read at an unmoved head, cashing the deferral our own e2e report recorded ("the approval decision is left to a re-read at an unmoved head").

State at the moment of this write (re-enumerated immediately before it, not carried over from the report):

  • Head is still 3fdc8e473ec6 — unmoved since the independent run, so nothing below is stale against a newer tree.
  • CI census complete and per-lane, not read off the rollup: total_count=92 fetched=92 MATCH=True, all six product lanes success (Test (ubuntu-latest, Node 22.x), Lint & Static (ubuntu-latest, Node 22.x), Integration Tests (no-AK, No Sandbox), Desktop Shell (ubuntu-22.04), Desktop Shell (windows-2022), web-shell E2E Smoke (ubuntu-latest, Node 22.x)), zero non-green lanes and zero lanes still incomplete. The skipped rows are event-type skips present on every PR and are counted as their own bucket, not folded into "green".
  • Zero [Critical] findings at this head on both surfaces — the inline comment thread and the review bodies — so the approval is not sitting on top of an unaddressed defect.
  • qwen-code-ci-bot APPROVED at this head (16:03:01Z), which post-dates the head commit's own committer date (15:12:25Z), so it is an approval of this tree rather than one re-anchored onto it.
  • No CHANGES_REQUESTED row from anyone at any commit, and the PR is not a draft.

The independent verification behind this is in the report comment above: the shipped Playwright suite re-run in an isolated worktree at this exact commit, with a base arm and a mutation arm, which is what closed the two gaps the static triage could not.

One non-blocking note, already recorded in the report and repeated here only so it is not lost: web-shell.remote-workspace-add.spec.ts:15 hardcodes REMOTE_ORIGIN = 'http://127.0.0.1:5199', so running the suite with PLAYWRIGHT_PORT=5199 collides the page origin with the mock-daemon origin and fails 5 of 6 scenarios. That is test-infrastructure portability, it pre-dates this diff, and it does not affect CI here.

@yiliang114
yiliang114 added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit c1c00cb Sep 19, 2026
110 of 111 checks 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.

2 participants