173 lines
5.1 KiB
Markdown
173 lines
5.1 KiB
Markdown
# 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)
|
||
```tsx
|
||
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)
|
||
```typescript
|
||
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
|
||
|
||
- [x] FolkShape base class with drag/resize/rotate
|
||
- [x] FolkMarkdown (markdown editing)
|
||
- [x] FolkWrapper (standardized header/actions)
|
||
- [x] FolkArrow (shape connections)
|
||
- [x] CommunitySync (Automerge integration)
|
||
- [x] Server with WebSocket sync
|
||
- [x] 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
|
||
|
||
1. All 28 shapes ported to FolkJS web components
|
||
2. Real-time collaboration working via Automerge
|
||
3. Mobile touch support
|
||
4. Presence cursors showing other users
|
||
5. No React dependencies in runtime
|
||
6. Bundle size < 500KB (vs ~2MB current)
|