Canvas V2 — Adaptive Layout Engine + Extension Components
Created: February 25, 2026 Status: Phase B DONE, Phase C1+C2 in design Depends on: Phase A (canvas persistence + AI silence) — DONE, Phase B (named canvases) — DONE
Core idea: The canvas is a blank page. Your words are a pen — you speak or type, and the AI draws on the screen. Layouts adapt dynamically to the content and how it's best displayed. Components are installable extensions running in JSRuntime. Chat is minimized by default — the canvas IS the interface.
Vision
"Parole is a pen to the screen"
Today's Morphee canvas is a spatial freeform with draggable cards. It works, but it's one layout for all content. The real vision is:
-
You talk. The AI renders. The chat input is a pen — not a chat thread. When you say "show me this week's schedule", the AI doesn't reply with text. It draws a calendar on the canvas. When you say "create a project dashboard", it assembles sections, cards, and charts into a page layout.
-
Layouts adapt to content. A single task? A floating card. A project overview? A full-page layout with hero banner, kanban board, and progress bars. A shopping list? A compact sidebar list. The AI picks the best layout for what it's showing, and the layout changes as the content evolves.
-
Components are extensions. Core components (card, list, table, form, calendar, kanban) ship built-in. But anyone can create new component types — a Gantt chart, a mind map, a recipe card, a music player — and install them as extensions. They run in JSRuntime (Level 2 in the Runtime Hierarchy).
-
Chat takes the smallest place. The canvas fills the screen. Chat is a collapsible drawer (already implemented). Users visit the conversation history when they want context, but the canvas is the primary experience.
Architecture
How It Maps to the Runtime Hierarchy
VectorRouter → WasmRuntime → PythonRuntime → JSRuntime → LLMRuntime
↑
Canvas components
live here (Level 2)
Canvas components are JSRuntime artifacts in the compilation chain:
| Level | What | Canvas example |
|---|---|---|
| 0 | Raw knowledge (LLMRuntime) | "Show me a project dashboard" — AI reasons about what to show |
| 1 | Structured skill (PythonRuntime) | dashboard_skill — fetches tasks, calendar, memory, calls frontend tools |
| 2 | Canvas component (JSRuntime) | <KanbanBoard>, <Calendar>, <ProgressBar> — live, interactive React |
| 3 | Compiled extension (WasmRuntime) | Hypothetical: a WASM component that runs layout computation natively |
Canvas stays an Integration. The AI interacts with it through tool calls (frontend__show_*, frontend__update, frontend__dismiss). The extension system adds new component types to the registry, but the Integration contract doesn't change.
Component Registry as Extension Point
Today, the component registry is hardcoded:
// Current: static registry
const REGISTRY = {
card: CardRenderer,
list: ListRenderer,
table: TableRenderer,
// ...
}
With extensions, it becomes dynamic:
// Future: dynamic registry with core + extensions
interface ComponentRegistration {
type: string // e.g. "gantt", "mindmap", "recipe"
renderer: React.ComponentType<RendererProps>
icon: React.ComponentType // for toolbox/palette
category: 'core' | 'extension'
source: string // "built-in" | extension ID
schema: JSONSchema // props validation
defaultProps: Record<string, unknown>
layoutHints: LayoutHint // how this component prefers to be laid out
events?: EventDeclaration[] // what events it can emit
}
Core components (ship with Morphee):
card— Generic content card with title, body, actionslist— Interactive list with optional checkboxes, drag reordertable— Data table with sortable columnsform— Input form with field types (text, number, date, select, etc.)choices— Quick selection (polls, yes/no, preference picks)actions— Button groups for quick operationsprogress— Progress bars and indicatorscalendar— Event calendar with month/week/day viewskanban— Columnar board with draggable cardsvideo— Video player with captionssection_header— Large text with optional subtitle and backgroundhero_banner— Full-width image/gradient with overlay textdivider— Horizontal rule with optional labeltext_block— Rich Markdown block (no border, flows inline in page layout)
Extension components (installed or user-created):
- Gantt chart, mind map, recipe card, music player, code editor, whiteboard, graph/chart, timeline, etc.
- Installed from marketplace or created via the Extension SDK
- Run in JSRuntime sandbox (React component + props schema + event declarations)
Adaptive Layout Engine
Instead of a single freeform layout, the canvas uses an adaptive layout engine that picks the best layout strategy based on content.
Layout Modes
| Mode | When | How |
|---|---|---|
freeform | Default, few components, spatial arrangement matters | Absolute positioning, drag to move, current behavior |
page | Content is sequential/narrative (dashboard, report, landing page) | Vertical flow of sections, like a web page |
grid | Multiple similar items, data-heavy | CSS Grid with configurable columns, responsive |
sidebar | Primary content + auxiliary info | Main area + collapsible side panel |
split | Two related views (comparison, before/after) | Horizontal or vertical split panes |
AI-Driven Layout Selection
The AI doesn't just place components — it chooses the layout:
User: "Show me a project dashboard"
AI thinks:
- Multiple component types (kanban, progress, calendar, card)
- Hierarchical importance (overview first, details below)
- Best layout: "page" with sections
AI renders:
1. set_layout("page")
2. add_section("overview", layout="grid-3")
3. show_progress(..., section="overview")
4. show_card("Team Velocity", section="overview")
5. show_card("Sprint Goals", section="overview")
6. add_section("board", layout="full-width")
7. show_kanban(..., section="board")
8. add_section("schedule", layout="full-width")
9. show_calendar(..., section="schedule")
User: "What's for dinner tonight?"
AI thinks:
- Single piece of info, quick answer
- Best layout: "freeform" with one card
AI renders:
1. show_card("Tonight's Dinner", body="Pasta carbonara - recipe in memory")
Dynamic Layout Transitions
Layouts can change as content evolves:
- User asks simple question → freeform with one card
- User asks follow-up → second card appears, still freeform
- User asks for deep dive → AI switches to page layout, organizes existing cards into sections
- User asks for comparison → AI switches to split layout
The transition is animated and preserves component state.
Section Model (for page/grid layouts)
Inspired by Divi's Section → Row → Module hierarchy, simplified:
interface CanvasSection {
id: string
label?: string // Optional section title
background?: {
type: 'color' | 'gradient' | 'image'
value: string // CSS color, gradient, or URL
}
padding?: { top: number; bottom: number }
layout: SectionLayout // How children are arranged
childIds: string[] // Ordered component IDs in this section
}
type SectionLayout =
| 'stack' // Vertical stack (default)
| 'grid-2' // 2-column grid
| 'grid-3' // 3-column grid
| 'grid-4' // 4-column grid
| 'flex-row' // Horizontal row, wraps on small screens
| 'sidebar-left' // Left sidebar + main
| 'sidebar-right' // Main + right sidebar
Canvas State Model (extended from Phase A)
interface CanvasState {
id: string
name: string // "Default", "Weekly Plan", "Dashboard"
spaceId: string | null
layoutMode: LayoutMode // 'freeform' | 'page' | 'grid' | 'sidebar' | 'split'
sections?: CanvasSection[] // For page/grid modes
gridCols?: number // For grid mode
components: ComponentSpec[]
positions: Record<string, CanvasPosition> // For freeform mode
minimized: Record<string, boolean>
createdAt: string
updatedAt: string
}
Named Canvases (Phase B)
Each space can have multiple named canvases. A canvas is a named, persistent page with its own layout and components.
Database
CREATE TABLE canvases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL,
space_id UUID,
name TEXT NOT NULL DEFAULT 'Default',
layout_mode TEXT NOT NULL DEFAULT 'freeform',
sections JSONB NOT NULL DEFAULT '[]'::jsonb,
components JSONB NOT NULL DEFAULT '[]'::jsonb,
positions JSONB NOT NULL DEFAULT '{}'::jsonb,
minimized JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
API
GET /api/canvas?space_id=X— list canvases for spacePOST /api/canvas— create named canvasGET /api/canvas/:id— get canvas with full statePUT /api/canvas/:id— update canvas state (debounced auto-save)DELETE /api/canvas/:id— delete canvasPUT /api/canvas/:id/rename— rename canvasPUT /api/canvas/:id/layout— change layout mode
Frontend Integration Tools (AI-facing)
frontend__create_canvas(name, layout_mode?) → creates new canvas, returns ID
frontend__switch_canvas(canvas_id) → switches active canvas
frontend__set_layout(layout_mode, grid_cols?) → changes current canvas layout
frontend__add_section(label?, layout?, background?) → adds section to page/grid canvas
frontend__remove_section(section_id) → removes section
frontend__move_to_section(component_id, section_id, position?) → moves component into section
Canvas Tabs UI
Horizontal tab bar below the space selector:
- Each tab = canvas name, editable on double-click
- "+" button to create new canvas
- Context menu: rename, delete, duplicate
- Active tab highlighted
- Tab overflow: horizontal scroll with arrows
Extension Components (Phase C, JSRuntime)
Extension Component Contract
An extension component is a React component bundled as a JS module that conforms to the ComponentRegistration interface:
// Extension manifest (in .morph/ or extension package)
{
"name": "gantt-chart",
"version": "1.0.0",
"type": "canvas-component",
"component": {
"type": "gantt",
"displayName": "Gantt Chart",
"category": "project-management",
"icon": "gantt-icon.svg",
"schema": {
"type": "object",
"properties": {
"tasks": { "type": "array", "items": { "$ref": "#/defs/GanttTask" } },
"startDate": { "type": "string", "format": "date" },
"endDate": { "type": "string", "format": "date" }
}
},
"defaultProps": { "tasks": [], "view": "month" },
"layoutHints": { "preferredWidth": "full-width", "minHeight": 400 },
"events": [
{ "name": "task_click", "ai_response": true },
{ "name": "task_drag", "ai_response": false },
{ "name": "date_change", "ai_response": true }
]
}
}
Extension Loading
- Extension installed → JS bundle stored in
.morph/extensions/or fetched from registry - On canvas mount →
ExtensionLoaderreads installed component extensions - Dynamic
import()loads the React component - Component registered in the dynamic registry
- AI can now use
frontend__show_gantt(...)— the tool is auto-generated from the schema
JSRuntime Sandbox
Extension components run in a sandboxed environment:
- No direct DOM access outside their container
- No network access — data flows through props (AI provides data via tool calls)
- No localStorage/indexedDB — state managed by the canvas store
- Limited API surface —
onEvent(type, payload)to emit events,useTheme()for styling - Size limits — max 500KB per component bundle
- CSP enforced — Content Security Policy prevents inline scripts
AI Auto-Discovery
When an extension component is installed, the AI automatically learns about it:
- Schema indexed in VectorRouter (
memory_type="component_extension") - Tool definition auto-generated:
frontend__show_{type}with params from schema - System prompt updated: "You can use
frontend__show_ganttto display Gantt charts..." - AI can find extensions via semantic search: "show me a timeline" → matches gantt component
Prompt Engineering for Adaptive Layouts
Updated System Prompt (canvas context)
## Canvas Layout Intelligence
The canvas supports multiple layout modes. Choose the best layout for the content:
- **freeform**: Spatial arrangement. Use for 1-3 standalone components, quick answers, casual content.
- **page**: Vertical sections. Use for dashboards, reports, multi-section content, narratives.
- **grid**: Grid cells. Use for collections of similar items, galleries, data grids.
- **sidebar**: Main + side panel. Use for detail views, navigation + content, filters + results.
- **split**: Side-by-side panes. Use for comparisons, before/after, source/preview.
Guidelines:
- Start with freeform for simple requests. Upgrade to page/grid as content grows.
- When rendering 4+ components, prefer page layout with sections over freeform.
- Group related components in sections (overview, details, actions).
- Use section backgrounds sparingly — they're for visual hierarchy, not decoration.
- If the user asks to "create a page" or "build a dashboard", use page layout.
- Never force a layout — if freeform works, keep it simple.
Current canvas: {layout_mode}, {component_count} components, {section_count} sections
Implementation Phases
Phase B: Named Canvases — DONE (Feb 25, 2026)
- ✅
canvasestable migration + CRUD API (8 endpoints) - ✅
canvasStore.ts— Zustand store for canvas list/CRUD/active selection - ✅
CanvasTabs.tsx— tab bar with inline rename, context menu, delete confirmation - ✅ Canvas.tsx — two-step load, tabs integration
- ✅ AI tools:
create_canvas,switch_canvas+ ToolCallCard handler - ✅ i18n keys (EN/FR)
- ✅ 25 new tests
Phase C1: Adaptive Layouts (~4-6 hours)
- Section model + types
PageLayout.tsx— vertical section rendererGridLayout.tsx— CSS grid rendererFreeformLayout.tsx— extract from current Canvas.tsxSidebarLayout.tsx— main + side panel- Canvas.tsx — layout mode switching
- New frontend tools:
set_layout,add_section,move_to_section - New core renderers:
SectionHeader,HeroBanner,Divider,TextBlock - Prompt updates — AI layout selection intelligence
- Tests
Phase C2: Extension Components (~6-8 hours)
ComponentRegistrationinterface + dynamic registryExtensionLoader— loads JS bundles from .morph/extensions/- JSRuntime sandbox (CSP, container isolation)
- Auto-tool generation from component schema
- VectorRouter indexing for component extensions
- Extension Developer Guide update
- Example extension: custom chart component
- Tests
Inspiration Sources
| Source | What we take | What we skip |
|---|---|---|
| Divi (WordPress) | Section → Row → Module hierarchy, visual settings panels, responsive previews | Drag-and-drop builder UI (our "builder" is the AI), WYSIWYG editing |
| Craft.js | Flat node tree, <Element> resolver pattern, user component config, JSON serialization | React-DnD complexity, useEditor/useNode hooks (too tightly coupled) |
| Puck | Slot-based nesting, plugin architecture, clean component config | Visual editor chrome (we don't need sidebars/toolbars — AI is the tool) |
| GrapeJS | Block system, trait panels, device previews | Full HTML/CSS builder scope (way beyond our needs) |
Key difference: In all these builders, a human drags and configures components. In Morphee, the AI is the builder. The user describes what they want; the AI assembles the layout. This means we need the layout engine and component registry from these builders, but NOT the visual builder UI (no drag handles, no property panels, no block palettes). The AI replaces all of that.
Open Questions
- Layout transitions — How smooth should freeform → page transitions be? Animate components into section positions, or instant switch?
- User override — Can users manually switch layout modes, or is it AI-only? (Recommendation: AI picks, user can override via chat: "switch to grid layout")
- Section editing — Can users drag components between sections? Or only via chat? (Recommendation: drag for power users, chat for everyone)
- Extension marketplace — When do we ship the component extension marketplace? (Recommendation: after V2.0, with the broader extension marketplace)
- Mobile layouts — How do page/grid layouts adapt on mobile? (Recommendation: single-column stack on narrow viewports, sections collapse to accordion)
Last Updated: February 25, 2026