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
@@ -17,6 +17,17 @@ interface ArtboardProps {
|
||||
width: number;
|
||||
height: number;
|
||||
renderUrl?: string;
|
||||
/** Route path appended to renderUrl so each artboard can show a different screen. */
|
||||
route?: string;
|
||||
}
|
||||
|
||||
/** Builds the iframe src from a base URL + optional route path.
|
||||
* e.g. ("http://localhost:4170/", "/dashboard") → "http://localhost:4170/dashboard" */
|
||||
function buildSrc(base: string, route?: string): string {
|
||||
if (!route || route === '/' || route === '') return base;
|
||||
const trimmed = base.replace(/\/$/, '');
|
||||
const path = route.startsWith('/') ? route : '/' + route;
|
||||
return trimmed + path;
|
||||
}
|
||||
|
||||
// Status color map matching the inspector
|
||||
@@ -27,10 +38,10 @@ const DIFF_STATUS_BADGE: Record<string, { color: string; bg: string; label: stri
|
||||
REJECTED: { color: '#FF8080', bg: 'rgba(255,128,128,0.15)', label: 'blocked' },
|
||||
};
|
||||
|
||||
export function Artboard({ id, label, x, y, width, height, renderUrl }: ArtboardProps) {
|
||||
export function Artboard({ id, label, x, y, width, height, renderUrl, route }: ArtboardProps) {
|
||||
const {
|
||||
selectedArtboardId, selectArtboard, workspaceId, projectId,
|
||||
setArtboardLive, setFiberRoot, selectComponent,
|
||||
setArtboardLive, setFiberRoot, selectComponent, setComponentStyles,
|
||||
selectedComponentId,
|
||||
} = useCanvas();
|
||||
const selected = selectedArtboardId === id;
|
||||
@@ -49,10 +60,23 @@ export function Artboard({ id, label, x, y, width, height, renderUrl }: Artboard
|
||||
}, [id, setFiberRoot, setArtboardLive]);
|
||||
|
||||
const handleComponentSelected = useCallback((nodeId: string) => {
|
||||
if (!nodeId) {
|
||||
// Empty nodeId = COMPONENT_DESELECTED — clear selection + styles
|
||||
selectComponent(null, null);
|
||||
setComponentStyles(null);
|
||||
return;
|
||||
}
|
||||
if (!localFiberRoot) return;
|
||||
const node = findFiberNode(localFiberRoot, nodeId);
|
||||
selectComponent(nodeId, node ?? null);
|
||||
}, [localFiberRoot, selectComponent]);
|
||||
}, [localFiberRoot, selectComponent, setComponentStyles]);
|
||||
|
||||
const handleComponentStylesUpdate = useCallback((_nodeId: string, styles: Record<string, string>) => {
|
||||
// Only update styles if this artboard is the one currently selected
|
||||
if (selectedArtboardId === id) {
|
||||
setComponentStyles(styles);
|
||||
}
|
||||
}, [id, selectedArtboardId, setComponentStyles]);
|
||||
|
||||
// ── Drag to reposition ─────────────────────────────────────────────────────
|
||||
const isDragging = useRef(false);
|
||||
@@ -318,12 +342,13 @@ export function Artboard({ id, label, x, y, width, height, renderUrl }: Artboard
|
||||
<>
|
||||
<LiveArtboard
|
||||
id={id}
|
||||
src={renderUrl}
|
||||
src={buildSrc(renderUrl, route)}
|
||||
width={width}
|
||||
height={height}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onFiberTreeUpdate={handleFiberUpdate}
|
||||
onComponentSelected={handleComponentSelected}
|
||||
onComponentStylesUpdate={handleComponentStylesUpdate}
|
||||
/>
|
||||
<SelectionOverlay
|
||||
artboardId={id}
|
||||
|
||||
Reference in New Issue
Block a user