Adds fusion-react/no-nested-component-definitions, a custom rule in the house
style of the existing detached-spawn guard. A component declared in render is a
new element type every render, so React remounts its subtree on each parent
update and destroys focus, scroll, and local state.
This pattern shipped three times without review or tests catching it: FN-8606's
ModalShell left Planning Mode and Settings untypable, and MailboxModal's
ReplyContextExpandable collapsed expanded reply rows. Tests missed it because
fireEvent.change sets a value without needing the node to stay mounted.
The rule reports PascalCase functions (including memo()/forwardRef()-wrapped)
that return JSX and are declared inside another JSX-returning function.
Lowercase render helpers are deliberately allowed — they are the sanctioned fix.
Escape hatch: // nested-component-allowlist: <reason>.
Scoped to production .tsx, with a vitest guard for the rule itself. Hoists the
two pre-existing violations (ProviderStatusBadge, GitHubStatusBadge in
ModelOnboardingModal) to module scope so the rule lands clean at "error".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prototype and land a rebuilt-every-run esbuild bundle of the @fusion/core gate-safe barrel closure, collapsing the engine-core gate's per-fork Vite SSR import-phase cost (18 forks x ~430-file closure re-resolved from scratch) into a single file load per fork.
- Add scripts/build-engine-core-gate-bundle.mjs: esbuild-bundles packages/core/src/index.gate.ts (220 first-party files, packages:"external" so third-party/node: imports stay external, treeShaking:false to preserve side effects) into packages/core/.gate-bundle/core.mjs + core.meta.json
- Wire the builder into packages/engine/vitest.config.ts's engine-core project globalSetup (alongside the existing vitest-teardown hook) so the bundle is rebuilt fresh before every gate invocation, and repoint the @fusion/core resolve.alias at the bundled output instead of index.gate.ts source
- Place the bundle output at packages/core/.gate-bundle/ as a sibling of packages/core/node_modules/ (not nested inside it) to avoid Vite SSR's external-dep heuristic, which would otherwise silently defeat vi.mock interception for imports nested in the bundle
- Gitignore packages/core/.gate-bundle/ and add a matching ESLint ignore entry so the generated bundle text is never linted or committed
- Add esbuild ^0.25.12 as a root devDependency (pnpm-lock.yaml updated accordingly)
- Document the pre-bundling rationale, placement constraints, and measured A/B wall-time results in docs/testing.md
Verified: pnpm test:gate passes (335/335 engine-core tests, 63/63 CLI ci-shape tests), engine package typecheck clean, eslint clean on touched files.
Files changed:
.gitignore | 11 ++
docs/testing.md | 3 +
eslint.config.mjs | 10 ++
package.json | 1 +
packages/engine/vitest.config.ts | 50 ++++++++-
pnpm-lock.yaml | 3 +
scripts/build-engine-core-gate-bundle.mjs | 174 ++++++++++++++++++++++++++++++
7 files changed, 247 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7669
Fusion-Task-Lineage: 62b06b2a-4ac6-45ae-ac79-9771132bc303
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Add an ESLint guard to keep plugin dashboard views out of server entrypoints.
- add a custom fusion/no-plugin-view-reexport ESLint rule for plugin src/index.ts files
- flag relative re-exports of *-view entrypoints so CSS-bearing dashboard modules stay out of Node-loaded plugin entries
- document the server-entry export constraint in the plugin authoring guide
Files changed:
docs/PLUGIN_AUTHORING.md | 1 +
eslint.config.mjs | 47 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
Fusion-Task-Id: FN-6106
Fusion-Task-Lineage: 6bbfa073-e7fd-42a9-a332-c638b81cd55f
Vitest creates .tmp-fn-vitest-workspace-resolution-* directories
containing built JS files that ESLint scans as source, producing
~9500 false-positive no-undef errors that mask real lint issues.
Added .tmp-fn-*/** to global ignores. These dirs are already in
.gitignore but ESLint doesn't read .gitignore by default.
- Add `pnpm build` step to test-shards job so plugin dist/ and core
packages are compiled before tests run (fixes plugin-loader test)
- Add cli-alias/index.js to ESLint node scripts pattern so Node globals
(process, AbortController, fetch) are recognized
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- eslint: ignore .claude/** so agent worktree scripts don't pull thousands of
spurious "console/process is not defined" errors into the workspace lint.
- AgentsView: lift the controls popup max-height/overflow into a CSS modifier
(.agent-controls-panel--scrollable) instead of an inline style so the
inline-style discipline test stays at 4 (only runtime health colors).
- board-mobile.test: scan every @media (max-width: 768px) block in the loaded
CSS bundle, not just the legacy section in styles.css. After the cards/
inline-create extraction those rules now live in component CSS files.
- AgentsView.test: update the layout assertion for the new "stats above the
collection" placement and open the controls popup before asserting on
the token-usage panel (which now lives there, not in the main view body).
- agents-view-mobile.test: open the controls popup before asserting on the
token-stats panel content.
- TaskDetailModal.test: bump tab-count assertions from 7→8 (or 6→7 for
triage/todo) to include the new Stats tab, and switch to the Stats tab
before asserting on token-usage text in the optimistic-loading suite.
- Column.test: add MoreVertical/ChevronDown/ChevronUp/Archive to the
lucide-react vi.mock so the new column dropdown menu renders.
After this: pnpm lint = 0 errors, pnpm typecheck = clean, pnpm test = all
9132 dashboard + plugin/engine/cli/desktop/mobile/core tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- 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>
- no-useless-escape: drop needless backslashes in character classes and
URL/path regexes (gh-cli, store, task, modelFilter, useFileMention,
RoutineEditor, ScheduleForm).
- no-case-declarations: wrap case bodies in ProjectOverview and
SettingsModal with block scopes.
- prefer-const: convert a never-reassigned slug binding in agent-import;
annotate legitimate forward-declared let bindings in dashboard.ts that
callbacks close over before assignment.
- no-fallthrough: add missing break after settings-subcommand error.
- no-empty-interface/no-empty-object-type: convert ProjectManifest from
empty interface extension to a type alias.
- no-unused-expressions: replace `x && x.method()` short-circuits in
TerminalModal with optional chaining.
Then ratchet these rules from warn → error so regressions are blocked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Widen config match from top-level *.cjs to **/*.cjs so nested CommonJS
scripts (e.g. mcp-schema-server.cjs) get Node globals and require() allowance.
- Replace lazy require() in claude-skills.ts with a normal top-level fs import.
- Tighten an any-typed tool map in pi-claude-cli to { name: string }.
Clears the remaining 12 lint errors; workspace now has 0 errors, 450 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codebase is now clean for this rule, so enforcing it as an error prevents
regressions. Intentionally unused bindings remain exempt via the \`^_\` prefix
convention already documented in the rule options.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add ESLint config section for plugin examples with relaxed rules (no-explicit-any, no-unused-vars, no-unsafe-function-type, prefer-const, no-fallthrough, no-useless-escape)
- Add ESLint config section for agent skill templates with underscore prefix support for unused vars
- Allows underscore-prefixed parameters in skill templates (e.g., _param) as intentional placeholders
- Add roadmap schema migration v32 with covering indexes for roadmapItems table
- Implement RoadmapStore with CRUD and deterministic ordering
- Wire RoadmapStore access via TaskStore.getRoadmapStore()
- Update architecture.md with RoadmapStore persistence docs
- Fix pre-existing test failures in SettingsModal and store tests
- Add ESLint configuration (eslint.config.mjs) for TypeScript/JavaScript linting
- Update executor prompts to include lint instruction before code submission
- Add lint check to triage prompt validation workflow
- Update agent prompts to emphasize lint compliance as quality requirement
- Add lint tool to agent toolset with file-level rule disabling capability
- Include lint in CI workflow with non-blocking status
- Update tests to verify lint-inclusive prompt behavior
- Add documentation for lint integration in contributing.md
- Add changeset for @gsxdsm/fusion minor release