App.tsx dropped from 2,729 to 1,636 lines through the module-breakup refactor
(U1-U7), now well under the 2,000-line cap. Remove its grandfathered entry
from scripts/line-count-baseline.json so the file is subject to the cap going
forward and can never regress above 2,000. Scoped change: only the App.tsx
entry is removed; every other ceiling is untouched (a full `--update` would
have re-derived the whole baseline and re-raised ceilings for unrelated
grown files, which AGENTS.md forbids).
U8 (App.tsx module-breakup plan).
Extract the conditional banner cluster (~14 banners each gated on
viewMode === "project" && currentProject) into a presentational component
packages/dashboard/app/components/dashboard/DashboardBanners.tsx with a typed
DashboardBannersProps interface (39 fields) added to types.ts. The
TaskIdIntegrityBanner setDashboardHealth updater and all FNXC comments move
verbatim; banner components are imported directly from siblings.
App.tsx: 1,704 -> 1,636 lines. Behavior-preserving; App.test.tsx identical
(5 pre-existing experimental-flag failures, none introduced). Verified by
typecheck and eslint.
Completes U7 (App.tsx module-breakup plan).
Extract the inline renderMainContent() view-switch (~647 lines, ~24 view
branches) into a presentational component
packages/dashboard/app/components/dashboard/MainContent.tsx with a typed
MainContentProps interface (141 fields) in types.ts. All 18 lazy view const
DECLARATIONS stay unchanged in App.tsx (the lazy-loaded-views-docs inventory
guard regex-scans App.tsx for them — R5) and are threaded to MainContent as
LazyExoticComponent props. Branch-local consts and render-prop arrows stay
co-located inside MainContent.
App.tsx: 2,219 -> 1,704 lines, now under the 2,000-line file-count ratchet
(R3 achieved). Behavior-preserving; App.test.tsx identical (5 pre-existing
experimental-flag failures, none introduced). Verified by typecheck, eslint,
and the lazy-loaded-views-docs inventory guard.
U7a (App.tsx module-breakup plan).
Extract three AppInner state clusters into hooks:
- useMainPanelTaskDetail: the main-panel task-detail snapshot + initial tab;
setTask accepts the SetStateAction updater form so the embedded detail can
patch the snapshot on task updates.
- useBoardScrollRestore: the board scroll snapshot refs, capture, and the
double-requestAnimationFrame restore effect keyed on taskView; exposes
requestRestore for App to schedule a restore on detail close.
- usePoppedOutTasks: the popped-out task-detail windows (dedupe-by-id popOut,
close-by-id).
App keeps the navigation-history composition (openTaskDetailInMainPanel /
closeTaskDetailMainPanel) and now consumes the hooks' primitives.
Behavior-preserving; App.test.tsx identical (5 pre-existing failures, none
introduced). Verified by typecheck, eslint, and 5 renderHook tests.
Completes U6 (App.tsx module-breakup plan).
Move the capacity-risk signal (computeCapacityRisk), the settings-hydrate
guard, the re-enable-clears-dismissal behavior, the per-project dismiss
state, and the dismiss action into app/hooks/useCapacityRiskBanner.ts.
App computes agentStats / inProgressCount / inReviewCount / settings and
passes them in. Uses the named CapacityRiskSignal type (no ReturnType<typeof>).
Behavior-preserving; App.test.tsx identical (5 pre-existing failures, none
introduced). Verified by typecheck, eslint, and 3 renderHook tests (signal
computation, dismiss, re-enable-clears-dismissal after hydrate).
Part of U5 (App.tsx module-breakup plan).
Extract three more AppInner state clusters into hooks:
- useDashboardHealth: dashboard health state + mount fetch + on-demand refresh;
exposes setHealth for the TaskIdIntegrityBanner remediation callback.
- useAuthTokenRecovery: the auth-token-recovery dialog open state driven by the
AUTH_TOKEN_RECOVERY_REQUIRED_EVENT window listener.
- useScopedDismissFlag: a generic per-project dismissable banner flag (scoped
storage + project-change re-read + dismiss); backs the setup-warning banner.
Capacity-risk dismiss stays inline pending its dedicated useCapacityRiskBanner
hook. Behavior-preserving; App.test.tsx identical (5 pre-existing failures,
none introduced). Verified by typecheck, eslint, and 7 renderHook tests.
Part of U5 (App.tsx module-breakup plan).
Consolidate the working/base branch-filter state, the per-project scoped-load
effect, the change handlers, and the branchOptions/baseBranchOptions/
filteredBoardTasks memos (including the NO_BRANCH_FILTER_VALUE "no branch"
sentinel) into app/hooks/useBranchTaskFilters.ts. App computes the
remote-aware boardSourceTasks and passes it in. Behavior-preserving;
App.test.tsx identical (5 pre-existing failures, none introduced). Verified
by typecheck, eslint, and 6 renderHook tests.
Part of U4 (App.tsx module-breakup plan).
Split AppInner's single /api/events subscriber (which drove mailbox counts,
the approval banner, and the first-done GitHub-star prompt) across two hooks:
- useMailboxUnread owns the unread/pending counts, the fetchUnreadCount
refresh, and the message:*/approval:* count SSE handlers; exposes refresh
(for the approval hook) and setMailboxUnreadCount (MailboxView reports its
own count via onUnreadCountChange).
- useApprovalBanner owns the approval-banner dedupe/dismiss state machine,
the task:updated + approval:requested SSE handlers, and the star + mailbox
side effects via onStarPrompt / onMailboxRefresh callbacks (KTD4: the single
task:updated subscriber is preserved; the awaiting-approval mailbox refresh
is preserved via the callback).
The showGitHubStarPrompt boolean stays inline in App (minimal surface) wired
through a stable onStarPrompt callback. App.tsx no longer imports
fetchUnreadCount, parseDateMs, the dismissal helpers, or
didEnterAwaitingApproval/didEnterDone (moved into the hooks; still
re-exported from App for the unit-test contract).
Behavior-preserving; App.test.tsx identical to before (5 pre-existing
experimental-flag failures, none introduced). Verified by typecheck, eslint,
and 8 new renderHook tests. Completes U2 + U3.
Move the chat-unread-response badge (clear-on-chat-view effect plus the
chat:message:added / chat:room:message:added SSE subscriber) out of AppInner
into app/hooks/useChatUnreadBadge.ts (byte-faithful extraction). App.tsx no
longer imports ChatRoomMessage (the chat SSE was its only user).
Behavior-preserving; verified by dashboard typecheck, eslint, and a new
renderHook test (assistant/user message gating + clear-on-view).
Part of U2 (App.tsx module-breakup plan).
Move the 30s stash-recovery orphan poll out of AppInner into
app/hooks/useStashOrphanCount.ts (byte-faithful extraction). App.tsx now
consumes the hook and no longer imports `api` directly (the stash poll was
its only direct call site). Behavior-preserving; verified by dashboard
typecheck, eslint, and a new renderHook test (fake-timer poll assertions).
Part of U2 (App.tsx module-breakup plan).
Move the module-level pure functions (approval-banner dedupe helpers, CLI
banner actions, boot-loader/shell-onboarding guards, remote-dashboard URL
builder) and storage-key constants out of App.tsx into
app/utils/appLifecycle.ts. App.tsx re-exports the seven unit-tested symbols
so existing `from "../../App"` test imports resolve unchanged.
Behavior-preserving; no functional change. Verified: dashboard typecheck
(both passes), eslint clean on both files, 16 pure-function unit tests
pass, App.test.tsx identical to pristine (5 pre-existing experimental-flag
failures, none introduced by this change).
U1 of the App.tsx module-breakup plan
(docs/plans/2026-06-24-001-refactor-dashboard-app-tsx-module-breakup-plan.md).
Raise CustomModelDropdown portal z-index from 1200 to 11000 so model
selection popups render above the shared floating-window stack (10100+)
instead of being obscured by popped-out ChatView and other floating modals.
Let ntfy test notifications use the current Settings form values before they are saved.
- Send unsaved ntfy enabled/topic/server/token values from all Settings ntfy test buttons.
- Resolve request-scoped ntfy config on the backend for general, message, and room test notifications.
- Publish message and room ntfy tests directly with the resolved config instead of the active persisted notification service.
- Cover unsaved ntfy test behavior across Settings UI and API routes.
Files changed:
.changeset/fn-6953-ntfy-test-unsaved-config.md | 5 +
.../dashboard/app/components/SettingsModal.css | 5 +-
.../dashboard/app/components/SettingsModal.tsx | 21 +-
.../components/__tests__/SettingsModal.test.tsx | 60 +++-
.../src/__tests__/routes-settings.test.ts | 181 ++++++++---
.../src/routes/register-settings-memory-routes.ts | 355 +++++++++------------
6 files changed, 380 insertions(+), 247 deletions(-)
Fusion-Task-Id: FN-6953
Fusion-Task-Lineage: 90557168-3b11-4ab4-b5c1-4261225600ba
Treat mailbox deep links as one-shot navigation so mobile users can switch messages without stale URL state restoring the prior selection.
- consume the current mailbox deep link before explicit user navigation, compose, delete, reply, and approval actions
- prevent repeated deep-link highlighting and clear stale selections when switching agent mailboxes or subtabs
- add regression coverage for modal and full mailbox views, plus a published CLI patch changeset
Files changed:
.changeset/fn-6957-mailbox-stale-deeplink.md | 5 +
packages/dashboard/app/components/MailboxModal.tsx | 56 ++++++---
packages/dashboard/app/components/MailboxView.tsx | 60 +++++++---
.../app/components/__tests__/MailboxModal.test.tsx | 76 ++++++++++++
.../app/components/__tests__/MailboxView.test.tsx | 130 +++++++++++++++++++++
5 files changed, 293 insertions(+), 34 deletions(-)
Fusion-Task-Id: FN-6957
Fusion-Task-Lineage: 2a85c8d1-2163-434a-987f-2e842b847651
Adds an executing-task picker so dev server previews can run against in-progress task worktrees.
- Pass live task data into the Dev Server view from the main app and overflow dock.
- List in-progress tasks with worktrees, show the selected task descriptor, and prefer its worktree when starting the server.
- Fall back to the legacy start endpoint when an explicit cwd override is needed.
- Cover the picker, descriptor, cwd selection, restart guidance, and empty-state behavior with dashboard tests.
- Document task-targeted dev server previews in the dashboard guide.
Files changed:
docs/dashboard-guide.md | 1 +
packages/dashboard/app/App.tsx | 2 +-
.../dashboard/app/components/DevServerView.css | 61 +++++++++
.../dashboard/app/components/DevServerView.tsx | 85 +++++++++++-
.../components/__tests__/DevServerView.test.tsx | 146 +++++++++++++++++++++
.../app/components/overflowViewRegistry.tsx | 2 +-
packages/dashboard/app/hooks/useDevServer.ts | 13 +-
7 files changed, 303 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-6926
Fusion-Task-Lineage: beb05a39-c9f1-4017-afcd-a8d361ef232c
- Await async spawned child session disposal
- Use own-key iteration for structured tool result previews
- Add FNXC requirement comments for new regression assertions
Measure Command Center Recharts wrappers directly and keep usable fallback dimensions until lazy dashboard cards report a drawable box. This restores line charts such as Daily activity, Tokens trend, and Activity trend, plus model-share pies that used the same sizing path.
Refresh the weekly test velocity report with the latest captured baseline.
- Update the latest baseline timestamp, metric deltas, quarantine counts, and #leads posting text.
- Append the 2026-06-23 test velocity history row with wall times and slowest test files.
Files changed:
docs/test-velocity-baseline.md | 20 +++----
scripts/test-velocity-history.json | 111 +++++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+), 10 deletions(-)
Fusion-Task-Id: FN-6948
Fusion-Task-Lineage: 7b2d265a-b6c9-494c-aa87-31ae2c71c5f0
Show the dashboard footer when the global AI engine stop control is active.
- Add a stopped executor state derived from globalPause and expose it through the legacy API types.
- Render Stopped with error-red styling and a stop-rectangle icon in the executor status bar.
- Cover stopped state derivation, footer rendering, API responses, and localized labels.
Files changed:
.../dashboard/app/__tests__/api-projects.test.ts | 3 ++-
packages/dashboard/app/api/legacy.ts | 14 +++++++----
.../dashboard/app/components/ExecutorStatusBar.tsx | 11 ++++++---
.../__tests__/ExecutorStatusBar.test.tsx | 27 ++++++++++++++++++++++
.../app/hooks/__tests__/useExecutorStats.test.ts | 22 ++++++++++++++++--
packages/dashboard/app/hooks/useExecutorStats.ts | 10 +++++---
packages/i18n/locales/en/app.json | 1 +
packages/i18n/locales/es/app.json | 2 ++
packages/i18n/locales/fr/app.json | 2 ++
packages/i18n/locales/ko/app.json | 2 ++
packages/i18n/locales/zh-CN/app.json | 2 ++
packages/i18n/locales/zh-TW/app.json | 2 ++
12 files changed, 85 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-6933
Fusion-Task-Lineage: 1f128b36-105a-4133-87ce-75bb8df8a42f
Remove the orphaned session-cross-tab quarantine ledger entry after confirming the test remains active and rescued.
- Document FN-6937's rescue proof and ledger/config lockstep expectation in testing guidance.
- Keep the dashboard Vitest quarantine list empty with a note explaining the stale ledger-only cleanup.
- Remove the session-cross-tab row from the flaky-test quarantine ledger.
Files changed:
docs/testing.md | 8 ++++++--
packages/dashboard/vitest.config.ts | 3 +++
scripts/lib/test-quarantine.json | 8 +-------
3 files changed, 10 insertions(+), 9 deletions(-)
Fusion-Task-Id: FN-6937
Fusion-Task-Lineage: 34a67bf3-2a7f-49ea-93ab-68fadc81f893