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:

// 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 site table, separate from page content in data_rows.
  • parseSiteDocument in src/core/page-tree/siteDocument.ts validates the shell with tolerant parsing, while runShellPostChecks ensures style-rule integrity.
  • SitePage.tsx renders the admin interface immediately, lazy-loading heavy canvas components via AdminCanvasLayout.
  • The multi-breakpoint canvas renders separate iframes for each breakpoint defined in the shell, using data-breakpoint-id attributes for CSS scoping.
  • responsiveChrome.ts provides the breakpoint switching UI, while canvas hooks like useCanvasKeyboardShortcuts respect the active viewport context.
  • CSS injection via frameworkCss.ts and cssCollector.ts ensures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →