5.1 KiB
5.1 KiB
Canvas-Website to FolkJS Migration Plan
Overview
This document outlines the transition from canvas-website (React + tldraw) to rspace-online (FolkJS web components). The goal is to simplify the architecture while preserving core functionality.
Architecture Comparison
| Aspect | canvas-website (tldraw) | rspace-online (FolkJS) |
|---|---|---|
| Framework | React 18 + tldraw v3 | Web Components (Lit-style) |
| State | tldraw store + Jotai atoms | Reactive properties + Automerge |
| Sync | Cloudflare Durable Objects | WebSocket + Automerge CRDT |
| Shapes | BaseBoxShapeUtil classes | FolkShape subclasses |
| Styling | CSS-in-JS / CSS modules | CSS in template literals |
| Hooks | React hooks (9 shared) | Class methods + utilities |
Key Simplifications
1. No React
- Before: JSX, useState, useEffect, useCallback, useMemo
- After: Template literals, reactive properties, event listeners
2. No tldraw Abstraction Layer
- Before: BaseBoxShapeUtil, editor.updateShape(), shape migrations
- After: FolkShape base class, direct property updates, Automerge patches
3. Simpler State Management
- Before: tldraw store → Automerge → tldraw store (bidirectional sync)
- After: FolkShape → Automerge → FolkShape (direct sync via CommunitySync)
4. Web-Native APIs
- Before: React context providers for auth, notifications, file system
- After: Custom events, localStorage, native browser APIs
Shape Porting Pattern
tldraw Shape (Before)
export class MyShapeUtil extends BaseBoxShapeUtil<MyShape> {
static type = 'my-shape'
getDefaultProps(): MyShape['props'] {
return { w: 300, h: 200, content: '' }
}
component(shape: MyShape) {
const { content } = shape.props
const handleChange = useCallback((e) => {
this.editor.updateShape({ id: shape.id, props: { content: e.target.value } })
}, [shape.id])
return (
<StandardizedToolWrapper shape={shape}>
<textarea value={content} onChange={handleChange} />
</StandardizedToolWrapper>
)
}
}
FolkJS Shape (After)
export class FolkMyShape extends FolkShape {
static tagName = 'folk-my-shape'
#content = ''
get content() { return this.#content }
set content(value: string) {
this.#content = value
this.requestUpdate('content')
this.dispatchEvent(new CustomEvent('content-change', { detail: { content: value } }))
}
override createRenderRoot() {
const root = super.createRenderRoot()
// Add UI to shadow DOM
const wrapper = document.createElement('div')
wrapper.innerHTML = `
<div class="header" data-drag>
<span>My Shape</span>
<button class="close-btn">×</button>
</div>
<div class="content">
<textarea></textarea>
</div>
`
// Event listeners...
return root
}
}
Migration Phases
Phase 1: Foundation (4 shapes)
Status: To Do
- folk-slide (passive display)
- folk-chat (real-time chat)
- folk-google-item (data display)
- folk-piano (interactive music)
Phase 2: Core Data (4 shapes)
Status: To Do
- folk-embed (URL embeds)
- folk-markdown (already done!)
- folk-calendar (date picker + events)
- folk-map (Mapbox/MapLibre collaborative maps)
Phase 3: AI Integration (4 shapes)
Status: To Do
- folk-image-gen (fal.ai Flux)
- folk-video-gen (WAN 2.1)
- folk-prompt (LLM execution)
- folk-transcription (Whisper)
Phase 4: Advanced (5+ shapes)
Status: To Do
- folk-video-chat (Daily.co)
- folk-obs-note (Obsidian sync)
- folk-holon (H3 geo-indexing)
- folk-workflow-block (visual workflows)
- folk-zine-generator (AI zine creation)
Shared Utilities to Port
| React Hook | FolkJS Equivalent | Status |
|---|---|---|
| useMaximize | maximizeShape() utility | ✅ Done |
| usePinnedToView | PinnedViewManager class | ✅ Done |
| useCalendarEvents | CalendarService class | To Do |
| useWhisperTranscription | WhisperService class | To Do |
| useLiveImage | LiveImageService class | To Do |
Already Completed
- FolkShape base class with drag/resize/rotate
- FolkMarkdown (markdown editing)
- FolkWrapper (standardized header/actions)
- FolkArrow (shape connections)
- CommunitySync (Automerge integration)
- Server with WebSocket sync
- Subdomain routing for communities
External Dependencies
Keep (MCP or direct API)
- fal.ai (image/video gen) - via MCP
- Gemini (text gen) - via MCP
- MapLibre GL (maps)
- Daily.co (video chat)
- Whisper API (transcription)
Replace/Simplify
- MDXEditor → simpler markdown renderer (already done)
- tldraw → FolkJS (in progress)
- React contexts → custom events + services
Remove (tldraw-specific)
- BaseBoxShapeUtil
- Shape migrations
- tldraw editor commands
- tldraw store subscriptions
Success Criteria
- All 28 shapes ported to FolkJS web components
- Real-time collaboration working via Automerge
- Mobile touch support
- Presence cursors showing other users
- No React dependencies in runtime
- Bundle size < 500KB (vs ~2MB current)