From 256c0fc04dfd3c2958122426c0ef7865343d261a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Fri, 4 Sep 2026 05:37:31 +0000 Subject: [PATCH] fix(web): keep change action spinners visible (#3339) Co-authored-by: Jelloeater's Agent Co-authored-by: Henry Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 --- .../task/changes-panel-file-row.tsx | 6 +- .../e2e/tests/git/git-changes-panel.spec.ts | 170 +++++++++++++++++- .../changes-file-action-feedback/plan.md | 132 ++++++++++++++ ...sk-01-keep-pending-file-actions-visible.md | 118 ++++++++++++ .../config-sync-reconciliation.md | 1 - .../specs/office/system-design/config-sync.md | 1 - docs/specs/ui/README.md | 56 +++--- .../acp-model-configuration-summary.md | 2 +- .../changes-file-action-feedback.md | 67 +++++++ .../changes-file-action-feedback.md | 102 +++++++++++ .../changes-file-row-containment.md | 2 +- .../ui/system-design/ci-pr-automation-01.md | 2 +- .../ui/system-design/ci-pr-automation-02.md | 2 +- .../ui/system-design/ci-pr-automation-03.md | 2 +- .../command-panel-task-activity-icons.md | 2 +- .../compact-workflow-step-navigation.md | 2 +- .../system-design/kandev-mcp-tool-results.md | 2 +- .../task-prompt-transcript-visibility.md | 2 +- 18 files changed, 626 insertions(+), 45 deletions(-) create mode 100644 docs/plans/changes-file-action-feedback/plan.md create mode 100644 docs/plans/changes-file-action-feedback/task-01-keep-pending-file-actions-visible.md create mode 100644 docs/specs/ui/requirements/changes-file-action-feedback.md create mode 100644 docs/specs/ui/system-design/changes-file-action-feedback.md diff --git a/apps/web/components/task/changes-panel-file-row.tsx b/apps/web/components/task/changes-panel-file-row.tsx index b4207d1535..356ae85bf8 100644 --- a/apps/web/components/task/changes-panel-file-row.tsx +++ b/apps/web/components/task/changes-panel-file-row.tsx @@ -202,12 +202,16 @@ function TreeModeFileActionSlot({ {isFinePointer && ( )}
diff --git a/apps/web/e2e/tests/git/git-changes-panel.spec.ts b/apps/web/e2e/tests/git/git-changes-panel.spec.ts index 6ca89a7da2..ee2b8ff429 100644 --- a/apps/web/e2e/tests/git/git-changes-panel.spec.ts +++ b/apps/web/e2e/tests/git/git-changes-panel.spec.ts @@ -4,7 +4,7 @@ import type { ApiClient } from "../../helpers/api-client"; import { waitForSessionState } from "../../helpers/session"; import { KanbanPage } from "../../pages/kanban-page"; import { SessionPage } from "../../pages/session-page"; -import type { Page } from "@playwright/test"; +import type { Page, WebSocketRoute } from "@playwright/test"; import fs from "node:fs"; import path from "node:path"; import { execSync } from "node:child_process"; @@ -94,6 +94,78 @@ async function openTaskSession(page: Page, title: string): Promise return session; } +type WorktreeFileAction = "worktree.stage" | "worktree.unstage"; + +type PausedWorktreeFileAction = { + action: WorktreeFileAction; + frame: string; + server: WebSocketRoute; +}; + +function isWorktreeFileActionFrame(part: string, action: WorktreeFileAction): boolean { + if (!part.trim()) return false; + try { + const frame = JSON.parse(part) as { type?: unknown; action?: unknown }; + return frame.type === "request" && frame.action === action; + } catch { + return false; + } +} + +function partitionWorktreeFileAction( + message: string | Buffer, + action: WorktreeFileAction, +): { frame: string; passthrough: string | null } | null { + if (typeof message !== "string") return null; + const frames = message.split("\n"); + const actionIndex = frames.findIndex((part) => isWorktreeFileActionFrame(part, action)); + if (actionIndex === -1) return null; + const passthrough = frames.filter((_, index) => index !== actionIndex).join("\n"); + return { + frame: frames[actionIndex]!, + passthrough: passthrough.trim() ? passthrough : null, + }; +} + +async function pauseNextWorktreeFileAction(page: Page) { + let armedAction: WorktreeFileAction | null = null; + let paused: PausedWorktreeFileAction | null = null; + + await page.routeWebSocket(/\/ws$/, (socket) => { + const server = socket.connectToServer(); + socket.onMessage((message) => { + const partition = armedAction ? partitionWorktreeFileAction(message, armedAction) : null; + if (partition && armedAction) { + paused = { action: armedAction, frame: partition.frame, server }; + armedAction = null; + if (partition.passthrough) server.send(partition.passthrough); + return; + } + server.send(message); + }); + server.onMessage((message) => socket.send(message)); + }); + + return { + arm(action: WorktreeFileAction) { + if (armedAction || paused) throw new Error("A worktree file action is already paused"); + armedAction = action; + }, + async waitForRequest(action: WorktreeFileAction) { + await expect + .poll(() => paused?.action ?? null, { + message: `${action} should reach the paused WebSocket boundary`, + }) + .toBe(action); + }, + release(action: WorktreeFileAction) { + if (paused?.action !== action) throw new Error(`No paused ${action} request`); + paused.server.send(paused.frame); + paused = null; + }, + }; +} + /** Create a non-passthrough (standard) agent profile for the mock agent. */ async function createStandardProfile(apiClient: ApiClient, name: string) { const { agents } = await apiClient.listAgents(); @@ -207,6 +279,102 @@ test.describe("Git Changes Panel", () => { await expect(session.changes.getByText("test-file.txt")).toBeVisible({ timeout: 15_000 }); }); + // @covers AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.1 + // @covers AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.2 + // @covers AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.3 + // @covers AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.4 + test("keeps stage and unstage progress visible after pointer leaves", async ({ + testPage, + apiClient, + seedData, + backend, + }) => { + const pausedAction = await pauseNextWorktreeFileAction(testPage); + const profile = await createStandardProfile(apiClient, "Git Pending Action Profile"); + + await apiClient.createTaskWithAgent( + seedData.workspaceId, + "Git Pending Action Test", + profile.id, + { + description: "Testing pending stage and unstage feedback", + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + repository_ids: [seedData.repositoryId], + }, + ); + + const session = await openTaskSession(testPage, "Git Pending Action Test"); + await session.waitForChatIdle(); + + const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo"); + const git = new GitHelper(repoDir, { + ...process.env, + HOME: backend.tmpDir, + GIT_AUTHOR_NAME: "E2E Test", + GIT_AUTHOR_EMAIL: "e2e@test.local", + GIT_COMMITTER_NAME: "E2E Test", + GIT_COMMITTER_EMAIL: "e2e@test.local", + }); + git.createFile("pending-action.txt", "base\n"); + git.createFile("pending-sibling.txt", "base\n"); + git.stageAll(); + git.commit("Add pending action fixtures"); + git.modifyFile("pending-action.txt", "base\nchanged\n"); + git.modifyFile("pending-sibling.txt", "base\nunchanged pending state\n"); + + await session.clickTab("Changes"); + await expect(session.changes).toBeVisible({ timeout: 10_000 }); + await session.expandChangesSection("unstaged-files-section"); + + const unstagedSection = testPage.getByTestId("unstaged-files-section"); + const unstagedRow = unstagedSection.getByTestId("file-row-pending-action.txt"); + const siblingRow = unstagedSection.getByTestId("file-row-pending-sibling.txt"); + await expect(unstagedRow).toBeVisible(); + await expect(siblingRow).toBeVisible(); + + pausedAction.arm("worktree.stage"); + await unstagedRow.hover(); + await unstagedRow.getByTitle("Stage file").click(); + await pausedAction.waitForRequest("worktree.stage"); + await testPage.mouse.move(0, 0); + + const pendingStageSlot = unstagedRow.getByTestId("file-row-icon-action-slot"); + expect(await unstagedRow.evaluate((row) => row.matches(":hover"))).toBe(false); + await expect(pendingStageSlot.locator(":scope > div")).toHaveCSS("opacity", "1"); + await expect(pendingStageSlot.locator(":scope > span[aria-hidden='true']")).toHaveCSS( + "opacity", + "0", + ); + await expect(pendingStageSlot.locator("svg.animate-spin")).toBeVisible(); + await expect(siblingRow.locator("svg.animate-spin")).toHaveCount(0); + + pausedAction.release("worktree.stage"); + await session.expandChangesSection("staged-files-section"); + const stagedSection = testPage.getByTestId("staged-files-section"); + const stagedRow = stagedSection.getByTestId("file-row-pending-action.txt"); + await expect(stagedRow).toBeVisible(); + + pausedAction.arm("worktree.unstage"); + await stagedRow.hover(); + await stagedRow.getByTitle("Unstage file").click(); + await pausedAction.waitForRequest("worktree.unstage"); + await testPage.mouse.move(0, 0); + + const pendingUnstageSlot = stagedRow.getByTestId("file-row-icon-action-slot"); + expect(await stagedRow.evaluate((row) => row.matches(":hover"))).toBe(false); + await expect(pendingUnstageSlot.locator(":scope > div")).toHaveCSS("opacity", "1"); + await expect(pendingUnstageSlot.locator(":scope > span[aria-hidden='true']")).toHaveCSS( + "opacity", + "0", + ); + await expect(pendingUnstageSlot.locator("svg.animate-spin")).toBeVisible(); + + pausedAction.release("worktree.unstage"); + await expect(unstagedSection.getByTestId("file-row-pending-action.txt")).toBeVisible(); + await expect(stagedRow).toHaveCount(0); + }); + // @covers AC-PLATFORM-WORKSPACE-GIT-STATUS-001.10 test("shows same path in staged and unstaged sections", async ({ testPage, diff --git a/docs/plans/changes-file-action-feedback/plan.md b/docs/plans/changes-file-action-feedback/plan.md new file mode 100644 index 0000000000..66e8bd77de --- /dev/null +++ b/docs/plans/changes-file-action-feedback/plan.md @@ -0,0 +1,132 @@ +--- +created: 2026-09-03 +status: implemented +requirements: + - REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001 +system_design: + - ../../specs/ui/system-design/changes-file-action-feedback.md +legacy_specs: [] +--- + +# Implementation Plan: Keep Changes File Action Feedback Visible + +## Overview + +Make per-file stage and unstage pending feedback override the tree row's idle +hover swap. One focused work order first captures the failure by pausing the +worktree request in Chromium, then makes the minimum shared component change +and verifies the completed stage and unstage transitions. + +## Confirmed root cause + +- `StageButton` correctly renders `IconLoader2` whenever `FileRow.isPending` is + true. +- In tree mode, `TreeModeFileActionSlot` wraps that button in a fine-pointer + layer whose visibility is controlled only by `group-hover` classes. +- The adjacent `FileIcon` also follows only the inverse hover rule. It does not + consider `isPending`. +- Leaving the row therefore makes the action layer, including its spinner, + transparent and restores the file-type icon even though the repository/path + pending key remains active. +- Coarse-pointer rows do not receive the hover-only classes, so their action + and spinner already remain visible. + +## Scope + +### In scope + +- Give pending state precedence over the fine-pointer file-icon/action hover + swap. +- Keep the existing spinner visible after pointer leave for both stage and + unstage operations. +- Preserve per-repository, per-path pending-state scoping. +- Add deterministic Chromium coverage that holds each WebSocket request open, + leaves the row, and observes the pending and completed states. +- Preserve the existing coarse-pointer action visibility and touch sizing. + +### Out of scope + +- Git operation dispatch, status polling, WebSocket contracts, and pending-key + lifecycle changes. +- Bulk actions, discard/edit feedback, cancellation, notifications, and new + copy. +- Changes-panel layout, path truncation, navigation, or mobile composition. + +## Technical approach + +Update `TreeModeFileActionSlot` in +`apps/web/components/task/changes-panel-file-row.tsx` so the fine-pointer idle +hover classes apply only when `isPending` is false. While pending, keep the +file-type icon hidden and the spinner layer visible independent of the row's +hover state. Continue rendering the same `StageButton`; do not introduce +component-local request state. + +Extend `apps/web/e2e/tests/git/git-changes-panel.spec.ts` with a narrow +WebSocket transport controller patterned after the existing request-pause +helpers. It shall pause only the armed `worktree.stage` or +`worktree.unstage` frame and forward all other frames. The regression creates a +target file (`pending-action.txt`) and a sibling file (`pending-sibling.txt`), +starts each action from the UI, waits until the request is paused, moves the +pointer to a neutral location, and asserts the existing spinner is still +visible only for the target row before releasing the request and observing the +file's new section. + +## Tests + +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.1 through .4:** the focused + Chromium scenario proves spinner visibility for both request types after + pointer leave, per-row scoping, and the final staged/unstaged section. +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.5:** the existing + `changes-panel-file-row.test.tsx` coarse-pointer assertion and + `mobile-changes-panel.spec.ts` stage/unstage path remain unchanged and green. + +## E2E tests + +- Add `keeps stage and unstage progress visible after pointer leaves` to + `apps/web/e2e/tests/git/git-changes-panel.spec.ts` in the Chromium project. +- No new Pixel 5 case is planned because the coarse-pointer branch already + keeps the action slot visible and this work changes neither its markup nor + its interaction. Existing mobile coverage exercises both stage and unstage + controls through the shared row. + +## Work orders + +- [x] [Task 01: Keep pending file actions visible](task-01-keep-pending-file-actions-visible.md) + +## Dependency order + +```text +Task 01 +``` + +The browser regression and component correction share one presentation +boundary and proceed sequentially through Red-Green-Refactor. + +## Verification results + +- RED: the focused Chromium scenario failed after pointer leave because the + pending action layer reached `opacity: 0` while `worktree.stage` remained + paused. +- GREEN: the production-build Chromium scenario passed both the paused stage + and paused unstage flows. A final no-build rerun after test formatting also + passed. +- The existing `changes-panel-file-row.test.tsx` suite passes all 16 tests, + including the coarse-pointer action visibility and touch-size assertion. +- Targeted ESLint, web typecheck, specification lint, and scoped diff checks + pass. + +## Risks + +- A visibility change that hides the file icon without explicitly revealing + the spinner layer could leave the slot blank after pointer leave. The E2E + assertion observes the rendered spinner, not only class names. +- A WebSocket pause helper can disrupt unrelated live updates if it buffers a + whole message instead of only the matching newline-delimited frame. The + helper must partition and forward unrelated frames immediately. +- Completion can move the row between sections and detach its locator. The + test must reacquire the row from the destination section before testing the + inverse action. + +## Open questions + +None. diff --git a/docs/plans/changes-file-action-feedback/task-01-keep-pending-file-actions-visible.md b/docs/plans/changes-file-action-feedback/task-01-keep-pending-file-actions-visible.md new file mode 100644 index 0000000000..43dd5e626b --- /dev/null +++ b/docs/plans/changes-file-action-feedback/task-01-keep-pending-file-actions-visible.md @@ -0,0 +1,118 @@ +--- +id: "01-keep-pending-file-actions-visible" +title: "Keep pending file actions visible" +status: done +wave: 1 +depends_on: [] +plan: "plan.md" +requirements: + - REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001 +acceptance_criteria: + - AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.1 + - AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.2 + - AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.3 + - AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.4 + - AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.5 +system_design: + - ../../specs/ui/system-design/changes-file-action-feedback.md +--- + +# Task 01: Keep Pending File Actions Visible + +## Summary + +Keep the tree-mode stage and unstage spinner visible after a fine-pointer user +leaves the acted-on row. Prove the repair by holding each outgoing worktree +request at the browser WebSocket boundary before changing the shared row. + +## In scope + +- Add a Chromium regression for stage and unstage pending feedback. +- Pause only the armed worktree action frame and forward unrelated WebSocket + traffic. +- Confirm the regression fails because the spinner becomes visually hidden + after pointer leave. +- Make `isPending` override the fine-pointer idle hover swap in + `TreeModeFileActionSlot`. +- Confirm only the acted-on file shows pending feedback and that completion + moves it to the expected staged or unstaged section. +- Preserve the coarse-pointer branch, its always-visible action, and its 44px + target. + +## Out of scope + +- Changes to Git execution, pending keys, status refresh, or error handling. +- Bulk, discard, edit, commit, or provider-file actions. +- New copy, loading primitives, notifications, cancellation, or progress + estimates. +- Changes-panel layout or responsive composition changes. + +## Acceptance + +- During an in-flight stage or unstage request, the acted-on row's spinner is + visibly rendered after the pointer leaves, and its file-type icon does not + replace the spinner. +- Releasing each request completes the current Git flow and moves the file to + the section matching its refreshed staged state. +- Idle fine-pointer hover behavior and coarse-pointer action reachability stay + unchanged. + +## Verification + +Run from the repository root: + +```bash +(cd apps/web && rtk pnpm e2e:run tests/git/git-changes-panel.spec.ts -- --grep "keeps stage and unstage progress visible after pointer leaves" --retries=0) +(cd apps/web && rtk pnpm exec eslint components/task/changes-panel-file-row.tsx e2e/tests/git/git-changes-panel.spec.ts) +(cd apps/web && rtk pnpm run typecheck) +rtk python3 scripts/lint-spec-files.py --all +rtk git diff --check -- apps/web docs/specs/ui docs/plans/changes-file-action-feedback +``` + +Before the production edit, run the exact Playwright scenario and record the +expected spinner-visibility failure. After the edit, rerun the scenario and +then the complete command set above. + +## Files likely touched + +- `apps/web/components/task/changes-panel-file-row.tsx` +- `apps/web/e2e/tests/git/git-changes-panel.spec.ts` +- `docs/plans/changes-file-action-feedback/plan.md` +- `docs/plans/changes-file-action-feedback/task-01-keep-pending-file-actions-visible.md` + +## Dependencies + +None. + +## Risks + +- Newline-delimited WebSocket messages can contain unrelated frames; the pause + controller must forward them instead of delaying the full message. +- The original row can detach after status refresh, so post-completion + assertions must use a locator scoped to the destination section. + +## Parallelism + +`sequential` + +## Inputs + +- `REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001` and its system design. +- `TreeModeFileActionSlot`, `StageButton`, and the existing per-file pending + state in `useSessionGit`. +- The current Chromium Changes-panel scenario and existing WebSocket + request-pause helpers. + +## Results + +- RED: `pnpm e2e:run tests/git/git-changes-panel.spec.ts -- --grep "keeps stage and unstage progress visible after pointer leaves" --retries=0` failed on the expected rendered-state assertion. After pointer leave, the pending stage layer had computed opacity `0`. +- GREEN: the same production-build command passed after pending state was given + precedence over the tree row's hover swap. The final formatted test passed + again with `--no-build`. +- `changes-panel-file-row.test.tsx` passes 16 tests, including the existing + coarse-pointer visibility and 44px target coverage. +- Targeted ESLint, `pnpm run typecheck`, `scripts/lint-spec-files.py --all`, + and the scoped `git diff --check` command pass. +- No Pixel 5 test was added because the changed conditions execute only for + fine pointers. The shared coarse-pointer branch and existing mobile + stage/unstage path are unchanged. diff --git a/docs/specs/office/system-design/config-sync-reconciliation.md b/docs/specs/office/system-design/config-sync-reconciliation.md index ce15ac2ea1..04c8d0f7a6 100644 --- a/docs/specs/office/system-design/config-sync-reconciliation.md +++ b/docs/specs/office/system-design/config-sync-reconciliation.md @@ -529,4 +529,3 @@ write leaves a correct manifest beside a stale status the next run overwrites. | Release fails partway on config delete | Stops at the failing entity; entities already released stay released and have no manifest row; config retained; retry resumes from the remainder. | | Manifest names an entity deleted out of band | Manifest entry dropped, run continues. | | Delete of a nonexistent config | Succeeds with no effect. | - diff --git a/docs/specs/office/system-design/config-sync.md b/docs/specs/office/system-design/config-sync.md index abbb57f0f4..ded2d9e32a 100644 --- a/docs/specs/office/system-design/config-sync.md +++ b/docs/specs/office/system-design/config-sync.md @@ -565,4 +565,3 @@ no step in this order can regress a shipped feature. - [GitLab workflow sync requirements](../../integrations/requirements/gitlab-workflow-sync.md) is the shipped provider contract this design reuses and whose write-back exclusion it follows. - diff --git a/docs/specs/ui/README.md b/docs/specs/ui/README.md index 1d82efcb64..0d9d9b2c3f 100644 --- a/docs/specs/ui/README.md +++ b/docs/specs/ui/README.md @@ -6,18 +6,12 @@ migration: in_progress owners: - kandev --- - # UI - ## Scope - UI owns responsive behavior; other systems own behavior/state. - ## Map - ### Requirements - -- [ACP Model Configuration Summary](requirements/acp-model-configuration-summary.md) +- [ACP model summary](requirements/acp-model-configuration-summary.md) - [ACP Shell Command Output](requirements/acp-shell-command-output.md) - [Adaptive Kanban](requirements/adaptive-kanban.md) - [Task Add-Panel PR Submenu](requirements/add-panel-pr-submenu.md) @@ -30,7 +24,8 @@ UI owns responsive behavior; other systems own behavior/state. - [Column visibility](requirements/board-step-visibility-filter.md) - [Inspect annotation submission](requirements/browser-inspect-annotations-save.md) - [Cancel-turn progress](requirements/cancel-turn-progress.md) -- [Changes File Row Containment](requirements/changes-file-row-containment.md) +- [Action feedback](requirements/changes-file-action-feedback.md) +- [File-row containment](requirements/changes-file-row-containment.md) - [Changes walkthrough](requirements/changes-walkthrough-toolbar-width.md) - [Task PR Automation Controls](requirements/ci-pr-automation.md) - [Merge Queue Recovery Controls](requirements/ci-pr-merge-queue-recovery-controls.md) @@ -47,7 +42,7 @@ UI owns responsive behavior; other systems own behavior/state. - [Context Window Reset Freshness](requirements/context-window-reset-freshness.md) - [Context Window Unmeasured State](requirements/context-window-unmeasured-state.md) - [Embedded VS Code Executor Availability](requirements/embedded-vscode-executor-availability.md) -- [Embedded VS Code Windows Availability](requirements/embedded-vscode-windows-availability.md) +- [VS Code windows](requirements/embedded-vscode-windows-availability.md) - [Empty-Turn Notice and Slash-Command Hint](requirements/empty-turn-notice.md) - [Entity Reference Composer](requirements/entity-reference-composer.md) - [Executor settings card spacing](requirements/executor-settings-card-spacing.md) @@ -63,13 +58,13 @@ UI owns responsive behavior; other systems own behavior/state. - [Repair merge commit details](requirements/merge-commit-details.md) - [Mermaid Rendering](requirements/mermaid-rendering.md) - [Message favorite star mobile sizing](requirements/message-favorite-star-mobile-size.md) -- [Message metadata dialog scroll containment](requirements/message-metadata-overflow.md) +- [Metadata dialog](requirements/message-metadata-overflow.md) - [Queue auto-merge](requirements/message-queue-auto-merge.md) - [Message queue management](requirements/message-queue-management.md) -- [Merge Enqueued Messages Individually](requirements/message-queue-merge.md) +- [Merge queued](requirements/message-queue-merge.md) - [Pin the Message Queue Panel](requirements/message-queue-pin.md) - [Reorder Queued Messages](requirements/message-queue-reorder.md) -- [Control Pending Message Auto-run](requirements/message-queue-run.md) +- [Pending auto-run](requirements/message-queue-run.md) - [Send Queued Messages Now](requirements/message-queue-send-now.md) - [Mobile topbar actions](requirements/mobile-quick-chat-topbar.md) - [Mobile Task Chrome](requirements/mobile-task-chrome.md) @@ -80,18 +75,18 @@ UI owns responsive behavior; other systems own behavior/state. - [Responsive PR Detail Header](requirements/pr-detail-header-width.md) - [Repair PR-only commit details](requirements/pr-only-commit-details.md) - [PR Task Status Summary](requirements/pr-task-status-summary.md) -- [Pull Request Walkthrough Generation](requirements/pr-walkthrough.md) +- [PR walkthrough](requirements/pr-walkthrough.md) - [Preview Sprites Transient Retry](requirements/preview-sprites-transient-retry.md) - [Persistent status motion](requirements/persistent-status-motion.md) - [Prompt History Panel](requirements/prompt-history-panel.md) - [Prompt paste: Nerd Font glyphs](requirements/prompt-paste-nerd-font-glyphs.md) - [Prompt turn duration](requirements/prompt-turn-duration.md) -- [Published Docs Preview Reliability](requirements/published-docs-preview-reliability.md) +- [Docs preview](requirements/published-docs-preview-reliability.md) - [Quick Chat elevation](requirements/quick-chat-elevation.md) - [Quick Chat viewport layout](requirements/quick-chat-viewport-layout.md) - [Quick Chat Idle Dot](requirements/quick-chat-idle-dot.md) - [Quick Chat and Terminal Tabs](requirements/quick-terminal.md) -- [Relative Last Seen in Account Security](requirements/relative-last-seen.md) +- [Relative last seen](requirements/relative-last-seen.md) - [Responsive Plan Formatting](requirements/responsive-plan-formatting.md) - [Resizable Markdown Table Columns](requirements/resizable-markdown-tables.md) - [Review File Status Cues](requirements/review-file-status.md) @@ -121,7 +116,7 @@ UI owns responsive behavior; other systems own behavior/state. - [Task Layout Profiles](requirements/task-layout-profiles.md) - [Task Agent Tab Reconciliation](requirements/task-agent-tab-reconciliation.md) - [Threads Deck](requirements/threads-conversation-deck.md) -- [Task Listing Display Preferences](requirements/task-listing-display-preferences.md) +- [Task listing](requirements/task-listing-display-preferences.md) - [Task transcript history visibility](requirements/task-prompt-transcript-visibility.md) - [Task Review Shortcut Switcher](requirements/task-review-shortcut.md) - [Task surface refresh and mobile create](requirements/task-surface-refresh.md) @@ -134,33 +129,32 @@ UI owns responsive behavior; other systems own behavior/state. - [Transcript Navigation Settings](requirements/transcript-navigation-settings.md) - [Voice Mode In Task Behavior](requirements/voice-mode-task-behavior.md) - [Walkthrough Feedback Controls](requirements/walkthrough-feedback-controls.md) -- [Stable Walkthrough Navigation](requirements/walkthrough-navigation-layout.md) +- [Walkthrough nav](requirements/walkthrough-navigation-layout.md) - [WebKit Task Dialog Rendering](requirements/webkit-task-dialog-rendering.md) - [Active workspace first in settings](requirements/workspace-active-first-order.md) - [WebSocket Connectivity Warning](requirements/ws-connectivity-warning.md) - ### Design - - [Clarification submit feedback](system-design/clarification-submit-feedback.md) -- [Growing Dialog Content Containment](system-design/dialog-content-containment.md) +- [Dialog containment](system-design/dialog-content-containment.md) - [Surface text](system-design/surface-text-hierarchy.md) - [Agent Todo List Panel](system-design/agent-todo-list-panel.md) - [App Status Bar](system-design/app-status-bar.md) -- [Changes File Row Containment](system-design/changes-file-row-containment.md) +- [Action feedback](system-design/changes-file-action-feedback.md) +- [File-row containment](system-design/changes-file-row-containment.md) - [Mention recency](system-design/composer-mention-recency.md) - [Composer Suggestion Overlays](system-design/composer-suggestion-overlays.md) -- [Compact Workflow Step Navigation](system-design/compact-workflow-step-navigation.md) -- [Kanban Preview Workflow Step Navigation](system-design/kanban-preview-workflow-step-navigation.md) +- [Compact step navigation](system-design/compact-workflow-step-navigation.md) +- [Kanban preview navigation](system-design/kanban-preview-workflow-step-navigation.md) - [Workflow column visibility (Part 1)](system-design/board-step-visibility-filter-01.md) - [Workflow column visibility (Part 2)](system-design/board-step-visibility-filter-02.md) -- [Task PR Automation Controls 1](system-design/ci-pr-automation-01.md) -- [Task PR Automation Controls 2](system-design/ci-pr-automation-02.md) -- [Task PR Automation Controls 3](system-design/ci-pr-automation-03.md) +- [Task PR auto 1](system-design/ci-pr-automation-01.md) +- [Task PR auto 2](system-design/ci-pr-automation-02.md) +- [Task PR auto 3](system-design/ci-pr-automation-03.md) - [Merge Queue Recovery Controls](system-design/ci-pr-merge-queue-recovery-controls.md) - [Entity Reference Composer](system-design/entity-reference-composer.md) - [File Tree Keyboard Scope](system-design/file-tree-keyboard-scope.md) -- [File Tree Chat Context](system-design/file-tree-chat-context.md) -- [Kandev MCP Tool Results](system-design/kandev-mcp-tool-results.md) +- [File Tree Chat](system-design/file-tree-chat-context.md) +- [MCP results](system-design/kandev-mcp-tool-results.md) - [Mobile Task Chrome](system-design/mobile-task-chrome.md) - [Persistent status motion](system-design/persistent-status-motion.md) - [Repository Groups](system-design/sidebar-repository-grouping.md) @@ -180,13 +174,11 @@ UI owns responsive behavior; other systems own behavior/state. - [Task Agent Tab Reconciliation](system-design/task-agent-tab-reconciliation.md) - [Threads Deck](system-design/threads-conversation-deck.md) - [Command-panel Sidebar Task Reveal](system-design/command-panel-sidebar-task-reveal.md) -- [Command Panel Task Activity Icons](system-design/command-panel-task-activity-icons.md) +- [Command-panel activity icons](system-design/command-panel-task-activity-icons.md) - [Terminal Rendering](system-design/terminal-rendering.md) - [Terminal Touch Scrolling](system-design/terminal-touch-scrolling.md) - [Thinking Message Preview](system-design/thinking-message-preview.md) -- [Task Transcript History Visibility](system-design/task-prompt-transcript-visibility.md) +- [Transcript history visibility](system-design/task-prompt-transcript-visibility.md) - [Transcript Auto-scroll Stability](system-design/transcript-auto-scroll.md) - ## Related - - [Platform](../platform/README.md) diff --git a/docs/specs/ui/requirements/acp-model-configuration-summary.md b/docs/specs/ui/requirements/acp-model-configuration-summary.md index d78983e130..36c7405737 100644 --- a/docs/specs/ui/requirements/acp-model-configuration-summary.md +++ b/docs/specs/ui/requirements/acp-model-configuration-summary.md @@ -5,7 +5,7 @@ created: 2026-07-15 owners: - kandev --- -# ACP Model Configuration Summary Requirements +# ACP model summary ## Overview diff --git a/docs/specs/ui/requirements/changes-file-action-feedback.md b/docs/specs/ui/requirements/changes-file-action-feedback.md new file mode 100644 index 0000000000..5909a19ebe --- /dev/null +++ b/docs/specs/ui/requirements/changes-file-action-feedback.md @@ -0,0 +1,67 @@ +--- +status: active +system: ui +created: 2026-09-03 +owners: + - kandev +--- + +# Changes File Action Feedback Requirements + +## Overview + +Working-tree file rows expose stage and unstage actions in the Changes panel. +On fine-pointer layouts, the action shares the file-type icon slot and normally +appears only while the row is hovered. Once an action starts, its progress +feedback must remain visible until the associated request finishes, even when +the pointer leaves the row. The UI system owns this transient presentation +contract; Git operation execution and pending-state lifetime remain owned by +their existing systems. + +## Terminology + +- **File action slot:** The leading Changes-row position that shows either the + file-type icon, the stage or unstage action, or its pending indicator. +- **Pending file action:** A stage or unstage request whose per-file pending + state has started and has not yet cleared. + +## Requirements + +### REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001: Persistent pending file-action feedback + +**Intent:** Let users identify which file operation is still running without +keeping the pointer over its row. + +**User story:** As a user staging or unstaging a file, I want its progress +indicator to remain visible until the operation finishes, so that I know when +the action is complete. + +#### Acceptance criteria + +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.1:** When a user starts a stage or + unstage action for one file, that row shall replace its stage or unstage + control with the existing animated pending indicator for the lifetime of the + file's pending state. +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.2:** On a fine-pointer layout, moving + the pointer away from a row with a pending file action shall keep the pending + indicator visible and shall not restore the file-type icon while the action + remains pending. +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.3:** Pending feedback shall remain + scoped to the acted-on repository and file. Other file rows shall retain + their current icons and actions. +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.4:** When the pending state clears, + the row shall return to its current idle presentation or move to the section + that reflects the refreshed staged state. +- **AC-UI-CHANGES-FILE-ACTION-FEEDBACK-001.5:** Coarse-pointer Changes surfaces + shall retain their always-visible, touch-sized file actions and show the same + pending indicator without introducing a hover dependency or reducing the + existing touch target. + +## Out of scope + +- Changing Git stage or unstage execution, pending-state creation, or + pending-state cleanup. +- Changing bulk stage, bulk unstage, discard, edit, or commit feedback. +- Adding user-facing copy, notifications, cancellation, or progress estimates. +- Changing Changes-panel navigation, grouping, row density, or scroll + ownership. diff --git a/docs/specs/ui/system-design/changes-file-action-feedback.md b/docs/specs/ui/system-design/changes-file-action-feedback.md new file mode 100644 index 0000000000..2f66a2b8a3 --- /dev/null +++ b/docs/specs/ui/system-design/changes-file-action-feedback.md @@ -0,0 +1,102 @@ +--- +status: current +system: ui +requirements: + - REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001 +--- + +# File-action feedback + +## Purpose and boundaries + +This design keeps the existing per-file stage and unstage spinner visible after +a fine-pointer user leaves the affected Changes row. It changes only the +presentation precedence inside the shared file-action slot. Git operation +dispatch, repository scoping, status refresh, and pending-state cleanup remain +unchanged. + +## Requirement mapping + +| Requirement | Design section | +| --- | --- | +| `REQ-UI-CHANGES-FILE-ACTION-FEEDBACK-001` | [Components and responsibilities](#components-and-responsibilities), [Visibility precedence](#visibility-precedence), [Responsive behavior](#responsive-behavior), and [Verification](#verification) | + +## Components and responsibilities + +- `useSessionGit` continues to own `pendingStageFiles`, keyed by repository and + path. It starts the per-file pending state before dispatching + `worktree.stage` or `worktree.unstage` and clears it through the existing + status-refresh and failure paths. +- `changes-panel-tree.tsx` and `changes-panel-timeline.tsx` continue to derive + `FileRow.isPending` from that set. No additional local loading state is added. +- `FileRow` continues to share one implementation across the desktop Changes + panel and the phone Changes surface. +- `TreeModeFileActionSlot` owns the fine-pointer swap between `FileIcon` and + `StageButton`. Pending state takes precedence over the idle hover rule. +- `StageButton` continues to replace either stage or unstage control with the + existing `IconLoader2` spinner while `isPending` is true. + +## Visibility precedence + +The tree-mode slot has three presentation states: + +1. On a fine pointer while idle and not hovered, the file-type icon is visible + and the stage or unstage action is hidden. +2. On a fine pointer while idle and hovered, the icon is hidden and the action + is visible and interactive. +3. While pending, the icon remains hidden and the action layer containing the + spinner remains visible regardless of hover. The pending layer stays + noninteractive through the existing spinner-only branch. + +The correction is expressed from `isPending` and the existing pointer mode. It +does not add timers, duplicate request state, pointer listeners, or a second +spinner. When pending state clears, the existing hover rule becomes +authoritative again or the refreshed Git status moves the file to its new +section. + +## Responsive behavior + +- **Desktop outcome:** the resizable Changes panel keeps the spinner in the + leading file-action slot after pointer leave, so the acted-on file remains + identifiable throughout the operation. +- **Mobile entry point and surface:** the existing Changes bottom-navigation + item opens `MobileChangesPanel`; the shared row keeps its always-visible + coarse-pointer action and 44px target. +- **Nearest shipped exemplar:** the current coarse-pointer branch in + `TreeModeFileActionSlot` already makes the action and pending branch + independent of hover. The desktop correction applies the same pending-state + precedence without changing desktop density. +- **Hierarchy and primary action:** file identity and the stage or unstage + control remain in the existing row. No drawer, route, or new navigation is + introduced. +- **Surface rationale:** this is short, row-scoped request feedback, so keeping + it inline preserves context better than opening a temporary surface. +- **Shared logic and geometry:** both viewport classes consume the same + `isPending` value. Scroll ownership, safe-area handling, path truncation, and + document overflow behavior remain unchanged. + +## Failure and recovery + +The UI does not infer completion from hover or elapsed time. It follows the +existing `isPending` input. Existing status-refresh and request-failure paths +clear that state; the slot then restores the idle icon/action presentation. +Backend and WebSocket errors remain surfaced through their current paths. + +## Verification + +- A Chromium Playwright regression pauses the outgoing `worktree.stage` + request, moves the pointer away from the row, and proves that the spinner + remains visible until the request is released and the file moves to the + staged section. +- The same regression pauses `worktree.unstage`, repeats the pointer-leave + assertion, and then proves the file returns to the unstaged section. +- The test intercepts only the selected worktree request at the WebSocket + transport boundary and forwards every unrelated frame. +- Existing coarse-pointer component and Pixel 5 Changes tests continue to prove + that stage and unstage actions are touch-reachable. No new mobile composition + or interaction is introduced. + +## Related decisions + +None. The change corrects local rendering precedence without changing a +runtime, data, or ownership boundary. diff --git a/docs/specs/ui/system-design/changes-file-row-containment.md b/docs/specs/ui/system-design/changes-file-row-containment.md index 226e740ee9..13d7ed2cfc 100644 --- a/docs/specs/ui/system-design/changes-file-row-containment.md +++ b/docs/specs/ui/system-design/changes-file-row-containment.md @@ -5,7 +5,7 @@ requirements: - REQ-UI-CHANGES-FILE-ROW-CONTAINMENT-001 --- -# Changes File Row Containment System Design +# File-row containment ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/ci-pr-automation-01.md b/docs/specs/ui/system-design/ci-pr-automation-01.md index 5ef2962991..ab3b0a89a9 100644 --- a/docs/specs/ui/system-design/ci-pr-automation-01.md +++ b/docs/specs/ui/system-design/ci-pr-automation-01.md @@ -7,7 +7,7 @@ created: 2026-06-18 owners: - tbd --- -# Task PR Automation Controls System Design Part 1 +# Task PR automation (part 1) ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/ci-pr-automation-02.md b/docs/specs/ui/system-design/ci-pr-automation-02.md index 2176e7d777..6003940145 100644 --- a/docs/specs/ui/system-design/ci-pr-automation-02.md +++ b/docs/specs/ui/system-design/ci-pr-automation-02.md @@ -7,7 +7,7 @@ created: 2026-06-18 owners: - tbd --- -# Task PR Automation Controls System Design Part 2 +# Task PR automation (part 2) ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/ci-pr-automation-03.md b/docs/specs/ui/system-design/ci-pr-automation-03.md index 20bdeefc92..291a82ffb8 100644 --- a/docs/specs/ui/system-design/ci-pr-automation-03.md +++ b/docs/specs/ui/system-design/ci-pr-automation-03.md @@ -7,7 +7,7 @@ created: 2026-06-18 owners: - tbd --- -# Task PR Automation Controls System Design Part 3 +# Task PR automation (part 3) ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/command-panel-task-activity-icons.md b/docs/specs/ui/system-design/command-panel-task-activity-icons.md index 33d309a79c..18c6fefd03 100644 --- a/docs/specs/ui/system-design/command-panel-task-activity-icons.md +++ b/docs/specs/ui/system-design/command-panel-task-activity-icons.md @@ -5,7 +5,7 @@ requirements: - REQ-UI-COMMAND-PANEL-TASK-ACTIVITY-001 --- -# Command Panel Task Activity Icons System Design +# Command-panel activity icons ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/compact-workflow-step-navigation.md b/docs/specs/ui/system-design/compact-workflow-step-navigation.md index 6bf0128c98..1252837a9a 100644 --- a/docs/specs/ui/system-design/compact-workflow-step-navigation.md +++ b/docs/specs/ui/system-design/compact-workflow-step-navigation.md @@ -5,7 +5,7 @@ requirements: - REQ-UI-COMPACT-WORKFLOW-STEP-NAVIGATION-001 --- -# Compact Workflow Step Navigation System Design +# Compact step navigation ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/kandev-mcp-tool-results.md b/docs/specs/ui/system-design/kandev-mcp-tool-results.md index a37b595fd6..e2003b4741 100644 --- a/docs/specs/ui/system-design/kandev-mcp-tool-results.md +++ b/docs/specs/ui/system-design/kandev-mcp-tool-results.md @@ -5,7 +5,7 @@ requirements: - REQ-UI-KANDEV-MCP-TOOL-RESULTS-001 --- -# Kandev MCP Tool Results System Design +# MCP tool results ## Purpose and boundaries diff --git a/docs/specs/ui/system-design/task-prompt-transcript-visibility.md b/docs/specs/ui/system-design/task-prompt-transcript-visibility.md index 3854db8211..101ceff138 100644 --- a/docs/specs/ui/system-design/task-prompt-transcript-visibility.md +++ b/docs/specs/ui/system-design/task-prompt-transcript-visibility.md @@ -8,7 +8,7 @@ owners: - kandev --- -# Task transcript history visibility system design +# Transcript history visibility ## Purpose and boundaries