diff --git a/AGENTS.md b/AGENTS.md index 0ac9a7d7ce..e63c248105 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -259,7 +259,7 @@ Scoped exception (FN-5819): shared-branch-group members (`branchContext.assignme ### Lazy-Loaded Heavy Views These 20 views are lazy-loaded via `React.lazy()` with ``. -Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal imports (`SettingsModal`, `WorkflowNodeEditor`, `SetupWizardModal`), and `packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts`. +Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal imports (`SettingsModal`, `WorkflowNodeEditor`, `SetupWizardModal`), plugin settings lazy imports (`PluginManager`, `PiExtensionsManager`), AgentsView lazy imports (`AgentDetailView`), and `packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts`. - `AgentsView` - `ChatView` @@ -282,7 +282,7 @@ Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal i - `PiExtensionsManager` - `AgentDetailView` -Note: the embedded main-content views Workflows (`_WorkflowEditorView`), Import Tasks (`_ImportTasksView`), Automations (`_AutomationsView`), and Settings (`_SettingsView`) in App.tsx are `_`-prefixed lazy splits that reuse already-documented chunks. They are intentionally excluded from the curated list above and from the count; `lazy-loaded-views-docs.test.ts` filters out `_`-prefixed lazy consts (`extractAppLazyViews`), so do not add them as bullets. +Note: the embedded main-content views Workflows (`_WorkflowEditorView`), Import Tasks (`_ImportTasksView`), Automations (`_AutomationsView`), and Settings (`_SettingsView`) in App.tsx are `_`-prefixed lazy splits that reuse already-documented chunks. Task session terminals, onboarding-internal modals, duplicate `AgentDetailView` imports, and right-dock overflow re-imports of already-counted views are also intentionally excluded. These exclusions stay out of the curated list and count; `lazy-loaded-views-docs.test.ts` asserts them explicitly, so do not add them as bullets. ## FNXC_LOG comments: - Please whenever you're working on a codebase. I want you to add comments describing the date of the change (must be in this format yyyy-MM-dd-hh:mm) and describing the requirements or the change in requirements that made you implement certain functionality. diff --git a/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts b/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts index 42b4733a89..e6b2effb04 100644 --- a/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts +++ b/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts @@ -17,6 +17,12 @@ FN-6881 removes StashRecoveryView from the App-level lazy inventory because Stas FNXC:DashboardLazyViews 2026-06-22-00:00: The navigation reshuffle promotes Workflows, Import Tasks, Automations, and Settings as embedded views that reuse existing lazy chunks. Their underscore-prefixed App consts stay out of the curated inventory so the docs count each heavy chunk once. + +FNXC:DashboardLazyViews 2026-06-26-00:00: +Curated chunks declared outside App.tsx/AppModals.tsx (Plugins settings section, AgentsView) must be scanned at their real call sites so the inventory cannot drift while this guard stays green. + +FNXC:DashboardLazyViews 2026-06-26-00:00: +Session terminals, onboarding internals, and overflow re-imports of already-counted chunks are explicit exclusions because they are not new heavy top-level views or modals for the AGENTS inventory. */ import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; @@ -72,6 +78,57 @@ const EXPECTED_APP_MODALS_LAZY_VIEWS = new Set([ "WorkflowNodeEditor", ]); +const EXPECTED_PLUGINS_SECTION_LAZY_VIEWS = new Set([ + "PluginManager", + "PiExtensionsManager", +]); + +const EXPECTED_AGENTS_VIEW_LAZY_VIEWS = new Set([ + "AgentDetailView", +]); + +const EXPECTED_EXCLUDED_LAZY = [ + { + file: "../App.tsx", + symbols: ["_WorkflowEditorView", "_ImportTasksView", "_AutomationsView", "_SettingsView"], + reason: "embedded App presentations reuse already-documented modal/import chunks", + }, + { + file: "../components/TaskDetailModal.tsx", + symbols: ["LazySessionTerminal"], + reason: "task session terminal, not a top-level heavy view or modal", + }, + { + file: "../components/TaskDetailModal.tsx", + symbols: ["AgentDetailView"], + reason: "re-import of the same AgentDetailView chunk curated from AgentsView.tsx", + countedBy: "../components/AgentsView.tsx", + }, + { + file: "../components/ModelOnboardingModal.tsx", + symbols: ["ExperimentalAgentOnboardingModal"], + reason: "onboarding internal loaded inside the existing model onboarding flow", + }, + { + file: "../components/SetupWizardModal.tsx", + symbols: ["ExperimentalAgentOnboardingModal"], + reason: "onboarding internal loaded inside the existing setup wizard flow", + }, + { + file: "../components/overflowViewRegistry.tsx", + symbols: ["DevServerView", "SecretsView", "TodoView", "PullRequestView"], + reason: "right-dock overflow re-imports of App-level chunks already counted once", + countedBy: "../App.tsx", + }, +] as const; + +const EXPECTED_CURATED_LAZY_SOURCES = [ + { file: "../App.tsx", symbols: EXPECTED_APP_LEVEL_VIEWS }, + { file: "../components/AppModals.tsx", symbols: EXPECTED_APP_MODALS_LAZY_VIEWS }, + { file: "../components/settings/sections/PluginsSection.tsx", symbols: EXPECTED_PLUGINS_SECTION_LAZY_VIEWS }, + { file: "../components/AgentsView.tsx", symbols: EXPECTED_AGENTS_VIEW_LAZY_VIEWS }, +] as const; + function extractLazyLoadedSection(agentsDoc: string): string { const match = agentsDoc.match(/### Lazy-Loaded Heavy Views[\s\S]*?(?=\n### |\n---|$)/); if (!match) { @@ -102,11 +159,20 @@ function extractAppModalsLazyViews(appModalsSource: string): Set { return new Set(extractConstLazyViews(appModalsSource)); } +function expectDocumentedViews(include: Iterable, section: string): void { + for (const view of include) { + expect(EXPECTED_DOCUMENTED_VIEWS.has(view)).toBe(true); + expect(section).toContain(`\`${view}\``); + } +} + describe("AGENTS lazy-loaded views inventory", () => { it("documents the App-level and AppModals lazy views accurately and keeps the curated 20-view list in sync", () => { const agentsDoc = readFileSync(resolve(__dirname, "../../../../AGENTS.md"), "utf-8"); const appSource = readFileSync(resolve(__dirname, "../App.tsx"), "utf-8"); const appModalsSource = readFileSync(resolve(__dirname, "../components/AppModals.tsx"), "utf-8"); + const pluginsSectionSource = readFileSync(resolve(__dirname, "../components/settings/sections/PluginsSection.tsx"), "utf-8"); + const agentsViewSource = readFileSync(resolve(__dirname, "../components/AgentsView.tsx"), "utf-8"); const section = extractLazyLoadedSection(agentsDoc); const countMatch = section.match(/These\s+(\d+)\s+views\s+are lazy-loaded/); @@ -123,21 +189,54 @@ describe("AGENTS lazy-loaded views inventory", () => { expect(section).toContain("`WorkflowNodeEditor`"); expect(section).toContain("`_ImportTasksView`"); expect(section).toContain("`_AutomationsView`"); - expect((section.match(/`AgentDetailView`/g) ?? []).length).toBe(1); + expect(documentedViews.filter((view) => view === "AgentDetailView")).toHaveLength(1); const appLevelViews = extractAppLazyViews(appSource); expect(appLevelViews).toEqual(EXPECTED_APP_LEVEL_VIEWS); - for (const view of appLevelViews) { - expect(EXPECTED_DOCUMENTED_VIEWS.has(view)).toBe(true); - } + expectDocumentedViews(appLevelViews, section); const appModalsLazyViews = extractAppModalsLazyViews(appModalsSource); expect(appModalsLazyViews).toEqual(EXPECTED_APP_MODALS_LAZY_VIEWS); + expectDocumentedViews(appModalsLazyViews, section); - for (const view of appModalsLazyViews) { - expect(EXPECTED_DOCUMENTED_VIEWS.has(view)).toBe(true); - expect(section).toContain(`\`${view}\``); + /* + * FNXC:DashboardLazyViews 2026-06-26-00:00: + * Plugin manager chunks and the agent detail chunk are curated AGENTS entries, but their lazy declarations live in feature-owned source files rather than App.tsx/AppModals.tsx. Scan those real call sites so removing or renaming one of these declarations fails this inventory guard. + */ + const pluginsSectionLazyViews = new Set(extractConstLazyViews(pluginsSectionSource)); + expect(pluginsSectionLazyViews).toEqual(EXPECTED_PLUGINS_SECTION_LAZY_VIEWS); + expectDocumentedViews(pluginsSectionLazyViews, section); + + const agentsViewLazyViews = new Set(extractConstLazyViews(agentsViewSource)); + expect(agentsViewLazyViews).toEqual(EXPECTED_AGENTS_VIEW_LAZY_VIEWS); + expectDocumentedViews(agentsViewLazyViews, section); + + /* + * FNXC:DashboardLazyViews 2026-06-26-00:00: + * These lazy consts are intentionally outside the curated 20: task session terminals, onboarding internals, duplicate AgentDetail/overflow imports, and underscore-prefixed embedded views do not create additional top-level heavy inventory entries. + */ + for (const exclusion of EXPECTED_EXCLUDED_LAZY) { + const excludedSource = readFileSync(resolve(__dirname, exclusion.file), "utf-8"); + const excludedLazyViews = new Set(extractConstLazyViews(excludedSource)); + for (const symbol of exclusion.symbols) { + expect(excludedLazyViews.has(symbol), exclusion.reason).toBe(true); + } + } + + const curatedSourceKeys = new Set( + EXPECTED_CURATED_LAZY_SOURCES.flatMap((source) => [...source.symbols].map((symbol) => `${source.file}:${symbol}`)), + ); + for (const exclusion of EXPECTED_EXCLUDED_LAZY) { + for (const symbol of exclusion.symbols) { + expect(curatedSourceKeys.has(`${exclusion.file}:${symbol}`), exclusion.reason).toBe(false); + if ("countedBy" in exclusion) { + expect(curatedSourceKeys.has(`${exclusion.countedBy}:${symbol}`), exclusion.reason).toBe(true); + expect(documentedViews.filter((view) => view === symbol), exclusion.reason).toHaveLength(1); + } else { + expect(EXPECTED_DOCUMENTED_VIEWS.has(symbol), exclusion.reason).toBe(false); + } + } } }); });