# How the Instatic Site Shell and Multi-Breakpoint Canvas Editor Work

> Discover how Instatic's site shell and multi-breakpoint canvas editor offer real-time WYSIWYG editing. See how responsive iframes mirror your published output for seamless site building.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-31

---

**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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/siteDocument.ts), which exports the `SiteShell` type and references `BreakpointSchema` from [`src/core/page-tree/breakpoint.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`.

```tsx
// 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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/frameworkCss.ts) and [`src/core/publisher/cssCollector.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/responsiveChrome.ts) module at [`src/admin/pages/site/layout/responsiveChrome.ts`](https://github.com/CoreBunch/Instatic/blob/main/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.

```tsx
// 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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/frameworkCss.ts) and [`cssCollector.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/useCanvasReorderDrag.ts)) enables drag-and-drop ordering of canvas nodes while respecting the active breakpoint's layout constraints.

```tsx
// 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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/siteDocument.ts) validates the shell with tolerant parsing, while `runShellPostChecks` ensures style-rule integrity.
- [`SitePage.tsx`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/responsiveChrome.ts) provides the breakpoint switching UI, while canvas hooks like `useCanvasKeyboardShortcuts` respect the active viewport context.
- CSS injection via [`frameworkCss.ts`](https://github.com/CoreBunch/Instatic/blob/main/frameworkCss.ts) and [`cssCollector.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/frameworkCss.ts) scopes styles to these attributes. The [`responsiveChrome.ts`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/frameworkCss.ts) and [`cssCollector.ts`](https://github.com/CoreBunch/Instatic/blob/main/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.