optionalGroupIdSet falls back to builtin:coding (mirroring the executor's
unselected-task resolution) so a toggled built-in group id like
browser-verification is no longer downgraded to a legacy WS-xxx step row the
graph executor never matches. Create-time optional-step controls resolve
builtin:coding when no project default workflow is set so the toggles render.
First unit of the graph-native workflow-step refactor (see
docs/plans/2026-06-25-001-refactor-workflow-steps-graph-native-plan.md).
Fusion-Task-Id: FN-7039
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GitHub import preview shows the full issue/PR body (markdown) + metadata (list already returned full bodies; removed client truncation).
- Task-detail main view: constrain width (no right cutoff); move 'Back to board' into the gray header far right.
- Dock pop-out: smoother touch drag (touch-action:none + captured-element listeners); popping out closes the dock but keeps the floating modal; modal survives dock dismiss.
- Remove duplicate inner headers in Git Manager / Activity Log / Dev Server / Secrets (dock + pop-out chrome already titles them); keep the title on mobile narrow; relocate gm Refresh into the section tab strip.
- Git Manager tabs: one scrollable row of compact icon-only tabs (max visible, scroll if needed).
- List quick entry is single-line (not tall) via singleLine prop; Board unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a defaultExpanded prop to QuickEntryBox (default true = open, preserving Board/column behavior); List view passes defaultExpanded={false} so the quick-add controls start collapsed. autoExpand (expand-on-focus) is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Many loading states rendered bare "Loading…" text with no spinner
element, and a couple rendered an unstyled `loading-spinner` div with no
matching CSS (invisible). The global spin animation was never broken —
it rotates in Chromium and WebKit — so prior transform-box edits chased
a non-bug, validated only by a string-matching CSS test.
Add a shared <LoadingSpinner> (self-contained animated SVG, no
lucide-react import so it survives partial test mocks) and adopt it
across ~47 loading placeholders so every loading state shows a
consistent animated spinner. Includes a component test that asserts the
svg carries the animate-spin utility.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preserve quick-entry mobile touch behavior for Fast and Priority controls.
- treat action touch targets as generic Elements so nested SVG taps still resolve to their parent buttons
- add mobile touch-action styling for quick-entry action buttons to improve tap handling
- extend QuickEntryBox mobile tests to cover Fast toggle, Priority picker, and SVG touch targets
Files changed:
packages/dashboard/app/components/QuickEntryBox.css | 5 +
packages/dashboard/app/components/QuickEntryBox.tsx | 2 +-
packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx | 117 +++++++++++++++++----
3 files changed, 102 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-6145
Fusion-Task-Lineage: 2af1c142-25ba-41a8-ba4f-c58e38cc870a
Reorder the Quick Entry action row so the primary create controls appear first.
- move Save, Fast, GitHub, and Priority controls to the start of the Quick Entry actions row
- preserve the existing priority picker behavior while relocating its trigger and portal markup
- update QuickEntryBox tests to assert the new DOM order and keep focus-coverage stable after the reorder
Files changed:
packages/dashboard/app/components/QuickEntryBox.tsx | 225 +++++++++++----------
packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx | 47 +++--
2 files changed, 145 insertions(+), 127 deletions(-)
Fusion-Task-Id: FN-6131
Fusion-Task-Lineage: d95401b9-4440-4c55-ad32-94df7d0c33fe
Prevent mobile quick-entry touches from stealing focus from the task textarea.
- intercept touch interactions on quick-entry action buttons before the browser refocuses them
- restore textarea focus after touch-triggered quick-entry actions complete
- avoid autofocus in the deps search input when the textarea is already focused
- add mobile regression coverage across the full quick-entry action button surface, including disabled buttons
Files changed:
packages/dashboard/app/components/QuickEntryBox.tsx | 32 +++++-
packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx | 123 +++++++++++++++++++++
2 files changed, 153 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-6128
Fusion-Task-Lineage: 598e6ba5-6e83-426c-9bf2-dd2ac4215092
Keep QuickEntryBox expanded or collapsed after creating a task instead of forcing a reset.
- stop resetForm from collapsing the textarea and disclosure controls after successful task creation
- update QuickEntryBox tests to expect preserved visible controls after submit and preserved collapsed state when manually collapsed
- cover the submit flow where focus restoration keeps controls visible without changing user-selected disclosure state
Files changed:
.../dashboard/app/components/QuickEntryBox.tsx | 2 --
.../components/__tests__/QuickEntryBox.test.tsx | 38 +++++++++++++++++-----
2 files changed, 29 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-6104
Fusion-Task-Lineage: fd77c491-cd37-4a57-8270-3eb5e7ba3c94
Make quick task entry open with its controls visible by default.
- default the quick entry textarea and disclosure controls to the expanded state
- update quick entry component tests to assert the new default-expanded behavior and collapse toggle flow
- tighten App settings-button selection to avoid collisions with quick-entry control labels
Files changed:
.../__tests__/quick-entry-expanded-height.test.tsx | 24 +--
.../dashboard/app/components/QuickEntryBox.tsx | 6 +-
.../app/components/__tests__/App.test.tsx | 8 +-
.../components/__tests__/QuickEntryBox.test.tsx | 174 ++++++++++-----------
4 files changed, 103 insertions(+), 109 deletions(-)
Fusion-Task-Id: FN-6081
Fusion-Task-Lineage: 37cce52f-170c-4f2a-b340-33bbe225f610
Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ensure quick entry keeps Shift+Enter newline behavior even before expansion.
- allow Shift+Enter to bypass submit handling in any quick-entry state
- expand the quick entry when Shift+Enter inserts a newline from the collapsed state
- update QuickEntryBox coverage to verify newline behavior without submission while collapsed
Files changed:
packages/dashboard/app/components/QuickEntryBox.tsx | 5 +++--
packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx | 13 ++++++++-----
2 files changed, 11 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-5860
Fusion-Task-Lineage: 58a65cb7-2d5b-45bb-bce9-8ffdbebbe586
Adds duplicate-submission guard to the Quick Entry box, locking the submit action before the duplicate check resolves and preventing concurrent or double submissions from creating duplicate tasks. Includes tests covering the lock/unlock flow and duplicate-skip behavior.
Fusion-Task-Id: FN-5136
Removes the `QuickEntryBox` component and its styles as part of FN-3632 cleanup, with its tests refactored accordingly. Also updates the test-changed script and fixes an isolated test home cleanup ordering issue.
Fusion-Task-Id: FN-3632
After dismissing and re-bringing up the mobile keyboard, iOS could
leave window.scrollY > 0 and visualViewport.offsetTop > 0. With
useMobileScrollLock then pinning body{position:fixed} relative to that
drifted scroll, the message thread anchored above the visible viewport
and a large blank area appeared below it.
handleInputFocus now resets window scroll to (0,0) on mobile in a
zero-delay timeout — late enough that iOS finishes its own
scroll-into-view first, but before useMobileScrollLock observes the
drifted state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes no-op `agents.filter((a) => true)` calls (a leftover from the
terminated AgentState refactor) flagged by eslint and updates affected
tests and fixtures so `terminated` is no longer referenced. Also:
- Deletes the duplicate `state === "paused"` render branch in
AgentListModal list view that produced two "Resume" buttons.
- Updates the AgentDetailView help text to reflect the current
deletable states ("idle or paused").
- Aligns the bundled-plugin-install test with the new auto-load
behavior for already-installed enabled plugins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops "terminated" from AGENT_STATES. The agent lifecycle now runs through
idle | active | running | paused | error. paused (carrying a pauseReason)
absorbs every former terminated use case — manual stop, heartbeat run
termination, spawned-child cleanup. Run status (agentRuns.status) is
unchanged: "terminated" stays a valid run-status value.
AGENT_VALID_TRANSITIONS allows direct any→idle transitions so resetAgent
no longer needs the intermediate hop.
Stack-wide:
- core/agent-store: lastError clearing + resetAgent simplified.
- engine/agent-heartbeat, executor, in-process-runtime: terminated state
writes → paused; halt-state listener fires on paused/error.
- dashboard: AgentsView/AgentListModal/AgentDetailView lose the Terminated
badge/option/state-block; agent pickers no longer filter terminated;
agentHealth drops the Terminated branch; routes/state cast widened to
the new AgentState union.
Tests across core and engine updated to assert paused for AgentState and
left "terminated" intact for run-status assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge adds a quick-entry priority control to the QuickEntryBox component, polishes GitHub source issue summary styling in the TaskDetailModal, and documents layered agent memory access in the agents reference. The changes include both the component implementation with 186 lines of new logic and
Fusion-Task-Id: FN-3186
- Increase desktop nested model menu width baseline for better model-name readability
- Raise desktop width floor from 240px to 320px while preserving viewport clamping
- Replace hardcoded QuickEntryBox menu spacing/shadow/radius values with design tokens
- Update QuickEntryBox nested menu tests to assert the new desktop width behavior
Fusion-Task-Id: FN-3010
- Add node status indicators and labels to task creation, quick entry, task form, and settings node selectors
- Surface selected node health in list bulk-edit controls with status dot styling for online/connecting/offline/error states
- Update dashboard component CSS and shared styles for consistent node status presentation
- Include changeset documenting unavailable-node routing policy and default node behavior
- Add a core node-override-guard module, export it from @fusion/core, and enforce conflicts in store updates
- Add API route and CLI extension safeguards so nodeId override updates are blocked when ownership would conflict
- Wire node override routing and validation through dashboard quick-create, list, modal, settings, and task form/detail surfaces
- Add focused unit and integration tests for core guard logic, workflow routes, and dashboard node override UX
Split app/styles.css from ~40k lines down to ~4.5k. Created 56 co-located
component CSS files in app/components/, each imported by its owning .tsx.
The remainder of styles.css holds genuinely global rules (design tokens,
.btn/.card/.modal/.form-input primitives, cross-component @media overrides).
- Lazy-load 13 heavy views (AgentsView, RoadmapsView, NodesView, etc.) via
React.lazy + Suspense; prefetch all chunks on idle so first navigation is
instant. Initial JS bundle: 1.58 MB → 1.16 MB (-26%). Initial CSS bundle:
635 kB → 471 kB (-26%); the rest splits into 13 per-view chunks.
- Add app/test/cssFixture.ts exposing loadAllAppCss() + loadAllAppCssBaseOnly()
so CSS regression tests load the full per-component bundle (mirroring Vite
source order). Migrate 30+ tests off direct readFileSync('../styles.css').
- Enable test.css: { include: [/.+/] } in vitest.config.ts so component CSS
imports actually inject styles in jsdom (fixes getComputedStyle assertions).
- Add ESLint rule (no-restricted-syntax) banning direct styles.css reads in
dashboard test files; points at loadAllAppCss() instead.
- Restore lost utility classes (.text-muted, .text-secondary, .text-dim,
.form-input) and rescue dropped chat tool-call rules into QuickChatFAB.css.
- Mobile fixes along the way: scroll containment for view containers
(min-height:0 + -webkit-overflow-scrolling), QuickChatFAB full-screen on
mobile (with safe-area-inset for iOS home bar), AgentsView single-row
header layout, ActivityLogModal close button on right, model-combobox
z-index above the mobile quick-chat panel.
- Bug fix: SkillsView toggle was display:none which hid the input from the
accessibility tree; replaced with the visually-hidden pattern so screen
readers + getByRole still find the checkbox.
- Bug fix: standalone Delete button in TaskDetailModal for triage-column
tasks (Actions dropdown is hidden in triage state, so previously no way
to delete a freshly-created task without status change first).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add QuickEntryBox state for a Fast toggle and include executionMode="fast" in create payloads when enabled
- Reset fast mode during form reset flows so follow-up creates default back to standard mode
- Add regression tests for fast toggle pressed state and payload behavior across Enter and Save submission paths
- Add tests ensuring fast mode is omitted when inactive and cleared after successful create, Plan, and Subtask flows
- TaskCard: four catch((err: any) => err.message) promise handlers in
archive/unarchive/delete/move → catch((err) => getErrorMessage(err)).
- InlineCreateCard + QuickEntryBox: .catch((err: any)) model-load handlers
→ getErrorMessage(err) with existing @fusion/core import.
- TerminalModal: drop (navigator as any).maxTouchPoints — modern lib.dom
types already expose the property.
- serve.ts: remove unused any annotation on OpenRouter model mapper; the
array element type is already inferred from json.data.
- pi.js, runtime-resolution.ts, dashboard.ts, serve.ts, dev-server-port-
detect.ts, devserver-manager.ts: drop now-stale eslint-disable comments
that the cleanup made redundant.
Fix a prompt-builder regression surfaced by agent's `any` cleanup: toolCall
with a raw string `arguments` field must be preserved verbatim (JSON-quoted)
rather than coerced to `{}`; restores a previously-passing test.
Then promote @typescript-eslint/no-explicit-any from warn → error. Future
new anys must either come with a one-line disable + justification or use a
real type. Workspace is now lint-clean (0 problems).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parallel subagent pass: four typescript-pro agents on non-overlapping scopes.
Patterns applied:
- catch (err: any) { ... err.message ... } → catch (err) { ... getErrorMessage(err) ... }
using the new @fusion/core helper. Bare catch {} where the error was unused.
- SQLite row types: defined typed XxxRow interfaces per table and cast
.all()/.get() results via `as unknown as XxxRow[]` (the double cast is
required because better-sqlite3 returns Record<string, SQLOutputValue>).
- rowToX(row: any) converters: typed argument with the matching row interface.
- Dynamic settings key writes: (settings as Record<string, unknown>)[key].
- React event handlers and setState callbacks: inferred types or concrete
React.{Mouse,Change,Form}Event<...> where needed.
- pi-claude-cli: local PiMessage / PiContext duck types to avoid re-typing
pi-ai concrete shapes; typed Claude stream event message fields.
72 files changed, ~400 anys eliminated. Typecheck passes across the workspace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Render SetupWarningBanner from App only in project view when setup readiness reports warnings
- Persist dismiss state per project via scoped storage and restore it when switching projects
- Add optional dismiss action to SetupWarningBanner with close button UI and responsive styles
- Remove inline banner from QuickEntryBox and add tests covering dismiss button behavior
- Add useSetupReadiness hook to evaluate setup state and expose actionable warning metadata
- Add reusable SetupWarningBanner component with compact and full warning presentation modes
- Render setup warnings in NewTaskModal and QuickEntryBox so task creation surfaces missing configuration early
- Add targeted tests for the hook and banner plus integration coverage updates for modal and quick-entry behavior