Files
fusion/packages/dashboard/app/utils/taskStatusBadgeLabel.ts
gsxdsm 99c9f14ee0 feat: run Plan Review in the planning lane with a Plan Review badge (#2462)
## What

Plan Review, planning, and the replan loop move from the implementation
column into the **planning lane** (`todo`), so a task under
specification never holds a WIP slot. The card crosses into
`in-progress` exactly once, at `parse`, released by the scheduler.

Operators also finally see a **Plan Review** badge while the gate runs —
it was previously invisible on the default workflow.

## The part that made it possible

Moving the node is ten lines. It was attempted three times and reverted
each time, because a graph run with no durable continuation replayed
from `start` and dragged an in-progress card *backward* out of the WIP
column, firing `abort-on-exit` and stranding it in a pre-WIP column with
no releaser.

So this PR adds the graph **entry contract** —
`resolveColumnResumeNode`:

| Card is in | Resumes at |
|---|---|
| `triage` | `start` |
| `todo` | `plan` |
| `in-progress` | `parse` — never re-plans, never moves backward |
| `in-review` | first review node — gates are not skipped |

`ir.columns` is ordered and that order is the lifecycle order; rework
and failure edges are excluded so the entry point is always the main
path. The proof it's the right fix: **`executor-task-done-invariant`
passes unmodified** after failing every previous attempt.

## Also in here

- **Release gate narrowed twice.** `isUnplannedForExecution` applies its
pre-release plan-review gate only when the node's column equals the
card's column *and* the group is enabled for the task. The enablement
check fixes a real deadlock — a task with Plan Review toggled off was
held forever waiting for evidence nothing would ever write.
- **Badge cleanup.** Gate badge reads "Plan Review" instead of the
ambiguous "Reviewing" and no longer hides behind a lane restriction; the
status badge stops duplicating it; `planning` renders as "Planning"
instead of the raw engine token.
- **Coding (Ideas)** renames its planner column to "Planning" (id `todo`
unchanged) and loses its private planning-node re-home — the graph it
clones is already plan-in-place.
- **New sweep** `reconcileUndeclaredTaskColumns` re-homes a row whose
column its workflow no longer declares. Written for a follow-up, kept
because it makes any column edit survivable.

## Test changes

Scheduler and release fixtures now model a card whose Plan Review passed
— the state every real card is in when the capacity sweep sees it. A
held unreviewed card is the gate working, and that path stays owned by
`pre-release-plan-review.test.ts`.

New `workflow-graph-entry-contract.test.ts` covers the invariant at
every lifecycle position, plus the gap-column and remediation-node
cases.

## Verification

Gate 299 + 70 + 10, dashboard badge suites 672, engine
workflow/entry/executor suites 147, core 122. Lint and typecheck clean.
Full engine suite sits at the pre-existing baseline (notifier /
plugin-runner / notification-service, untouched by this).

## Follow-up

Removing the Todo column entirely is a separate ~207-site
lifecycle-vocabulary refactor — planned in
`docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md`
(companion docs PR).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Plan Review now runs in the Planning lane before implementation
begins.
* Cards resume from their current workflow column without replaying
earlier steps.
* Added automatic recovery for cards stranded in outdated workflow
columns.
* **Improvements**
  * Renamed the Coding (Ideas) planner column to “Planning.”
* Refined Plan Review gating to respect enabled settings and the card’s
current column.
* Updated planning and Plan Review badges for clearer, consistent labels
across cards and lists.
* **Bug Fixes**
* Improved workflow transitions and release behavior around planning,
review, and execution.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 23:42:46 -07:00

66 lines
3.1 KiB
TypeScript

/*
FNXC:MergeQueue 2026-07-15-10:45:
AI merge sets task.status to reviewing/landing for most of the live merge window. Board/list badges must never show those raw engine strings; map the full active-merge pipeline to operator-facing Merging… (and Merging fixes… for merging-fix).
*/
import type { TFunction } from "i18next";
import { isActiveMergeStatus } from "../../../core/src/active-merge-status";
/*
FNXC:TaskStatusBadge 2026-08-19-00:00:
FN-8475 restores truthful status visibility: Coding (Ideas) deliberately plans in Todo,
so a real non-queued task status must not be hidden based only on its board column.
Queued remains an intake-only presentation exclusion at TaskCard and ListView call sites.
*/
export function hasTaskStatusBadge(status: string | null | undefined): boolean {
return typeof status === "string" && status.trim().length > 0;
}
export function getTaskStatusBadgeLabel(
status: string | null | undefined,
t: TFunction<"app">,
/*
FNXC:TaskStatusBadge 2026-07-19-02:55 (U12 / R2 / R11):
Workflow-step state wins over the raw status vocabulary. A card whose Plan Review is running
reads "Plan Review" — the step's own IR-declared name — instead of the engine token "planning"
or "needs-replan". Pass `getRunningWorkflowStepLabel(task)` here; omit it and the legacy status
mapping below is unchanged, so every existing caller keeps its behavior.
*/
workflowStepLabel?: string,
): string {
/*
FNXC:TaskStatusBadge 2026-07-19-09:40:
Every active-merge status ("merging", "merging-pr", "merging-fix", "reviewing", "landing") must
win over a still-running workflow-step label (a pre-merge step's startedAt-without-completedAt
state can survive into the merge pipeline). Checking the status before the workflow-step override
enforces this for every caller (TaskCard, ListView grouped rows, ListView table rows) instead of
relying on per-call-site pre-checks. "merging-fix" keeps its distinct "Merging fixes…" label.
*/
if (isActiveMergeStatus(status)) {
return status === "merging-fix"
? t("tasks.statusMergingFix", "Merging fixes…")
: t("tasks.statusMerging", "Merging…");
}
if (workflowStepLabel) return workflowStepLabel;
if (!status) return "";
/*
FNXC:TaskStatusBadge 2026-07-22-09:22:
FN-8493 requires operator copy "Revising" while a revise/replan cycle is in progress. Keep the
engine token "needs-replan" unchanged and map centrally so board cards and list rows agree.
*/
if (status === "needs-replan") {
return t("tasks.statusReplan", "Revising");
}
/*
FNXC:TaskStatusBadge 2026-07-26-14:05:
"planning" is an engine token, not operator copy, and it was only ever hidden because U12's
workflow-step override happened to cover the same cards. Now that the Plan Review gate has its own
badge, callers suppress the step-name override while that badge renders — which uncovered the raw
lowercase token underneath. Map it to the same "Planning" copy the transient-planner badge already
uses so the two paths cannot read differently.
*/
if (status === "planning") {
return t("tasks.statusPlanning", "Planning");
}
return status;
}