yamlover 0.3.56 → 0.3.58

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.
Files changed (46) hide show
  1. package/README.md +48 -5
  2. package/bin/yamlover.js +41 -4
  3. package/dist/agent-docs/AGENTS.md +20 -333
  4. package/dist/agent-docs/AGENTS.yo +621 -0
  5. package/dist/builtin-taxonomy/$defs/.yo/meta.yo +0 -2
  6. package/dist/builtin-taxonomy/$defs/decision +34 -0
  7. package/dist/builtin-taxonomy/$defs/edit +60 -0
  8. package/dist/builtin-taxonomy/$defs/edit-answer +21 -0
  9. package/dist/builtin-taxonomy/$defs/edit-batch +31 -0
  10. package/dist/builtin-taxonomy/$defs/envelope +169 -0
  11. package/dist/builtin-taxonomy/$defs/event +85 -0
  12. package/dist/builtin-taxonomy/$defs/link +58 -0
  13. package/dist/builtin-taxonomy/$defs/task +28 -19
  14. package/dist/builtin-taxonomy/$defs/workflow +3 -0
  15. package/dist/builtin-taxonomy/ontos/.yo/body.yo +7 -1
  16. package/dist/client/assets/{decoded-CytDRqwJ.js → decoded-Bj1qJCo_.js} +1 -1
  17. package/dist/client/assets/{djvu-Da-iyAV_.js → djvu-lnurzbzN.js} +1 -1
  18. package/dist/client/assets/{docx-CX_gHuMq.js → docx-DpN_PtM8.js} +1 -1
  19. package/dist/client/assets/{heic-Bf9423pJ.js → heic-BqJutS0N.js} +1 -1
  20. package/dist/client/assets/imagemap-rdhcPlWy.js +1 -0
  21. package/dist/client/assets/index-0ALNONIF.js +962 -0
  22. package/dist/client/assets/index-BiETmvLO.css +1 -0
  23. package/dist/client/assets/map-Btm4SlVs.js +1 -0
  24. package/dist/client/assets/paged-Cjy3svw2.js +1 -0
  25. package/dist/client/assets/{openable-D48EbC_P.js → panzoom-BXEAw479.js} +2 -2
  26. package/dist/client/assets/{pdf-Dj6UMm-B.js → pdf-DvC2sbDN.js} +1 -1
  27. package/dist/client/assets/psd-BKmCWsdC.js +11 -0
  28. package/dist/client/assets/{spreadsheet-BmqsAKDF.js → spreadsheet-Coy5sooG.js} +1 -1
  29. package/dist/client/assets/tiff-IcWiNdYs.js +1 -0
  30. package/dist/client/assets/video-YC-ZvXg7.js +1 -0
  31. package/dist/client/assets/xyflow-CFNcu5cT.js +23 -0
  32. package/dist/client/index.html +2 -2
  33. package/dist/server.js +8818 -5114
  34. package/dist/wire/openapi.yo +935 -0
  35. package/package.json +6 -1
  36. package/dist/agent-docs/CLAUDE.md +0 -7
  37. package/dist/builtin-taxonomy/$defs/board +0 -15
  38. package/dist/client/assets/imagemap-BaCsSWT2.js +0 -1
  39. package/dist/client/assets/index-CLllgDtO.js +0 -765
  40. package/dist/client/assets/index-CYdxG8JF.css +0 -1
  41. package/dist/client/assets/map-C0h_m0kt.js +0 -1
  42. package/dist/client/assets/paged-YrFta0Z1.js +0 -1
  43. package/dist/client/assets/psd-vtV8RE5t.js +0 -11
  44. package/dist/client/assets/tiff-DTjYlW1c.js +0 -1
  45. package/dist/client/assets/xyflow-DLSA3Qi6.js +0 -23
  46. /package/dist/client/assets/{openable-CIGW-MKW.css → panzoom-CIGW-MKW.css} +0 -0
@@ -0,0 +1,935 @@
1
+ # THE OPENAPI DOCUMENT of the yamlover wire (docs/server/wire) — authored in yamlover (OpenAPI is
2
+ # YAML, and yamlover reads it), served as JSON at `GET /api/openapi` and printed by
3
+ # `npx yamlover --print-openapi`. Two things are filled in when it is served (wire-openapi.ts):
4
+ # `info.version` from wire-version.ts, and `components.schemas` from the `$defs` meta files this
5
+ # document names in `x-yamlover-defs` — the schemas live ONCE, in `$defs/`, and this document
6
+ # only refers to them. The routes are the CONTRACT and CONVENIENCE families of the inventory
7
+ # (docs/server/wire, the routes); the private surface (doctor, reindex, tasks, config,
8
+ # agent-docs) is deliberately absent. Status codes are quoted — a bare integer key is a position
9
+ # in yamlover, not a string.
10
+ openapi: 3.1.0
11
+ info:
12
+ title: the yamlover wire
13
+ version: '0' # replaced by WIRE_VERSION when served
14
+ description: >
15
+ The HTTP protocol between a yamlover server and its clients. A node is read as a yamlover
16
+ document (the content envelope), edited by batches of surgical text edits, and every change -
17
+ a client's or an external one - is pushed as a diff on one event stream. Paths in parameters
18
+ and bodies are colon paths (`:a:b:0`); only `/api/content/{path}` takes a slash path.
19
+ x-yamlover-defs:
20
+ - envelope
21
+ - link
22
+ - edit
23
+ - edit-batch
24
+ - edit-answer
25
+ - event
26
+ servers:
27
+ - url: /
28
+ description: the local server (`npx yamlover <root>`), or the deployment's `--base-path`
29
+ tags:
30
+ - name: contract
31
+ description: the model - what any client needs and any server answers
32
+ - name: convenience
33
+ description: derived helpers a second client may skip or compute for itself
34
+ components:
35
+ parameters:
36
+ path:
37
+ name: path
38
+ in: query
39
+ description: a colon path (`:a:b:0`); absent or `:` is the root
40
+ schema:
41
+ type: string
42
+ depth:
43
+ name: depth
44
+ in: query
45
+ description: the document-boundary budget - an integer, or `.inf`; absent picks the node's default (unlimited for a text document, 1 for a directory or a binary)
46
+ schema:
47
+ type: string
48
+ responses:
49
+ refused:
50
+ description: the request was refused or needs recovery; an explicit recovery conflict preserves its journal and competing external bytes
51
+ content:
52
+ application/json:
53
+ schema:
54
+ type: object
55
+ properties:
56
+ error:
57
+ type: string
58
+ example:
59
+ error: 'unknown edit op: frobnicate'
60
+ notFound:
61
+ description: the path names no node
62
+ content:
63
+ application/json:
64
+ schema:
65
+ type: object
66
+ properties:
67
+ error:
68
+ type: string
69
+ paths:
70
+ /api/info:
71
+ get:
72
+ tags: [contract]
73
+ summary: the root title, the read-only flag and the wire version
74
+ responses:
75
+ '200':
76
+ description: ok
77
+ content:
78
+ application/json:
79
+ schema:
80
+ type: object
81
+ properties:
82
+ root:
83
+ type: string
84
+ readOnly:
85
+ type: boolean
86
+ wire:
87
+ type: integer
88
+ example:
89
+ root: yamlover
90
+ readOnly: false
91
+ wire: 1
92
+ '/api/content/{path}':
93
+ get:
94
+ tags: [contract]
95
+ summary: THE ONE WIRE - the node as a content envelope
96
+ description: >
97
+ Answers `text/yamlover`: the node's authored text under a store-derived header, with the
98
+ sidecar and the relations panel - `$defs/envelope`. `path` is a SLASH path here (the URL);
99
+ `/api/content` alone is the root.
100
+ parameters:
101
+ - name: path
102
+ in: path
103
+ required: true
104
+ description: 'the node''s slash path, segments percent-encoded; a position that is the slot of a pull link walks THROUGH it (`host/11` is the card `- *:: elsewhere: card.yo` names, `host/11/0` its first element) - the envelope''s `path` is then the walked path and `canonical` where the node lives'
105
+ schema:
106
+ type: string
107
+ - $ref: '#/components/parameters/depth'
108
+ - name: editing
109
+ in: query
110
+ description: use 1 to reconcile source and return a revision for guarded saves
111
+ schema:
112
+ type: string
113
+ - name: range
114
+ in: query
115
+ description: 'the window over the top node''s rendered entries - `A-B`, `A-`, `-B`, `.len-(N)-` (the range grammar minus `.`); a node past 1000 entries auto-windows to `0-500` without it'
116
+ schema:
117
+ type: string
118
+ responses:
119
+ '200':
120
+ description: the content envelope
121
+ headers:
122
+ X-Yamlover-Revision:
123
+ description: project SHA-256 revision, only when editing=1
124
+ schema:
125
+ type: string
126
+ content:
127
+ text/yamlover:
128
+ schema:
129
+ $ref: '#/components/schemas/envelope'
130
+ example: |
131
+ wire: 2
132
+ path: :notes:todo.yo
133
+ documentPath: :notes:todo.yo
134
+ type: object
135
+ hasKeyed: true
136
+ hasOrdinal: false
137
+ concrete: file/yamlover
138
+ title: todo
139
+ depth: .inf
140
+ source: |
141
+ title: todo
142
+ items: *: items
143
+ side:
144
+ "": {}
145
+ \/items:
146
+ member: true
147
+ stub:
148
+ $yamloverLink: {kind: array, type: array, hasKeyed: false, hasOrdinal: true, path: :notes:todo.yo:items, concrete: yamlover, hasChildren: true, count: 3}
149
+ relations:
150
+ ..:
151
+ $yamloverLink: {kind: omni, type: omni, path: :notes, format: x-yamlover-chapter, concrete: dir/index.yo, hasChildren: true, count: 2, title: Notes}
152
+ ':: ...: ?#: pet: $':
153
+ $yamloverLink: {kind: omni, type: omni, path: :team:alice, concrete: yamlover, hasChildren: true, count: 3}
154
+ '404':
155
+ $ref: '#/components/responses/notFound'
156
+ '400':
157
+ $ref: '#/components/responses/refused'
158
+ /api/blob:
159
+ get:
160
+ tags: [contract]
161
+ summary: the raw bytes of a file-backed node, with single byte-range support
162
+ parameters:
163
+ - $ref: '#/components/parameters/path'
164
+ - name: Range
165
+ in: header
166
+ schema: {type: string}
167
+ - name: If-Range
168
+ in: header
169
+ schema: {type: string}
170
+ responses:
171
+ '200':
172
+ description: the bytes, Content-Type from the format; Accept-Ranges, Content-Length and ETag
173
+ content:
174
+ '*/*':
175
+ schema: {type: string, format: binary}
176
+ '206':
177
+ description: the requested byte range, with Content-Range
178
+ '416':
179
+ description: invalid or unsatisfiable byte range
180
+ '404':
181
+ $ref: '#/components/responses/notFound'
182
+ head:
183
+ tags: [contract]
184
+ summary: file headers without the body
185
+ parameters:
186
+ - $ref: '#/components/parameters/path'
187
+ responses:
188
+ '200':
189
+ description: Content-Type, Content-Length, Accept-Ranges and ETag
190
+ '404':
191
+ $ref: '#/components/responses/notFound'
192
+ /api/tree:
193
+ get:
194
+ tags: [contract]
195
+ summary: the TOC projection of a subtree
196
+ parameters:
197
+ - $ref: '#/components/parameters/path'
198
+ - name: depth
199
+ in: query
200
+ description: levels of the tree (default 3)
201
+ schema:
202
+ type: integer
203
+ responses:
204
+ '200':
205
+ description: a TreeNode - label, path, type, children; a truncation as `notLoaded`
206
+ content:
207
+ application/json:
208
+ schema:
209
+ type: object
210
+ properties:
211
+ path:
212
+ type: string
213
+ label:
214
+ type: string
215
+ children:
216
+ type: array
217
+ items:
218
+ type: object
219
+ '404':
220
+ $ref: '#/components/responses/notFound'
221
+ /api/source:
222
+ get:
223
+ tags: [contract]
224
+ summary: the node's yamlover source, as authored
225
+ parameters:
226
+ - $ref: '#/components/parameters/path'
227
+ responses:
228
+ '200':
229
+ description: ok
230
+ content:
231
+ application/json:
232
+ schema:
233
+ type: object
234
+ properties:
235
+ source:
236
+ type: string
237
+ example:
238
+ source: "a: 1\nb: hello\n"
239
+ '404':
240
+ $ref: '#/components/responses/notFound'
241
+ /api/schema:
242
+ get:
243
+ tags: [contract]
244
+ summary: the projected instance schema of a node
245
+ parameters:
246
+ - $ref: '#/components/parameters/path'
247
+ - $ref: '#/components/parameters/depth'
248
+ responses:
249
+ '200':
250
+ description: the schema projection (yamlover meta as JSON)
251
+ content:
252
+ application/json:
253
+ schema:
254
+ type: object
255
+ /api/query:
256
+ get:
257
+ tags: [contract]
258
+ summary: evaluate a query
259
+ parameters:
260
+ - name: q
261
+ in: query
262
+ required: true
263
+ description: the query, in the colon grammar (docs/language/pointers/queries)
264
+ schema:
265
+ type: string
266
+ - $ref: '#/components/parameters/path'
267
+ - name: shape
268
+ in: query
269
+ description: absent - matching paths; `tree` - one TreeNode per match; `filter` - the tree filtered to the matches
270
+ schema:
271
+ type: string
272
+ enum: [tree, filter]
273
+ responses:
274
+ '200':
275
+ description: the matches
276
+ content:
277
+ application/json:
278
+ schema:
279
+ type: object
280
+ properties:
281
+ results:
282
+ type: array
283
+ root:
284
+ type: object
285
+ matches:
286
+ type: array
287
+ items:
288
+ type: string
289
+ truncated:
290
+ type: boolean
291
+ example:
292
+ results: [':notes:todo.yo', ':notes:done.yo']
293
+ '400':
294
+ $ref: '#/components/responses/refused'
295
+ /api/events:
296
+ get:
297
+ tags: [contract]
298
+ summary: the event stream - every change, as it lands
299
+ description: '`text/event-stream`: one `$defs/event` per `data:` frame; `: connected` then a `hello` frame on connect, `: ping` every 30 s. No replay - but a reconnect whose `hello` names the same `instance` and `seq` it last saw missed nothing and keeps what it holds; anything else means everything loaded is stale.'
300
+ responses:
301
+ '200':
302
+ description: the stream
303
+ content:
304
+ text/event-stream:
305
+ schema:
306
+ $ref: '#/components/schemas/event'
307
+ example: |
308
+ : connected
309
+
310
+ data: {"type":"diff","added":[],"changed":[":notes:todo.yo"],"removed":[],"moved":[]}
311
+
312
+ data: {"type":"task","task":{"id":"index","label":"indexing","state":"done","progress":{"done":12,"total":12},"startedAt":1756400000000,"finishedAt":1756400000400}}
313
+ /api/undo:
314
+ post:
315
+ tags: [contract]
316
+ summary: reverse a recorded operation; redo reverses the undo operation
317
+ description: >
318
+ Requires safety revision and request identity. Preserves files and references through
319
+ the mutation journal; conflicts rather than overwriting intervening changes. Receipts
320
+ and undo data expire after 30 days. The returned operationId identifies the inverse.
321
+ requestBody:
322
+ required: true
323
+ content:
324
+ application/json:
325
+ schema:
326
+ type: object
327
+ required: [operationId, safety]
328
+ properties:
329
+ operationId:
330
+ type: string
331
+ safety:
332
+ type: object
333
+ required: [revision, operationId]
334
+ properties:
335
+ revision:
336
+ type: string
337
+ operationId:
338
+ type: string
339
+ responses:
340
+ '200':
341
+ description: the inverse committed or its original result was replayed
342
+ content:
343
+ application/json:
344
+ schema:
345
+ $ref: '#/components/schemas/edit-answer'
346
+ '409':
347
+ description: changed source, reused identity or already reversed operation
348
+ '410':
349
+ description: undo history expired or unavailable
350
+ '428':
351
+ description: revision and request identity required
352
+ /api/edit:
353
+ post:
354
+ tags: [contract]
355
+ summary: a batch of surgical edits
356
+ requestBody:
357
+ required: true
358
+ content:
359
+ application/json:
360
+ schema:
361
+ $ref: '#/components/schemas/edit-batch'
362
+ example:
363
+ edits:
364
+ - {path: ':notes:todo.yo:title', op: emplace, yamlover: 'todo, revised'}
365
+ - {path: ':notes', op: insert, concrete: file/yamlover, name: done, yamlover: 'title: done'}
366
+ responses:
367
+ '200':
368
+ description: the batch folded
369
+ content:
370
+ application/json:
371
+ schema:
372
+ $ref: '#/components/schemas/edit-answer'
373
+ example:
374
+ ok: true
375
+ path: :notes:done.yo
376
+ '400':
377
+ $ref: '#/components/responses/refused'
378
+ '409':
379
+ description: stale source revision, reused identity with different payload, or recovery conflict
380
+ '410':
381
+ description: operation identity expired; reconcile before submitting a new operation
382
+ '428':
383
+ description: safety was supplied without a valid revision
384
+ /api/link:
385
+ post:
386
+ tags: [contract]
387
+ summary: link a node into a target's body - the node stays, the target gains a pointer element to it
388
+ requestBody:
389
+ required: true
390
+ content:
391
+ application/json:
392
+ schema:
393
+ type: object
394
+ members:
395
+ from:
396
+ type: string
397
+ description: the node to link (a file or directory path)
398
+ to:
399
+ type: string
400
+ description: the chapter, task or directory whose body gains the pointer
401
+ responses:
402
+ '200':
403
+ description: the target and the pointer text appended
404
+ content:
405
+ application/json:
406
+ schema:
407
+ type: object
408
+ '400':
409
+ $ref: '#/components/responses/refused'
410
+ /api/unlink:
411
+ post:
412
+ tags: [contract]
413
+ summary: delete a LINKED node's slot - the pointer entry goes, references to that place follow the node
414
+ requestBody:
415
+ required: true
416
+ content:
417
+ application/json:
418
+ schema:
419
+ type: object
420
+ members:
421
+ path:
422
+ type: string
423
+ description: the link's slot - the holder's path plus the pointer entry's position (or key)
424
+ responses:
425
+ '200':
426
+ description: the removed slot, the node's actual path, the references rewritten, the reindex diff
427
+ content:
428
+ application/json:
429
+ schema:
430
+ type: object
431
+ '400':
432
+ $ref: '#/components/responses/refused'
433
+ /api/mv:
434
+ post:
435
+ tags: [contract]
436
+ summary: move a file or directory, relinking what pointed at it
437
+ requestBody:
438
+ required: true
439
+ content:
440
+ application/json:
441
+ schema:
442
+ type: object
443
+ properties:
444
+ from:
445
+ type: string
446
+ to:
447
+ type: string
448
+ example:
449
+ from: :notes:todo.yo
450
+ to: :archive:todo.yo
451
+ responses:
452
+ '200':
453
+ description: the move report and the index diff
454
+ content:
455
+ application/json:
456
+ schema:
457
+ type: object
458
+ '400':
459
+ $ref: '#/components/responses/refused'
460
+ /api/rekey:
461
+ post:
462
+ tags: [contract]
463
+ summary: rename a key in place, relinking what pointed at it — keyless → keyed and back too
464
+ requestBody:
465
+ required: true
466
+ content:
467
+ application/json:
468
+ schema:
469
+ type: object
470
+ properties:
471
+ path:
472
+ type: string
473
+ description: the entry — by its key, or by its POSITION when a keyless chunk takes a key
474
+ key:
475
+ type: string
476
+ nullable: true
477
+ description: the new key; `null` DROPS it — the entry turns keyless in place (the key cell's dash); `title` and hidden overlay keys are refused
478
+ example:
479
+ path: :notes:todo.yo:title
480
+ key: heading
481
+ responses:
482
+ '200':
483
+ description: the new path, the relink report and the diff
484
+ content:
485
+ application/json:
486
+ schema:
487
+ type: object
488
+ properties:
489
+ path:
490
+ type: string
491
+ '400':
492
+ $ref: '#/components/responses/refused'
493
+ /api/paste:
494
+ post:
495
+ tags: [contract]
496
+ summary: file clipboard content beside a node
497
+ requestBody:
498
+ required: true
499
+ content:
500
+ application/json:
501
+ schema:
502
+ type: object
503
+ responses:
504
+ '201':
505
+ description: what was born
506
+ content:
507
+ application/json:
508
+ schema:
509
+ type: object
510
+ '400':
511
+ $ref: '#/components/responses/refused'
512
+ /api/annotations:
513
+ get:
514
+ tags: [contract]
515
+ summary: the annotations on a material
516
+ parameters:
517
+ - $ref: '#/components/parameters/path'
518
+ responses:
519
+ '200':
520
+ description: the tag applications, with their fragments
521
+ content:
522
+ application/json:
523
+ schema:
524
+ type: array
525
+ items:
526
+ type: object
527
+ /api/annotate:
528
+ post:
529
+ tags: [contract]
530
+ summary: apply a tag to a node or a fragment of it
531
+ requestBody:
532
+ required: true
533
+ content:
534
+ application/json:
535
+ schema:
536
+ type: object
537
+ properties:
538
+ target:
539
+ type: string
540
+ description: the NODE to tag — a walked path (`:session:5`, a card through its slot) resolves to the node it names; the bookmark lands in that node's own document, never in the host's
541
+ tag:
542
+ type: string
543
+ selector:
544
+ type: object
545
+ example:
546
+ target: :notes:todo.yo
547
+ tag: :yamlover:ontos:workflow:dev:ready
548
+ responses:
549
+ '201':
550
+ description: applied
551
+ content:
552
+ application/json:
553
+ schema:
554
+ type: object
555
+ properties:
556
+ ok:
557
+ type: boolean
558
+ '400':
559
+ $ref: '#/components/responses/refused'
560
+ delete:
561
+ tags: [contract]
562
+ summary: remove a tag application
563
+ parameters:
564
+ - name: target
565
+ in: query
566
+ required: true
567
+ schema:
568
+ type: string
569
+ - name: tag
570
+ in: query
571
+ required: true
572
+ schema:
573
+ type: string
574
+ - name: selector
575
+ in: query
576
+ description: the fragment's selector as JSON, when the application is on a fragment
577
+ schema:
578
+ type: string
579
+ responses:
580
+ '200':
581
+ description: removed
582
+ content:
583
+ application/json:
584
+ schema:
585
+ type: object
586
+ '400':
587
+ $ref: '#/components/responses/refused'
588
+ /api/fragment:
589
+ post:
590
+ tags: [contract]
591
+ summary: mark a region of a material, with no tag yet
592
+ requestBody:
593
+ required: true
594
+ content:
595
+ application/json:
596
+ schema:
597
+ type: object
598
+ properties:
599
+ target:
600
+ type: string
601
+ selector:
602
+ type: object
603
+ responses:
604
+ '201':
605
+ description: the fragment made
606
+ content:
607
+ application/json:
608
+ schema:
609
+ type: object
610
+ '400':
611
+ $ref: '#/components/responses/refused'
612
+ /api/tagged:
613
+ get:
614
+ tags: [contract]
615
+ summary: the materials filed under a tag
616
+ parameters:
617
+ - $ref: '#/components/parameters/path'
618
+ responses:
619
+ '200':
620
+ description: the targets
621
+ content:
622
+ application/json:
623
+ schema:
624
+ type: array
625
+ '404':
626
+ $ref: '#/components/responses/notFound'
627
+ /api/tag:
628
+ post:
629
+ tags: [contract]
630
+ summary: create a tag in the project taxonomy (or find it)
631
+ requestBody:
632
+ required: true
633
+ content:
634
+ application/json:
635
+ schema:
636
+ type: object
637
+ properties:
638
+ name:
639
+ type: string
640
+ responses:
641
+ '201':
642
+ description: the tag
643
+ content:
644
+ application/json:
645
+ schema:
646
+ type: object
647
+ properties:
648
+ path:
649
+ type: string
650
+ name:
651
+ type: string
652
+ color:
653
+ type: string
654
+ created:
655
+ type: boolean
656
+ '400':
657
+ $ref: '#/components/responses/refused'
658
+ /api/workflows:
659
+ get:
660
+ tags: [contract]
661
+ summary: every workflow the index holds
662
+ description: >-
663
+ Each `x-yamlover-workflow` node, the bundled taxonomy's included, projected as a tag ref -
664
+ the task page's workflow picker.
665
+ responses:
666
+ '200':
667
+ description: the workflows, in path order
668
+ content:
669
+ application/json:
670
+ schema:
671
+ type: object
672
+ properties:
673
+ workflows:
674
+ type: array
675
+ items:
676
+ type: object
677
+ properties:
678
+ path: { type: string }
679
+ name: { type: string }
680
+ color: { type: [string, 'null'] }
681
+ /api/board:
682
+ get:
683
+ tags: [contract]
684
+ summary: a task's board - its subtasks in lanes
685
+ parameters:
686
+ - $ref: '#/components/parameters/path'
687
+ responses:
688
+ '200':
689
+ description: the resolved board
690
+ content:
691
+ application/json:
692
+ schema:
693
+ type: object
694
+ properties:
695
+ seeded:
696
+ type: boolean
697
+ workflow:
698
+ description: >-
699
+ the workflow the lanes read - `own: true` when the task's own `workflow:`
700
+ field names it, else inherited from the nearest ancestor task; null when none
701
+ type: [object, 'null']
702
+ properties:
703
+ path: { type: string }
704
+ name: { type: string }
705
+ color: { type: [string, 'null'] }
706
+ own: { type: boolean }
707
+ '404':
708
+ $ref: '#/components/responses/notFound'
709
+ post:
710
+ tags: [contract]
711
+ summary: move a card, save the layout, or reconcile the board
712
+ requestBody:
713
+ required: true
714
+ content:
715
+ application/json:
716
+ schema:
717
+ type: object
718
+ properties:
719
+ path:
720
+ type: string
721
+ op:
722
+ type: string
723
+ enum: [structure, move, reconcile]
724
+ structure:
725
+ type: array
726
+ task:
727
+ type: string
728
+ from:
729
+ type: object
730
+ to:
731
+ type: object
732
+ responses:
733
+ '201':
734
+ description: the resolved board
735
+ content:
736
+ application/json:
737
+ schema:
738
+ type: object
739
+ '400':
740
+ $ref: '#/components/responses/refused'
741
+ /api/dangling:
742
+ get:
743
+ tags: [contract]
744
+ summary: the pointers that did not resolve at index time
745
+ responses:
746
+ '200':
747
+ description: the list
748
+ content:
749
+ application/json:
750
+ schema:
751
+ type: array
752
+ items:
753
+ type: object
754
+ properties:
755
+ from:
756
+ type: string
757
+ raw:
758
+ type: string
759
+ reason:
760
+ type: string
761
+ /api/video:
762
+ get:
763
+ tags: [convenience]
764
+ summary: prepare an MP4 for frame playback, or stream its playable bytes
765
+ description: >-
766
+ Requires ffmpeg and ffprobe. Non-H.264 videos receive a cached H.264 preview under .yo/videos.
767
+ Completed metadata and previews survive restart; changed or deleted sources invalidate them.
768
+ Read-only servers reuse existing products without writing caches.
769
+ Concurrent work is shared; original files are unchanged. Frame timestamps describe
770
+ the playable stream, in seconds. Byte streaming accepts the same Range and If-Range
771
+ headers as /api/blob. HEAD with stream=1 returns stream headers only.
772
+ parameters:
773
+ - $ref: '#/components/parameters/path'
774
+ - name: stream
775
+ in: query
776
+ schema: {type: string, enum: ['1']}
777
+ - name: Range
778
+ in: header
779
+ schema: {type: string}
780
+ - name: If-Range
781
+ in: header
782
+ schema: {type: string}
783
+ responses:
784
+ '200':
785
+ description: metadata, or the playable MP4 when stream=1
786
+ content:
787
+ application/json:
788
+ schema:
789
+ type: object
790
+ required: [duration, frames, width, height, codec, preview, revision]
791
+ properties:
792
+ duration: {type: number}
793
+ frames:
794
+ type: array
795
+ items: {type: number}
796
+ width: {type: integer}
797
+ height: {type: integer}
798
+ codec: {type: string}
799
+ preview: {type: boolean}
800
+ revision: {type: string}
801
+ video/mp4:
802
+ schema: {type: string, format: binary}
803
+ '206':
804
+ description: playable stream byte range
805
+ '416':
806
+ description: unsatisfiable stream byte range
807
+ '415':
808
+ description: not an MP4
809
+ '503':
810
+ description: preparation failed, including missing FFmpeg
811
+ '404':
812
+ $ref: '#/components/responses/notFound'
813
+ /api/thumb:
814
+ get:
815
+ tags: [convenience]
816
+ summary: a thumbnail of a file-backed blob, generated once and cached beside it
817
+ parameters:
818
+ - $ref: '#/components/parameters/path'
819
+ - name: w
820
+ in: query
821
+ schema:
822
+ type: integer
823
+ - name: h
824
+ in: query
825
+ schema:
826
+ type: integer
827
+ responses:
828
+ '200':
829
+ description: the image
830
+ content:
831
+ image/jpeg: {}
832
+ image/png: {}
833
+ '415':
834
+ description: no decoder for the format
835
+ '404':
836
+ $ref: '#/components/responses/notFound'
837
+ /api/cards:
838
+ get:
839
+ tags: [convenience]
840
+ summary: board card stubs for a list of paths
841
+ description: >-
842
+ Each path is resolved by the engine exactly as /api/content walks it: a pointer's slot
843
+ (`:task-sessions:1` for `- *: '20260829'`) answers the card of the task behind it, with
844
+ `path` as asked and `canonical` naming where the task lives when the two differ. A path
845
+ naming no node answers an inert stub, never an error.
846
+ parameters:
847
+ - name: paths
848
+ in: query
849
+ required: true
850
+ description: comma-separated colon paths
851
+ schema:
852
+ type: string
853
+ responses:
854
+ '200':
855
+ description: one stub per path
856
+ content:
857
+ application/json:
858
+ schema:
859
+ type: array
860
+ /api/preview:
861
+ post:
862
+ tags: [convenience]
863
+ summary: the content envelope of a standalone text (stateless)
864
+ requestBody:
865
+ required: true
866
+ content:
867
+ application/json:
868
+ schema:
869
+ type: object
870
+ properties:
871
+ source:
872
+ type: string
873
+ responses:
874
+ '200':
875
+ description: the envelope
876
+ content:
877
+ text/yamlover:
878
+ schema:
879
+ $ref: '#/components/schemas/envelope'
880
+ '400':
881
+ $ref: '#/components/responses/refused'
882
+ /api/edit-text:
883
+ post:
884
+ tags: [convenience]
885
+ summary: an edit batch applied to a standalone text (stateless)
886
+ requestBody:
887
+ required: true
888
+ content:
889
+ application/json:
890
+ schema:
891
+ type: object
892
+ properties:
893
+ source:
894
+ type: string
895
+ edits:
896
+ type: array
897
+ items:
898
+ $ref: '#/components/schemas/edit'
899
+ responses:
900
+ '200':
901
+ description: the text after the batch
902
+ content:
903
+ application/json:
904
+ schema:
905
+ type: object
906
+ properties:
907
+ source:
908
+ type: string
909
+ '400':
910
+ $ref: '#/components/responses/refused'
911
+ /api/dead-links:
912
+ get:
913
+ tags: [convenience]
914
+ summary: the in-tree links the doctor found dead
915
+ responses:
916
+ '200':
917
+ description: the targets
918
+ content:
919
+ application/json:
920
+ schema:
921
+ type: object
922
+ properties:
923
+ targets:
924
+ type: array
925
+ /api/openapi:
926
+ get:
927
+ tags: [convenience]
928
+ summary: this document
929
+ responses:
930
+ '200':
931
+ description: the OpenAPI document, `info.version` the wire version
932
+ content:
933
+ application/json:
934
+ schema:
935
+ type: object