## Summary
`003948033` ("harden runtime-fallback agent-card viewport gating")
landed on `main` **without a changeset**, but it affects published
`@runfusion/fusion`. This PR adds the missing patch changeset so the fix
shows up in release notes.
## Context
Supersedes the now-closed #1963, whose code was fully redundant with
`main` — all four FUX-039 findings plus every Greptile/CodeRabbit review
comment already shipped via `003948033` and the preceding FUX-039
commits. The only remaining gap was this release-notes entry.
## What the changeset documents
- **summary (user-facing):** Prevent redundant polling and a re-render
loop in agent-card runtime-fallback badges.
- **category:** `fix`
- **dev:** `AgentsView` caches one stable ref callback per viewport key
(avoids an infinite re-render loop when `IntersectionObserver` is
unavailable) and evicts it on unmount; the test-only toast-dedupe reset
is guarded to a no-op in production builds.
## Status
- Diff: a single new file under `.changeset/`. No production code
changes.
- Gate: ✅ green — Lint, Typecheck, Build, and Gate all pass on
`d8ce3f408`.
- An earlier revision also trimmed `fn-7692`'s over-length summary to
unblock the gate, but `main` since fixed that itself in `5815cd170`, so
this branch was rebased to drop the now-redundant commit. The diff is
now purely the FUX-039 changeset.
🤖 Generated with an autonomous coding agent
Main landed the FUX-039 runtime-fallback agent-card hardening (003948033)
without a changeset, but it affects published @runfusion/fusion. This adds
the missing patch changeset so the fix appears in release notes.
## Summary
Follow-up to #1947. The full-suite on `main` is still red on 3 surfaces
introduced by post-#1947 commits. This PR fixes the two real test
failures and the i18n parity gap.
## Fixes
- **i18n key parity (FN-7658)** —
`settings.scheduling.autoArchiveDuplicateTasks` + `...Help` were added
to `en` but not the 5 non-en catalogs, breaking the i18n parity gate
(`parity.test.ts`, `i18n-gate-coverage.test.ts`). Added the 2 keys
(empty-string per the untranslated-entry convention) to `zh-CN`,
`zh-TW`, `fr`, `es`, `ko` in `packages/i18n/locales` (the single source
of truth; `dashboard/app/locales` is gitignored and synced in CI).
- **chat.test.ts (FN-7675)** — `chat.ts` now imports
`FUSION_RUNTIME_SELF_AWARENESS` from `@fusion/core` (CHAT_SYSTEM_PROMPT
embeds it); the hand-written core mock didn't stub it, so the module
failed to load. Added a stub (importOriginal intentionally avoided to
preserve the fs-cascade block).
- **verification-followup-dedup.test.ts (FN-7658)** — the "remains
additive with FN-4892 same-agent duplicate intake" test asserts the
ARCHIVE path, but FN-7658 made same-agent auto-archiving opt-in
(`autoArchiveDuplicateTasksEnabled` defaults false → flag-in-place in
triage). The test now opts into the legacy archive behavior it asserts.
## Note on shard-2 engine[2/2] timeout
The full-suite shard 2 times out on `@fusion/engine [2/2]` (watchdog
900s). Locally `[2/2]` runs in ~96s and is green (the lone
`provider-registration.test.ts` failure is local-only `pi-ai@0.79.9`
staleness — the lockfile pins `0.80.3` which exports `/compat`, so CI
resolves it). The `verification-followup-dedup` failure above is the
only real `[2/2]` defect; this PR fixes it. If the CI timeout persists
it's aggregate real-git load, which I'll address separately (not a code
bug).
## Verification
- i18n `parity` + `i18n-gate-coverage`: 7/7 ✅
- `chat.test.ts`: 14/14 ✅
- `verification-followup-dedup`: 5/5 ✅
- engine `--shard=2/2` (excluding the local-staleness file): 363 files /
4483 tests ✅ in ~96s
No production behavior change; no changeset needed (i18n catalog +
test-only).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added scheduling settings for automatic duplicate-task archiving,
including label and help text entries (currently placeholders) across
Spanish, French, Korean, Simplified Chinese, and Traditional Chinese.
* **Bug Fixes**
* Updated “awaiting confirmation” merger messaging to better reflect
when auto-merge proceeds automatically.
* **Tests**
* Updated reliability interaction tests to explicitly opt into legacy
duplicate-task archiving behavior.
* Adjusted chat-related tests by extending the runtime mock to satisfy a
new core import requirement.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Follow-up hardening on the RuntimeFallbackBadge viewport-gating work:
- AgentsView.tsx: registerAgentCardRef now returns a cached, stable callback per
key (agentCardRefCallbacksRef) instead of a fresh closure each render. A fresh
closure reads as unmount+remount to React; in environments without
IntersectionObserver the mount path calls setVisibleAgentCardKeys -> re-render
-> another fresh closure -> an infinite re-render loop (including jsdom). The
cached entry is evicted on true unmount (el === null) so the Map cannot grow
unbounded across created/deleted agents.
- ActiveAgentsPanel.tsx: document the viewport-gated badge polling with an FNXC
comment (behavior unchanged).
- useRuntimeFallbackStatus.ts: guard __resetRuntimeFallbackToastDedupeStoreForTests
to a no-op outside the test build (import.meta.env.MODE !== "test") so the
test-only dedupe reset can never affect production code paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Every animated README GIF opened on the app's page-load sequence — dark
skeleton placeholder boxes, then a "Loading files…" spinner (some also
an "AI engine is not running" banner) — before real content appeared,
making the looping GIFs look broken on first paint. This trims those
leading frames so each GIF starts on fully-rendered, populated content.
**17 dashboard-capture GIFs trimmed** (leading loading frames dropped,
per-GIF):
| GIF | dropped | GIF | dropped |
|---|---|---|---|
| command-center | 16 | chat-rooms | 18 |
| command-center-light | 10 | chat-rooms-light | 9 |
| command-center-gray | 9 | chat-rooms-gray | 10 |
| command-center-ember | 11 | chat-rooms-ember | 10 |
| workflows | 10 | agent-mail | 27 |
| workflows-light | 9 | agent-mail-light | 8 |
| workflows-gray | 16 | agent-mail-gray | 4 |
| workflows-ember | 3 | agent-mail-ember | 4 |
| agent-chat | 3 | | |
Loading-intro length varied widely per recording (3 frames to 27), so
each cut point was determined individually via frame-by-frame inspection
and visually verified.
**Left untouched:** `fusion-reel.gif`, `fusion-company-reel.gif`,
`fusion-mesh.gif` — edited reels that open on designed title cards
("From a rough idea.", "Import a company."), not loading skeletons.
## Technical notes
- Re-encoded with `gifsicle --unoptimize … --optimize=3 --lossy=60`.
`--unoptimize` is required — a naive frame cut leaves the new first
frame as a broken transparency-delta (renders as white garbage).
- `--lossy=60` keeps files at or below original size (a plain
re-optimize bloated them ~30–50%); at 2× zoom it's visually
indistinguishable from lossless and text stays crisp.
- Net total: **40.0 MB → 38.1 MB**.
- Verified frame 0 of all 17 outputs shows clean populated content — no
skeletons, no delta corruption.
No README edits needed (filenames unchanged).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Real root cause of the blank mobile terminal (the FN-7692 remeasure guard did not
fix it and is reverted here). styles.css has a mobile-only reset
`@media (max-width: 768px) { * { max-width: 100% } }` to prevent horizontal
overflow. That universal selector also matches xterm's hidden character-measurement
subtree (`.xterm-helpers` / `.xterm-char-measure-element`). That subtree's containing
block (`.xterm-helpers`) is a 0x0 absolutely-positioned box, so `max-width: 100%`
resolves to `max-width: 0` and hard-caps xterm's character-cell measurement at 0.
FitAddon.fit() then proposes 0 columns/rows and `.xterm-screen` (plus the WebGL
canvas) collapses to 0x0 — the prompt streams in and is written into xterm's row DOM
but paints into a zero-size box, so the terminal is blank. Mobile-only, which is why
desktop always rendered fine.
Reproduced live via mobile emulation: `.xterm-char-measure-element` measured 0 while
an identical monospace span in the same container measured ~295px; `max-width: none`
on the measure element restored ~295px, and reopening the terminal with the exemption
active rendered the prompt with `.xterm-screen` sized 369x760. No amount of
remeasure/refit can fix this — the CSS re-caps the measurement to 0 every time — so
the FN-7692 CharSizeService guard is removed.
- Exempt `.xterm-helpers` / `.xterm-char-measure-element` from the mobile max-width
reset in styles.css (covers both TerminalModal and SessionTerminal)
- Revert the ineffective FN-7692 remeasure guard and its tests
- Update changeset (patch) and the docs/solutions write-up to the real root cause
Note: root cause + fix validated in the automation browser via mobile emulation
(393px, iPhone UA, forced touch), not a physical device.
Fusion-Task-Id: FN-7693
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correct the planner-oversight confirmation messaging so it no longer claims a hard block when the active auto-merge policy will actually advance the merge/pull-request stage unattended.
- decidePlannerRecovery accepts an additive, messaging-only `autoMergeWillProceed` flag and picks accurate reason wording (advisory vs. genuine human-approval block vs. neutral/unknown) for merger/pull-request await_confirmation decisions
- PlannerRecoveryController.tick threads `allowsAutoMergeProcessing(task, settings)` into decidePlannerRecovery as `autoMergeWillProceed`
- project-engine's requestConfirmation steering comment prefix changed from "confirmation required" to neutral "merge checkpoint" so it doesn't contradict the now-accurate reason text
- added regression tests in planner-recovery.test.ts and planner-overseer-intervention-wiring.test.ts
- added changeset and doc note
Files changed:
.changeset/fn-7692-merger-confirmation-copy.md | 7 +++
docs/architecture.md | 10 +++-
packages/core/src/__tests__/planner-recovery.test.ts | 66 ++++++++++++++++++++++
packages/core/src/planner-recovery.ts | 36 +++++++++++-
packages/engine/src/__tests__/planner-overseer-intervention-wiring.test.ts | 37 ++++++++++++
packages/engine/src/planner-recovery-controller.ts | 14 ++++-
packages/engine/src/project-engine.ts | 11 +++-
7 files changed, 176 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7692
Fusion-Task-Lineage: 187684b8-1d24-425d-85d4-627587469908
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Summary
`RuntimeFallbackBadge.tsx` (added by an earlier change) calls
`useToast()` unconditionally. It's rendered inside `LiveAgentCard` (used
by `ActiveAgentsPanel`) and on both the board/list agent cards in
`AgentsView`, but the render helpers in `ActiveAgentsPanel.test.tsx` and
`AgentsView.test.tsx` never wrapped the component under test in a
`ToastProvider`. Every test that mounts a card threw `"useToast must be
used within ToastProvider"`.
## Fix
Adds a `renderPanel()` / `renderView()` helper to each test file —
matching the exact wrapping pattern already used correctly in
`RuntimeFallbackBadge.test.tsx` — and routes every `render(...)` call
site through it. No test assertions, fixtures, or expected behavior were
changed; this is strictly a render-setup fix.
## Before / After
- `ActiveAgentsPanel.test.tsx`: 12/15 → 15/15 passing
- `AgentsView.test.tsx`: ~96-108/132 → 130/132 passing (the 2 remaining
failures are pre-existing, unrelated CSS-content assertion failures —
not `useToast`-related — and are out of scope for this fix)
- `RuntimeFallbackBadge.test.tsx`: unaffected, still 8/8 passing
## Verification
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/ActiveAgentsPanel.test.tsx` — 15/15 pass
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/AgentsView.test.tsx` — 130/132 pass
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/RuntimeFallbackBadge.test.tsx` — 8/8 pass
- `pnpm --filter @fusion/dashboard run typecheck` — clean
- `eslint` on both modified files — clean
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Updated dashboard component tests to render within the toast context,
preventing crashes when nested UI needs toast access.
* Standardized test helpers for the agents views to better match real
app behavior.
* Adjusted one token-usage assertion to open the controls popup before
checking its content.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
A Staff Engineer pre-landing review (Greptile/CodeRabbit) on #1957
(merged) flagged four structural issues. This PR fixes the three that
were confirmed still present on `main`; the fourth (an unregistered-rule
`eslint-disable-next-line react-hooks/exhaustive-deps` comment) was
already fixed in #1957's second commit before merge and needed no
further change.
1. **`resolvePluginRuntime()` mislabeled "found but failed to init" as
`not_found`.** When a `runtimeHint` plugin registration is found but
`pluginContext`/`createRuntimeContext(...)` comes back falsy, the
resolver returned `reason: "not_found"` — indistinguishable from "never
registered" — defeating the point of a distinct `FallbackReason`. Now
returns `reason: "init_error"`. Updated the existing test that wrongly
asserted `"not_found"` for this path, and added a new test asserting all
three reachable `FallbackReason` values (`not_found`, `init_error`,
`factory_error`) are pairwise distinct.
2. **`ActiveAgentsPanel.tsx`/`AgentsView.tsx` hardcoded
`isInViewport={true}`.** Every agent card (live-agent header, board
card, list card) polled the runtime-fallback endpoint every 30s forever,
even scrolled off-screen — unlike `TaskCard.tsx`'s correct
`IntersectionObserver`-gated pattern. Both files now thread a real
`IntersectionObserver`-backed viewport signal into
`RuntimeFallbackBadge`. Added regression tests proving polling stops
once a badge instance's `isInViewport` transitions to `false` and
resumes once it goes back to `true` (desktop + a mobile-breakpoint
variant), plus verified via `tsc --noEmit` for `@fusion/dashboard`.
3. **Toast dedupe was per-hook-instance, not shared.**
`useRuntimeFallbackStatus`'s `lastToastedEventIdRef` was a local
`useRef`, so the same task rendered simultaneously in two card surfaces
(e.g. `ActiveAgentsPanel` + `AgentsView`) fired two separate toasts for
one fallback event. Dedupe now lives in module-level shared state (a
bounded `Map` keyed by `taskId:eventId`, FIFO-evicted past 500 entries)
so a fallback event toasts exactly once across every
simultaneously-mounted badge instance for the same task. Added a
cross-instance regression test mounting two badges for the same
`taskId`/`eventId` and asserting exactly one toast fires.
## Test evidence
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/runtime-resolution.test.ts --reporter=dot` — 25/25 pass
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/RuntimeFallbackBadge.test.tsx --reporter=dot` —
11/11 pass
- `pnpm --filter @fusion/dashboard run typecheck` — clean
- `pnpm --filter @fusion/engine run typecheck` — clean
## Scope
Isolated 6-file diff on top of current `main`
(`packages/engine/src/runtime-resolution.ts`,
`packages/engine/src/__tests__/runtime-resolution.test.ts`,
`packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts`,
`packages/dashboard/app/components/ActiveAgentsPanel.tsx`,
`packages/dashboard/app/components/AgentsView.tsx`,
`packages/dashboard/app/components/__tests__/RuntimeFallbackBadge.test.tsx`).
No behavior outside the three findings above was touched.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- The desktop dashboard now supports plugin-backed runtime features,
improving how plugin-enabled workflows are loaded and run.
- Agent cards now pause background fallback polling when they’re
off-screen, helping the dashboard feel smoother and more responsive.
- **Bug Fixes**
- Improved runtime fallback handling so missing runtimes and
initialization failures are reported more accurately.
- Toast notifications are now better deduplicated, reducing repeated
alerts when multiple views show the same fallback state.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The 17 dashboard-capture GIFs opened on the app page-load sequence
(skeleton placeholder boxes + "Loading files…" spinner) before real
content. Trim those leading frames per-GIF so each starts on fully
populated content. Re-encoded with gifsicle --unoptimize/--lossy=60;
total 40.0MB -> 38.1MB. Edited reels (fusion-reel/company-reel/mesh)
left untouched — they open on designed title cards, not skeletons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RuntimeFallbackBadge.tsx calls useToast() unconditionally, but the
ActiveAgentsPanel.test.tsx and AgentsView.test.tsx render helpers never
wrapped the component under test in a ToastProvider, causing every mount
that transitively renders RuntimeFallbackBadge to throw
'useToast must be used within ToastProvider'.
Adds renderPanel()/renderView() helpers (matching the existing
RuntimeFallbackBadge.test.tsx pattern) that wrap render(...) calls in
ToastProvider, and routes every render call site in both files through
them. No test assertions or fixtures were changed.
Fixes 12/15 ActiveAgentsPanel.test.tsx failures and ~96-108/132
AgentsView.test.tsx failures.
The mobile terminal rendered blank even though the WebSocket was Connected and
the shell prompt had already streamed in. Root cause (reproduced live): on the
mobile fullscreen layout xterm's CharSizeService can measure a 0-width character
cell, so FitAddon.fit() proposes 0 columns/rows and .xterm-screen (plus the WebGL
canvas) collapses to 0x0 — prompt bytes arrive and are written into xterm's row
DOM but paint into a zero-size box. Renderer-independent and mobile-layout-
specific; not fixed by resize/font-size re-fits because prior guards only validate
the container width and font load, never the resulting measured screen/cell width.
Add guardAgainstCollapsedTerminalScreen (app/utils/terminalPreferences.ts) and arm
it from both terminal surfaces (TerminalModal + SessionTerminal) right after their
initial fit. While the container has a width but .xterm-screen does not, it forces
a genuine DOM-strategy remeasure (forceTerminalFontRemeasure) + fit, re-driven by a
ResizeObserver until the screen has a real width. It waits (does not give up) while
the container is not yet measurable, is bounded so it never spins, and is disposed
on every re-init/close path. Recurrence of FN-7620/FN-7686.
- Add isTerminalScreenCollapsed + guardAgainstCollapsedTerminalScreen with tests
- Wire + dispose the guard across all xterm (re)init/close paths in both surfaces
- Add changeset (patch) and a docs/solutions write-up
Note: reproduced via mobile emulation (393px, iPhone UA, forced touch), not a
physical device; the guard is the structural fix — confirm on a real device.
Fusion-Task-Id: FN-7692
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Custom providers previously never enabled prompt-cache control, so agent turns re-billed the full context every request even on cache-capable backends.
- Add `CustomProvider.anthropicPromptCaching` opt-in flag in @fusion/core types
- Set pi-ai `compat.cacheControlFormat="anthropic"` on opted-in models in both registration paths: custom-provider-registry `toProviderConfig` and pi.ts `createFnAgent`
- Expose the new toggle in the dashboard CustomProvidersSection UI (with supporting CSS) and thread it through the legacy API + custom-provider routes
- Update docs (dashboard-guide, settings-reference) to document the new setting
- Add engine test coverage for the caching flag across provider registration and pi-create-fn-agent paths
- Add changeset for the fix
Files changed:
.changeset/fn-7689-custom-provider-prompt-caching.md | 7 +
docs/dashboard-guide.md | 1 +
docs/settings-reference.md | 2 +-
packages/core/src/types.ts | 15 ++
packages/dashboard/app/api/legacy.ts | 12 ++
packages/dashboard/app/components/CustomProvidersSection.css | 24 +++
packages/dashboard/app/components/CustomProvidersSection.tsx | 58 +++++-
packages/dashboard/src/routes/register-custom-provider-routes.ts | 16 ++
packages/engine/src/__tests__/pi-create-fn-agent.test.ts | 71 +++++++
packages/engine/src/__tests__/provider-registration.test.ts | 204 ++++++++++++++++++++-
packages/engine/src/custom-provider-registry.ts | 71 +++++--
packages/engine/src/pi.ts | 27 ++-
12 files changed, 473 insertions(+), 35 deletions(-)
Fusion-Task-Id: FN-7689
Fusion-Task-Lineage: b4f88f32-50da-4651-a546-432a95a1ab1c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Investigated whether --login in TerminalService first-prompt latency is a meaningful contributor and added a one-time diagnostic hint plus documentation of findings.
- Add SLOW_LOGIN_PROFILE_HINT_MS (2000ms) threshold and one-time, non-blocking console.info hint in createSession()'s PTY onData handler when a login shell is slow to produce first output
- Track spawnStartedAt and loginProfileHintLogged per session, and whether the succeeding spawn attempt used --login, without altering spawn args, timeouts, or the retry-without-login fallback
- Add regression tests covering the slow-login-profile hint behavior in terminal-service.test.ts
- Document the investigation and findings in docs/solutions/developer-experience/login-shell-profile-latency.md and link it from docs/dashboard-guide.md
- Add a patch changeset for @runfusion/fusion describing the new server-log hint
Files changed:
.changeset/fn-7688-login-shell-profile-latency.md | 7 ++
docs/dashboard-guide.md | 17 +++
.../login-shell-profile-latency.md | 79 ++++++++++++++
.../src/__tests__/terminal-service.test.ts | 121 +++++++++++++++++++++
packages/dashboard/src/terminal-service.ts | 57 ++++++++++
5 files changed, 281 insertions(+)
Fusion-Task-Id: FN-7688
Fusion-Task-Lineage: 08d5dd47-ea9f-4973-9f0e-a8d5fdeae111
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
On tablet-width viewports (769-1024px) the terminal header was overcrowded, so shortcut/zoom/preference controls now move into the bottom status-bar footer instead, mirroring the existing FN-7560 mobile layout while true desktop keeps the header controls.
- Add an isTabletTerminal detection flag (769-1024px, non-mobile) alongside the existing mobile flag
- Render the shared terminalActionControls fragment in the .terminal-status-bar footer for tablet widths, keeping desktop pin/pop-out toggles available there too
- True desktop (>1024px) continues to render font size / clear / shortcuts / preferences controls in the header
- Update TerminalModal.css with footer layout styles for the tablet action controls
- Update TerminalModal tests to cover the new tablet breakpoint rendering
- Update docs/dashboard-guide.md to describe the tablet footer control location
- Add a changeset (@runfusion/fusion: patch) documenting the fix
Files changed:
.changeset/fn-7684-tablet-terminal-footer.md | 7 ++
docs/dashboard-guide.md | 6 +-
.../dashboard/app/components/TerminalModal.css | 34 ++++++
.../dashboard/app/components/TerminalModal.tsx | 103 ++++++++++++-----
.../components/__tests__/TerminalModal.test.tsx | 126 ++++++++++++++++++---
5 files changed, 231 insertions(+), 45 deletions(-)
Fusion-Task-Id: FN-7684
Fusion-Task-Lineage: 627c40af-a152-451a-a045-65ad8babea9e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Speed up initial terminal load by short-circuiting the no-op server session list call when there are no persisted local tabs to validate.
- useTerminalSessions: when readTabsFromStorage returns zero tabs, skip the listTerminalSessions HTTP call entirely and mark bootstrap ready immediately, unblocking auto-create/WebSocket connect instead of serializing behind a provably-discarded round trip
- Reload-with-persisted-tabs path is unchanged and still awaits the list call since its result is decision-relevant there
- Add regression tests covering the fresh-load fast path and the persisted-tabs path
- Add changeset (patch) and a docs/solutions write-up of the bootstrap-list-serialized-before-auto-create issue
Files changed:
.changeset/fn-7686-slow-terminal-initial-load.md | 7 ++
...bootstrap-list-serialized-before-auto-create.md | 87 ++++++++++++++++++++++
.../hooks/__tests__/useTerminalSessions.test.ts | 73 ++++++++++++++++++
.../dashboard/app/hooks/useTerminalSessions.ts | 23 +++++-
4 files changed, 189 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7686
Fusion-Task-Lineage: 9c708329-6362-4c2e-967f-aea12849c47c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the mobile top header wrapping onto a second line after a foldable phone is unfolded then refolded (a live resize, not a reload).
- .header now explicitly sets flex-wrap: nowrap instead of relying on the flex default
- .header-left gets flex: 1 1 auto; min-width: 0 promoted from the mobile-only media query to the base rule, so it shrinks/truncates during the resize before the width media query re-settles
- .header-actions gets flex: 0 0 auto; min-width: 0 so the action icon cluster stays at intrinsic size and is never squeezed off-row
- Added Header.test.tsx coverage asserting the nowrap/shrink contract across populated and empty header states, on mobile/tablet/desktop
- Added useViewportMode.test.ts regression reproducing a fold->unfold->refold visualViewport resize cycle, confirming mode resolves back to mobile
- Added changeset for @runfusion/fusion (patch/fix)
Files changed:
.changeset/fn-7687-mobile-header-single-line-refold.md | 7 ++
packages/dashboard/app/components/Header.css | 14 ++++
packages/dashboard/app/components/__tests__/Header.test.tsx | 78 ++++++++++++++++++++++
packages/dashboard/app/hooks/__tests__/useViewportMode.test.ts | 59 ++++++++++++++++
4 files changed, 158 insertions(+)
Fusion-Task-Id: FN-7687
Fusion-Task-Lineage: 2b88a26e-d380-458c-b602-b6496e39311d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Upgrades Quick Add action-row height parity from a bare min-height floor to a true fixed box height, then adds a mobile-only Save correction per operator feedback so Save matches its siblings at the <=768px breakpoint without touching desktop/tablet sizing.
- Pair min-height with an equal max-height (plus tokenized line-height and centered alignment) on `.quick-entry-actions .btn, .quick-entry-actions .wf-optional-steps-dropdown-trigger` at both the desktop base rule and the <=768px touch-target media block (FN-5751: never a breakpoint-only fix)
- Add a mobile-only override scoped to `[data-testid="quick-entry-save"]` inside the <=768px media query (zero vertical padding, line-height:1) so Save's text+icon content fits the same fixed box as its siblings, leaving desktop/tablet Save sizing untouched
- Add regression coverage asserting the fixed min-height==max-height contract at both breakpoints, that shared .btn-sm/.btn-icon/.dep-trigger rules are untouched, and that the Save-only override exists only inside the mobile media query
- Add a patch changeset documenting the fix and follow-up for @runfusion/fusion
Files changed:
.changeset/FN-7683-quick-add-height-parity.md | 7 +
.../quick-entry-workflow-trigger-height.test.tsx | 181 ++++++++++++++++++++-
.../dashboard/app/components/QuickEntryBox.css | 53 ++++++
3 files changed, 234 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7683
Fusion-Task-Lineage: d3df638a-812e-4fc2-aada-cfee58d29f2b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Summary: The Planner Chat idle send button now renders icon-only, dropping its visible "Send" text span to match TaskChatTab's regular chat send button, while keeping the accessible name "Send" via aria-label.
- Removed `showSendText` prop from the planner chat send/stop button so the idle send button no longer shows a visible text span.
- Updated the CSS FNXC comment documenting that the send-text-hiding rule is now solely load-bearing for the streaming Stop button's icon-visibility contract, not the idle Send button.
- Updated the corresponding test to assert the send button has no visible text span (icon-only) while still asserting the accessible name resolves to "Send".
Files changed:
packages/dashboard/app/components/TaskPlannerChatTab.css | 7 +++++++
packages/dashboard/app/components/TaskPlannerChatTab.tsx | 5 ++++-
.../dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx | 4 +++-
3 files changed, 14 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7685
Fusion-Task-Lineage: 5ec896c5-7ca3-415b-bd5f-99a49f6a21ec
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Narrative: swaps a hardcoded px padding value for token-based spacing on the quick-entry workflow-selector trigger so it complies with the tokenized-CSS lint rule, while preserving visual sizing parity.
- Replace `padding: 4px 10px` with `padding: var(--space-sm) var(--space-md)` on `.quick-entry-workflow-trigger`.
- Add FNXC:QuickAddWorkflow comment explaining the token substitution and confirming height parity is governed by the existing FN-7680 min-height normalization, not this padding.
Files changed:
packages/dashboard/app/components/QuickEntryBox.css | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7682
Fusion-Task-Lineage: e2213e7d-5c38-460b-9727-57199d86b153
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Start OAuthRefreshScheduler before the refresh-blind OAuthExpiryMonitor so a
stale-but-refreshable access token is renewed before the monitor's first
awaited check() reads `expires`. Previously the monitor fired a false
"OAuth token expired" ntfy push on startup, moments before the refresher
silently renewed the token. Ordering locked by an invocationCallOrder
assertion in project-engine.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the self-grantable FUSION_RELEASE_AUTHORIZED env signal and replace
it with an interactive prompt: a real release now requires a live human to
type "authorized" at a TTY. Releases can no longer run non-interactively
(no TTY is blocked outright), and --yes does not bypass the typed phrase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task cards previously could show the progress/steps breakdown while still in the Planning (triage) column when a review gate was active; this changes it to only show once a task leaves Planning, matching ListView's behavior.
- TaskCard.showProgressSection now only shows for `in-progress`/`executing` tasks, dropping the special-case `triage` + active-progress-count branch
- Updated FNXC:TaskCardWorkflowProgress comment to document the FN-7676 requirement
- Updated TaskCard tests to cover the new triage-column behavior
- Added changeset for the patch
Files changed:
.changeset/fn-7676-planning-steps-breakdown.md | 7 +++
packages/dashboard/app/components/TaskCard.tsx | 7 ++-
.../app/components/__tests__/TaskCard.test.tsx | 50 ++++++++++++++++++----
3 files changed, 51 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-7676
Fusion-Task-Lineage: 85fcf8f9-1010-4b68-9323-9d8ce03aa66f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Agents were composing plans (e.g. reboot/wait-and-retry loops) that assumed they could keep acting even after the Fusion platform itself shut down, since prompts never told them they run inside Fusion. This adds a shared, docs-grounded self-awareness preamble prepended to chat, heartbeat, and executor base prompts so agents know their own runtime constraints.
- Added FUSION_RUNTIME_SELF_AWARENESS shared preamble in packages/core/src/agent-prompts.ts, exported via packages/core/src/index.ts
- Prepended the preamble to the chat system prompt (packages/dashboard/src/chat.ts)
- Prepended the preamble to the heartbeat session prompt (packages/engine/src/agent-heartbeat.ts)
- Prepended the preamble to the executor base prompt (packages/engine/src/executor.ts)
- Updated docs/agents.md and CONCEPTS.md to document the new self-awareness/capability-grounding behavior
- Added regression tests across core, dashboard, and engine covering the new prompt content
- Added changeset for @runfusion/fusion (minor, fix category)
Files changed:
.changeset/fn-7675-agent-runtime-self-awareness.md | 7 ++++
CONCEPTS.md | 4 +-
docs/agents.md | 17 ++++++++
packages/core/src/__tests__/agent-prompts.test.ts | 41 ++++++++++++++++++++
packages/core/src/agent-prompts.ts | 32 ++++++++++++++-
packages/core/src/index.ts | 1 +
packages/dashboard/src/__tests__/chat-system-prompt.test.ts | 17 ++++++++
packages/dashboard/src/chat.ts | 6 ++-
packages/engine/src/__tests__/executor-prompt.test.ts | 45 ++++++++++++++++++++++
packages/engine/src/__tests__/heartbeat-session-prompt.test.ts | 35 +++++++++++++++++
packages/engine/src/agent-heartbeat.ts | 10 +++--
packages/engine/src/executor.ts | 7 +++-
12 files changed, 213 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-7675
Fusion-Task-Lineage: 126d04a6-2c68-4347-9789-591b274277bf
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
## Problem
A task's model fields (`modelProvider`/`modelId` and the
planning/validator
equivalents) are snapshotted once at task-creation time from the
workflow's
model-lane default in `workflow_settings`. Nothing re-syncs, flags, or
surfaces drift when that default is later changed.
Concretely: the `builtin:coding` workflow's execution default was
`claude-sonnet-4-6` until it was corrected on 2026-07-05. Every task
created
before that correction stayed permanently, invisibly pinned to the stale
model id — 52 tasks were found silently stuck on it.
## Fix
- `TaskStore.getModelLaneDrift(workflowId, before, after)`
(`packages/core/src/store.ts`):
read-only diff over the three model lanes
(execution/planning/validator).
For any lane whose provider+modelId actually changed, it lists the
non-terminal (`column` not `archived`/`done`, not soft-deleted) tasks on
that workflow still pinned to the old value. Never mutates `tasks`.
- Wired into `PATCH /workflows/:id/setting-values`
(`packages/dashboard/src/routes/register-workflow-routes.ts`): captures
a
`before` snapshot, runs the existing `updateWorkflowSettingValues`
unchanged, then attaches an optional `modelDrift` field to the response
when a lane change orphans existing tasks. Backward compatible — the
field
is only present when non-empty.
- Operators can act on the surfaced drift via the existing
`POST /tasks/batch-update-models` endpoint; this change intentionally
does
not auto-rewrite any task (avoids touching tasks mid-execution).
## Testing
- New tests in `packages/core/src/__tests__/workflow-settings.test.ts`
(`TaskStore.getModelLaneDrift`): verifies a task pinned to a changed
lane's
old value is surfaced, a task already on the new value and a
`done`-column
task are excluded, and an unrelated/unchanged lane produces no drift
entry.
- `packages/core`: `npx vitest run
src/__tests__/workflow-settings.test.ts` — 24/24 pass.
- `npx tsc --noEmit` clean in both `packages/core` and
`packages/dashboard`.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Workflow setting updates can now return a lane-based model drift
summary (execution, planning, validator) showing task IDs still pinned
to the previous model configuration.
* The PATCH workflow setting response conditionally includes
`modelDrift` when impacted tasks are found.
* **Bug Fixes**
* Drift detection now compares a consistent “before” snapshot with the
updated values to avoid stale pairing.
* “No workflow selection” tasks are handled correctly based on the
default-workflow behavior.
* **Tests**
* Added coverage for lane drift across model changes and null-selection
inclusion rules.
* **Documentation**
* Added a release note entry for the change.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- getModelLaneDrift now takes an explicit includeNullSelection option; the
setting-values route passes it when patching the project default workflow so
no-workflow-selection tasks (which resolve through the default) are counted
instead of silently dropped (Greptile P1).
- Add updateWorkflowSettingValuesWithPrevious so the drift baseline is captured
inside the settings write transaction, removing the stale-read race against a
concurrent patch of the same row (Greptile P2).
- Broaden getModelLaneDrift tests to cover planning and validator lanes and the
null-selection/default-workflow case (FN-5893 invariant across surfaces).
- Add changeset.
CodeRabbit's effective-values suggestion is intentionally skipped: model-lane
declarations carry no declaration-level default (KTD-7), so effective == raw for
these keys and comparing effective values is a no-op.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes/relates to Runfusion/Fusion#1956.
## Summary
Surfaces silent runtime-resolution fallback in the dashboard, and
threads the real `FallbackReason` ("not_found" vs "factory_error")
through instead of hardcoding `"not_found"` for every fallback.
## Changes
- `packages/engine/src/runtime-resolution.ts`: `resolvePluginRuntime()`
now returns a tagged miss result (`{ ok: false, reason }`)
distinguishing "not found" from "factory/instantiation error" instead of
collapsing both to `null`. `resolveRuntime()` threads the real reason
through to `logRuntimeFallback(...)` and returns it via
`ResolvedRuntime.fallbackReason`.
- `packages/engine/src/agent-session-helpers.ts`:
`createResolvedAgentSession()` includes `fallbackReason` in the
`session:runtime-resolved` audit event metadata when present.
- `packages/dashboard/src/routes/register-task-workflow-routes.ts`: new
`GET /api/tasks/:id/runtime-fallback` endpoint, returning the most
recent `session:runtime-resolved` event normalized for UI consumption
(`wasConfigured`, `runtimeHint`, `reason`, `showFallbackBadge`).
- `packages/dashboard/app/hooks/useRuntimeFallbackStatus.ts` (new):
polls the endpoint, dedupes toast firing per audit-event-id.
- `packages/dashboard/app/components/RuntimeFallbackBadge.tsx` (new):
renders the badge + fires the toast; wired into `TaskCard.tsx`,
`ActiveAgentsPanel.tsx`, and `AgentsView.tsx` (board and list variants).
## Test plan
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/runtime-resolution.test.ts` — 24/24 pass (21 pre-existing
+ 3 new, none weakened)
- `pnpm --filter @fusion/dashboard exec vitest run
src/routes/__tests__/register-task-workflow-routes.runtime-fallback.test.ts`
— 5/5 pass
(empty/configured-ok/fallback-with-hint/fallback-blank-hint/stale-superseded
states)
- `pnpm --filter @fusion/dashboard exec vitest run
app/components/__tests__/RuntimeFallbackBadge.test.tsx` — 8/8 pass (all
data states + mobile breakpoint + toast-fires-once)
- `pnpm --filter @fusion/dashboard run typecheck` and `pnpm --filter
@fusion/engine run typecheck` — both clean
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added runtime-fallback warning badges across task and agent views
(including board and “working on” sections) with automatic toast
notifications.
* Introduced a new backend API to surface the latest runtime-fallback
state for a task.
* Added runtime-fallback status polling and UI messaging to reflect the
most recent state.
* **Bug Fixes**
* Prevented repeated toasts by deduplicating notifications across
polling updates.
* Improved fallback reporting so the UI reflects the latest
runtime-resolved audit event.
* Enhanced diagnostics by distinguishing fallback reasons (e.g., missing
runtime vs factory failure) for clearer user guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
The repo eslint config omits react-hooks/exhaustive-deps, so a disable
directive for it is itself a lint error (rule-not-found). Replace with a
plain comment documenting the intentional dep omission.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Narrative: FN-7673 re-attempted the engine-core gate bundle lever with a single combined-entry engine-graph design (all 14 mock-safe roots redirected through a resolveId plugin to one synthetic packages/engine/.gate-bundle/engine.mjs) after FN-7670's 14-separate-root attempt was inconclusive. This update records the negative A/B result and closes the lever.
- Documented that the combined-entry design achieved its structural goal (149 first-party inputs -> 1 output file) and full 335/335 coverage parity
- Recorded a true interleaved A/B (5 warm + 1 cold pair) showing the combined-entry bundle is consistently slower than the @fusion/core-only baseline (warm median +29.1%, import-phase aggregate +74.0%)
- Captured the working theory: funnelling 14 relative-import sites through a resolveId-plugin redirect to one large synthetic export-* file adds more transform/resolution overhead than it saves, unlike @fusion/core's plain resolve.alias
- Noted the experiment was NOT landed; wiring (engine-graph scans, combined-entry builder, resolveId plugin) was fully reverted
- Marked this lever (bundling the @fusion/engine relative-import graph for the engine-core gate, in either 14-file or single-combined-entry shape) as CLOSED absent new evidence
Files changed:
packages/engine/vitest.config.ts | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7673
Fusion-Task-Lineage: 46951e5f-e7dc-4f7c-9601-0cfa0b082d70
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Removes a dead test-file reference from the engine-core vitest gate include list, with a code comment documenting why.
- Remove the nonexistent `src/__tests__/merger-post-merge.test.ts` entry from packages/engine/vitest.config.ts's engine-core include list (retired by FN-7039; graph is now sole post-merge owner)
- Add FNXC comment noting the entry matched zero files and that graph post-merge coverage lives in workflow-graph-post-merge.test.ts (engine-default)
Files changed:
packages/engine/vitest.config.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7671
Fusion-Task-Lineage: 73447412-7b8a-4578-a2b8-07f83e381548
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Root-causes 4 correlated CTO-report agent failures where durable non-ephemeral agents got stuck in `error` state indefinitely because the heartbeat scheduler stops ticking error-state agents entirely, and self-healing's recovery sweep previously only considered them when their manager row was missing.
- SelfHealingManager: scope the `managerMissing` gate to the "running" orphan-detection path only, so "error"-state durable agents with a present/active manager now fall through to the existing transient/operator-actionable/active-execution/cooldown/retry-budget recovery guards instead of being skipped outright
- Add FNXC:AgentHeartbeat comment documenting the FN-7672 incident and rationale for the scoping change
- Extend self-healing.test.ts with coverage for manager-present durable agents in error state
- Add changeset (patch) describing the fix for release notes
- Update docs/agents.md accordingly
Files changed:
.changeset/fn-7672-durable-agent-recovery.md | 7 ++
docs/agents.md | 2 +
packages/engine/src/__tests__/self-healing.test.ts | 129 ++++++++++++++++++++-
packages/engine/src/self-healing.ts | 24 +++-
4 files changed, 160 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7672
Fusion-Task-Lineage: 6676dc9e-66e7-4f70-804a-cccf77e8d337
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Prototyped extending the @fusion/core pre-bundle alias lever to @fusion/engine's relative-import production graph reached by the 18 gate files, but an A/B showed no clear win over the @fusion/core-only bundle, so the change was not landed and only the rationale is recorded.
- Added an FNXC:EngineTests comment block in packages/engine/vitest.config.ts documenting the FN-7670 prototype (171 first-party files → 35 output files via esbuild multi-entry splitting)
- Recorded the negative A/B result: byte-size growth of 14 separate large root bundles offset per-file-dispatch savings, with no clear win beyond host run-to-run noise
- Left the vitest alias wiring unchanged at the @fusion/core-only bundle state, pointing future attempts to FN-7670's task docs for full analysis and to consider a single combined engine-graph entry instead of 14 separate root entries
Files changed:
packages/engine/vitest.config.ts | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
Fusion-Task-Id: FN-7670
Fusion-Task-Lineage: efd27f94-a6c4-49c7-a78e-50213fd42a24
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Prototype and land a rebuilt-every-run esbuild bundle of the @fusion/core gate-safe barrel closure, collapsing the engine-core gate's per-fork Vite SSR import-phase cost (18 forks x ~430-file closure re-resolved from scratch) into a single file load per fork.
- Add scripts/build-engine-core-gate-bundle.mjs: esbuild-bundles packages/core/src/index.gate.ts (220 first-party files, packages:"external" so third-party/node: imports stay external, treeShaking:false to preserve side effects) into packages/core/.gate-bundle/core.mjs + core.meta.json
- Wire the builder into packages/engine/vitest.config.ts's engine-core project globalSetup (alongside the existing vitest-teardown hook) so the bundle is rebuilt fresh before every gate invocation, and repoint the @fusion/core resolve.alias at the bundled output instead of index.gate.ts source
- Place the bundle output at packages/core/.gate-bundle/ as a sibling of packages/core/node_modules/ (not nested inside it) to avoid Vite SSR's external-dep heuristic, which would otherwise silently defeat vi.mock interception for imports nested in the bundle
- Gitignore packages/core/.gate-bundle/ and add a matching ESLint ignore entry so the generated bundle text is never linted or committed
- Add esbuild ^0.25.12 as a root devDependency (pnpm-lock.yaml updated accordingly)
- Document the pre-bundling rationale, placement constraints, and measured A/B wall-time results in docs/testing.md
Verified: pnpm test:gate passes (335/335 engine-core tests, 63/63 CLI ci-shape tests), engine package typecheck clean, eslint clean on touched files.
Files changed:
.gitignore | 11 ++
docs/testing.md | 3 +
eslint.config.mjs | 10 ++
package.json | 1 +
packages/engine/vitest.config.ts | 50 ++++++++-
pnpm-lock.yaml | 3 +
scripts/build-engine-core-gate-bundle.mjs | 174 ++++++++++++++++++++++++++++++
7 files changed, 247 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7669
Fusion-Task-Lineage: 62b06b2a-4ac6-45ae-ac79-9771132bc303
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Introduces a project-scoped @fusion/core barrel used only by the engine-core
gate project, so new feature modules added to the full barrel don't silently
inflate the gate's transform/import cost.
- Add packages/core/src/index.gate.ts, a copy of the full @fusion/core barrel
minus export statements for modules added since the last re-audit baseline
(i.e. it still re-exports everything the full barrel does except newly
added, gate-irrelevant feature modules).
- Update packages/engine/vitest.config.ts to add a project-scoped
resolve.alias mapping @fusion/core -> packages/core/src/index.gate.ts for
the engine-core project only; engine-default/engine-reliability/engine-slow
and @fusion/engine continue to resolve the full barrel.
- Document the gate-safe barrel and its audit procedure in docs/testing.md.
Files changed:
docs/testing.md | 3 +
packages/core/src/index.gate.ts | 2102 ++++++++++++++++++++++++++++++++++++++
packages/engine/vitest.config.ts | 17 +
3 files changed, 2122 insertions(+)
Fusion-Task-Id: FN-7667
Fusion-Task-Lineage: 054ec89a-d973-44dd-b9ac-ad266f553f01
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Rename the Command Center SDLC funnel's intake stage display text from "Triage" to "Planning" to match the renamed board column, updating English locale copy and tests; the underlying aggregator/i18n key names stay "triage".
- SdlcFunnel.tsx: stage label fallback, enteredInRange, and completionRateAria copy changed to Planning/Entered planning/in-range planning entrants; added FNXC comment explaining the key-vs-label distinction
- packages/i18n/locales/en/app.json: synced English strings for the same three keys
- SdlcFunnel.test.tsx: updated assertions to match new Planning copy
- Added changeset (patch) documenting the label rename
Files changed:
.changeset/fn-7665-funnel-planning-label.md | 7 +++++++
.../app/components/command-center/SdlcFunnel.tsx | 16 ++++++++++++----
.../command-center/__tests__/SdlcFunnel.test.tsx | 6 +++---
packages/i18n/locales/en/app.json | 6 +++---
4 files changed, 25 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-7665
Fusion-Task-Lineage: d8aec609-6b8b-4f36-bb43-61d7c7c421f9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Removes duplicated SSE streaming logic between the /automations/:id/run/stream and /routines/:id/run/stream endpoints by extracting a single generic makeRunStreamHandler factory.
- Add makeRunStreamHandler<TStore, TEntity> factory in routes.ts, parameterized by store resolver, entity getter, and not-found message
- Replace the two near-identical inline SSE handlers (connect headers, buffered replay, run subscription, auto-attach, teardown, error handling) with calls to the shared factory
- Import ScopeValue type from ./routes/types.js to type the factory's scope parameter
- Add regression tests in routes-automation.test.ts covering the consolidated handler behavior for both routes
Files changed:
.../src/__tests__/routes-automation.test.ts | 35 +++
packages/dashboard/src/routes.ts | 246 +++++++++------------
2 files changed, 137 insertions(+), 144 deletions(-)
Fusion-Task-Id: FN-7663
Fusion-Task-Lineage: 55ede468-c4ed-4a72-a428-7d19686e8ea5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>