# Originmain — Claude Code Build Prompt
## Layer-by-Layer Engineering Guide for AI Coding Agents
**Product:** Originmain — AI-Native Design Engineering Platform
**Stack:** React 19 · Next.js 15 · Fluent UI v9 · TypeScript 5 · PostgreSQL · Supabase · Webpack Module Federation · MCP · Claude Sonnet 4
**Renderer:** @pierre/diffs (code diff display) · @pierre/trees (codebase file browser)
**Tooling:** pnpm workspaces · Vitest · Playwright · ESLint strict · Zod
**Date:** April 2026
---
## How to Read This Document
This prompt is structured as a strict sequence of layers. Each layer has a **goal**, a set of **files to create or modify**, a **verification gate** you must pass before proceeding, and **critical constraints** that must not be violated. Do not begin a layer until the previous layer's gate passes. Do not skip gates under any circumstances.
When you see `[FILE]`, create or modify that file. When you see `[VERIFY]`, run the specified command and confirm it passes before continuing. When you see `[CRITICAL]`, treat that constraint as a hard requirement — violations will cause downstream failure.
---
## Repository Bootstrap (Before Layer 0)
```bash
# Initialise the pnpm monorepo
mkdir originmain && cd originmain
git init
pnpm init
echo "packages:\n - 'packages/*'" > pnpm-workspace.yaml
# Create all package directories
mkdir -p packages/{app,renderer,diff-engine,origin-graph,ai-layer,agent-bridge,ui,integrations}
mkdir -p .github/workflows
```
The root `package.json` sets `"type": "module"` and declares the pnpm workspace. All packages share TypeScript 5 strict config via a root `tsconfig.base.json`. All packages run tests via a root `vitest.config.ts` with `projects` pointing to each package.
---
## Layer 0 — Infrastructure & DevOps
**Goal:** A working monorepo skeleton that CI can lint, type-check, build, and test. No product logic yet.
### 0.1 Root Configuration
**[FILE] `tsconfig.base.json`**
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"jsx": "react-jsx",
"paths": {
"@originmain/ui": ["./packages/ui/src/index.ts"],
"@originmain/diff-engine": ["./packages/diff-engine/src/index.ts"],
"@originmain/origin-graph": ["./packages/origin-graph/src/index.ts"],
"@originmain/agent-bridge": ["./packages/agent-bridge/src/index.ts"],
"@originmain/ai-layer": ["./packages/ai-layer/src/index.ts"],
"@originmain/renderer": ["./packages/renderer/src/index.ts"],
"@originmain/integrations": ["./packages/integrations/src/index.ts"]
}
}
}
```
**[FILE] `.github/workflows/ci.yml`**
Define a GitHub Actions workflow named `CI` that runs on every pull request to `main`. It must execute these steps in strict sequence:
1. `pnpm install --frozen-lockfile`
2. `pnpm run typecheck` (runs `tsc --noEmit` in every package)
3. `pnpm run lint` (ESLint with `@typescript-eslint/strict` ruleset, zero warnings policy)
4. `pnpm run test` (Vitest across all packages, minimum 80% coverage on `diff-engine` and `origin-graph`)
5. `pnpm run build` (Next.js production build for `packages/app`)
6. `pnpm run migration:dry-run` (Supabase migration dry-run against production schema snapshot)
No step may be skipped or made non-blocking. The CI pipeline is the product's quality gate.
**[FILE] `.github/workflows/preview.yml`**
A separate workflow that deploys a Vercel preview URL on every PR and posts it as a PR comment.
### 0.2 Supabase Project Setup
Create a `supabase/` directory at the monorepo root. Initialise it with `supabase init`. The `supabase/config.toml` sets:
- `project_id = "originmain-local"`
- `db.port = 54322`
- `studio.port = 54323`
- `api.port = 54321`
The local Supabase instance is started with `supabase start` and used by all local development. Staging and production use separate Supabase cloud projects.
### 0.3 Environment Variables
**[FILE] `.env.example`** — document every required variable:
```
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
ANTHROPIC_API_KEY=
CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
LIVEBLOCKS_SECRET_KEY=
LINEAR_WEBHOOK_SECRET=
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
AGENT_BRIDGE_PORT=3001
NEXT_PUBLIC_APP_URL=http://localhost:3000
```
Never commit `.env.local`. Add it to `.gitignore`. The CI pipeline uses GitHub Actions secrets injected as environment variables.
**[VERIFY]** Run `pnpm install` and `pnpm run typecheck`. Both must exit 0.
---
## Layer 1 — Canvas UI Shell
**Goal:** A navigable Next.js app with the Fluent 2 chrome, a working infinite canvas viewport, and the two-surface navigation architecture (artboard navigator + codebase file browser). No artboard content yet — placeholder divs only.
### 1.1 Package Setup (`packages/app`)
Install core dependencies:
```bash
cd packages/app
pnpm add next@15 react@19 react-dom@19
pnpm add @fluentui/react-components @fluentui/react-icons
pnpm add @pierre/trees
pnpm add zustand immer
pnpm add @tanstack/react-query
pnpm add -D typescript @types/react @types/node
```
The Next.js app uses the App Router (`app/` directory). No Pages Router.
### 1.2 Root Layout and Fluent Provider
**[FILE] `packages/app/src/app/layout.tsx`**
```tsx
import { FluentProvider } from '@fluentui/react-components';
import { originmainTheme } from '@originmain/ui';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**[FILE] `packages/ui/src/themes/originmain-theme.ts`**
Create the Originmain brand theme using `createLightTheme` from `@fluentui/react-components`. The brand palette anchors at `#0F52BA`. Map this colour to all 16 `BrandVariants` slots (10 through 160 in increments of 10), interpolating from a near-white tint at the 10 end to a near-black shade at the 160 end. Export as `originmainTheme` and also export `originmainDarkTheme` using `createDarkTheme` with the same brand variants.
**[CRITICAL]** The `FluentProvider` wraps the entire application. No component outside it uses Fluent 2 hooks or tokens. This is a hard constraint — Fluent 2's context system requires it.
### 1.3 Canvas Viewport
**[FILE] `packages/app/src/store/viewport.store.ts`**
A Zustand store that manages:
```typescript
interface ViewportState {
panX: number; // pixels
panY: number; // pixels
zoom: number; // 0.1 to 4.0
setPan: (x: number, y: number) => void;
setZoom: (level: number, originX: number, originY: number) => void;
resetViewport: () => void;
}
```
The `setZoom` function adjusts `panX` and `panY` to keep the zoom origin stationary on screen (the same point under the cursor before and after zooming).
**[FILE] `packages/app/src/components/canvas/Canvas.tsx`**
A full-viewport div with `overflow: hidden` and a pointer-event handler that:
- On mouse drag (middle button or space+left): updates `setPan`
- On scroll wheel: calls `setZoom` with the wheel delta and current cursor position
- On pinch gesture (via `onPointerDown` multi-touch detection): calls `setZoom`
Inside, render a transform div:
```tsx
{children}
```
**[CRITICAL]** The canvas transform is applied via a CSS matrix to a single container. Never apply individual transforms to artboard elements. This is the only way to achieve 60fps pan/zoom without re-rendering artboard content.
### 1.4 Navigation Architecture
**[FILE] `packages/app/src/components/navigator/ArtboardNavigator.tsx`**
Uses Fluent 2's `Tree` and `TreeItem` components to render the workspace hierarchy:
- Workspace (root)
- Project folders
- Artboard groups
- Individual artboards
Each `TreeItem` renders the artboard's origin badge (Linear issue, Git commit, User feedback, Manual) alongside its name. Clicking selects the artboard and centres the canvas viewport on it.
```typescript
// Data shape this component expects:
interface NavigatorNode {
id: string;
label: string;
type: 'workspace' | 'folder' | 'group' | 'artboard';
originType?: 'linear' | 'git' | 'feedback' | 'manual';
children?: NavigatorNode[];
}
```
**[FILE] `packages/app/src/components/codebase/CodebaseFileTree.tsx`**
Uses `@pierre/trees` to render the connected application's repository file tree. This component is entirely separate from the artboard navigator — it lives in a collapsible side panel on the opposite side of the canvas.
```tsx
import { FileTree } from '@pierre/trees';
interface CodebaseFileTreeProps {
nodes: FileTreeNode[]; // from the renderer's component tree extraction
onFileSelect: (path: string) => void;
}
export function CodebaseFileTree({ nodes, onFileSelect }: CodebaseFileTreeProps) {
return (
);
}
```
**[CRITICAL]** The CSS custom property names above match the ones @pierre/trees exposes for theming. Verify against the @pierre/trees documentation before shipping. Never hardcode colour values — always reference Fluent 2 tokens.
### 1.5 Chrome Layout
**[FILE] `packages/app/src/app/(workspace)/layout.tsx`**
A three-column layout: left panel (ArtboardNavigator, 240px), centre (Canvas, flex 1), right panel (Inspector + CodebaseFileTree, 320px). All panels use Fluent 2's `makeStyles` from Griffel for styling. No external CSS files.
Fluent 2 Toolbar across the top with these tool groups (left to right): Workspace name, Select tool, Pan tool, Artboard tools (new, duplicate, fork), AI tools (Completion Zone trigger), Export, Settings.
**[VERIFY]** `pnpm run build` passes. `pnpm run dev` opens the canvas in a browser with visible toolbar, left panel, and right panel. Panning and zooming work.
---
## Layer 2 — Live Rendering Engine
**Goal:** Originmain can connect to a running Next.js application, render one of its routes as a Live Artboard in the canvas, and extract the component tree from the rendered iframe.
### 2.1 Package Setup (`packages/renderer`)
```bash
cd packages/renderer
pnpm add react@19 react-dom@19
pnpm add -D webpack@5 @module-federation/enhanced
pnpm add -D typescript zod
```
### 2.2 The Renderer Protocol
**[FILE] `packages/renderer/src/protocol.ts`**
Define the message contract between the iframe and the host application:
```typescript
export type RendererMessage =
| { type: 'COMPONENT_TREE_READY'; payload: ComponentTreeNode }
| { type: 'COMPONENT_SELECTED'; payload: { componentId: string; rect: DOMRect } }
| { type: 'THEME_INJECTED'; payload: { success: boolean } }
| { type: 'ROUTE_CHANGED'; payload: { path: string } }
| { type: 'RENDER_ERROR'; payload: { message: string; stack?: string } };
export type HostMessage =
| { type: 'SELECT_COMPONENT'; payload: { componentId: string } }
| { type: 'INJECT_THEME'; payload: { tokens: Record } }
| { type: 'NAVIGATE'; payload: { path: string } }
| { type: 'REQUEST_TREE' };
export interface ComponentTreeNode {
id: string; // stable ID derived from fiber key + display name
displayName: string; // React component display name
filePath?: string; // source file path (from sourcemaps)
props: Record; // serialisable props only
rect: DOMRect; // bounding rect at render time
designTokens?: Record; // resolved Fluent 2 token values
children: ComponentTreeNode[];
}
```
All messages are validated with Zod schemas before processing. Invalid messages are silently dropped.
### 2.3 The Fiber Tree Injector
**[FILE] `packages/renderer/src/fiber-injector.ts`**
A script injected into the iframe before the remote application initialises. It hooks into React's DevTools global hook (`__REACT_DEVTOOLS_GLOBAL_HOOK__`) to intercept the Fiber tree after each render commit. From the Fiber tree, it extracts `ComponentTreeNode` records by walking the `child` / `sibling` / `return` fiber links. It posts the extracted tree to the parent frame via `window.parent.postMessage`.
**[CRITICAL]** This injector must never import React or any application code — it runs in the iframe's global scope and must be a plain IIFE. It reads from the global hook only, produces a serialisable data structure, and posts it. Do not access `fiber.stateNode` beyond reading its bounding rect.
### 2.4 The Artboard Iframe Wrapper
**[FILE] `packages/app/src/components/artboard/ArtboardFrame.tsx`**
```tsx
interface ArtboardFrameProps {
artboardId: string;
remoteUrl: string; // URL of the connected app's Module Federation entry
route: string; // path to render (e.g., '/dashboard')
width: number;
height: number;
}
```
The component renders an `