- Remove legacy .fusion/memory.md fallback references and normalize prompts/docs to .fusion/memory/MEMORY.md - Stop legacy mirror writes and fallback reads in core memory backend and project memory flows - Update engine worktree boundary checks and tests for canonical memory file handling - Align dashboard memory/settings surfaces and route tests with canonical memory behavior - Add model-favorites persistence test coverage for mission interview and new agent dialogs
30 KiB
Project Guidelines
Finalizing Changes
When making changes that affect published packages, create a changeset file:
cat > .changeset/<short-description>.md << 'EOF'
---
"@gsxdsm/fusion": patch
---
Short description of the change.
EOF
Bump types:
- patch: bug fixes, internal changes
- minor: new features, new CLI commands, new tools
- major: breaking changes
Include the changeset file in the same commit as the code change. The filename should be a short kebab-case description (e.g., fix-merge-conflict.md, add-retry-button.md).
Only create changesets for changes that affect the published @gsxdsm/fusion package — user-facing features, bug fixes, CLI changes, tool changes. Do NOT create changesets for internal docs (AGENTS.md, README), CI config, or refactors that don't change behavior.
Package Structure
@fusion/core— domain model, task store (private, not published)@fusion/dashboard— web UI + API server (private, not published)@fusion/engine— AI agents: triage, executor, reviewer, merger, scheduler (private, not published)@gsxdsm/fusion— CLI + pi extension (published to npm)
Only @gsxdsm/fusion is published. The others are internal workspace packages.
Storage Model
Fusion uses a hybrid storage architecture: structured metadata lives in SQLite (.fusion/fusion.db) while large blob files (PROMPT.md, agent.log, attachments) remain on the filesystem under .fusion/tasks/{ID}/. The database runs in WAL mode for concurrent access.
See docs/storage.md for the full storage architecture documentation.
Multi-Project Support
Fusion supports multiple projects with a central registry at ~/.fusion/fusion-central.db. Each project has its own SQLite database at .fusion/fusion.db. See docs/multi-project.md for details on:
- CentralCore API and project registration
- Isolation modes (in-process, child-process)
- Global concurrency management
Testing
pnpm test # run all tests
pnpm build # build all packages
Tests are required. Typechecks and manual verification are not substitutes for real tests with assertions.
Port 4040 is Reserved
Port 4040 is the production dashboard port. A user's live dashboard session is typically running there. Agents must NEVER:
- Run
kill,kill -9,pkill, orkillallagainst processes on port 4040 - Start a test server on port 4040 — always use
--port 0for random free port
Engine Process Rules
The engine (packages/engine) runs the executor, merger, scheduler, IPC host, and dashboard-facing activity loop on a single Node event loop. Blocking that loop stalls every task concurrently in-flight.
Never use execSync for User-Configured Commands
execSync blocks the entire event loop until the child process exits. Any command from project settings — testCommand, buildCommand, workflow step scripts, etc. — must run via promisify(exec) with timeout. Never use execSync for user-configured commands.
import { exec } from "node:child_process";
import { promisify } from "node:util";
const execAsync = promisify(exec);
const { stdout, stderr } = await execAsync(command, {
cwd: worktreePath,
timeout: 120_000,
maxBuffer: 10 * 1024 * 1024,
});
execSync is only acceptable for short, deterministic git plumbing (git rev-parse, git branch -d, git worktree remove, etc.). When in doubt, use async.
Git Conventions
- Commit messages:
feat(FN-XXX):,fix(FN-XXX):,test(FN-XXX): - One commit per step (not per file change)
- Always include the task ID prefix
Node Dashboard
Fusion has a Node Dashboard view for managing mesh network nodes. See docs/architecture.md for dashboard components and API endpoints.
Node Settings Sync API Endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /api/nodes/:id/settings |
Fetch settings from a remote node |
| POST | /api/nodes/:id/settings/push |
Push local settings to a remote node |
| POST | /api/nodes/:id/settings/pull |
Pull settings from a remote node |
| GET | /api/nodes/:id/settings/sync-status |
Get sync status and diff summary |
| POST | /api/nodes/:id/auth/sync |
Sync model auth credentials |
| POST | /api/settings/sync-receive |
Receive pushed settings (inbound) |
| POST | /api/settings/auth-receive |
Receive auth credentials (inbound) |
| GET | /api/settings/auth-export |
Export local auth credentials |
All remote node endpoints require the target node to have an apiKey configured. Inbound endpoints validate the Authorization: Bearer <apiKey> header against the local node's apiKey.
Pi Extension (packages/cli/src/extension.ts)
The pi extension provides tools and a /fn command for interacting with fn from within a pi session. It ships as part of @gsxdsm/fusion.
Update the extension when:
- CLI commands change (behavior, flags, or output)
- Task store / Agent store API changes (method signatures or behavior)
- New user-facing features are added that chat agents should be able to use
Don't add tools for engine-internal operations (move, step updates, logging, merge) — those are handled by the engine's own agents.
The extension has no skills — tool descriptions give the LLM everything it needs.
Agent Spawning (spawn_agent tool)
The executor agent can spawn child agents that run in parallel. Each spawned agent:
- Runs in its own git worktree (branched from the parent's worktree)
- Receives a task prompt describing what to do
- Executes autonomously until completion or termination
- Reports status back to the parent via AgentStore
Parameters
| Parameter | Type | Description |
|---|---|---|
name |
string |
Name for the child agent |
role |
string |
Role: "triage", "executor", "reviewer", "merger", "engineer", or "custom" |
task |
string |
Task description for the child agent to execute |
Settings
maxSpawnedAgentsPerParent(default:5) — Maximum children per parent agentmaxSpawnedAgentsGlobal(default:20) — Maximum total spawned agents per executor instance
Lifecycle
- Child agents are tracked in
AgentStorewithreportsToset to the parent task ID - When the parent session ends, all spawned children are terminated
- State transitions:
idle→active→running→active(success) orerror(failure)
Error Handling
- Per-parent and global limits are enforced with descriptive error messages
- Failures during agent creation or worktree setup return error results
- State update failures are non-blocking (logged but don't prevent execution)
Agent Delegation Tools
Two tools enable inter-agent delegation — discovering other agents and assigning work to them.
list_agents Tool
List all available agents in the system. Shows each agent's name, role, state, personality (soul), and current assignment.
Parameters:
| Parameter | Type | Description |
|---|---|---|
role |
string (optional) |
Filter by agent role/capability (e.g., "executor", "reviewer") |
state |
string (optional) |
Filter by agent state (e.g., "idle", "active", "running") |
includeEphemeral |
boolean (optional) |
Include ephemeral/runtime agents (default: false) |
Example usage:
// Find all idle executor agents
list_agents({ role: "executor", state: "idle" })
// See all agents including runtime task-workers
list_agents({ includeEphemeral: true })
delegate_task Tool
Create a new task and assign it to a specific agent for execution. The task goes to todo and will be picked up by the target agent on their next heartbeat cycle.
Parameters:
| Parameter | Type | Description |
|---|---|---|
agent_id |
string (required) |
The agent ID to delegate work to |
description |
string (required) |
What needs to be done |
dependencies |
string[] (optional) |
Task IDs this new task depends on |
Example workflow — CEO agent discovers QA agent and delegates testing:
// 1. Discover available agents
list_agents({ role: "qa" })
// → Returns QA agent with id "qa-agent-001"
// 2. Delegate the testing task
delegate_task({
agent_id: "qa-agent-001",
description: "Run integration tests for the authentication module",
dependencies: ["FN-100"] // depends on implementation being done
})
// → Created FN-105: Delegated to QA Agent (qa-agent-001).
// The task will be picked up on their next heartbeat cycle.
Error cases:
"ERROR: Agent {agent_id} not found"— The agent ID does not exist"ERROR: Cannot delegate to ephemeral/runtime agent {agent_id}"— Cannot delegate to runtime task-worker agents (usespawn_agentfor parallel worktree tasks instead)
Note: The delegation tools are available to both executor agents and heartbeat agents when the agent store is configured. Use list_agents first to discover agent IDs and capabilities before delegating.
Checkout Leasing
Task ownership supports explicit checkout leases. Agents should be aware of:
Conflict Semantics
- Checkout conflicts return 409 Conflict when another agent already holds the lease
- Response shape:
{ error: "Task is already checked out", currentHolder, taskId } - Clients must not retry 409 automatically — this is ownership contention, not a transient failure
Heartbeat Enforcement
HeartbeatMonitor.executeHeartbeat() validates checkout before work begins:
- If
task.checkedOutByis set to another agent, the run exits withreason: "checkout_conflict" - Heartbeat execution does not auto-checkout — callers are responsible for obtaining checkout before starting work
Per-Agent Heartbeat Configuration
Each agent can override heartbeat behavior via runtimeConfig. Key settings:
heartbeatIntervalMs— How often heartbeats are triggeredheartbeatTimeoutMs— Time without heartbeat before agent is considered unresponsivemaxConcurrentRuns— Max concurrent heartbeat runs per agent
See docs/agents.md for the full configuration reference.
Budget Governance
Per-agent token budget tracking controls costs and prevents runaway AI spending. Budget enforcement happens at multiple points:
- HeartbeatMonitor.executeHeartbeat() — Checks budget before creating sessions; skips when
isOverBudget: trueorisOverThreshold: true(for timer triggers) - HeartbeatTriggerScheduler.onTimerTick() — Skips timer ticks when budget is exceeded
Agents can be paused by budget exhaustion. See docs/agents.md for the full budget configuration reference.
Heartbeat Trigger Scheduling
HeartbeatTriggerScheduler manages three trigger mechanisms:
- Timer — Periodic wakeup based on
heartbeatIntervalMs - Assignment — Automatic wakeup when a task is assigned
- On-demand — Manual trigger via
POST /api/agents/:id/runs
See docs/agents.md for WakeContext and API details.
Agent Performance Ratings
Agent performance ratings allow users and agents to provide feedback that influences future behavior through system prompt injection. Ratings use a 1–5 scale with trend analysis (improving/declining/stable).
See docs/agents.md for the full API and dashboard configuration reference.
Engine Diagnostic Logging
The task executor, scheduler, and related subsystems use structured logging via createLogger() from packages/engine/src/logger.ts. All log lines are prefixed with the subsystem name.
Key Diagnostic Points
When debugging agent execution issues (agents stuck on "starting"), check these log points:
[executor] TaskExecutor constructed— Confirms the executor initialized with expected options[executor] [event:task:moved] FN-XXX → in-progress— Confirms the scheduler moved the task[executor] execute() called for FN-XXX— Confirms execute() was entered[executor] FN-XXX: worktree ready at ...— Confirms worktree creation[executor] FN-XXX: creating agent session— Confirms model resolution and session creation started[pi] createKbAgent called— Confirms the agent factory was invoked[pi] Session created successfully— Confirms the AI session was created[executor] FN-XXX: calling promptWithFallback()...— Confirms the prompt was sent[stuck-detector] Tracking task FN-XXX— Confirms heartbeat monitoring started
Semaphore Resilience
AgentSemaphore (packages/engine/src/concurrency.ts) has defensive guards:
limitgetter returns minimum 1 (prevents indefinite blocking)availableCountreturns 0 for invalid limits (NaN, Infinity, ≤0)
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.
See docs/architecture.md for the full reference including health endpoint and startup banner.
Settings
fn uses a two-tier settings hierarchy:
- Global settings — User preferences in
~/.fusion/settings.json(theme, models, notifications) - Project settings — Project-specific settings in
.fusion/config.json(concurrency, worktrees, commands)
Project settings override global settings. Configure via the dashboard Settings modal or fn settings CLI.
See docs/settings-reference.md for the complete settings reference.
Settings Hierarchy for Model Selection
For Task Specification (Triage):
- Per-task
planningModelProvider/planningModelId - Project
planningProvider/planningModelId - Global
planningGlobalProvider/planningGlobalModelId - Project
defaultProviderOverride/defaultModelIdOverride - Global
defaultProvider/defaultModelId - Automatic provider/model resolution
For Task Execution (Executor):
- Per-task
modelProvider/modelId - Project
executionProvider/executionModelId - Global
executionGlobalProvider/executionGlobalModelId - Project
defaultProviderOverride/defaultModelIdOverride - Global
defaultProvider/defaultModelId - Automatic provider/model resolution
For Code/Spec Review (Reviewer):
- Per-task
validatorModelProvider/validatorModelId - Project
validatorProvider/validatorModelId - Global
validatorGlobalProvider/validatorGlobalModelId - Project
defaultProviderOverride/defaultModelIdOverride - Global
defaultProvider/defaultModelId - Automatic provider/model resolution
Per-Task Model Overrides
Tasks can override project/global AI model settings on a per-task basis:
- Executor Model — The model used to implement the task
- Validator Model — The model used for code and plan review
- Planning Model — The model used for task specification
When both provider and modelId are set, the task override is used instead of global defaults. Set via the task detail modal's Model tab.
Model Presets
Model presets let teams standardize AI model choices. Each preset contains executor/validator model pairs. Presets can be auto-selected by task size (Small → Budget, Medium → Normal, Large → Complex).
See docs/settings-reference.md for the full configuration reference.
Mission Autopilot
Missions can run in autopilot mode for autonomous progression. When enabled:
- Autopilot watches task completion events
- Automatically activates the next slice when the current one finishes
- Progresses through:
inactive → watching → activating → completing
See docs/missions.md for the full autopilot reference.
Mission Planning Context
When features are triaged to tasks, the system enriches descriptions with full mission hierarchy context (mission → milestone → slice → feature), giving implementation agents comprehensive context.
See docs/missions.md for the planning context system and interview flow documentation.
Workflow Steps
Workflow steps are reusable quality gates that run at configurable lifecycle phases:
- Pre-merge — After task implementation, before merge (can block)
- Post-merge — After successful merge (informational only)
Steps can be defined as prompt (AI agent review) or script (deterministic command).
See docs/workflow-steps.md for the full reference including templates, API, and execution details.
Run Audit
The run-audit system records every mutation performed by the engine across three domains:
- Database — task:create, task:update, task:move, etc.
- Git — worktree:create, commit:create, merge:resolve, etc.
- Filesystem — file:write, prompt:write, attachment:create, etc.
Events are tied to specific run IDs for end-to-end traceability. See docs/architecture.md for the audit API reference.
Archive Cleanup
Archived tasks can be cleaned up from the filesystem while preserving metadata. Restored tasks keep all metadata but lose attachments and agent logs.
See docs/task-management.md for the archive and restore reference.
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.
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"].
| Token | Value | Purpose |
|---|---|---|
--space-xs |
4px |
Tight inline spacing |
--space-sm |
8px |
Small gaps |
--space-md |
12px |
Default gaps |
--space-lg |
16px |
Section padding |
--space-xl |
24px |
Large section padding |
--space-2xl |
32px |
Extra-large spacing |
--radius-sm |
4px |
Small corners |
--radius-md |
8px |
Default corners |
--radius-lg |
12px |
Card/modal corners |
--radius-xl |
16px |
Large corners |
--radius-pill |
10px |
Pill/badge corners |
--shadow-sm |
0 1px 2px rgba(0,0,0,0.1) |
Subtle lift |
--shadow-md |
0 4px 6px rgba(0,0,0,0.1) |
Standard lift |
--shadow-lg |
0 4px 24px rgba(0,0,0,0.4) |
Modals, dropdowns |
--shadow-glow |
0 0 8px rgba(88,166,255,0.3) |
Focus glow |
--glow-success |
0 0 8px rgba(46,160,67,0.3) |
Success state |
--glow-danger |
0 0 8px rgba(248,81,73,0.3) |
Danger state |
--glow-warning |
0 0 8px rgba(227,179,65,0.3) |
Warning state |
--focus-ring |
0 0 0 2px rgba(88,166,255,0.15) |
Subtle focus ring |
--focus-ring-strong |
0 0 0 2px rgba(88,166,255,0.3) |
Prominent focus ring |
--transition-instant |
0.1s ease |
Immediate |
--transition-fast |
0.15s ease |
Quick |
--transition-normal |
0.2s ease |
Default |
--transition-slow |
0.3s ease |
Smooth |
--font-primary |
system font stack | Body font |
--font-mono |
monospace stack | Code, IDs |
--header-height |
57px |
Fixed header height |
--mobile-nav-height |
44px |
Mobile nav bar |
--standalone-bottom-gap |
0px / 8px (PWA) |
iOS home bar gap |
--overlay-padding-top |
10vh |
Modal vertical position |
Never hardcode pixel values, colors, or durations in component CSS. Always reference a token. The sole exception is inside :root or theme blocks where tokens are defined.
Color Variables
Core palette (dark defaults at :root, overridden in [data-theme="light"]):
| Variable | Purpose |
|---|---|
--bg |
Page background |
--surface |
Elevated surface |
--card |
Card background |
--card-hover |
Card hover state |
--border |
Borders and dividers |
--text |
Primary text |
--text-muted |
Secondary/muted text |
--text-dim |
Tertiary/disabled text |
Task status colors (semantic — consistent meaning across all 54 color themes):
| Variable | Status |
|---|---|
--triage |
Triage |
--todo |
Todo |
--in-progress |
In Progress |
--in-review |
In Review |
--done |
Done |
Semantic status colors:
| Variable | Purpose |
|---|---|
--color-success |
Success green |
--color-error |
Error red (light) |
--color-error-dark |
Error red (dark) |
--color-warning |
Warning amber |
--color-info |
Info blue |
--color-muted |
Muted gray |
Rule: Never use raw hex or rgba(...) for colors in component styles. Use var(--token) or color-mix(in srgb, var(--color) X%, transparent) for translucent backgrounds. The only place hardcoded colors are acceptable is inside :root theme blocks defining tokens.
Status background tokens already use color-mix for theme adaptability — reuse them rather than creating parallel variants:
--status-triage-bg, --status-todo-bg, --status-in-progress-bg,
--status-in-review-bg, --status-done-bg, --status-archived-bg,
--status-error-bg, --status-error-bg-deep
Theme System
The dashboard supports dark/light modes (controlled by data-theme attribute) plus 54 color themes (controlled by data-color-theme attribute). Theme blocks live in packages/dashboard/app/public/theme-data.css and are lazy-loaded when a non-default theme is active.
Token categories:
- Base tokens (
--bg,--surface,--text, etc.) — redefined in every theme block (dark + light for each of the 54 themes). Components using these tokens adapt automatically. - Semantic tokens (tokens with consistent meaning across all themes, like
--autopilot-pulse,--event-error-text,--badge-mission-*,--fab-*) — only need dark/light adaptation via[data-theme="light"]. They do NOT need per-color-theme overrides because the semantic meaning is always consistent. - Status tokens (
--triage,--todo,--in-progress, etc.) — redefined per theme block to match each theme's palette.
Adding theme-aware CSS custom properties:
- For base tokens: add to
:root,[data-theme="light"], and all 54 theme blocks (dark + light variants) - For semantic tokens: add to
:rootand[data-theme="light"]only - For status tokens: add to
:rootand all theme blocks
The automated test in status-colors-theme.test.ts iterates all theme blocks to catch regressions.
New components must use var(--token) references so themes apply without any per-component dark/light handling.
Component Classes
Reuse these existing CSS classes rather than creating parallel styles. They live in styles.css.
Buttons
| Class | Purpose |
|---|---|
.btn |
Base button |
.btn-primary |
Primary CTA (uses --cta-bg) |
.btn-danger |
Destructive action (red) |
.btn-warning |
Warning action (amber) |
.btn-sm |
Compact button (4px 10px padding, 12px font) |
.btn-icon |
Icon-only button (square, icon fills) |
.btn-icon--active |
Active icon button state |
.btn-badge |
Notification badge on button |
All buttons use --btn-padding, --btn-border-width, --transition-fast, and --radius-md tokens. All have :focus-visible using --focus-ring-strong and :active using transform: scale(0.97).
Modals
| Class | Purpose |
|---|---|
.modal-overlay |
Backdrop; use with .open class |
.modal-overlay.open |
Visible overlay (uses backdrop-filter: blur(4px)) |
.modal |
Default modal (480px wide, --radius-lg, --shadow-lg) |
.modal-lg |
Large modal (640px wide) |
.modal-header |
Modal title bar (flex, space-between) |
.modal-close |
Close button (× icon, hover color change) |
.modal-actions |
Action bar at modal bottom |
.modal-actions-left |
Left-aligned actions (use margin-right: auto) |
.modal-actions-right |
Right-aligned actions |
The overlay uses position: fixed; inset: 0; z-index: 100;. Pad the top with --overlay-padding-top (default 10vh) to center vertically.
Forms
| Class | Purpose |
|---|---|
.form-group |
Field container with --space-xl horizontal padding |
.form-group label |
Uppercase label (12px, 0.5px letter-spacing) |
.input |
Global input (surface bg, --radius-sm) |
.select |
Global select (same style as .input) |
.checkbox-label |
Checkbox label (flex, gap, no uppercase) |
.form-error |
Error box (uses color-mix, --color-error) |
Inputs and selects in .form-group get focus styles (border-color: var(--todo) + --focus-ring). Global .input and .select classes apply independently.
Cards
| Class | Purpose |
|---|---|
.card |
Task card base (grab cursor, --card-padding) |
.card-header |
Card top row (flex, gap: 6px) |
.card-id |
Monospace task ID (11px, --text-muted) |
.card-title |
Card title (13px, break-word) |
.card-meta |
Meta row (flex, gap, --space-sm) |
.card-status-badge |
Status badge (pill shape, status-bg/text colors) |
.card-status-badge--triage |
Triage badge variant |
.card-status-badge--todo |
Todo badge variant |
.card-status-badge--in-progress |
In-Progress badge variant |
.card-status-badge--in-review |
In-Review badge variant |
.card-status-badge--done |
Done badge variant |
.card-status-badge--archived |
Archived badge variant |
Cards have --focus-ring-strong focus style and --card-hover background on hover. Use .card-status-badge--{status} classes for column-color badges.
Utility
| Class | Purpose |
|---|---|
.touch-target |
44px minimum touch target (Apple HIG / WCAG 2.5.8) |
.visually-hidden |
Screen-reader-only (clip rect) |
Mobile Responsive
Breakpoints (use @media (max-width: N)):
| Breakpoint | Value | Use |
|---|---|---|
| Mobile | 768px |
Primary mobile breakpoint |
| Tablet | 1024px |
min-width: 769px and max-width: 1024px |
| Small | 640px |
Compact mobile |
| XSmall | 480px |
Very narrow devices |
Mobile CSS placement: All mobile overrides go in @media (max-width: 768px) blocks at the bottom of styles.css, after their base styles.
Bottom spacing:
--mobile-nav-height(44px) controls mobile nav bar height--standalone-bottom-gap(0pxdefault,8pxin PWAdisplay-mode: standalone) adds iOS home indicator breathing room- All bottom-positioned elements use
calc(..., var(--mobile-nav-height), env(safe-area-inset-bottom, 0px), var(--standalone-bottom-gap))
Touch targets: All interactive elements must be at least 36px on mobile. Use .touch-target for elements below this threshold (which sets 44px minimum). Inside mobile media queries, individual component touch targets may be 36px.
Safe area: Use max(var(--space-md), env(safe-area-inset-left, 0px)) pattern for content respecting device notches on mobile.
Adding New CSS
- Always use tokens —
var(--space-md),var(--text-muted),var(--radius-md),var(--transition-fast), etc. Never writepadding: 8pxorcolor: #e6edf3directly. - Section headers — Mark new component sections with
/* === ComponentName === */instyles.cssso they are discoverable. - Reuse existing classes — Don't create parallel button or form styles. Add states (
:hover,:focus-visible,:active) to the existing.btn,.card,.inputchains. - Theme-aware backgrounds — Use
color-mix(in srgb, var(--color) X%, transparent)instead ofrgba(...). For example, error backgrounds:color-mix(in srgb, var(--color-error) 10%, transparent). - Accessibility — Add
:focus-visiblestyles usingvar(--focus-ring-strong)on every interactive component. Never suppress focus entirely. - Test both themes — Verify new styles look correct in both dark and light modes before committing.
- Mobile overrides — Add mobile variants below the base styles, inside a
@media (max-width: 768px)block.
Common Pitfalls
--surface-hoveris undefined — This token is referenced in several places but never defined in:rootor theme blocks. Use a fallback:var(--surface-hover, rgba(0,0,0,0.03))or define the token explicitly.- Hardcoded
rgba(...)for error states — Usecolor-mix(in srgb, var(--color-error) 10%, transparent)instead ofrgba(248, 81, 73, 0.1). .form-errorstyle — Should usecolor-mix(in srgb, var(--color-error) 10%, transparent)for the background, not hardcoded rgba.lucide-reacticon changes — When adding new icons, update test mocks (vi.mock("lucide-react")) immediately. Missing mock exports cascade into runtime failures.- Light-theme overrides — Components using
var(*)tokens generally inherit correctly from[data-theme="light"]root redefinitions. Only add explicit[data-theme="light"]overrides where fine-tuning is needed (opacity, subtle shadows). - CSS regex tests in test files — When changing mobile CSS values (e.g.,
min-height), update both the CSS and the corresponding test assertions. Use non-greedy[^}]*patterns for block-scoped regex, not[\s\S]*which can bleed across block boundaries. - BEM specificity conflicts — When a container state class (
.quick-entry-box--expanded) and an element modifier (.quick-entry-input--expanded) both target the same element, the container may win due to higher specificity. Use:not(.modifier)to scope container rules:.quick-entry-box--expanded .quick-entry-input:not(.quick-entry-input--expanded). - CSS in
@mediablocks — Don't search backwards for the nearest@mediato check if a rule is mobile-scoped. Track brace depth to confirm the line is inside the block. Many components are defined globally even if they only visually appear on mobile.