tldraw Whiteboard
SkillFiles & storageUse when David wants to create visual whiteboard graphics for YouTube videos using the tldraw SDK, OR inspect/extract content from an existing .tldr board file. Triggers on tldraw, whiteboard, visual diagram, canvas graphic, video visual, board design, "what's in this board", "read this column from the tldr".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the tldraw Whiteboard skill
What this skill tells your AI
The instructions your AI receives, as published by matyasstoch/david-skills in skills/tldraw-whiteboard/SKILL.md and read by ahel’s review.
Two modes:
- Authoring — build new board graphics in
sandbox/tldraw/(App.tsx programmatic shapes). - Inspecting — read content from an existing
.tldrfile (parse JSON, extract richText, isolate a column).
For inspection workflow see references/extract-from-tldr.md.
Authoring Mode
Build visual whiteboard graphics for David's YouTube videos using the tldraw SDK. The app lives at sandbox/tldraw/ and runs locally via npm run dev on port 5173.
Setup
The tldraw app is already scaffolded at sandbox/tldraw/. If it doesn't exist:
npm create vite@latest tldraw -- --template react-ts
cd tldraw
npm install
npm install tldraw
All visuals are built programmatically in src/App.tsx using the tldraw Editor API.
How It Works
Everything happens in the onMount callback. You create shapes programmatically using editor.createShape(). Each "page" or section is offset horizontally (e.g. section 1 at x=0, section 2 at x=1500, section 3 at x=3200). David pans between them during recording.
import { Editor, Tldraw, createShapeId, toRichText } from 'tldraw'
import 'tldraw/tldraw.css'
export default function App() {
const handleMount = (editor: Editor) => {
editor.user.updateUserPreferences({ colorScheme: 'light' })
// Create shapes here...
setTimeout(() => {
editor.zoomToFit({ animation: { duration: 500 } })
}, 100)
}
return (
<div style={{ position: 'fixed', inset: 0, background: '#ffffff' }}>
<Tldraw onMount={handleMount} />
</div>
)
}
Shape Types & API
Text
editor.createShape({
id: createShapeId('mytext'),
type: 'text',
x: 100, y: 100,
props: {
richText: toRichText('HEADLINE TEXT'),
size: 'xl', // 's' | 'm' | 'l' | 'xl'
color: 'black',
},
})
Geo
editor.createShape({
id: createShapeId('mybox'),
type: 'geo',
x: 100, y: 200,
props: {
w: 340, h: 140,
geo: 'rectangle',
color: 'violet',
fill: 'solid',
richText: toRichText('Box content here'),
size: 'm',
font: 'sans',
},
})
Arrows (point-based only — bindings don't work via createShape)
editor.createShape({
id: createShapeId('myarrow'),
type: 'arrow',
x: 300, y: 400,
props: {
color: 'black',
start: { x: 0, y: 0 },
end: { x: 100, y: 150 },
},
})
CRITICAL: Do NOT use binding-based arrows (boundShapeId). They throw validation errors.
Colors
'black' 'white' 'grey' 'light-violet' 'violet' 'blue' 'light-blue' 'green' 'light-green' 'yellow' 'orange' 'light-red' 'red'
Geo Shapes
'rectangle' 'ellipse' 'diamond' 'star' 'hexagon' 'cloud' 'triangle' 'arrow-right' 'arrow-left' 'arrow-up' 'arrow-down'
David's Design Preferences
- Headlines:
size: 'xl', uppercase,color: 'black'(or'white'on dark bg) - Subtitles:
size: 'm', sentence case,color: 'grey' - Section labels:
size: 's', uppercase, colored to match their section - Bullet text inside shapes: 5-10 words max per box.
- No newline characters — they render as literal
\n. Use separate shapes. - Generous spacing — min 80px gap between rows, 40px between columns. Row gap between tiers: 140-180px.
- Multiple sections: Offset each section horizontally by 1500-1700px.
- Always
editor.zoomToFit()at the end with a small delay. - Different colors for different concepts. Mix shapes. Color-code: red=warning, green=positive, violet=vision, orange=question, blue=tools.
- Match arrow colors to target shape's color.
- Default to white bg (
colorScheme: 'light',#ffffff), but always ask David — he may want dark mode (colorScheme: 'dark',#1e1e2e, titles white). - Animations sparingly via
setTimeout. When in doubt, show everything at once. - Keep toolbar visible during recording.
Loading an existing .tldr into the live app
Use parseTldrawJsonFile + getSnapshot + loadSnapshot from the SDK. Put the .tldr in sandbox/tldraw/public/ and fetch() it on mount. Back up App.tsx first — the load-on-mount pattern replaces the current store.
Running
cd sandbox/tldraw
npm run dev
Opens at http://localhost:5173/. After code changes, hit Reset data in the error screen or browser, then refresh.
Process
- David provides section content (bullets from video outline).
- Decide structure pattern (concept map, bullet list, flow, timeline).
- New file per video:
src/videos/<video-name>.tsx. Import fromApp.tsx. - David reviews in browser, gives feedback. Iterate.
Authoring Rules
- Max 10 words per shape.
- Never use newline characters.
- Each section gets its own horizontal space.
- Always use point-based arrows.
- Variety over uniformity.
Inspection Mode
When David asks "what's in this board / column / section" → load the .tldr and parse. See references/extract-from-tldr.md for the full workflow, code snippets, and column-isolation algorithm.
Quick rules:
.tldris JSON. Records atdata['records'], filtertypeName == 'shape'.- Text lives in
props.richText(ProseMirror tree), NOTprops.text. Walk the tree. - Sort by
(y, x)for reading order. - David's boards are columns of vertically-stacked sections. To isolate a column: find its header → find next header in the same x-band → that's your y bound.
- Gaps in y usually mean a tall video/image shape, not missing content.
Signals
- GitHub stars
- 59
- Forks
- 11
- Last commit
- Jun 2026
Advanced
- Catalog kind
- skill
- Gateway key
tldraw-whiteboard- Source
- github.com/matyasstoch/david-skills