diff --git a/tldraw-file/SKILL.md b/tldraw-file/SKILL.md new file mode 100644 index 0000000..a17128d --- /dev/null +++ b/tldraw-file/SKILL.md @@ -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.