fix: green full-suite after mock and stacking drift (#2287)

## Summary
Restacks onto latest main after #2285 and clears the remaining Full
Suite red classes from run
[29633869887](https://github.com/Runfusion/Fusion/actions/runs/29633869887)
(post-#2285):

- **Shard 1:** `agent-skills-flow` vitest TDZ — hoist `mockFiles` via
`vi.hoisted` (same class as skill-resolver in #2285)
- **Shard 2/3:** incomplete mocks after product drift
- `isFullScreenSheetViewport` / `isShortViewport` on viewport mocks
(without overriding dynamic mobile helpers)
  - `fetchCodebaseMetrics` on Command Center `api/legacy` mocks
  - `fetchSettings` on `agent-modals-mobile` api mock
- **Shard 3:** PlanningMode `ui-interactions` race — sync-settle
`fetchGlobalSettings` (FN-8245 pattern from planning-flow)
- **Shard 3:** settings search drift guard — inventory
`SettingsFieldRow` `htmlFor` keys (`mobileNavPrimaryItems`)
- **Shard 2:** FloatingWindow shared-stack product bug — only reclaim
z-index on hidden→visible (not every mount effect), so last-mounted
utility stays on top
- **Shard 4:** grok process-lifecycle timeout under shard load — prove
bound with 5 reimports instead of 15

## Test plan
- [x] `agent-skills-flow.test.ts` green
- [x] `process-lifecycle.test.ts` green
- [x] FileBrowserModal, FloatingWindowStack.cross-type,
agent-modals-mobile, settings-search-index, SystemControlsArea,
PlanningModeModal.ui-interactions + planning-flow (210 tests) green
- [ ] PR merge gate (Lint/Typecheck/Build/Gate)
- [ ] Post-merge Full Suite on `main` green

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved floating-window stacking so reopened or interacted windows
appear in the correct order.
* Restored consistent layering between floating windows and expanded
dock modals.
* **Tests**
* Updated automated coverage for viewport behavior, codebase metrics,
settings search indexing, and process lifecycle scenarios.
* Improved test reliability and consistency across responsive layouts
and modal interactions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
gsxdsm
2026-07-17 23:53:50 -07:00
committed by GitHub
parent b60833c419
commit 38c6fdc60d
48 changed files with 150 additions and 15 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Keep floating windows stacked by last-opened and last-interacted order.
category: fix
dev: FloatingWindow only reclaims z-index on hidden→visible (not every mount effect), restoring shared-stack cross-type ordering with RightDockExpandModal.

View File

@@ -252,9 +252,17 @@ export function FloatingWindow({
A hidden Quick Chat must reclaim a fresh z-index when reopened because another task-detail
popup may have been focused while chat was invisible. Hidden windows do not otherwise affect
the shared interaction stack.
FNXC:FloatingWindow 2026-07-18-07:15:
Only reclaim on the hidden→visible transition. Initial mount already claims via useState;
re-claiming after sibling mount (RightDockExpandModal, etc.) inverted last-mounted-on-top
and broke the shared-stack cross-type contract in FloatingWindowStack.cross-type.test.
*/
const wasHiddenRef = useRef(hidden);
useEffect(() => {
if (!hidden) bringToFront();
const wasHidden = wasHiddenRef.current;
wasHiddenRef.current = hidden;
if (wasHidden && !hidden) bringToFront();
}, [bringToFront, hidden]);
const handleDragPointerDown = useCallback(

View File

@@ -8,6 +8,8 @@ import * as apiModule from "../../api";
const mockViewportMode = vi.fn<() => "mobile" | "tablet" | "desktop">(() => "desktop");
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode(),
isMobileViewport: () => mockViewportMode() === "mobile",
useViewportMode: () => mockViewportMode(),

View File

@@ -79,6 +79,8 @@ const mockViewportMode = vi.fn<() => "mobile" | "tablet" | "desktop">(() => "des
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode(),
isMobileViewport: () => mockViewportMode() === "mobile",
useViewportMode: () => mockViewportMode(),

View File

@@ -5,6 +5,7 @@ import { CliBinaryPanel } from "../CliBinaryPanel";
import { fetchFnBinaryStatus, installFnBinary } from "../../api/legacy";
vi.mock("../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
fetchFnBinaryStatus: vi.fn(),
installFnBinary: vi.fn(),
}));

View File

@@ -9,7 +9,9 @@ const viewportModeMock = vi.hoisted(() => ({ value: "desktop" as "desktop" | "ta
const mockFetchScripts = vi.hoisted(() => vi.fn());
vi.mock("../../hooks/useViewportMode", () => ({
useViewportMode: () => viewportModeMock.value,
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => viewportModeMock.value,
}));
vi.mock("../../api", () => ({

View File

@@ -14,6 +14,8 @@ vi.mock("../../hooks/useViewportMode", () => {
const mode = () => (window.innerWidth <= 768 ? "mobile" : "desktop");
return {
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: mode,
isMobileViewport: () => mode() === "mobile",
useViewportMode: mode,

View File

@@ -20,6 +20,8 @@ const mockUseMobileKeyboard = vi.fn(() => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: () => mockUseViewportMode(),

View File

@@ -32,7 +32,9 @@ vi.mock("../../api", () => ({
vi.mock("../../hooks/useViewportMode", () => {
const useViewportMode = vi.fn();
return {
return { isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
getViewportMode: () => useViewportMode(),
isMobileViewport: () => useViewportMode() === "mobile",

View File

@@ -50,6 +50,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => "mobile",
isMobileViewport: () => true,
useViewportMode: () => "mobile",

View File

@@ -22,6 +22,8 @@ const mockSubscribeSse = vi.fn(() => vi.fn());
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode(),
isMobileViewport: () => mockViewportMode() === "mobile",
useViewportMode: () => mockViewportMode(),

View File

@@ -19,6 +19,8 @@ const mockSubscribeSse = vi.fn(() => vi.fn());
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode(),
isMobileViewport: () => mockViewportMode() === "mobile",
useViewportMode: () => mockViewportMode(),

View File

@@ -26,7 +26,9 @@ vi.mock("../../hooks/useMobileScrollLock", () => ({
vi.mock("../../hooks/useNodes", () => ({ useNodes: vi.fn(() => ({ nodes: [] })) }));
vi.mock("../../hooks/useViewportMode", () => {
const useViewportMode = vi.fn(() => "desktop");
return {
return { isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
getViewportMode: () => useViewportMode(),
isMobileViewport: () => useViewportMode() === "mobile",

View File

@@ -83,6 +83,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
let mockViewportMode: "mobile" | "desktop" = "mobile";
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode,
isMobileViewport: () => mockViewportMode === "mobile",
useViewportMode: () => mockViewportMode,

View File

@@ -80,6 +80,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => mockUseViewportMode(),
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",

View File

@@ -111,6 +111,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: () => mockUseViewportMode(),

View File

@@ -159,6 +159,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: () => mockUseViewportMode(),

View File

@@ -105,7 +105,28 @@ vi.mock("../../api", () => ({
refineTask: (...args: any[]) => mockRefineTask(...args),
fetchSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
fetchTaskEffectiveSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
fetchGlobalSettings: vi.fn().mockResolvedValue({}),
/*
FNXC:PlanningModeSettings 2026-07-18-07:20:
Same FN-8245 deterministic settle as planning-flow: Start Planning is gated on
clarificationSettingsLoading, so a microtask-resolved fetchGlobalSettings makes
fireEvent.click races no-op and leaves the suite red under full-suite load.
*/
fetchGlobalSettings: vi.fn(() => {
const settled = {
then(onFulfilled: (settings: Record<string, never>) => unknown) {
onFulfilled({});
return settled;
},
catch() {
return settled;
},
finally(onFinally: () => unknown) {
onFinally();
return settled;
},
};
return settled;
}),
fetchModels: (...args: any[]) => mockFetchModels(...args),
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
fetchBoardWorkflows: vi.fn().mockResolvedValue({ flagEnabled: false, defaultWorkflowId: "", workflows: [], taskWorkflowIds: {} }),
@@ -122,6 +143,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: () => mockUseViewportMode(),

View File

@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
const apiMock = vi.fn();
vi.mock("../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -15,6 +15,7 @@ vi.mock("../../api", async (importOriginal) => {
});
vi.mock("../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
fetchFnBinaryStatus: vi.fn(() => Promise.resolve({
binary: { binary: "fn", installed: false, path: null, version: null },
expectedVersion: "1.2.3",
@@ -29,6 +30,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
}));
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => "desktop",
getViewportMode: () => "desktop",
isMobileViewport: () => false,

View File

@@ -159,6 +159,8 @@ let viewportMode: "mobile" | "desktop" = "mobile";
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => viewportMode,
isMobileViewport: () => viewportMode === "mobile",
useViewportMode: () => viewportMode,

View File

@@ -30,6 +30,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
}));
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => "desktop",
getViewportMode: () => "desktop",
isMobileViewport: () => false,

View File

@@ -37,6 +37,8 @@ vi.mock("../../api", async (importOriginal) => {
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: (...args: unknown[]) => mockUseViewportMode(...args),
getViewportMode: (...args: unknown[]) => mockUseViewportMode(...args),
isMobileViewport: () => mockUseViewportMode() === "mobile",

View File

@@ -158,6 +158,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => "mobile",
isMobileViewport: () => true,
useViewportMode: () => "mobile",

View File

@@ -148,6 +148,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => viewportMock.mode,
isMobileViewport: () => viewportMock.mode === "mobile",
useViewportMode: () => viewportMock.mode,

View File

@@ -150,6 +150,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => "mobile",
isMobileViewport: () => true,
useViewportMode: () => "mobile",

View File

@@ -151,6 +151,8 @@ vi.mock("../../hooks/useConfirm", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => "mobile",
isMobileViewport: () => true,
useViewportMode: () => "mobile",

View File

@@ -18,6 +18,8 @@ vi.mock("../../hooks/useMemoryBackendStatus", () => ({
}));
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => "desktop",
getViewportMode: () => "desktop",
isMobileViewport: () => false,

View File

@@ -32,6 +32,8 @@ vi.mock("../../hooks/useWorktrunkInstallStatus", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
useViewportMode: () => "desktop",
getViewportMode: () => "desktop",
isMobileViewport: () => false,

View File

@@ -46,6 +46,8 @@ vi.mock("../../hooks/useMobileKeyboard", () => ({
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockViewportMode.value,
isMobileViewport: () => mockViewportMode.value === "mobile",
useViewportMode: () => mockViewportMode.value,

View File

@@ -234,6 +234,8 @@ vi.mock("../../hooks/useNodes", () => ({
const mockUseViewportMode = vi.fn(() => "desktop");
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),

View File

@@ -308,6 +308,8 @@ vi.mock("../../hooks/useNodes", () => ({
const mockUseViewportMode = vi.fn(() => "desktop");
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),

View File

@@ -9,6 +9,11 @@ import { AgentImportModal } from "../AgentImportModal";
import { AgentListModal } from "../AgentListModal";
import * as api from "../../api";
/*
FNXC:DashboardTests 2026-07-18-07:15:
AgentDetailView/AgentListModal now call fetchSettings for runtime-config surfaces.
Include it on the ../../api mock so vitest does not fail the suite on missing export.
*/
vi.mock("../../api", () => ({
fetchAgent: vi.fn(),
updateAgent: vi.fn(),
@@ -43,6 +48,7 @@ vi.mock("../../api", () => ({
resetAgentBudget: vi.fn(),
upgradeAgentHeartbeatProcedure: vi.fn(),
fetchCompanies: vi.fn(),
fetchSettings: vi.fn().mockResolvedValue({}),
}));
vi.mock("../AgentLogViewer", () => ({

View File

@@ -349,6 +349,8 @@ vi.mock("../../hooks/useNodes", () => ({
const mockUseViewportMode = vi.fn(() => "desktop");
vi.mock("../../hooks/useViewportMode", () => ({
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
isFullScreenSheetViewport: () => false,
isShortViewport: () => false,
getViewportMode: () => mockUseViewportMode(),
isMobileViewport: () => mockUseViewportMode() === "mobile",
useViewportMode: (..._args: unknown[]) => mockUseViewportMode(..._args),

View File

@@ -8,6 +8,7 @@ import { CommandCenter } from "../CommandCenter";
const apiMock = vi.fn();
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -8,6 +8,7 @@ import { CommandCenter } from "../CommandCenter";
const apiMock = vi.fn();
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -5,6 +5,7 @@ import type { LiveSnapshot } from "@fusion/core";
// Mock the api() helper so the panel fetches deterministic snapshots.
const apiMock = vi.fn();
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -4,6 +4,7 @@ import { render, screen, waitFor } from "@testing-library/react";
// Mock the api() helper so the funnel fetches a deterministic fixture.
const apiMock = vi.fn();
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -16,6 +16,7 @@ const mockFetchGlobalSettings = vi.fn();
const mockFetchNodes = vi.fn();
vi.mock("../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -16,6 +16,7 @@ const mocks = vi.hoisted(() => ({
}));
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
fetchOrgTree: mocks.fetchOrgTree,
fetchExecutorStats: mocks.fetchExecutorStats,
fetchSettings: mocks.fetchSettings,

View File

@@ -7,6 +7,7 @@ import type { DateRange } from "../../DateRangePicker";
const apiMock = vi.fn();
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => apiMock(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({
}));
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -25,6 +25,7 @@ const fetchExecutorStatsMock = mocks.fetchExecutorStats;
const toggleEnginePauseMock = mocks.toggleEnginePause;
const appSettingsMock = mocks.appSettings;
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -3,6 +3,7 @@ import { render, screen, within } from "@testing-library/react";
const mocks = vi.hoisted(() => ({ api: vi.fn() }));
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -30,6 +30,7 @@ const fetchExecutorStatsMock = mocks.fetchExecutorStats;
const toggleEnginePauseMock = mocks.toggleEnginePause;
const appSettingsMock = mocks.appSettings;
vi.mock("../../../../api/legacy", () => ({
fetchCodebaseMetrics: vi.fn().mockResolvedValue({ tokenEstimate: 0, sourceFileCount: 0, sourceByteCount: 0, diskBytes: 0, diskFileCount: 0, method: "local", truncated: false }),
api: (path: string, opts?: RequestInit) => mocks.api(path, opts),
withProjectId: (path: string, projectId?: string) =>
projectId ? `${path}${path.includes("?") ? "&" : "?"}projectId=${encodeURIComponent(projectId)}` : path,

View File

@@ -18,9 +18,21 @@ const SECTIONS_DIR = resolve(__dirname, "../../sections");
* Extracts the descriptor keys a section renders. Matches the established
* `descriptor={{ key: "..." }}` idiom every typed row uses (see GeneralSection
* and AppearanceSection); `key` is always the first property by convention.
*
* FNXC:SettingsSearch 2026-07-18-07:15:
* Also inventory bespoke SettingsFieldRow controls that anchor search via
* `htmlFor` → `data-settings-key` (e.g. general.mobileNavPrimaryItems). Those
* rows are indexed and jumpable but never use the descriptor={{ key }} shape.
*/
function extractDescriptorKeys(source: string): string[] {
return [...source.matchAll(/descriptor=\{\{\s*key:\s*"([^"]+)"/g)].map((m) => m[1]);
const keys = new Set<string>();
for (const m of source.matchAll(/descriptor=\{\{\s*key:\s*"([^"]+)"/g)) {
keys.add(m[1]);
}
for (const m of source.matchAll(/<SettingsFieldRow\b[\s\S]*?\bhtmlFor="([^"]+)"/g)) {
keys.add(m[1]);
}
return [...keys];
}
/** Section .tsx files, excluding co-located tests and non-section helpers. */

View File

@@ -6,12 +6,19 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const { mockPiLog } = vi.hoisted(() => ({
/*
FNXC:EngineTests 2026-07-18-06:40:
Hoist mock filesystem state for vi.mock factories. Module import of schema-applier
can call existsSync before const mockFiles initializes (TDZ), same class as skill-resolver.
*/
const { mockPiLog, mockFiles, mockDirCounter } = vi.hoisted(() => ({
mockPiLog: {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
mockFiles: new Map<string, string>(),
mockDirCounter: { value: 0 },
}));
vi.mock("../logger.js", () => ({
@@ -24,17 +31,13 @@ import type { Agent, AgentStore } from "@fusion/core";
// ── Mock Setup ───────────────────────────────────────────────────────────────
// In-memory file system for tests - using a module-scoped Map
const mockFiles = new Map<string, string>();
let mockDirCounter = 0;
vi.mock("node:fs", async () => {
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
return {
...actual,
existsSync: (path: unknown) => mockFiles.has(String(path)),
readFileSync: (path: unknown) => mockFiles.get(String(path)) ?? "{}",
mkdtempSync: () => `/tmp/agent-skills-flow-mock-${++mockDirCounter}`,
mkdtempSync: () => `/tmp/agent-skills-flow-mock-${++mockDirCounter.value}`,
writeFileSync: (path: unknown, content: unknown) => mockFiles.set(String(path), String(content)),
rmSync: (path: unknown) => {
const pathStr = String(path);
@@ -48,7 +51,7 @@ vi.mock("node:fs", async () => {
// ── Test Helpers ─────────────────────────────────────────────────────────────
function createMockProjectDir(settings: Record<string, unknown> | null): string {
const dir = `/tmp/agent-skills-flow-mock-${++mockDirCounter}`;
const dir = `/tmp/agent-skills-flow-mock-${++mockDirCounter.value}`;
if (settings !== null) {
mockFiles.set(`${dir}/.fusion/settings.json`, JSON.stringify(settings));
}
@@ -60,7 +63,7 @@ function createMockProjectDir(settings: Record<string, unknown> | null): string
describe("agent skills flow - full integration", () => {
beforeEach(() => {
mockFiles.clear();
mockDirCounter = 0;
mockDirCounter.value = 0;
mockPiLog.log.mockClear();
mockPiLog.warn.mockClear();
mockPiLog.error.mockClear();

View File

@@ -14,6 +14,12 @@ describe("Grok plugin process lifecycle", () => {
vi.resetModules();
});
/*
FNXC:GrokRuntimeTests 2026-07-18-07:15:
Bound is proven by a few re-evaluations (Symbol.for exit-hook guard). Full-suite
shard load made 15 dynamic imports hit the default 5s testTimeout (transform-heavy
plugin graph), not a product hang — keep the assertion, reduce iterations.
*/
it("keeps its process cleanup owner bounded across repeated module evaluation", async () => {
const baseline = listenerCounts();
const warnings: Error[] = [];
@@ -21,7 +27,7 @@ describe("Grok plugin process lifecycle", () => {
process.on("warning", onWarning);
try {
for (let iteration = 0; iteration < 15; iteration += 1) {
for (let iteration = 0; iteration < 5; iteration += 1) {
vi.resetModules();
await import("../index.js");
}