Add tldraw file skill
This commit is contained in:
375
tldraw-file/SKILL.md
Normal file
375
tldraw-file/SKILL.md
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
---
|
||||||
|
name: tldraw-file
|
||||||
|
description: Create, repair, validate, or generate valid `.tldr` files that tldraw can open. Use when the user asks for a tldraw diagram, `.tldr` file, corrupted tldraw repair, or a diagram they can open in tldraw.
|
||||||
|
argument-hint: "[diagram request, file path, or validation target]"
|
||||||
|
user-invocable: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# tldraw File
|
||||||
|
|
||||||
|
Use this skill when the user asks to create, repair, validate, or generate a tldraw document/file.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- "Make me a tldraw diagram"
|
||||||
|
- "Create a `.tldr` file"
|
||||||
|
- "tldraw says this file is corrupted"
|
||||||
|
- "Generate a diagram I can open in tldraw"
|
||||||
|
|
||||||
|
## Core Rule
|
||||||
|
|
||||||
|
Do not hand-wave validity. A `.tldr` file must be validated with tldraw's own schema/importer, not just `JSON.parse`.
|
||||||
|
|
||||||
|
A file can be valid JSON and still be corrupted to tldraw.
|
||||||
|
|
||||||
|
## Temporary Setup
|
||||||
|
|
||||||
|
If the current project does not already depend on `tldraw`, install it in `/tmp/opencode`, not in the user's repo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Work outside the repo
|
||||||
|
bun init -y
|
||||||
|
bun add tldraw
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `/tmp/opencode` as the working directory for validation scripts.
|
||||||
|
|
||||||
|
## `.tldr` File Shape
|
||||||
|
|
||||||
|
A current `.tldr` JSON file has this top-level shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tldrawFileFormatVersion": 1,
|
||||||
|
"schema": {
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"sequences": {
|
||||||
|
"com.tldraw.store": 5,
|
||||||
|
"com.tldraw.asset": 1,
|
||||||
|
"com.tldraw.camera": 1,
|
||||||
|
"com.tldraw.document": 2,
|
||||||
|
"com.tldraw.instance": 26,
|
||||||
|
"com.tldraw.instance_page_state": 5,
|
||||||
|
"com.tldraw.page": 1,
|
||||||
|
"com.tldraw.instance_presence": 6,
|
||||||
|
"com.tldraw.pointer": 1,
|
||||||
|
"com.tldraw.shape": 4,
|
||||||
|
"com.tldraw.user": 1,
|
||||||
|
"com.tldraw.asset.image": 6,
|
||||||
|
"com.tldraw.asset.video": 5,
|
||||||
|
"com.tldraw.asset.bookmark": 2,
|
||||||
|
"com.tldraw.shape.arrow": 8,
|
||||||
|
"com.tldraw.shape.bookmark": 2,
|
||||||
|
"com.tldraw.shape.draw": 4,
|
||||||
|
"com.tldraw.shape.embed": 4,
|
||||||
|
"com.tldraw.shape.frame": 1,
|
||||||
|
"com.tldraw.shape.geo": 11,
|
||||||
|
"com.tldraw.shape.group": 0,
|
||||||
|
"com.tldraw.shape.highlight": 3,
|
||||||
|
"com.tldraw.shape.image": 5,
|
||||||
|
"com.tldraw.shape.line": 5,
|
||||||
|
"com.tldraw.shape.note": 12,
|
||||||
|
"com.tldraw.shape.text": 4,
|
||||||
|
"com.tldraw.shape.video": 4,
|
||||||
|
"com.tldraw.binding.arrow": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"records": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Records must be an array of valid tldraw records. Each record needs at least:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "shape:example",
|
||||||
|
"typeName": "shape"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Actual records must also satisfy their shape-specific schema.
|
||||||
|
|
||||||
|
## Required Base Records
|
||||||
|
|
||||||
|
Include at least:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gridSize": 10,
|
||||||
|
"name": "",
|
||||||
|
"meta": {},
|
||||||
|
"id": "document:document",
|
||||||
|
"typeName": "document"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "page:page",
|
||||||
|
"name": "Page",
|
||||||
|
"index": "a1",
|
||||||
|
"meta": {},
|
||||||
|
"typeName": "page"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every shape should have:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"parentId": "page:page",
|
||||||
|
"index": "a2",
|
||||||
|
"typeName": "shape"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rich Text
|
||||||
|
|
||||||
|
Use tldraw rich text for labels.
|
||||||
|
|
||||||
|
Helper shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Label"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For compatibility, also include the legacy `text` prop with the same plain text.
|
||||||
|
|
||||||
|
## Geo Shape Template
|
||||||
|
|
||||||
|
Use geo shapes for most boxes. They are easier than notes and less fragile.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"x": 100,
|
||||||
|
"y": 100,
|
||||||
|
"rotation": 0,
|
||||||
|
"isLocked": false,
|
||||||
|
"opacity": 1,
|
||||||
|
"meta": {},
|
||||||
|
"id": "shape:box",
|
||||||
|
"type": "geo",
|
||||||
|
"props": {
|
||||||
|
"geo": "rectangle",
|
||||||
|
"w": 260,
|
||||||
|
"h": 90,
|
||||||
|
"color": "blue",
|
||||||
|
"labelColor": "black",
|
||||||
|
"fill": "solid",
|
||||||
|
"dash": "solid",
|
||||||
|
"size": "m",
|
||||||
|
"font": "sans",
|
||||||
|
"align": "middle",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"growY": 0,
|
||||||
|
"url": "",
|
||||||
|
"scale": 1,
|
||||||
|
"richText": {
|
||||||
|
"type": "doc",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "My box"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text": "My box"
|
||||||
|
},
|
||||||
|
"parentId": "page:page",
|
||||||
|
"index": "a2",
|
||||||
|
"typeName": "shape"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Text Shape Template
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"x": 100,
|
||||||
|
"y": 40,
|
||||||
|
"rotation": 0,
|
||||||
|
"isLocked": false,
|
||||||
|
"opacity": 1,
|
||||||
|
"meta": {},
|
||||||
|
"id": "shape:title",
|
||||||
|
"type": "text",
|
||||||
|
"props": {
|
||||||
|
"color": "black",
|
||||||
|
"size": "xl",
|
||||||
|
"font": "sans",
|
||||||
|
"textAlign": "start",
|
||||||
|
"autoSize": true,
|
||||||
|
"w": 900,
|
||||||
|
"scale": 1,
|
||||||
|
"richText": {
|
||||||
|
"type": "doc",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Diagram Title"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text": "Diagram Title"
|
||||||
|
},
|
||||||
|
"parentId": "page:page",
|
||||||
|
"index": "a1",
|
||||||
|
"typeName": "shape"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arrow Shape Template
|
||||||
|
|
||||||
|
Arrows require `labelColor`. Missing it can make tldraw report the file as corrupted.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"x": 400,
|
||||||
|
"y": 200,
|
||||||
|
"rotation": 0,
|
||||||
|
"isLocked": false,
|
||||||
|
"opacity": 1,
|
||||||
|
"meta": {},
|
||||||
|
"id": "shape:arrow",
|
||||||
|
"type": "arrow",
|
||||||
|
"props": {
|
||||||
|
"kind": "arc",
|
||||||
|
"color": "black",
|
||||||
|
"labelColor": "black",
|
||||||
|
"fill": "none",
|
||||||
|
"dash": "solid",
|
||||||
|
"size": "m",
|
||||||
|
"arrowheadStart": "none",
|
||||||
|
"arrowheadEnd": "arrow",
|
||||||
|
"bend": 0,
|
||||||
|
"start": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"x": 120,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"labelPosition": 0.5,
|
||||||
|
"font": "sans",
|
||||||
|
"scale": 1,
|
||||||
|
"richText": {
|
||||||
|
"type": "doc",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "label"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text": "label"
|
||||||
|
},
|
||||||
|
"parentId": "page:page",
|
||||||
|
"index": "a3",
|
||||||
|
"typeName": "shape"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Valid Colors
|
||||||
|
|
||||||
|
For `color` and `labelColor`, use only known tldraw colors:
|
||||||
|
|
||||||
|
- `black`
|
||||||
|
- `grey`
|
||||||
|
- `light-violet`
|
||||||
|
- `violet`
|
||||||
|
- `blue`
|
||||||
|
- `light-blue`
|
||||||
|
- `yellow`
|
||||||
|
- `orange`
|
||||||
|
- `green`
|
||||||
|
- `light-green`
|
||||||
|
- `light-red`
|
||||||
|
- `red`
|
||||||
|
- `white`
|
||||||
|
|
||||||
|
Invalid or missing `labelColor` can corrupt the file.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Avoid note shapes unless you know the current schema. They require additional props and are more version-sensitive. Prefer geo rectangles with `fill: solid` or `fill: semi`.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
First validate JSON syntax:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun -e "const f='/path/to/file.tldr'; const data=await Bun.file(f).text(); JSON.parse(data); console.log('valid json')"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then validate with tldraw's store/schema:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun -e "import { createTLStore } from 'tldraw'; const file=JSON.parse(await Bun.file('/path/to/file.tldr').text()); const snapshot={store:Object.fromEntries(file.records.map(r=>[r.id,r])), schema:file.schema}; try { const store=createTLStore({snapshot}); console.log('ok records', store.allRecords().length); } catch(e){ console.error(e); process.exit(1)} process.exit(0)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally validate with the same importer path tldraw uses for `.tldr` files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun -e "import { createTLStore } from 'tldraw'; import { parseTldrawJsonFile } from './node_modules/tldraw/dist-esm/lib/utils/tldr/file.mjs'; const json=await Bun.file('/path/to/file.tldr').text(); const schema=createTLStore().schema; const result=parseTldrawJsonFile({json, schema}); console.log(result.ok ? 'parse ok' : result.error); process.exit(result.ok ? 0 : 1)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run importer validation from the temp directory where tldraw is installed, for example `/tmp/opencode`.
|
||||||
|
|
||||||
|
## Common Corruption Causes
|
||||||
|
|
||||||
|
- File is valid JSON but does not match tldraw schema.
|
||||||
|
- Missing `labelColor` on arrow shapes.
|
||||||
|
- Missing required shape props for note shapes.
|
||||||
|
- Old schema sequence numbers copied from another tldraw version.
|
||||||
|
- Invalid color names.
|
||||||
|
- Shape `parentId` points to a missing page.
|
||||||
|
- Duplicate record IDs.
|
||||||
|
- Records object used instead of records array in `.tldr` file format.
|
||||||
|
- `richText` missing where current schemas expect it.
|
||||||
|
|
||||||
|
## Recommended Workflow
|
||||||
|
|
||||||
|
1. Create or update `.tldr` with `apply_patch`.
|
||||||
|
2. Run JSON validation.
|
||||||
|
3. Run `createTLStore({ snapshot })` validation.
|
||||||
|
4. Run `parseTldrawJsonFile` validation.
|
||||||
|
5. If tldraw says corrupted, trust tldraw and inspect the validation error path.
|
||||||
|
6. Fix the specific schema path, not just the JSON.
|
||||||
|
|
||||||
|
## Practical Diagram Advice
|
||||||
|
|
||||||
|
- Use geo rectangles for sections and entities.
|
||||||
|
- Use text for headings.
|
||||||
|
- Use simple arrow shapes for flow.
|
||||||
|
- Keep IDs stable and readable: `shape:admin-review`, `shape:stream-category`.
|
||||||
|
- Use indexed records like `a1`, `a2`, `a3`.
|
||||||
|
- Keep diagrams editable rather than pixel-perfect.
|
||||||
Reference in New Issue
Block a user