made tiny updates

This commit is contained in:
SinachPat
2026-05-01 10:41:55 +01:00
parent 548cd11719
commit 20e0c4b4aa
6 changed files with 565 additions and 5270 deletions
+538
View File
@@ -0,0 +1,538 @@
# Source-Aware Canvas & Agent Bridge: Implementation Design
**Status:** Draft — awaiting review before implementation begins
**Scope:** Two-layer system to close the gap between visual intent and real code edits
**Packages touched:** `@originmain/renderer`, `@originmain/cli`, `@originmain/agent-bridge`, `@originmain/app`
---
## 1. Problem Statement
The current canvas pipeline gives us **runtime truth** — what the component looks like, what props it accepts, what DOM rect it occupies — but zero **source truth**: which file defines it, what token it consumes, how to write a change back to disk.
Style patches applied via `PATCH_ELEMENT_STYLE` mutate the live DOM through `el.style.setProperty()`. They vanish on the next hot-reload. That is not an Intent Diff; it is DevTools with a nicer UI.
The corrected architecture requires two layers working together:
```
┌─────────────────────────────────────────────────────────────┐
│ LAYER 1 — Runtime (exists today) │
│ CLI proxy → iframe → fiber hook → FIBER_TREE_UPDATE │
│ Gives: visual render, component name, DOM rect, live props │
└───────────────────────────┬─────────────────────────────────┘
│ component name + _debugSource
┌───────────────────────────▼─────────────────────────────────┐
│ LAYER 2 — Source (what we are building) │
│ │
│ 2a. _debugSource extraction (renderer) │
│ React dev mode attaches fileName + lineNumber to every │
│ fiber. We already walk the fiber tree — we just need │
│ to forward this field. Zero CLI changes required. │
│ │
│ 2b. CLI AST Indexer │
│ Watches .tsx/.ts files, builds component→file map. │
│ Exposes a local HTTP API: GET /components?name=Card │
│ Returns: file, line, exported props, design tokens used │
│ │
│ 2c. Intent Message protocol │
│ Canvas emits a structured Intent (not a DOM patch). │
│ Intent carries: component, file, change, before/after. │
│ Sent to Agent Bridge → Claude Code / Cursor apply it. │
└─────────────────────────────────────────────────────────────┘
```
---
## 2. What Exists Today (audit)
### `@originmain/cli` — proxy only
- Reverse-proxies the user's dev server through port 4170
- Strips `X-Frame-Options` / CSP so the iframe can load
- Injects `buildProxyFiberHookScript()` into HTML responses via string injection
- Passes WebSocket upgrades for HMR
- **No AST indexing. No file watching. No component→file map.**
### `@originmain/renderer` — fiber hook + DOM inspector
- `buildProxyFiberHookScript()`: hooks `window.__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot`
- Serialises the fiber tree → `FIBER_TREE_UPDATE` to parent canvas
- Sends `COMPONENT_SELECTED` on click, `ELEMENT_STYLES` on request
- Node IDs are stable path strings: `"ComponentName:idx/Child:idx/…"`
- `_debugSource` is present on fiber objects in dev mode but **is never read or forwarded**
- `PATCH_ELEMENT_STYLE` / `REMOVE_ELEMENT` apply DOM-only mutations — no source awareness
### `@originmain/agent-bridge` — MCP over JSON-RPC 2.0
- 5 tools: `get_pending_diffs`, `get_artboard_context`, `ask_design_agent`, `update_diff_status`, `get_design_language`
- Transport: WebSocket (long-running) or HTTP POST (polling)
- Auth: signed workspace token in first message (`AuthRequest`)
- Claude Code adapter generates `CLAUDE.md` section + `.claude/settings.json` block
- **No tool for "I changed this component — apply it to source"**
- **No tool for querying component→file mapping**
### `@originmain/app` — canvas host
- Canvas receives `FIBER_TREE_UPDATE`, stores in Zustand (`artboardFiberRoots`)
- Inspector reads `selectedComponentData` (a `FiberNode`) — name + props + domRect
- DOM patches queued in `styleEditQueue`, drained by `LiveArtboard`
- **No intent message emitted. Style changes stop at the DOM.**
---
## 3. Layer 2a — `_debugSource` Extraction (Quick Win)
**What it is:** React's dev build attaches `_debugSource: { fileName, lineNumber, columnNumber }` to every fiber created by JSX. This is the same data React DevTools uses to show "Defined in src/components/Card.tsx:12".
**What we need to do:**
- In `buildProxyFiberHookScript()` (renderer package), read `fiber._debugSource` during `serializeFiber()`
- Forward it as an optional `sourceFile: { fileName, lineNumber }` field on `FiberNode`
- Update the `FiberNode` TypeScript interface to include it
**Protocol change — add to `FiberNode`:**
```ts
export interface FiberNode {
id: string;
name: string;
props: Record<string, unknown>;
children: FiberNode[];
domRect?: DOMRectLike;
// NEW
sourceFile?: { fileName: string; lineNumber: number; columnNumber?: number };
}
```
**Constraint:** `_debugSource` is only present in development builds (when React is compiled with `__DEV__ = true`). Production builds strip it. The CLI proxy is a dev-only tool, so this is acceptable. The field must be optional everywhere.
**What this unlocks immediately (no CLI changes):**
- Inspector can show "Defined in `src/components/DashboardCard.tsx:12`"
- Intent messages can carry an accurate `file` field without needing the AST indexer
- The AST indexer becomes a fallback/enrichment path, not a blocker
**Files to change:**
| File | Change |
|---|---|
| `packages/renderer/src/fiber-hook.ts` | Read `fiber._debugSource` in `serializeFiber()`, include in serialised node |
| `packages/renderer/src/protocol.ts` | Add `sourceFile?` to `FiberNode` interface |
| `packages/app/src/components/inspector/Inspector.tsx` | Display file path + line in the Inspector panel |
---
## 4. Layer 2b — CLI AST Indexer
**What it does:** Watches the user's project `.tsx`/`.ts` files, builds and maintains an in-memory map of `componentName → { filePath, exportedProps, designTokensUsed }`. Exposes a local HTTP API the canvas queries to enrich intent messages.
### 4.1 The index structure
```ts
interface ComponentEntry {
name: string;
filePath: string; // relative to project root, e.g. "src/components/Card.tsx"
lineNumber: number; // line of the export declaration
isDefaultExport: boolean;
props: PropEntry[]; // extracted from the component's Props interface / type
tokensUsed: string[]; // CSS custom property references found in the file (--color-primary etc.)
lastIndexed: number; // Date.now() — for cache invalidation
}
interface PropEntry {
name: string;
type: string; // string representation, e.g. "string | undefined"
optional: boolean;
defaultValue?: string;
}
```
### 4.2 AST parsing approach
Use TypeScript's compiler API (`ts.createSourceFile()`) — it is already available in the dev environment since projects are TypeScript. The indexer does NOT need to type-check; it only needs to parse.
**What to extract per file:**
1. All named and default exported functions/arrow functions that start with a capital letter (component heuristic)
2. Their associated `Props` / `interface` / `type` parameter (first parameter type annotation)
3. All string literals matching `var(--…)` in JSX attribute values and template literals (design token references)
**What NOT to do:**
- Full type resolution (expensive, requires full `tsconfig` program)
- Module graph traversal (only index direct file exports)
- Watch `node_modules` (skip entirely)
### 4.3 Local HTTP API (exposed by CLI)
The CLI adds a second local server on port `4171` (or configurable):
```
GET /components → ComponentEntry[] all indexed components
GET /components?name=Card → ComponentEntry[] fuzzy-matched by name
GET /components?file=src/… → ComponentEntry[] all exports from a file
GET /health → { status, indexed, lastScan }
POST /reindex → triggers full rescan (useful after large refactors)
```
**The canvas calls this API when an intent is about to be emitted** — it enriches the intent with `props` and `tokensUsed` before sending it to the Agent Bridge.
### 4.4 File watching
Use Node's `fs.watch()` (recursive, available since Node 22 which is the CLI's minimum). On change events, re-index only the changed file (incremental update, not full rescan). Full rescan on startup.
### 4.5 CLI command change
```
originmain dev --target http://localhost:3000 [--port 4170] [--index-port 4171] [--no-index]
```
`--no-index` disables the AST indexer for users who only want the proxy (e.g., non-TypeScript projects). The indexer is opt-in-by-default, gracefully degraded.
### 4.6 Files to create / change
| File | Change |
|---|---|
| `packages/cli/src/indexer.ts` | NEW — AST walker, component→file map, file watcher |
| `packages/cli/src/index-server.ts` | NEW — HTTP API server on port 4171 |
| `packages/cli/src/cli.ts` | Add `--index-port` / `--no-index` flags, start indexer alongside proxy |
| `packages/cli/src/proxy.ts` | Inject index server URL into HTML as `window.__OM_INDEX_URL__` |
---
## 5. Layer 2c — Intent Message Protocol
An Intent Message is the structured description of **what the user wants to change** — precise enough for a coding agent to apply it without guessing.
### 5.1 Intent Message format
```ts
interface IntentMessage {
// Identity
intentId: string; // uuid, for tracking
workspaceId: string;
artboardId: string;
// Component context
component: {
name: string; // "DashboardCard"
nodeId: string; // fiber path ID "DashboardCard:0/..."
sourceFile?: string; // "src/components/DashboardCard.tsx"
sourceLine?: number; // 12
props: Record<string, unknown>; // current runtime props
propsSchema?: PropEntry[]; // from AST indexer (richer type info)
};
// The change(s) — an array to support multi-property edits
changes: IntentChange[];
// Before/after visual evidence
snapshot?: {
before: string; // base64 PNG of the component at the time of selection
after?: string; // base64 PNG after DOM preview (optional, captured on confirm)
};
// Context the agent needs
designLanguage?: {
tokensUsed: string[]; // CSS custom props referenced in source
palette: Record<string, string>; // current token values
};
createdAt: string; // ISO 8601
}
interface IntentChange {
type: 'style' | 'prop' | 'layout' | 'remove';
cssProperty?: string; // for type: 'style' e.g. "border-radius"
propName?: string; // for type: 'prop' e.g. "variant"
from: unknown; // previous value
to: unknown; // intended value
confidence: 'exact' | 'approximate'; // 'exact' = token match; 'approximate' = raw value
}
```
### 5.2 Where intent is created — canvas flow
```
User adjusts border-radius slider in Inspector
Inspector dispatches patchStyleEdit() to Zustand
LiveArtboard applies PATCH_ELEMENT_STYLE → DOM preview
Inspector shows "Confirm as Intent" button
User clicks Confirm
Canvas builds IntentMessage:
- component name + sourceFile from selectedComponentData (fiber node)
- change: { cssProperty: 'border-radius', from: '8px', to: '12px' }
- enriches with indexer API if available (/components?name=DashboardCard)
Canvas sends IntentMessage to Agent Bridge via new MCP tool: push_intent
Agent Bridge stores intent, forwards to connected Claude Code / Cursor session
Agent reads file, makes edit, hot reload confirms visually
```
### 5.3 The "Confirm as Intent" UX decision
There are two valid approaches here, with different trade-offs:
**Option A — Optimistic / auto-emit**
Every DOM patch immediately generates an intent. No confirm button. The agent applies changes in real-time as the user drags.
- Pro: Fluid, no friction
- Con: Noisy — dragging a slider fires dozens of intents. The agent would be overwhelmed. Race conditions between hot-reload and the next drag.
**Option B — Explicit confirm**
DOM patch is a preview only. A "Commit as Intent →" button appears. The user confirms when satisfied.
- Pro: Clean one-shot intents. Agent gets a stable before/after to work with.
- Con: Extra click. Users might forget to commit.
**Recommendation: Option B** — matches the git mental model (stage → commit). The canvas already has the `styleEditQueue` pattern which fits this model. A "commit" button in the Inspector is the natural end of the interaction.
---
## 6. Layer 2d — Agent Bridge Enhancements
Two new MCP tools and one protocol extension are required.
### 6.1 New tool: `push_intent`
**Direction:** Canvas → Agent Bridge → coding agent
**Purpose:** Deliver a confirmed IntentMessage to the connected agent session
```ts
// Input schema
{
workspace_id: z.string().uuid(),
intent: z.object({
component: z.object({
name: z.string(),
sourceFile: z.string().optional(),
sourceLine: z.number().optional(),
}),
changes: z.array(z.object({
type: z.enum(['style', 'prop', 'layout', 'remove']),
cssProperty: z.string().optional(),
propName: z.string().optional(),
from: z.unknown(),
to: z.unknown(),
})),
snapshot: z.object({
before: z.string(), // base64 PNG
after: z.string().optional(),
}).optional(),
}),
}
```
**What the tool does:**
1. Stores the intent in the `intent_diffs` table with status `EXPORTED`
2. Broadcasts it over the WebSocket connection to any connected agent
3. Returns the intent's ID so the canvas can poll for `IMPLEMENTED` status
The existing `update_diff_status` tool is how the agent signals completion. No changes needed there.
### 6.2 New tool: `resolve_component`
**Direction:** Coding agent → Agent Bridge → CLI indexer
**Purpose:** Let Claude Code ask "where is `DashboardCard` defined?" without needing direct filesystem access through the MCP.
```ts
// Input
{ component_name: z.string() }
// Output
{
name: string;
sourceFile: string;
sourceLine: number;
props: PropEntry[];
tokensUsed: string[];
}
```
**How it works:** The Agent Bridge proxies the request to the CLI indexer's HTTP API (`GET /components?name=DashboardCard`). This requires the CLI to register its indexer URL with the Agent Bridge on startup.
**CLI registration flow:**
```
originmain dev starts
→ proxy on :4170
→ indexer on :4171
→ POST /api/agent-bridge/register-indexer
{ workspaceToken, indexerUrl: "http://localhost:4171" }
→ Agent Bridge stores indexerUrl per workspace session
→ resolve_component proxies to it
```
### 6.3 Protocol extension — server-initiated INTENT_RECEIVED
The Agent Bridge needs to push intents to the agent without the agent polling. For WebSocket connections this is a server-initiated message (not a JSON-RPC request/response):
```ts
// Server pushes to agent over WebSocket
{
type: 'INTENT_RECEIVED',
intent: IntentMessage,
}
```
Claude Code / Cursor sessions listening over WebSocket receive this immediately after `push_intent` is called from the canvas.
### 6.4 Files to create / change
| File | Change |
|---|---|
| `packages/agent-bridge/src/tools.ts` | Add `push_intent` and `resolve_component` tools |
| `packages/agent-bridge/src/protocol.ts` | Add `IntentMessage`, `IntentChange` types; `INTENT_RECEIVED` server push |
| `packages/agent-bridge/src/index.ts` | Handle `/register-indexer` endpoint, store per-workspace indexer URL |
| `packages/agent-bridge/src/adapters/claude-code.ts` | Document new tools in `CLAUDE.md` section |
| `packages/app/src/app/api/agent-bridge/route.ts` | Wire `push_intent` and `resolve_component` to the tool executor |
---
## 7. Complete Data Flow
```
─────────────────────────────────────── DEVELOPMENT MACHINE ────
user's next dev originmain CLI Originmain canvas
server :3000 proxy :4170 indexer:4171 (browser tab)
│ │ │ │
│ GET /page │ │ │
│◄───────────────│ │ │
│ HTML + HMR │ │ │
│───────────────►│ inject │ │
│ │ fiber hook │ │
│ │─────────────────────────────►│
│ │ │ READY │
│ │◄─────────────────────────────│
│ │ FIBER_TREE_UPDATE │
│ │◄─────────────────────────────│
│ │ │ user clicks │
│ │ COMPONENT_SELECTED │
│ │◄─────────────────────────────│
│ │ │ user adjusts │
│ │ PATCH_ELEMENT_STYLE │
│ │───────────────────────────── │
│ DOM preview │ │ │
│ │ │ user confirms │
│ │ GET /components?name=Card │
│ │──────────────►│ │
│ │ ComponentEntry │
│ │◄─────────────│ │
│ │ │ IntentMessage │
│ │ │◄──────────────│
─────────────────────────────── CLOUD ──────────────────────────
│ │ │ push_intent │
│ │ │───────────────►│ Agent Bridge
│ │ │ │ stores intent
│ │ │ │ → INTENT_RECEIVED
│ │ │ │───────────────►
│ │ │ │ Claude Code
│ │ │ │ reads file, edits
│ │ │ │◄───────────────
│ hot reload │ │ FIBER_TREE_UPDATE (post-edit)
│◄───────────────│◄─────────────────────────────│
│ │ │ update_diff_status(IMPLEMENTED)
```
---
## 8. Implementation Phases
### Phase 1 — `_debugSource` (12 days, no dependency)
1. Extend `FiberNode` in `renderer/src/protocol.ts`
2. Read `fiber._debugSource` in `buildProxyFiberHookScript()`
3. Display file path in Inspector panel
4. No CLI or Agent Bridge changes needed
**Milestone:** Inspector shows "src/components/DashboardCard.tsx:12" for any selected component.
---
### Phase 2 — Intent Message + "Commit" UX (23 days, depends on Phase 1)
1. Define `IntentMessage` and `IntentChange` types in a new shared package or `agent-bridge/src/protocol.ts`
2. Add "Commit as Intent →" button to Inspector (appears after any DOM patch)
3. Canvas builds `IntentMessage` from `selectedComponentData` + `styleEditQueue`
4. For now, log intent to console and show a "copied to clipboard" confirmation
5. No Agent Bridge wiring yet — this phase validates the UX
**Milestone:** User can make a visual change and see a well-formed IntentMessage describing it.
---
### Phase 3 — CLI AST Indexer (34 days, parallel to Phase 2)
1. Implement `packages/cli/src/indexer.ts` — TypeScript AST walker
2. Implement `packages/cli/src/index-server.ts` — HTTP API on :4171
3. Wire into `cli.ts` alongside proxy
4. Inject `window.__OM_INDEX_URL__` into proxied HTML
5. Canvas queries indexer to enrich IntentMessage before emit
**Milestone:** `GET http://localhost:4171/components?name=DashboardCard` returns file path, props, tokens.
---
### Phase 4 — Agent Bridge `push_intent` + `resolve_component` (23 days, depends on Phase 2)
1. Add `push_intent` tool to `agent-bridge/src/tools.ts`
2. Add `resolve_component` tool (proxies to CLI indexer via registered URL)
3. Add `/register-indexer` endpoint
4. Implement `INTENT_RECEIVED` server push over WebSocket
5. Update Claude Code adapter's `CLAUDE.md` section to document the new workflow
6. Wire canvas Intent emit through to the API route → Agent Bridge
**Milestone:** User confirms an intent in the canvas; Claude Code session receives it and can open the right file.
---
### Phase 5 — E2E validation (12 days)
1. Full flow test: proxy → fiber hook → component select → DOM patch → confirm → Claude Code applies edit → hot reload
2. Validate that `_debugSource.fileName` matches the AST indexer's `filePath` for the same component
3. Edge cases: components without `_debugSource` (production build, dynamically generated), components defined in `node_modules`
---
## 9. Open Questions / Decisions Needed
### Q1: Intent storage — Supabase or in-memory only?
`push_intent` can either:
- **A) Write to Supabase** `intent_diffs` table, return an ID, allow polling via existing `get_pending_diffs`. Agent Bridge is stateless.
- **B) In-memory queue per WebSocket session**. Simpler, no DB write, but intents are lost if the agent disconnects.
Option A integrates with the existing diff workflow and gives the canvas visibility into `IMPLEMENTED` status. Option B is faster to ship but creates a parallel, disconnected system.
**Recommendation:** Option A. The existing `DiffStatus` enum (`DRAFT → EXPORTED → IMPLEMENTED → BLOCKED`) maps cleanly onto the intent lifecycle. The canvas already has diff-tracking UX built.
---
### Q2: Source of truth for component→file mapping — `_debugSource` or AST indexer?
| | `_debugSource` | AST Indexer |
|---|---|---|
| Availability | Dev builds only, present in fiber | Any .ts/.tsx file |
| Accuracy | Exact (React sets it) | High (AST, not heuristics) |
| Prop types | Not available | Full type extraction |
| Token refs | Not available | String-literal scan |
| Setup cost | Zero | CLI must be running |
| Fallback if absent | Graceful (no file info) | N/A — indexer is offline |
**Recommendation:** Use `_debugSource` as the primary source (always present if CLI is running in dev). Use AST indexer as enrichment (adds prop schema + token list). If neither is available, IntentMessage is valid but less specific.
---
### Q3: Snapshot capture — before only, or before + after?
Capturing a before-snapshot when the component is selected is straightforward (`html2canvas` or `getComputedStyle` snapshot). Capturing an after-snapshot requires waiting for the DOM patch to settle.
**Recommendation:** Capture before on component selection (cheap). Capture after on "Confirm" click with a 100ms delay to allow the DOM patch to apply. Make both optional — the agent can act on the textual change description alone.
---
### Q4: AST indexer scope — props only, or prop defaults too?
Extracting default values requires evaluating expressions (e.g., `const { size = 'md' } = props`). This touches type inference territory and can be expensive.
**Recommendation:** Phase 3 extracts prop names and types only. Default values are Phase 5+ once the basic pipeline is proven.
---
## 10. Non-Goals for This Iteration
- **Vue / Svelte support**: The fiber hook is React-specific. The DOM inspector (`buildDomInspectorScript`) is framework-agnostic but has no AST indexer equivalent. Out of scope.
- **Production build support**: `_debugSource` is dev-only. Intent messages from production artboards will lack source file info; the AST indexer can partially fill this gap via name matching.
- **Multi-file edits**: A single intent maps to a single component change. Refactoring that spans multiple files (e.g., extracting a token to a design system) is out of scope for Phase 14.
- **Automatic apply without agent**: The Agent Bridge is the exit point. We are not building a code-writing engine inside the canvas. The canvas describes intent; an agent with file access applies it.
-2493
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -26,8 +26,11 @@ const nextConfig: NextConfig = {
},
async rewrites() {
return {
beforeFiles: [
{ source: '/', destination: '/marketing.html' },
// afterFiles runs after the filesystem check — static HTML files in
// public/features/ are served when the clean URL is requested
// (e.g. /features/live-artboard → /features/live-artboard.html)
afterFiles: [
{ source: '/features/:slug', destination: '/features/:slug.html' },
],
};
},
File diff suppressed because it is too large Load Diff
+21 -21
View File
@@ -154,21 +154,21 @@ export default function MarketingPage() {
onMouseEnter={openDd}
onMouseLeave={schedDd}
>
<a className="mkt-nav-dd-item" href="/features/live-artboard.html">
<a className="mkt-nav-dd-item" href="/features/live-artboard">
<div className="mkt-nav-dd-icon"><IconArtboard /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Live Artboard</span>
<span className="mkt-nav-dd-desc">Your app rendered live in the canvas</span>
</div>
</a>
<a className="mkt-nav-dd-item" href="/features/origin-graph.html">
<a className="mkt-nav-dd-item" href="/features/origin-graph">
<div className="mkt-nav-dd-icon"><IconGraph /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Origin Graph</span>
<span className="mkt-nav-dd-desc">Every component, indexed and queryable</span>
</div>
</a>
<a className="mkt-nav-dd-item" href="/features/intent-diff.html">
<a className="mkt-nav-dd-item" href="/features/intent-diff">
<div className="mkt-nav-dd-icon"><IconDiff /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Intent Diff</span>
@@ -176,21 +176,21 @@ export default function MarketingPage() {
</div>
</a>
<div className="mkt-nav-dd-sep" />
<a className="mkt-nav-dd-item" href="/features/completion-zone.html">
<a className="mkt-nav-dd-item" href="/features/completion-zone">
<div className="mkt-nav-dd-icon"><IconStar /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Completion Zone</span>
<span className="mkt-nav-dd-desc">AI-filled design, constrained to your system</span>
</div>
</a>
<a className="mkt-nav-dd-item" href="/features/agent-bridge.html">
<a className="mkt-nav-dd-item" href="/features/agent-bridge">
<div className="mkt-nav-dd-icon"><IconBridge /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Agent Bridge</span>
<span className="mkt-nav-dd-desc">MCP channel to Cursor, Claude Code & more</span>
</div>
</a>
<a className="mkt-nav-dd-item" href="/features/integrations.html">
<a className="mkt-nav-dd-item" href="/features/integrations">
<div className="mkt-nav-dd-icon"><IconLink /></div>
<div className="mkt-nav-dd-text">
<span className="mkt-nav-dd-name">Integrations</span>
@@ -236,12 +236,12 @@ export default function MarketingPage() {
</svg>
</a>
<div className={`mkt-nav-mobile-sub${mobileSubOpen ? ' open' : ''}`}>
<a href="/features/live-artboard.html" onClick={() => setMobileOpen(false)}>Live Artboard</a>
<a href="/features/origin-graph.html" onClick={() => setMobileOpen(false)}>Origin Graph</a>
<a href="/features/intent-diff.html" onClick={() => setMobileOpen(false)}>Intent Diff</a>
<a href="/features/completion-zone.html" onClick={() => setMobileOpen(false)}>Completion Zone</a>
<a href="/features/agent-bridge.html" onClick={() => setMobileOpen(false)}>Agent Bridge</a>
<a href="/features/integrations.html" onClick={() => setMobileOpen(false)}>Integrations</a>
<a href="/features/live-artboard" onClick={() => setMobileOpen(false)}>Live Artboard</a>
<a href="/features/origin-graph" onClick={() => setMobileOpen(false)}>Origin Graph</a>
<a href="/features/intent-diff" onClick={() => setMobileOpen(false)}>Intent Diff</a>
<a href="/features/completion-zone" onClick={() => setMobileOpen(false)}>Completion Zone</a>
<a href="/features/agent-bridge" onClick={() => setMobileOpen(false)}>Agent Bridge</a>
<a href="/features/integrations" onClick={() => setMobileOpen(false)}>Integrations</a>
</div>
</li>
<li><a href="#mkt-integrations" onClick={() => setMobileOpen(false)}>Integrations</a></li>
@@ -467,7 +467,7 @@ export default function MarketingPage() {
<li><span className="mkt-feat-point-icon"><IconGraph /></span>Changes in the canvas write back to your component props no round trips.</li>
<li><span className="mkt-feat-point-icon"></span>Hot reload and real-time data. The canvas reflects your app state at all times.</li>
</ul>
<a href="/features/live-artboard.html" className="mkt-btn mkt-btn-primary">See it in action </a>
<a href="/features/live-artboard" className="mkt-btn mkt-btn-primary">See it in action </a>
</div>
<div className="mkt-feat-visual mkt-feat-visual-dark mkt-reveal">
<div className="mkt-fv-artboard-scene">
@@ -498,7 +498,7 @@ export default function MarketingPage() {
<li><span className="mkt-feat-point-icon">🤖</span>Agents receive the diff and apply it precisely no interpretation required.</li>
<li><span className="mkt-feat-point-icon"></span>Parity is verified automatically. The canvas confirms the result.</li>
</ul>
<a href="/features/intent-diff.html" className="mkt-btn mkt-btn-primary">Explore Intent Diff </a>
<a href="/features/intent-diff" className="mkt-btn mkt-btn-primary">Explore Intent Diff </a>
</div>
<div className="mkt-feat-visual mkt-feat-visual-dark mkt-reveal">
<div className="mkt-fv-diff-scene">
@@ -530,7 +530,7 @@ export default function MarketingPage() {
<li><span className="mkt-feat-point-icon"><IconLink /></span>Completion Zone uses the graph to constrain AI suggestions to your design language.</li>
<li><span className="mkt-feat-point-icon"><IconBridge /></span>Agent Bridge exposes the graph to external AI tools via MCP protocol.</li>
</ul>
<a href="/features/origin-graph.html" className="mkt-btn mkt-btn-primary">Explore the Graph </a>
<a href="/features/origin-graph" className="mkt-btn mkt-btn-primary">Explore the Graph </a>
</div>
<div className="mkt-feat-visual mkt-feat-visual-dark mkt-reveal" style={{ position: 'relative' }}>
<div className="mkt-fv-graph-scene">
@@ -964,12 +964,12 @@ export default function MarketingPage() {
<div className="mkt-ft-col">
<h4>Product</h4>
<ul>
<li><a href="/features/live-artboard.html">Live Artboard</a></li>
<li><a href="/features/origin-graph.html">Origin Graph</a></li>
<li><a href="/features/intent-diff.html">Intent Diff</a></li>
<li><a href="/features/completion-zone.html">Completion Zone</a></li>
<li><a href="/features/agent-bridge.html">Agent Bridge</a></li>
<li><a href="/features/integrations.html">Integrations</a></li>
<li><a href="/features/live-artboard">Live Artboard</a></li>
<li><a href="/features/origin-graph">Origin Graph</a></li>
<li><a href="/features/intent-diff">Intent Diff</a></li>
<li><a href="/features/completion-zone">Completion Zone</a></li>
<li><a href="/features/agent-bridge">Agent Bridge</a></li>
<li><a href="/features/integrations">Integrations</a></li>
<li><a href="#mkt-pricing">Pricing</a></li>
</ul>
</div>
File diff suppressed because one or more lines are too long