@creator-notes/cli 0.2.20 → 0.2.23

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.
@@ -100,7 +100,13 @@ cn canvas add-richtext <canvasId> --content "<content>" [--size small|medium|lar
100
100
  cn canvas add-list <canvasId> --name "<name>" --notes <id1,id2,...> [--view list|grid] [--x <n>] [--y <n>]
101
101
  cn canvas add-link <canvasId> --target <otherCanvasId> [--x <n>] [--y <n>]
102
102
 
103
- # Bulk add multiple notes at once (more efficient than repeated add-node)
103
+ # Place items using a declarative layout (NO x/y math server packs them)
104
+ # This is the PREFERRED way to add multiple items. See "Canvas Layout" below.
105
+ cn canvas place <canvasId> --spec ./layout.json
106
+ cn canvas place <canvasId> --spec-stdin # JSON from stdin
107
+ cn canvas place <canvasId> --spec-inline '<short JSON>' # for tiny specs only
108
+
109
+ # Bulk add with explicit positions (escape hatch — only for pixel-perfect templates)
104
110
  cn canvas bulk-add <canvasId> --notes '[{"noteId":"NOTE-1","x":100,"y":100},{"noteId":"NOTE-2","x":400,"y":100}]'
105
111
 
106
112
  # Move / remove nodes
@@ -230,109 +236,177 @@ cn config set-server <url>
230
236
 
231
237
  ### Canvas Layout
232
238
 
233
- When adding 3+ items to a canvas, **plan all positions upfront** and use `bulk-add` in a single call instead of repeated `add-node` calls.
234
-
235
- #### Grid Cell System (Note Units)
236
-
237
- All canvas positioning uses a **note unit (1u)** as the base measurement. 1u = 400px wide × 180px tall. Every item type has a known width in note units — you must account for this when planning layouts.
238
-
239
- | Item type | Width (px) | Width (u) | Height (px) | Height (u) |
240
- |-----------|-----------|-----------|-------------|------------|
241
- | Note node | 400 | 1.0 | 180 | 1.0 |
242
- | Text node | ~280 | 0.7 | ~40 | 0.2 |
243
- | Richtext small | 560 | 1.4 | varies | varies |
244
- | Richtext medium | 1120 | 2.8 | varies | varies |
245
- | Richtext large | 1680 | 4.2 | varies | varies |
246
- | List node (grid) | 580 | 1.45 | 160+ | varies |
247
- | Canvas link | 400 | 1.0 | 80 | 0.4 |
248
- | Edge label | ~80 | 0.2 | ~14 | — |
249
-
250
- Node center offset from top-left: `(+140, +50)`.
239
+ **Don't compute coordinates by hand. Use `cn canvas place` with a layout spec.**
251
240
 
252
- #### Richtext Nodes
241
+ The server measures every item, packs them into a no-overlap layout, and inserts them in a single batch. You describe **structure** (rows, columns, what's near what) — the server resolves pixels. This is what `cn canvas place` is for, and you should reach for it whenever you're adding more than one item.
253
242
 
254
- Use `add-richtext` for formatted content with headings, lists, bold, italic, and code blocks:
243
+ The call is **best-effort, not transactional** per-item failures are reported in the response (HTTP 207) but the items that succeeded stay on the canvas. Always inspect `items[].error` and `edges[].error` before assuming success.
255
244
 
256
- | Size | Width (px) | Width (u) | Use for |
257
- |------|-----------|-----------|---------|
258
- | `small` (default) | 560 | 1.4 | Annotations, callouts, small formatted notes |
259
- | `medium` | 1120 | 2.8 | Section descriptions, formatted explanations |
260
- | `large` | 1680 | 4.2 | Billboard-scale hero text, full-width banners |
245
+ Use `cn canvas bulk-add` with explicit `x/y` ONLY for pixel-perfect templates (e.g. demo recordings or tutorial walkthroughs that need to match a specific screenshot).
261
246
 
262
- Optional `--color` sets a tinted background (hex, e.g. `#3B82F6` blue, `#F59E0B` amber, `#6366F1` indigo).
247
+ #### The layout DSL
263
248
 
264
- **Content via file (IMPORTANT):** For multi-line richtext, **always use `--content-file`**: write markdown to a temp file first, then pass the path. Inline `--content` does NOT interpret `\n` as newlines bash passes literal backslash-n, which renders as broken text.
249
+ A layout document is `{ root, edges?, origin? }` where `root` is one of four node kinds:
265
250
 
266
- **Richtext height is dynamic** it depends on content length, font size, and padding (colored nodes have much larger padding). The server computes `estimatedHeight` (px) and returns it in the JSON response from `add-richtext` and `bulk-add` (for richtext items).
267
-
268
- **Workflow:** Create richtext nodes first with `--json`, read `estimatedHeight` from the response, then use it to calculate positions for items below: `nextY = richtextY + estimatedHeight + vGap`.
251
+ - **`stack`**items flow along one axis (like CSS flexbox)
252
+ ```json
253
+ { "kind": "stack", "axis": "vertical", "gap": "medium",
254
+ "align": "start", "items": [ /* LayoutNode */ ] }
255
+ ```
256
+ - **`grid`** — N columns, items wrap to new rows (like CSS grid auto-flow)
257
+ ```json
258
+ { "kind": "grid", "columns": 3, "gap": "medium",
259
+ "items": [ /* LayoutNode */ ] }
260
+ ```
261
+ - **`anchor`** — place a sub-tree relative to an EXISTING canvas node
262
+ ```json
263
+ { "kind": "anchor", "to": "NOTE-12", "direction": "right",
264
+ "gap": "medium", "child": /* LayoutNode */ }
265
+ ```
266
+ - **`item`** — a leaf node (no x/y!). One per visible thing on the canvas.
269
267
 
270
- **Fallback estimates** (only when you can't create richtext first):
268
+ `gap` accepts a preset (`"tight"`, `"medium"`, `"spacious"`) or a raw pixel number. Defaults to `"medium"`.
271
269
 
272
- | Size | Colored | Uncolored |
273
- |------|---------|-----------|
274
- | small | 1.5u (270px) | 1u (180px) |
275
- | medium | 2u (360px) | 1.5u (270px) |
276
- | large | 2.5u (450px) | 2u (360px) |
270
+ #### Leaf item shapes
277
271
 
278
- **Layout impact — CRITICAL:** A `medium` richtext is 2.8u wide (1120px) — it spans almost 3 note columns. You MUST account for this when planning positions. Items below a richtext must use its actual height (not 180px) to calculate the next row.
272
+ ```json
273
+ { "kind": "item", "type": "note", "noteId": "NOTE-3", "key": "a" }
274
+ { "kind": "item", "type": "text", "content": "Hello", "fontSize": 32, "colorVariant": "muted" }
275
+ { "kind": "item", "type": "richtext", "content": "# Markdown OK", "size": "medium", "colorHex": "#3B82F6" }
276
+ { "kind": "item", "type": "list", "name": "Risks", "noteIds": ["A","B","C"], "viewMode": "list" }
277
+ { "kind": "item", "type": "canvas", "linkedCanvasId": "abc..." }
278
+ ```
279
279
 
280
- #### Text Node Sizing
280
+ The optional `key` lets edges and other items reference this leaf later (`@<key>`).
281
281
 
282
- | Size | Use for |
283
- |------|---------|
284
- | `heading` (default) | Section headers, zone titles, canvas labels |
285
- | `paragraph` | Descriptions, annotations, supporting context |
282
+ `richtext.content` accepts markdown directly the server converts to TipTap before measuring.
286
283
 
287
- **Never use ALL CAPS** for text node content. Use title case instead (e.g., "Key Tensions" not "KEY TENSIONS").
284
+ #### Edges
288
285
 
289
- #### Layout Density
286
+ Edges live at the top level alongside `root`. Each endpoint is resolved in this priority order:
290
287
 
291
- Gaps between items are expressed as **multiples of 1u**:
288
+ 1. **`@<key>`** any item placed in the same call that carries a matching `key`
289
+ 2. **Original `noteId`** of a note placed in this call (e.g. `"NOTE-7"`) — no `key` needed
290
+ 3. **Display id or convex id** of a node already on the canvas
292
291
 
293
- | Density | H gap (px) | V gap (px) | When to use |
294
- |---------|-----------|-----------|-------------|
295
- | **tight** | 60 | 27 | Same concern, one board retro, kanban |
296
- | **medium** | 200 | 72 | Organized, clear separation — SWOT, roadmap |
297
- | **spacious** | 400 | 144 | Distinct topics, breathing room |
292
+ ```json
293
+ "edges": [
294
+ { "from": "@a", "to": "@b", "label": "depends on" },
295
+ { "from": "NOTE-7", "to": "@a" }
296
+ ]
297
+ ```
298
298
 
299
- #### Positioning Algorithm (Notes Only)
299
+ #### Examples
300
+
301
+ **Fan-out under a heading** (the failure case the agent kept getting wrong):
302
+ ```json
303
+ {
304
+ "root": {
305
+ "kind": "stack", "axis": "vertical", "gap": "medium",
306
+ "items": [
307
+ { "kind": "item", "type": "richtext",
308
+ "content": "# Goals\nWhat we're committing to this quarter.",
309
+ "size": "medium" },
310
+ { "kind": "grid", "columns": 4,
311
+ "items": [
312
+ { "kind": "item", "type": "note", "noteId": "GOAL-1" },
313
+ { "kind": "item", "type": "note", "noteId": "GOAL-2" },
314
+ { "kind": "item", "type": "note", "noteId": "GOAL-3" },
315
+ { "kind": "item", "type": "note", "noteId": "GOAL-4" }
316
+ ] }
317
+ ]
318
+ }
319
+ }
320
+ ```
300
321
 
301
- 1. **Build the graph** — list all notes and their relationships.
302
- 2. **Assign ranks** — Rank 0 for root nodes (no incoming edges), Rank N = `max(parent ranks) + 1`.
303
- 3. **Calculate positions** — `Y = 100 + rank × (180 + vGap)`. Center each rank horizontally with `(400 + hGap)` spacing.
304
- 4. **Use `bulk-add`** for all notes, then add edges with `add-edge`.
322
+ **SWOT-style four-quadrant layout**:
323
+ ```json
324
+ {
325
+ "root": {
326
+ "kind": "stack", "axis": "vertical", "gap": "spacious",
327
+ "items": [
328
+ { "kind": "stack", "axis": "horizontal", "gap": "spacious", "items": [
329
+ { "kind": "grid", "columns": 2, "items": [
330
+ { "kind": "item", "type": "note", "noteId": "STR-1" },
331
+ { "kind": "item", "type": "note", "noteId": "STR-2" }
332
+ ] },
333
+ { "kind": "grid", "columns": 2, "items": [
334
+ { "kind": "item", "type": "note", "noteId": "WEAK-1" }
335
+ ] }
336
+ ] },
337
+ { "kind": "stack", "axis": "horizontal", "gap": "spacious", "items": [
338
+ { "kind": "grid", "columns": 2, "items": [
339
+ { "kind": "item", "type": "note", "noteId": "OPP-1" }
340
+ ] },
341
+ { "kind": "grid", "columns": 2, "items": [
342
+ { "kind": "item", "type": "note", "noteId": "THR-1" }
343
+ ] }
344
+ ] }
345
+ ]
346
+ }
347
+ }
348
+ ```
305
349
 
306
- #### Mixed-Type Layout (Notes + Richtext + Text)
350
+ **Anchor a new column to the right of an existing note**:
351
+ ```json
352
+ {
353
+ "root": {
354
+ "kind": "anchor", "to": "NOTE-12", "direction": "right", "gap": "medium",
355
+ "child": {
356
+ "kind": "stack", "axis": "vertical", "gap": "tight",
357
+ "items": [
358
+ { "kind": "item", "type": "note", "noteId": "NEW-1" },
359
+ { "kind": "item", "type": "note", "noteId": "NEW-2" }
360
+ ]
361
+ }
362
+ }
363
+ }
364
+ ```
307
365
 
308
- When a canvas mixes node types, plan as a **row-based grid** where each row's Y depends on the tallest item in the previous row.
366
+ **Diamond topology with edges**:
367
+ ```json
368
+ {
369
+ "root": {
370
+ "kind": "stack", "axis": "vertical", "gap": "medium", "align": "center",
371
+ "items": [
372
+ { "kind": "item", "type": "note", "noteId": "A", "key": "root" },
373
+ { "kind": "stack", "axis": "horizontal", "gap": "medium", "items": [
374
+ { "kind": "item", "type": "note", "noteId": "B", "key": "left" },
375
+ { "kind": "item", "type": "note", "noteId": "C", "key": "right" }
376
+ ] },
377
+ { "kind": "item", "type": "note", "noteId": "D", "key": "tail" }
378
+ ]
379
+ },
380
+ "edges": [
381
+ { "from": "@root", "to": "@left" },
382
+ { "from": "@root", "to": "@right" },
383
+ { "from": "@left", "to": "@tail" },
384
+ { "from": "@right", "to": "@tail" }
385
+ ]
386
+ }
387
+ ```
309
388
 
310
- **Execution order:**
311
- 1. Create richtext/text nodes first (`add-richtext --json`) — capture `estimatedHeight` from response
312
- 2. Calculate note positions using actual richtext heights for Y offsets
313
- 3. `bulk-add` all notes with correctly calculated positions
314
- 4. `add-edge` for all connections
389
+ #### Workflow
315
390
 
316
- **Row Y calculation:**
317
- ```
318
- Row 0 Y = 100
319
- Row 1 Y = Row0_Y + tallestItemInRow0 + vGap
320
- Row 2 Y = Row1_Y + tallestItemInRow1 + vGap
321
- ```
391
+ 1. Write the layout doc to a temp file (`/tmp/layout.json`).
392
+ 2. Run `cn canvas place <canvasId> --spec /tmp/layout.json --json`.
393
+ 3. The response includes `items[]` (with resolved `positionX/Y`, `width`, `height`, `id`, and your `key`), `edges[]` (with resolved edge ids), and `bbox` (the bounding box of the whole layout).
322
394
 
323
- **Row X calculation** — place items left-to-right using actual widths:
324
- ```
325
- Item 1: x = 100, right edge = 100 + itemWidth
326
- Item 2: x = rightEdge1 + hGap
327
- ```
395
+ #### Constraints
328
396
 
329
- #### Common Topologies
397
+ - **Anchors take zero space in their parent** — when nested inside a `stack` or `grid`, an anchor reserves no slot, so siblings collapse together. Anchors are best used at the root or as their own top-level branch.
398
+ - **Anchor `to:` must be a node already on the canvas** — local `@key` refs only work for edges, not for anchor targets in this version.
399
+ - **Never use ALL CAPS** for text node content. Use title case (e.g., "Key Tensions" not "KEY TENSIONS").
400
+ - **Multi-line richtext content** is best authored as markdown — the server converts it server-side.
330
401
 
331
- **Linear chain** (A→B→C→D): stack vertically at X=300, Y increments of `180 + vGap`.
402
+ #### When the layout engine isn't enough
332
403
 
333
- **Fan-out** (A→B, A→C, A→D): root on top, children spread horizontally at `400 + hGap` intervals.
404
+ A few cases still need explicit `x/y` via `bulk-add`:
405
+ - Reproducing a canvas pixel-for-pixel for a demo/screenshot
406
+ - Templates with intentional off-grid placement
407
+ - Adding a single item next to a known coordinate without invoking the solver
334
408
 
335
- **Diamond** (A→B, A→C, B→D, C→D): 2-column layout centered under root.
409
+ In all other cases and especially when adding 3+ items at once — use `place`.
336
410
 
337
411
  ### Interlinking Notes
338
412