Fixes TaskDetailModal so manual PR affordances stay visible based on the live global auto-merge setting rather than the per-task effective override, and repairs a pre-existing test regression from the FN-7510 oversight default change.
- isManualPrFlow now checks mergeStrategy === "pull-request" && !autoMergeEnabled (live global setting) instead of the per-task effective auto-merge override, fixing a regression from FN-7255 that stranded users without manual PR controls when a task's auto-merge override was true but global auto-merge was off.
- Pinned plannerOversightLevel: "off" on the Chat-first default-routing test fixture so the FN-7510 autonomous-oversight default doesn't add an extra Activity-view option and break the test's actual intent (asserting Chat-first tab routing).
- Added changeset documenting the fix.
Files changed:
.changeset/fn-7607-manual-pr-flow.md | 7 +++++++
packages/dashboard/app/components/TaskDetailModal.tsx | 14 +++++++++++++-
.../TaskDetailModal.attachments-and-tabs.test.tsx | 12 +++++++++++-
3 files changed, 31 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7607
Fusion-Task-Lineage: f0b077d4-792f-4e43-8e40-43d325920be5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Unifies the previously mobile-only Oversight overflow menu into a single, always-present dropdown that replaces the scattered desktop oversight buttons and the separate mobile affordance.
- Replace discrete desktop oversight action buttons in TaskDetailModal's footer with one universal "Oversight actions" dropdown trigger, reusing the menu across desktop and mobile breakpoints.
- Simplify TaskDetailModal.tsx footer rendering logic, removing now-redundant responsive branching for oversight controls.
- Update TaskDetailModal.css to drop the old mobile-only oversight-overflow styles and support the unified dropdown across breakpoints.
- Update definition-actions, oversight-controls, oversight-mobile, rendering, and responsive-and-dependencies tests to assert the single dropdown behavior and disambiguate the exact "Actions" button query from the new "Oversight actions" aria-label.
- Refresh docs/dashboard-guide.md to describe the unified oversight dropdown UX.
Files changed:
docs/dashboard-guide.md | 14 +-
packages/dashboard/app/components/TaskDetailModal.css | 89 +++++------
packages/dashboard/app/components/TaskDetailModal.tsx | 177 +++------------------
packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx | 70 ++++----
packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-controls.test.tsx | 130 ++++++++++-----
packages/dashboard/app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx | 42 +++--
packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx | 27 ++--
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 57 ++++---
8 files changed, 278 insertions(+), 328 deletions(-)
Fusion-Task-Id: FN-7604
Fusion-Task-Lineage: afbb7573-d654-48db-a9ac-aecbd8e22e46
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Give the TaskDetailModal priority chip distinct tinted borders/backgrounds per level so low/high/urgent/normal are visually distinguishable at a glance.
- Add per-level border-color and stronger background overrides for .detail-priority-chip.card-priority-badge--{low,high,urgent}, using the matching semantic color token (info/warning/error) with higher specificity than the shared base rule.
- Leave the FN-7585 shared base chip rule and FN-7597 neutral 'normal' treatment untouched; scope changes strictly to .detail-priority-chip so read-only TaskCard badge tints are unaffected.
- Add a regression test asserting each level has distinct, non-var(--border) border-colors and backgrounds, mutually distinct across levels, while the read-only TaskCard badge selectors remain unchanged.
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 33 ++++++++++
...etailModal.responsive-and-dependencies.test.tsx | 71 ++++++++++++++++++++++
2 files changed, 104 insertions(+)
Fusion-Task-Id: FN-7601
Fusion-Task-Lineage: 1e2fc574-835c-43b0-8689-02884ad5c2d6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
A present, unexpired Anthropic subscription OAuth token that lacks an
inference scope (e.g. a profile-only grant) authenticates identity but
403s on every model call. /auth/status previously validated only token
presence + expiry, so it reported such a token as connected while all
inference failed. It now treats an inference-incapable Anthropic OAuth
token as not-connected (authenticated:false, expired:true so the
re-login banner fires) with a scope-specific loginError. Gated to
Anthropic providers only; tokens with no recorded scopes are treated as
usable to avoid false negatives on fresh logins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes overlapping Record and Clear buttons on the Keyboard Shortcuts settings rows by replacing the icon-only button class with a text button class and locking layout with flex-shrink.
- Swap ShortcutCaptureInput Record/Clear buttons off the icon-only `btn-icon` class (which forced line-height:0 and a 36px mobile square, clipping labels) onto a text-button class
- Add `.shortcut-capture` row CSS with `flex-shrink:0` on controls so the input and buttons never overlap and stack cleanly on mobile
- Add regression tests covering the Keyboard Shortcuts section layout
- Add changeset documenting the fix
Files changed:
.changeset/fn-7602-shortcut-row-layout.md | 7 ++
.../dashboard/app/components/SettingsModal.css | 17 ++++
.../settings/sections/ShortcutCaptureInput.tsx | 14 +++-
.../__tests__/KeyboardShortcutsSection.test.tsx | 95 ++++++++++++++++++++++
4 files changed, 131 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7602
Fusion-Task-Lineage: 50cf6975-f0fb-42dd-87b0-50578977a0f4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Attach the transient plannerOverseerState snapshot to the single-task detail route so the Nudge control reflects live overseer observation instead of always showing the periodic-observation message.
- GET /api/tasks/:id now best-effort attaches plannerOverseerState (mirrors the list route), never throwing on enrichment failure.
- TaskDetailModal reads overseerSnapshot from workingTask (merged full-detail object) instead of the raw task prop, so detail refetches via fetchTaskDetail (dependency chips, Documents view, logs, post-open refetch) no longer drop the snapshot.
- Added regression tests for the detail-route enrichment and the modal's Nudge-availability behavior.
- Added a patch changeset documenting the fix.
Files changed:
.changeset/fn-7600-oversight-nudge-detail-snapshot.md | 7 ++
packages/dashboard/app/components/TaskDetailModal.tsx | 14 ++-
.../TaskDetailModal.oversight-controls.test.tsx | 131 +++++++++++++++++++++
.../__tests__/tasks-planner-overseer-state.test.ts | 95 +++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 25 +++-
5 files changed, 269 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7600
Fusion-Task-Lineage: 500614d0-091a-461c-8e7b-329a7b791502
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Aligns the task-detail Priority dropdown's size, border, and typography with the Oversight dropdown so both controls read as one consistent style.
- Give the untinted `normal` priority level a neutral, token-based chip background (scoped to `.detail-priority-chip.card-priority-badge--normal`) instead of an empty bordered shell, matching the Oversight `--off` chip treatment.
- Remove the Priority-only forced uppercase text-transform on the select/option so it relies on the ancestor label's uppercase transform like the Oversight select does.
- Add regression coverage asserting shared box-size/border tokens across the Priority chip, Oversight chip, and mobile Oversight overflow trigger, no duplicated text-transform overrides, preserved low/high/urgent semantic tints, and unaffected --saving state.
- Add a patch changeset documenting the fix.
Files changed:
.changeset/fn-7597-priority-dropdown-matches-oversight.md | 7 +++
packages/dashboard/app/components/TaskDetailModal.css | 27 ++++++++++--
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 51 ++++++++++++++++++++++
3 files changed, 82 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7597
Fusion-Task-Lineage: d703e59a-35d8-4788-9ad2-1462d6f3c588
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Tasks added to a workflow whose intake column differs from the default
(e.g. Coding (Ideas) -> "ideas") disappeared from the board until a manual
reload. The board resolves a card's lane from the board-workflows
taskWorkflowIds map, which only refetches on mount/focus/workflow-CRUD SSE
-- never on task creation. A freshly created card was absent from that map,
fell back to the default workflow (no "ideas" column), and was dropped from
every lane.
- Board.tsx: force one board-workflows refetch (deferred a tick,
signature-guarded) whenever a rendered task is missing from taskWorkflowIds,
so its real workflow + intake column resolve for any create surface.
- Board.tsx: re-home a selected-workflow task whose column the workflow no
longer declares into the intake lane instead of a phantom bucket.
- useBoardWorkflows.ts: widen refreshBoardWorkflows type to accept forceFresh.
- Add regression tests for tasks arriving via the tasks prop (SSE / non-board
create surfaces) and the orphan-column safety net.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swaps the planner-overseer status badge from an uppercase text pill to a compact icon glyph, keeping accessibility text on aria-label/title.
- Render a small lucide-react Eye icon instead of the state-label text inside the overseer badge
- Keep the readable state name on aria-label and the composed tooltip on title for accessibility
- Add per-state coloring (watching/steering/recovering/awaiting-confirmation) keyed off the data-planner-overseer-state attribute in TaskCard.css, sized tightly around the icon
- Update TaskCard tests to assert the icon renders and the accessible name moved to aria-label instead of textContent
Files changed:
packages/dashboard/app/components/TaskCard.css | 40 ++++++++++++++++++++++
packages/dashboard/app/components/TaskCard.tsx | 14 ++++++--
.../app/components/__tests__/TaskCard.test.tsx | 25 +++++++++++---
3 files changed, 72 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7592
Fusion-Task-Lineage: d7ca93a6-9236-4fa0-a829-80f5b99dbd5f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a presentation-only enter animation for mobile task-detail surfaces (modal and board main-panel), layered on top of the existing FN-7583/FN-7586 dismissal routing, without altering close/back timing.
- Gate a new `.task-detail-modal--mobile-transition` class in TaskDetailModal.tsx via a local resize listener at the 768px breakpoint, mirroring the existing OVERSIGHT_MENU_MOBILE_BREAKPOINT pattern
- Add matching `.task-detail-main-panel--mobile-transition` modifier in MainContent.tsx gated by the existing isMobile prop
- Add slide/fade keyframe animations in TaskDetailModal.css and styles.css, both honoring prefers-reduced-motion
- Add regression tests covering the modal and board-panel mobile transition behavior
- Document the Capacitor WebView limitation preventing a true interactive predictive-back in packages/mobile/README.md
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 33 ++
.../dashboard/app/components/TaskDetailModal.tsx | 31 +-
...skDetail.mobile-transition.board-panel.test.tsx | 333 +++++++++++++++++++++
.../TaskDetail.mobile-transition.test.tsx | 156 ++++++++++
.../app/components/dashboard/MainContent.tsx | 10 +-
packages/dashboard/app/styles.css | 35 +++
packages/mobile/README.md | 30 ++
7 files changed, 626 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7587
Fusion-Task-Lineage: cc5f08df-4aaf-447d-9c30-237b32191d3f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Enables the WKWebView interactive-pop (edge-swipe-back) gesture on iOS, matching the Android predictive-back parity already in place, by patching the generated AppDelegate.swift during cap sync.
- Add packages/mobile/scripts/patch-ios-webview.ts: patches AppDelegate.swift to cast the root view controller to CAPBridgeViewController and set webView?.allowsBackForwardNavigationGestures = true before the didFinishLaunchingWithOptions return, is idempotent, and no-ops safely when no ios/ project exists yet
- Wire capacitor:sync:after to run both patch-android-manifest.ts and patch-ios-webview.ts so cap sync keeps both native back-gesture opt-ins in sync; add patch:ios-webview script
- Add unit tests covering patch, idempotency, already-patched, missing-project, and source-preservation cases for the new iOS webview patch, alongside the existing Android manifest patch tests
- Add TaskDetail.swipe-back.test.tsx coverage proving the shared popstate-driven nav-history dismissal stack (no iOS-specific native-back emitter needed) already satisfies the gesture's dismissal contract
- Add mobile-scripts.test.ts dashboard coverage and update MOBILE.md / packages/mobile/README.md documenting the new iOS gesture opt-in
Files changed:
MOBILE.md | 12 ++
.../dashboard/app/__tests__/mobile-scripts.test.ts | 31 +++++
.../__tests__/TaskDetail.swipe-back.test.tsx | 131 +++++++++++++++++++++
packages/mobile/README.md | 36 ++++++
packages/mobile/package.json | 3 +-
packages/mobile/scripts/patch-ios-webview.ts | 119 +++++++++++++++++++
packages/mobile/src/__tests__/native-shell.test.ts | 122 +++++++++++++++++++
7 files changed, 453 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7586
Fusion-Task-Lineage: 248092a1-de1b-49b6-94ef-70675a7b93a1
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the Android predictive back-gesture (edge swipe) not returning from a task detail view to the board, even though the hardware/legacy Back button worked correctly.
- Patch the generated Android manifest post-sync to set android:enableOnBackInvokedCallback="true" on the <application> tag, opting into AndroidX's OnBackPressedDispatcher for predictive-back gesture completion (mitigating ionic-team/capacitor-plugins#2418 via the disableBackButtonHandler toggle shipped in @capacitor/app@7.1.0).
- Wire the patch script into Capacitor's capacitor:sync:after npm-script hook so it runs automatically after every cap sync (and therefore after cap run android / build:mobile).
- Ensure both the back gesture and the hardware Back button funnel through the same AndroidBackButtonManager backButton listener, dispatching the shared fusion:native-back event consumed by the dashboard's nav-history stack.
- Add regression coverage: a unit test for the manifest patch script's idempotent opt-in behavior, and a dashboard test asserting the task detail view returns to the board on fusion:native-back.
- Document the Android manifest patch rationale and the unchanged dashboard-side invariant in packages/mobile/README.md.
Files changed:
.../__tests__/TaskDetail.swipe-back.test.tsx | 13 +++
packages/mobile/README.md | 31 +++++++
packages/mobile/capacitor.config.ts | 13 +++
packages/mobile/package.json | 2 +
packages/mobile/scripts/patch-android-manifest.ts | 90 ++++++++++++++++++
packages/mobile/src/__tests__/native-shell.test.ts | 102 +++++++++++++++++++++
6 files changed, 251 insertions(+)
Fusion-Task-Id: FN-7583
Fusion-Task-Lineage: ad0cb02c-6e49-448a-8c93-607cd5ae657b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Unifies the visual styling of the task-detail modal's Priority, Execution-mode, and Oversight quick-control chips so the cluster reads as one consistent control group.
- Add a shared --detail-control-border-radius token (resolving to --radius-md) alongside the existing --detail-priority-control-min-height token
- Override .detail-priority-chip's inherited transparent border with a visible --btn-border-width/--border pairing so the "normal" priority level renders as a bordered box instead of borderless text
- Pin the same border-width/color/radius trio on .detail-execution-mode-toggle so a future change to .btn defaults can't desync the cluster
- Apply the same trio to .detail-oversight-chip, overriding .card-oversight-badge's transparent border (covers the neutral "off" tint too)
- Apply the same trio to the mobile .detail-oversight-menu-trigger swap-in so the mobile overflow-trigger variant matches the desktop chip
- Add a changeset (patch) documenting the fix for @runfusion/fusion
- Extend TaskDetailModal.responsive-and-dependencies.test.tsx coverage for the unified styling
Files changed:
.changeset/FN-7585-unify-task-detail-quick-control-styling.md | 7 +++
packages/dashboard/app/components/TaskDetailModal.css | 55 ++++++++++++++++++++++
packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx | 27 +++++++++++
3 files changed, 89 insertions(+)
Fusion-Task-Id: FN-7585
Fusion-Task-Lineage: 0cca8c82-c9fb-4410-af48-51861a743f96
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Reword the disabled-Nudge tooltip/helper text so it no longer reads as an overseer fault, and split it into two distinct reasons.
- Add taskDetail.oversight.nudgeSuppressedTitle for the human-control-suppressed case (user-paused, done/archived, autoMerge:false human-review terminal), naming manual control as the cause
- Reword taskDetail.oversight.nudgeDisabledTitle to a reassuring periodic-poll framing for the no-observation-yet case instead of implying the overseer is idle
- Compute the shared nudgeDisabledReason once and reuse it at all four render sites (mobile menu + desktop inline title/helper) so the two copies can't drift
- Add a changeset (patch) documenting the operator-facing copy fix
- Extend TaskDetailModal.oversight-controls tests and test-helpers to cover the new suppressed-vs-disabled copy branching
Files changed:
.changeset/fn-7582-oversight-guideline-copy.md | 7 ++
.../dashboard/app/components/TaskDetailModal.tsx | 37 +++++++++--
.../TaskDetailModal.oversight-controls.test.tsx | 76 +++++++++++++++++++++-
.../__tests__/TaskDetailModal.test-helpers.ts | 7 ++
4 files changed, 121 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7582
Fusion-Task-Lineage: bf2ceab4-2fb0-4686-a9e1-9e015502a521
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a discoverable built-in Brainstorming workflow composing the ask-user + exit-gate reach-out loop ahead of the standard coding plan/execute/review/merge spine, plus a WorkflowNodeEditor fix so clearing the ask-user question textarea deletes the config key instead of persisting an empty string.
- Add packages/core/src/builtin-brainstorming-workflow-ir.ts registering builtin:brainstorming (non-default, default-enabled): ask-user -> refine prompt -> exit-gate-on-approval ahead of the unmodified Coding plan/execute/review/merge spine
- Wire the new builtin into packages/core/src/builtin-workflows.ts and extend the builtin-workflows parity test suite
- Add builtin-brainstorming-workflow-ir.test.ts covering the new workflow's IR shape and validation
- Fix WorkflowNodeEditor.tsx ask-user question textarea onChange to delete the config.question key when cleared to empty (validateAskUserAndExitGateNodes rejects present-but-empty question; only an absent key falls back to the engine default)
- Update docs/workflow-steps.md to document builtin:brainstorming as a selectable built-in composition
- Add .changeset/fn-7584-brainstorming-builtin.md (minor, feature)
Files changed:
.changeset/fn-7584-brainstorming-builtin.md | 7 ++
docs/workflow-steps.md | 2 +-
.../builtin-brainstorming-workflow-ir.test.ts | 79 ++++++++++++++++
.../core/src/__tests__/builtin-workflows.test.ts | 66 +++++++++++++
.../core/src/builtin-brainstorming-workflow-ir.ts | 103 +++++++++++++++++++++
packages/core/src/builtin-workflows.ts | 46 +++++++++
.../app/components/WorkflowNodeEditor.tsx | 18 +++-
7 files changed, 319 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7584
Fusion-Task-Lineage: 2c0258c2-9a35-403a-8688-ee49393a7231
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Stop the Interventions Activity segment from reserving padding for an overlay toggle button it never renders, which was insetting the FN-7519 timeline from the right edge (worse on mobile).
- Add `.detail-activity--interventions` CSS modifier that zeroes `padding-inline-end` for the Interventions segment, both base and mobile breakpoints, while leaving Live/Feed/Raw's reserved padding untouched
- Apply the new modifier class to the Interventions `.detail-activity` container in TaskDetailModal.tsx
- Add a regression test asserting the Interventions container carries `detail-activity--interventions` while the Feed container (which still renders the overlay toggle) does not
Files changed:
.../dashboard/app/components/TaskDetailModal.css | 18 ++++++++++
.../dashboard/app/components/TaskDetailModal.tsx | 2 +-
.../TaskDetailModal.oversight-controls.test.tsx | 38 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7581
Fusion-Task-Lineage: d5c55773-7df3-4eb4-aa70-01f6e0a622c6
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add workflow nodes for mid-flow user reach-out and early exit from a workflow run.
- Add `ask-user` IR node kind that reuses the await-input park/resume mechanism and surfaces the question in the task chat for brainstorming/clarification.
- Add `exit-gate` IR node kind that terminates the workflow early, with an optional condition.
- Wire both node kinds through the engine executor and workflow-node-handlers, including a new exit-gate-runner.
- Update the WorkflowNodeEditor palette, node summaries, and node help text for the two new node types.
- Extend workflow-flow-mapping to support the new node kinds.
- Keep `prompt`+`awaitInput` as a back-compat alias.
- Add core/engine/dashboard tests covering the new node kinds.
- Document the new nodes in docs/workflow-steps.md.
- Add changeset for the new minor feature.
Files changed:
.changeset/fn-7579-ask-user-exit-gate-nodes.md | 7 +
docs/workflow-steps.md | 28 ++++
packages/core/src/__tests__/workflow-ir.test.ts | 120 ++++++++++++++
packages/core/src/workflow-ir-types.ts | 12 +-
packages/core/src/workflow-ir.ts | 47 ++++++
.../app/components/WorkflowNodeEditor.tsx | 181 ++++++++++++++++++++-
.../app/components/__tests__/node-summary.test.ts | 43 +++++
.../__tests__/workflow-flow-mapping.test.ts | 49 ++++++
.../app/components/nodes/WorkflowNodeTypes.tsx | 14 +-
.../dashboard/app/components/nodes/node-help.ts | 24 +++
.../dashboard/app/components/nodes/node-summary.ts | 28 ++++
.../app/components/workflow-flow-mapping.ts | 4 +
.../workflow-graph-executor-handlers.test.ts | 115 +++++++++++++
.../src/__tests__/workflow-node-handlers.test.ts | 66 ++++++++
packages/engine/src/executor.ts | 23 ++-
packages/engine/src/workflow-node-handlers.ts | 18 +-
.../src/workflow-node-runners/exit-gate-runner.ts | 81 +++++++++
17 files changed, 849 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7579
Fusion-Task-Lineage: 9a89ff49-200d-4a6c-b97c-15d219349ee5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Extends FN-7554's single-repo PR revert path to workspace (multi-repo) tasks: when autoMerge is disabled, the revert route now opens one dedicated fusion/revert-<id> PR per sub-repo instead of refusing workspace tasks outright.
- Add prepareWorkspaceRevertPrBranches (packages/engine/src/task-revert.ts): classifies every sub-repo first and only prepares a per-sub-repo fusion/revert-<id> branch when all sub-repos are clean/already-reverted (all-or-nothing at branch-prep phase); never force-writes any sub-repo integration branch.
- Export the new helper from packages/engine/src/index.ts.
- Extend POST /api/tasks/:id/revert (register-task-workflow-routes.ts) to resolve owner/repo and check the GitHub rate limiter for every sub-repo before pushing/creating any PR, opening one PR per sub-repo and returning an additive { mode: "pr", clean: true, workspace: { repos: [...] } } result; degrades the whole task to needsHuman if GitHub is unconfigured or any sub-repo is rate-limited, rather than opening a partial subset of PRs.
- Leave existing { mode: "git" | "ai" | "pr" } shapes, the autoMerge:true workspace path, and FN-7554's single-repo PR path unchanged.
- Add engine real-git coverage (task-revert-workspace-pr.real-git.test.ts) and extend dashboard route tests (task-revert-route.test.ts) for the new workspace PR path.
- Add changeset (.changeset/fn-7577-workspace-pr-revert.md, minor) and update docs/task-management.md.
Files changed:
.changeset/fn-7577-workspace-pr-revert.md | 7 +
docs/task-management.md | 3 +-
.../src/__tests__/task-revert-route.test.ts | 313 ++++++++++++++++-
.../src/routes/register-task-workflow-routes.ts | 181 +++++++++-
.../task-revert-workspace-pr.real-git.test.ts | 371 +++++++++++++++++++++
packages/engine/src/index.ts | 4 +
packages/engine/src/task-revert.ts | 295 ++++++++++++++++
7 files changed, 1166 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-7577
Fusion-Task-Lineage: bedbfab7-5804-485f-9b40-64531edfc64a
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Extends GitHubIssueCommentService so task-close comments on issues in the runfusion/fusion repo itself append both a current-version and target-next-minor-release line, while comments on all other linked repos stay byte-for-byte unchanged.
- Add isFusionSelfRepo() and computeNextMinorVersion() helpers to github-issue-comment.ts
- Append "Current version: v{current}" and "Target release: v{next-minor}" lines only when the linked source issue's repo is runfusion/fusion (case-insensitive)
- Fall back silently (no version lines) when the resolved version is unparseable or the unresolved 0.0.0 sentinel
- Add changeset (minor) documenting the new behavior
- Update docs/settings-reference.md and docs/gitlab-parity-inventory.md
- Expand github-issue-comment.test.ts coverage for self-repo vs other-repo behavior and version edge cases
Files changed:
.changeset/fn-7575-release-version-comment.md | 7 +
docs/gitlab-parity-inventory.md | 2 +-
docs/settings-reference.md | 2 +-
packages/dashboard/src/__tests__/github-issue-comment.test.ts | 142 ++++++++++++++++++++-
packages/dashboard/src/github-issue-comment.ts | 69 +++++++++-
5 files changed, 212 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-7575
Fusion-Task-Lineage: b7cf7e6f-8d96-4442-8595-5d54ea911481
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
GitHub tracking-issue creation reused old/closed issues via dedup:
- searched state:"all", so a resolved issue from an unrelated task could be reused
- accepted keyword-only matches (generic shared identifiers) with no file overlap
Dedup now only reuses OPEN issues and requires a File-Scope path overlap;
without an open path-overlapping issue a fresh tracking issue is always created.
Adds two regression tests asserting closed and keyword-only matches are not reused.
Fusion-Task-Id: FN-7579
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a PR-based revert path for done/archived tasks in autoMerge:false projects instead of refusing outright.
- New engine export `prepareRevertPrBranch` (packages/engine/src/task-revert.ts) prepares a dedicated `fusion/revert-<id>` branch off the base branch's HEAD and applies the revert commit(s) there, never mutating the base branch itself.
- `POST /api/tasks/:id/revert` route gains an additive `{ mode: "pr", clean: true, prUrl, prNumber, revertBranch, existingPr? }` result for clean single-repo reverts under autoMerge:false, reusing GitHubClient.createPr, findPrForBranch idempotency, and the manual:true PR handoff.
- Existing `{ mode: "git" | "ai", ... }` result shapes and the autoMerge:true path are unchanged.
- Workspace (multi-repo) tasks are explicitly refused for PR-based revert (out of scope; single PR cannot represent a multi-repo revert).
- Adds real-git integration tests for the new branch-prep/apply/commit flow and expands the dashboard route test coverage.
- Adds a changeset for @runfusion/fusion (minor) and a small task-management doc update.
Files changed:
.changeset/fn-7554-pr-based-revert.md | 7 +
docs/task-management.md | 2 +-
.../src/__tests__/task-revert-route.test.ts | 196 +++++++++++++++++++-
.../src/routes/register-task-workflow-routes.ts | 182 ++++++++++++++++++
.../src/__tests__/task-revert-pr.real-git.test.ts | 206 +++++++++++++++++++++
packages/engine/src/index.ts | 3 +
packages/engine/src/task-revert.ts | 154 +++++++++++++++
7 files changed, 747 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7554
Fusion-Task-Lineage: 9b1bfd82-2428-4cf7-9b36-77afe3517a14
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Subscription OAuth is aliased across the legacy `anthropic` id (where login
persists the credential) and `anthropic-subscription` (where the settings card
and status read are keyed). After an in-session logout, re-login wrote only
`anthropic` and never cleared the in-memory `anthropic-subscription` logged-out
flag, so the card reported "Login did not complete" despite a valid stored
credential until the process restarted.
auth-storage's proxy now clears the logged-out suppression on both aliases when
either is re-authenticated (new `login` trap + hardened `set` trap via
clearReauthenticatedLogoutState); raw api_key writes stay scoped to their own
card. Also surface previously-swallowed background OAuth login failures on
GET /auth/status (`loginError`) plus server logs and a settings toast, so real
paste-callback failures are diagnosable instead of a generic error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unifies OAuth expiry detection so expired Claude subscription logins correctly show as disconnected with a re-login prompt, and adds a proactive engine-side scheduler that refreshes tokens before they expire.
- Share expiry-detection logic between OAuthExpiryMonitor and the /api/auth/status route so both agree on when a token is expired.
- Add engine-side oauth-refresh-scheduler that proactively refreshes OAuth tokens ahead of expiry, wired into project-engine (guarded by skipNotifier).
- Extend auth-storage with the helpers needed for expiry checks/refresh.
- Add tests covering routes-auth status detection, auth-storage expiry helpers, and the new refresh scheduler.
- Document the new behavior in dashboard-guide.md and settings-reference.md.
- Add changeset for the user-facing fix.
Files changed:
.../fn-7574-oauth-expiry-detection-refresh.md | 7 +
docs/dashboard-guide.md | 4 +
docs/settings-reference.md | 4 +
.../dashboard/src/__tests__/routes-auth.test.ts | 76 +++++++++++
.../dashboard/src/routes/register-auth-routes.ts | 25 +++-
packages/engine/src/__tests__/auth-storage.test.ts | 60 +++++++++
packages/engine/src/auth-storage.ts | 14 +-
.../__tests__/oauth-refresh-scheduler.test.ts | 141 ++++++++++++++++++++
packages/engine/src/notification/index.ts | 3 +
.../src/notification/oauth-refresh-scheduler.ts | 143 +++++++++++++++++++++
packages/engine/src/project-engine.ts | 14 +-
11 files changed, 488 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7574
Fusion-Task-Lineage: 59996eac-c070-4992-9727-d066c6934b69
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a client-side helper and UI affordances so AI-undo tasks and their source tasks link to each other in the dashboard, without any new API or backend changes.
- Add packages/dashboard/app/utils/taskRevert.ts with getRevertOfId() (resolves the revertOf marker written by createAiUndoTask, with a defensive sourceType==="recovery" fallback to sourceParentTaskId) and findOpenUndoTaskForSource() (reverse lookup for the most recent open undo task pointing at a source task)
- TaskCard and TaskDetailModal now render "Undo of <id>" / "Undo task: <id>" provenance links using the shared helper, keeping forward and reverse affordances consistent
- Add TaskCard.css styling for the new undo-link affordance
- Add/extend tests: TaskCard.test.tsx and TaskDetailModal.rendering.test.tsx cover the new undo/source link rendering
- Update docs/task-management.md and packages/i18n/locales/en/app.json for the new UI copy
Files changed:
docs/task-management.md | 2 +-
packages/dashboard/app/components/TaskCard.css | 37 +++++
packages/dashboard/app/components/TaskCard.tsx | 29 +++-
.../dashboard/app/components/TaskDetailModal.tsx | 46 ++++++
.../app/components/__tests__/TaskCard.test.tsx | 46 ++++++
.../__tests__/TaskDetailModal.rendering.test.tsx | 160 +++++++++++++++++++++
packages/dashboard/app/utils/taskRevert.ts | 80 +++++++++++
packages/i18n/locales/en/app.json | 7 +-
8 files changed, 403 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7555
Fusion-Task-Lineage: c3d2dbd2-4d86-40b5-afce-0ca7a6dfc73d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes recurrence #4 of the mobile terminal excess character-spacing bug: SessionTerminal and TerminalModal now force a second genuine xterm font remeasure AFTER fitAddon.fit() settles the post-fit column count, since handleResize() never re-bakes DomRenderer's letter-spacing compensation itself.
- SessionTerminal.tsx: call forceTerminalFontRemeasure() again after fit()/sendResizeMessage() in the resize handler, re-baking spacing against the settled (post-fit) column count instead of the stale pre-fit one.
- TerminalModal.tsx: same second forceTerminalFontRemeasure() call after fitAddon.fit()/sendResize() in its resize handling path.
- Expanded SessionTerminal.test.tsx and TerminalModal.test.tsx coverage to assert the post-fit remeasure occurs.
- Added docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md documenting recurrence #4 root cause (DomRenderer._setDefaultSpacing() never recomputes from handleResize()).
- Added changeset fn-7567-mobile-terminal-spacing.md (patch, category fix).
Files changed:
.changeset/fn-7567-mobile-terminal-spacing.md | 7 +
docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md | 89 ++++++
packages/dashboard/app/components/SessionTerminal.tsx | 23 ++
packages/dashboard/app/components/TerminalModal.tsx | 43 ++-
packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 127 +++++++-
packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 334 ++++++++++++++++++++-
6 files changed, 619 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7567
Fusion-Task-Lineage: 5da20522-82d3-4c4d-9008-db71bc5b4d75
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Manual plan approval now skips re-asking for approval when a re-specification produces an identical plan to one already approved.
- Add nullable Task.approvedPlanFingerprint field with DB migration 139 to track the approved PROMPT.md fingerprint
- Skip re-parking at awaiting-approval when replan/plan-review-retry/self-healing rebound yields the same plan fingerprint as before
- Require fresh approval when the plan content changes or when a plan is rejected
- Leave Release Authorization, Workflow Plan Review, and auto-approve-all behavior unchanged
- Add/extend tests across core (db, plan-approval, store-persistence), engine (triage), and dashboard (routes-github) to cover fingerprint comparison and idempotent re-approval
- Update docs (settings-reference.md, workflow-steps.md) to describe the idempotent approval behavior
- Add changeset for @runfusion/fusion (patch)
Files changed:
.changeset/fn-7569-plan-approval-idempotent.md | 7 +
docs/settings-reference.md | 2 +-
docs/workflow-steps.md | 2 +
packages/core/src/__tests__/db.test.ts | 54 +++++++
packages/core/src/__tests__/plan-approval.test.ts | 31 +++-
.../core/src/__tests__/store-persistence.test.ts | 39 +++++
packages/core/src/db.ts | 22 ++-
packages/core/src/index.ts | 2 +-
packages/core/src/plan-approval.ts | 23 +++
packages/core/src/store.ts | 20 ++-
packages/core/src/types.ts | 13 ++
.../dashboard/src/__tests__/routes-github.test.ts | 69 +++++++-
.../src/routes/register-task-workflow-routes.ts | 37 ++++-
packages/engine/src/__tests__/triage.test.ts | 178 ++++++++++++++++++++-
packages/engine/src/triage.ts | 58 +++++--
15 files changed, 527 insertions(+), 30 deletions(-)
Fusion-Task-Id: FN-7569
Fusion-Task-Lineage: 7d3855ae-6f45-4571-90db-cf1ae3b541dd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Relocates the FN-7519 Intervention Timeline out of the inline oversight cluster and into the task Activity view as a fourth selectable segment.
- Add `interventions` to the `ActivitySegment` union and gate its dropdown option on the same oversight-active expression previously used by the inline mount
- Render `PlannerInterventionTimeline` in a new Activity segment panel instead of inline next to the oversight controls
- Fall back to the Live segment automatically if oversight turns off (or was never resolved) while Interventions is selected, avoiding an orphaned selection
- Update oversight-controls and task-activity-chat tests to cover the new dropdown segment and removed inline mount
- Add changeset and update dashboard guide docs
Files changed:
...7571-intervention-timeline-activity-dropdown.md | 7 +
docs/dashboard-guide.md | 4 +-
.../dashboard/app/components/TaskDetailModal.tsx | 49 ++++---
.../TaskDetailModal.oversight-controls.test.tsx | 148 +++++++++++++++++++++
.../TaskDetailModal.task-activity-chat.test.tsx | 7 +-
5 files changed, 195 insertions(+), 20 deletions(-)
Fusion-Task-Id: FN-7571
Fusion-Task-Lineage: 331a1b32-5712-4583-9777-e36ff5cc3ffc
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Corner-pins the mobile terminal header's close (X) button so it renders last in flex order and hugs the top-right edge, instead of falling back to order:0 and landing at the far left of the header.
- Add .terminal-close--corner class (order:3 + margin-inline-start:auto) applied only on mobile, both in the base .terminal-modal--mobile selector and the max-width:768px media query (covers folded-Android visualViewport path)
- Apply terminal-close--corner alongside terminal-close on the mobile close button in TerminalModal.tsx; desktop/floating/pinned-below placement inside .terminal-actions is unchanged
- Add regression tests asserting the corner-pin class, its persistence across connection/exit states, the unaffected desktop .terminal-actions placement, and the CSS flex-order relationship vs .terminal-mobile-tabs/.terminal-workspace-picker
- Add changeset (patch) documenting the fix for release notes
Files changed:
.changeset/fn-7565-mobile-terminal-close-corner.md | 7 ++
.../dashboard/app/components/TerminalModal.css | 30 +++++
.../dashboard/app/components/TerminalModal.tsx | 14 ++-
.../components/__tests__/TerminalModal.test.tsx | 126 +++++++++++++++++++++
4 files changed, 176 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7565
Fusion-Task-Lineage: f5d82128-6c14-4281-9cfc-fd87dbc89d5d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Enforce the FN-7559 release-authorization hold in the approve-plan and reject-plan routes, since a direct API call previously bypassed the dashboard's UI-only protection.
- Add a guard in register-task-workflow-routes.ts: reject approve-plan and reject-plan requests with 400 when task.awaitingApprovalReason === "release-authorization", instructing the caller to add the **Release Authorized By User:** yes marker instead.
- Add regression tests in routes-github.test.ts covering both approve-plan and reject-plan against release-authorization-held tasks.
- Add changeset fn-7564-approve-plan-release-authorization-guard.md (patch, security) documenting the fix.
Files changed:
.changeset/fn-7564-approve-plan-release-authorization-guard.md | 7 ++
packages/dashboard/src/__tests__/routes-github.test.ts | 82 ++++++++++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 23 ++++++
3 files changed, 112 insertions(+)
Fusion-Task-Id: FN-7564
Fusion-Task-Lineage: ccadd492-ee2b-4f0e-af2f-5f37c6351922
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Relocates the mobile (<=768px) terminal action cluster from the header into a new bottom footer bar and ensures the shortcut key row can scroll horizontally without clipping.
- Add terminal-footer-actions bar for mobile viewports, keeping desktop/floating/pinned-below header layout (FN-7502) unchanged
- Apply min-width:0 and overflow-x:auto to the footer actions cluster so it no longer crowds the header
- Preserve the FN-7550 shortcut-panel horizontal scroll fix
- Update TerminalModal and mobile header-row tests to cover the new footer layout
- Add changeset documenting the fix
Files changed:
.changeset/fn-7560-mobile-terminal-footer.md | 7 +
.../__tests__/terminal-mobile-header-row.test.ts | 17 +-
.../dashboard/app/components/TerminalModal.css | 46 ++--
.../dashboard/app/components/TerminalModal.tsx | 233 +++++++++++++--------
.../components/__tests__/TerminalModal.test.tsx | 142 ++++++++++++-
5 files changed, 331 insertions(+), 114 deletions(-)
Fusion-Task-Id: FN-7560
Fusion-Task-Lineage: 98ffeb22-ec33-419e-987a-18ec98af7aa3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds all-or-nothing git-revert support for multi-repo workspace tasks and wires it into the existing revert route/AI-undo/per-sha machinery.
- Add `resolveWorkspaceTaskRevertCommits` and `revertWorkspaceTask` to `packages/engine/src/task-revert.ts`, dry-run classifying every sub-repo first and only committing per-repo revert commits when every sub-repo is clean/already-reverted; any conflicting sub-repo rolls back every already-committed sub-repo.
- Extract shared `applyAndCommitRevert" apply/commit machinery (built on the existing `applyRevertNoCommit` primitive) so the workspace path reuses the same commit-message/trailer contract as the single-repo path.
- Add a defensive `isWorkspaceTask` guard to `performTaskRevert` so workspace tasks can never be silently reverted through the single-repo path.
- Wire `POST /api/tasks/:id/revert` (register-task-workflow-routes.ts) to dispatch workspace tasks to `revertWorkspaceTask`, preserving the existing `mode` (git/ai/auto) and AI-undo-fallback contract for workspace conflicts.
- Export the new workspace revert types/functions from `packages/engine/src/index.ts`.
- Add route-dispatch and real-git workspace revert test coverage; update docs and add a changeset.
Files changed:
.changeset/fn-7547-workspace-task-revert.md | 7 +
docs/task-management.md | 8 +-
packages/dashboard/src/__tests__/task-revert-route.test.ts | 102 +++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 76 +++-
packages/engine/src/__tests__/task-revert.workspace.real-git.test.ts | 272 +++++++++++++
packages/engine/src/index.ts | 6 +
packages/engine/src/task-revert.ts | 448 ++++++++++++++++++++-
7 files changed, 897 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-7547
Fusion-Task-Lineage: b1b5eeda-06fd-43c1-8163-74b62c77b000
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Replace the raw kebab-case planner-overseer badge state and bare tooltip with a human-readable label and an explanatory tooltip built from the existing runtime snapshot.
- Add packages/dashboard/app/components/plannerOverseerBadge.ts: pure, type-only helper exposing plannerOverseerStateLabel() and plannerOverseerBadgeTooltip(), composing the tooltip from reason/watchedStage/signal/pendingConfirmation with graceful fallbacks
- Re-export PlannerOverseerState and PlannerOverseerRuntimeSnapshot as type-only from packages/core/src/types.ts so the dashboard's @fusion/core vite alias can resolve them
- Update TaskCard.tsx to render the new label/tooltip instead of the raw state string
- Add unit tests for the new badge helper and extend TaskCard tests for the updated label/tooltip behavior
- Add a patch changeset documenting the operator-facing fix
Files changed:
.changeset/fn-7563-overseer-badge-explanation.md | 7 ++
packages/core/src/types.ts | 7 ++
packages/dashboard/app/components/TaskCard.tsx | 7 +-
.../app/components/__tests__/TaskCard.test.tsx | 72 ++++++++++++++
.../__tests__/plannerOverseerBadge.test.ts | 103 ++++++++++++++++++++
.../app/components/plannerOverseerBadge.ts | 104 +++++++++++++++++++++
6 files changed, 296 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7563
Fusion-Task-Lineage: 64ed011b-5486-4272-abd1-a6123c60785f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Root-cause fix for xterm text rendering with wide inter-character gaps on mobile after fonts settle post-load.
- Added forceTerminalFontRemeasure() in terminalPreferences.ts to work around xterm's OptionsService setter being a no-op when reassigning an already-current fontFamily/fontSize
- Applied the remeasure helper at every post-waitForTerminalFontMetrics() settle site in TerminalModal.tsx and SessionTerminal.tsx
- Added regression tests covering the remeasure invariant across SessionTerminal, TerminalModal, and terminalPreferences
- Documented root cause and fix in docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md
- Added changeset for @runfusion/fusion (patch)
Files changed:
.changeset/fn-7561-mobile-terminal-spacing.md | 7 +
docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md | 120 ++++++++++
packages/dashboard/app/components/SessionTerminal.tsx | 11 +-
packages/dashboard/app/components/TerminalModal.tsx | 11 +-
packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx | 108 ++++++++-
packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 257 ++++++++++++++++++++-
packages/dashboard/app/utils/__tests__/terminalPreferences.test.ts | 55 +++++
packages/dashboard/app/utils/terminalPreferences.ts | 38 +++
8 files changed, 601 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-7561
Fusion-Task-Lineage: ad9da396-abf4-46ea-8c60-f2ed40fa4b01
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the mobile oversight overflow menu rendering two overlapping menus by narrowing auto-focus to actionable button menuitems.
- Change the menu-open auto-focus effect to query `button.detail-oversight-menu-item` instead of matching any `.detail-oversight-menu-item` element, since the native level `<select>` also carried that class and was matched first.
- Programmatically focusing the native `<select>` triggered its OS option picker, which rendered as a second overlapping menu on top of the custom `role="menu"` popover.
- Add regression tests covering: auto-focus landing on the first button menuitem when nudge/stop/explain are available, no fallback focus on the select when oversight is off (level-only state), and confirming the desktop inline select is unaffected.
- Update dashboard-guide.md docs to describe the fixed auto-focus behavior.
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/TaskDetailModal.tsx | 3 +-
.../app/components/__tests__/TaskDetailModal.oversight-mobile.test.tsx | 96 ++++++++++++++++++++++
3 files changed, 99 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7562
Fusion-Task-Lineage: f39068f1-811f-4663-ade1-ff020a389ae5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Disambiguate release-authorization approval holds from manual plan-approval holds so auto-approve no longer appears broken.
- Add `Task.awaitingApprovalReason` (`"release-authorization" | null`) to distinguish the release-authorization gate from the independent manual plan-approval gate, both of which set `status: "awaiting-approval"`.
- Stamp `awaitingApprovalReason: "release-authorization"` when the release gate blocks a task, and explicitly clear it (`null`) when the manual plan-approval gate parks the task, so a stale reason never survives a replan.
- Add DB migration/persistence support for the new column in `db.ts`/`store.ts`/`types.ts`.
- TaskCard/TaskDetailModal now render a distinct status for release-authorization holds and suppress the generic manual Approve/Reject affordance for them.
- Add i18n string and docs updates (`settings-reference.md`, `workflow-steps.md`) plus a changeset.
- Extend regression tests in db, triage, TaskCard, and TaskDetailModal to cover the new reason field and disambiguated UI.
Files changed:
$(git diff --cached --stat)
Fusion-Task-Id: FN-7559
Fusion-Task-Lineage: 0b37cbf0-40a4-4165-8088-482ed365ba19
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds an opt-in per-sha commit granularity mode to the task-revert git path, alongside its default squash behavior, and merges it cleanly with the existing FN-7524 AI-undo mode support.
- Add `TaskRevertGranularity` ("squash" | "per-sha") and thread an optional `granularity` option through `performTaskRevert`/`PerformTaskRevertOptions`.
- Factor a shared `applyRevertNoCommit` primitive (stage + no-op/conflict detection) used by both the squash and new per-sha apply paths.
- `"per-sha"` creates one attributed `revert(FN-xxxx): ...` commit per original sha (each with its own `Fusion-Task-Id` trailer and audit line), skipping no-op shas without empty commits; a mid-batch conflict rolls the whole batch back to the pre-call HEAD.
- Extend `TaskRevertResult`'s clean shape with `revertCommitShas: string[]` (all created commits) alongside the existing `revertCommitSha`.
- `POST /api/tasks/:id/revert` accepts an optional `granularity` request-body field (default `"squash"`, validated, 400 on unknown values) and forwards it to the engine service; documented alongside the existing `mode` (git/ai/auto) contract.
- Add real-git and route-level test coverage for per-sha creation, no-op skipping, default-squash behavior, and mid-batch conflict rollback.
- Update docs/task-management.md's revert section and add a changeset.
Files changed:
.changeset/fn-7548-per-sha-revert-granularity.md | 7 +
docs/task-management.md | 3 +-
packages/dashboard/src/__tests__/task-revert-route.test.ts | 46 +++++-
packages/dashboard/src/routes/register-task-workflow-routes.ts | 51 ++++--
packages/engine/src/__tests__/task-revert.real-git.test.ts | 124 +++++++++++++++
packages/engine/src/index.ts | 2 +
packages/engine/src/task-revert.ts | 176 +++++++++++++++++----
7 files changed, 359 insertions(+), 50 deletions(-)
Fusion-Task-Id: FN-7548
Fusion-Task-Lineage: b9548f5e-fcc2-45d4-98e0-dd7340928208
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds an AI-undo fallback to the revert route: when a git-based revert conflicts or is unsupported, an ordinary board task is created to perform the undo via AI instead of a forced/failed git write.
- POST /tasks/:id/revert now accepts an optional `{ mode?: "git" | "ai" | "auto" }` body (default "auto"); unknown values reject with 400.
- "git" preserves the FN-7523 git-only contract unchanged; "ai" always creates the AI-undo task; "auto" tries git first and falls back to AI only on a conflicting or unsupported (e.g. workspace) result — needsHuman (autoMerge:false) never triggers the fallback.
- New engine helpers in task-revert.ts: `createAiUndoTask`, `buildAiUndoTaskDescription`, `REVERT_OF_METADATA_KEY`, plus `AiUndoTaskResult`/`CreateAiUndoTaskDeps` types, exported from packages/engine/src/index.ts.
- The AI-undo task is created via the normal triage-column `store.createTask` path with no dependency on the source task, referencing the source task's mission, id, and landed files, and instructing an undo commit using the `revert(FN-xxxx): ...` convention.
- New core `TaskStore.findOpenRevertTaskForSource` backs an idempotency guard: a repeated call while an AI-undo task is still open returns the same `createdTaskId` with `alreadyOpen: true` instead of creating a duplicate.
- Updated docs/task-management.md's revert section to document the git path + AI-undo fallback contract.
- Added a minor changeset for the @runfusion/fusion release notes.
- Added/extended tests: packages/engine/src/__tests__/task-revert-ai-undo.test.ts (new) and packages/dashboard/src/__tests__/task-revert-route.test.ts (extended) covering mode validation, auto-fallback-on-conflict, forced "ai" mode, and the duplicate-open-task guard.
Files changed:
.changeset/fn-7524-ai-undo-revert.md | 7 +
docs/task-management.md | 13 +-
packages/core/src/store.ts | 31 +++++
packages/dashboard/src/__tests__/task-revert-route.test.ts | 143 ++++++++++++++++++++-
packages/dashboard/src/routes/register-task-workflow-routes.ts | 75 +++++++++--
packages/engine/src/__tests__/task-revert-ai-undo.test.ts | 114 ++++++++++++++++
packages/engine/src/index.ts | 5 +
packages/engine/src/task-revert.ts | 117 ++++++++++++++++-
8 files changed, 487 insertions(+), 18 deletions(-)
Fusion-Task-Id: FN-7524
Fusion-Task-Lineage: 64dfedcf-c286-4c46-8cf8-51ec5e668bf7
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Two related mission-loop fixes, both observed wedging a live autonomous
board.
### 1. Triage ignores `mission.branchStrategy` when `branchAssignment`
is omitted (dashboard)
`resolveBranchAssignmentContext` fabricated `{ mode: "shared" }` for
absent input, so the mission triage routes (`triage`, `triage-all`)
always passed an explicit `assignmentMode` into
`missionStore.triageFeature`/`triageSlice`. That defeats the store's
fallback — `branchOptions?.assignmentMode ??
strategyDefaults.assignmentMode` — so a mission configured with
`branchStrategy: auto-per-task` still produced a **shared** branch
group, named after the base branch.
docs/missions.md documents the intended behavior: missions "can also
persist a `branchStrategy` used whenever triage is triggered without
explicit branch options."
**Fix:** absent input resolves to `{ mode: undefined }`; callers pick
their own default. The mission routes need no change (undefined now
flows through to the strategy fallback). The two planning-subtask call
sites keep their historical `shared` default via a destructure default,
since they have no strategy to fall back to. Explicit
`branchAssignment.mode` is unchanged and still overrides the strategy.
**Observed impact:** with `baseBranch: main`, every triaged task joined
a shared group literally named `main` — tasks tried to push to `main` /
open PRs with head=main base=main, and the whole group wedged in
`merge-retries-exhausted`. The only workaround was remembering to send
`{"branchAssignment": {"mode": "per-task-derived"}}` on every triage
call, which silently ignores the mission's configured strategy the rest
of the time.
### 2. Task-completion validation runs for parked missions (engine)
`MissionExecutionLoop.processTaskOutcome` validated every completed
feature-linked task with no mission-status check — unlike
`recoverActiveMissions`, which already skips missions with `status !==
"active"`. A parked mission (`status: planning`) kept minting validator
runs, and on validator failure, new "Fix:" features — for tasks that
completed after parking. On our board a stale validator workspace
produced a `Fix: → Fix: Fix: → Fix: Fix: Fix:` spiral of bogus features
for already-merged work; the only mitigation was re-parking the mission
after every release and manually archiving the minted features.
**Fix:** gate `processTaskOutcome` on the resolved mission being active,
mirroring the `recoverActiveMissions` guard. The gate sits before the
`needs_fix → implementing` transition so an inactive mission's features
get zero state mutation; the skip logs a `warning` mission event
(`validation_skipped_mission_inactive`) so it's visible in the mission
log. Features that don't resolve to a mission keep the current behavior.
(Out of scope but worth noting: the validator that triggered the spiral
was judging merged work against a stale workspace checkout — that
freshness issue is a separate problem this PR doesn't attempt.)
## Tests
- `branch-selection.test.ts` — updated: absent/`{}` input resolves
`mode: undefined`; explicit modes and the bad-mode error unchanged.
- `mission-execution-loop.test.ts` — two new tests: parked mission skips
validation and logs the warning event; active mission still validates.
- Existing `mission-store.test.ts` coverage ("uses mission
branchStrategy … when branch options are omitted", explicit `shared`
override still creates a group) pins the store side end-to-end — those
pass unchanged, as do the planning/branch-group route suites (182 tests)
and full workspace `pnpm typecheck`.
Changeset included (`patch`, category `fix`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Branch selection now keeps an unspecified mode unset and falls back to
the mission’s configured branch strategy where appropriate.
* Task outcome processing now skips validation for missions that are not
active, preventing unnecessary follow-up actions.
* Added coverage for branch selection and mission execution behavior to
verify the updated handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->