Prevent transient automatic-retry tasks from appearing as terminal failures. - Centralize pending-recovery and manual-retry presentation rules. - Suppress failed styling, failure alerts, and Retry actions across list, card, and detail views. - Cover recovery timing and desktop/mobile task surfaces with regression tests. Files changed: .changeset/fn-8167-transient-retry-affordance.md | 7 ++ packages/dashboard/app/components/ListView.tsx | 16 ++--- packages/dashboard/app/components/TaskCard.tsx | 13 ++-- .../dashboard/app/components/TaskDetailModal.tsx | 13 ++-- .../app/components/__tests__/ListView.test.tsx | 38 +++++++++++ .../app/components/__tests__/TaskCard.test.tsx | 22 +++++++ .../__tests__/TaskDetailModal.rendering.test.tsx | 26 ++++++++ .../app/utils/__tests__/taskRecovery.test.ts | 77 ++++++++++++++++++++++ packages/dashboard/app/utils/taskRecovery.ts | 32 +++++++++ 9 files changed, 215 insertions(+), 29 deletions(-) Fusion-Task-Id: FN-8167 Fusion-Task-Lineage: 60d599e2-2e0e-46e7-ad16-cc6a836b5ac7 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
33 lines
1.2 KiB
TypeScript
33 lines
1.2 KiB
TypeScript
import type { Task } from "@fusion/core";
|
|
|
|
/**
|
|
* FNXC:TaskRecoveryAffordance 2026-07-16-12:00:
|
|
* FN-8167 treats a finite, strictly-future automatic recovery schedule as non-terminal.
|
|
* It wins over a stale `failed` status, so failed chrome and manual Retry render only
|
|
* after automatic recovery is no longer pending.
|
|
*/
|
|
export function hasPendingAutomaticRecovery(task: Task, nowMs = Date.now()): boolean {
|
|
const recoveryAtMs = Date.parse(task.nextRecoveryAt ?? "");
|
|
return Number.isFinite(recoveryAtMs) && recoveryAtMs > nowMs;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a task needs a human-initiated retry.
|
|
*
|
|
* FNXC:TaskRecoveryAffordance 2026-07-16-12:00:
|
|
* A nonzero `recoveryRetryCount` and elapsed `nextRecoveryAt` do not themselves make a
|
|
* task retryable: elapsed or absent schedules fall back to terminal-status rules. A
|
|
* strictly-future schedule suppresses manual retry regardless of status.
|
|
*/
|
|
export function isTaskManuallyRetryable(task: Task, nowMs = Date.now()): boolean {
|
|
if (hasPendingAutomaticRecovery(task, nowMs)) {
|
|
return false;
|
|
}
|
|
|
|
return task.status === "failed"
|
|
|| task.status === "stuck-killed"
|
|
|| task.status === "planning"
|
|
|| task.status === "needs-replan"
|
|
|| (task.stuckKillCount ?? 0) > 0;
|
|
}
|