Completes the IR-driven lifecycle cutover: **the workflow IR becomes the
single source of truth for task lifecycle.** Node column assignments
move cards at runtime, every lifecycle predicate re-keys on column
traits instead of literal column ids, and the graph exclusively owns
review gates.
Plan (the spec for this work):
[`docs/plans/2026-07-18-001-refactor-ir-driven-lifecycle-cutover-plan.md`](docs/plans/2026-07-18-001-refactor-ir-driven-lifecycle-cutover-plan.md)
## What changed
**IR as runtime authority (R1, R2).** Graph traversal crossing a node
column boundary moves the card through the store's trait-hook `moveTask`
path, attributed `workflowMoveSource: "workflow-graph"` and emitting
`task:column-transition`. This replaces the executor's hardcoded
`moveTask(id, "in-review")` merge boundary and its handoff-invariant
allowlist. Scheduler, hold/release, self-healing, merger and
finalization now key on column traits
(`intake`/`hold`/`wip`/`merge-blocker`/`human-review`/`merge`/`complete`/`archived`/`timing`/`abort-on-exit`/`reset-on-entry`/`stall-detection`),
with rebound targets resolved per KTD-10.
**Single ownership of review gates (R4, R5).** Triage's out-of-graph
Plan Review gate is deleted; the graph is the sole author. `pending`
step results are CAS-claimed leases with owner and staleness floor
(KTD-4), so a crash/restart re-entry can no longer dispatch a second
reviewer and silently discard the losing verdict.
**Graph ownership is unconditional (R9).** The legacy execute fallback
is gone: `maybeExecuteWorkflowGraph` is now `executeWorkflowGraph`
returning `void`, `graphCompletion` is a required parameter, and a store
that cannot resolve a workflow fails closed rather than silently running
nothing. Also deleted, with a tombstone ratchet: `fn_review_step` and
its RETHINK/session-rewind machinery, `workflow-cutover.ts`,
`workflow-authoritative-driver.ts`, `workflow-parity-observer.ts`, and
the `graphCompletionInterceptors` map.
**`reviewLevel` becomes a creation-time preset (R6)** writing
`enabledWorkflowSteps`, with zero runtime reads.
**Upgrade path (R10).** Migration 0026 adds the durable per-node-entry
IR pin (KTD-3) and the one-time adoption stamp (KTD-8);
`planLegacyAdoption` is the single shared decision run by both the
startup sweep and the store-open reconcile, so pre-cutover rows are
adopted instead of freezing. A stale-binary guard refuses to open a
database migrated by a newer binary.
**Operator surfaces (R2, R11).** Four places still closed the column
set: the dashboard coerced every ingested task's column through the
legacy six-id enum (a card in a custom `Merging` column rendered in
**Triage**), `POST /tasks/:id/move` answered 400 for any
workflow-defined column, retry/reset/re-engage/unassign/spec-revise used
hardcoded move targets, and GitHub issue open/closed mapping
literal-compared `done`/`archived`. All now resolve from the task's
workflow by trait, each with a legacy fallback so `builtin:coding` is
byte-identical.
## Evidence
`builtin:coding` keeps its column ids and observable behavior
byte-compatible (R8, KTD-7), pinned by a characterization oracle. A new
**6-column benchmark acceptance suite** drives a user-authored workflow
— `Ideas → Todo → In-progress → In-review → Merging → Done` — asserting
the ordered transition trail, single-mover at the hold→wip seam (KTD-2),
column-role purity (R12), bounded review cycles from workflow config,
and park-in-place on failure (R3). The same fixture is proven
**editor-buildable** through the real save-validation path, plus
negative cases.
Verified locally on this branch, post-rebase:
- `pnpm test:gate` — green (engine-core 294/294, pg-gate 126/126,
ci-workflow 63/63)
- characterization oracle 59/59, tombstones 5/5, 6-column benchmark
11/11
- `tsc --noEmit` clean for `@fusion/core`, `@fusion/engine`,
`@fusion/dashboard` (both `tsconfig.json` and `tsconfig.app.json`)
## Known reds
- **`executor-task-done-invariant` → "moves a cleanly completed task to
in-review via the merge-node boundary"** — red on this branch. A
real-Postgres test whose graph re-entry rebounds the card to
`in-progress` after `execute()` returns. Not in the merge gate, so it
does not gate CI. Honest status: I could **not** verify it green on
pristine `main` — running main's tests in this worktree reuses built
artifacts and produced obviously polluted results, so I am not claiming
"pre-existing". It needs its own look.
- **`html2canvas` / FN-8309 — fixed here by deleting dead code.**
`packages/dashboard/app/utils/capture-screenshot.ts` imported
`html2canvas`, which is not a dependency of `@fusion/dashboard` and is
**not in `pnpm-lock.yaml` at all**, so it had never compiled in CI. The
file had **zero importers**. Main never caught it because PR Checks runs
only on pull requests (main's last PR Checks run was in June) while
main's own pushes run just the non-blocking Full Suite — so the required
**Typecheck** check was failing on *every* PR against main, including
this one. Inherited from `88b0db0f4` (FN-8309). **To restore when the
feature lands its dependency properly:** `git checkout 88b0db0f4 --
packages/dashboard/app/utils/capture-screenshot.ts` and add
`html2canvas` to `packages/dashboard/package.json` in the same change.
- **pg-gate rotating contention** — historically a different file set
each run with zero assertion failures. It passed 126/126 on the final
run here.
Two entries that were on the provisional ledger turned out **not** to be
pre-existing and are fixed in this PR: the
`workflow-graph-optional-step-fix` replan-cap pair were stale assertions
against U3's own contract change (cap-exhausted now *parks*
awaiting-approval and reports handled, rather than silently leaving the
task in place), and `executor-column-agent-seams` /
`executor-fast-mode-workflows` are green.
## Deferred follow-ups
- **Graph does not suspend at the ready-for-release seam.** A parked
`onNodeEntry` returns `void` and the node executes anyway, so within one
walk the card can run In-progress work while still displayed in Todo.
The benchmark models the scheduler explicitly for this reason and says
so at the seam. Making the graph actually suspend is U4-scope follow-up.
- **`needs-replan` reader migration.** Post-U3 the durable write happens
at the graph's own `plan-replan` seam, so the workflow *is* the writer
and the 14 readers form one coherent graph-owned loop — it is the
graph's durable replan signal wearing a legacy name, not un-migrated
legacy. The adoption census guard requiring that literal in
`executor.ts` is correct and stays. Migrating those readers to a
purpose-built run-state signal is a post-cutover naming change with its
own risk budget.
- **U9b seam-node refinement.** The merge substates
(`merging`/`merging-pr`/`merging-fix`) are adopted as `resume-graph`
rather than mapped to an exact re-entry node; naming a precise node
would require resolving the task's IR, which the adoption module
deliberately cannot do.
🤖 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**
* Workflows now drive task lifecycle columns, transitions, capacity
limits, review gates, and completion behavior—including custom
workflows.
* Tasks created with review levels automatically receive the
corresponding workflow review steps.
* Legacy in-progress tasks are automatically recovered during upgrades.
* Dashboard status badges now show the active workflow step name.
* Added safeguards for workflow changes, review ownership, database
compatibility, and workflow validation.
* **Bug Fixes**
* Fixed custom-column rendering and task movement.
* Improved merge-boundary handling and completion for workflows without
merge steps.
* Prevented cards from stalling, moving backward, or exceeding pooled
WIP capacity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds a shared urgency color source to priorityIndicator and wires it into every priority-glyph surface so low/normal/high/urgent read consistently by color, not just icon shape.
- priorityIndicator.tsx: add colorVar (low=info/blue, normal=muted, high=warning/amber, urgent=error/red) and new getPriorityColorVar() export as the single source of truth
- QuickEntryBox: tint the icon-only priority trigger and each option row in the priority picker with the matching urgency color
- TaskForm: tint the New Task inline priority glyph using the same color source
- TaskCard: render a colored priority glyph alongside the existing text label in the card-priority-badge (kept the non-normal visibility gate and badge geometry via a small CSS gap addition)
- Updated/added tests for QuickEntryBox, TaskCard (badge, badge-height, badge-wrap), and priorityIndicator to assert the new colors; added a patch changeset and a dashboard-guide.md doc update
Files changed:
.changeset/fn-7842-priority-color-coding.md | 7 +++++
docs/dashboard-guide.md | 7 +++--
.../dashboard/app/components/QuickEntryBox.tsx | 7 +++--
packages/dashboard/app/components/TaskCard.css | 2 ++
packages/dashboard/app/components/TaskCard.tsx | 6 +++-
packages/dashboard/app/components/TaskForm.tsx | 7 +++--
.../components/__tests__/QuickEntryBox.test.tsx | 17 +++++++++--
.../__tests__/TaskCard.badge-height.test.tsx | 5 ++++
.../__tests__/TaskCard.badge-wrap.test.tsx | 5 ++++
.../app/components/__tests__/TaskCard.test.tsx | 33 +++++++++++++++++++++-
.../app/utils/__tests__/priorityIndicator.test.tsx | 13 +++++----
packages/dashboard/app/utils/priorityIndicator.tsx | 16 ++++++++---
12 files changed, 103 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-7842
Fusion-Task-Lineage: 0b2d0d15-6e61-45fc-9378-bc09002bef55
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
optionalGroupIdSet falls back to builtin:coding (mirroring the executor's
unselected-task resolution) so a toggled built-in group id like
browser-verification is no longer downgraded to a legacy WS-xxx step row the
graph executor never matches. Create-time optional-step controls resolve
builtin:coding when no project default workflow is set so the toggles render.
First unit of the graph-native workflow-step refactor (see
docs/plans/2026-06-25-001-refactor-workflow-steps-graph-native-plan.md).
Fusion-Task-Id: FN-7039
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New Task dialog: deep options (models, branch, node, review level, auto-merge, workflow, github tracking, attachments) collapse behind an 'Advanced' disclosure (collapsed by default); inline Attach/Fast/Priority quick-add buttons (QuickEntryBox style) sit next to Plan, wired to existing TaskForm state.
- Memory view: cap the editor box to ~a page (max-height: 60vh) with internal CodeMirror scroll, so a long memory file no longer forces endless page scrolling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FloatingWindow: reusable non-blocking, draggable, smoothly-resizable window with focus-to-front z-index so multiple coexist (file browser + terminal + several task details open and movable at once).
- Task detail gains a Pop out (Maximize2) button in List + Board; App tracks multiple open floating task-detail windows (dedupe by id).
- New Task dialog is now a floating, draggable, resizable, non-blocking window; all quick-add controls visible without expanding (TaskForm forceMoreOptionsOpen).
- Dashboard Overview: removed the duplicate AI Engine row — View Board / View Agents moved into the first instance (the AI engine card, under Stop AI Engine).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Many loading states rendered bare "Loading…" text with no spinner
element, and a couple rendered an unstyled `loading-spinner` div with no
matching CSS (invisible). The global spin animation was never broken —
it rotates in Chromium and WebKit — so prior transform-box edits chased
a non-bug, validated only by a string-matching CSS test.
Add a shared <LoadingSpinner> (self-contained animated SVG, no
lucide-react import so it survives partial test mocks) and adopt it
across ~47 loading placeholders so every loading state shows a
consistent animated spinner. Includes a component test that asserts the
svg carries the animate-spin utility.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FNXC requirement-trace comments on the stepwise workflow-step seam,
the flowToIr v2-signal/byte-identity contract, and TaskForm's
optional-steps create-mode behavior
- Dropdown: aria-multiselectable, drop dead aria-labelledby, ArrowUp opens panel
- Dirty-state: NewTaskModal tracks enabledWorkflowSteps so toggles trigger discard prompt
- TaskForm: reset optionalStepsLoading on the no-workflow early return
- Node editor: pass plugin step templates into the optional-steps panel (both layouts)
- FNXC requirement comments on the new optional-steps components
Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch task base-branch selection to prefer existing branches while preserving custom entry when needed.
- fetch local git branches for TaskForm and sort common integration branches (main/master/trunk/develop) first
- render base branch as a dropdown with default and Custom… options, with fallback custom input mode and toggle back to dropdown
- update TaskForm/NewTaskModal tests for branch loading, dropdown ordering, unknown-branch fallback, and fetch failure behavior
- document the new branch dropdown + custom fallback behavior in dashboard guide branch planning rules
Files changed:
docs/dashboard-guide.md | 4 +-
packages/dashboard/app/components/TaskForm.tsx | 100 +++++++++++++++++++--
.../app/components/__tests__/NewTaskModal.test.tsx | 1 +
.../app/components/__tests__/TaskForm.test.tsx | 66 +++++++++++++-
4 files changed, 156 insertions(+), 15 deletions(-)
Fusion-Task-Id: FN-5716
Fusion-Task-Lineage: 0ed72020-d8a0-4675-92d3-9476f53a173a
Reorganized the TaskForm to move the GitHub tracking section and locked the create-form section order, with tests verifying the new layout and documentation added for the tracking placement in the dashboard guide.
Fusion-Task-Id: FN-3950
Implements a tracking auth resolver with forced GitHub client authentication mode, wiring it across the GitHub tracking lifecycle and settings UI. The feature spans six steps: adding the resolver, forced auth mode, routing tracking issue creation through the resolver, and wiring into lifecycle and s
Fusion-Task-Id: FN-3875
After dismissing and re-bringing up the mobile keyboard, iOS could
leave window.scrollY > 0 and visualViewport.offsetTop > 0. With
useMobileScrollLock then pinning body{position:fixed} relative to that
drifted scroll, the message thread anchored above the visible viewport
and a large blank area appeared below it.
handleInputFocus now resets window scroll to (0,0) on mobile in a
zero-delay timeout — late enough that iOS finishes its own
scroll-into-view first, but before useMobileScrollLock observes the
drifted state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Resolve merge conflicts in InlineCreateCard and QuickEntryBox using the existing node picker UX
- Replace ad-hoc node status rendering with NodeHealthDot in task creation surfaces
- Keep advanced quick-create controls and portal-based dropdown behavior intact
- Preserve related dashboard styling and tests included in the staged squash
Fusion-Task-Id: FN-2729
- Add node status indicators and labels to task creation, quick entry, task form, and settings node selectors
- Surface selected node health in list bulk-edit controls with status dot styling for online/connecting/offline/error states
- Update dashboard component CSS and shared styles for consistent node status presentation
- Include changeset documenting unavailable-node routing policy and default node behavior
- Add a core node-override-guard module, export it from @fusion/core, and enforce conflicts in store updates
- Add API route and CLI extension safeguards so nodeId override updates are blocked when ownership would conflict
- Wire node override routing and validation through dashboard quick-create, list, modal, settings, and task form/detail surfaces
- Add focused unit and integration tests for core guard logic, workflow routes, and dashboard node override UX
Review level select now treats the empty option as undefined (auto — let
triage decide) rather than coercing to 0/None, and relabels the option
accordingly. Widens onReviewLevelChange to accept undefined.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a new renderBelowModelConfiguration prop in TaskForm and render it beneath the Model Configuration section inside More options
- Update TaskDetailModal to use the new slot so the Source Issue edit block appears below model settings instead of near the primary section
- Adjust TaskDetailModal source-issue edit tests to expand More options before interacting with source fields
- Add ordering assertions in TaskForm and TaskDetailModal tests to verify the injected block sits between Model Configuration and Workflow Steps
- Add explicit executionMode props to TaskForm edit controls and render a Standard/Fast selector in More Options
- Track editExecutionMode state in TaskDetailModal and reset it consistently when opening, canceling, or syncing task changes
- Include executionMode in update payload diffing so standard→fast sends "fast" and fast→standard clears to null
- Add regression tests for TaskForm selector behavior and TaskDetailModal update payload handling
- Add an autoExpandMoreOptionsOnSelection prop to TaskForm and gate advanced-section auto-expansion behind it
- Disable auto-expansion in NewTaskModal so default-on workflow step preselection does not open More options on first render
- Mark collapsed advanced content with the hidden attribute to prevent interaction until expanded
- Add dashboard tests for default collapsed state, opt-out auto-expand behavior, and default-on workflow-step regression coverage
- Replace hardcoded spacing/transition values in NewTaskModal.css with design tokens
- Wire priority through task creation and update API payloads for dashboard clients
- Add priority selection to TaskForm, New Task modal, and inline create card with default/reset behavior
- Support priority editing and display in Task Detail modal plus non-default priority badges on task cards
- Extend dashboard styles and component tests to cover priority selectors, rendering, and mobile behavior
Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.
Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
.all()/.get() results via `as unknown as XxxRow[]` (the double cast is
required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
pi-ai concrete shapes; typed Claude stream event message fields.
72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clears the remaining no-unused-vars warnings across the dashboard app and
server, desktop main, and engine sources. Dead React state destructures are
collapsed to setter-only, unused props are underscore-prefixed to preserve
API shape, and unreferenced catch bindings are dropped. No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Extend core task types/store and dashboard API route handling to persist task reviewLevel
- Add review level controls to TaskForm, NewTaskModal, and TaskDetailModal flows
- Improve workflow step selector presentation in WorkflowResultsTab and styles for clearer review settings UX
- Document the new review level behavior and add route/form/modal tests to cover create and edit scenarios
- Add TaskForm renderBelowPrimary and hideDependencies props to support injected primary-section content
- Build NewTaskModal quick-fields for dependency selection and agent assignment above the More options panel
- Style quick-fields and mobile modal spacing with design tokens, including full-width triggers and 36px touch targets
- Expand modal and form tests to cover slot rendering, dependency hiding behavior, quick-fields visibility, and mobile CSS rules