Files
fusion/packages/dashboard/app/lib/step-display.ts
gsxdsm 32e8bbe459 FN-7612: fix off-by-one step number mismatch between task dialog and Activity tab
Introduces a single canonical step-number helper so the task-dialog step indicators agree with the Activity tab for the same underlying step.

- Add `getCanonicalStepNumber()` in `packages/dashboard/app/lib/step-display.ts`, returning the raw 0-based, PROMPT.md-numbered step index (Step 0 = Preflight), clamped to a valid range.
- Update `ActiveAgentsPanel` to derive its step/total-steps display from the new helper instead of adding its own +1 to `task.currentStep`.
- Update `TaskTokenStatsPanel`'s step-progress row to use the same canonical helper instead of its own +1 math.
- Add regression tests (`step-number-alignment.test.tsx`) asserting the task-dialog and Activity-tab step numbers stay in sync across surfaces.

Files changed:
 .../dashboard/app/components/ActiveAgentsPanel.tsx |   8 +-
 .../app/components/TaskTokenStatsPanel.tsx         |   7 +-
 .../__tests__/step-number-alignment.test.tsx       | 162 +++++++++++++++++++++
 packages/dashboard/app/lib/step-display.ts         |  57 ++++++++
 4 files changed, 229 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7612

Fusion-Task-Lineage: a33703f5-32cb-4d10-9153-399821517ea3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:06 -07:00

58 lines
3.0 KiB
TypeScript

/*
FNXC:TaskStepNumbering 2026-07-05-00:00:
Canonical step-number convention for the dashboard. Fusion's engine, executor tool
contracts (`fn_task_update`'s `step` arg, `fn_task_log`'s implicit step context, review
verdict/request log lines such as "code review requested for Step N", and
`step-session-executor.ts`'s `createWorkflowStepActivityRun` `stepIndex`) all use a
0-based step number that is IDENTICAL to the literal `### Step N:` numbering in
PROMPT.md, where Step 0 is Preflight. `task.currentStep` and `task.steps[i]` already
live in that same 0-based index space (task.steps[0] IS the Preflight step).
Before this fix, the task-dialog indicators (`ActiveAgentsPanel`, `TaskTokenStatsPanel`)
independently added `+ 1` to `task.currentStep` to render a "1-based" step number, while
the Activity tab / workflow-step activity runs / task log lines rendered the raw 0-based
`stepIndex`/`currentStep`. That meant the SAME underlying step showed as e.g. "Step 1"
in the task dialog and "Step 0" in Activity — an off-by-one that made operators unable
to tell which step was actually running (Runfusion/Fusion#1921).
Fix: every surface that displays a step number must derive it from this single helper,
which returns the raw (PROMPT-numbered, 0-based) step index — clamped into
`[0, totalSteps - 1]` so an out-of-range `currentStep` never renders as "Step 6/5" —
instead of re-deriving its own +1/-1 math. Do NOT reintroduce a per-surface `+ 1`;
if a "1-based ordinal" is ever desired for a NEW surface, it must be computed FROM this
helper's canonical number (`+ 1` at render time, clearly labeled), never from raw
`task.currentStep` directly, so it stays traceable back to the one convention.
*/
/** Minimal shape needed to compute the canonical step-number display. */
export interface StepNumberDisplayTask {
currentStep?: number | null;
steps?: unknown[] | null;
}
export interface StepNumberDisplayInfo {
/** Canonical 0-based step number (PROMPT.md convention — Step 0 is Preflight), clamped to a valid index when steps exist. */
stepNumber: number;
/** Total number of steps (`task.steps.length`), 0 when there are no steps. */
totalSteps: number;
/** False when the task has no steps at all (nothing to display). */
hasSteps: boolean;
}
/**
* Computes the canonical step number for display, matching the PROMPT.md /
* engine convention (0-based, Step 0 = Preflight) used by the Activity tab,
* workflow-step activity runs, and task log lines. Every surface that shows a
* numeric "Step N" for a task must call this instead of doing its own math on
* `task.currentStep`, so the number is guaranteed to agree everywhere.
*/
export function getCanonicalStepNumber(task: StepNumberDisplayTask | null | undefined): StepNumberDisplayInfo {
const totalSteps = task?.steps?.length ?? 0;
if (totalSteps === 0) {
return { stepNumber: 0, totalSteps: 0, hasSteps: false };
}
const raw = task?.currentStep ?? 0;
const stepNumber = Math.min(Math.max(raw, 0), totalSteps - 1);
return { stepNumber, totalSteps, hasSteps: true };
}