Projection Mutation Layer (Shadow DOM)
The Projection Mutation Layer is the isolated Shadow DOM where user HTML is rendered. It leverages the browser’s native layout engine for high-performance text wrapping, Flexbox, CSS Grid, and all CSS layout calculations.
How It Works
Managed by ShadowMount (src/shadow-mount.ts), the Projection Mutation Layer:
- Creates an open Shadow Root (
mode: 'open') on the container element - Injects a reset stylesheet (
SHADOW_RESET_CSS) to isolate styles - Wraps each content node in a light wrapper (
.canvus-node-wrapper) - Monitors layout changes via a shared
ResizeObserver
Host Application DOM
└── #canvas-container
├── #shadow-host
│ └── ShadowRoot (open)
│ ├── <style> SHADOW_RESET_CSS </style>
│ ├── <style> User CSS (Tailwind, custom) </style>
│ ├── .canvus-node-wrapper#node-1
│ │ └── <div class="card">User content</div>
│ └── .canvus-node-wrapper#node-2
│ └── <nav>More content</nav>
└── <canvas> (Viewport Surface Layer)Style Isolation
The Shadow DOM boundary ensures that:
- Host styles don’t leak in — Your CMS dashboard, admin panel, or editor shell styles won’t affect the user’s HTML preview
- SDK styles don’t leak out — Wrapper classes (
.canvus-node-wrapper,.canvus-flow-child) stay inside the Shadow Root - User styles are scoped — Custom CSS injected via
injectCSS()is mounted as scoped<style>blocks
JavaScript execution inside the Shadow DOM is disabled by default. The SDK uses standard innerHTML behavior which strips <script> tags. See ADR-0005 for the rationale.
Scoped Stylesheets
To apply custom styling (e.g., Tailwind CSS, brand fonts) to the content inside the Shadow DOM:
// Inject CSS that's scoped to the Shadow Root via the Workspace API
ws.injectCSS(`
.card { padding: 24px; border-radius: 12px; }
.btn { background: #6366f1; color: white; }
`)
// Or load an external stylesheet
await ws.injectCSSLink('https://cdn.example.com/tailwind.css')These styles are mounted as <style> or <link> blocks inside the Shadow Root, never polluting the host application.
Node Wrappers
Each content node is wrapped in a positioning container:
- Root-level nodes: Wrapped in
.canvus-node-wrapperwithposition: absolutefor free-form placement - Child nodes: Wrapped in
.canvus-flow-childto participate in the parent’s CSS layout flow (flex, grid, block)
These wrappers are invisible to the consumer — the Flat String Bridge strips them when exporting HTML.
ResizeObserver Integration
A single shared ResizeObserver monitors all wrapper elements. When the browser recalculates layout (e.g., after a style change or resize), the observer fires and the workspace:
- Reads the new bounding rectangles
- Converts them from screen-space to canvas-space
- Updates the cached bounds in the
NodeTree - Schedules a canvas overlay redraw
The suppressObserver flag in ShadowMount prevents feedback loops during programmatic DOM manipulations. Always use this flag when batch-updating node styles or positions.