Root cause of the recurring ChatView skill-menu CI flakes (and a real UX
bug): the highlight-reset effect keyed on filteredSkills array identity,
but useDiscoveredSkillsCache (SWR) re-delivers content-identical lists
with fresh identities — cache reads re-parse JSON and revalidation
notifies a new array. A revalidation landing between a user's (or the
test's) arrow-key press and the next frame wiped the highlight back to 0.
Key the reset on the joined skill-id list instead, so only a semantic
list change resets the keyboard position. Regression test proves the
invariant: deferred revalidation with identical content lands mid-
navigation and the highlight persists (fails on the old identity-keyed
reset in all three vitest projects).
This test family needed three prior stabilization passes (FN-5864,
FN-5745, FN-5725) — this addresses the underlying race rather than the
assertions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the remaining review threads:
- Reset to auto-detect across all three layers (Greptile): store passes
language:null through as null-as-delete; dashboard gains an Auto option
(clearLanguage + hasExplicitChoice in useLanguage, re-detects from
navigator, syncs cross-tab); CLI accepts 'fn settings set language auto'.
Catalog keys added for all five locales; tests at every layer.
- CLI i18n test singleton: afterEach locale restore so zh-CN/fr switches
can't leak across cases (CodeRabbit nitpick)
- useLocaleFormat memoized per locale for stable formatter identities
(CodeRabbit nitpick)
- settings-reference.md documents the auto reset path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- config: explicit Hans script wins over HK/MO region (zh-Hans-HK -> zh-CN),
with regression tests for script-vs-region precedence
- dashboard i18n: detection.caches [] so the detector's init-time auto-persist
can't masquerade as a user choice and suppress server-settings hydration
- LanguageSelector: role=group (radiogroup conflicted with aria-pressed)
- bin: validate --lang against SUPPORTED_LOCALES (fail loudly, not silent
fallback); help text clarifies the flag is terminal-UI-only
- cli i18n test: assert a real fr catalog lookup (defaultValue could mask a
catalog that never loaded); comment the async changeLanguage re-init seam
- assert-locale-chunks: only dirs containing common.json count as locales
- plan doc: merge duplicate Ink 6.8->7.0 risk bullets
- AGENTS.md/solution doc: frontmatter field list + normalizer excerpt synced
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/solutions/architecture-patterns/: knowledge doc covering the Vite
code-split catalog constraint, zh-CN/zh-TW routing, three-tier language
persistence, CLI inline-resources init, and the CI failure modes hit
- CONCEPTS.md: seed shared domain vocabulary (Surface, Global Settings,
Three-Tier Setting, Supported Locale)
- AGENTS.md: surface docs/solutions/ and CONCEPTS.md in Reference docs
- docs/settings-reference.md: add the missing GlobalSettings.language row
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- settings.test.ts mocks @fusion/core; add SUPPORTED_LOCALES to the mock so the
new language enum import resolves (shard 1).
- LanguageSelector.css used the banned --text-secondary token; switch to the
canonical --text-muted (shard 2 text-token-canonicalization guard).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract a shared normalizeToSupportedLocale helper in @fusion/i18n used by
both the CLI env detection and the dashboard navigator detection
(convertDetectedLanguage), fixing multi-subtag navigator tags (zh-Hans-CN)
and unifying Chinese script resolution.
- Add a cross-tab storage listener to useLanguage so a language change in one
tab propagates to others.
- Dedup the namespace lists into packages/i18n/namespaces.json, consumed by
config.ts and all three build scripts (no more 3-way drift risk).
- Add tests: dashboard i18n/index.ts (document.lang mirror, init non-blocking),
useLanguage concurrent-hydration race + cross-tab adoption,
normalizeToSupportedLocale, namespace-source consistency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Validate GlobalSettings.language at the write boundary (store.ts) via
validateLocale, so invalid locales are dropped not persisted (api-contract P1).
- Fix detectEnvLocale: Traditional-script env tags (zh_Hant/zh_Hant_TW/zh_HK/
zh_MO) now resolve to zh-TW instead of Simplified; use replaceAll for
multi-underscore POSIX tags (adversarial P2). Add coverage.
- Agent-native parity: add 'language' to the CLI settings allowlist
(VALID_SETTINGS + GLOBAL_ONLY + enum) so 'fn settings set language' works
like the dashboard switcher.
- Document --lang in the bin.ts help table (api-contract P3).
- Derive the expected locale-chunk count from the locales dir instead of a
hardcoded 5 (maintainability), and add prebuild:client so a raw vite build
on a fresh clone still syncs catalogs (adversarial P2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add /* global */ directives to the i18n node scripts (matching the existing
prepare-publish-manifest.mjs convention) and flip the plan status to completed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill zh-CN, zh-TW, fr, and es catalogs for the migrated keys. zh-CN and zh-TW
are independently localized (Simplified vs Traditional script + Taiwan
vocabulary, e.g. 项目/任务 vs 專案/工作), not script-converted. Translations are
machine-drafted and flagged for human review in locales/TRANSLATION_STATUS.md.
Add docs/i18n-contributing.md (translate / add-a-language / --lang usage) and
cross-link it from contributing.md and cli-reference.md. i18n:status reports
100% for all four locales across the migrated key set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Establish the terminal-UI migration pattern: route BoardView's loading/empty
labels through useTranslation("cli"), keeping single-letter keybinding
accelerators literal via interpolation ([{{key}}]). Add the keys to the en cli
catalog, sync the four locales, and regenerate the CLI import map. Tests cover
the migrated label and accelerator preservation. The remaining TUI labels are
deferred long-tail work; the pattern is in place.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a synchronous Node-side i18next instance built from the generated
@fusion/i18n CLI catalog map (no async backend, first frame localized), with
locale precedence --lang flag -> GlobalSettings.language -> env (LC_ALL/LANG/..)
-> en. Wrap the Ink DashboardApp render in <I18nextProvider> and thread a
--lang flag through runDashboard.
Upgrade ink 6.8 -> 7.0 (native CJK double-width measurement) and raise the
react/@types/react peer floor to ^19.2.0. A spike test confirms react-i18next
works under Ink's custom reconciler: localized first frame + re-render on
changeLanguage (including CJK), retiring the KTD1 unknown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the two reusable migration primitives: useLocaleFormat (date/number bound
to the active locale, replacing implicit-browser-locale toLocale* calls — R8)
and useColumnLabel (translate core COLUMN_LABELS via common:columns.* with
English fallback). Migrate the Settings Appearance heading to t() as a worked
example. The bulk migration of the ~464-file long tail is deferred follow-up
work per the plan's Scope Boundaries; the pattern and primitives are in place
so remaining clusters are mechanical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add useLanguage (three-tier persistence mirroring useTheme: localStorage cache
+ server GlobalSettings write-through + hydrate-on-mount, local choice wins)
and a LanguageSelector rendered in the Settings Appearance section. Switching
applies in place via i18n.changeLanguage — no reload, so unsaved state and
in-flight agent views survive. Endonyms name each language in its own script.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Initialize the browser i18next instance with lazy per-locale catalog loading,
localStorage->navigator->htmlTag detection, script-aware zh fallback, and the
shared @fusion/i18n config. Catalogs are synced from @fusion/i18n into a
gitignored app/locales/ (predev/prebuild) and imported app-relative so Vite
emits one chunk per locale/namespace (verified: 15 chunks, none inlined into
the main bundle). First paint is gated on i18nReady to avoid raw-key flashes;
<I18nextProvider> wraps the App provider stack; vendor-i18n manualChunk added.
A build-assertion script (verify:locale-chunks) guards the KTD3a splitting
invariant. Fallback/namespace behavior is covered by @fusion/i18n config tests;
the live instance is verified via the build assertion rather than a unit test
(the dynamic catalog backend is impractical to exercise in vitest).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Create the @fusion/i18n package as the authored source-of-truth: shared
i18next config (namespace split, script-aware zh-CN/zh-TW fallback, plural
setup), en base catalogs, and a generated CLI static-import map so the
terminal surface is drop-in for new locales. Add the i18next-cli workflow
(extract/sync/types/status/lint) wired as root i18n:* scripts, install the
i18next stack into dashboard + CLI, strip @fusion/i18n from the published CLI
manifest, gitignore the generated dashboard catalog tree, and add a changeset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SUPPORTED_LOCALES (en, zh-CN, zh-TW, fr, es), Locale, DEFAULT_LOCALE,
isLocale, and validateLocale to @fusion/core, plus an optional
GlobalSettings.language field registered in the schema defaults and keys.
Locale primitives live in types.ts so the dashboard's @fusion/core->types.ts
Vite alias can reach them. language defaults to undefined (resolve-at-runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep mobile discovered-skill rows aligned after toggles and skill enablement.
- keep .skills-view-item rows from wrapping at the mobile breakpoint
- let .skills-view-item-info flex instead of forcing full-width stacking
- add CSS regression coverage for the mobile row layout and toggle geometry
- add component coverage that preserves row structure across enabled and disabled toggle states
Files changed:
packages/dashboard/app/components/SkillsView.css | 5 ++-
.../components/__tests__/SkillsView.css.test.ts | 49 ++++++++++++++++++++++
.../app/components/__tests__/SkillsView.test.tsx | 47 +++++++++++++++++++++
.../__tests__/skills-view-mobile.test.tsx | 9 ++--
4 files changed, 104 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-5903
Fusion-Task-Lineage: 1004a702-28e7-4e7e-af22-591cc122be01
Keep MissionManager mission interview state and detail rendering in sync while stabilizing the failing test coverage.
- reload assertion-linked feature data when mission details or milestone panels load
- keep selected mission detail state synchronized after mission update events instead of forcing a full mission reload
- hide the sidebar edit form while the selected mission is already being edited in the detail pane
- add a non-destructive Send to background action for resume-launched mission interview modals
- tighten MissionManager tests to target the detail pane and slice-scoped controls that now render alongside sidebar content
Files changed:
docs/missions.md | 2 +-
.../app/components/MissionInterviewModal.tsx | 30 ++++++++
.../dashboard/app/components/MissionManager.tsx | 77 +++++++++++--------
.../components/__tests__/MissionManager.test.tsx | 88 +++++++++++++++-------
4 files changed, 137 insertions(+), 60 deletions(-)
Fusion-Task-Id: FN-5895
Fusion-Task-Lineage: d018a945-33a7-4471-9636-35bc36dc88a9
Document that workflow IR v1 keeps agent-call and typed-edge semantics as conventions rather than a 1.1 schema bump.
- replace the deferred-v1.1 note with the FN-5769 evaluation outcome
- record that prompt config and edge.condition remain the canonical v1 representation for parity rollout
Files changed:
docs/workflow-steps.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Fusion-Task-Id: FN-5769
Fusion-Task-Lineage: 341ce4fe-d87d-4c25-a1fc-652ccfbc2e94
Ensure agent-created ntfy notifications fall back to the task description when the title is still empty.
- pass task descriptions through task-created notification payloads
- cover empty-title task creation in notification service tests
- verify ntfy task-created messages render the id+description fallback
- add a patch changeset for the published CLI package
Files changed:
.changeset/fn-5890-task-created-title.md | 5 +++++
.../engine/src/__tests__/notification-service.test.ts | 12 +++++++++++-
packages/engine/src/__tests__/ntfy-provider.test.ts | 16 ++++++++++++++++
packages/engine/src/notification/notification-service.ts | 1 +
4 files changed, 33 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5890
Fusion-Task-Lineage: 4a67a4d0-c3a8-4923-a506-7a67a8836127
Retry incomplete executor work with a fresh session after non-continuable session errors.
- add executor recovery handling that clears session state and requeues incomplete tasks to todo while retry budget remains
- preserve terminal failure behavior once the fresh-session retry budget is exhausted
- extend reliability coverage and AGENTS.md backstop notes for the new retry path
Files changed:
AGENTS.md | 1 +
.../post-done-continuation-no-wedge.test.ts | 48 +++++++++++++++++++++-
packages/engine/src/executor.ts | 40 ++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5888
Fusion-Task-Lineage: 4ebce260-8923-4599-aced-541533b94543
Keep mission interview drafts recoverable when closing and refreshing Missions.
- make MissionInterviewModal use a single non-destructive close path for header, Escape, and backdrop actions
- refresh mission interview draft/session rows on mission and ai-session SSE updates and reconnects
- open Plan New Mission as a fresh interview instead of always resuming the last session, with regression coverage and docs updates
Files changed:
docs/missions.md | 2 +-
.../app/components/MissionInterviewModal.tsx | 89 +++-----
.../dashboard/app/components/MissionManager.tsx | 156 ++++++++++----
.../__tests__/MissionInterviewModal.test.tsx | 100 ++++++++-
.../components/__tests__/MissionManager.test.tsx | 224 +++++++++++++++++++++
5 files changed, 455 insertions(+), 116 deletions(-)
Fusion-Task-Id: FN-5882
Fusion-Task-Lineage: 75de9e94-edef-4564-a266-91782e60c2ac
Keep quick chat warm and restore the last opened conversation per project.
- persist the active quick chat session id in per-project local storage and restore it before falling back to latest activity
- retain in-memory quick chat state across close/reopen while resetting chat state correctly when the project changes
- add storage, hook, and FAB coverage for restored sessions, warm reopen behavior, and project switching; document the updated quick chat behavior
Files changed:
docs/dashboard-guide.md | 3 +-
packages/dashboard/app/components/QuickChatFAB.tsx | 29 ++++--
.../app/components/__tests__/QuickChatFAB.test.tsx | 112 +++++++++++++++++++--
.../__tests__/quickChatLastSessionStorage.test.ts | 55 ++++++++++
.../app/hooks/__tests__/useQuickChat.test.ts | 70 +++++++++++++
.../app/hooks/quickChatLastSessionStorage.ts | 41 ++++++++
packages/dashboard/app/hooks/useQuickChat.ts | 25 +++++
7 files changed, 319 insertions(+), 16 deletions(-)
Fusion-Task-Id: FN-5884
Fusion-Task-Lineage: 4d689611-1824-41db-8fc2-d543407316ff
Handle empty usage responses without leaving the panel in its loading skeleton.
- add a hasFetched flag to useUsageData so the UI can distinguish initial loading from an empty completed response
- update UsageIndicator to render the skeleton only before the first fetch completes and show the empty state afterward
- refresh dashboard hook and component tests to cover empty fetches, refresh behavior, and the new loading semantics
Files changed:
.../dashboard/app/components/UsageIndicator.tsx | 19 +-
.../components/__tests__/UsageIndicator.test.tsx | 395 +++++++++++----------
.../app/hooks/__tests__/useUsageData.test.ts | 42 ++-
packages/dashboard/app/hooks/useUsageData.ts | 5 +
4 files changed, 245 insertions(+), 216 deletions(-)
Fusion-Task-Id: FN-5886
Fusion-Task-Lineage: 8fa901a9-3167-4e33-a082-e5de95f692da
Restore expected terminal clipboard shortcuts.
- intercept Ctrl/Cmd+C in the dashboard terminal to copy the current selection while preserving plain SIGINT when nothing is selected
- intercept Ctrl/Cmd+V to read clipboard text and send it into the active PTY session
- add terminal shortcut regression coverage and document the new integrated terminal behavior
- add a patch changeset for @runfusion/fusion
Files changed:
.changeset/friendly-ravens-hammer.md | 5 +
docs/dashboard-guide.md | 2 +
packages/dashboard/app/components/TerminalModal.tsx | 48 +++++++++
packages/dashboard/app/components/__tests__/TerminalModal.test.tsx | 113 +++++++++++++++++++++
4 files changed, 168 insertions(+)
Fusion-Task-Id: FN-5885
Fusion-Task-Lineage: f8b3658c-2b52-4094-8cef-79c762f4d78d
Reduce SettingsModal test setup overhead around Remote and Notifications flows.
- switch key authentication assertions to await section controls directly instead of waiting for full modal readiness
- share a zero-delay user-event instance across Remote and Notifications suites and consolidate default-render setup in nested describe blocks
- replace several clear/type interaction sequences with direct change events to cut async churn while preserving coverage
Files changed:
.../components/__tests__/SettingsModal.test.tsx | 473 ++++++++++-----------
1 file changed, 224 insertions(+), 249 deletions(-)
Fusion-Task-Id: FN-5873
Fusion-Task-Lineage: d1f33301-a43f-467f-97e8-b7bfad28e009
Keep the task detail tab strip scrollable on desktop without shrinking tab labels.
- move horizontal tab-strip scrolling and momentum styles into the base .detail-tabs rule
- keep .detail-tab items from shrinking at all viewport sizes so overflow remains scrollable
- add a CSS contract test for the base tab strip behavior and extend task detail API mocks with refreshPrStatus
Files changed:
packages/dashboard/app/components/TaskDetailModal.css | 7 ++++---
.../dashboard/app/components/__tests__/TaskDetailModal.css.test.ts | 7 +++++++
.../app/components/__tests__/TaskDetailModal.test-helpers.ts | 1 +
3 files changed, 12 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5879
Fusion-Task-Lineage: b990a88e-7b1d-44d9-b222-986a28ae143d
Persist merge metadata for AI fast-forward landings and done-task recovery.
- capture landed files and shortstat metadata from the single landed commit in the AI merge finalizer
- persist mergeDetails and modifiedFiles for landed squash commits, and record commit associations without setting no-op attribution flags
- extend done-task self-healing to backfill merge metadata when baseCommitSha exists but mergeDetails is empty
- add reliability and self-healing coverage for landed-file persistence, empty AI merges, and recovery behavior
Files changed:
AGENTS.md | 1 +
.../ai-merge-ff-landed-files.test.ts | 150 +++++++++++++++++++++
packages/engine/src/__tests__/self-healing.test.ts | 76 +++++++++++
packages/engine/src/merger-ai.ts | 40 +++++-
packages/engine/src/merger.ts | 30 ++++-
packages/engine/src/self-healing.ts | 11 +-
6 files changed, 303 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-5874
Fusion-Task-Lineage: a407910c-9ce8-4049-86ef-e80f045c981a
Add a one-click Remotes sync flow that rebases on pull before pushing.
- add a Sync button in Git Manager Remotes that runs pull --rebase and then push
- stop before pushing when the rebase pull reports a conflict or fails, and surface toasts/status updates
- cover successful, conflicting, failing, and loading sync behavior in GitManagerModal tests
- document the new Sync action in the dashboard guide
Files changed:
docs/dashboard-guide.md | 1 +
.../dashboard/app/components/GitManagerModal.tsx | 39 ++++++++
.../components/__tests__/GitManagerModal.test.tsx | 100 +++++++++++++++++++++
3 files changed, 140 insertions(+)
Fusion-Task-Id: FN-5877
Fusion-Task-Lineage: 2e28c00d-b965-4f44-a6fa-26536d91d16d
Let users hide a revealed secret again from the row visibility toggle.
- add a hideSecret helper that clears reveal timers and resets the revealed value
- make the row eye toggle switch between reveal and hide states instead of reveal-only
- add a SecretsView regression test covering reveal, hide, and copy button state changes
Files changed:
packages/dashboard/app/components/SecretsView.tsx | 18 +++++++++-
packages/dashboard/app/components/__tests__/SecretsView.test.tsx | 40 ++++++++++++++++++++++
2 files changed, 57 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5878
Fusion-Task-Lineage: 936195db-88c9-48f5-8273-9b930bf6b858
Reduce SettingsModal test overhead by reusing setup and waiting on stable UI signals.
- replace generic waitFor polling with findBy* queries and direct mock call assertions
- consolidate project-general persistence coverage and split default-value checks from save-path assertions
- share Memory and Merge section setup across related tests to avoid repeated navigation and duplicate work
Files changed:
packages/dashboard/app/components/__tests__/SettingsModal.test.tsx | 333 ++++++++-------------
1 file changed, 127 insertions(+), 206 deletions(-)
Fusion-Task-Id: FN-5871
Fusion-Task-Lineage: 1b625571-f16e-4aec-8640-22d8f3bb8139
Keep the usage indicator's hidden-row toggle in sync with rendered duplicate windows.
- count hidden usage rows from the currently rendered provider windows instead of persisted label entries
- preserve the Show hidden button when duplicate live windows share a stored hidden label
- add a regression test covering duplicate Daily windows with persisted hidden state
Files changed:
.../dashboard/app/components/UsageIndicator.tsx | 12 ++++++-
.../components/__tests__/UsageIndicator.test.tsx | 42 ++++++++++++++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5875
Fusion-Task-Lineage: 648f9cb9-ea0a-4573-b08f-b1a412a46ae4