feat: Design tab, live style editing, route-aware artboards, canvas onboarding
Design tab (4-tab inspector: Design / Props / Diff / Graph) - Click any component in a live artboard to inspect computed CSS - Typography, Layout, Visual sections auto-populated from getComputedStyle - Every property is inline-editable; changes apply live via PATCH_ELEMENT_STYLE - Zero source-code modifications — purely in-browser style overrides Protocol additions (packages/renderer) - REQUEST_ELEMENT_STYLES / ELEMENT_STYLES round-trip for CSS inspection - PATCH_ELEMENT_STYLE for live style patching Live SDK (packages/live-sdk) - respondWithStyles(): reads 30 curated computed CSS properties - patchElementStyle(): applies inline style override to fiber's DOM element Canvas store - selectedComponentStyles: current element's CSS map - styleEditEvent mailbox: Design tab → LiveArtboard style dispatch (Zustand v5 compatible) Route-aware artboards - Each artboard now has an optional route (e.g. /dashboard) - buildSrc(base, route) helper in Artboard.tsx - Route field editable in Inspector Props tab, persisted to metadata_jsonb On-canvas onboarding - Empty canvas now shows 3-step guide: press A → CLI command → paste URL - CLI command shown inline in a highlighted code block Changelog and Handoff updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
8514ffdc1a
commit
edada3091e
@@ -6,6 +6,7 @@ import {
|
||||
isRendererEnvelope,
|
||||
} from '@originmain/renderer';
|
||||
import type { FiberNode, RendererMessage } from '@originmain/renderer';
|
||||
import { useCanvas } from '@/store/canvas';
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -26,6 +27,10 @@ export interface LiveArtboardProps {
|
||||
onReady?: () => void;
|
||||
onFiberTreeUpdate?: (root: FiberNode) => void;
|
||||
onComponentSelected?: (nodeId: string) => void;
|
||||
/** Called when the iframe responds with computed CSS properties for a selected element. */
|
||||
onComponentStylesUpdate?: (nodeId: string, styles: Record<string, string>) => void;
|
||||
/** Forwards a CSS property patch from the Design tab into the iframe. */
|
||||
patchElementStyle?: (nodeId: string, property: string, value: string) => void;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
@@ -41,6 +46,7 @@ export function LiveArtboard({
|
||||
onReady,
|
||||
onFiberTreeUpdate,
|
||||
onComponentSelected,
|
||||
onComponentStylesUpdate,
|
||||
style,
|
||||
}: LiveArtboardProps) {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
@@ -91,17 +97,22 @@ export function LiveArtboard({
|
||||
break;
|
||||
case 'COMPONENT_SELECTED':
|
||||
onComponentSelected?.(msg.nodeId);
|
||||
// Request computed styles so the Design tab can populate immediately.
|
||||
sendMessage('REQUEST_ELEMENT_STYLES', { nodeId: msg.nodeId });
|
||||
break;
|
||||
case 'COMPONENT_DESELECTED':
|
||||
// Renderer clicked empty space — clear the host-side selection.
|
||||
onComponentSelected?.('');
|
||||
break;
|
||||
case 'ELEMENT_STYLES':
|
||||
onComponentStylesUpdate?.(msg.nodeId, msg.styles);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [id, designTokens, selectedComponentId, sendMessage, onReady, onFiberTreeUpdate, onComponentSelected]);
|
||||
}, [id, designTokens, selectedComponentId, sendMessage, onReady, onFiberTreeUpdate, onComponentSelected, onComponentStylesUpdate]);
|
||||
|
||||
// ── Push updated design tokens whenever they change ───────────────────────
|
||||
useEffect(() => {
|
||||
@@ -109,6 +120,23 @@ export function LiveArtboard({
|
||||
sendMessage('SET_DESIGN_TOKENS', { tokens: designTokens });
|
||||
}, [designTokens, sendMessage]);
|
||||
|
||||
// ── Style edit mailbox ─────────────────────────────────────────────────────
|
||||
// Watches the Zustand mailbox for PATCH_ELEMENT_STYLE events addressed to
|
||||
// this artboard and forwards them to the iframe immediately.
|
||||
const styleEditEvent = useCanvas((s) => s.styleEditEvent);
|
||||
const clearStyleEdit = useCanvas((s) => s.clearStyleEdit);
|
||||
|
||||
useEffect(() => {
|
||||
if (styleEditEvent?.artboardId === id && isReadyRef.current) {
|
||||
sendMessage('PATCH_ELEMENT_STYLE', {
|
||||
nodeId: styleEditEvent.nodeId,
|
||||
property: styleEditEvent.property,
|
||||
value: styleEditEvent.value,
|
||||
});
|
||||
clearStyleEdit();
|
||||
}
|
||||
}, [id, styleEditEvent, sendMessage, clearStyleEdit]);
|
||||
|
||||
// ── Sync selection changes into the iframe ────────────────────────────────
|
||||
// Sends SELECT_COMPONENT on every selectedComponentId change so the blue
|
||||
// highlight ring stays in sync with the canvas selection store.
|
||||
|
||||
Reference in New Issue
Block a user