How the Instatic Site Shell and Multi-Breakpoint Canvas Editor Work
Instatic separates site-level configuration into a "site shell" stored in the site table, then renders multiple responsive iframes—one per breakpoint—to provide a real-time, WYSIWYG editing canvas that mirrors the final published output.
The CoreBunch/Instatic repository implements a novel architecture for visual site building that decouples global site configuration from individual page content. Understanding how the Instatic site shell and multi-breakpoint canvas editor work requires examining how the system stores breakpoints, injects scoped CSS, and manages per-viewport interactions. This architecture enables designers to edit responsive layouts across multiple device widths simultaneously without leaving the canvas.
Understanding the Site Shell Architecture
The site shell (also called a site document) acts as the top-level configuration container for every Instatic project. Unlike page-specific content, which lives in separate database tables, the shell maintains global settings that affect the entire site.
Site Document Schema and Storage
In the CoreBunch/Instatic codebase, the site shell resides in the site table and stores the site name, breakpoint definitions, global settings, style-rule registry, files, explorer organization, package JSON, and runtime configuration. Individual pages, visual components (VCs), and saved layouts are stored separately in data_rows, allowing the shell to load independently of content-heavy resources.
The shell schema is defined in src/core/page-tree/siteDocument.ts, which exports the SiteShell type and references BreakpointSchema from src/core/page-tree/breakpoint.ts. Required fields include id, name, breakpoints, createdAt, and updatedAt, while optional entries like styleRules, files, and settings receive sensible defaults during parsing.
Parsing and Validation
When loading a project, parseSiteDocument in src/core/page-tree/siteDocument.ts performs tolerant parsing of the shell data. This function ensures required fields exist while providing fallbacks for optional configuration. After parsing, runShellPostChecks in src/core/persistence/validate.ts validates cross-cutting invariants, such as verifying that style-rule selectors reference existing classes in the registry.
Loading the Editor Interface
The admin interface follows a shell-first loading strategy that prioritizes immediate UI rendering while deferring heavy editor components.
SitePage Entry Point
The SitePage component at src/admin/pages/site/SitePage.tsx serves as the entry point for the /admin/site route. This component immediately renders the admin UI shell and bridges Model Context Protocol (MCP) tool calls to the live editor through useMcpWorkspaceBridge.
// src/admin/pages/site/SitePage.tsx – entry point for the editor
export function SitePage() {
// Bridge MCP tool calls to the live editor
useMcpWorkspaceBridge('site', executeAgentTool);
// Consume pending actions (e.g. create‑page or create‑visual‑component)
useEffect(() => {
const unsubscribe = useEditorStore.subscribe(() => {
// …run pending actions once the store is hydrated
});
return unsubscribe;
}, []);
// Render the shell; heavy canvas work is lazy‑loaded
return <AdminCanvasLayout />;
}
Lazy Loading Heavy Components
Rather than blocking the initial render with drag-and-drop libraries, CodeMirror instances, and canvas panels, the SitePage delegates heavy work to AdminCanvasLayout. This component lazy-loads the multi-breakpoint canvas, ensuring the admin shell appears instantly while editor assets load in the background.
Multi-Breakpoint Canvas Implementation
The canvas editor implements a multi-breakpoint preview system that renders a separate iframe for each breakpoint defined in the site shell. This approach allows designers to view and edit responsive layouts across multiple device widths simultaneously.
Breakpoint Definitions and Data Attributes
Breakpoints are defined in the breakpoints array within the site shell, conforming to BreakpointSchema. Each iframe receives a unique data-breakpoint-id attribute corresponding to its breakpoint identifier. The CSS injection logic in src/core/publisher/frameworkCss.ts and src/core/publisher/cssCollector.ts uses these attributes to scope generated rules to specific viewports, ensuring that styles applied in the editor match the final published output exactly.
Responsive Chrome and Preview Switching
The responsiveChrome.ts module at src/admin/pages/site/layout/responsiveChrome.ts builds the UI controls that allow users to switch between breakpoint previews. This component synchronizes the editor state with the chosen viewport and updates the data-breakpoint-id attribute on the active iframe.
// src/admin/pages/site/layout/responsiveChrome.ts – breakpoint preview UI
export function ResponsiveChrome() {
const breakpoints = useEditorStore(state => state.site?.breakpoints ?? []);
const [activeBp, setActiveBp] = useState<string | null>(null);
return (
<div className="breakpoint-picker">
{breakpoints.map(bp => (
<button
key={bp.id}
className={cn({ active: activeBp === bp.id })}
onClick={() => setActiveBp(bp.id)}
>
{bp.label ?? `${bp.width}px`}
</button>
))}
</div>
);
}
Per-Breakpoint Iframe Rendering
All breakpoints share the same underlying page tree structure, but the editor renders per-breakpoint snapshots. This design allows designers to see how a page looks at each size without leaving the canvas context. The canvas component at src/admin/pages/site/canvas/index.ts creates these iframes and mounts the editor panels within each viewport container.
CSS Injection and WYSIWYG Preview
To guarantee that the canvas preview matches the published site, Instatic injects site-wide style rules directly into each iframe. This includes reusable media-query conditions stored in site.conditions. The publisher helpers in src/core/publisher/frameworkCss.ts and cssCollector.ts generate CSS scoped to [data-breakpoint-id] selectors, creating a live preview environment that faithfully mirrors the final static build.
Editor Store and Canvas Interactions
State management for the multi-breakpoint editor flows through a centralized store that provides breakpoint context to all canvas operations.
State Management with useEditorStore
The useEditorStore hook in src/admin/pages/site/store/store.ts holds the loaded site object and tracks the active breakpoint ID. When the store hydrates, it assembles the full SiteDocument by combining the shell with pages, VCs, and layouts from data_rows.
Keyboard Shortcuts and Drag Operations
Canvas hooks consume the active breakpoint from the editor store to adjust their behavior:
useCanvasKeyboardShortcuts(src/admin/pages/site/canvas/useCanvasKeyboardShortcuts.ts) binds navigation keys using the current breakpoint to adjust selection behavior.useCanvasReorderDrag(src/admin/pages/site/canvas/useCanvasReorderDrag.ts) enables drag-and-drop ordering of canvas nodes while respecting the active breakpoint's layout constraints.
// src/admin/pages/site/canvas/useCanvasKeyboardShortcuts.ts – per‑breakpoint shortcuts
export function useCanvasKeyboardShortcuts() {
const activeBreakpoint = useEditorStore(state => state.activeBreakpointId);
useHotkeys('mod+z', () => undo(), [activeBreakpoint]);
// …other shortcuts that respect the current breakpoint context
}
Real-Time Collaboration and Persistence
User interactions flow through the editor store into a CRDT (Conflict-free Replicated Data Type) layer powered by Yjs. This layer streams edits to the server-side collaboration socket, enabling real-time co-editing across multiple users and viewports. Changes mutate the in-memory page tree immediately, with eventual persistence handled by the CRDT synchronization protocol.
Summary
- The site shell stores global configuration (breakpoints, styles, settings) in the
sitetable, separate from page content indata_rows. parseSiteDocumentinsrc/core/page-tree/siteDocument.tsvalidates the shell with tolerant parsing, whilerunShellPostChecksensures style-rule integrity.SitePage.tsxrenders the admin interface immediately, lazy-loading heavy canvas components viaAdminCanvasLayout.- The multi-breakpoint canvas renders separate iframes for each breakpoint defined in the shell, using
data-breakpoint-idattributes for CSS scoping. responsiveChrome.tsprovides the breakpoint switching UI, while canvas hooks likeuseCanvasKeyboardShortcutsrespect the active viewport context.- CSS injection via
frameworkCss.tsandcssCollector.tsensures the canvas preview matches the final published output. - All changes persist through a Yjs-based CRDT layer, enabling real-time collaboration.
Frequently Asked Questions
What is a site shell in Instatic?
The site shell is a site document stored in the site table that contains top-level configuration including the site name, breakpoint definitions, global settings, style-rule registry, and file organization. It loads independently of pages and visual components, allowing the editor to initialize quickly while content-heavy resources load separately from data_rows.
How does Instatic handle multiple breakpoints in the canvas editor?
Instatic renders a separate iframe for each breakpoint defined in the site shell's breakpoints array. Each iframe receives a data-breakpoint-id attribute, and CSS injection logic in frameworkCss.ts scopes styles to these attributes. The responsiveChrome.ts component provides UI controls for switching between breakpoints, while canvas hooks adjust their behavior based on the active breakpoint ID stored in useEditorStore.
Where are site configurations stored versus page content?
According to the CoreBunch/Instatic source code, site configurations (the shell) reside in the site table, while individual pages, visual components, and saved layouts are stored in data_rows. This separation allows the site shell to manage global settings like breakpoints and style rules independently of specific page content.
How does the editor ensure the canvas preview matches the published site?
The editor injects site-wide style rules—including reusable media-query conditions from site.conditions—directly into each breakpoint iframe using src/core/publisher/frameworkCss.ts and cssCollector.ts. These modules generate CSS scoped to data-breakpoint-id attributes, creating a WYSIWYG preview that mirrors the final static build's output exactly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →