docs: document CSS extraction, test consolidation, and TUI merge
- AGENTS.md: per-component CSS layout, loadAllAppCss test helper, ESLint guardrail, lazy view list + Suspense pattern; __tests__/ convention; TUI now invoked via fn (no separate @fusion/tui package). - docs/contributing.md: Dashboard CSS organization + test layout sections; workspace package table reflects fn CLI bundling the TUI. - docs/architecture.md: CSS architecture subsection; updated CLI/TUI references to packages/cli/src/commands/dashboard-tui/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
85
AGENTS.md
85
AGENTS.md
@@ -54,6 +54,15 @@ pnpm build # build all packages
|
||||
|
||||
Tests are required. Typechecks and manual verification are not substitutes for real tests with assertions.
|
||||
|
||||
### Test File Organization
|
||||
|
||||
All test files have been moved into `__tests__/` subdirectories alongside the code they test:
|
||||
|
||||
- Test for `src/foo.ts` → `src/__tests__/foo.test.ts`
|
||||
- Test for `app/components/Bar.tsx` → `app/components/__tests__/Bar.test.tsx`
|
||||
|
||||
When writing new tests, follow this convention. A few legacy co-located test files may remain, but `__tests__/` is the standard.
|
||||
|
||||
### What NOT to write
|
||||
|
||||
New tests should cover behavior a user could notice break, not implementation shape. Don't write:
|
||||
@@ -320,6 +329,19 @@ When debugging agent execution issues (agents stuck on "starting"), check these
|
||||
- `limit` getter returns minimum 1 (prevents indefinite blocking)
|
||||
- `availableCount` returns 0 for invalid limits (NaN, Infinity, ≤0)
|
||||
|
||||
## Terminal UI (TUI) — Now Part of `fn` CLI
|
||||
|
||||
The `@fusion/tui` package has been merged into the `fn` CLI. The Ink-based TUI (status panel, logs, tail-follow, cursor visibility) is now invoked as part of the `fn` command.
|
||||
|
||||
**Invocation:**
|
||||
- Running `fn` with no arguments defaults to the dashboard (web UI by default)
|
||||
- The TUI surfaces inside the dashboard command when configured
|
||||
- Implementation lives in `packages/cli/src/commands/dashboard-tui/`
|
||||
|
||||
There is no separate `@fusion/tui` package or `pnpm tui` command anymore. Refer to `packages/cli/src/commands/dashboard-tui/` for current TUI implementation details.
|
||||
|
||||
---
|
||||
|
||||
## Headless Node Mode (`fn serve`)
|
||||
|
||||
The `fn serve` command starts Fusion as a headless node (API server + AI engine, no frontend). It binds to `0.0.0.0` by default for remote accessibility.
|
||||
@@ -419,7 +441,55 @@ See [docs/task-management.md](./docs/task-management.md) for the archive and res
|
||||
|
||||
## Dashboard UI Styling Guide
|
||||
|
||||
This guide documents the dashboard's design system so that any AI agent or developer building new UI components follows established conventions automatically. All CSS lives in `packages/dashboard/app/styles.css` (≈60K lines). For deeper context on the theme system and known pitfalls, see `.fusion/memory/MEMORY.md`.
|
||||
This guide documents the dashboard's design system so that any AI agent or developer building new UI components follows established conventions automatically.
|
||||
|
||||
### CSS Architecture
|
||||
|
||||
The dashboard's CSS has been split into modular per-component files alongside a consolidated global stylesheet:
|
||||
|
||||
- **Global stylesheet**: `packages/dashboard/app/styles.css` (~4,500 lines)
|
||||
- Design tokens (spacing, colors, shadows, transitions, fonts)
|
||||
- Primitives (`.btn`, `.card`, `.modal`, `.form-input`)
|
||||
- Cross-component `@media` overrides and base breakpoints
|
||||
- **Per-component stylesheets**: `packages/dashboard/app/components/ComponentName.css` (56 files)
|
||||
- Each component that needs CSS has a co-located `ComponentName.css`
|
||||
- Each `ComponentName.tsx` must import its stylesheet at the top: `import "./ComponentName.css";`
|
||||
|
||||
**Rule:** New CSS for a component goes in `app/components/ComponentName.css`, NOT in `styles.css`. Only genuinely global rules (design tokens, primitives, cross-component `@media` blocks) belong in `styles.css`.
|
||||
|
||||
### CSS Testing and Lazy-Loaded Views
|
||||
|
||||
For CSS regression tests, use the helper at `packages/dashboard/app/test/cssFixture.ts`:
|
||||
|
||||
```ts
|
||||
import { loadAllAppCss, loadAllAppCssBaseOnly } from "../test/cssFixture";
|
||||
|
||||
// Concatenates styles.css + all component .css
|
||||
const allCss = await loadAllAppCss();
|
||||
|
||||
// Strips @media/@supports blocks for base-rule assertions
|
||||
const baseOnly = await loadAllAppCssBaseOnly();
|
||||
```
|
||||
|
||||
**Never** directly `readFileSync('../styles.css')` — an ESLint rule (`no-restricted-syntax` in `eslint.config.mjs`) bans this in `packages/dashboard/**/*.test.{ts,tsx}` and points devs at `cssFixture.ts`.
|
||||
|
||||
The test config (`vitest.config.ts`) includes `test.css: { include: [/.+/] }` so component CSS imports actually inject into jsdom (needed for `getComputedStyle` assertions).
|
||||
|
||||
### Lazy-Loaded Heavy Views
|
||||
|
||||
These 13 views are lazy-loaded via `React.lazy()` to manage bundle size:
|
||||
|
||||
- `AgentsView`, `RoadmapsView`, `NodesView`, `ChatView`, `MemoryView`
|
||||
- `DevServerView`, `InsightsView`, `DocumentsView`, `SkillsView`
|
||||
- `SetupWizardModal`, `PluginManager`, `PiExtensionsManager`, `AgentDetailView`
|
||||
|
||||
They are loaded in `App.tsx` / `AppModals.tsx` / `SettingsModal.tsx` / `AgentsView.tsx` with `<Suspense fallback={null}>`.
|
||||
|
||||
A `prefetchLazyViews()` function runs once on mount via `requestIdleCallback` to warm chunks. **Do not make these eager again** — bundle size matters.
|
||||
|
||||
### Design Tokens
|
||||
|
||||
All new CSS **must** use these token variables instead of hardcoded values. Tokens are defined at `:root` and adapted for light mode via `[data-theme="light"]`.
|
||||
|
||||
---
|
||||
|
||||
@@ -636,12 +706,13 @@ Cards have `--focus-ring-strong` focus style and `--card-hover` background on ho
|
||||
### Adding New CSS
|
||||
|
||||
1. **Always use tokens** — `var(--space-md)`, `var(--text-muted)`, `var(--radius-md)`, `var(--transition-fast)`, etc. Never write `padding: 8px` or `color: #e6edf3` directly.
|
||||
2. **Section headers** — Mark new component sections with `/* === ComponentName === */` in `styles.css` so they are discoverable.
|
||||
3. **Reuse existing classes** — Don't create parallel button or form styles. Add states (`:hover`, `:focus-visible`, `:active`) to the existing `.btn`, `.card`, `.input` chains.
|
||||
4. **Theme-aware backgrounds** — Use `color-mix(in srgb, var(--color) X%, transparent)` instead of `rgba(...)`. For example, error backgrounds: `color-mix(in srgb, var(--color-error) 10%, transparent)`.
|
||||
5. **Accessibility** — Add `:focus-visible` styles using `var(--focus-ring-strong)` on every interactive component. Never suppress focus entirely.
|
||||
6. **Test both themes** — Verify new styles look correct in both dark and light modes before committing.
|
||||
7. **Mobile overrides** — Add mobile variants below the base styles, inside a `@media (max-width: 768px)` block.
|
||||
2. **Place new rules correctly** — Component CSS goes in `app/components/ComponentName.css`. Only genuinely global rules go in `styles.css`.
|
||||
3. **Import stylesheet in component** — Add `import "./ComponentName.css";` at the top of `ComponentName.tsx`.
|
||||
4. **Reuse existing classes** — Don't create parallel button or form styles. Add states (`:hover`, `:focus-visible`, `:active`) to the existing `.btn`, `.card`, `.input` chains.
|
||||
5. **Theme-aware backgrounds** — Use `color-mix(in srgb, var(--color) X%, transparent)` instead of `rgba(...)`. For example, error backgrounds: `color-mix(in srgb, var(--color-error) 10%, transparent)`.
|
||||
6. **Accessibility** — Add `:focus-visible` styles using `var(--focus-ring-strong)` on every interactive component. Never suppress focus entirely.
|
||||
7. **Test both themes** — Verify new styles look correct in both dark and light modes before committing.
|
||||
8. **Mobile overrides** — Add mobile variants below the base styles, inside a `@media (max-width: 768px)` block.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -23,14 +23,15 @@ At a high level, Fusion is split into:
|
||||
```text
|
||||
┌──────────────────────────────┐
|
||||
│ Human + AI Interactions │
|
||||
│ (Dashboard, CLI, Pi tools) │
|
||||
│ (Dashboard SPA, CLI, Pi) │
|
||||
└──────────────┬───────────────┘
|
||||
│
|
||||
┌──────────────────────┼──────────────────────┐
|
||||
│ │ │
|
||||
┌─────────▼─────────┐ ┌─────────▼─────────┐ ┌─────────▼─────────┐
|
||||
│ Dashboard (API) │ │ CLI `fn` router │ │ Pi extension tools │
|
||||
│ + React SPA │ │ (commands/*) │ │ (extension.ts) │
|
||||
│ + React SPA │ │ + TUI component │ │ (extension.ts) │
|
||||
│ (lazy-loaded) │ │ (commands/*) │ │ │
|
||||
└─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘
|
||||
└──────────────┬────────┴──────────────┬───────┘
|
||||
│ │
|
||||
@@ -422,6 +423,27 @@ Key server capabilities:
|
||||
- Planning/roadmap/insight UI: `MissionManager.tsx`, `RoadmapsView.tsx`, `InsightsView.tsx`, `DocumentsView.tsx`
|
||||
- Dev server UI: `DevServerView.tsx` (controls + status/log panel + embedded preview with iframe fallback messaging)
|
||||
|
||||
### CSS Architecture
|
||||
|
||||
The dashboard's CSS is split between a consolidated global stylesheet and modular per-component files:
|
||||
|
||||
- **Global stylesheet** (`packages/dashboard/app/styles.css`, ~4,500 lines)
|
||||
- Design tokens (spacing, colors, shadows, transitions, fonts)
|
||||
- Primitive component classes (`.btn`, `.card`, `.modal`, `.form-input`)
|
||||
- Cross-component `@media` overrides and breakpoint definitions
|
||||
- **Per-component stylesheets** (56+ files in `packages/dashboard/app/components/`)
|
||||
- Each component has a co-located `ComponentName.css` file
|
||||
- Each `ComponentName.tsx` imports its stylesheet: `import "./ComponentName.css";`
|
||||
- Component-specific CSS rules live in the component's `.css` file, not in the root stylesheet
|
||||
|
||||
**Lazy-loaded views** (bundle size optimization):
|
||||
The following 13 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null}>`:
|
||||
- `AgentsView`, `RoadmapsView`, `NodesView`, `ChatView`, `MemoryView`
|
||||
- `DevServerView`, `InsightsView`, `DocumentsView`, `SkillsView`
|
||||
- `SetupWizardModal`, `PluginManager`, `PiExtensionsManager`, `AgentDetailView`
|
||||
|
||||
A `prefetchLazyViews()` function runs once on mount via `requestIdleCallback` to warm chunks. Do not make these views eager — bundle size is carefully managed.
|
||||
|
||||
### Key hooks
|
||||
- Task + realtime: `useTasks.ts`, `useBadgeWebSocket.ts`, `useAiSessionSync.ts`
|
||||
- Chat: `useChat.ts`, `useQuickChat.ts`
|
||||
@@ -468,6 +490,10 @@ Events are tied to specific run IDs for end-to-end traceability.
|
||||
### Command modules
|
||||
- `packages/cli/src/commands/*`
|
||||
- Task operations, settings, git wrappers, backup operations, project/node management
|
||||
- **TUI component** (`packages/cli/src/commands/dashboard-tui/`)
|
||||
- Ink-based terminal UI (status panel, logs, cursor visibility, tail-follow)
|
||||
- Merged from former `@fusion/tui` package
|
||||
- Invoked as part of the `fn` command (no separate package or `pnpm tui` command)
|
||||
|
||||
### Project selection
|
||||
- `packages/cli/src/project-resolver.ts`
|
||||
@@ -481,6 +507,7 @@ Events are tied to specific run IDs for end-to-end traceability.
|
||||
|
||||
### Binary identity
|
||||
- Published package defines `fn` binary (`packages/cli/package.json`)
|
||||
- Running `fn` with no arguments defaults to dashboard (web UI by default)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ pnpm build
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| `@fusion/core` | Shared domain types, stores, persistence, and core utilities |
|
||||
| `@fusion/dashboard` | Express API + React UI |
|
||||
| `@fusion/dashboard` | Express API + React UI (including dashboard TUI in CLI) |
|
||||
| `@fusion/engine` | Scheduling, triage, execution, merge orchestration |
|
||||
| `@fusion/desktop` | Electron shell around Fusion dashboard/client |
|
||||
| `@fusion/mobile` | Capacitor + PWA mobile packaging |
|
||||
| `@fusion/plugin-sdk` | Plugin SDK for building Fusion extensions |
|
||||
| `@runfusion/fusion` | Published CLI + pi extension |
|
||||
| `@runfusion/fusion` | Published CLI + pi extension (includes merged TUI) |
|
||||
|
||||
## Development Workflow
|
||||
|
||||
@@ -83,6 +83,15 @@ pnpm test:coverage:cli
|
||||
pnpm test:coverage:dashboard
|
||||
```
|
||||
|
||||
### Test File Organization
|
||||
|
||||
All test files live in `__tests__/` subdirectories alongside the code they test:
|
||||
|
||||
- Test for `src/foo.ts` → `src/__tests__/foo.test.ts`
|
||||
- Test for `app/components/Bar.tsx` → `app/components/__tests__/Bar.test.tsx`
|
||||
|
||||
When adding new tests, follow this convention. The monorepo has been standardized on `__tests__/` organization.
|
||||
|
||||
## Build Standalone Executables
|
||||
|
||||
Fusion supports standalone binary builds through Bun compile scripts in the CLI package.
|
||||
@@ -135,6 +144,34 @@ Fusion can automatically extract insights from memory and prune transient conten
|
||||
|
||||
See [Settings Reference](./settings-reference.md#background-memory-summarization--audit) for configuration details.
|
||||
|
||||
## Dashboard CSS Organization
|
||||
|
||||
The dashboard's CSS has been modularized:
|
||||
|
||||
- **Global stylesheet** (`packages/dashboard/app/styles.css`, ~4,500 lines)
|
||||
- Design tokens, primitives (`.btn`, `.card`, `.modal`, `.form-input`), global cross-component rules
|
||||
- **Per-component stylesheets** (56+ files in `packages/dashboard/app/components/`)
|
||||
- Each component needing CSS has a co-located `ComponentName.css`
|
||||
- Each `ComponentName.tsx` must import: `import "./ComponentName.css";`
|
||||
|
||||
**Rule:** New component CSS goes in the component's `.css` file, not in `styles.css`. Only truly global rules belong in the root stylesheet.
|
||||
|
||||
### CSS Testing
|
||||
|
||||
For CSS regression tests, use the helper at `packages/dashboard/app/test/cssFixture.ts`:
|
||||
|
||||
```ts
|
||||
import { loadAllAppCss, loadAllAppCssBaseOnly } from "../test/cssFixture";
|
||||
|
||||
// Load all CSS (styles.css + all component .css)
|
||||
const allCss = await loadAllAppCss();
|
||||
|
||||
// Load base rules only (strips @media/@supports)
|
||||
const baseOnly = await loadAllAppCssBaseOnly();
|
||||
```
|
||||
|
||||
Never directly `readFileSync('../styles.css')` — the ESLint rule `no-restricted-syntax` in `eslint.config.mjs` blocks this in test files and directs you to `cssFixture.ts`.
|
||||
|
||||
## SQLite Test Runner Pitfall
|
||||
|
||||
When running engine tests with Vitest and `node:sqlite`, ensure the engine Vitest config uses thread pool mode:
|
||||
|
||||
Reference in New Issue
Block a user