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