Project Conventions
CSS
Variables
Use CSS custom properties for all shared values. Define them in :root at the top of the main stylesheet.
:root {
/* Colors */
--color-bg: #0f0f17;
--color-surface: #16161f;
--color-border: #66667A;
--color-text: #e0e0e0;
--color-text-muted: #888;
--color-primary: #7b68ee;
--color-success: #4acea0;
--color-danger: #ff6b6b;
--color-warning: #e8a838;
/* Spacing, radii, etc. */
--radius-sm: 3px;
--radius-md: 4px;
--radius-lg: 6px;
}
Never hardcode hex colors in rules — always reference variables. Exception: one-off opacity variants (e.g., var(--color-primary) with an alpha channel).
Property Ordering
Alphabetical. Always.
/* YES */
.my-class {
align-items: center;
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
display: flex;
padding: 0.5rem;
}
/* NO */
.my-class {
display: flex;
padding: 0.5rem;
background: var(--color-surface);
align-items: center;
}
Naming
Flat class names, no BEM. Use descriptive, hyphenated names.
/* YES */
.editor-panel { }
.list-item { }
.choice-editor { }
/* NO */
.editor-panel__header--active { }
.EditorPanel { }
Nesting
Use native CSS nesting for pseudo-classes, pseudo-elements, and direct modifier states. Don't nest deeper than 2 levels.
.list-item {
background: var(--color-surface);
cursor: pointer;
&:hover {
background: var(--color-surface-hover);
}
&.selected {
border-color: var(--color-primary);
}
}
File Structure
One CSS file per app/package (e.g., App.css for story-maker). Organize with section comments:
/* ================================================================
Section Name
================================================================ */
TypeScript
Naming
- Files: PascalCase for React components (
CharacterEditor.tsx), camelCase for utilities/stores (gameStore.ts,ancestry.ts) - Variables/functions: camelCase (
buildColorMap,handleSave) - Types/interfaces: PascalCase (
CharacterProfile,TimelineEvent) - Constants: UPPER_SNAKE_CASE only for true constants that are enum-like (
ANCESTRY_PALETTE). Regular config objects use camelCase. - React components: PascalCase (
GamePreview,TagInput)
Imports
Order imports in groups, separated by blank lines:
- React / framework imports
- Third-party libraries
- Internal modules (absolute/aliased paths)
- Relative imports (components, utils, types)
- Styles
import { useState, useMemo } from 'react';
import { ReactFlow } from '@xyflow/react';
import { Engine } from '../../src/core/Engine.js';
import type { GameDefinition } from '../../src/types/index.js';
import { buildAncestryMap } from '../utils/ancestry';
import { TagInput } from './shared/TagInput';
import './App.css';
Types
- Prefer
interfaceovertypefor object shapes - Use
typefor unions, intersections, and aliases - Export types from barrel
index.tsfiles - Use
typeimports when importing only types:import type { Foo } from ...
Components
- Functional components only (no classes)
- Props interface defined above the component, named
Props(local) orComponentNameProps(exported) - Destructure props in the function signature
- Hooks at the top of the component, then handlers, then derived values, then render
State
- Prefer immutable patterns — spread to create new objects/arrays, never mutate
- Engine state operations always return new state objects
- Use
useCallbackfor handlers passed as props,useMemofor expensive computations
Project Structure
engine/
src/ # Engine core (npm package)
core/ # Engine, TimelineManager, ConditionEvaluator, etc.
state/ # GameState, StateManager
types/ # All TypeScript interfaces
llm/ # LLM providers and CharacterEngine
plugins/ # Plugin system (future)
react/ # React bindings (hooks, components)
tests/ # Vitest tests mirroring src/ structure
demo/ # Vanilla HTML demo
demo-react/ # React demo app
story-maker/ # Story Maker editor app
Git
- Commit messages: lowercase, imperative mood, concise first line
- Co-author line for AI-assisted commits
- Don't commit
.env,node_modules,dist,*.local