anniedrawing 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/NOTICE +292 -0
- package/README.md +162 -0
- package/dist/agent/describe.d.ts +12 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/place.d.ts +4 -0
- package/dist/agent/query.d.ts +3 -0
- package/dist/agent/toolDefs.d.ts +343 -0
- package/dist/board.d.ts +193 -0
- package/dist/core/catalog.d.ts +22 -0
- package/dist/core/clipboard.d.ts +2 -0
- package/dist/core/defaults.d.ts +19 -0
- package/dist/core/doc.d.ts +2 -0
- package/dist/core/ids.d.ts +5 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +1 -0
- package/dist/core/item.d.ts +25 -0
- package/dist/core/links.d.ts +21 -0
- package/dist/core/locks.d.ts +3 -0
- package/dist/core/migrate.d.ts +5 -0
- package/dist/core/paste.d.ts +27 -0
- package/dist/core/schema.d.ts +644 -0
- package/dist/core/types.d.ts +313 -0
- package/dist/docs/agents-repository.md +56 -0
- package/dist/docs/agents.md +157 -0
- package/dist/docs/api.md +258 -0
- package/dist/docs/board-js.md +345 -0
- package/dist/docs/code-of-conduct.md +11 -0
- package/dist/docs/contributing.md +35 -0
- package/dist/docs/decisions.md +107 -0
- package/dist/docs/extensions.md +122 -0
- package/dist/docs/format.md +79 -0
- package/dist/docs/index.html +993 -0
- package/dist/docs/license.txt +21 -0
- package/dist/docs/llms-full.txt +345 -0
- package/dist/docs/llms.txt +90 -0
- package/dist/docs/mcp.md +97 -0
- package/dist/docs/notice.txt +292 -0
- package/dist/docs/nunito-OFL.txt +93 -0
- package/dist/docs/releasing.md +38 -0
- package/dist/docs/security.md +33 -0
- package/dist/favicon.svg +1 -0
- package/dist/fellow.d.ts +7 -0
- package/dist/fellow.js +2 -0
- package/dist/fellow.js.map +1 -0
- package/dist/geo/box.d.ts +13 -0
- package/dist/geo/index.d.ts +5 -0
- package/dist/geo/picker.d.ts +25 -0
- package/dist/geo/router.d.ts +11 -0
- package/dist/geo/vec.d.ts +8 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/input/autosave.d.ts +2 -0
- package/dist/input/cursors.d.ts +8 -0
- package/dist/input/measure.d.ts +6 -0
- package/dist/input/urlPaste.d.ts +18 -0
- package/dist/kinds/index.d.ts +2 -0
- package/dist/kinds/registry.d.ts +45 -0
- package/dist/porter/index.d.ts +5 -0
- package/dist/porter/indexedPng.d.ts +13 -0
- package/dist/porter/json.d.ts +3 -0
- package/dist/porter/png.d.ts +10 -0
- package/dist/porter/svg.d.ts +9 -0
- package/dist/shared/board-Bya0O1I7.js +2 -0
- package/dist/shared/board-Bya0O1I7.js.map +1 -0
- package/dist/shared/cards-DWgSVE54.js +2 -0
- package/dist/shared/cards-DWgSVE54.js.map +1 -0
- package/dist/shared/defaults-C-aC_B4z.js +2 -0
- package/dist/shared/defaults-C-aC_B4z.js.map +1 -0
- package/dist/shared/describe-DaWyf3qV.js +3 -0
- package/dist/shared/describe-DaWyf3qV.js.map +1 -0
- package/dist/shared/paste-C22i8o-g.js +2 -0
- package/dist/shared/paste-C22i8o-g.js.map +1 -0
- package/dist/shared/picker-Dv_i4iNP.js +2 -0
- package/dist/shared/picker-Dv_i4iNP.js.map +1 -0
- package/dist/shared/ui-Cay6AVqT.js +2 -0
- package/dist/shared/ui-Cay6AVqT.js.map +1 -0
- package/dist/shared/urlPaste-DN4JtgeB.js +2 -0
- package/dist/shared/urlPaste-DN4JtgeB.js.map +1 -0
- package/dist/stage/cards.d.ts +2 -0
- package/dist/stage/index.d.ts +3 -0
- package/dist/stage/itemView.d.ts +22 -0
- package/dist/stage/lens.d.ts +26 -0
- package/dist/stage/paint.d.ts +23 -0
- package/dist/stage/presence.d.ts +50 -0
- package/dist/stage/stage.d.ts +60 -0
- package/dist/style.css +2 -0
- package/dist/ui/icons.d.ts +2 -0
- package/dist/ui/index.d.ts +16 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/version.d.ts +3 -0
- package/llms-full.txt +345 -0
- package/llms.txt +90 -0
- package/package.json +120 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Read [AGENTS.md](agents-repository.md), run the demo, and keep each change to one understandable improvement.
|
|
4
|
+
|
|
5
|
+
Use Node.js 24 or newer to develop this repository, and `npm ci`. `npm run dev` starts the demo on port 5173. For a code change, run `npm run check`. For interface changes, also build the demo and run the browser checks. Document the behavior you changed, the checks you actually ran, and any known limits. Add a regression test when it protects an invariant.
|
|
6
|
+
|
|
7
|
+
## Independent work and dependency policy
|
|
8
|
+
|
|
9
|
+
Write original code from AnnieDrawing's requirements and public platform documentation. Do not copy, port, translate, or paraphrase another drawing editor's source, even if you believe its license permits it. Do not keep another editor's source open while implementing equivalent functionality here. Tell reviewers where any third-party material came from and add its notice before submission.
|
|
10
|
+
|
|
11
|
+
Dependencies must have a reviewed, compatible license. The default library is limited to five runtime dependencies and 100 KiB gzipped, including CSS. Optional integrations belong in separate entry points. Development tools should not leak into the runtime. Fonts, fixtures, and icons need compatible provenance too.
|
|
12
|
+
|
|
13
|
+
## Developer Certificate of Origin
|
|
14
|
+
|
|
15
|
+
By signing a commit you certify the [Developer Certificate of Origin 1.1](https://developercertificate.org/): you created the contribution or have the right to submit it under this project's license, and you understand that the contribution and sign-off are public.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
git commit -s
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The trailer must identify the contributor who can make that certification:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Signed-off-by: Your Name <you@example.com>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Never add someone else's sign-off or invent a contributor identity. CI checks for a sign-off on non-merge pull request commits. Maintainers may ask for provenance details. An automated check cannot verify authorship itself.
|
|
28
|
+
|
|
29
|
+
## Review expectations
|
|
30
|
+
|
|
31
|
+
Preserve the JSON format and the atomic operations contract. Keep the headless model free of DOM access. Test rollback, undo, and connector bindings when changing document operations. Test keyboard, cancellation, and focus when changing input. Check light and dark themes and narrow screens for visible UI work. Explain new public APIs in `docs/api.md` and keep AI-facing documentation current.
|
|
32
|
+
|
|
33
|
+
Comment only when a name does not make the contract obvious. Keep comments short. Prefer a regression test for an invariant (atomic apply, undo, connector detach, locks, input cancellation) over a test that restates the implementation.
|
|
34
|
+
|
|
35
|
+
Follow the [code of conduct](code-of-conduct.md). Share minimal reproductions without secrets or private drawings. Report security concerns to pau@frontierz.com using [SECURITY.md](security.md). Do not post an exploit and sensitive data in public.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Design decisions
|
|
2
|
+
|
|
3
|
+
## 2026-09-20: Public unfurl hosts
|
|
4
|
+
|
|
5
|
+
Unfurl is a same-browser GET of a user-pasted URL, not a metadata proxy. `isPublicHttpUrl` is a hostname check: it rejects loopback, RFC1918, link-local, ULA, IPv4-mapped IPv6, NAT64, multicast, and names that start with a private IPv4. It does not look up DNS. Preview images from Open Graph go through the same check so a public page cannot point the card at a private URL. The local Vite `/__ad-unfurl` plugin additionally resolves A/AAAA records and refuses private answers. Hosts that need stricter policy pass `unfurl: false` or their own function.
|
|
6
|
+
|
|
7
|
+
## 2026-09-20: Indexed PNG for vision snapshots
|
|
8
|
+
|
|
9
|
+
Vision models need a raster of the screen, not SVG markup. `board_snapshot` writes a labeled viewport PNG. It defaults to a 32-color indexed encoding and a 240 KiB budget so ID labels stay sharp without JPEG ringing. The same defaults apply to `board.export('png', { labels: true })`. The tool schema advertises those defaults. The Export control and unlabeled `export('png')` stay truecolor. Pass `colors` (2–256) for a different palette. PNG `maxBytes` without `colors` tries 32 colors before shrinking the scale.
|
|
10
|
+
|
|
11
|
+
## 2026-09-20: Agent duplicate create ids
|
|
12
|
+
|
|
13
|
+
An `agent:` `apply` that creates an item id already in the document, or repeated in the same batch, stores `id_1` then `_2` instead of failing the batch. Same-batch `place`, parent, and connector refs follow the stored ids. `ID_REMAPPED` is a warning. `result.created` is the stored ids; `get` with the id you sent returns the older item. User and API origins still reject duplicates so a host that supplies ids gets a hard error.
|
|
14
|
+
|
|
15
|
+
## 2026-09-20: Public contacts and safer host defaults
|
|
16
|
+
|
|
17
|
+
Security and community reports go to pau@frontierz.com. `package.json` records the GitHub repository, issues URL, and that mailbox. `createBoard` does not register `window.__anniedrawing` unless `exposeGlobal` is true; the local demo opts in. `sanitizeHTML` is probed at create time so identity functions fail closed. npm publication remains a separate maintainer action.
|
|
18
|
+
|
|
19
|
+
## 2026-09-20: Session revision, not catalog version
|
|
20
|
+
|
|
21
|
+
`revision` counts committed session transactions (`apply`, `undo`, `redo`). It is not `CATALOG_VERSION`, not a history index, and not a field in compact `.annie` JSON. `changesSince(since)` reads a 500-slice session log so hosts can poll without re-reading the scene. `kindsSince` still answers “what can I create that I did not know about.” `load()` and `clear()` reset the session counter, stamps, and log.
|
|
22
|
+
|
|
23
|
+
## 2026-09-20: Hidden agent history
|
|
24
|
+
|
|
25
|
+
`agentHistory: 'hidden'` makes default `undo` / `redo` and `canUndo` / `canRedo` walk only non-`agent:` entries. Origin-specific `undo({ origin })` still targets that origin. Agent batches emit `change`, increment `revision`, and appear in `changesSince`. Ordinary keyboard undo follows the default walk.
|
|
26
|
+
|
|
27
|
+
## 2026-09-20: Host vocabulary aliases
|
|
28
|
+
|
|
29
|
+
`rectangle` and `arrow`, a small set of English color names, and string connector ends are accepted on `apply`. The document stores `rect`, `connector`, palette tokens, and `{ item, side }` endpoints. Aliases are not catalog kinds and not format fields. `query` expands the same kind aliases.
|
|
30
|
+
|
|
31
|
+
## 2026-09-19: Kind catalog version, not per-item revisions
|
|
32
|
+
|
|
33
|
+
When a built-in kind is added or its create/read contract changes, increment `CATALOG_VERSION` and set that kind's `since` to the new number. `kindsSince(since?)` returns those entries. Omit `since` or pass `0` to list every built-in kind. This is the check for "what can I create that I did not know about." It does not stamp a version on every item in a drawing. The current board is `describe`, `read`, `get`, and `query`. The `.annie` format version stays a document-schema number.
|
|
34
|
+
|
|
35
|
+
## 2026-09-19: Independent DOM and SVG editor
|
|
36
|
+
|
|
37
|
+
Build from AnnieDrawing's own requirements with standard TypeScript, DOM, SVG, and Pointer Events. The document is a readable tree. The renderer derives its output from that tree. No other drawing editor's implementation is used.
|
|
38
|
+
|
|
39
|
+
## 2026-09-19: Page coordinates and atomic edits
|
|
40
|
+
|
|
41
|
+
All item positions use page coordinates, including nested children. Array order defines stacking. A single validated operation batch is the write boundary and the undo unit. This keeps tool calls compact and prevents an agent from leaving half a requested change behind.
|
|
42
|
+
|
|
43
|
+
## 2026-09-19: Five direct runtime dependencies
|
|
44
|
+
|
|
45
|
+
Use signals, pressure strokes, a spatial index, validation, and short IDs. The schema converter is installed during development and bundled into the separate agent entry point. HTML sanitization is supplied by the application. CI checks dependency licenses and the main bundle budget.
|
|
46
|
+
|
|
47
|
+
## 2026-09-19: 100 KiB gzipped default bundle
|
|
48
|
+
|
|
49
|
+
The default `anniedrawing` editor plus its CSS stays under 100 KiB gzipped. Optional entry points, async chunks, and peer sanitizers stay outside that total. The check is `scripts/check-size.mjs`.
|
|
50
|
+
|
|
51
|
+
## 2026-09-19: Frontierz color and typography
|
|
52
|
+
|
|
53
|
+
Use the Frontierz palette so the demo matches the rest of the family. Use Nunito in the demo chrome and rounded system fallbacks in the library. The project does not depend on the brand's commercial font. The font is self-hosted and attributed under OFL 1.1. Board text defaults to the handwritten Comic Sans-like stack (`hand`).
|
|
54
|
+
|
|
55
|
+
## 2026-09-19: Demo without a backend
|
|
56
|
+
|
|
57
|
+
The demo works without a backend or account. The development server uses Vite's default port 5173. Portable JSON files are the durable exchange format. Browser autosave does not synchronize users or devices.
|
|
58
|
+
|
|
59
|
+
## 2026-09-19: Live agent integration is explicit
|
|
60
|
+
|
|
61
|
+
Readable summaries, precise JSON, labeled snapshots, and small operation batches are the integration surfaces. Browser inspection is available through a removable global hook. The separate MCP package uses stdio and an explicitly attached, token-authenticated localhost bridge. It does not synchronize multiple users.
|
|
62
|
+
|
|
63
|
+
## 2026-09-19: Pages and versioned migration
|
|
64
|
+
|
|
65
|
+
Version 2 uses pages in the document, scopes, and editing API. Version 1 documents migrate at load time, preserving content and IDs. New saves use the current format. Page creation and switching live in bottom tabs, with overflow and contextual rename and delete actions. Manual alignment and distribution remain ordinary undoable editing actions.
|
|
66
|
+
|
|
67
|
+
## 2026-09-19: Host chrome options
|
|
68
|
+
|
|
69
|
+
`createBoard` appearance is `'light'`, `'dark'`, or `'auto'` for the system. The AnnieDrawing header menu and the Export control are independent `ui` flags. An imported board shows the menu and offers PNG and SVG. AnnieDoc (`json`) is opt-in. The repository demo passes all three formats. Programmatic `board.export` is not limited by the Export menu.
|
|
70
|
+
|
|
71
|
+
## 2026-09-19: Toolbar and inspector layout
|
|
72
|
+
|
|
73
|
+
Keep primary drawing tools on the sidebar: eraser after hand, image after sticky note, and line and arrow inside Shapes. Do not hide those tools behind a More overflow. On phones, including landscape, and on short tablet-width hosts, the tools move to a bottom bar and hand stays in the board menu so that bar remains tappable, unless a host hides that menu. A short desktop host keeps the tool sidebar and inspector vertically centered and tightens chrome padding and icons instead of pinning those bars to the top. The selection inspector is vertically centered like the tool sidebar. Export downloads the current page. An imported board offers PNG and SVG; AnnieDoc is opt-in through `ui.export`. The local demo offers all three. The AnnieDrawing control opens the same kind of menu for document, appearance, documentation, and GitHub, with the package version at the bottom, and hosts may hide it. Show item-specific style controls only when something is selected, with palettes opened on demand and compact line and text controls visible beside the selection. The inspector does not repeat the selected kind as a title. Page management remains in the bottom bar. Positioning and rotation follow the user's pointer. Shift constrains the move axis or resize proportions.
|
|
74
|
+
|
|
75
|
+
## 2026-09-19: MIT licensing and the named package
|
|
76
|
+
|
|
77
|
+
Provide MIT licensing, dependency attribution, DCO checks, contribution and agent guides, verification workflows, and release steps. The npm package name is `anniedrawing`.
|
|
78
|
+
|
|
79
|
+
## 2026-09-19: Direct controls and ordinary groups
|
|
80
|
+
|
|
81
|
+
Use anchored context menus for element and page actions, keep the active page visible when tabs overflow, and put opacity and locking directly in the inspector. Only groups own children. Retired frames convert on import into groups containing ordinary rectangle and text items plus their original children. Preserve IDs and bindings. Remove clipping so no hidden outside content is lost.
|
|
82
|
+
|
|
83
|
+
## 2026-09-19: Selectable locks
|
|
84
|
+
|
|
85
|
+
Locking protects interactive editing while leaving selection and unlocking accessible. Disable editing for an entire selection if any part is locked. Protect group descendants and group operations that would affect a locked child. Unlock clears the locks affecting the selection in one undoable action. Browser operations labeled `user` reject protected item mutations atomically with `LOCKED`. Explicit unlock patches are allowed. Programmatic API and headless operations retain their existing ability to edit locked items, so this is an editing aid, not authorization. A newly applied lock cancels an active pointer or text edit.
|
|
86
|
+
|
|
87
|
+
## 2026-09-19: Agent origin cursor
|
|
88
|
+
|
|
89
|
+
Show browser agent additions through one lilac cursor that enters from the nearest edge, curves to each placement, and leaves after the reveal. Reuse the native cursor silhouette, keep the overlay below controls, and ignore pointer events. The cursor has no name by default. `apply` may pass `agentName` to show one. A 220 ms opacity and 97-100% scale reveal adds a small settling motion. Connectors use opacity alone to preserve path geometry.
|
|
90
|
+
|
|
91
|
+
Keep the document authoritative and synchronous. The arrival is an optional renderer presentation after a successful agent-origin `apply`, with no new history entries, schema fields, or delays for agents. Group descendants arrive together. The cursor visits the first eight on-screen non-connector placements, then the remaining batch arrives together, including connectors and off-screen items held back until that dump. Never pan on behalf of the animation; sample the camera each frame so a person's pan and zoom stay in sync. After the cursor leaves, fit created ids on the current page when any sit outside the viewport. Consecutive batches share one cursor and one recenter. Pending items are not hittable, so a person can keep selecting and editing visible work. Reduced motion, hidden tabs, document replacement, page changes, and destruction clear pending effects. Removing a pending item drops it from the walk without revealing the rest.
|
|
92
|
+
|
|
93
|
+
## 2026-09-19: Pasted videos and link cards
|
|
94
|
+
|
|
95
|
+
Paste classifies a single URL locally. YouTube and Vimeo become a `video` item whose iframe `src` is built from a parsed video id, using youtube-nocookie.com or player.vimeo.com. Other `http(s)` URLs become a `link` card. Image URLs become `image` items. These are first-class kinds, not stored HTML, so they do not depend on a sanitizer.
|
|
96
|
+
|
|
97
|
+
A pasted link card appears immediately from the URL. The browser may then fetch that same URL, without credentials, to fill title, description, and image. That fetch is a user-initiated paste. Hosts can pass `unfurl: false` or their own function. Do not send pasted URLs to a third-party metadata service. Agents do not unfurl.
|
|
98
|
+
|
|
99
|
+
The inspector and context menu can change that stored href later. The item kind stays the same: a video still requires a YouTube or Vimeo URL. A link card resets to a hostname fallback, then unfurls when the host allows it, using the same user-initiated fetch as paste.
|
|
100
|
+
|
|
101
|
+
Notes, images, videos, and link cards share one 12px corner and one soft shadow so media on the board reads as one family. Video players keep pointer events off until a double-click, matching HTML items, so a clip can be moved without hitting play.
|
|
102
|
+
|
|
103
|
+
Portable SVG and PNG cannot keep a live player or a working Open control. Export those items as still cards: a video still with provider, title, and URL; a link card with preview, title, URL, and description. SVG wraps the card in a safe `http(s)` link. Do not write iframes, foreignObject, or a raw user URL into `iframe.src`.
|
|
104
|
+
|
|
105
|
+
## 2026-09-19: Publish-facing library surface
|
|
106
|
+
|
|
107
|
+
Package entry points export the documented API, not every helper in a folder. Host page chrome (`html`, `body`, `#app`) belongs in the demo stylesheet; `anniedrawing/style.css` styles the editor host only. Draft merges, copies, translation, and connector detach share one implementation. Comments mark non-obvious contracts. Tests cover invariants, not restated implementation.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Extensions
|
|
2
|
+
|
|
3
|
+
Custom kinds extend the built-in renderer. They do not appear in `kindsSince` until they are added to the built-in catalog. Applications that embed HTML must pass an explicit sanitizer.
|
|
4
|
+
|
|
5
|
+
## Sanitized HTML
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import DOMPurify from 'dompurify';
|
|
9
|
+
const board = createBoard(host, {
|
|
10
|
+
sanitizeHTML: (html) =>
|
|
11
|
+
DOMPurify.sanitize(html, {
|
|
12
|
+
USE_PROFILES: { html: true },
|
|
13
|
+
FORBID_TAGS: ['iframe', 'form'],
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The host and its custom callbacks remain responsible for rendering policy. A sanitizer is not a sandbox. `createBoard` / `createDoc` reject a sanitizer that leaves a script or event-handler probe in place. For agent-origin changes, the model runs the supplied sanitizer before committing HTML. Without one it escapes markup. The renderer runs the supplied sanitizer again at the DOM boundary. Mount IDs from agent changes are removed. Keep interactive embeds narrow. Do not store executable callbacks in `.annie` JSON.
|
|
19
|
+
|
|
20
|
+
Built-in `video` and `link` items are not HTML. They store an `href` and optional title, description, and preview media. The renderer builds the YouTube or Vimeo iframe and the link card. Pasting those URLs into an `html` item does not produce the same result.
|
|
21
|
+
|
|
22
|
+
## Custom kinds
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createBoard, defineKind, registerKind } from 'anniedrawing';
|
|
26
|
+
|
|
27
|
+
const badge = defineKind({
|
|
28
|
+
kind: 'badge',
|
|
29
|
+
summarize: (item) => `Badge: ${item.text?.value ?? item.name ?? item.id}`,
|
|
30
|
+
paint(view, item) {
|
|
31
|
+
view.shape.innerHTML = '';
|
|
32
|
+
view.element.style.border = '2px solid #8F93F9';
|
|
33
|
+
view.element.style.borderRadius = '999px';
|
|
34
|
+
view.element.style.background = '#8F93F91a';
|
|
35
|
+
view.text.textContent = item.text?.value ?? 'Badge';
|
|
36
|
+
},
|
|
37
|
+
toSVG(item, context) {
|
|
38
|
+
return `<rect x="0" y="0" width="${item.w}" height="${item.h}" rx="${item.h / 2}" fill="#8F93F91a" stroke="#8F93F9"/>`;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
const board = createBoard(host, { kinds: [badge] });
|
|
42
|
+
board.apply([
|
|
43
|
+
{
|
|
44
|
+
op: 'add',
|
|
45
|
+
item: {
|
|
46
|
+
kind: 'badge',
|
|
47
|
+
x: 40,
|
|
48
|
+
y: 40,
|
|
49
|
+
w: 140,
|
|
50
|
+
h: 60,
|
|
51
|
+
text: { value: 'Ready' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Kind names must start with a letter and contain only letters, digits, `_`, or `-`. Kind callbacks are trusted application code. Escape user text with `context.escape` when composing SVG strings. Use DOM `textContent` in the browser. Shape callbacks have `element`, `shape`, `text`, and the current document. Implement `toSVG` for anything that must survive portable SVG export. Unknown kinds remain saved and render as placeholders when the application has not registered them.
|
|
58
|
+
|
|
59
|
+
`defineKind` returns the definition. Pass it in `createBoard({ kinds })` or `createDoc({ kinds })` for that instance. `registerKind` installs a default for boards created afterward and returns an uninstall function:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { registerKind } from 'anniedrawing';
|
|
63
|
+
const uninstall = registerKind(badge);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Custom kind `defaults` supply item properties before validation. A kind may also provide a Valibot `schema` for its extra fields. That schema runs during imports and operation validation. Pass the same kind definitions to `createDoc(..., { kinds })` for headless validation.
|
|
67
|
+
|
|
68
|
+
A local-coordinate `outline(item)` supplies one or more `{ points: [{ x, y }], closed }` shapes for hit testing and attached connector routing in the live board, exports, and headless deletion. Explicit normalized connector anchors stay exact. Automatic and named-side endpoints meet the custom outline. `summarize(item)` supplies the accessible item label.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import * as v from 'valibot';
|
|
72
|
+
|
|
73
|
+
const ratedBadge = defineKind({
|
|
74
|
+
...badge,
|
|
75
|
+
kind: 'rated-badge',
|
|
76
|
+
defaults: { w: 140, h: 60, rating: 3 },
|
|
77
|
+
schema: v.looseObject({
|
|
78
|
+
kind: v.literal('rated-badge'),
|
|
79
|
+
rating: v.pipe(v.number(), v.minValue(1), v.maxValue(5)),
|
|
80
|
+
}),
|
|
81
|
+
outline: (item) => ({
|
|
82
|
+
closed: true,
|
|
83
|
+
points: [
|
|
84
|
+
{ x: 0, y: 0 },
|
|
85
|
+
{ x: item.w, y: 0 },
|
|
86
|
+
{ x: item.w, y: item.h },
|
|
87
|
+
{ x: 0, y: item.h },
|
|
88
|
+
],
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Keep custom schemas and callbacks in trusted application code. The document stores the kind name and JSON fields. Opening a file does not import a module or run an embedded callback.
|
|
94
|
+
|
|
95
|
+
## Mount, paint, and handles
|
|
96
|
+
|
|
97
|
+
`mount(view)` creates custom content once and may return a cleanup function. Cleanup runs when the kind changes or the view is destroyed. Custom `mount` and `paint` callbacks own the shape, text, and auxiliary content. The stage owns the wrapper's position, size, opacity, and accessible label. `paint(view, item, changed)` receives a set containing the changed groups: `transform`, `geometry`, `style`, `text`, or `content`.
|
|
98
|
+
|
|
99
|
+
A custom handle uses unrotated coordinates inside the item. Return a patch from `dragHandle`. The editor previews it as a draft and commits one undoable operation on release.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const adjustableBadge = defineKind({
|
|
103
|
+
...badge,
|
|
104
|
+
kind: 'adjustable-badge',
|
|
105
|
+
handles: (item) => [
|
|
106
|
+
{
|
|
107
|
+
id: 'width',
|
|
108
|
+
x: item.w,
|
|
109
|
+
y: item.h / 2,
|
|
110
|
+
label: 'Badge width',
|
|
111
|
+
cursor: 'ew-resize',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
dragHandle: (item, handleId, point, { shift }) => {
|
|
115
|
+
if (handleId !== 'width') return {};
|
|
116
|
+
const width = Math.max(40, point.x);
|
|
117
|
+
return shift ? { w: width, h: (item.h * width) / Math.max(1, item.w) } : { w: width };
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Custom controls expose `data-ad-custom-handle` with the handle ID and `data-ad-handle="custom:<id>"` for inspection. Canceling a drag discards the draft and leaves the document unchanged.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Document format
|
|
2
|
+
|
|
3
|
+
A document is UTF-8 JSON. The file extension is `.annie`. The MIME type is `application/vnd.anniedrawing+json`.
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"format": "anniedrawing",
|
|
8
|
+
"version": 2,
|
|
9
|
+
"meta": { "title": "Architecture" },
|
|
10
|
+
"pages": [
|
|
11
|
+
{
|
|
12
|
+
"id": "p_main",
|
|
13
|
+
"name": "Page 1",
|
|
14
|
+
"background": "paper",
|
|
15
|
+
"items": [
|
|
16
|
+
{
|
|
17
|
+
"id": "i_note",
|
|
18
|
+
"kind": "note",
|
|
19
|
+
"x": 120,
|
|
20
|
+
"y": 100,
|
|
21
|
+
"w": 200,
|
|
22
|
+
"h": 180,
|
|
23
|
+
"text": { "value": "Note" },
|
|
24
|
+
"style": { "fill": "moss" }
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"media": {}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The tree is `pages` to `items` to `children`. Only groups have children. Array order is back to front. IDs are unique across the document.
|
|
34
|
+
|
|
35
|
+
Child coordinates are in page space. They are not relative to the parent. Distances use CSS pixels at zoom 1. Positive y points down. Rotation is clockwise degrees around the item's center.
|
|
36
|
+
|
|
37
|
+
## Item fields
|
|
38
|
+
|
|
39
|
+
Common fields: `id`, `kind`, `x`, `y`, `w`, `h`, `rotation`, `style`, `text`, `name`, `locked`, `hidden`, and application-owned `data`. Generated IDs use `i_`, `p_`, and `m_` prefixes. Callers may supply their own IDs.
|
|
40
|
+
|
|
41
|
+
The model expands defaults internally and omits default style flags on serialization. Compact JSON also omits `x`, `y`, `w`, and `h` on connectors. `data` is JSON metadata. The editor does not execute it.
|
|
42
|
+
|
|
43
|
+
Built-in kinds: `rect`, `ellipse`, `diamond`, `line`, `connector`, `path`, `text`, `note`, `image`, `video`, `link`, `group`, and `html`. Unknown kinds are kept and shown as a placeholder. Load and save do not drop custom content.
|
|
44
|
+
|
|
45
|
+
Default sizes when `w` and `h` are omitted: rect and ellipse 180×110, diamond 160×140, line 180×0, connector 0×0, path 0×0, text 200×48, note 200×180, image 240×180, video 480×270, link 220×200, group 0×0, html 240×160.
|
|
46
|
+
|
|
47
|
+
`text` has a plain `value`, horizontal `align` (`start`, `center`, `end`), vertical `valign` (`top`, `middle`, `bottom`), `size` (`s`, `m`, `l`, `xl`, or a number from 1 to 1000), and `font` (`sans`, `serif`, `mono`, `hand`). Omitted `font` is `hand`, the handwritten Comic Sans-like stack. `autoWidth: true` on a browser `text` item stores measured width and height.
|
|
48
|
+
|
|
49
|
+
Style fields: `stroke`, `strokeWidth` (0 to 1000), `dash` (`solid`, `dashed`, `dotted`), `fill`, `fillMode` (`solid`, `tint`, `hatch`), `corner`, and `opacity` (0 to 1). Notes, images, videos, and link cards default to a 12px corner. `hatch` is a document and export fill. The inspector does not offer it.
|
|
50
|
+
|
|
51
|
+
`href` is an `http(s)` URL on `video` and `link` items. `description` is optional plain text for a link card. A `video` href must be a YouTube or Vimeo watch or embed URL. The renderer derives the player address from the parsed id. It does not store iframe markup. `html` holds markup for `html` items. `mount` is an internal id the renderer assigns; do not treat it as a file field to author.
|
|
52
|
+
|
|
53
|
+
Named colors: `ink`, `slate`, `coral`, `amber`, `moss`, `teal`, `sky`, `violet`, `rose`, and `paper`. These map to Frontierz colors and theme-aware foreground and background values. Ordinary CSS colors are also accepted. `fill: 'none'` draws a hollow shape. `apply` also accepts host color names (`black`, `grey`, `gray`, `blue`, `light-blue`, `green`, `light-green`, `red`, `light-red`, `orange`, `yellow`, `violet`, `light-violet`); compact JSON writes the token, not the alias. Kind aliases `rectangle` and `arrow` are operation input only. They are not format kinds.
|
|
54
|
+
|
|
55
|
+
## Connectors and paths
|
|
56
|
+
|
|
57
|
+
A connector has `from` and `to`. Each stored end is either `{ "item": "i_note", "side": "right" }` or a free `{ "x": 100, "y": 200 }` point. `apply` also accepts a string item id, stored as `{ item, side: "auto" }`. `side` is `auto`, `top`, `right`, `bottom`, or `left`. An attached endpoint can use a normalized `anchor: [0, 0.5]`. Routes are `straight`, `elbow`, or `curve`. Heads accept `none`, `arrow`, or `dot`. Optional `waypoints` are page-space `[x, y]` points between the ends. When a target disappears, that endpoint becomes a free point at its last position in the same atomic edit.
|
|
58
|
+
|
|
59
|
+
Line and freehand `points` are relative to the item's `x` and `y`. Freehand points may include pressure: `[x, y, pressure]`. `closed: true` closes a path. Item IDs and connector bindings survive moves and export or import.
|
|
60
|
+
|
|
61
|
+
## Media and HTML
|
|
62
|
+
|
|
63
|
+
Image items reference a key in the top-level `media` map. A record contains `{ mime, w, h, src }`. Allowed `mime` values are `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/avif`, and `image/svg+xml`. The toolbar `addImage` helper accepts the raster types under 10 MB; it does not accept SVG files. Embedded data URLs make files portable. Remote images require an explicit allowed origin. PNG export can still fail if that server does not permit cross-origin loading. Link cards may reuse `media` for a preview image. Optional `crop` is a box in media pixel space.
|
|
64
|
+
|
|
65
|
+
HTML is an optional application integration. Plain text is safe by construction. Rendered HTML requires an explicit sanitizer. `video` and `link` items are structured fields rendered with trusted DOM. They are not HTML items. Custom kind mount callbacks live in trusted application code. They are never executable code stored in the file. See [security](security.md).
|
|
66
|
+
|
|
67
|
+
## Versions
|
|
68
|
+
|
|
69
|
+
The integer `version` belongs to the document format. It is independent of the package version, the built-in kind catalog (`CATALOG_VERSION` / `kindsSince`), and the session `revision` used by `changesSince`. Version 2 is the current format and uses `pages`. New examples, saves, and exports use this form. Session revision is not a file field.
|
|
70
|
+
|
|
71
|
+
The loader accepts version 1 drawings with `sheets` and migrates them to version 2 `pages` before validation. It keeps page and item IDs, item trees, coordinates, connector references, media, and custom page names. Default names such as `Sheet 1` become `Page 1`. Existing `.annie` files and browser autosaves remain readable. The next save writes version 2. Migration reads the original object without mutating it.
|
|
72
|
+
|
|
73
|
+
The editing API uses `page.add`, `page.set`, `page.remove`, the `page` operation property, and the `page` read and export scope. Legacy field names belong only to imported version 1 data. Unsupported future versions are rejected.
|
|
74
|
+
|
|
75
|
+
Older drawings that contain `frame` items load as ordinary groups. The loader keeps the container ID, children, metadata, and connector bindings, adds a rectangle for the background and text for the heading, and drops clipping. Content that sat outside a container becomes visible. New drawings use groups for nesting and rectangles for visible boxes.
|
|
76
|
+
|
|
77
|
+
## Export
|
|
78
|
+
|
|
79
|
+
`board.export` writes JSON (AnnieDoc), SVG, PNG, JPEG, or WebP. PNG may be truecolor or an indexed palette (`colors`, 2–256). The editor Export control downloads the current page as PNG, SVG, or AnnieDoc. Programmatic export is not limited by that menu. See [API reference](api.md#export).
|