updated stuff
This commit is contained in:
@@ -0,0 +1,478 @@
|
||||
# Originmain SDK Architecture
|
||||
### From Proxy to SDK — System Design & Implementation Progress
|
||||
|
||||
> **Living document.** Updated every time a piece of this architecture is implemented.
|
||||
> Supersedes `RENDERING-ARCHITECTURE.md` (proxy-era, now deprecated).
|
||||
>
|
||||
> Last updated: 2026-05-13
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Why We Moved Away From the Proxy](#1-why-we-moved-away-from-the-proxy)
|
||||
2. [Target Architecture](#2-target-architecture)
|
||||
3. [System Components](#3-system-components)
|
||||
4. [Wire Protocol](#4-wire-protocol)
|
||||
5. [Implementation Progress](#5-implementation-progress)
|
||||
6. [Package Map](#6-package-map)
|
||||
7. [Data Flow — Step by Step](#7-data-flow--step-by-step)
|
||||
8. [Local Dev Problem & Tunnel Strategy](#8-local-dev-problem--tunnel-strategy)
|
||||
9. [Edge Cases](#9-edge-cases)
|
||||
10. [What Remains To Be Built](#10-what-remains-to-be-built)
|
||||
|
||||
---
|
||||
|
||||
## 1. Why We Moved Away From the Proxy
|
||||
|
||||
The original approach (`@originmain/cli`) ran a local HTTP reverse proxy that injected a
|
||||
`<script>` tag into every HTML response. This failed in practice for four compounding reasons:
|
||||
|
||||
| Failure | Root Cause | Fatal? |
|
||||
|---|---|---|
|
||||
| Script never executed | React 19 `hydrateRoot(document, ...)` reconciles the entire `<head>` and removes any `<script>` tag not present in React's VDOM | ✅ Yes |
|
||||
| `window.name` always empty | Chrome 88+ strips `window.name` on cross-origin iframe loads (Spectre mitigation) — the SDK used this as its artboard ID source | ✅ Yes |
|
||||
| Inline script blocked | CSPs without `'unsafe-inline'` blocked the injected `<script>…</script>` content | ✅ Yes |
|
||||
| Hook installed too late | `__REACT_DEVTOOLS_GLOBAL_HOOK__` must exist **before** React's module body evaluates. Injected scripts run after HTML parses — after React is already loaded | ✅ Yes |
|
||||
|
||||
**The only reliable fix**: the fiber hook must be installed by the app itself, not by an external injector. That means it ships as an npm package the developer imports **before React** in their app entry point.
|
||||
|
||||
---
|
||||
|
||||
## 2. Target Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ORIGINMAIN CLOUD (originmain.com) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ ┌──────────────────────────────────┐ │
|
||||
│ │ Canvas (Next.js app) │◄───►│ WebSocket Bridge │ │
|
||||
│ │ • Artboards / iframes │ │ /api/sdk/[projectId] │ │
|
||||
│ │ • Inspector / Editor │ │ • auth via SDK token │ │
|
||||
│ │ • File diff viewer │ │ • routes messages to user │ │
|
||||
│ └─────────────────────────────┘ └──────────────────────────────────┘ │
|
||||
│ ▲ │
|
||||
└────────────────────────────────────────────────────────│───────────────────┘
|
||||
│ WSS
|
||||
│ (wss://originmain.com/api/sdk/[projectId])
|
||||
┌────────────────────────────────────────────────────────│───────────────────┐
|
||||
│ USER MACHINE (localhost / staging) │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────┐ ┌────────────────────────────────────┐ │
|
||||
│ │ Next.js dev server │◄───►│ @originmain/dev (SDK) │ │
|
||||
│ │ (next dev / Vercel) │ │ • client runtime in browser │ │
|
||||
│ │ │ │ • server runtime in Node │ │
|
||||
│ │ │ │ • direct React fiber access │ │
|
||||
│ │ Components.tsx ◄────────┼─────┤ • file-write capability │ │
|
||||
│ │ │ │ • opens WSS to cloud canvas │ │
|
||||
│ └──────────────────────────┘ └────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ next.config.js — wrapped with @originmain/next plugin │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key invariant**: the fiber hook lives *inside* the user's app bundle.
|
||||
No proxy, no injection, no external script — the user's `import '@originmain/live'`
|
||||
is what instruments React.
|
||||
|
||||
---
|
||||
|
||||
## 3. System Components
|
||||
|
||||
### 3.1 `@originmain/live` (client runtime) — `packages/live-sdk`
|
||||
|
||||
The browser-side SDK. A side-effect import that:
|
||||
|
||||
- Installs `__REACT_DEVTOOLS_GLOBAL_HOOK__` **before React evaluates** (module-load time)
|
||||
- Resolves the artboard ID from three sources in priority order:
|
||||
1. URL fragment: `location.hash` contains `#__om_artboard=<id>` ← primary, cross-origin safe
|
||||
2. `window.name`: starts with `om:` (same-origin iframes only)
|
||||
3. postMessage handshake: sends `{ __om_init_request: true }` to parent, awaits reply
|
||||
- Runs the full postMessage protocol (see §4) once the artboard ID is known
|
||||
- Is a **complete no-op** outside an Originmain artboard iframe (zero runtime cost in production)
|
||||
|
||||
### 3.2 `@originmain/next` (build plugin) — `packages/next`
|
||||
|
||||
A `withOriginmain(nextConfig)` wrapper for `next.config.ts` that:
|
||||
|
||||
- Prepends `@originmain/live` to every client-side webpack entry point
|
||||
- Ensures the fiber hook import runs **before any other module** in the bundle
|
||||
- Skips the server-side bundle (fiber hook is browser-only)
|
||||
- Is idempotent (safe to wrap twice)
|
||||
- Delegates to any existing `webpack` customisation in the user's config
|
||||
|
||||
### 3.3 `@originmain/dev` (full SDK) — `packages/dev` ❌ NOT BUILT YET
|
||||
|
||||
The full SDK intended for local development. Will combine:
|
||||
|
||||
- Everything in `@originmain/live` (client runtime, fiber hook)
|
||||
- A **server runtime** that runs inside the Next.js dev server process
|
||||
- A **WebSocket client** that connects outbound to the cloud canvas bridge
|
||||
- **File-write capability**: receives design panel edits from the canvas and applies them to source files (`.tsx`, `.ts`, CSS modules)
|
||||
|
||||
### 3.4 WebSocket Bridge — `packages/app/src/app/api/sdk/[projectId]/` ❌ NOT BUILT YET
|
||||
|
||||
A Next.js API route on the cloud canvas that:
|
||||
|
||||
- Accepts an inbound WSS connection from `@originmain/dev`
|
||||
- Authenticates via a project-scoped SDK token (issued in project settings)
|
||||
- Routes messages bidirectionally:
|
||||
- SDK → Canvas: `FIBER_TREE_UPDATE`, `ELEMENT_STYLES`, `ROUTES_DISCOVERED`, etc.
|
||||
- Canvas → SDK: `PATCH_ELEMENT_STYLE`, `REQUEST_ELEMENT_STYLES`, `CAPTURE_SNAPSHOT`, etc.
|
||||
- Maintains one WebSocket connection per active project session
|
||||
|
||||
### 3.5 Canvas (Next.js app) — `packages/app`
|
||||
|
||||
Originmain's cloud editor. Current transport: **postMessage via iframe**.
|
||||
Future transport: **WebSocket via bridge** (for `@originmain/dev` local dev).
|
||||
|
||||
Key components:
|
||||
- `LiveArtboard.tsx` — manages the `<iframe>`, postMessage listener, message dispatch
|
||||
- `DesignTab.tsx` — design property sections (Frame, Layout, Fill, Typography, etc.)
|
||||
- `Inspector.tsx` — tab container (Design / Props / Code / Diff / Graph)
|
||||
- `Artboard.tsx` — artboard frame, selection overlay, static-page detection
|
||||
- `Canvas.tsx` — infinite canvas, zoom/pan, artboard layout, onboarding overlay
|
||||
|
||||
---
|
||||
|
||||
## 4. Wire Protocol
|
||||
|
||||
Defined in `packages/renderer/src/protocol.ts`. Both the postMessage transport
|
||||
(current) and the WebSocket transport (planned) use the same message shapes.
|
||||
|
||||
### 4.1 Canvas → App (Host → Renderer)
|
||||
|
||||
| Message | Payload | Purpose |
|
||||
|---|---|---|
|
||||
| `SET_DESIGN_TOKENS` | `{ tokens: Record<string, string> }` | Push CSS custom property values |
|
||||
| `NAVIGATE` | `{ path: string }` | SPA navigation inside the iframe |
|
||||
| `SELECT_COMPONENT` | `{ nodeId: string }` | Show highlight ring on component |
|
||||
| `DESELECT` | — | Remove highlight ring |
|
||||
| `REQUEST_ELEMENT_STYLES` | `{ nodeId: string }` | Ask for computed CSS properties |
|
||||
| `PATCH_ELEMENT_STYLE` | `{ nodeId, property, value }` | Apply one inline CSS override |
|
||||
| `PATCH_CHILDREN_STYLE` | `{ parentNodeId, selector, property, value }` | Patch CSS on all matching children |
|
||||
| `REMOVE_ELEMENT` | `{ nodeId: string }` | Set `display:none` on element |
|
||||
| `CAPTURE_THUMBNAIL` | — | Capture full-page JPEG via html2canvas |
|
||||
| `CAPTURE_SNAPSHOT` | `{ nodeId: string }` | Capture selected element PNG |
|
||||
| `CANCEL_SNAPSHOT` | — | Abort in-flight snapshot |
|
||||
|
||||
### 4.2 App → Canvas (Renderer → Host)
|
||||
|
||||
| Message | Payload | Purpose |
|
||||
|---|---|---|
|
||||
| `READY` | `{ rootFontSizePx?: number }` | Hook active, React detected |
|
||||
| `FIBER_TREE_UPDATE` | `{ root: FiberNode }` | Full serialized component tree |
|
||||
| `COMPONENT_SELECTED` | `{ nodeId, rect }` | User clicked a component |
|
||||
| `COMPONENT_DESELECTED` | — | User clicked empty space |
|
||||
| `ELEMENT_STYLES` | `{ nodeId, styles, hasDirectText, hasParagraphChildren }` | Computed CSS response |
|
||||
| `ROUTES_DISCOVERED` | `{ routes: Array<{ path, label }> }` | App's navigation routes |
|
||||
| `THUMBNAIL_READY` | `{ dataUrl: string \| null }` | JPEG data URL or null |
|
||||
| `SNAPSHOT_READY` | `{ dataUrl: string \| null, nodeId: string }` | PNG data URL or null |
|
||||
| `ERROR` | `{ message: string }` | Hook or serialization error |
|
||||
|
||||
### 4.3 Envelope Format (postMessage)
|
||||
|
||||
```ts
|
||||
// Canvas → App
|
||||
{ source: 'originmain-host', artboardId: string, message: HostMessage }
|
||||
|
||||
// App → Canvas
|
||||
{ source: 'originmain-renderer', artboardId: string, message: RendererMessage }
|
||||
```
|
||||
|
||||
### 4.4 Artboard ID Resolution (how the SDK finds its artboard)
|
||||
|
||||
The canvas appends the artboard ID to the iframe's `src` as a URL fragment:
|
||||
|
||||
```tsx
|
||||
// LiveArtboard.tsx
|
||||
src={url + '#__om_artboard=' + encodeURIComponent(id)}
|
||||
```
|
||||
|
||||
The SDK reads it in priority order:
|
||||
```
|
||||
1. location.hash → /__om_artboard=abc123/ (primary, cross-origin safe)
|
||||
2. window.name → "om:abc123" (same-origin iframes)
|
||||
3. postMessage handshake (async fallback, 10s timeout)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Progress
|
||||
|
||||
### ✅ Done
|
||||
|
||||
| Component | File(s) | Notes |
|
||||
|---|---|---|
|
||||
| `@originmain/live` client runtime | `packages/live-sdk/src/hook.ts` | Full rewrite. URL fragment guard. Full protocol parity. html2canvas support. |
|
||||
| `@originmain/live` build step | `packages/live-sdk/build.mjs` | esbuild → `dist/index.js` (9.4kb browser ESM bundle, minified). |
|
||||
| `@originmain/next` build plugin | `packages/next/src/index.ts` | `withOriginmain()` — webpack entry prepend. Idempotent. |
|
||||
| `@originmain/next` build step | `packages/next/build.mjs` | esbuild → ESM + CJS; tsc → `dist/index.d.ts` type declarations. |
|
||||
| Root `sdk:build` script | `package.json` | `pnpm sdk:build` builds both SDK packages in order. |
|
||||
| LiveArtboard postMessage handshake | `packages/app/src/components/canvas/LiveArtboard.tsx` | Responds to `__om_init_request` from SDK |
|
||||
| LiveArtboard URL fragment injection | `packages/app/src/components/canvas/LiveArtboard.tsx` | Appends `#__om_artboard=<id>` to all iframe src URLs |
|
||||
| Style refresh after design panel edit | `packages/app/src/components/canvas/LiveArtboard.tsx` | 120ms debounced `REQUEST_ELEMENT_STYLES` after queue drains |
|
||||
| Design panel → history tracking | `packages/app/src/components/inspector/DesignTab.tsx` | `pushEdit()` on every `patch()` call — feeds Diff tab |
|
||||
| Design panel → optimistic styles | `packages/app/src/components/inspector/DesignTab.tsx` | `setComponentStyles()` optimistic update — inputs reflect change immediately |
|
||||
| Canvas onboarding UI | `packages/app/src/components/canvas/Canvas.tsx` | Updated from proxy instructions to SDK install instructions |
|
||||
| Static page banner | `packages/app/src/components/canvas/Artboard.tsx` | Now says "add `import '@originmain/live'`" instead of confusing message |
|
||||
| `tsconfig.base.json` path alias | `tsconfig.base.json` | `@originmain/next` added to paths |
|
||||
|
||||
### 🚧 In Progress / Partially Done
|
||||
|
||||
| Component | File(s) | Status | Blocker |
|
||||
|---|---|---|---|
|
||||
| `@originmain/live` publishing | `packages/live-sdk/package.json` | Build complete. `"private": false`. **Not yet `npm publish`-ed** | Run `pnpm sdk:build && cd packages/live-sdk && npm publish` |
|
||||
| `@originmain/next` publishing | `packages/next/package.json` | Build complete. `"private": false`. **Not yet `npm publish`-ed** | Same (depends on live being published first) |
|
||||
| CLI proxy deprecation | `packages/cli/` | Still exists and still works | Can delete once SDK is published and users migrate |
|
||||
|
||||
### ❌ Not Built
|
||||
|
||||
| Component | Target File(s) | Priority | Depends On |
|
||||
|---|---|---|---|
|
||||
| WebSocket bridge API route | `packages/app/src/app/api/sdk/[projectId]/route.ts` | HIGH | — |
|
||||
| `@originmain/dev` package | `packages/dev/` | HIGH | WebSocket bridge |
|
||||
| SDK server runtime (Node.js) | `packages/dev/src/server.ts` | HIGH | WebSocket bridge |
|
||||
| File-write capability | `packages/dev/src/file-writer.ts` | HIGH | Server runtime |
|
||||
| SDK token issuance in canvas | `packages/app/src/app/api/sdk/token/route.ts` | HIGH | WebSocket bridge |
|
||||
| Local dev tunnel (localhost → WSS) | TBD | MEDIUM | `@originmain/dev` |
|
||||
| `IsolationFrame.tsx` SDK migration | `packages/app/src/components/canvas/IsolationFrame.tsx` | LOW | `@originmain/dev` |
|
||||
|
||||
---
|
||||
|
||||
## 6. Package Map
|
||||
|
||||
```
|
||||
packages/
|
||||
├── app/ @originmain/app — Cloud canvas (Next.js, Vercel)
|
||||
├── live-sdk/ @originmain/live — Client SDK: browser fiber hook ✅ DONE
|
||||
├── next/ @originmain/next — Next.js build plugin ✅ DONE
|
||||
├── dev/ @originmain/dev — Full SDK (client + server) ❌ NOT BUILT
|
||||
├── renderer/ @originmain/renderer — Protocol types, message shapes
|
||||
├── cli/ @originmain/cli — Legacy proxy (deprecated, not deleted yet)
|
||||
├── diff-engine/ @originmain/diff-engine
|
||||
├── design-language/ @originmain/design-language
|
||||
├── agent-bridge/ @originmain/agent-bridge
|
||||
├── ai-layer/ @originmain/ai-layer
|
||||
├── platform/ @originmain/platform
|
||||
├── multiplayer/ @originmain/multiplayer
|
||||
├── origin-graph/ @originmain/origin-graph
|
||||
├── ui/ @originmain/ui
|
||||
└── e2e/ (test suite)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Flow — Step by Step
|
||||
|
||||
### 7.1 Current Flow (postMessage via iframe)
|
||||
|
||||
```
|
||||
1. User adds @originmain/live to their Next.js app
|
||||
2. User wraps next.config.ts with withOriginmain()
|
||||
3. User deploys to Vercel — @originmain/live is in the bundle
|
||||
|
||||
4. Canvas: user pastes Vercel URL → artboard created in DB
|
||||
5. Canvas: LiveArtboard renders <iframe src="https://app.vercel.app/route#__om_artboard=abc123">
|
||||
6. Browser: iframe loads app from Vercel
|
||||
|
||||
7. App bundle: @originmain/live runs (module-level side effect, before React)
|
||||
→ installs __REACT_DEVTOOLS_GLOBAL_HOOK__
|
||||
→ reads location.hash → finds __om_artboard=abc123
|
||||
→ calls startMainLoop("abc123")
|
||||
|
||||
8. React evaluates → sees hook → registers onCommitFiberRoot
|
||||
|
||||
9. App renders → React commits
|
||||
→ onCommitFiberRoot fires
|
||||
→ SDK serializes fiber tree
|
||||
→ postMessage({ source: 'originmain-renderer', artboardId: 'abc123',
|
||||
message: { type: 'FIBER_TREE_UPDATE', root: ... } })
|
||||
|
||||
10. Canvas: LiveArtboard.handleMessage receives FIBER_TREE_UPDATE
|
||||
→ setFiberRoot(artboardId, root) [Zustand]
|
||||
→ SelectionOverlay renders component hit-test overlay
|
||||
→ Inspector shows component tree (Graph tab)
|
||||
|
||||
11. User clicks a component in the iframe
|
||||
→ SDK: click handler → getFiberKey(el) → walk fiber.return chain
|
||||
→ postMessage COMPONENT_SELECTED { nodeId, rect }
|
||||
|
||||
12. Canvas: receives COMPONENT_SELECTED
|
||||
→ selectComponent(nodeId) [Zustand]
|
||||
→ LiveArtboard sends REQUEST_ELEMENT_STYLES { nodeId }
|
||||
|
||||
13. SDK: respondWithStyles() → getComputedStyle(el) → all CSS properties
|
||||
→ postMessage ELEMENT_STYLES { nodeId, styles, hasDirectText, hasParagraphChildren }
|
||||
|
||||
14. Canvas: setComponentStyles(styles) [Zustand]
|
||||
→ DesignTab re-renders with actual computed values
|
||||
→ Frame/Layout/Fill/Typography sections show live data
|
||||
|
||||
15. User edits width in Frame section: 200px → 250px
|
||||
[DesignTab.patch('width', '250px')]
|
||||
→ patchStyleEdit(artboardId, nodeId, 'width', '250px') → styleEditQueue
|
||||
→ setComponentStyles({ ...styles, width: '250px' }) → optimistic panel update
|
||||
→ pushEdit({ key: 'width', before: '200px', after: '250px', ... }) → history
|
||||
|
||||
16. LiveArtboard: styleEditQueue effect drains
|
||||
→ postMessage PATCH_ELEMENT_STYLE { nodeId, property: 'width', value: '250px' }
|
||||
→ starts 120ms debounce timer
|
||||
|
||||
17. SDK: patchElementStyle(nodeId, 'width', '250px')
|
||||
→ el.style.setProperty('width', '250px')
|
||||
→ iframe visually updates ✅
|
||||
|
||||
18. 120ms later: LiveArtboard sends REQUEST_ELEMENT_STYLES
|
||||
→ SDK: getComputedStyle now returns '250px' (inline style overrides)
|
||||
→ ELEMENT_STYLES response → panel shows accurate computed value
|
||||
|
||||
19. Diff tab: pendingChanges = [{ key: 'width', before: '200px', after: '250px' }]
|
||||
→ "Export diff →" button active
|
||||
→ Code tab shows generated file patch
|
||||
→ "Send to Agent" sends diff to AI for code application
|
||||
```
|
||||
|
||||
### 7.2 Target Flow (WebSocket — not built yet)
|
||||
|
||||
Same as above, except steps 5–9 use WebSocket instead of postMessage:
|
||||
|
||||
```
|
||||
5. Canvas: LiveArtboard connects to wss://originmain.com/api/sdk/abc123
|
||||
6. @originmain/dev (server runtime): connects to same WSS URL with SDK token
|
||||
7. Bridge authenticates token, pairs the two connections
|
||||
8. All messages flow through the bridge instead of postMessage
|
||||
→ This unlocks local dev (no Vercel deploy required)
|
||||
→ This enables file-write (SDK has Node.js fs access)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Local Dev Problem & Tunnel Strategy
|
||||
|
||||
### The Problem
|
||||
|
||||
The cloud canvas (`https://originmain.com`) is served over HTTPS. Browsers
|
||||
enforce **mixed content policy**: an HTTPS page cannot iframe `http://localhost:3000`.
|
||||
This means the current SDK approach (iframe + postMessage) **only works for deployed apps**
|
||||
(Vercel, Netlify, etc.) — not for local development.
|
||||
|
||||
### Current Workaround (v1)
|
||||
|
||||
Users must deploy to Vercel (or any HTTPS host) to use the canvas.
|
||||
Vercel's preview deployments are free and instant (`vercel deploy --prod` is optional).
|
||||
|
||||
### Planned Solution (`@originmain/dev` + WebSocket bridge)
|
||||
|
||||
When the WebSocket bridge exists:
|
||||
- `@originmain/dev` runs a Node.js server alongside `next dev`
|
||||
- It connects **outbound** from the user's machine via WSS to the canvas bridge
|
||||
- Outbound WSS from localhost → cloud is always allowed (no mixed content issue)
|
||||
- The canvas receives fiber data via the bridge, not via the iframe
|
||||
- The iframe can be replaced with a screenshot stream or kept pointing to a tunnel URL
|
||||
|
||||
### Tunnel Options (if iframe is still needed for visual rendering)
|
||||
|
||||
| Option | Effort | Notes |
|
||||
|---|---|---|
|
||||
| Vercel deploy (v1 recommendation) | Lowest | Works today. No extra tooling. |
|
||||
| `cloudflared tunnel` (user-run) | Low | `npx cloudflared tunnel --url http://localhost:3000` |
|
||||
| Originmain-managed tunnel | High | Requires Originmain to run tunnel infrastructure |
|
||||
|
||||
---
|
||||
|
||||
## 9. Edge Cases
|
||||
|
||||
### 9.1 React DevTools Coexistence
|
||||
|
||||
React DevTools extension installs its own `__REACT_DEVTOOLS_GLOBAL_HOOK__` first.
|
||||
The SDK detects the existing hook and wraps `onCommitFiberRoot` — both DevTools and
|
||||
Originmain receive fiber commits. Neither overwrites the other.
|
||||
|
||||
### 9.2 HMR (Hot Module Replacement)
|
||||
|
||||
When webpack/turbopack pushes a hot update, React re-renders affected components.
|
||||
`onCommitFiberRoot` fires again → SDK sends a new `FIBER_TREE_UPDATE` → canvas updates.
|
||||
The artboard ID is stable across hot updates (URL fragment persists).
|
||||
|
||||
### 9.3 SPA Navigation
|
||||
|
||||
SPA routers change the URL without a full page reload. React re-renders.
|
||||
`onCommitFiberRoot` fires → new `FIBER_TREE_UPDATE`. Route discovery re-runs via
|
||||
a `popstate` listener. The SDK sends updated `ROUTES_DISCOVERED`.
|
||||
|
||||
### 9.4 React 19 Server Components
|
||||
|
||||
Server Components don't produce fiber nodes in the client tree — they're rendered
|
||||
to RSC payload and hydrated as static DOM. The SDK correctly skips these
|
||||
(they have no `type` function). Only client components appear in the tree.
|
||||
|
||||
### 9.5 Non-React Pages (Static HTML)
|
||||
|
||||
If the app serves a page with no React (e.g., a static landing page),
|
||||
`onCommitFiberRoot` never fires. The `READY` message is still sent.
|
||||
After 8 seconds with no `FIBER_TREE_UPDATE`, `LiveArtboard` calls
|
||||
`onStaticPageDetected()` → the "No React detected" banner appears.
|
||||
|
||||
### 9.6 Multiple Artboards, Same App
|
||||
|
||||
Multiple artboards can iframe the same app at different routes. Each gets a
|
||||
unique `#__om_artboard=<id>` in its URL. The SDK is a module singleton — it runs
|
||||
once per page load — and binds to the single artboard ID from the URL fragment.
|
||||
Each iframe is a separate browsing context with its own SDK instance.
|
||||
|
||||
### 9.7 CSP (`script-src 'self'`)
|
||||
|
||||
Since the SDK ships in the app's own bundle (not injected externally), CSP
|
||||
`script-src 'self'` does not block it. The SDK is part of the same origin as the app.
|
||||
|
||||
---
|
||||
|
||||
## 10. What Remains To Be Built
|
||||
|
||||
Priority order for next implementation sprint:
|
||||
|
||||
### Priority 1 — Publish packages (unblocks users) ✅ BUILD COMPLETE
|
||||
|
||||
- [x] Add esbuild build step for `@originmain/live` → `packages/live-sdk/build.mjs`
|
||||
- [x] Add esbuild + tsc build step for `@originmain/next` → `packages/next/build.mjs`
|
||||
- [x] Update exports in both `package.json` files to point to `dist/`
|
||||
- [x] Add `@originmain/live` as workspace dependency of `@originmain/next`
|
||||
- [x] Root `pnpm sdk:build` script for one-command build
|
||||
- [ ] `npm publish` `@originmain/live` — ready to publish, command: `cd packages/live-sdk && npm publish`
|
||||
- [ ] `npm publish` `@originmain/next` — depends on live being published first
|
||||
|
||||
### Priority 2 — WebSocket Bridge (unblocks local dev)
|
||||
|
||||
- [ ] `packages/app/src/app/api/sdk/[projectId]/route.ts`
|
||||
- Accept WSS upgrade from `@originmain/dev`
|
||||
- Authenticate via `Authorization: Bearer <sdk-token>` header
|
||||
- Pair with the canvas session for the same projectId
|
||||
- Bidirectional message routing
|
||||
- Handle reconnect / heartbeat
|
||||
|
||||
### Priority 3 — `@originmain/dev` package (file-write, local dev)
|
||||
|
||||
- [ ] `packages/dev/src/client.ts` — re-export `@originmain/live`
|
||||
- [ ] `packages/dev/src/server.ts` — Node.js WebSocket client, connects to bridge
|
||||
- [ ] `packages/dev/src/file-writer.ts` — applies design panel patches to source files
|
||||
- Parse `PATCH_ELEMENT_STYLE` messages → locate source file via `callSite`
|
||||
- Rewrite Tailwind classes / CSS modules / inline styles
|
||||
- [ ] `packages/next/src/index.ts` — extend `withOriginmain()` to also start the server runtime
|
||||
|
||||
### Priority 4 — SDK token issuance
|
||||
|
||||
- [ ] Project settings UI: "Generate SDK token" button
|
||||
- [ ] `packages/app/src/app/api/sdk/token/route.ts` — create/rotate tokens
|
||||
- [ ] Store tokens in Supabase (scoped to project, revocable)
|
||||
|
||||
### Priority 5 — IsolationFrame migration
|
||||
|
||||
- [ ] Migrate `IsolationFrame.tsx` from proxy URL to SDK-based approach
|
||||
- Currently still requires the CLI proxy to serve `/__om_isolation__/*`
|
||||
|
||||
---
|
||||
|
||||
*Document owner: engineering*
|
||||
*Last updated: 2026-05-13*
|
||||
*Next review: when WebSocket bridge is implemented*
|
||||
Reference in New Issue
Block a user