Skip to content

refactor: retire @qwen-code/webui - #9812

Draft
yiliang114 wants to merge 132 commits into
mainfrom
codex/remove-webui
Draft

refactor: retire @qwen-code/webui#9812
yiliang114 wants to merge 132 commits into
mainfrom
codex/remove-webui

Conversation

@yiliang114

@yiliang114 yiliang114 commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This stacked draft implements the physical retirement of the legacy @qwen-code/webui workspace package.

  • Deletes packages/webui and removes its build, release, visual-test, lint, workspace, and lockfile references.
  • Moves exported conversation HTML to the canonical sanitized transcript document and the self-contained Web Shell renderer, with offline light/dark theme support.
  • Removes the old WebUI UMD/CDN export paths and updates the concurrent export helper to consume source ChatRecords.
  • Renames active WebUI-specific documentation and integration fixtures to Web Shell terminology while preserving the legacy webui_ client identifier prefix for persisted-session compatibility.
  • Adds a repository guard that rejects reintroduction of the retired package.

This PR is stacked on #9811, which owns the VS Code adapter cutover. It reuses the hardened document-only export work from #9641 without pulling the VS Code adapter into that already-large PR.

Why it's needed

After #9811, the remaining package was still kept alive by exported HTML, release scripts, CI, documentation, fixtures, and the lockfile. Removing only the chat components would leave a dead workspace package and two export renderers. This PR removes the entire legacy package and leaves Web Shell as the single shared transcript UI implementation.

Reviewer Test Plan

How to verify

  • Export a conversation as HTML, open it offline, and confirm transcript text, thoughts, tool calls, syntax highlighting, file references, partial-export messaging, and the light/dark toggle.
  • Confirm the CLI and server export paths both use the sanitized versioned transcript document.
  • Confirm Web Shell and the VS Code companion have no active dependency on @qwen-code/webui, and the repository contains no packages/webui workspace.
  • Reuse refactor(vscode-ide-companion): migrate chat to WebShell and qwen serve #9811's real VS Code UI matrix as the no-regression check for the companion.

Evidence (Before & After)

Pending the final consolidated verification pass. No intermediate VS Code launches or screenshot passes are being performed while the implementation is still a draft.

Tested on

OS Status
macOS Pending final consolidated verification
Windows CI
Linux CI

Risk & Scope

  • Main risk: exported HTML is a self-contained offline artifact, so the replacement must not depend on a CDN and must keep the hardened document schema and sanitization boundary.
  • Out of scope: packages/desktop/apps/webui is a separately named desktop application and is not deleted; the user-facing qwen --webui command remains because it launches Web Shell.
  • Compatibility: the legacy persisted webui_ client identifier prefix is intentionally retained as wire/storage data, even though active code and documentation now use Web Shell terminology.

Linked Issues

Part of #5883. Stacked on #9811. Reuses the document-only export boundary from #9641.

heyang.why and others added 30 commits August 20, 2026 15:26
Establish a shared transcript model and document-mode projection so Web
Shell, VS Code, and HTML export can consume the same stable conversation
semantics without changing interactive rendering.

- Preserve daemon and ACP segment identity across replay and normalization
- Add export-safe previews and a versioned transcript document builder
- Add document-mode Web Shell rendering with bounded Mermaid processing
- Lock direct-daemon and ACP behavior with contract fixtures
- Cover render and export equivalence in integration tests
Move transcript prevalidation onto real product paths while preserving
the legacy VS Code timeline as the default fallback.

- Add version-bound document HTML with strict CSP and schema checks
- Add opt-in ACP projection with stable source identity
- Reuse product export code across hosts and the integration runner
- Remove test-only production probes and consolidate shared validation

Note: Browser, host-action, VSIX, and packaging gates remain pending.
Keep the npm lockfile aligned with the VS Code and HTML export package
manifests added by the transcript consumer migration.

- Lock the VS Code Web Shell workspace dependency
- Lock Web Templates SDK, Web Shell, and React build dependencies
…plit-20260818

# Conflicts:
#	packages/acp-bridge/src/transcript-replay.ts
#	packages/sdk-typescript/scripts/build.js
#	packages/sdk-typescript/src/daemon/ui/transcript.ts
#	packages/web-shell/client/components/MessageList.tsx
#	packages/web-shell/client/components/messages/GoalStatusMessage.tsx
…imental flag

Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off).

The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged.
Resolve review findings across document export and the VS Code ACP
timeline while preserving default interactive and readonly semantics.

- Preserve stable text and non-text identity across live and replay
- Harden export projection, budgets, URL handling, CSP, and nonces
- Make document rendering complete, inert, and browser-validated
- Keep VS Code transcript scope, theme, copy, and flags reactive
- Restore fixture, schema, hash, and compatibility contract locks

Note: The overall gate remains failed pending VSIX, host-action, and
packaged-artifact evidence.
…eb-shell-adapter-before-split-20260818

# Conflicts:
#	packages/web-shell/client/components/MessageList.tsx
#	packages/web-shell/client/components/messages/AssistantMessage.tsx
#	packages/web-shell/client/components/messages/ToolGroup.tsx
…line

Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting).

The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM.
… switch

The experimental useAcpTranscript hook only consumed transcriptUpdate
messages, so its reducer state survived session boundaries. When the
extension switched sessions it kept the webview mounted and replayed the
newly-selected session through ACP, causing the previous session's blocks
to merge with the new replay (e.g. user text "alpha" from session A leaked
into session B as "alphabeta").

Reset both the reducer state and the rendered blocks on the same
boundaries the legacy message flow uses: qwenSessionSwitched (sent before
the ACP replay of the selected session) and conversationCleared (new
session). Adds a regression test that replays two sessions with a switch
between them.
- reset the transcript on `conversationLoaded` too, closing the same
  cross-session leak the previous commit fixed for `qwenSessionSwitched`
  and `conversationCleared` (agent reconnect posts only this boundary)
- track the active session id and drop late `transcriptUpdate` frames
  whose `sessionId` no longer matches, so a previous session's trailing
  frames cannot contaminate the next session's timeline
- seed the transcript from cached messages carried by
  `qwenSessionSwitched` so offline restores and load-failure fallbacks
  render their history instead of a blank timeline
- dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the
  final assistant/thought block of a turn (or history replay) does not
  stay `streaming: true` forever
Close the latest review findings across transcript identity, VS Code
rehydration, and the HTML export security boundary.

- Terminate discrete ACP segments and bind automatic turn provenance
- Reset VS Code transcript scopes across replay and reconnect lifecycles
- Align Markdown sanitization and envelope budgets with document rendering
- Run browser gates with Chromium and complete third-party notices
…n session-switch seeding guard

Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN).
… dead submit options

The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b).
The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time (bd09e19), so the loadingMessage string was write-only dead state. Keep the isWaitingForResponse flag (submit gating / cancel) and pin its API surface (R1-19 direction b).
Reduce the shared transcript review surface while keeping the VS Code and
HTML Export consumers and their security gates intact.

- Merge VS Code feature state into the ACP transcript hook
- Collect source identity once and strip it at the compatibility boundary
- Remove test-only export adapters, gate reports, and duplicate helpers
- Keep shell output separated when stable producer segments change
- Mark third-party notices as generated review content
…imeline

The prompt carries pasted/attached images as ACP resource_link blocks,
which the transcript reducer cannot render (no inline data), so user
images vanished from the timeline while the attach path stayed alive.
Read each saved prompt image back from disk and echo it alongside the
text echo as an inline user_message_chunk image part (the daemon-echo
content shape), which the shared reducer folds into the user block and
the WebShell renderer already displays. Unreadable images are skipped
without breaking the send.
webShellTheme was snapshotted once at mount via useMemo with an empty
dependency array, so switching the VS Code color theme left the
timeline on the stale theme (VS Code updates data-vscode-theme-kind on
<body> in place without reloading the webview). Hold the theme in state
and refresh it with a MutationObserver on the body theme attributes.
yiliang114 and others added 26 commits August 28, 2026 02:07
The companion now drives Web Shell against a shared `qwen serve` daemon,
so the CLI, the browser Web Shell, and this extension all create sessions
in the same workspace catalog. Web Shell recorded `'default'` for every
surface, leaving VS Code conversations indistinguishable from terminal and
browser ones — the panel's history listed sessions the user never opened
here, and nothing attributed a session back to the editor.

Give Web Shell a `sessionSourceType` prop (defaulting to today's
`'default'`) and have the companion stamp `'vscode'` on the sessions it
creates, then scope the history dropdown to that source. The host also
supplies a stable daemon `clientId`, which the bootstrap previously
declared but never sent.

Web Shell localizes its own surface from the `language` signal while the
companion's chrome was hardcoded English, so a zh-CN panel rendered a
Chinese transcript under an English header, history dropdown, onboarding
screen, and account dialog. Route that chrome through a small string table
driven by the same signal, including the host-only slash entries.

Also fix accessibility defects in the history dropdown: rename and delete
were revealed on hover alone and unreachable by keyboard, date headers sat
inside `role="listbox"` as invalid non-option children, arrow-key roving
stopped at group boundaries, `aria-modal` had no focus trap, and a primed
"Delete?" survived both search changes and the pointer leaving the row.

Formatting: `FileMessageHandler` and `SessionMessageHandler` were left
unformatted earlier in this branch and failed the Prettier gate.
The webview entry now renders EmbeddedApp against the daemon, which left
the ACP-era hook layer unreachable: nothing imports acpTranscriptAdapter,
useWebViewMessages, useAcpTranscript, useToolCalls, useSessionManagement,
useMessageHandling, useFileContext, useImage, or the permissionTypes added
by this branch. A reachability walk from webview/index.tsx reaches eight
modules; every reference to the rest comes from inside the orphaned set
itself, so it deletes as a closed unit.

EmbeddedWebShell goes with them. It was the host-driven entry point from
the earlier stage of this branch, superseded when EmbeddedApp moved to
WebShellWithProviders, and has had no consumer since — only its own DOM
test and a barrel export.

Also harden the daemon process lifecycle. `start()` returned the cached
runtime without comparing the workspace, so in a multi-root window the
second folder's chat silently reused a daemon bound to the first and
scoped every session, history page, and prompt to the wrong root. Bind the
daemon to its workspace and respawn on a change, keep a superseded child's
late exit from tearing down its successor, and report a post-startup exit
to the webview instead of leaving it fetching against a dead port.
…hips

The design doc still recorded the plan this branch started from: keep ACP
as the runtime boundary, add no daemon server or loopback port, and treat
"replacing ACP with daemon HTTP/SSE" as a non-goal. The final stage did
exactly that, so the document argued against the code beneath it.

Record the decision and its consequences instead — two processes per
workspace, a daemon shared with the CLI and browser Web Shell, the vscode
source type that keeps the panel's history its own, workspace rebinding in
multi-root windows, and the turn-driven host features that stopped firing.
…l-cutover

The branch had gone conflicting against main, which stops GitHub from
building the merge ref — so `Qwen Code CI` (triggered on `pull_request`)
had not run since the previous main merge, leaving recent commits with no
test signal at all.

The single conflict was in skills-manager-logic.test.ts: this branch moved
the daemon React import from `@qwen-code/webui/daemon-react-sdk` to
`@qwen-code/web-shell/daemon-react-sdk`, while #10296 on main added
`skillExtensionLabel` to the same import. Kept the branch's module path
and main's new symbol.
…9811)

- closeDiff now resolves workspace-relative paths the same way showDiff
  does, so permission-cycle diffs opened from daemon-relative paths can
  actually be matched and closed
- a superseded or disposed daemon child no longer reports its exit as a
  crash of the live daemon
- authCancelled no longer hides an already-authenticated session behind
  onboarding; only an unknown auth state settles to unauthenticated
- selection-only activeEditorChanged events no longer undo an explicit
  active-file exclusion
- prepareSubmit dedupes mentions in both path spaces and matches typed
  references on a whole-reference boundary
- permission diffs open only from the SDK's authoritative file_diff
  preview (writes included, model-controlled toolCall mining removed)
- the webview HTML carries VS Code's locale so chrome strings localize
- discontinued qwen-oauth models are no longer re-applied through the
  new-session initial-model route
#9811)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The cutover branch dropped the ./daemon-react-sdk export from @qwen-code/webui,
but the TerminalPanel merged in from main still imports it, breaking the
web-shell vite build (Missing "./daemon-react-sdk" specifier). Point the import
and its test mock at @qwen-code/web-shell/daemon-react-sdk, which re-exports the
same useWorkspace hook and matches every other web-shell call site.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Resolve import-path conflicts in favor of the cutover: keep the
@qwen-code/web-shell/daemon-react-sdk entrypoint for SessionOverviewPanel
(adding main's new useWorkspace import), keep the findIndex rationale
comment in App.tsx, and union the lucide icons in UserMessage. Convert
main's new webui daemon-react-sdk imports (LocalControlQrButton,
ChatContextHeader/SettingsMessage tests) to the web-shell entrypoint,
which the cutover leaves as the only provider.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The renderItem useCallback reads hasOlderHistory to gate the edit action
but omitted it from its dependency array, failing CI's
react-hooks/exhaustive-deps gate.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…onError re-render loop (#10454)

* fix(web-shell): report each connection error once to stop the onError re-render loop

While a connection error persists (e.g. the daemon is unreachable), the
error-notification effect re-fires whenever the onError callback identity
changes. Hosts such as the VS Code embedded app pass an inline onError and
update their own state when it fires, so every notification triggers a host
re-render that hands the effect a fresh callback identity — re-notifying the
same persistent error forever (#10406).

Track the last reported connection.error value in a ref and notify only when
the value changes, resetting the tracker once the connection recovers. This
guards every inline-callback consumer, not just memoized hosts.

Fixes #10406

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): only stamp the dedup ref once an onError handler exists

Stamping lastReportedConnectionErrorRef before delivery meant a host
that attaches onError after a persistent connection error appeared never
received it: the no-op delivery already marked the error as reported.
Guard on the handler first and add a regression test covering the
late-attach case (red when the guard is removed).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(web-shell): document the onError dedup contract and fix comment wording

Describe the reported-once-per-distinct-error semantics, the reset on
recovery, and that replacing the handler mid-error does not re-deliver.
Reword the effect and test comments to describe the host class instead
of naming the VS Code embedded app, which passes a useCallback handler.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(vscode): mirror the web-shell value-dedup in the EmbeddedApp mock

The WebShellWithProviders mock re-notified on every onError identity
change, mirroring the loop App.tsx can no longer produce. Rewrite it to
report each distinct error value once (resetting on recovery), keep the
loop guard as a regression tripwire, exercise it with a changing
callback identity plus a post-delivery effect re-run, and refresh the
handleShellError comment that still cited the old loop as the
memoization reason.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(vscode): cast the captured onError prop for the mock wrapper

CapturedProps is an unknown index signature, so the destructured
onError needs the same cast the previous mock applied inline to stay
callable under tsc.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(vscode): bail the EmbeddedApp mock before stamping when no onError exists

The mirrored dedup effect stamped lastReportedError and counted a
notification even when no handler was attached, while App.tsx returns
before stamping on that path. Add the same early return so a handler
attached mid-error still receives the persistent error, and pin the
no-handler no-stamp behavior with a test that fails if the guard is
removed.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Resolve directory-rename conflict: accept main's new daemon session-context
files (#10418) at the renamed packages/web-shell/client path.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…into codex/remove-webui

# Conflicts:
#	package-lock.json
#	packages/vscode-ide-companion/package.json
#	packages/webui/package.json
#	scripts/build.js
@yiliang114
yiliang114 changed the base branch from codex/vscode-web-shell-cutover to main September 3, 2026 06:36
@yiliang114
yiliang114 dismissed qwen-code-ci-bot’s stale review September 3, 2026 06:36

The base branch was changed.

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.

4 participants