## The bug
`moves.ts` asked `countActiveInCapacitySlotAsync` for occupants of pool
`"builtin:coding"`, while the counter buckets selection-less rows under
`DEFAULT_WORKFLOW_POOL_ID` (`"__default-workflow__"`). Nothing ever
landed in the pool being asked about, so the count came back **0** and a
finite limit could never bind.
## Root fix, not a literal swap
A shared *constant* would not have prevented this:
**`DEFAULT_WORKFLOW_ID` was already imported in `moves.ts` and the code
still wrote a literal.** So both sides now call a shared **function**,
`resolveCapacityPoolId` — "which pool does a selection-less task belong
to" has exactly one answer and no call site is in a position to disagree
with it.
The one variable serving two masters is split: a capacity **pool key**
(a bucketing sentinel that must not collide with a workflow id) and a
**workflow id** (telemetry, must stay a real id). The emitted
`TaskTransitioned` payload is byte-identical.
## Checked, not assumed: no second copy
`scheduler.ts:2514` and `:2536` do carry `?? "builtin:coding"` — but as
an **IR resolution key** (`resolveWorkflowIrById`), where a real
workflow id is required and the pool sentinel would not resolve at all.
Same literal, different concept, correctly used. A blanket replace would
have broken it.
## Something did depend on the gate being dead — exactly one thing
`move-path-equivalence.pg.test.ts` → *"UNPROVEN: in-transaction column
capacity did NOT reject on EITHER path in this fixture"*. It left the
cause open —
> something further in (`resolveColumnCapacity`'s limit resolution, or
what `countActiveInCapacitySlotAsync` counts as an occupant — a task
with no session/agent may not count) keeps the check from firing … This
suite does not establish which.
— and predicted its own obsolescence (*"if a future change makes this
reject, that is the capacity gate coming alive"*). **Neither guess was
right; it was the pool id.** Updated to assert the divergence with the
answer recorded — **not weakened**. Its fixture also had to start each
phase from an empty wip column: once the gate binds, the inline phase's
leftovers trip the cap on the *holder* move before the contended move
under test runs.
`schema-applier.test.ts` failed only in the full-suite run and passes in
isolation both with and without the fix — cross-file contamination, not
mine.
## Before / after — measured, both directions
`maxConcurrent: 1`, real PG store, real `moveTask`:
| | flagOFF / no selection | flagOFF / selection | flagON / no selection
| flagON / selection |
|---|---|---|---|---|
| **before** | ADMITTED | ADMITTED | **ADMITTED** ← the bug | REJECTED |
| **after** | ADMITTED | ADMITTED | **REJECTED** | REJECTED |
The E2E acceptance row asserts **held at cap 1 and admitted at cap 2 on
the same fixture**, so it cannot pass by simply never admitting
anything. **With the fix reverted that row fails**; the `admitted` case
still passes, as it should. The Phase A3 ratchet's two flipped
assertions also fail with the fix reverted.
Ratchet flipped exactly as its author specified: `DEFECT (R1)` becomes a
rejection, and `it.fails` on the invariant becomes a plain `it`.
## ⚠️ This is NOT user-visible yet — please read before merging
The premise this was approved on ("once it binds, cards that currently
slip through will start being held") **does not hold for this change
alone.** The whole capacity block sits inside `if (useWorkflow &&
workflowIr && fromColumn !== toColumn)`, and `useWorkflow` is
`experimentalFeatures.workflowColumns === true` — absent from
`DEFAULT_GLOBAL_SETTINGS`, with **no writer anywhere outside tests**.
That is Phase A3's R2, still live and now retitled `DEFECT (R2, STILL
LIVE)` with the measured matrix recorded in it.
So on merge: nothing changes for any real project. Making it actually
bind means **also** removing the `useWorkflow` condition — a materially
larger, genuinely user-visible change that I have not made unilaterally.
Escalated for a decision; if that lands, the changeset here should be
re-categorised.
## Review follow-up (48e79ffd9): the convention was still duplicated —
swept and ratcheted
The first pass added the resolver and routed the transactional gate +
counters, but **hold-release still derived the pool independently**.
Swept the repo: six sites name the sentinel, **five derive the
convention** and now call `resolveCapacityPoolId`
(`hold-release.ts:116/118/442/576`, `task-store-helpers.ts:290`). The
sixth, `scheduler.ts:1558`, names the default pool as a literal in a
capacity *diagnostic* — no selection input, nothing to disagree with —
so it keeps the constant.
**Does this change hold-release behavior? No, and it was never releasing
against the wrong pool.** hold-release computed `x ??
DEFAULT_WORKFLOW_POOL_ID`, which is exactly what the counter buckets
under; `moves.ts` (`?? "builtin:coding"`) was the sole disagreeing site,
and the first commit moved *it* into agreement with hold-release, not
the reverse. `resolveCapacityPoolId(x)` **is** `x ??
DEFAULT_WORKFLOW_POOL_ID`, so every routed site computes an identical
value for every input. **No second user-visible change rides along with
this PR** — the only behavior delta remains the gate binding on the
flag-ON path, which per R2 is still not the path production takes.
Evidence: hold-release + capacity suites **43/43 identical before and
after**.
**The resolver is now the only way to compute a pool id, not merely the
newest way.** `scripts/check-capacity-pool-id.mjs` fails on any inline
`?? DEFAULT_WORKFLOW_POOL_ID` outside `workflow-capacity.ts`, wired into
**both `pretest` and the blocking `test:gate`**. A review note would not
have sufficed: the original defect landed in a file that *already
imported* the canonical constant. Verified both ways — clean run scans
1124 files and passes; reintroducing the old hold-release expression
exits 1 and names the line.
## Review follow-up (a5b675503): the ratchet was rebuilt because it
would not have caught the bug
The first ratchet matched one spelling (`?? DEFAULT_WORKFLOW_POOL_ID`)
and the real defect used another (`?? "builtin:coding"`). **Verified:
reintroducing the original defect and running the old checker exits 0.**
A guard that reports success without checking is worse than no guard —
it stops anyone looking.
Rebuilt on the TypeScript AST with two rules. **Rule 1 (sink):** a value
reaching a capacity counter's `workflowId` must come from
`resolveCapacityPoolId`, or a local initialized from it — so it fires on
the original defect regardless of which literal was used, on one line or
twenty. **Rule 2 (sentinel):** no `??` onto the sentinel at any
qualification depth or as its raw value; multiline is one AST node and
caught by construction. `?? "builtin:coding"` is deliberately *not*
banned outright — it is the legitimate default for a *workflow* id in ~8
places, and is only a bug when it reaches a capacity pool.
**Fails closed three ways** that previously reported success without
inspecting: unreadable file, unparseable file, and an empty file listing
(the old script would have printed a green tick off a broken glob).
**Acceptance was not "passes on main".** Each form was reintroduced into
the real source and confirmed to fail: the original defect in
`moves.ts`, a multiline fallback, and a deeply qualified sentinel. All
are pinned in `capacity-pool-id-check.test.ts` (12 cases: 7 must-catch
starting with the reduced actual pre-fix `moves.ts`, 4 must-not-flag, 1
fail-closed) so the guard cannot silently narrow again.
Also added to `pretest:full`, which had omitted it.
### Follow-up (0be8df6ea): a dead rule found by fixing a test title
Splitting the mislabelled fail-closed test surfaced more than a
mislabel: **`ts.createSourceFile` is error-tolerant and does not throw
on malformed syntax**, so the `try/catch` behind the `unparseable` rule
was unreachable and that rule could never fire. The earlier "fails
closed three ways" claim was overstated — the guard advertised a
capability it did not have. Detection now reads `sf.parseDiagnostics`; a
partial AST can silently lack the `??` nodes and sink calls the rules
look for, so "did not parse" must not read as "inspected and clean".
Mutation-verified: reverting the detection fails that case and only that
case.
Test-file exclusion also moved to the repo's `{test,spec}.{ts,tsx}`
guideline shape — a `.spec.ts` under `packages/<pkg>/src/` was being
scanned as production source. Verified both ways: the `.spec.ts` is
skipped, and the identical content in a non-test file is still caught,
so the exclusion is scoped rather than a hole.
## Verification
- engine + core `tsc --noEmit` clean
- `pnpm test:gate` green (299 + 10 + 71)
- E2E 20/20; capacity + move-path suites 14/14
- full core PG: **1037 passed / 3 failed** — all three reproduce with
the fix stashed (pre-existing)
- engine-default: **279 failed** vs **280 at baseline** with the fix
stashed — pre-existing red lane, no regression
- hold-release + capacity suites: **43/43 identical before and after**
the resolver routing
- `check-capacity-pool-id` ratchet: 14/14 regression cases; clean over
1124 files; exits 1 on the original defect, a multiline fallback, and a
deeply qualified sentinel reintroduced into real source
🤖 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**
* Fixed capacity-limit accounting when workflow selection is missing by
consistently deriving the correct capacity pool id.
* Made capacity enforcement align across move and hold/release paths,
rejecting over-limit moves with `capacity-exhausted`.
* **Tests**
* Updated PostgreSQL and added an E2E scenario to verify the corrected
in-transaction gating behavior at `maxConcurrent` limits of 1 and 2.
* **Chores**
* Added an automated guard to detect inconsistent capacity pool id
fallback patterns in code.
* **Public API**
* Exposed `resolveCapacityPoolId` for consistent capacity pool id
derivation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
- Advance the matched Pi runtime pin (`pi-ai`, `pi-coding-agent`,
`pi-agent-core`, `pi-tui`) from **0.82.0 → 0.82.1** so
electron-builder's production-dependency walk accepts `pi-agent-core`'s
`pi-ai@^0.82.1` requirement.
- Fixes the Desktop packaging PR-lane failure:
`Production dependency @earendil-works/pi-ai not found for package
@earendil-works/pi-agent-core` (required `^0.82.1`).
- Keep the workspace override guard; update pin-policy fixtures and CLI
package-config expectations.
- Tighten the advisory packaging step-order test so it asserts against
the real `electron-builder --dir` step (not a missing release-only step
name that previously passed via `indexOf === -1`).
- Run `pnpm dedupe` so the packaging lane's lockfile dedupe
early-warning is clean.
## Context
#2439 pinned the full Pi closure at 0.82.0 and made recent main-based
packaging runs green. This advances to the current upstream patch so
deploy + electron-builder stay aligned with `pi-agent-core@0.82.1`'s
declared dependency range.
## Test plan
- [x] `node scripts/check-pi-versions-pinned.mjs`
- [x] `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs`
- [x] `pnpm --filter @runfusion/fusion exec vitest run
src/__tests__/package-config.test.ts`
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm dedupe --check`
- [ ] GitHub: Desktop packaging (should run full packaging walk —
lockfile/package.json touched)
- [ ] GitHub: PR Checks (Lint, Typecheck, Build, Gate)
Add an automated guard that keeps CI test-sharding timings usable.
- Validate snapshot structure, freshness, and recorded test-file paths.
- Confirm planning loads the snapshot and shard dry-runs use it without stale warnings.
Files changed:
scripts/__tests__/ci-test-shard-timings.test.mjs | 50 +++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-8626
Fusion-Task-Lineage: ada6525f-8dcc-4494-8586-3aa2e41618f3
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The client bundle aliases `@fusion/core` to the leaf `core/src/types.ts` to
keep Node-only dependencies out of the browser, so a package-root import of
`FUSION_CLIENT_HEADER`/`FUSION_DASHBOARD_UI_CLIENT` typechecked but failed
`vite build`:
"FUSION_CLIENT_HEADER" is not exported by "../core/src/types.ts"
Follow the documented pattern instead of widening the root alias: declare a
`./task-delete-attribution` subpath export, add the matching Vite alias ahead
of the broader `@fusion/core` key (Vite matches in order), register the module
in the browser-safe-core allowlist, and import the subpath from the client.
`task-delete-attribution.ts` has no imports at all, so it is a safe leaf.
`app/utils/detectContentLanguage.ts` already warned about exactly this trap;
the miss was mine for verifying with typecheck, lint and test:gate but not
`pnpm build`, which is one of the four checks CI blocks on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add the `autoUpdateAndRestart` global setting (default off, Settings ->
General next to Release channel). When enabled, the dashboard host installs
available updates on the selected channel by itself and requests the
supervised in-place restart. Supervised hosts only: without a parent to
respawn, installing would leave a running process whose code no longer
matches its own install.
Fix two ways the restart affordance could silently do nothing:
- The supervisor now stamps FUSION_SUPERVISOR_PID and supervision is only
counted when that pid is the real parent. FUSION_RESTART_SUPERVISED is
inherited by every process Fusion spawns, so `fn dashboard` launched from
an agent terminal skipped its own supervisor while still advertising
restart support -- a restart request then killed it for good.
- Settings and the update banner probe /system/info on mount and treat
capability as advisory: the button always issues the request and shows the
server's actual refusal instead of sitting disabled after a failed probe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- distillation runs on opus (env-overridable) with a 4-minute budget
- highlights must name the surface and outcome; vague filler is banned
- tweets target 200-280 chars with concrete changes and varied structure
- betas get their own tester-facing draft carrying `fn update --channel beta`
- prerelease openers read as "Fusion 0.74 beta:" instead of "Fusion 0.74-beta.0"
After a stable release, main stayed inside the old pre-mode cycle, so the next
beta numbered below the published stable (v0.73.0-beta.7 after v0.73.0) and the
dev checkout kept reporting the last beta.
- beta releases re-anchor a stale pre-mode cycle on the newest stable tag
- both channels refuse a version at or below the newest published stable
- stable promotion now back-merges release into main automatically (fail-soft
on conflict) so the local dev version is the stable version
- Shard watchdog floor 25min (was 15): the July PG-cutover test growth pushed
@fusion/core past 900s on contended CI runners; run 30075604930 killed a
healthy core run at exactly the floor because the 27-day-old (still "fresh")
undercounting timings snapshot tightened the budget to it — the same
false-kill class as the 5->15min raise. Floor pin + in-band example updated.
- full-suite.yml timing upload: include-hidden-files — the .timings/ dot-dirs
were silently excluded by upload-artifact@v4, so the step has uploaded
nothing since it was added and the snapshot could never be refreshed from CI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-mode preserves consumed changeset .md files, so every beta's distilled notes and GitHub prerelease body aggregated the entire cycle since the last stable (v0.73.0-beta.4 shipped the full 0.72.0→0.73.0 aggregate). Betas now distill only changesets not yet recorded in pre.json's consumed ledger, and fail loudly when a beta would ship nothing new. Stable promotion still feeds the full preserved set, keeping its notes an explicit rollup of every beta in the cycle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary
Wave 15 of package code organization.
### Peels
- `types/settings-scope.ts` — global/project settings (~2.2k lines)
- `types/archive-planning.ts` — archive, mesh/multi-project, planning
sessions
- `task-store/project-store-ops.ts` — rename of `remaining-ops-1` (last
numbered ops module)
### LOC
- `types.ts` ~5872 → ~3074
## Test plan
- [x] `@fusion/core` typecheck
- [ ] CI merge gate
**Stack:** this PR → #2397 → #2398
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Reorganized and expanded the core public type surface into dedicated
modules for settings, archive/planning, board, tasks, todo lists, plugin
activation, and multi-project setup.
* Improved the browser-safe type exports to keep the public contracts
consistent.
* Updated internal project-level operation wiring to use the correct
project implementations.
* **Bug Fixes**
* Fixed a workflow creation test hook to inject the correct pre-insert
behavior for workflow-definition collision/allocator scenarios.
* **Chores**
* Refreshed internal headers and updated line-count baselines.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Refresh the weekly test velocity publication with the latest measurements.
- Record updated gate, boot smoke, and changed-only test timings.
- Append the W29 velocity history entry and update the published summary.
- Point testing guidance to the canonical weekly velocity workflow.
Files changed:
docs/test-velocity-baseline.md | 16 +++---
docs/testing.md | 10 +---
scripts/test-velocity-history.json | 115 +++++++++++++++++++++++++++++++++++++
3 files changed, 124 insertions(+), 17 deletions(-)
Fusion-Task-Id: FN-8495
Fusion-Task-Lineage: db432471-13a2-41b1-a951-f735c96456e9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
Restores green merge-gate and package-default suites after repeated
`origin/main` merges brought workflow-graph ownership cutover drift into
CI.
- Align engine/dashboard/core tests with post-cutover contracts
(`moveTaskIf`/`deleteTaskIf`, graph handoff, worktree-pool reclaim via
`removeWorktree` + `RemovalReason`, multi-step RESUMING parse,
soft-pause merge requester, graph-terminal failure surfaces).
- Small product fixes needed for real regressions uncovered by the
suite: soft-delete refuse before graph routing, skip DUPLICATE
step-heading withhold when an explicit marker is present, PG schema
applier guards, and related bookkeeping (research promote tool inventory
/ migration seed, stop shell `psql` in PG admin DDL).
- Quarantine/ledger hygiene only where required by standing rules; no
timeout/worker appeasement.
## Verification
- `pnpm test:gate` ×2 green
- `@fusion/engine` full package suite green (~9083 tests)
- Targeted core/dashboard clusters green (schema applier, agent-runs UI,
settings descriptions, mobile close)
## Test plan
- [x] `pnpm test:gate` (twice)
- [x] `pnpm --filter @fusion/engine test`
- [ ] CI full suite / PR checks on this branch
- [ ] Confirm no unrelated product behavior changes beyond the listed
regression fixes
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added support for `roadmap-item` native structure kinds, including
native structure embeds and metadata validation.
* Added Stable and Beta release channel options in General settings.
* Added per-action reporting target configuration with clearer “unset”
guidance.
* **Bug Fixes**
* Improved heartbeat/prompt behavior when patrol is disabled.
* Prevented deleted tasks from continuing through execution.
* Made recovery for explicit duplicate redirects more permissive.
* Hardened database migration and test database cleanup to reduce flaky
failures.
* **Documentation**
* Updated settings text for release channels, reporting targets, and
inheritance/unset behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
syncRootChangelog rewrote every prior release from raw package notes, so only
the latest distilled Highlights view survived. Re-emit already-distilled bodies
on sync, keep the archive pointer outside version sections, and restore wiped
summaries from release history.
Keep planning questions in their dedicated surface while preserving ntfy alerts, and tighten the desktop planning panes without changing compact or shared layouts.
## Summary
- restores the missing Simplified Chinese duplicate-roadmap report label
- restores the missing Traditional Chinese duplicate-roadmap report
label
- adds a patch changeset for the catalog correction
## Test plan
- `pnpm --filter @fusion/i18n test` (5 files, 29 tests)
- `pnpm build`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Restored Simplified and Traditional Chinese translations for duplicate
roadmap report titles.
* Updated the roadmap reporting UI text to clarify when a report is
already in the roadmap and ask whether to add the user’s data point.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Fusion can now ship on two release tracks. Betas are cut from `main` as
`vX.Y.Z-beta.N` (npm dist-tag `beta`, GitHub prerelease), stable
releases are promoted to a long-lived `release` branch and published to
`latest`, and users pick their track with the new `updateChannel` global
setting — via **Settings → General → Release channel** or `fn update
--channel <stable|beta>`. Previously everything was single-track: every
publish landed on `latest` and every update surface could only see it.
| | beta | stable |
|---|---|---|
| Cut from | `main` | `release` branch |
| Version | `X.Y.Z-beta.N` (changesets pre-mode) | `X.Y.Z` |
| npm dist-tag | `beta` | `latest` |
| GitHub Release | prerelease | latest |
| Homebrew tap / X draft | skipped | bumped / printed |
## How releasing works now
`pnpm release` prompts for the channel and **defaults to beta**, so
day-to-day releases are betas; stable is always an explicit choice.
Choosing stable from `main` triggers assisted promotion: the script
proposes the newest beta tag reachable from HEAD, verifies `release`
fast-forwards to it, then runs the whole stable release inside a
temporary git worktree on `release` — the primary checkout never leaves
`main`. Changesets pre-mode preserves changeset files across betas, so
the promoted stable release aggregates every changeset since the last
stable into one clean changelog entry.
## Design decisions
- **Every publish path names an explicit `--tag`.** A beta accidentally
landing on `latest` is the one unrecoverable failure of a dual-track
scheme, so nothing relies on npm's implicit default (`release.mjs`,
`version.yml`).
- **Beta channel resolves to semver-max of `latest` and `beta`**, so
beta users are offered each promoted stable once it overtakes their
prerelease. Switching beta → stable never downgrades; `fn update
--channel stable --force` is the explicit escape hatch.
- **One comparator instead of three.** CLI, dashboard, and desktop each
had their own `isRemoteNewer` that ignored prerelease identifiers —
`0.73.0-beta.2`, `-beta.3`, and `0.73.0` all compared equal, which
breaks the moment any beta exists. They now share full SemVer-precedence
helpers (`compareVersions`, `resolveUpdateTargetVersion`) from
`@fusion/core`.
- **Installs pin exact versions** (`@runfusion/fusion@0.73.0-beta.2`),
never a dist-tag, so an install can't silently land on the wrong track.
- **Desktop channels via electron-updater manifests.** Beta tags build
desktop artifacts with `publish.channel=beta` (emitting `beta*.yml`);
the app sets `channel`/`allowPrerelease` from the shared setting,
re-read on every manual check.
- **Update caches are channel-stamped** — a cache written for one
channel is never served to the other, so switching tracks takes effect
on the next check instead of after TTL.
## Test plan
- New unit coverage: SemVer precedence + channel resolution in
`@fusion/core` (30), channel behavior of the dashboard update check (28,
incl. 9 new) and `fn update` (16, incl. 8 new: persist `--channel`,
no-downgrade, `--force`, cache channel mismatch).
- `pnpm verify:fast` green (scoped typecheck, builds, CLI build, boot
smoke); desktop + settings-section suites green.
- `release.mjs` dry-run matrix exercised by hand: channel prompt
(default/override/invalid), branch preflights per channel,
assisted-promotion target selection, fast-forward guard against a
diverged `release` branch, and bootstrap when no `release` branch
exists.
- Not exercised live: an end-to-end publish (needs TTY authorization +
real npm publish). First real run is the first `pnpm release --channel
beta`.
---
[](https://github.com/EveryInc/compound-engineering-plugin)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added beta and stable release channels across CLI, dashboard, and
desktop updates.
* Users can select a channel via Settings or `fn update --channel
<stable|beta>` (stored as a global default).
* Desktop beta releases now generate beta update manifests and publish
as prereleases.
* **Documentation**
* Expanded release-track, settings, and CLI references to explain
channel semantics and workflows.
* **Bug Fixes**
* Updates now pin the resolved version per channel, improve version
comparison, and prevent unintended cross-channel downgrades unless
`--force` is used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Main Full Suite shards have been red after recent landings. Root causes:
1. **Executor tests** — `execute()` now polls
`getTaskVerificationRequestAsync` (chat-enqueued verification). Shared
`createMockStore()` (and soft-delete inline store) lacked the method, so
nearly every execute-path suite failed with `is not a function`.
2. **TaskDetailModal suites** — `NativeStructurePreview` imports `Map` /
`Lightbulb` / `BarChart3` / `Target` / `CircleAlert` from lucide; the
shared TaskDetail lucide mock omitted them, so suites failed at import.
3. **Grok process-lifecycle** — 15s bound stress timed out under
full-suite load without product-bug evidence → quarantined on sight per
AGENTS.md.
## Test plan
- [x] `executor-task-done-blocked`, `executor-fast-mode-workflows`,
concurrent-execute race
- [x] `executor-step-session`, plan-only scope leak, review-step
indexing
- [x] `TaskDetailModal.create-pr` + `TaskDetail.mobile-transition`
- [ ] Full Suite CI on this PR
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Added `html2canvas` support in the dashboard to enable HTML-to-canvas
rendering needed for visual structure previews.
* **Tests**
* Updated task execution test mocks to handle task verification-request
flows reliably.
* Improved task deletion safeguard coverage and related execution
behavior checks.
* Enhanced test stubs to support structure preview rendering elements
during modal-related tests.
* **Chores**
* Quarantined a timing-sensitive process lifecycle test and refreshed
quarantine tracking to improve full-suite stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Prevent automated tests from inheriting production PostgreSQL URLs and route global test-mode startups to a dedicated external or embedded test database.
## Summary
- Default `createAgentTask` in dashboard `@fusion/engine` mock so
planning/subtask create routes return 201 (FN-8277).
- Mock `findRecentTasksBySourceParentTaskId` on github/planning route
stores.
- Quarantine `merge-reuse-task-worktree.slow.test.ts` (engine-slow load
flake, run 29663725381).
## Evidence
- Prior full green: Full Suite run **29663526777** on #2325.
- Tip red class: routes-github/planning 500 + engine-slow lease
residual.
## Test plan
- [x] routes subtask create-tasks / shared branch groups tests green
locally
- [ ] Full Suite all 4 shards + engine-slow green on main tip after
merge
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved task and subtask creation test coverage to correctly handle
parent-scoped duplicate checks.
* Updated test behavior to return reliable task creation results.
* **Tests**
* Quarantined a flaky integration test from the slow test suite to
improve test run reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Quarantine `bundle-output.test.ts` and `extension-dist-barrel.test.ts`
after tip Full Suite shard 4 (run 29662476909) hit package-lane-only
desktop build ENOENT + 10s beforeAll timeout. Prior green Full Suite:
run 29662309385 on #2323.
## Test plan
- [ ] Full Suite all 4 shards green on main after merge
## Summary
- FN-8277 parent-scoped uniqueness: mock
`findRecentTasksBySourceParentTaskId` in heartbeat/triage/split suites.
- FN-8326: index `reportRoadmapDedup` in Settings search.
- Quarantine re-flaked `dev-server-process` under full-suite API load
(run 29661202279).
## Test plan
- [x] Targeted createTask / search-index tests green locally
- [ ] Full Suite all 4 shards green on main after merge
## Summary
- Heartbeat customTools inventory includes FN-8295 ideation tools (63
total).
- Non-en i18n parity for FN-8286 `reviewArtifacts` Command Center +
settings keys.
- Quarantine `TaskDetailModal.tab-persistence.test.tsx` (CI load flake;
green focused thrice).
## Test plan
- [x] heartbeat expected-tools case green
- [x] i18n-gate-coverage + parity green
- [ ] Full Suite all 4 shards green on main after merge