mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-20 22:25:30 +00:00
16 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dade3ab334
|
feat(web-shell): add git branch picker, commit dialog, and create PR flow (#7731)
* feat(web-shell): add git branch picker, commit dialog, and create PR flow Add an IntelliJ-style branch picker popover to the web shell git workspace, accessible from the branch chip in both the composer toolbar and sidebar. The picker provides search-filtered branch listing (local, remote, tags, recent), branch checkout, new branch creation, pull, push, and a commit view integrated into the existing GitDialog. The commit view reuses the diff panel (expandable file diffs with syntax highlighting, fullscreen support) and adds a commit message textarea with Commit / Commit and Push buttons. When a session is available (or one is auto-created), the commit message and PR title/body are generated via the model using session side-queries (btwSession), giving the agent full conversation context for accurate generation. The Create PR flow provides an inline form with auto-detected base branch and model-generated title/description, backed by a new daemon route that shells out to gh pr create. New daemon routes: - GET /workspaces/:workspace/git/branches - POST /workspaces/:workspace/git/checkout - POST /workspaces/:workspace/git/branch - POST /workspaces/:workspace/git/push - POST /workspaces/:workspace/git/pull - POST /workspaces/:workspace/git/commit - POST /workspaces/:workspace/github/prs/create - GET /workspaces/:workspace/github/default-branch * fix(web-shell): resolve correct workspace session for AI generation The commit message and PR title/body generation now resolves the most recent session for the target workspace via listWorkspaceSessions, rather than using the globally active connection.sessionId which may belong to a different workspace or session. Falls back to creating a new session only when no sessions exist for the workspace. Also improves the PR body editor with an Edit/Preview toggle using the existing Markdown component, and updates the generation prompt to follow the project PR template structure from AGENTS.md. * fix(web-shell): stabilize session resolver prop to prevent infinite re-generation Pass resolveSessionForWorkspace as a stable useCallback reference instead of an inline arrow function. The inline function created a new reference on every App render, causing the GitDialog useEffect to abort and restart generation in an infinite loop. * fix(web-shell): group remote branches by remote name and add PR target branch dropdown Remote branches in the branch picker are now grouped by remote (origin, upstream, etc.) with sub-headers, making fork workflows clear. The PR create form's base branch field is now a select dropdown populated from the workspace's branch list, grouped by remote with optgroup labels, instead of a free-text input. * fix(web-shell): use ref for session resolver to prevent effect re-run abort When resolveSessionForWorkspace creates a new session, it updates connection.sessionId in the provider, which changes the useCallback reference, which triggers the useEffect to re-run and abort the in-flight btwSession generation. Store the callback in a ref so the effect never depends on its identity. * fix(web-shell): resolve session per workspace, not from global active session The commit/PR generation effects used connection.sessionId directly without checking if it belongs to the target workspace. When opening the commit dialog from a sidebar workspace different from the active session's workspace, the wrong session was used for generation, producing incorrect content. Now always routes through resolveSessionForWorkspace(workspaceCwd) which checks workspace membership before reusing the active session. Also replaces 'PR' with 'Pull Request' / '合并请求' in all UI strings and adds error logging to the generation catch blocks. * fix(web-shell): retry btwSession with fresh session when stale session detected When listWorkspaceSessions returns a session that no longer exists in the daemon's memory (e.g. after daemon restart), btwSession fails with 'No session with id ...'. The generation effects now catch this error, force-create a new session via resolveSessionForWorkspace(cwd, true), and retry the btwSession call once. Both btwWithRetry and resolveSessionForWorkspace are stored in refs to avoid useEffect dependency chain aborts. * fix(web-shell): base PR generation on branch diff, not working tree PR title/body generation now fetches the commit log between the resolved base branch and HEAD (git log <base>..HEAD) plus any uncommitted changes, instead of only the working tree diff. The base branch is resolved inside the effect's promise chain (not from state) to avoid stale values and dependency warnings. Also adds range parameter support to fetchGitLog and workspaceGitLog. * feat(web-shell): replace PR base branch select with searchable popover The native <select> for the PR target branch is replaced with a custom searchable popover (BranchSelect) that shows a search input and a filtered branch list grouped by remote. The default selection is the target repository's main branch (resolved via getDefaultBranch). Supports filtering by typing in the search box. * fix(web-shell): show full remote ref in branch select (origin/main) Branch select now stores and displays full remote refs like origin/main instead of stripped names. getDefaultBranch returns the full ref (origin/main) instead of stripping the prefix. When creating the PR, the remote prefix is stripped for the gh pr create --base flag (origin/main → main). * fix(web-shell): stop pointer propagation in branch picker list to prevent popover dismiss Radix Popover's outside-click detection was incorrectly firing when clicking section headers (Recent/Local/Remote/Tags) inside the popover content, causing the popover to close immediately. Adding onPointerDown stopPropagation on the list container prevents pointer events from reaching Radix's document-level handlers. * fix(web-shell): use onPointerDownOutside guard for branch picker popover The previous stopPropagation approach failed because Radix Popover uses capture-phase document listeners for outside-click detection, which fire before bubble-phase stopPropagation. The correct fix is onPointerDownOutside on PopoverContent: when Radix incorrectly fires the outside handler for a click that is actually inside the content (can happen with portal containers), we check contentRef.contains() and preventDefault to keep the popover open. * fix(web-shell): stop click propagation on branch picker popover content Root cause: the ChatEditor composer container has onClick that calls core.focus(), stealing focus from the popover. React synthetic events bubble through the React tree (not DOM tree), so portaled popover clicks reach the container handler. Radix then detects focus-outside and dismisses the popover. Fix: onClick stopPropagation on PopoverContent, matching the existing pattern in GitModePopover and ToolbarPopover which already have this fix with an explanatory comment. * docs: add PR verification screenshots for branch picker feature * fix(web-shell): mock useWorkspace in tests for BranchPickerPopover BranchPickerPopover calls useWorkspace() which requires DaemonWorkspaceProvider context. The existing WorkspaceSection and ChatEditor tests didn't provide this context, causing 5 test failures. Added vi.mock with importActual to preserve other exports while providing a mock useWorkspace. Also updated the git chip click test to reflect that clicking now opens the branch picker popover instead of directly calling onOpenGitDiff. * fix: harden git write paths against argument injection Address review feedback on the web-shell git surface: - Reject option/pathspec injection in git checkout ref and branch start point (isValidCheckoutRef), and terminate `git checkout` argv with `--`. - Drop `git log` range values that start with `-` and terminate the argv with `--` so a range can never be reinterpreted as `--output=<file>`. - Fix getDefaultBranch always falling back to origin/main: the promisified exec lacked `encoding: 'utf8'`, so stdout was a Buffer and .trim() threw. - Parameterize ghErrorMessage so `gh pr create` timeouts name the right command and duration; sanitize workspace paths in PR-create errors. - GitDialog: guard doCommit against double-submit (button + keyboard), and strip only a known remote prefix from the PR base so local branches with "/" are not mangled; use theme tokens for commit button/success colors. - BranchPickerPopover: guard checkout/new-branch behind busyAction, reset inline-input text on reopen, and hide the commit action when unavailable. Adds regression tests for the checkout/branch validation and the git log range guard. * fix(web-shell): address review feedback on branch picker PR (#7731) - Fix Commit+Push error masking: split try/catch so push failure reports alongside the successful commit SHA - Replace hardcoded screenshot path with captureScreenshot harness - Replace silent if-isVisible skip with explicit assertion in visual test - Add focus-visible style for search input accessibility - Fix CSS specificity for active PR tab hover state - Add viewChanges to actionsVisible search filter - Wrap toggleSection in useCallback to avoid unnecessary re-renders - Add windowsHide: true to getDefaultBranch subprocess - Fix trailing slash handling in mockDaemon git action routing - Add git methods to top-level client mock in tests - Remove dead branchPicker.commitSuccess i18n key - Remove dead .actionShortcut CSS class - Show generation failure feedback in commit message placeholder * fix(web-shell): address review feedback on branch picker PR (#7731) - Add workspace trust checks to bound git branch routes - Use workspace-scoped client in BranchPickerPopover (fixes wrong-workspace mutation) - Add branch name validation and -- terminator to gitCreateBranch - Filter refs/remotes/*/HEAD from branch listings - Force LC_ALL=C for reflog parsing (non-English locale fix) - Narrow 'could not resolve' error regex to avoid DNS false positives - Add range validation to git log (reject path traversal) - Fix commit+push error i18n (dedicated key instead of concatenation) - Add i18n for BranchSelect component strings - Fix commit tab ARIA attributes - Add onBranchChanged callback to handlePush - Add btw to mockDaemon isDaemonPath regex - Add workspace_github_prs to visuals spec capabilities - Add -- terminator regression test - Remove docs/pr-assets/ from repo * fix(web-shell): address review feedback on branch picker PR (#7731) * fix(cli): reject dash-prefixed branch name with 400 in branch route (#7731) * fix(web-shell): address review feedback on git branch picker (#7731) Security: - Clear GIT_DIR/GIT_WORK_TREE/GIT_COMMON_DIR/GIT_INDEX_FILE from git subprocess env to prevent repository redirection - Add strict mutation gate to all POST git branch routes - Add generation guard to qualified write routes - Fail closed on invalid ?cwd= in mutation routes (resolveContainedCwdOrFail) - Reject wrong-typed startPoint, fetchOnly, rebase, and PR options with 400 Correctness: - Filter remote symbolic refs (origin/HEAD) by %(symref) instead of /HEAD name suffix, preserving valid branches like feature/HEAD - Add git rev-parse --git-dir probe so non-git dirs get 404 instead of empty available:true - Push preserves existing upstream; only adds --set-upstream when unset, resolving the remote from branch config or the sole configured remote - git commit -a replaced with git add -A + git commit so untracked files displayed in the UI are included - Always pass --body to gh pr create to prevent interactive prompts - getDefaultBranch returns null instead of fabricating origin/main - Memoize workspaceByCwd client in BranchPickerPopover to fix infinite render loop - Move sessionId to a ref in GitDialog effects to prevent self-abort - Bound commit-message prompt to fit /btw 4096-char limit - Mark all platforms as unverified in PR template (no fabricated ✅) - Guard PR auto-fill effect against wiping user edits on reconnect Accessibility: - Add tabIndex and onKeyDown to commit-mode tab span - Add aria-label to BranchSelect trigger and search input Cleanup: - Remove dead CSS (.prInputSmall, .prSelect) - Remove 9 unused i18n keys - Add ^ to git log range validation regex - Add busyAction guard to handlePush/handlePull - Add unit tests for gitCommit, gitPull, and route input validation * fix(web-shell): address review feedback on git branch picker PR (#7731) - Change commit tab from <span> to <button> for keyboard accessibility - Move setCommitMsg('') to success-only branches so the message is preserved when push fails after a successful commit - Add mutate middleware and generationGuard to PR creation route, matching all other POST mutation routes - Set genFailed when session resolution returns undefined so the user sees the failure indicator instead of a silent empty textarea - Make PR number nullable when URL regex does not match instead of returning a misleading 0 - Add LC_ALL=C and LANG=C to gitEnv() so for-each-ref upstream track parsing is locale-independent - Validate setUpstream and force as booleans in handlePush, matching the existing validation in handlePull - Add missing workspaceCwd and available fields to test mocks - Use stable data-web-shell-git-branch attribute in e2e selector * fix(web-shell): address R5 review feedback on git branch picker PR (#7731) - Classify git errors on stdout+stderr instead of err.message to fix false-positive no_upstream on every push failure and dead nothing_to_commit classifier - Sanitize workspace paths and cap error message length in sendGitError - Fix remote branch checkout to strip remote prefix so git DWIM creates a local tracking branch instead of detaching HEAD - Restore keyboard accessibility on composer branch chip (span → button) - Trim startPoint in handleCreateBranch before forwarding to git - Return bare branch name from getDefaultBranch (strip remote prefix) - Fix i18n shortcut hint to show ⌘/Ctrl+Enter for cross-platform - Update aria-label to reflect git management menu, not just changes - Add available: true to mockDaemon gitDiff default payload - Add regression tests: upstream preservation, sole remote resolution, strengthened fetch-only with divergent remote commit - Add aria-expanded assertion to sidebar picker test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(web-shell): address R6 review feedback on git branch picker PR (#7731) * fix(web-shell): address R7 review feedback on git branch picker PR (#7731) * fix(web-shell): address R8 review feedback on git branch picker PR (#7731) * fix(web-shell): address R9 review feedback on git branch picker PR (#7731) * fix(web-shell): address R10 review feedback on git branch picker PR (#7731) * fix(web-shell): address R11 review feedback on git branch picker PR (#7731) * fix(web-shell): address R12 review feedback on git branch picker PR (#7731) * fix(web-shell): address R13 review feedback on git branch picker PR (#7731) * fix(web-shell): address R14 review feedback on git branch picker PR (#7731) * fix(web-shell): address R15 review feedback on git branch picker PR (#7731) - Validate localName derived from remote-tracking ref to prevent option injection (e.g. origin/-f → git checkout -f) - Add gitCwd prop to BranchPickerPopover and pass it to all git SDK calls so worktree sessions target the correct directory - Add symlink-escape and non-existent-path tests for resolveContainedCwdOrFail - Pin initial branch name in makeRepo() with git init -b master - Add gitPull merge and rebase integration tests * fix(web-shell): address git branch picker review feedback (#7731) * fix(web-shell): address R6 review feedback on git branch picker PR (#7731) * fix(web-shell): address review feedback on git branch picker PR (#7731) - Strip GIT_CONFIG_GLOBAL/SYSTEM/NOSYSTEM in gitEnv to prevent inherited config redirection (consistent with extension/github.ts) - Pass gitCwd to workspaceGitBranches in GitDialog loadPrBranches so worktree sessions fetch branches from the correct repository - Add aria-expanded to collapsible branch section headers - Add happy-path tests for PR create (201) and default-branch (200) routes, including the null fallback to origin/main * fix(cli): add sendGenerationClosedError to POST routes and cover untested branches (#7731) * fix(web-shell): address review feedback on branch picker and PR creation (#7731) - Refresh branch list after push/pull to avoid stale ahead/behind counts - Add pre-flight check for unpushed branches before PR creation - Fix base branch prefix stripping when branch list is unavailable - Cap PR body file list at MAX_SUMMARY_CHARS to bound model prompt size - Add qualified route tests: trust guard, input validation, cwd containment * fix(web-shell): hoist MAX_SUMMARY_CHARS to module scope for PR body generation (#7731) * fix(web-shell): address review feedback for git branch picker (#7731) - Hoist onOpenCommit to useCallback to fix App.test.tsx prop stability test - Keep commit tab visible after navigating away (startedInCommit ref) - Add onClick handler to commit tab for navigation back to commit view - Fix branch-prefix strip mangling local branch names containing '/' - Update sessionIdRef after force-creating a stale session replacement - Pin core.hooksPath in test makeRepo for reliable rollback tests - Add test asserting --force-with-lease is used for force pushes * fix(web-shell): target the worktree for sidebar commits and harden git actions (#7731) Scope the sidebar commit dialog to the active session's worktree checkout (matching the composer path) so linked-worktree sessions commit to the right checkout, guard PR creation against a double-click race, and surface an error when an invalid branch name is submitted. Adds focused coverage for the branch picker action wiring and the git branch route validation paths. * fix(web-shell): address review feedback for git branch picker (#7731) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
d61b0ea475
|
perf(web-shell): paint the composer git chip before git status completes (#7680)
* perf(web-shell): paint the composer git chip before git status completes New sessions gated the chip on a full `git status --porcelain` subprocess behind GET /workspaces/:ws/git, so the branch chip appeared hundreds of milliseconds (worst case seconds) after the composer was ready. The daemon now keeps a per-workspace last-known summary with in-flight dedup and a 2s background-refresh throttle: the default GET returns the cached status (branch-only on a cold start) immediately and recomputes in the background, publishing git_status_changed over SSE only on a delta, while ?wait=1 keeps the previous blocking semantics. The composer fetches both paths concurrently — the fresh GET also covers the no-session state, which has no per-session SSE stream — so the branch paints in ~3ms and the counters land when the computation finishes. The sidebar keeps wait:true since it has no SSE fill-in path. * fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680) * fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680) * fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680) * fix(cli): use writeStderrLineSafe in git-status refresh error path (#7680) * fix(web-shell): add debug trail to fresh-path catch and test branch-watcher dispose guard (#7680) * fix(cli): assert writeStderrLineSafe in git-status refresh failure test (#7680) --------- Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
1130865949
|
fix(web-shell): show full session names on hover (#7662)
* fix(web-shell): show full session names on hover Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): cover archived session tooltip attribute (#7662) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
38ff85430c
|
fix(web-shell): avoid redundant git status requests (#7496)
Co-authored-by: ytahdn <ytahdn@gmail.com> |
||
|
|
2709d1fccd
|
feat(web-shell): surface worktree isolation in the new-session empty state (#7365)
* feat(web-shell): surface worktree isolation in the new-session empty state The worktree-isolated session entry was buried in the sidebar git-branch pill dropdown, making it hard to discover. Add a visible toggle to the chat empty state — the de-facto new-session page — that reuses the existing pending-worktree state machine and lazy session creation, so no SDK or daemon changes are needed. Enabling it shows the pending badge with a cancel affordance; the first prompt then creates the session in an isolated worktree. The toggle is offered only when the target workspace is trusted and is a git repository, mirroring the sidebar entry gating. Also simplify the sidebar git pill: drop the now-redundant "New worktree task" item and make the pill open the changes view directly instead of a single-item dropdown. * chore: add PR verification screenshots for the worktree toggle Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): capture the new-session empty state in the visuals suite The worktree toggle lives in the new-session empty state, and every visuals scenario navigates to /session/:id via gotoSession — so the suite had never rendered the empty state at all, and the before/after preview reported "no screenshot changes" for this PR despite the new UI. Add a `gotoNewSession` harness helper (primes the theme, lands on `/`, asserts the theme took effect; no replay to settle) and a `worktree empty state` scenario using the git-ready workspace this PR already made mockable (`gitStatus` + the /workspaces/:cwd/git route). It captures both states — the offered toggle and, after clicking, the pending-worktree badge with its cancel affordance — and asserts the swap, so a regression fails an assertion rather than only differing in the screenshot. All four captures are byte-stable across runs (0% pixel diff). The helper also closes the structural gap: any future empty-state work (onboarding copy, first-run affordances) now has a way into the preview. * refactor(web-shell): drop dead worktree session opt; click-test git chip (#7365) * fix(web-shell): address review feedback on worktree toggle (#7365) - Move focus to cancel button on toggle enable and back on cancel (a11y) - Include branch name in git-pill button aria-label (a11y) - Replace hardcoded flush() ticks with vi.waitFor() in test helper - Move git-repo mock default from afterEach to beforeEach - Add test: sidebar New chat clears pending worktree intent --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
9e822d6004
|
feat: support workspace display names (#7179)
* feat(sdk): support workspace display names * docs: add Web Shell screenshot * feat(web-shell): add workspace display names * fix(serve): harden workspace display name updates * refactor(serve): simplify workspace display names * fix(serve): validate trimmed workspace display names * feat(serve): add workspace update API * docs(serve): clarify workspace display name null handling * docs(sdk): list addWorkspace in daemon client methods |
||
|
|
0c271659df
|
feat(daemon): worktree-isolated sessions for parallel tasks (#7221)
Add support for creating sessions in isolated git worktrees from the Web Shell, enabling multiple tasks to run in parallel within the same workspace without polluting the main working directory. Daemon: - POST /session accepts optional worktree param, creates worktree via GitWorktreeService, relocates session via changeSessionCwd - Worktree metadata persisted in SessionEntry, BridgeSessionSummary, and sidecar file (<sessionId>.worktree.json) for daemon restart recovery - GET /workspaces/:workspace/git supports ?cwd= for worktree-scoped git status queries (path.resolve + containment check) SDK: - CreateSessionRequest/DaemonSession/DaemonSessionSummary gain worktree field; DaemonSessionClient exposes worktree getter - WorkspaceDaemonClient.workspaceGit() accepts optional cwd param Web Shell: - Workspace branch pill dropdown offers 'New Worktree Task' (git repos only) with purple GitForkIcon and description - Git chip turns purple with GitForkIcon for worktree sessions - Session list shows inline ⑂ badge for worktree sessions - Empty-state welcome badge explains worktree isolation - Git status queries target worktree path, not workspace root - session_cwd_changed event filtered from chat transcript Design doc: docs/design/2026-07-19-webshell-worktree-sessions.md |
||
|
|
582fb49603
|
feat(web-shell): git status chip, visual working-tree diff, and sidebar git status (#7054)
* feat(web-shell): git status chip, visual working-tree diff, and sidebar git status
Bring working-tree Git awareness to the Web Shell (browser daemon session UI):
- Toolbar branch chip becomes a live status indicator: dirty (staged/unstaged/
untracked), ahead/behind upstream, stash count, detached HEAD, in-progress
operation (merge/rebase/cherry-pick/revert/bisect), and conflict count, each
with a non-color cue.
- Read-only "Changes" dialog: working-tree-vs-HEAD file list with per-file,
line-level, per-side syntax-highlighted diffs; opens via /diff or a dirty
chip; untracked files expand as fully-added and deleted files still diff.
- Per-workspace git status in the sidebar: a compact icon-only chip per trusted
workspace (status dot + hover tooltip); click opens that workspace's dialog.
All git access goes through the daemon REST API with per-workspace trust
gating; new SDK status fields are optional and additive (v2).
* fix(web-shell): themed tooltips and git-chip review follow-ups
Tooltips now render on the themed popover surface (bg-popover /
text-popover-foreground / border + fill-popover arrow) instead of the
inverted bg-foreground default, so they read dark-on-dark rather than a
bright box on the dark theme. Fixing the shared primitive corrects the
git branch tooltip in the composer toolbar and sidebar, plus every other
tooltip, at once.
Also addressing review feedback on the git integration:
- Replace the hand-drawn detached/conflict/stash SVG icons with
lucide-react (CircleDot / TriangleAlert / Layers) per the web-shell
icon convention.
- Gate the tooltip "Working tree clean" message on an enriched status
(computedAt) so a branch-only status no longer asserts clean.
- Include the file path in the diff dialog row aria-label so screen
readers can distinguish files.
- Reset the toolbar git chip on workspace switch so it never shows the
previous repo's branch/counts while the new fetch resolves.
- Log a sidebar git poll failure only on the success->failure transition
to avoid spamming a long-lived tab.
- Correct the SDK doc for DaemonWorkspaceGitDiffFile.added/removed
(0, not undefined, for binary files).
* fix(web-shell): address git-integration review suggestions
Follow-ups from the /review pass on the git integration:
- GitBranchIndicator: include the short SHA in the detached-HEAD tooltip
title, and add the "Working tree clean" status to the aria-label (gated
on an enriched status, matching the tooltip) so the two never drift.
- WorkspaceSection: keep the last known git status on a transient poll
failure instead of blanking the chip for a whole interval.
- App: surface a toast for `/diff` when no workspace is available instead
of silently consuming the composer input.
- Tests: cover the diff dialog's list-load and per-file load error paths,
and detectGitOperation's revert/bisect branches.
- Design doc: align the getGitWorkingTreeStatus spec text with the
decision (transient states return status with `operation`; null is
reserved for non-repo / git failure).
* fix(web-shell): focus-visible ring for git chip button; align doc poll interval
- Add a :focus-visible outline to .gitBranchChipButton so keyboard users
get a visible focus indicator (the chip resets UA button chrome).
- Design doc: align the active-workspace poll-interval references at 30s
to match the implementation.
* fix(web-shell): surface capped diffs, catch row-build failures, cover degradation paths
Address the remaining review findings on the git integration:
- Truncation is no longer silent: fetchGitDiffHunksForFile now returns
{ hunks, truncated } — the parser records files that actually lost
lines to MAX_LINES_PER_FILE (tracked path), and the untracked
synthesis reports its byte/line caps. The route forwards an additive
`truncated` flag on the hunks response (absent when not truncated, so
older clients and daemons are unaffected), and the Changes dialog
renders a "Diff truncated" note under the visible window.
- DiffHunks catches an unexpected buildRows rejection (e.g. malformed
hunk lines) and shows the per-file error instead of leaving an
unhandled rejection and a silently empty diff area.
- New tests: untracked and tracked truncation at the core caps, the
route's truncated passthrough (and its absence when clean), the
branch-only degradation when the working-tree summary throws, the
malformed-hunks error path, and the Shiki success path (a fake
tokenizer proving add rows pull new-side tokens and del rows pull
old-side tokens, not the plain-text fallback).
* fix(web-shell): drop dialog backdrop-blur that froze the page on open
The dialog and alert-dialog overlays applied `backdrop-blur-xs`, which
forces the browser to rasterize and blur the entire content behind the
overlay when a dialog opens. With a long transcript behind it, that
main-thread paint+blur froze the whole page — e.g. clicking the git
branch chip to open the Changes dialog. Keep the bg-black/10 scrim for
separation and drop the blur.
* fix(core): guard synthesizeUntrackedHunk against non-regular files
synthesizeUntrackedHunk opened an untracked path before checking its
type, so an untracked FIFO (listed by `ls-files --others`) would block
on open() forever waiting on a writer — hanging the daemon's event loop
and leaving the Web Shell Changes dialog stuck on a permanent loading
state. lstat-gate on regular files before opening, matching the existing
guard in countUntrackedLines. Adds a FIFO regression test.
* fix(web-shell,core): rename expansion, no-newline marker, chip measurement
Round-5 review Criticals:
- core: key renamed diff entries by the real (post-rename) path and carry
the old path for display, so renamed rows can be expanded — the synthetic
`old => new` key was sent to git as a nonexistent literal path. The diff
dialog renders the rename as `old → new`.
- core: preserve Git's `\ No newline at end of file` marker through the hunk
parser so a trailing-newline-only edit isn't shown as identical
removed/added lines (the viewer already renders it as a meta row).
- web-shell: the toolbar's hidden git-chip measurement replica now renders
the full chip content via the extracted GitBranchChipContent, so the
expanded width includes the status indicators and the compact/expanded
toggle no longer oscillates near the responsive threshold.
* fix(build): generate git-commit info even when prepare build is skipped
The review tooling runs `npm ci` with QWEN_SKIP_PREPARE=1 (to skip the
heavy prepare build) and then builds only the changed workspaces. Because
`prepare` exited before generating the gitignored git-commit.ts, a
per-workspace build of packages/cli failed at the unchanged systemInfo.ts
on the missing `../generated/git-commit.js` module. Generate the git-commit
info in the skip path too — it is cheap and never fails hard — so a later
per-workspace build or typecheck finds the module. The non-skip path still
generates it via `npm run build`.
* fix(web-shell,cli): address round-6 review suggestions
- cli: carry the pre-rename path (oldPath) through DiffRenderRow and show
renamed files as `old → new` in both the Ink and plain-text renderers.
The rename-keying fix updated the daemon and web-shell dialog but not the
CLI `/diff` renderer, which silently dropped the old path.
- web-shell: key DiffFileRow by workspace + path so switching workspace
remounts the row instead of reusing another workspace's hunks/open state
for a path both workspaces share.
- web-shell: show a loading placeholder in DiffHunks while rows are (re)built
(e.g. after a theme switch) instead of an empty, jumpily-resized box.
- web-shell: cover the /diff local intercept in App.test.tsx (opens the
Changes dialog and is not forwarded to the agent).
* fix(web-shell,cli,core): address round-7 review suggestions
- cli: sanitize the rendered filename (and pre-rename oldPath) in the Ink
DiffStatsDisplay via sanitizeFilenameForDisplay, matching the plain-text
renderer so a crafted path can't inject into the interactive view.
- cli: apply the read headers before awaiting the per-file diff fetch (as
handleDiffList does) so error responses also carry no-store/nosniff.
- cli + web-shell: strip Unicode bidi embedding/isolate controls
(U+202A-202E, U+2066-2069) in the filename/control-char sanitizers so a
crafted filename can't visually spoof its extension.
- core: guard countStashEntries with an lstat type check before readFile, so
a symlink-to-FIFO at logs/refs/stash can't block the event loop (the same
hazard already guarded in the untracked-file readers).
- core: cover fetchGitDiffHunksForFile's transient-state guard with a test
(the sibling helpers already had one).
* fix(web-shell,cli,core): address round-8 review suggestions
- core: pass --no-optional-locks to the ls-files call in
fetchGitDiffHunksForFile, matching the other runGit calls so it doesn't
contend for an optional index-refresh lock alongside concurrent git
add/commit.
- cli: add a route test asserting a rename's oldPath survives serialization
end-to-end (keyed by the new path, old path carried alongside).
- web-shell: add a GitDiffDialog test for the hiddenCount>0 "N more files
not shown" note (every payload previously used hiddenCount: 0).
- web-shell: drop the nonexistent primaryLabel prop from the WorkspaceSection
test (it is not a WorkspaceSectionProps member).
- docs: correct the plan doc — large-diff virtual scrolling was explicitly
descoped (core caps + per-file lazy loading), not implemented in Phase 2.
* fix(cli,web-shell): address round-9 review findings
- cli: propagate the pre-rename oldPath through DiffDialog's
perFileToUnified and render renamed files as `old → new` in the
interactive diff viewer (the rename-keying fix had updated the daemon,
the web-shell dialog, and the /diff stats, but not this viewer).
- cli: cover DiffStatsDisplay's rename (`old → new`) rendering and the
sanitizeFilenameForDisplay path for hostile filenames carrying control
characters.
- web-shell: guard the GitBranchIndicator test afterEach against
double-unmounting an already-unmounted root (the localization tests
assert on getTranslator without calling render()).
* fix(core,cli,web-shell): rename-aware single-file diff (old→new)
fetchGitDiffHunksForFile pathspec-limited the diff to the new path, which
defeats git's rename detection — a renamed file was reported as fully
added (every line +) instead of its actual edit. Thread an optional
pre-rename path through the single-file endpoint (core → route → SDK →
dialog) and diff old→new with -M when it is present, so expanding a
renamed file shows its real content change.
* fix(cli): address round-10 review suggestions
- DiffDialog: split the path-width budget between old and new paths for a
rename (reserving the " → " separator) so the combined width stays within
maxPathChars instead of overflowing the row layout.
- textUtils: extend MULTILINE_CONTROL_CHARS_REGEX with the Unicode bidi
ranges (matching FILENAME_CONTROL_CHARS_REGEX) and add a test that
sanitizeFilenameForDisplay strips bidi embedding/isolate controls.
- workspace-git-diff route: add a test that ?oldPath= is parsed and
forwarded to fetchGitDiffHunksForFile.
* test(sdk),docs: cover diff client methods; align design doc
- sdk: add DaemonClient unit tests for workspaceGitDiff() and
workspaceGitDiffFile(path, oldPath?) — URL construction (incl. urlEncode
on path/oldPath, with and without oldPath, plus the workspace-qualified
route) and response deserialization, mirroring the existing workspaceGit()
test.
- docs: add the oldPath? param to the workspaceGitDiffFile API spec; record
that the diff client methods now have unit tests (correcting the claim
that workspaceGit() had none); attribute the bundle-limit bump to
packages/sdk-typescript/scripts/build.js; clarify ahead/behind are relative
to upstream (0, and ↑N/↓N not shown, without one).
* fix(web-shell,core): address round-11 review suggestions
- GitBranchIndicator: count conflicted entries as dirty — a merge where every
changed file is conflicted (staged=unstaged=untracked=0) is still
uncommitted, so the expanded chip's dirty dot / data-dirty now reflect it.
- core: split the status branch line at the last "..." (the branch/upstream
separator) so a dotted branch name isn't truncated at the first "...".
- GitDiffDialog: guard DiffFileRow's in-flight fetch against unmount via a
cancelled ref, matching DiffHunks / GitDiffDialog.
- tests: forward oldPath when expanding a renamed file in the web-shell
dialog; bidi-strip coverage for the web-shell sanitizeControlChars;
untrusted-guard coverage on the single-file diff route; conflicted-only
dirty; branch-line "..." split.
* fix(web-shell,cli): address round-12 review suggestions
- DiffDialog: only render the rename "old → new" when there's room for both
sides (≥19 cols, so each gets ≥8); otherwise fall back to the new path
alone, so a narrow terminal no longer overflows the row (the Math.max(8,…)
floor could exceed maxPathChars).
- GitBranchIndicator test: guard afterEach container.remove() for non-render
tests run in isolation, and make the compact-mode ↑-suppression assertion
non-vacuous by giving the fixture an ahead count.
- App: compute the active workspace once (useMemo) and share it between the
git-status effect and the Changes-dialog entry point, so the chip and the
dialog can't drift onto different repos.
* fix(core,docs): address round-13 review suggestions
- core: add a rebase-apply detection test (git am / an interrupted
`rebase --apply` creates rebase-apply, which detectGitOperation also maps
to 'rebase'); previously only rebase-merge was exercised.
- docs: correct section 5 to describe the actual diff-dialog mechanism
(diffWorkspaceCwd state, not the stale activePanel design).
* test(core): cover stray no-newline marker before any hunk header
parseGitDiff's pre-hunk guard already skips a "\ No newline at end of
file" marker that appears before any @@ header, so a malformed/truncated
diff can't throw on a null currentHunk and lose subsequent files' hunks;
add a regression test pinning that behavior.
* fix(web-shell): unstick per-file diff loading and skip non-path git poll
- DiffFileRow: reset the cancelled-fetch flag on mount so StrictMode's
mount/unmount/mount replay no longer leaves it latched at true, which
dropped the fetched hunks and froze the row on "Loading changes…" despite
a 200 response.
- WorkspaceSection: skip the git status poll when the workspace cwd is not an
absolute path. A synthetic fallback workspace carries a display name there,
which the cwd-qualified route rejects with a 400.
* fix(web-shell,cli): address review suggestions on the git diff surface
- GitDiffDialog: highlight each diff side independently so a small side
keeps syntax highlighting even when the other side exceeds the size cap
(the old guard dropped both as soon as either was too large).
- ChatEditor: complete the .gitBranchChipButton reset (font/color/padding/
margin) so the clickable dirty-tree chip matches the read-only output chip
instead of picking up UA button styling.
- DiffDialog: cover the interactive rename display (old to new on a wide
terminal), mirroring the rename tests DiffStatsDisplay and GitDiffDialog
already have.
* test(web-shell,cli): cover git chip clean/reload/traversal paths, fix doc
- GitDiffDialog: add the missing expect(header).not.toBeNull() guard to the
three expand-file tests that lacked it, matching the others in the block.
- GitBranchIndicator: cover the known-clean aria-label branch (computedAt set
and every change counter zero).
- WorkspaceSection: verify a reloadToken change re-fetches git status instead
of waiting for the next 60s poll.
- workspace-git-diff route: verify a traversal oldPath is forwarded to core
and surfaced as available:false rather than escaping the workspace.
- Design doc: /diff is handled via setDiffWorkspaceCwd, not setActivePanel.
* fix(core): allow literal `..foo` paths in diff normalization
- toRepoRelativePath: reject only a real climb-out (`..` or `../…`), not a
literal `..foo` filename at the repo root, which the bare startsWith('..')
over-rejected, leaving the diff viewer unable to render such a file.
- parseGitDiff: cover the truncatedPaths output set directly (it was only
exercised indirectly through fetchGitDiffHunksForFile).
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
|
||
|
|
9e95505551
|
refactor(web-shell): drop redundant primary-workspace label (#7035)
* refactor(web-shell): drop redundant primary-workspace label The workspace selector in the composer already marks the default target with its own checkmark, so appending "· Primary" to the primary entry's name carried no extra information. Remove that tag everywhere it showed: - composer selector: trigger, tooltip, and dropdown list - sidebar workspace header badge (also lets the name show untruncated) - session overview / split-view picker badges — the primary now shows its folder basename, consistent with the other workspaces - scheduled-tasks dialog workspace labels Delete the now-unused i18n keys (sidebar.workspacePrimary, scheduledTasks.workspacePrimaryTag; en + zh) and update the two tests that asserted the old tag. * refactor(web-shell): reuse workspaceBasename + cover primary-badge removal Address /review suggestions on the primary-workspace-label cleanup: - ScheduledTasksDialog's local workspaceLabel() is now functionally identical to the shared workspaceBasename() util (both return the cwd's last path segment), so reuse the util and delete the duplicate. - Add a WebShellSidebar test asserting the primary workspace header no longer renders a "Primary" badge, so a regression re-adding it fails. * test(web-shell): assert SplitView primary picker item has no "Primary" tag Covers the fourth /review suggestion (terminal-only): the multi-workspace picker test now asserts primary-workspace sessions render their basename, not the removed "Primary" label. * test(web-shell): assert scheduled-tasks picker option text drops (primary) Covers the re-review suggestion: the workspace <select> picker options were checked for count and value but not visible text, so a regression re-adding a "(primary)" suffix to the primary option would pass undetected. Assert the option labels are the bare basenames. --------- Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
c8290f8e49
|
fix(web-shell): persist collapsed session group sections across reload (#6878)
* fix(web-shell): persist collapsed session group sections across reload Store collapsed section ids in localStorage using the existing qwen-code-web-shell-* key namespace, and skip the first catalog sync auto-collapse so restored expand/collapse state survives remount. Fixes QwenLM/qwen-code#6870 Co-authored-by: Cursor <cursoragent@cursor.com> * docs(web-shell): clarify collapsed-groups demo GIF storyboard Crop to the sidebar, caption the four beats (expand → collapse → reload → still collapsed), and keep Pinned out of the organized session list mock so the Backend collapse is obvious. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): address collapsed-groups CR feedback Export the storage key for unit tests, use an explicit first-catalog latch instead of size===0, and cover corrupt/disabled storage plus mid-session auto-collapse of newly appeared sections. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web-shell): harden collapsed-groups persistence for CR feedback Wait for groups+sessions catalog settlement before the initial latch, persist secondary-workspace collapse via shared namespaced localStorage, and keep primary/workspace writers from clobbering each other. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(web-shell): drop demo-capture tooling from collapsed-groups test The committed GIF, capture script, and frame-assembly helper only served the PR description's embedded image and were referenced by nothing else in the repo; the CAPTURE_DEMO branches in the e2e spec were pure screenshot staging with no assertions. The remaining spec still covers every acceptance criterion of #6870 and keeps its @smoke tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KuV2hx2ZqEqdRwsJz4KJH * fix(web-shell): keep collapse latch armed until catalogs settle successfully Two paths could consume the first-sync latch against a partial catalog and then auto-collapse (and persist over) the user's restored expansions: a failed initial sessions/groups request counted as settled, and a mid-session organization_enabled flip let the auto-collapse effect run one commit before the groups gate closed. Errors no longer settle either readiness gate, and the gate now closes during the flip render itself. Also drop the WorkspaceSection reload effect and exhaustive-deps suppression that defended a workspace.id change which cannot happen (the render site keys the component by workspace id), and import the storage key in tests from collapsedSessionSections directly instead of re-exporting it through WebShellSidebar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KuV2hx2ZqEqdRwsJz4KJH --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0c6212c0b0
|
feat(web-shell): add workspace path lock (#6853)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
13c224f5e9
|
feat(serve): support runtime workspace removal (#6745)
* feat(serve): support runtime workspace removal Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): address workspace removal review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): strengthen workspace removal regressions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(webui): fix timeout assertion lint Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): address workspace removal review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): update workspace Git test registry Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): address workspace removal review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #6745 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): cover workspace removal after sidebar rebase Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6745) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
79ae054bb8
|
feat(web-shell): modernize multi-workspace sidebar (#6804)
* feat(web-shell): modernize multi-workspace sidebar * fix(web-shell): address sidebar review feedback * fix(web-shell): address remaining review feedback --------- Co-authored-by: ytahdn <ytahdn@gmail.com> |
||
|
|
e403246dc2
|
feat(serve): Expose read-only untrusted session catalogs (#6717)
* feat(serve): expose read-only untrusted session catalogs Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): address session catalog review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6717) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6717) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#6717) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
732d85df39
|
fix(web-shell): correct Add Workspace dialog theming and multi-workspace session rows (#6705)
* fix(web-shell): theme and lay out the Add Workspace dialog correctly The dialog's stylesheet referenced CSS variables that are defined nowhere (--text-secondary, --input-bg, --border-color, --accent-color, --hover-bg), so they fell back to hardcoded dark values — in light mode the input rendered dark-on-light with a purple focus ring, inconsistent with every other dialog. It also reused a two-column form-row grid meant for label/value pairs, which cramped the path input into a narrow column. Rebuild the dialog on the shared dialog primitives (themed .dialog-form input, dialog-inline-button, dialog-primary-button) so it tracks the active theme in both light and dark and gives the path a full-width input. Add a proactive absolute-path hint, an accessible inline error (role="alert", aria-describedby, aria-invalid) that clears as you type, and a localized "Adding…" state. Keep the hint and error as siblings of the input rather than nested in the label, so their text stays out of the input's accessible name. * fix(web-shell): render full session rows for every workspace in the sidebar Registering a second workspace switched the sidebar to the multi-workspace view, which rendered each workspace's sessions with a bespoke minimal row: a smaller font (12px vs 14px) and no per-session actions. The rich single-workspace row (hover actions, current-session highlight, inline rename, running/unread state) was hidden entirely, so even the primary workspace's list degraded. Render sessions through the sidebar's shared renderSessionRow so every workspace matches the single-workspace list. Gate the mutation actions to the primary (daemon-bound) workspace: the daemon can't resolve another workspace's session for pin/archive/export/delete (they 404 or silently no-op), so non-primary rows are read-only — they keep click-to-load and the font/highlight but drop the action buttons. Session mutations now also bump the per-workspace poll token so those lists refresh promptly. * refactor(web-shell): move Add Workspace footer padding into a CSS class The footer sits inside .dialog-form (which already pads its sides) and reuses the shared dialog-footer-actions primitive, doubling the horizontal padding; the override lived as an inline style, hiding the deviation from the stylesheet. Move it to a local .footer class applied alongside the shared class. A doubled selector keeps it winning over the primitive regardless of stylesheet order. * test(web-shell): cover workspace read-only gating and Add Workspace dialog Add tests for the behaviors introduced by the sidebar and dialog fixes: - non-primary workspace rows render the session but expose no action buttons (the daemon, bound to the primary workspace, can't service their mutations), while the primary workspace keeps its full actions; - a session mutation re-polls the per-workspace list instead of waiting for the 10s interval; - the Add Workspace dialog's absolute-path validation, accessible error wiring (role="alert", aria-describedby/aria-invalid), error-clear-on-edit, trimmed submit, and onAdd-failure handling. * refactor(web-shell): centralize the workspace reload-token bump in a helper Per review: the setWorkspaceSessionsReloadToken bump was repeated verbatim across the session-mutation handlers. Extract a stable bumpWorkspaceReload() helper and route every site through it, including assignSessionGroup and assignSessionColor — the two organization mutations that were missing the bump — so assigning a group or color now also re-polls the per-workspace session lists instead of waiting for the 10s interval. * test(web-shell): cover Windows paths and non-Error rejections in Add Workspace dialog Per review: add the two untested handleSubmit branches — a Windows-style absolute path accepted by the drive-letter regex, and a non-Error onAdd rejection falling back to the generic error message. * fix(web-shell): gate inline rename on readOnly and refresh on group-create assign Per review: the readOnly option hid the hover action buttons but not the inline rename — onDoubleClick and the isEditing branch still fired on read-only rows, so a session shown in multiple workspaces could render an editable rename input on its non-primary (read-only) copy. Gate both on !readOnly. Also add the missing bumpWorkspaceReload() to saveGroupEditor's create-with-target-session path so that organization mutation refreshes the per-workspace lists like the others. * test(web-shell): cover readOnly rename gating and dialog submitting state Per review: assert the read-only (non-primary) row does not open the inline rename form when the shared session is renamed from the primary row, and that the Add Workspace dialog shows the localized "Adding…" label with disabled controls while onAdd is pending. |
||
|
|
2523a36b52
|
feat(web-shell): workspace management sidebar with dynamic registration (daemon multi-workspace phase 4) (#6625)
* feat(web-shell): add workspace picker for new sessions (issue #6378 phase 4) Multi-workspace daemons now show a new-session workspace picker in the sidebar (default primary, untrusted disabled); the chosen workspace cwd is sent on POST /session so the session spawns in that workspace. daemon-react-sdk createSession gains an optional per-call workspaceCwd override covering both the detached and active-session paths; omitting it preserves the previous primary behavior. * feat(web-shell): workspace management with dynamic registration Replace the new-session workspace picker with a full workspace management sidebar. Registered workspaces render as a parallel, collapsible list (folder icon per workspace), each with its own sessions nested underneath, and a "+" entry registers an existing directory as a new workspace at runtime with no daemon restart. Backend: WorkspaceRegistry becomes mutable (add()/onChange()); a new POST /workspaces route validates the directory (exists, not a duplicate, not nested) and registers it; run-qwen-serve exposes a runtime factory that builds a complete workspace runtime (bridge, fs factory, channel factory, workspace service) on demand. The SDK DaemonClient and daemon-react-sdk gain addWorkspace(). * fix(web-shell): show newly registered workspace without a reload Registering a workspace via the sidebar "+" left the list unchanged until a full page reload. handleAddWorkspace called workspace.getCapabilities(), which returns a cached promise and only feeds setCapabilities from the mount effect, so the refresh was a no-op. Add DaemonWorkspaceProvider.refreshCapabilities(): it bypasses the promise cache, issues a fresh /capabilities fetch, and pushes the result into state so consumers re-render. handleAddWorkspace now awaits it (best-effort, so a refresh failure never masks a successful registration). * fix(web-shell): address review feedback for workspace management - registry: list() returns a frozen snapshot so callers can't mutate the internal runtimes array (restores the push()-throws invariant) - POST /workspaces: reject relative paths on the raw input, canonicalize via realpath so symlink aliases can't bypass the duplicate/nesting checks, and serialize concurrent registrations to close a TOCTOU race that leaked bridge/channel infrastructure - sidebar: restore a compact single-workspace project header (name, search toggle, collapse) so single-workspace users keep those affordances and searchOpen/projectExpanded are no longer dead - daemon session: include the target workspace in the create-session failure message - tests: rework WebShellSidebar tests for the WorkspaceSection UI (add the useWorkspace mock, query workspace buttons, cover primary->undefined), use the canonical DaemonWorkspaceCapability type, and add a createSession workspaceCwd forwarding test * fix(cli): harden dynamic workspace registration per review - POST /workspaces: bound cwd by MAX_WORKSPACE_PATH_LENGTH before any filesystem work, and return a generic 500 (log the full error to stderr) so responses can't leak internal filesystem paths - createDynamicWorkspaceRuntime: log a stderr warning when a workspace's settings can't be read, matching the startup secondary-workspace path * qwen: address PR review feedback (#6625) Dynamic workspace reloadDaemonEnv now mirrors the startup secondary path: after reloadEnvironment() it rebuilds the runtime env via buildRuntimeEnvironment(), calls wsEnv.replace(), and updates the env metadata (envFileReadFailed / envFileReadFailures / overlayKeys / envFilePaths). Without this, .env changes on a dynamically registered workspace never propagated to that workspace's spawned child processes. * qwen: address PR review feedback (#6625) Harden POST /workspaces and the workspace registry per review: - canonicalize with realpathSync.native (matches startup) so the same physical dir on a case-insensitive FS can't register twice - nesting guard now also checks in-flight registrations, closing a concurrent parent/child registration race - error responses no longer echo resolved/other-workspace paths - registry add() isolates onChange listener throws so a bad listener can't abort a caller after the workspace is already committed * qwen: address PR review feedback (#6625) - POST /workspaces: cap total registered workspaces (startup + dynamic) to guard against unbounded registration exhausting resources - createDynamicWorkspaceRuntime: register shutdown-cleanup arrays only after the runtime is fully built, so a throw during workspace-service construction can't orphan the bridge/channel - web-shell App: reset selectedWorkspaceCwd after session creation so the workspace picker is one-shot (next new chat defaults to primary) * qwen: address human review suggestions (batch 1) - WorkspaceSection: add console.warn on session-poll failure (was silent) - WorkspaceSection: add aria-expanded for screen readers - AddWorkspaceDialog: associate label/input (htmlFor/id), i18n the absolute-path error, accept Windows drive-letter paths - i18n: remove unused workspaceUntrustedHint key, add addWorkspaceAbsError * qwen: address human review suggestions (batch 2) - Remove dead CSS (.workspacePickerSelect, .workspaceItem* classes from the old select-based picker, replaced by WorkspaceSection) - Add title tooltip to single-workspace project name (shows full path) - WorkspaceSection: sync expanded state on workspace.primary change * qwen: address human review suggestions (batch 3) - DaemonWorkspaceProvider: refreshCapabilities now clears error on success and sets error+status on failure (was incomplete vs mount) - Remove unused onChange/WorkspaceRegistryEvent from workspace registry per simplicity-first (no consumer exists; defers API surface until a real subscriber like SSE push is needed) * qwen: add workspace-management route test coverage Tests cover: 501 (no factory), 400 (missing/empty/relative/long/ nonexistent cwd), 409 (duplicate canonical path), 201 (success), and verifying error messages are generic (no path leak). * qwen: fix CI build failure — add explicit types in route test The CLI's tsconfig includes test files in tsc --build, so all noImplicitAny violations in tests cause build failures. Add explicit type annotations to mock parameters. * qwen: add type/title to single-workspace add-button --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |