Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs-web/user/dashboard/sprints.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ Click **Plan sprint**. The planner agent (typically a Gemini, Codex or Claude se
- Inferred `depends_on` edges.
- A best-effort `is_independent` flag.

While planning is in flight, the dashboard shows an ETA/elapsed overlay with a course indicator, request-specific status copy, minimize controls, and cancel/recovery actions when they are available.
While planning is in flight, the dashboard shows a shared planning overlay for normal sprints and quicksprints. It includes ETA and elapsed timers, request-specific status copy, a vessel that travels across the course and respawns smoothly, and minimize, cancel, and new sprint/quicksprint recovery actions when they are available. The ETA is a visual planning estimate; it does not change how the backend planner runs.

The vessel can also be activated as a small coffee reminder. Planning continues normally, and the minimize, new sprint/quicksprint, and cancellation controls remain available.

You can:

Expand Down
11 changes: 6 additions & 5 deletions docs/dashboard/dashboard-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,15 @@ Legacy runtime:
- The sprint composer prompt area renders a full-width editor until an original prompt exists, at which point it uses a split layout.
- Sprint key previews now reserve pending creation numbers in the page state, so opening `New Sprint` or `Quicksprint` while another create/plan request is still pending advances the visible key sequentially (for example `SPR-02` then `SPR-03`) and releases reservations after refresh or failure.
- When planning a sprint (`Plan Only` or `Plan & Start`), the pre-improvement raw prompt is saved to `originalPrompt` if it isn't already set, keeping the worker-improved text as the goal.
- The planning feedback overlay surfaces both an ETA countdown and an elapsed runtime timer. ETA comes from `GET /api/projects/:projectId/sprints/composer/eta`, computed server-side from the latest 10 planning invocations for the selected project, with a 3:00 fallback when no usable sample exists.
- The planning feedback overlay surfaces both an ETA countdown and an elapsed runtime timer. ETA comes from `GET /api/projects/:projectId/sprints/composer/eta`, computed server-side from the latest 10 planning invocations for the selected project, with a 3:00 fallback when no usable sample exists. The dashboard uses that estimate visually only; the overlay does not change backend planning behavior.
- When editing a sprint that already has planned tasks, the composer offers `Replan` (discard and regenerate subtasks), `Append Tasks` (open a task-creation modal pre-scoped to the sprint with dependency selection from existing tasks), and `Save Draft` (update name/goal only)
- The sprint composer includes a planning-agent selector that allows operators to choose an alternate planning preset (filtered for presets with a `planning` label) for the current sprint. Leaving this on the default `Planning agent` preserves existing behavior, and any selection is honored by `Plan ahead with AI`, `Plan Only`, `Plan & Start`, and `Replan`.
- Imported GitHub/GitLab/Jira issues render as linked issue cards directly under the Sprint Prompt field and are persisted with the sprint. Each card shows source metadata, state, labels, assignees, and whether conversation context is included. The prompt receives a linked-issues markdown section so planning sees the imported issue scope. Linked Jira imports also attempt the Settings -> Integrations -> Jira import transition, enabled by default as `In Work`; transition failures are returned as warnings and do not remove the local linked issue. Special remediation tasks render in a separate composer tray and are persisted through the imported-task endpoint instead of being folded into planning prose or moved through Jira import transitions.
- Settings -> Sprint -> Git Flow includes `Auto-close linked issues`, which closes imported GitHub/GitLab issues only after sprint completion and the main merge gate is no longer blocking. Jira auto-close is configured separately in Settings -> Integrations -> Jira and uses the close transition name, defaulting to `Done`; it is separate from the import transition.
- The sprint composer now features a visible, animated planning feedback overlay that replaces the generic spinner during `Plan ahead with AI`, `Plan Only`, `Plan & Start`, and `Replan` actions.
- Planning feedback is deterministic and staged, using an animated ship treatment (Wooden Ship for AI improvement, Container Ship for planning) that drifts across the composer based on elapsed time to make progress visible
- Planning and prompt-improvement requests continue server-side if the browser tab is refreshed or closed. Request initiation immediately disables duplicate composer actions, marks the composer busy for assistive technology, keeps the current form content visible, and announces state-specific pending copy through the existing action feedback region. The overlay's `Cancel Active Request` action sends an explicit cancellation request and leaves visible recovery feedback in the composer. Form validation focuses the first invalid required field for prompt improvement or submission attempts instead of silently leaving the operator in place. The `Save Draft` and `Append Tasks` modes also have defined progressive text for planning feedback, while `New Sprint` and `New Quicksprint` detach the current planning run from the visible composer, immediately reset the form controls, and leave the old run to finish without closing or mutating the fresh composer. Reduced-motion users see the planning overlay in a static-but-informative state rather than a moving ship animation.
- The sprint composer and quicksprint panel share a visible planning feedback overlay that replaces the generic spinner during `Plan ahead with AI`, `Plan Only`, `Plan & Start`, `Replan`, and quicksprint planning actions. It presents request-specific status copy, ETA/elapsed timers, and the same minimize/cancel/recovery model in both flows.
- Planning feedback is deterministic and staged, using an animated vessel treatment (Wooden Ship for AI improvement, Container Ship for planning and quicksprints) that travels across the course, exits cleanly to the right, and respawns offscreen on the left for the next pass so progress feels continuous without jumping.
- The vessel itself is an accessible interaction target: activating it swaps the vessel for a coffee reminder and keeps planning running. The reminder is a lightweight dashboard easter egg; it does not alter the request, timers, cancellation, or `New Sprint` / `New Quicksprint` recovery behavior.
- Planning and prompt-improvement requests continue server-side if the browser tab is refreshed or closed. Request initiation immediately disables duplicate composer actions, marks the composer busy for assistive technology, keeps the current form content visible, and announces state-specific pending copy through the existing action feedback region. The overlay's `Cancel Active Request` action sends an explicit cancellation request and leaves visible recovery feedback in the composer. Quicksprint requests expose `Cancel Quicksprint Request` in the same shared overlay and keep an inline cancel control available after minimizing. Form validation focuses the first invalid required field for prompt improvement or submission attempts instead of silently leaving the operator in place. The `Save Draft` and `Append Tasks` modes also have defined progressive text for planning feedback, while `New Sprint` and `New Quicksprint` detach the current planning run from the visible composer, immediately reset the form controls, and leave the old run to finish without closing or mutating the fresh composer. Reduced-motion users see the planning overlay in a static-but-informative state rather than a moving ship animation.
- Clicking `Minimize` fully dismisses the planning overlay action row (`Minimize`, secondary action, and overlay cancel button). Any progress restore affordance remains inside the composer/quicksprint panel layout instead of floating over unrelated page content.
- Sprints page `View Tasks`/`Open` links pass the sprint id to the Tasks page as `/tasks?sprintId=<id>`. The Tasks controller treats that value as a project-local route filter: it applies the sprint only when it belongs to the currently selected project, ignores stale sprint ids after navbar project changes, and clears invalid sprint query state without changing the global project selector.
- Settings now expose separate CLI retry controls for quota resets and rate limits, including the rate-limit delay and a max rate-limit retry count (`5` by default). Exact provider reset timestamps are honored, while ambiguous Codex wall-clock hints fall back to a bounded 30-minute retry. Session sync preserves quota/rate-limit dispatch errors so active retry timers remain visible, and runtime events plus invocation records surface the same `retryAfterIso` metadata the worker actually uses. Expired or missing cooldown metadata still requeues the task instead of leaving it stuck in `QUOTA`.
Expand Down Expand Up @@ -408,7 +409,7 @@ Legacy runtime:
- Sprint markdown export now includes direct download actions and per-section copy-to-clipboard buttons (with brief `Copied` confirmation) in the export modal
- The in-page sprint composer collapses into a stacked single-column layout on smaller screens, and both create and edit now use that same inline flow. The Quicksprint panel and the Sprint Composer are mutually exclusive; opening one automatically dismisses the other to maintain focus.
- The Quicksprint panel shows default and custom templates in one shared browse rail and includes a purpose selector for built-in template sets. The first shipped built-in purpose is `Fullstack JS App`, which groups six project-agnostic engineering and UI quicksprint templates loaded from `.code-ux/quicksprints/templates` and overrideable from project or home `.code-ux` directories. The browse rail uses a two-row horizontal slider with left/right paging controls, direct horizontal scrolling, vertical wheel forwarding so normal page scrolling still works over the panel, and project-local deletion markers for default templates. See [Quicksprint Templates](./quicksprint-templates.md).
- Quicksprint browse mode is browse-only: it changes how templates are discovered, but template execution still uses the same planning flow and the same subtask-count controls as before.
- Quicksprint browse mode is browse-only: it changes how templates are discovered, but template execution still uses the same planning flow, shared ETA/elapsed overlay, coffee reminder interaction, and subtask-count controls as sprint planning.
- The refreshed sprint ledger below the showcase renders as a responsive card/table hybrid: mobile rows collapse into touch-friendly sprint cards, desktop keeps sortable table scanning, and the header includes live visible/pinned/active/completed counters.
- The sprint ledger receives the full project sprint collection for counting, searching, sorting, selection, and task-count/progress accounting; the local `Show` selector is the only row-windowing layer, so large projects do not under-report sprints or task totals during initial render.
- The desktop ledger table now enforces mirrored per-column width guards (`w-*` + `min-w-*`) with a container-scoped horizontal scroller, preventing header/body overlap at narrow widths while avoiding page-level horizontal overflow.
Expand Down
3 changes: 3 additions & 0 deletions docs/dashboard/design-system-sprints.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ This document outlines the design system for the Sprints page and related planni
* Consistent treatment for async feedback states, planning ETA indicators, and linked issue chips.
* The expanded task append flows should transition smoothly and maintain context.
* Planning, replan, append, and prompt-improvement requests use client request IDs and `ActionFeedbackRegion`. The composer form sets `aria-busy` while a request is active, disables duplicate controls, preserves current field values, and exposes a `PlanningProgressOverlay` with cancel and "New Sprint" recovery actions when available. The overlay renders ETA/elapsed telemetry with the shared planning feedback ship-visual contract, using transform-based offscreen entry/exit phases and a static midpoint course under reduced motion.
* The shared sprint/quicksprint ETA overlay should keep ETA and elapsed timers legible, visually separate, and stable while the progress vessel moves. The vessel may exit the right side of the course and respawn offscreen on the left, but the progressbar label, staged status copy, and control row must remain static enough to scan during the loop.
* Reduced-motion mode must remove continuous travel and decorative SVG animation while preserving the static course, ETA/elapsed timers, progressbar semantics, visible status copy, and all cancel/minimize/recovery controls. Motion can clarify state, but it must never be the only indication that planning is active or cancellable.
* Interactive visual affordances inside the ETA overlay, including the coffee reminder easter egg, must use real button semantics, explicit accessible labels, visible focus rings, `aria-pressed` or status copy when state changes, and event handling that does not trigger backdrop dismissal. Activating the affordance must not hide `Minimize`, `New Sprint` / `New Quicksprint`, or request cancellation controls.
* Pending planning uses polite live-region feedback and `asyncFeedback`; blocking request failures use persistent assertive errors with retry actions; operator cancellation uses a non-auto-dismissing warning. On validation failure, custom validation runs because native validation is disabled, and focus moves to the first missing required field.
* Composer entry and field stagger use modal/list reveal timing and resolve to instant state changes under reduced motion. Reduced motion must not remove required progress, cancel, warning, or error copy.
* Quicksprint planning buttons expose `aria-busy` for the active request, describe disabled controls through the visible busy status, and keep the combined prompt expansion available through `aria-expanded`/`aria-controls`.
Expand Down
50 changes: 50 additions & 0 deletions tests/dashboard/v2/quicksprint-panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,56 @@ describe("QuicksprintPanel", () => {
resolveExecute!();
});

it("runs the shared planning overlay through quicksprint coffee, minimize, new, and cancel controls", async () => {
let capturedSignal: AbortSignal | null = null;
const executePromise = new Promise<void>(() => undefined);
const mockOnExecute = vi.fn((_templateId, _taskCount, _mode, _prompt, _route, _model, signal: AbortSignal) => {
capturedSignal = signal;
return executePromise;
});

const { getByRole, getByText, queryByRole, queryByText } = render(
<QuicksprintPanel {...defaultProps} onExecute={mockOnExecute} />
);

fireEvent.click(getByRole("button", { name: "API Tests" }));
fireEvent.click(getByRole("button", { name: "Plan Only" }));

const planningDialog = await waitFor(() => getByRole("dialog", { name: "Quicksprint in motion" }));
expect(mockOnExecute).toHaveBeenCalledTimes(1);
expect(within(planningDialog).getByText("ETA")).toBeInTheDocument();
expect(within(planningDialog).getByText("Elapsed")).toBeInTheDocument();
expect(within(planningDialog).getByRole("progressbar")).toBeInTheDocument();

const vesselButton = within(planningDialog).getByRole("button", { name: /turn planning vessel into a coffee break reminder/i });
fireEvent.click(vesselButton);

expect(vesselButton).toHaveAttribute("aria-pressed", "true");
expect(within(planningDialog).getByTestId("planning-coffee-cup")).toBeInTheDocument();
expect(within(planningDialog).getByText("Coffee break unlocked. Grab a fresh cup while planning keeps moving.")).toBeInTheDocument();
expect(within(planningDialog).getByRole("button", { name: "New Quicksprint" })).toBeInTheDocument();
expect(within(planningDialog).getByRole("button", { name: "Minimize" })).toBeInTheDocument();
expect(within(planningDialog).getByRole("button", { name: "Cancel Quicksprint Request" })).toBeInTheDocument();

fireEvent.click(within(planningDialog).getByRole("button", { name: "Minimize" }));

await waitFor(() => {
expect(queryByText("Quicksprint in motion")).not.toBeInTheDocument();
});
expect(capturedSignal?.aborted).toBe(false);
expect(queryByRole("button", { name: "Minimize" })).not.toBeInTheDocument();
expect(getByRole("button", { name: "New Quicksprint" })).toBeInTheDocument();
expect(getByRole("button", { name: "Cancel Request" })).toBeInTheDocument();

fireEvent.click(getByRole("button", { name: "Cancel Request" }));

await waitFor(() => {
expect(capturedSignal?.aborted).toBe(true);
});
expect(getByText("Cancelled plan only request for API Tests.", { selector: "p" })).toBeInTheDocument();
expect(queryByText("Planning only")).not.toBeInTheDocument();
});

it("blocks duplicate planning submissions and announces cancellation", async () => {
let capturedSignal: AbortSignal | null = null;
const executePromise = new Promise<void>(() => undefined);
Expand Down