feat(FN-2604): merge fusion/fn-2604 (auto-resolved)
- feat(FN-2604): complete Step 8 — add changeset and delivery docs - test(FN-2604): complete Step 7 — align tests and regenerate extension skill docs - test(FN-2604): complete Step 6 — update dashboard tests for planning statuses - feat(FN-2604): complete Step 5 — update CLI planning terminology - feat(FN-2604): complete Step 4 — update workflow route status strings - feat(FN-2604): complete Step 3 — update settings planning terminology - feat(FN-2604): complete Step 2 — rename replanning actions in column and spec editor - feat(FN-2604): complete Step 1 — update dashboard component status strings
This commit is contained in:
@@ -70,7 +70,7 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const [visibleTaskCount, setVisibleTaskCount] = useState(VISIBLE_TASKS_INITIAL);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isRespecifying, setIsRespecifying] = useState(false);
|
||||
const [isReplanning, setIsReplanning] = useState(false);
|
||||
const [isPausingAll, setIsPausingAll] = useState(false);
|
||||
const [isMovingAllToTodo, setIsMovingAllToTodo] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -161,16 +161,16 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
setVisibleTaskCount((current) => Math.min(current + VISIBLE_TASKS_INCREMENT, tasks.length));
|
||||
}, [tasks.length]);
|
||||
|
||||
const handleRespecifyAll = useCallback(async () => {
|
||||
const handleReplanAll = useCallback(async () => {
|
||||
setIsMenuOpen(false);
|
||||
if (tasks.length === 0) return;
|
||||
|
||||
const confirmed = window.confirm(
|
||||
`Move all ${tasks.length} todo task${tasks.length === 1 ? "" : "s"} back to triage to be respecified?`,
|
||||
`Move all ${tasks.length} todo task${tasks.length === 1 ? "" : "s"} back to planning to be replanned?`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setIsRespecifying(true);
|
||||
setIsReplanning(true);
|
||||
try {
|
||||
// Issue moves in parallel — onMoveTask is per-task, no bulk endpoint.
|
||||
const results = await Promise.allSettled(
|
||||
@@ -179,19 +179,19 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
const failed = results.filter((r) => r.status === "rejected").length;
|
||||
const moved = results.length - failed;
|
||||
if (failed === 0) {
|
||||
addToast(`Moved ${moved} task${moved === 1 ? "" : "s"} to triage for respec`, "success");
|
||||
addToast(`Moved ${moved} task${moved === 1 ? "" : "s"} to planning for replanning`, "success");
|
||||
} else {
|
||||
addToast(`Moved ${moved} of ${results.length} tasks; ${failed} failed`, "error");
|
||||
}
|
||||
} finally {
|
||||
setIsRespecifying(false);
|
||||
setIsReplanning(false);
|
||||
}
|
||||
}, [tasks, onMoveTask, addToast]);
|
||||
|
||||
const pauseEligibleTasks = useMemo(() => tasks.filter((task) => !task.paused), [tasks]);
|
||||
const pauseEligibleCount = pauseEligibleTasks.length;
|
||||
const hasColumnBulkActions = column === "todo" || column === "in-progress" || column === "in-review";
|
||||
const isMenuBusy = isRespecifying || isPausingAll || isMovingAllToTodo;
|
||||
const isMenuBusy = isReplanning || isPausingAll || isMovingAllToTodo;
|
||||
|
||||
const handlePauseAll = useCallback(async () => {
|
||||
if (!onPauseTask) return;
|
||||
@@ -332,12 +332,12 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className="column-menu-item"
|
||||
onClick={() => void handleRespecifyAll()}
|
||||
disabled={tasks.length === 0 || isRespecifying}
|
||||
onClick={() => void handleReplanAll()}
|
||||
disabled={tasks.length === 0 || isReplanning}
|
||||
>
|
||||
Respecify All
|
||||
Replan All
|
||||
<span className="column-menu-item-hint">
|
||||
Move {tasks.length} task{tasks.length === 1 ? "" : "s"} to Triage
|
||||
Move {tasks.length} task{tasks.length === 1 ? "" : "s"} to Planning
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,7 @@ const COLUMN_COLOR_MAP: Record<Column, string> = {
|
||||
archived: "var(--text-secondary)",
|
||||
};
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "specifying"]);
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging"]);
|
||||
|
||||
type SortField = "id" | "title" | "status" | "column";
|
||||
type SortDirection = "asc" | "desc";
|
||||
|
||||
@@ -882,7 +882,7 @@ export function SettingsModal({
|
||||
globalModelKey: "planningGlobalModelId",
|
||||
projectProviderKey: "planningProvider",
|
||||
projectModelKey: "planningModelId",
|
||||
helperText: "AI model used for task specification (triage).",
|
||||
helperText: "AI model used for task planning.",
|
||||
fallbackOrder: "Project override → Global planning lane → Global default lane → Automatic resolution",
|
||||
},
|
||||
{
|
||||
@@ -2102,7 +2102,7 @@ export function SettingsModal({
|
||||
setForm((f) => ({ ...f, maxTriageConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
|
||||
}}
|
||||
/>
|
||||
<small>Maximum concurrent triage/specification agents</small>
|
||||
<small>Maximum concurrent planning agents</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="pollIntervalMs">Poll Interval (ms)</label>
|
||||
@@ -2144,9 +2144,9 @@ export function SettingsModal({
|
||||
setForm((f) => ({ ...f, specStalenessEnabled: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
Enable specification staleness enforcement
|
||||
Enable plan staleness enforcement
|
||||
</label>
|
||||
<small>When enabled, tasks with stale specifications (PROMPT.md older than the threshold) are automatically sent back to triage for re-specification</small>
|
||||
<small>When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="specStalenessMaxAgeMs">Stale Spec Threshold (hours)</label>
|
||||
@@ -2163,7 +2163,7 @@ export function SettingsModal({
|
||||
}}
|
||||
disabled={!form.specStalenessEnabled}
|
||||
/>
|
||||
<small>Maximum age in hours before a specification is considered stale. Default: 6 hours.</small>
|
||||
<small>Maximum age in hours before a plan is considered stale. Default: 6 hours.</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="autoArchiveDoneTasksEnabled" className="checkbox-label">
|
||||
|
||||
@@ -163,7 +163,7 @@ export function SpecEditor({
|
||||
<div className="spec-editor-revision">
|
||||
<h4>Ask AI to Revise</h4>
|
||||
<p className="spec-editor-revision-help">
|
||||
Provide feedback for the AI to improve this specification. The task will move to triage for re-specification.
|
||||
Provide feedback for the AI to improve this specification. The task will move to planning for replanning.
|
||||
</p>
|
||||
<textarea
|
||||
ref={feedbackRef}
|
||||
|
||||
@@ -81,7 +81,7 @@ function abbreviateBadge(text: string, max: number): string {
|
||||
|
||||
const EDITABLE_COLUMNS: Set<Column> = new Set(["triage", "todo"]);
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "specifying"]);
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging"]);
|
||||
|
||||
const COLUMN_PROGRESS_COLOR_MAP: Record<Column, string> = {
|
||||
triage: "var(--triage)",
|
||||
|
||||
@@ -299,8 +299,8 @@ export function TaskDetailModal({
|
||||
const canRetryTask =
|
||||
task.status === "failed" ||
|
||||
task.status === "stuck-killed" ||
|
||||
task.status === "specifying" ||
|
||||
task.status === "needs-respecify" ||
|
||||
task.status === "planning" ||
|
||||
task.status === "needs-replan" ||
|
||||
(task.stuckKillCount ?? 0) > 0 ||
|
||||
(task.recoveryRetryCount ?? 0) > 0 ||
|
||||
Boolean(task.nextRecoveryAt);
|
||||
@@ -903,7 +903,7 @@ export function TaskDetailModal({
|
||||
if (!confirm("Reject this plan? The specification will be discarded and regenerated.")) return;
|
||||
try {
|
||||
await rejectPlan(task.id, projectId);
|
||||
addToast(`Plan rejected — ${task.id} returned to Triage for re-specification`, "info");
|
||||
addToast(`Plan rejected — ${task.id} returned to Planning for replanning`, "info");
|
||||
onClose();
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err), "error");
|
||||
@@ -911,11 +911,11 @@ export function TaskDetailModal({
|
||||
}, [task.id, onClose, addToast]);
|
||||
|
||||
const handleRespecify = useCallback(async () => {
|
||||
if (!confirm("Rebuild the specification for this task? The task will move to triage for re-specification.")) return;
|
||||
if (!confirm("Rebuild the plan for this task? The task will move to planning for replanning.")) return;
|
||||
try {
|
||||
await rebuildTaskSpec(task.id, projectId);
|
||||
onClose();
|
||||
addToast(`Respecifying ${task.id}...`, "info");
|
||||
addToast(`Replanning ${task.id}...`, "info");
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err), "error");
|
||||
}
|
||||
@@ -1152,8 +1152,8 @@ export function TaskDetailModal({
|
||||
setIsRequestingRevision(true);
|
||||
try {
|
||||
await requestSpecRevision(task.id, feedback, projectId);
|
||||
addToast("AI revision requested. Task moved to triage.", "success");
|
||||
// Task has been moved to triage, close modal
|
||||
addToast("AI revision requested. Task moved to planning.", "success");
|
||||
// Task has been moved to planning, close modal
|
||||
onClose();
|
||||
} catch (err) {
|
||||
const msg = getErrorMessage(err);
|
||||
@@ -1730,7 +1730,7 @@ export function TaskDetailModal({
|
||||
<div className="spec-editor-revision">
|
||||
<h4>Ask AI to Revise</h4>
|
||||
<p className="spec-editor-revision-help">
|
||||
Provide feedback for the AI to improve this specification. The task will move to triage for re-specification.
|
||||
Provide feedback for the AI to improve this specification. The task will move to planning for replanning.
|
||||
</p>
|
||||
<textarea
|
||||
className="spec-editor-feedback"
|
||||
|
||||
@@ -374,10 +374,10 @@ describe("TaskDetailModal", () => {
|
||||
expect(retryButtons).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("shows Retry for a stranded specifying triage task", () => {
|
||||
it("shows Retry for a stranded planning triage task", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ column: "triage", status: "specifying", stuckKillCount: 6 })}
|
||||
task={makeTask({ column: "triage", status: "planning", stuckKillCount: 6 })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2992,7 +2992,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(requestSpecRevision).toHaveBeenCalledWith("FN-099", "Please add more error handling details", undefined);
|
||||
expect(addToast).toHaveBeenCalledWith("AI revision requested. Task moved to triage.", "success");
|
||||
expect(addToast).toHaveBeenCalledWith("AI revision requested. Task moved to planning.", "success");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3219,7 +3219,7 @@ describe("TaskDetailModal", () => {
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
column: "triage",
|
||||
status: "specifying",
|
||||
status: "planning",
|
||||
prompt: "# Task Spec",
|
||||
})}
|
||||
onClose={noop}
|
||||
@@ -3325,7 +3325,7 @@ describe("TaskDetailModal", () => {
|
||||
expect(mockRejectPlan).toHaveBeenCalledWith("FN-001", undefined);
|
||||
});
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
"Plan rejected — FN-001 returned to Triage for re-specification",
|
||||
"Plan rejected — FN-001 returned to Planning for replanning",
|
||||
"info"
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user