Conventions
File Organization
app/ # Next.js App Router pages and layouts
components/
branding/ # Brand assets and logo components
graph/ # React Flow canvas, custom nodes, custom edges
generate/ # Prompt builder form/output components for /generate
layout/ # Shell UI: project sidebar, switcher, breadcrumb, minimap, badges
panels/ # Slide-in panels and forms
ui/ # shadcn/ui primitives (do not edit directly — use CLI)
lib/
config/ # Typed const arrays: species, statuses, platforms, edge types
data/ # DataProvider interface + implementations
hooks/ # React hooks for state management
prompts/ # Prompt assembly blocks/types for the AI prompt builder
utils/ # Helpers: layout, export, cn()
public/
schema/ # Public JSON schema + example bundle for import contract
llms.txt # Concise LLM manifest
robots.txt # Crawl directives + sitemap pointer
seed/ # Example project JSON for development
docs/ # This documentation
State Management
- No global store. No Zustand, Redux, or Context-based state.
- Reusable state logic lives in hooks:
useNodes,useEdges,useProject,useProjects,useGraphNavigation. - Hook intent:
useNodesanduseEdgeshandle project graph CRUD.useProjecthandles project-level metadata (includingroot_node_idand card preferences).useProjectspowers project lists/switching in route shell UI.useGraphNavigationremains a generic helper for graph navigation state when needed.
- The project canvas page (
app/project/[id]/canvas/page.tsx) usesuseNodesanduseEdgesfor data, and manages flow expansion as localuseState(expandedFlows). - Data flows via props from the project page down to canvas components.
- Route-shell concerns such as the project switcher and persistent sidebar should stay in the project layout and use route state plus lightweight hooks instead of introducing shared global state.
Keyboard Shortcuts
- Project-page shortcuts are wired in
app/project/[id]/canvas/page.tsxusinglib/hooks/useKeyboardShortcuts.ts. - Shortcut key checks and focus guards live in
lib/utils/keyboard.ts. - Keep shortcut handlers thin: they should call existing page handlers (
handleDeleteNodeRequest,handleExport) instead of duplicating business logic. - Delete shortcuts must not directly mutate storage. Always route through the existing confirmation dialog flow.
- Ignore destructive shortcuts when focus is in editable controls (
input,textarea,contenteditable, or combobox/textbox roles).
Styling
- Tailwind CSS for all styling — no CSS modules, no styled-components.
- shadcn/ui for UI primitives (
components/ui/). Generated via CLI — don't edit these files by hand. - Sidebar primitives are also generated via shadcn CLI. Compose with them in
components/layout/rather than forking the generated files. - class-variance-authority (CVA) for component variants.
cn()helper (lib/utils.ts) for merging Tailwind classes:cn("base-class", conditional && "active-class").tailwind-mergeresolves conflicting Tailwind classes automatically viacn().
Config / Taxonomies
All domain enums live in lib/config/ as const arrays with as const:
// lib/config/species.ts
export const SPECIES = [
{ id: "flow", level: 1, label: "Flow", description: "an ordered sequence of views and sub-flows" },
// ...
] as const;
export type SpeciesId = (typeof SPECIES)[number]["id"];
This pattern gives you:
- Runtime array for iteration (dropdowns, mapping)
- Compile-time union type for type safety
- Single source of truth — no duplicate enum + array
To add a new taxonomy value, add it to the array. The type updates automatically.
Components
- Node components receive React Flow
NodePropswith adataobject containinglabel,status,platforms,expanded,onToggle. - Edge components receive React Flow
EdgePropsand render SVG paths. - All node components are in
components/graph/nodes/and must be registered in thenodeTypesmap inCanvas.tsx. - Species affordances in panel and library cards should use a compact icon trigger with a hover card explaining the species from
lib/config/species.tsdescriptions.
Data Mutations
All writes go through the DataProvider interface:
Component → Hook (useNodes.addNode) → Provider (localProvider.createNode) → Storage
Never write to localStorage directly. Always use the provider.
Routing UI
- Shared project navigation belongs in
app/project/[id]/layout.tsx, not inside individual route pages. - Route-aware active states should derive from
usePathname()anduseSearchParams(). - When a UI control represents a shareable filter, keep it URL-driven. The library
speciesfilter is the current example. - Cross-project navigation should preserve the current in-project destination when it can be mapped safely.
Cursor Semantics
Graph interactive elements must use the correct Tailwind cursor class. Do not leave clickable elements without an explicit cursor — React Flow's canvas can suppress browser defaults.
| Action | Cursor class | Example |
|---|---|---|
| Show hover card | cursor-help | species badge (EntityBadges) |
| Insert node | cursor-copy | insert button on compose edge |
| Unfold flow | cursor-zoom-in | collapsed FlowNode |
| Collapse flow | cursor-zoom-out | expanded FlowNode |
| Open panel | cursor-pointer | info button on any node |
| Show popover | cursor-context-menu | API/platform buttons on ViewNode |
Non-interactive but focusable elements (e.g. branch nodes, static cards) use cursor-default.
Naming
- Files: kebab-case for config and utils (
edge-types.ts), PascalCase for components (FlowNode.tsx)- Current graph node components include
FlowNode.tsx,ViewNode.tsx,DataModelNode.tsx,ApiEndpointNode.tsx
- Current graph node components include
- Types: PascalCase (
SpeciesId,ProjectBundle) - Config arrays: UPPER_SNAKE_CASE (
SPECIES,STATUSES,EDGE_TYPES) - Hooks: camelCase with
useprefix (useNodes,useGraphNavigation)