18 KiB
status, system, requirements, updated
| status | system | requirements | updated | |||
|---|---|---|---|---|---|---|
| current | ui |
|
2026-09-10 |
Task Confirmation Surface System Design
Purpose and boundaries
This design owns the presentation contract for the shared still-working warning, the fine-pointer archive confirmation surface, and the cleanup consequence hierarchy used by task archive and delete workflows. It also owns the explicit discard choice for task worktrees. Task state and cleanup rules remain owned by the task runtime contract.
Requirement mapping
Phone archive surface selection is now owned by the mobile action confirmation design. The popover and inline mechanics below remain the desktop/tablet contract. Cleanup content and full task-delete consent remain owned here.
| Requirement | Design section |
|---|---|
REQ-TASKS-CONFIRMATION-WARNING-001 |
Components and responsibilities and Mobile and desktop containment |
REQ-TASKS-CONFIRMATION-SURFACE-002 |
Classification-gated surface selection, Popover width contract, Fine-pointer mounting, and Mobile and desktop containment |
REQ-UI-TASK-CLEANUP-CONFIRMATION-001 |
Task cleanup content model, Full-dialog composition, Compact archive surfaces, Source-specific archive routing, and Mobile and desktop containment |
Components and responsibilities
apps/web/components/task/task-still-working-warning.tsxremains the single markup and style owner.TaskArchiveConfirmDialogandTaskDeleteConfirmDialogrender it inside the existing full confirmation dialog.TaskArchiveConfirmationroutes the card-owned request to the existing dialog, desktop popover, or explicitly inline branch while reusing the same cleanup content and callbacks.- Consumers continue to decide whether a warning mounts. The shared component does not inspect task state or alter callbacks.
Task cleanup content model
getCleanupSummary and getBulkCleanupSummary remain the single place that
maps executor types to localized cleanup consequences. Their return value moves
from a flat lines array to structured effects and supporting notes. Effects
cover resources that are stopped, removed, or destroyed. Notes explain
unaffected repository scope or best-effort qualifications. The generic running
session effect remains present for every executor path.
The task subject sentence stays outside that model because archive and delete
describe different task outcomes. Single-task delete uses direct declarative
copy naming the task and its irreversibility. Archive uses corresponding
archive copy without describing archive as irreversible. Bulk strings retain
locale-aware count handling. Translations land together in the five real
catalogs: en, pt-pt, zh-cn, zh-hk, and zh-tw. The Traditional Chinese
catalogs are generated through the repository's i18n:zh-hant workflow. The
QA-only pseudo locale remains hidden in production, must stay synchronized
with en under i18n:check, and is regenerated with pnpm run i18n:pseudo.
A shared task-local renderer presents effects as a semantic list and notes as supporting prose. Full dialogs use paragraphs and a list. The existing compact archive popover and inline confirmation use the same ordered model with their established compact spacing. This removes copy drift without moving cleanup policy into a visual component.
Single-task delete also accepts an inherited-parent workspace context. When
that context is true, getCleanupSummary returns only the generic agent-session
effect and the localized note that the parent task's worktree, branch, and files
remain untouched. It does not return the worktree deletion effect or its
single-worktree reassurance. TaskDeleteConfirmDialog receives this context
from task-aware callers and, when a caller provides only taskId, resolves
workspaceMode from kanbanMulti.snapshots with kanban.tasks as the fallback.
If the task cannot be resolved, the existing executor-based summary remains the
safe fallback.
Full-dialog composition
TaskDeleteConfirmDialog keeps Radix AlertDialog and the existing centered
inset surface, including size="lg" on phones. Archive shares its state and
content across the existing non-phone alert and the new phone confirmation
surface described in the mobile design.
TaskDeleteConfirmDialog shows an unchecked discard selection only when fresh
inspection finds dirty worktrees in the selected scope. Clean results hide the
selection. Loading or failed inspection disables Delete without showing discard
consent. The label states that tracked and untracked files will be permanently
removed. Dirty results require explicit selection before Delete is enabled.
The task cleanup design
owns inspection, cascade scope, retry, and stale-response handling, including
retained worktrees whose task has no executor projection.
Each full surface uses an auto/minmax/auto layout: title in the first row, one
minmax(0, 1fr) body containing description, cleanup consequences, warning,
and cascade choice, then the persistent action footer. The surface is capped by
the dynamic viewport; the body owns vertical overflow. Short confirmations
retain intrinsic height, while long task names, bulk executor groups, longer
locales, warnings, and cascade copy remain reachable.
Both footer actions use min-h-11 w-full below sm, restoring compact
automatic dimensions at sm. Delete selects variant="destructive" on
AlertDialogAction and removes manual color utilities. This matters because
the action is slotted through Button: a default wrapper currently contributes
bg-primary while the child contributes bg-destructive, and stylesheet order
allows the primary color to win. Archive continues to use the default action
variant.
Compact archive surfaces
TaskArchiveConfirmation consumes the same structured cleanup model for its
fine-pointer popover and coarse-pointer inline confirmation. It keeps the
existing ActionConfirmPopover and InlineConfirmActions components, widths,
touch density, callbacks, and focus-return behavior. Only the internal copy
hierarchy changes: direct archive outcome, ordered effects, then supporting
notes and the existing still-working warning.
Source-specific archive routing
Phone archive uses a confirmation step in an open owned surface, or a compact
standalone bottom drawer from a page. This includes Kanban, task rows, direct
archive-flow callers, bulk archive, and the phone command panel's owned step.
The mobile branch precedes legacy forceDialog, inline, and pointer routing.
Cards and rows do not grow while the confirmation is open.
Fine-pointer desktop and compact-desktop retain the anchored popover; coarse-pointer tablet retains its inline zero-descendant surface. Non-phone command-panel callers retain explicit inline confirmation. The mobile design owns focus transfer, retained host content, and cancellation restoration.
Classification-gated surface selection
TaskArchiveConfirmation keeps descendant classification as the authoritative
input for choosing between the compact confirmation and the cascade dialog. A
standard fine-pointer caller does not mount a provisional popover while the
classification is idle or loading. A resolved zero count mounts the anchored
popover; a positive count mounts the full cascade dialog; and an error mounts
the same full dialog as the fail-safe path. No archive action is exposed before
that choice is known.
The controlled archive request remains open while classification is pending, so the final surface can mount without another user action. This avoids moving focus and assistive-technology context from a temporary popover into a dialog. It also preserves the existing final surfaces and their callbacks instead of adding cascade controls to the compact popover.
A non-rendering pending-dismissal controller retains the lifecycle previously owned by the provisional popover. Escape closes the controlled request and returns focus to the configured trigger; a new pointer interaction closes it without preventing the new target's interaction. If live data removes the originating anchor, the controller closes the request before classification can select a final surface. Closing disables the classification hook, whose cleanup ignores any late response, so a dismissed request cannot surface later. The controller mounts no role, action, or visual confirmation shell.
Callers whose final presentation is already known keep their current behavior. Non-phone forced and bulk operations may mount the full dialog immediately with its action disabled until classification settles. Phone archive may mount its known mobile surface with the same disabled action. Tablet inline confirmations retain their established loading treatment.
Popover width contract
ActionConfirmPopover gains a small width/size contract whose default remains
the current w-64 surface. The archive confirmation opts into a modest wider
variant, targeting w-72, with a viewport-aware maximum width such as
max-w-[calc(100vw-1rem)]. The class contract stays local to the shared
popover primitive, so watcher and other confirmation consumers do not widen
implicitly. The archive body keeps text-pretty wrapping and the existing
title/action hierarchy.
Fine-pointer mounting
At the TaskItemWithContextMenu adapter boundary, useResponsiveBreakpoint
determines where the already-created archive confirmation node mounts:
- Fine-pointer confirmation mounts as a sibling of the cloned task row inside
the existing anchor wrapper. It does not pass
archiveConfirmationintoTaskItem, soTaskItemdoes not add itsflex-wraprow branch or thebasis-fullaction slot for a portaled popover. - Tablet coarse-pointer confirmation continues through
TaskItem's existing inline action slot. Phone confirmation mounts beside the row and uses the mobile host, leaving the origin row's geometry unchanged.
Both branches reuse the same useTaskSwitcherArchiveConfirmation node,
callbacks, anchor ref, and focus-return ref. No business logic or duplicate
confirmation markup is introduced. The change removes the layout cause of
fine-pointer row growth instead of compensating for it with row dimensions.
Data and contracts
The component preserves data-testid="still-working-warning", role="alert",
the translated task:stillWorkingWarning and subject keys, and the existing
yellow border/background/text classes. The compact style contract is:
- warning container:
gap-1.5,p-2.5,text-xs,leading-5, andtext-pretty; - warning icon:
h-3.5 w-3.5,mt-0.5, andshrink-0; - existing rounded border, yellow semantic colors, and dark-mode contrast stay unchanged.
The dialog passes discardWorktreeChanges with the existing cascade choice.
The task API sends it as discard_worktree_changes=true. A typed HTTP 409 uses
task_delete_dirty_worktree when consent is absent. A shared client classifier
maps this code to localized feedback for single and bulk deletion surfaces.
Localization catalogs change in the five real locales. The Traditional Chinese catalogs use the existing generation command. No browser state is persisted.
forceDialog is an optional internal presentation prop with a default of
false. It is passed from the card-owned source to the existing routing
component and does not change archive commands, descendant classification,
preference state, mutation state, or callbacks.
Control flow
The existing task-level foregroundActivity projection and explicit
isInFlight props continue to determine whether the warning is rendered. The
dialog computes the localized task outcome and cleanup model during render,
then passes the model to the shared task-local renderer. Archive/delete
callbacks and domain state remain unchanged. Phone sources choose the shared
mobile surface before legacy forceDialog and explicit-inline selection.
Outside phone widths, descendant classification settles before a standard
fine-pointer request mounts its popover or dialog. Tablet coarse-pointer and
explicit non-phone inline callers retain their current routing. The mobile
design owns presentation and focus timing; archive commands, preference state,
classification and callbacks retain their existing ownership.
The delete dialog resets discard selection when it closes. On confirm, it sends the cascade and discard choices through the existing callback. A typed dirty conflict keeps the task in every local cache and shows one localized message. Other deletion errors keep their existing error handling.
Failure and recovery
A failed descendant classification selects the full dialog without first mounting a compact confirmation. If localized text is longer than the available width, the shared surface-text contract wraps it inside the body. If content is taller than the dynamic viewport, the body scrolls without moving the title or actions.
If deletion returns task_delete_dirty_worktree, the task remains visible. The
localized message tells the user to reopen the confirmation and select discard.
Unknown executor types retain the generic running session effect. If no task
activity is present, no warning mounts, as before.
Persistence
The browser does not persist discard selection or consent. Backend
cleanup-snapshot persistence, the discard_worktree_changes contract, and
typed HTTP 409 handling are defined in
Dirty worktree task deletion.
Observability
Existing component tests continue to cover warning visibility and cleanup content. Cleanup-summary tests cover every executor, bulk grouping, ordering, effects, and supporting notes. Delete-dialog tests cover selection reset, disabled actions, single and bulk callback values, cascade behavior, and typed conflict feedback. Dialog and compact-surface tests assert equivalent structured content, semantic action variants, unchanged callbacks, and that a deferred positive descendant result does not expose a provisional fine-pointer popover. The inherited-parent cleanup path is covered at both the summary and rendered dialog boundaries, including the task-id resolution fallback. Pending-dismissal component cases cover Escape, trigger focus restoration, and outside pointer intent. Rendered desktop and phone checks cover viewport bounds, scroll ownership, action reach, and document overflow. The desktop cascade E2E holds the descendant-count response, dismisses the invisible request, proves a late result cannot mount a confirmation, then reopens Archive and observes only the cascade dialog.
Mobile and desktop containment
The desktop check uses the real sidebar task row with a fine pointer. It
records the row getBoundingClientRect().height before opening Archive, while
the archive popover is visible, and after Cancel; all three values must remain
stable within subpixel precision. It also verifies the archive popover is
strictly wider than 256px and remains inside the viewport at compact widths.
For a parent task, a delayed descendant-count response leaves the confirmation
shell absent until classification settles; after release, only the full cascade
dialog is rendered and the anchored popover never becomes visible.
The phone Kanban check enters through a card overflow menu and expects an inset bottom confirmation even for a zero-descendant task. It retains stable card geometry, stacked actions, cleanup copy, theme contrast, cancellation/focus, and zero horizontal-overflow checks.
The phone task-switcher check expects one confirmation step inside the same drawer, unchanged row geometry, and restoration of list state/scroll on Cancel. It asserts reachable actions, one active scroll owner, and no horizontal overflow.
The task-delete phone check enters through the real task drawer action menu and opens Delete with a long task title and a longer bundled locale. After portal animations settle, it verifies the surface's 16px viewport insets, zero document horizontal overflow, title/body wrapping, body scroll ownership when needed, and visible persistent actions. The discard label has a 44px touch target and remains inside the scrolling body. Both footer actions remain full-width and at least 44px high. A desktop check retains compact row actions.