Architecture
Overview
arkaik is a product graph browser built on Next.js 16 App Router with React Flow (@xyflow/react). It renders an interactive graph centered on reusable flows and views, with parallel layers for data models and API endpoints.
App Router Structure
app/
layout.tsx # Root layout: fonts (Geist), ThemeProvider, global CSS
page.tsx # Home page: component showcase / project list
generate/
page.tsx # Prompt builder UI for LLM-assisted ProjectBundle generation
llms-full.txt/
route.ts # Full LLM-readable context bundle (docs + schema + example)
sitemap.ts # XML sitemap route
docs/
layout.tsx # Documentation shell with sidebar + page frame
page.tsx # Docs home: renders repository root README.md
[...slug]/
page.tsx # Markdown document route mapped from docs/**/*.md
project/
[id]/
layout.tsx # Shared project shell: persistent sidebar, switcher, panel-stack provider
page.tsx # Redirects to /project/[id]/overview — a project opens on the global picture
canvas/
page.tsx # Redirects to /project/[id]/maps/journey (old links keep working)
overview/
page.tsx # Overview dashboard — the strategist reading over lib/utils/coverage.ts projections
maps/
page.tsx # Maps index — built-ins + custom maps from project.metadata.maps
[mapId]/
page.tsx # Renderer shell: journey → JourneyMap, system → SystemMap
library/
page.tsx # Gallery/directory node browser (species via sidebar ?species= links)
delivery/
page.tsx # Delivery board — (node × platform) items grouped by status
changelog/
page.tsx # Releases + backlog derived from the journal
p/
[id]/
page.tsx # Publik snapshot preview (server-rendered)
api/ # Publik, Synk, and auth route handlers (spec/services.md)
The Journey map (components/maps/JourneyMap.tsx) is the core of the graph renderer; its graph construction is the pure buildJourneyGraph in lib/utils/journey-graph.ts (golden-tested against the Pebbles seed). It:
- Loads nodes and edges via
useNodesanduseEdges - Manages expansion state for flows via local
useState - Computes per-platform view statuses and flow rollup gauges
- Maps domain nodes to React Flow nodes with position, type, card-variant preference, and toggle handlers
- Renders the
Canvascomponent with computed nodes and edges - Pushes a detail panel on node click, onto the stack owned by the project layout
- Opens
NewNodeForm(Dialog) via a floating "New node" button for creating nodes - Opens
InsertBetweenDialogfrom compose-edge insert actions for search-or-create insertion in flow playlists
Component Map
components/
graph/
Canvas.tsx # ReactFlow wrapper — registers node/edge types, renders Controls, MiniMap, Background
nodes/ # Custom React Flow node components
FlowNode.tsx # Container card for flow nodes with rollup gauges
ViewNode.tsx # Variant-based View cards (compact/large), API actions, platform/API popovers
PlatformGaugeList.tsx # Shared stacked gauge renderer for flow cards and panels
DataModelNode.tsx # Parallel layer — amber, Database icon
ApiEndpointNode.tsx # Parallel layer — teal, Plug icon
node-styles.ts # Status/platform style maps
edges/
ComposeEdge.tsx # Straight — hierarchy (composes)
CrossLayerEdge.tsx # Dashed straight — cross-layer references (not yet registered in Canvas)
maps/
JourneyMap.tsx # The Journey map surface: expansion state, editing, dialogs, toolbar
SystemMap.tsx # The System map surface: species tiers, cross-layer edges, connect-to-create
MapCard.tsx # Maps-index card with kind badge + subgraph counts
MapEditorDialog.tsx # Create/edit custom maps (project.metadata.maps)
delivery/
DeliveryBoard.tsx # Status columns of (node × platform) items
PlatformItemCard.tsx # Slim node×platform card
DeliveryFilterBar.tsx # Platform/species chips, all-statuses toggle, search
overview/
OverviewSection.tsx # Shared dashboard card shell (title + jump-off link)
PlatformGaugesCard.tsx # Product-wide per-platform delivery gauges (PlatformGaugeList)
DeliverySnapshotCard.tsx # Board column totals without the board
ReleasePulseCard.tsx # Tagged releases, newest first, with change counts
BacklogCard.tsx # Open ideas/requests summary + first rows
InventoryCard.tsx # Census by species with status dots; rows link into the library
HealthCard.tsx # Doc-health indicators with per-indicator evidence links
MapsCard.tsx # Every map with live subgraph counts
layout/
CommandPalette.tsx # ⌘K overlay: ranked search over a catalogue (project or docs)
Minimap.tsx # React Flow minimap wrapper (unused — Canvas uses @xyflow/react MiniMap directly)
ProjectSidebar.tsx # Persistent in-project sidebar navigation
ProjectSwitcher.tsx # Sidebar header dropdown for cross-project navigation
StatusBadge.tsx # Colored pill with status label
library/
LibraryFilterBar.tsx # Species/search/display controls for the library page
NodeCard.tsx # Gallery-mode card for a single node
NodeTable.tsx # Directory-mode sortable table for nodes
generate/
PromptBuilderForm.tsx # Use-case aware form (pitch/plan/extend) and advanced options
PromptOutput.tsx # Prompt preview, token estimate, copy/download actions
panels/
NewNodeForm.tsx # Dialog form for creating a node with species-aware status/platform defaults
InsertBetweenDialog.tsx # Dialog for insert-between actions: choose view/flow, search existing, or create inline
PanelStack.tsx # Content-agnostic push-panel grid: the surface is cell 0; keyboard, focus, breadcrumb, window
ProjectPanels.tsx # Binds the stack to panel kind: node detail (id → node), or the raw bundle
NodeDetailPanel.tsx # One panel's body: edit node fields, platform-specific statuses, computed rollups, and flow playlists
PlaylistEditor.tsx # Flow-only playlist editor: add/remove/reorder and branch editing
PlaylistEntryRow.tsx # Recursive playlist row renderer for condition/junction branches
NodeSearchCombobox.tsx # Search-or-create selector for flow/view references
PlatformVariants.tsx # Platform tab switcher with per-platform status and notes
RawBundleSheet.tsx # Raw JSON/YAML bundle viewer/editor sheet (guarded edit + save-back)
ui/ # shadcn/ui primitives (button, card, dialog, input, etc.)
dropdown-menu.tsx # Radix dropdown wrapper used by the project switcher
popover.tsx # Radix popover wrapper used by View card API/platform details
sidebar.tsx # shadcn sidebar primitives used by the project layout shell
Data Flow
IndexedDB (Dexie)
↕ (read/write)
localProvider (implements DataProvider, via getProvider())
↕ (async calls)
Hooks: useNodes, useEdges, useProject, useProjects, useJournal
↕ (state)
app/project/[id]/layout.tsx (sidebar shell + route-aware navigation)
↕ (props)
ProjectSidebar + ProjectSwitcher
↕ (route changes)
components/maps/JourneyMap.tsx (expansion state; buildJourneyGraph builds topology)
↕ (props)
Canvas → ReactFlow → Custom Nodes/Edges
↕ (click events)
NodeDetailPanel → Hook (updateNode) → Provider → Storage
NewNodeForm (Dialog) → Hook (addNode) → Provider → Storage
View card variant selector → Hook (useProject.updateProject) → Provider → Storage
Library route data flow:
Hooks: useNodes, useEdges
↕ (state)
app/project/[id]/library/page.tsx
↕ (props)
LibraryFilterBar + NodeCard/NodeTable
↕ (click events)
NodeDetailPanel / NewNodeForm → hooks → Provider → Storage
Project-shell navigation flow:
Hooks: useProject, useProjects
↕ (state)
app/project/[id]/layout.tsx
↕ (props)
ProjectSidebar / ProjectSwitcher
↕ (pathname + searchParams)
Route-aware active states + cross-project navigation
Docs route flow:
app/docs/layout.tsx
↕ (server-loaded nav items)
DocsSidebar
↕ (pathname)
Route-aware docs links
↕ (slug lookup)
app/docs/page.tsx + app/docs/[...slug]/page.tsx
↕
lib/utils/docs.ts (filesystem discovery + slug-safe lookup)
↕
components/docs/MarkdownContent.tsx (react-markdown + GFM + highlighting)
Docs pages are rendered from markdown at request/build time using server-side file reads. The home route (`/docs`) is pinned to repository `README.md`, while nested routes resolve to markdown under `docs/`. Unknown paths redirect back to `/docs`. The same index also feeds the docs ⌘K palette (`getDocsSearchPages()` → `components/docs/DocsSearch.tsx`), so the sidebar and the palette can never disagree about what exists.
Prompt generation flow:
app/generate/page.tsx
↕ (local state)
components/generate/PromptBuilderForm.tsx
↕ (typed config)
lib/prompts/assemble.ts
↕ (text blocks)
lib/prompts/blocks.ts + lib/prompts/types.ts
↕ (preview actions)
components/generate/PromptOutput.tsx
LLM affordance assets:
- `public/llms.txt` exposes a concise site + model manifest.
- `app/llms-full.txt/route.ts` serves a larger, plain-text context bundle for crawlers/agents.
- `public/schema/project-bundle.json` and `public/schema/example-bundle.json` define and demonstrate the import contract.
- `public/robots.txt` and `app/sitemap.ts` support discoverability.
All data mutations flow through the DataProvider interface (lib/data/data-provider.ts). The current implementation is localProvider backed by IndexedDB (Dexie — lib/data/db.ts), which writes per project rather than rewriting the whole store on every mutation. The interface plus the getProvider()/setProvider() seam (lib/data/provider-registry.ts) let the backend change without touching hooks or UI — the seam a future read-only repo-bundle provider (rfcs/arkaik-dev.md) injects through.
Playlist Expansion
The project page manages one expansion set as local useState:
expandedFlows— which flows show their direct flow/view children
When project.root_node_id is present, the canvas walks the full compose closure from that node — views always render and chain the walk onward; flows render as collapsed cards. When it is missing, root nodes are inferred from nodes with no compose parent. The first top-level flow auto-expands on initial load.
Expanded flows reveal ordered children from metadata.playlist and composes edges. Positions are computed by ELK (lib/utils/elk-layout.ts, layered algorithm over compose edges).
Canvas visibility rule (Journey map):
- Rendered nodes:
flow,view - Not rendered here:
data-model,api-endpoint(still persisted; they render on the System map once roadmap CP-C lands — spec/maps.md)
Card rendering is per map, set from the header's Display popover and resolved
by resolveMapDisplay (spec/maps.md § Display Options). Three
independent options, not a two-way preset:
images— the view's screenshot, falling back to its cover art (default on)flow_platforms— a flow card's delivery as the Pyramid'srings(default) or stackedbarsview_platforms— a view card's availability as footerchips(default) or labelledrows
Storage: project.metadata.map_display[mapId], plus a definition-level display
for agent-authored custom maps. The superseded project-wide
project.metadata.view_card_variant is no longer read — it still parses and
round-trips, but every map now starts from the defaults above.
Node Detail Panel
Clicking any node pushes a column onto the panel stack — an inline grid, no
overlay and no focus trap, in which the surface itself is the first cell. The
stack is owned by ProjectPanelsProvider in app/project/[id]/layout.tsx (a
page segment would remount on every param change and reset it), rendered by
PanelStack, and bound to what a panel can be — a node, or the raw bundle — by
ProjectPanels. Its rule, URL contract (?node=) and the split across the
three files are documented in conventions.md § Panel Stack;
the transitions themselves are pure, in lib/utils/panel-stack.ts.
NodeDetailPanel is one panel's body — the stack owns the frame, the chrome
and the keyboard. Historically only the five node-bearing surfaces (Journey
map, System map, library, delivery, acceptances) rendered the stack at all; the
intent, as the pages move onto PageShell, is that every project page mounts
it, since the raw bundle panel needs no node data to be worth reaching. Those
five pass ProjectPanels their own data and handlers and none keeps a
selected-node of its own; a page that passes none still gets the grid, and a
?node= it cannot resolve renders a body saying so rather than an empty
column. RawBundleSheet stays a Sheet: a project-level raw-JSON view with no
traversal, and genuinely modal.
The body carries:
- Editable fields: title, description, and species-aware status/platform controls
- Connections: cross-layer nodes (data-model, api-endpoint); clicking one pushes it as the next panel
- Where Used: reverse reference list showing which flow playlists currently include the selected node
- Platform Variants (view only): per-platform status + notes stored in
node.metadata - Computed gauges (
flow): read-only per-platform rollups built from descendant views - Playlist editor (
flow): orderedmetadata.playlist.entriesediting with add/remove/reorder and recursive condition/junction branch editing
Flow playlist editing uses fuzzy search-or-create for view and flow entries. When adding a flow reference, cycle checks run before persisting and invalid inserts are blocked with toast feedback.
Compose edges in expanded sequences expose a single insert action. It opens InsertBetweenDialog, where users choose view, flow, condition, or junction. For view/flow, the dialog reuses NodeSearchCombobox to select existing nodes or create new ones inline; for condition/junction, it inserts structured entries with sensible defaults. Node-reference inserts are placed at the correct playlist position and ensure the compose edge exists.
Edits call useNodes.updateNode which flows through the DataProvider.
Library
The library route (app/project/[id]/library/page.tsx) is the project-wide browser for reusable nodes.
- Gallery view: card layout using
NodeCardfor scanning titles, species/status badges, and flow playlist previews. - Directory view: sortable table using
NodeTablefor dense auditing (id,title,species,status,used in). - Filter controls: species selection is owned by the sidebar (
?species=deep links);LibraryFilterBarowns text search and the gallery/directory display toggle.
Library interactions reuse the same edit/create surfaces as canvas (ProjectPanels, NewNodeForm) so data mutation paths stay identical.
Sidebar Navigation
Project-level navigation is defined in app/project/[id]/layout.tsx and rendered by ProjectSidebar + ProjectSwitcher.
- Sidebar links are route-aware (
canvas,library) and preserve active state from pathname/search params. - The switcher supports cross-project navigation while keeping users in the closest equivalent destination.
- Keeping navigation in the shared project layout avoids duplicated route chrome in child pages.
Command palette (⌘K)
The same destinations are reachable by typing. app/project/[id]/layout.tsx owns
the palette's open state (⌘K / Ctrl+K, plus the sidebar's Search row) and
dispatches the non-navigating commands; CommandPalette renders the overlay and
lib/utils/command-palette.ts holds the catalogue and the pure ranking rules
(prefix > word-start > substring > subsequence, synonyms one tier below labels).
- The catalogue mirrors the sidebar — a destination added there belongs here too,
and
tests/app/command-palette.test.jspins the pairing. - Enter validates the top suggestion, Tab fills in its wording, arrows browse.
- Publish lives in the layout rather than the sidebar, so both triggers open one dialog.
The palette is catalogue-agnostic: buildProjectCommands and buildDocsCommands
are two lists feeding one overlay, one ranker and one shortcut. A new surface
needs a builder, not a palette.
Docs palette (⌘K in /docs)
components/docs/DocsSearch.tsx mounts the same overlay in the docs header —
it owns the trigger button, the shortcut and the open state, because the docs
shell has no other command state to share. Its catalogue comes from
getDocsSearchPages() (lib/utils/docs.ts), the navigation tree flattened back
into landable pages, and crosses to the client as plain data.
- The page's own address is a synonym, hyphens and spaces both: "graph model"
finds
/docs/graph-modeleven though its title says The five species. - The folder trail rides along as the row's hint ("Spec"), so same-named pages in different sections stay tellable apart.
- Publish is a project action and is simply absent from the catalogue; the theme command is shared, so ⌘K switches it from either space.
Theming
next-themesfor light/dark modeThemeProviderwraps the entire app in the root layoutThemeTogglecomponent for user switching- Tailwind CSS with shadcn/ui design tokens
- Sidebar theme tokens live in
app/globals.cssand back the shadcn sidebar primitives
Source References
- Graph orchestration: components/maps/JourneyMap.tsx, lib/utils/journey-graph.ts, lib/utils/system-graph.ts
- Project shell: app/project/[id]/layout.tsx
- Panel stack: lib/utils/panel-stack.ts, components/panels/PanelStack.tsx, lib/hooks/useProjectPanels.tsx, components/panels/ProjectPanels.tsx
- Library orchestration: app/project/[id]/library/page.tsx
- Sidebar components: components/layout/ProjectSidebar.tsx, components/layout/ProjectSwitcher.tsx
- Command palette: components/layout/CommandPalette.tsx, lib/utils/command-palette.ts, components/docs/DocsSearch.tsx
- Docs shell + renderer: app/docs/layout.tsx, app/docs/page.tsx, app/docs/[...slug]/page.tsx, components/layout/DocsSidebar.tsx, components/docs/MarkdownContent.tsx, components/docs/DocsSearch.tsx, lib/utils/docs.ts
- Prompt builder: app/generate/page.tsx, components/generate/PromptBuilderForm.tsx, components/generate/PromptOutput.tsx, lib/prompts/assemble.ts, lib/prompts/blocks.ts, lib/prompts/types.ts
- React Flow registry: components/graph/Canvas.tsx
- Data hooks: lib/hooks/useNodes.ts, lib/hooks/useEdges.ts, lib/hooks/useProject.ts, lib/hooks/useProjects.ts
- Data provider: lib/data/local-provider.ts
- LLM surfaces: public/llms.txt, app/llms-full.txt/route.ts, public/schema/project-bundle.json, public/schema/example-bundle.json, public/robots.txt, app/sitemap.ts