yamlover 0.3.55 → 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 (48) hide show
  1. package/README.md +48 -5
  2. package/bin/yamlover.js +41 -4
  3. package/dist/agent-docs/AGENTS.md +20 -330
  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/annotation +3 -1
  7. package/dist/builtin-taxonomy/$defs/decision +34 -0
  8. package/dist/builtin-taxonomy/$defs/edit +60 -0
  9. package/dist/builtin-taxonomy/$defs/edit-answer +21 -0
  10. package/dist/builtin-taxonomy/$defs/edit-batch +31 -0
  11. package/dist/builtin-taxonomy/$defs/envelope +169 -0
  12. package/dist/builtin-taxonomy/$defs/event +85 -0
  13. package/dist/builtin-taxonomy/$defs/fragment +7 -9
  14. package/dist/builtin-taxonomy/$defs/link +58 -0
  15. package/dist/builtin-taxonomy/$defs/task +28 -19
  16. package/dist/builtin-taxonomy/$defs/workflow +3 -0
  17. package/dist/builtin-taxonomy/ontos/.yo/body.yo +7 -1
  18. package/dist/client/assets/{decoded-DYF3m3qY.js → decoded-Bj1qJCo_.js} +1 -1
  19. package/dist/client/assets/{djvu--bwAI3Ad.js → djvu-lnurzbzN.js} +1 -1
  20. package/dist/client/assets/{docx-rhrp57OA.js → docx-DpN_PtM8.js} +1 -1
  21. package/dist/client/assets/{heic-CNvQbH_2.js → heic-BqJutS0N.js} +1 -1
  22. package/dist/client/assets/imagemap-rdhcPlWy.js +1 -0
  23. package/dist/client/assets/index-0ALNONIF.js +962 -0
  24. package/dist/client/assets/index-BiETmvLO.css +1 -0
  25. package/dist/client/assets/map-Btm4SlVs.js +1 -0
  26. package/dist/client/assets/paged-Cjy3svw2.js +1 -0
  27. package/dist/client/assets/{openable-CaZBG8d8.js → panzoom-BXEAw479.js} +2 -2
  28. package/dist/client/assets/{pdf-CJhbTDg_.js → pdf-DvC2sbDN.js} +1 -1
  29. package/dist/client/assets/psd-BKmCWsdC.js +11 -0
  30. package/dist/client/assets/{spreadsheet-B71fTduw.js → spreadsheet-Coy5sooG.js} +1 -1
  31. package/dist/client/assets/tiff-IcWiNdYs.js +1 -0
  32. package/dist/client/assets/video-YC-ZvXg7.js +1 -0
  33. package/dist/client/assets/xyflow-CFNcu5cT.js +23 -0
  34. package/dist/client/index.html +2 -2
  35. package/dist/server.js +9228 -5278
  36. package/dist/wire/openapi.yo +935 -0
  37. package/package.json +6 -1
  38. package/dist/agent-docs/CLAUDE.md +0 -7
  39. package/dist/builtin-taxonomy/$defs/board +0 -15
  40. package/dist/client/assets/imagemap-BHOOud3m.js +0 -1
  41. package/dist/client/assets/index-D7Uc99a8.css +0 -1
  42. package/dist/client/assets/index-DDCohDX6.js +0 -759
  43. package/dist/client/assets/map-DRUkC_pT.js +0 -1
  44. package/dist/client/assets/paged-CKzW-eq6.js +0 -1
  45. package/dist/client/assets/psd-CPdDl_Gy.js +0 -11
  46. package/dist/client/assets/tiff-D6Ts7aAc.js +0 -1
  47. package/dist/client/assets/xyflow-DWUupXDf.js +0 -23
  48. /package/dist/client/assets/{openable-CIGW-MKW.css → panzoom-CIGW-MKW.css} +0 -0
@@ -1,4 +1,6 @@
1
- # $defs/annotation — ONE TAG APPLICATION on the host node (a whole document, or a fragment within
1
+ # $defs/annotation — RETIRED (docs/annotations: applications are membership bookmarks now).
2
+ # Kept because legacy trees still carry it — read forever, never written anew.
3
+ # ONE TAG APPLICATION on the host node (a whole document, or a fragment within
2
4
  # it). It lives as an element of the host's `yamlover-annotations` array. Two forms:
3
5
  # • parameterless — a bare tag pointer (`- *::ontos:…`): the host simply HAS this tag.
4
6
  # • parametrized — an object carrying `tag: *::ontos:…` plus parameters (e.g. `description`).
@@ -0,0 +1,34 @@
1
+ # $defs/decision — a DECISION RECORD: a task whose work is a HUMAN'S CALL. It IS-A task (allOf:
2
+ # task, hence a chapter): the scalar self-value is its title, the body is the chapter's, the
3
+ # planning fields are the task's — and it adds the decision's own fields. When an investigation
4
+ # or a refinement meets a question the agent must not answer alone, the agent births one UNDER
5
+ # THE SECTION where the question arose (the session, the ticket being refined), assigned to the
6
+ # user, carrying the `question`, the `options` with their consequences, and in the body the
7
+ # information and links a decision needs. It LINKS the record into the board where the problem
8
+ # occurred: a body pointer from the task that depends on it — which by the link law makes the
9
+ # record that task's subtask and BLOCKER, a card on its lanes in its own state. The user decides
10
+ # by writing `decision` (and `decided`) and moving the record to done; the blocked task resumes.
11
+ #
12
+ # Its lifecycle is the task's (a tag application into a workflow): born `ready` — the question
13
+ # is prepared, it waits for the user; `done` — decided. Below the workflow's active state it
14
+ # never raises the task it blocks (the parent-follows law): a question waiting is not work
15
+ # happening. Attach with !!<*::yamlover:$defs:decision>. Format: x-yamlover-decision.
16
+ # Docs: docs/documents/chapter/decision.
17
+ allOf:
18
+ - *:: yamlover: $defs: task
19
+ type: variant
20
+ members:
21
+ question: # THE thing to decide, in one sentence — marklower
22
+ type: string
23
+ format: text/marklower
24
+ options: # the choices, each with its consequences — one line each
25
+ type: array
26
+ others:
27
+ type: string
28
+ format: text/marklower
29
+ decision: # the answer, once the user has taken it — marklower
30
+ type: string
31
+ format: text/marklower
32
+ decided: # when it was taken
33
+ type: string
34
+ format: date-time
@@ -0,0 +1,60 @@
1
+ # $defs/edit — ONE SURGICAL EDIT of the tree: the element of a `POST /api/edit` batch
2
+ # ($defs/edit-batch) and of `POST /api/edit-text`. An edit names a node by its colon path and
3
+ # says what to do there; its payload is VALID INLINE YAMLOVER SOURCE (the caller escapes its own
4
+ # prose) and is parsed before any splice, so a malformed edit fails with the file untouched.
5
+ # The server splices the AUTHORED TEXT of the backing file (never a re-serialization — comments,
6
+ # spelling and order survive), grouped by file, one reindex per touched file. Semantics:
7
+ # tools/server/src/server/engine-api.ts applyEdits / editChapterSource; docs/server/wire.
8
+ #
9
+ # THE INVARIANTS an implementation holds (docs/server/editor, concrete-rules.ts, drop-policy.ts):
10
+ # - a payload never carries member STORAGE — a directory member is born by `concrete`/`name`,
11
+ # never by pasting its body into the parent; an existing node never changes concrete through
12
+ # an edit (a conversion is a move, `POST /api/mv`);
13
+ # - content inside a file document speaks that file's language; a JSON-family file takes
14
+ # scalar `emplace` only;
15
+ # - a `remove` of a member archives its storage to the parent's `.yo/.trash/` after the batch
16
+ # commits — deletion never destroys;
17
+ # - THE POSITION LAW (docs/language/partial): an `insert` before a pinned position is refused —
18
+ # it would renumber what the author pinned;
19
+ # - the `.yo/` subtree is engine-managed and read-only (`settings.yo` excepted);
20
+ # - the field is spelled `yamlover:` on the wire (the yo migration keeps the old spelling).
21
+ type: object
22
+ members:
23
+ path:
24
+ type: string
25
+ description: the colon path of the node edited (`:a:b:0`) — a key or an absolute position per segment; for `insert`, the parent (append) or a positional slot (`:a:3`, insert before it)
26
+ op:
27
+ type: string
28
+ description: unlist removes only an authored reference placement and preserves member storage; emplace | replace | insert | remove — `emplace` sets the entry, creating it when absent (a scalar self-value, a keyed field, a whole node); `replace` only when it exists; `insert` splices a new entry at a position (keyed with `key`); `remove` drops the entry
29
+ yamlover:
30
+ type: string
31
+ format: text/yamlover
32
+ description: the node's value as valid inline yamlover source — the payload of emplace, replace and insert
33
+ meta:
34
+ type: string
35
+ description: the `!!<…>` schema pointer to set on the entry; `null` drops it; omitted leaves it alone
36
+ concrete:
37
+ type: string
38
+ description: insert only — the storage a NEW document is born in — yamlover (inline, the default) | file/yamlover | dir/index.yo | dir/.yo | dir (a bare folder)
39
+ name:
40
+ type: string
41
+ description: insert only — the file or directory name when `concrete` births a document; the server uniquifies it
42
+ key:
43
+ type: string
44
+ description: insert only — create a KEYED entry (`key: value`) at the position; keeps authored order, where a fresh keyed emplace splices at the top of the block
45
+ at:
46
+ type: integer
47
+ description: scalar emplace only — the self-value line's authored position (the number of entries that precede it)
48
+ flat:
49
+ type: boolean
50
+ description: insert only — the payload's first row is a FLAT continuation of the key (docs/language/flattening), spliced as one line when its shape allows
51
+ safety:
52
+ type: object
53
+ description: optional wire-2 extension; omitted preserves legacy unguarded writes
54
+ members:
55
+ revision:
56
+ type: string
57
+ description: SHA-256 project revision from X-Yamlover-Revision on an editing read
58
+ operationId:
59
+ type: string
60
+ description: milliseconds-since-epoch:UUID; keep identical across retries; expires after 30 days
@@ -0,0 +1,21 @@
1
+ # $defs/edit-answer — what `POST /api/edit` answers. 200 with `ok` when the batch folded; `path`
2
+ # names what was BORN (the last document created) or APPENDED (the parent's new last child), so
3
+ # the caller can navigate to it; 400 with `error` — the message of the first edit that could not
4
+ # fold — and no file touched. `POST /api/edit-text` answers `{source}` instead (the text after
5
+ # the batch). The `{error}` form is every route's refusal (docs/server/wire, the routes).
6
+ type: object
7
+ members:
8
+ ok:
9
+ type: boolean
10
+ path:
11
+ type: string
12
+ description: the created or appended node's colon path — present only when the batch created or appended something
13
+ error:
14
+ type: string
15
+ description: refusal or recovery conflict; 409 means stale revision or recovery required, 410 means expired identity, 428 means missing revision inside safety
16
+ revision:
17
+ type: string
18
+ description: post-commit project revision, present for guarded writes
19
+ operationId:
20
+ type: string
21
+ description: the original request identity, unchanged on replay
@@ -0,0 +1,31 @@
1
+ # $defs/edit-batch — the body of `POST /api/edit`: a BATCH of $defs/edit, applied in order, grouped
2
+ # by backing file, the later edits addressed against the state the earlier ones leave (nothing is
3
+ # written until the whole batch folds — a batch that fails half-way leaves every file untouched).
4
+ # One reindex per touched file, one `diff` event each ($defs/event); a batch that births a document
5
+ # reindexes the tree. A bare single edit — the $defs/edit fields at the top level, no `edits` — is
6
+ # the same request with a batch of one. The answer is $defs/edit-answer. Spec: docs/server/wire.
7
+ type: object
8
+ members:
9
+ edits:
10
+ type: array
11
+ others: *:: yamlover: $defs: edit
12
+ safety:
13
+ type: object
14
+ description: optional wire-2 extension; omitted preserves legacy unguarded writes
15
+ members:
16
+ revision:
17
+ type: string
18
+ description: SHA-256 project revision from X-Yamlover-Revision on an editing read
19
+ operationId:
20
+ type: string
21
+ description: milliseconds-since-epoch:UUID; keep identical across retries; expires after 30 days
22
+ renames:
23
+ type: array
24
+ description: ordered key renames after edits, committed in the same recovery boundary
25
+ others:
26
+ type: object
27
+ members:
28
+ path:
29
+ type: string
30
+ key:
31
+ description: the new key string, or null to make an inline entry keyless
@@ -0,0 +1,169 @@
1
+ # $defs/envelope — THE CONTENT ENVELOPE: the answer of `GET /api/content/{path}?depth=&range=`
2
+ # (and of `POST /api/preview`), a `text/yamlover` document whose top-level keys are the HEADER
3
+ # (what the store knows about the node), `source` (the node's authored text, cut at the depth
4
+ # and the window), `side` (the SIDECAR — what the text cannot say, keyed by fragment) and
5
+ # `relations` (the upstream panel). Built by tools/server/src/content-envelope.ts, read by
6
+ # tools/server/src/client/content.ts; the laws are the comments of the builder and the prose of
7
+ # docs/server/wire. Every key here is a promise of the wire (wire-version.ts): a client ignores
8
+ # keys it does not know; a key renamed, retyped or dropped bumps the wire.
9
+ type: object
10
+ members:
11
+ wire:
12
+ type: integer
13
+ description: the wire version this envelope speaks — the first key, so a saved envelope says what it pins
14
+ path:
15
+ type: string
16
+ description: the requested node's colon path (`:a:b:0`) — as asked; through a link (engine-tasks paths-over-link-edges) the WALKED path, the slot's
17
+ canonical:
18
+ type: string
19
+ description: where the node lives, when `path` walked through a link (`:host:11` → `:elsewhere:card.yo`); absent when the two coincide
20
+ documentPath:
21
+ type: string
22
+ description: the document (the nearest yamlover entity — a file, an index.yo, a .yo body) the node lives in
23
+ type:
24
+ type: string
25
+ description: the TYPE name — a scalar leaf's scalar type (null | boolean | integer | number | string), `binary`, or a container's corner of the type cube (docs/meta/facets) — map | seq | kseq (keyed + ordinal) | vmap | vseq (a self-value too) | omni
26
+ valueType:
27
+ type: string
28
+ description: the self-value's scalar type — null | boolean | integer | number | string | binary; absent without a value facet
29
+ hasKeyed:
30
+ type: boolean
31
+ hasOrdinal:
32
+ type: boolean
33
+ format:
34
+ type: string
35
+ description: the node's format when it has one (a mime, an x-yamlover-* document format)
36
+ concrete:
37
+ type: string
38
+ description: the storage the node lives in — yamlover | file/yamlover | dir/index.yo | dir/.yo | dir | …
39
+ title:
40
+ type: string
41
+ description:
42
+ type: string
43
+ size:
44
+ type: integer
45
+ description: the byte size — blob nodes only; the bytes themselves live behind `/api/blob`
46
+ parseError:
47
+ type: string
48
+ description: a DEGRADED node's parse error — the page below it is the fallback
49
+ special:
50
+ type: string
51
+ description: '`overlay` when the node lives inside a `.yo/` subtree (settings.yo excepted); `graft` when it lives inside the bundled `yamlover` self-import (IMPORTS.md §4) — both are engine-owned: read-only on the page, no row in the TOC'
52
+ depth:
53
+ anyOf: # an integer, or the token `.inf` for unlimited
54
+ - type: integer
55
+ - type: string
56
+ description: the document-boundary budget the answer was cut at — an integer, or `.inf` for unlimited (the default for a text document; 1 for a directory or a binary)
57
+ childCount:
58
+ type: integer
59
+ description: WINDOWED answers only — the node's rendered entry count
60
+ range:
61
+ type: string
62
+ description: WINDOWED answers only — the resolved absolute half-open window, `A-B` over rendered entry indices
63
+ large:
64
+ type: boolean
65
+ description: the node has more than LARGE_THRESHOLD (1000) rendered entries — a bare GET auto-windows `0-500` and browsing is read-only
66
+ length:
67
+ type: integer
68
+ description: SPARSE or OPEN nodes only (docs/language/partial) — the last position + 1, which is not the count when pins left gaps
69
+ open:
70
+ type: boolean
71
+ description: the node's last entry is a keyless postponed slot (`- *.`) — its tail is unknown
72
+ source:
73
+ type: string
74
+ format: text/yamlover
75
+ description: >-
76
+ the node's authored text — the merged IR subtree serialized WITH comments, members beyond the depth respelled as their pointer (`- *: name` / `name: *: name`), a window's first entry pinned at its position and a cut tail one open `- *.`; the empty text for an empty document; blobs have no text
77
+ side:
78
+ type: object
79
+ description: the SIDECAR — one bucket per fragment; a fragment is the continuation the client computes over the parsed source (`""` the node itself, `/key`, `/3` a rendered index, `/~` the null key, nested by `/`), so sidecar keys and client keys can never disagree
80
+ others:
81
+ type: object
82
+ members:
83
+ format:
84
+ type: string
85
+ description: the engine-derived format of the node at this fragment (extension folds, resolved tags)
86
+ fileText:
87
+ type: boolean
88
+ description: a whole-file text scalar — the raw IS the value; the decoder restores it
89
+ position:
90
+ type: integer
91
+ description: the original member position when omitted hidden entries or sparse positions differ from the visible source rank; hiding never frees an occupied position
92
+ anchorKey:
93
+ type: string
94
+ description: a body-positioned MEMBER (a file or directory) serialized keyless here — its storage name
95
+ member:
96
+ type: boolean
97
+ description: a keyed-remainder member — its key IS its storage name
98
+ other:
99
+ type: boolean
100
+ description: a filesystem member not placed by an existing directory body; thumbnail metadata alone does not place it. Chapter pages show unconsumed members in the Other pane while retaining them in editor IR.
101
+ consumed:
102
+ type: boolean
103
+ description: a keyed-remainder member that a `*` pointer deeper in the document (inside a section, at any nesting) already places — the page and the TOC draw it there, never a second time at the foot; decided by the store, so the depth window cannot hide the placing pointer
104
+ stub: # every member's link stub, cut or not — and a postponed branch's facets when the engine knows the node behind it
105
+ type: object
106
+ members:
107
+ $yamloverLink: *:: yamlover: $defs: link
108
+ deco:
109
+ type: object
110
+ description: a CUT member's node decorations — the comment-map bucket (anchors, tags, raw, tail) its inlined form would have carried
111
+ others: true
112
+ refPath:
113
+ type: string
114
+ description: a realized reference entry — its resolved target's path
115
+ refText:
116
+ type: string
117
+ description: …and the scope-correct canonical pointer spelling, framed at the holder's document
118
+ target: # …and the target's link stub
119
+ type: object
120
+ members:
121
+ $yamloverLink: *:: yamlover: $defs: link
122
+ via:
123
+ type: array
124
+ description: EDGE PROVENANCE (docs/language/redundant) — the authorings the engine read for this edge — `pull` (a `*` pointer entry), `push` (a `&` bookmark on the target), or both
125
+ others:
126
+ type: string
127
+ postponed:
128
+ type: boolean
129
+ description: a POSTPONED branch (docs/language/partial) — the slot is known, the value not loaded; also the window's open tail
130
+ notloaded:
131
+ type: boolean
132
+ description: a pointer into a GAP — the target position is not loaded; fetch it, never a dead link
133
+ bookmarked:
134
+ type: array
135
+ description: the `&` bookmarks PUSHED into this node, which the projection appends after its own entries — they exist nowhere in the authored text
136
+ others:
137
+ type: object
138
+ members:
139
+ label:
140
+ type: string
141
+ path:
142
+ type: string
143
+ text:
144
+ type: string
145
+ stub:
146
+ type: object
147
+ members:
148
+ $yamloverLink: *:: yamlover: $defs: link
149
+ via:
150
+ type: array
151
+ others:
152
+ type: string
153
+ relations:
154
+ type: object
155
+ description: 'the upstream relations panel, in the query language (wire 2): `..` is the spine parent, every other key is the minimal query that finds the holder from this node (`: ...: ?#: pet: $`, `:: ...: ?#: -: $`); each value is the holder''s link marker — its description (a duplicate key gets ` (2)`, ` (3)`, …)'
156
+ others:
157
+ type: object
158
+ members:
159
+ $yamloverLink:
160
+ type: object
161
+ $yamloverRef:
162
+ type: object
163
+ members:
164
+ text:
165
+ type: string
166
+ description: the pointer's canonical text WITHOUT the `*` sigil (the IR's `Pointer.raw`); the face spells the deref token, once
167
+ path:
168
+ type: string
169
+ description: the target path it links to — null for an unrealized (dangling or external) reference
@@ -0,0 +1,85 @@
1
+ # $defs/event — ONE FRAME of `GET /api/events`, the server's `text/event-stream`: `data: <json>`
2
+ # per frame, comment lines `: connected` (on connect) and `: ping` (every 30 s) between them.
3
+ # Two kinds. A DIFF is what one reindex changed — after every write route, and after the
4
+ # watcher sees an external edit — as ROOT-RELATIVE FILE PATHS spelled as colon paths (the
5
+ # storage that changed, `:dir:index.yo`, `:a:.yo:body.yo`; not node paths — the client maps a
6
+ # file onto the branch it backs and refetches it). A TASK frame is the progress of one
7
+ # long-running job (indexing, hashing, thumbnails, reconciling), throttled to one every 150 ms
8
+ # per task, unthrottled on a state change.
9
+ #
10
+ # THE ORDER LAW: frames arrive in the order the server applied the changes; one write route
11
+ # yields one diff per touched file (a batch that births a document yields one tree-wide diff).
12
+ # THE RESYNC LAW (2026-09-07): there is still no replay — a client that missed frames must refetch
13
+ # — but it can now tell WHETHER it missed any. A HELLO frame opens every connection, naming the
14
+ # server `instance` and the number of change frames it has broadcast (`seq`); each diff carries the
15
+ # `seq` it is. A reconnect (the `open` after the first) whose greeting names the same instance and
16
+ # the same count missed nothing and may keep everything it holds; a different instance (a restart —
17
+ # a fresh process counts from zero), a higher count, or no greeting at all means everything loaded
18
+ # is stale: drop the document store, refetch the tree. `GET /api/tasks` is the snapshot of tasks in
19
+ # flight for a fresh page. Source: tools/server/src/server/engine-api.ts broadcast, tasks.ts;
20
+ # docs/server/wire.
21
+ type: object
22
+ members:
23
+ type:
24
+ type: string
25
+ description: hello | diff | task
26
+ instance:
27
+ type: string
28
+ description: hello — this server process's identity; a change of it means a restart, so nothing loaded may be trusted
29
+ seq:
30
+ type: integer
31
+ description: hello — change frames broadcast so far; diff — which one this frame is. A reconnect compares them (THE RESYNC LAW above)
32
+ added:
33
+ type: array
34
+ description: diff — files the reindex found new
35
+ others:
36
+ type: string
37
+ changed:
38
+ type: array
39
+ description: diff — files whose content changed
40
+ others:
41
+ type: string
42
+ removed:
43
+ type: array
44
+ description: diff — files gone
45
+ others:
46
+ type: string
47
+ moved:
48
+ type: array
49
+ description: diff — moves the reindex inferred (a removed file whose content reappeared elsewhere)
50
+ others:
51
+ type: object
52
+ members:
53
+ from:
54
+ type: string
55
+ to:
56
+ type: string
57
+ task:
58
+ type: object
59
+ description: task — the job's state
60
+ members:
61
+ id:
62
+ type: string
63
+ label:
64
+ type: string
65
+ state:
66
+ type: string
67
+ description: running | done | error
68
+ progress:
69
+ type: object
70
+ members:
71
+ done:
72
+ type: integer
73
+ total:
74
+ type: integer
75
+ description: absent — indeterminate (an activity bar, not a percent)
76
+ message:
77
+ type: string
78
+ description: human-readable detail — the current path, "writing index…"
79
+ startedAt:
80
+ type: integer
81
+ description: epoch milliseconds
82
+ finishedAt:
83
+ type: integer
84
+ error:
85
+ type: string
@@ -1,10 +1,11 @@
1
1
  # $defs/fragment — a user-marked REGION inside a document (the locate/highlight data only; no
2
- # description or color). Stored under the host node's `yamlover-fragments` mapping, keyed by a
3
- # slug. `type` selects the locator: a text quote {type:text, exact, prefix, suffix}, an image /
4
- # pdf / djvu rect {type:rect|pdf|djvu, x,y,w,h (pdf/djvu add page)}, or a map box
5
- # {type:map, n,s,e,w (geographic edges)}. Image-like fragments also carry an `image` — a PNG crop
6
- # of the region (a sidecar blob, referenced by a `*` pointer). A fragment may itself be tagged via
7
- # its own `yamlover-annotations`. Attach with the tag !!<*::yamlover/$defs/fragment>.
2
+ # color). Stored under the host node's `.yo: fragments:` mapping (the legacy `yo:` spelling is
3
+ # read forever), keyed by a slug. `type` selects the locator: a text quote {type:text, exact,
4
+ # prefix, suffix}, an image / pdf / djvu rect {type:rect|pdf|djvu, x,y,w,h (pdf/djvu add page)},
5
+ # or a map box {type:map, n,s,e,w (geographic edges)}. Image-like fragments also carry an
6
+ # `image` — a PNG crop of the region (a sidecar blob, referenced by a `*` pointer). A fragment
7
+ # is tagged by MEMBERSHIP BOOKMARKS (`&…:-` own-line, docs/annotations/applications).
8
+ # Attach with the tag !!<*::yamlover/$defs/fragment>.
8
9
  type: object
9
10
  format: x-yamlover-fragment
10
11
  members:
@@ -39,6 +40,3 @@ members:
39
40
  description: A PNG crop of the region (image-like fragments only); a sidecar blob by pointer
40
41
  created:
41
42
  type: string
42
- yamlover-annotations:
43
- type: array
44
- others: *:: yamlover: $defs: annotation
@@ -0,0 +1,58 @@
1
+ # $defs/link — a LINK STUB: the navigable summary of a node the wire hands over INSTEAD of the
2
+ # node itself — a depth-cut member, a postponed branch's facets, a resolved reference's target,
3
+ # a TOC row. One shape everywhere (engine-api.ts linkMarker), always wrapped as the value of a
4
+ # `$yamloverLink` key (`stub: {$yamloverLink: {...}}`) so a consumer can tell a stub from data. The client draws an icon, a title and a count from it and fetches the
5
+ # node when the reader opens it. Spec: docs/server/wire (the content envelope).
6
+ type: object
7
+ members:
8
+ kind:
9
+ type: string
10
+ description: the DISPLAY kind — scalar | binary | object | array | mix | omni
11
+ type:
12
+ type: string
13
+ description: the TYPE name — a scalar leaf's scalar type (null | boolean | integer | number | string), `binary`, or a container's corner of the type cube (docs/meta/facets) — map | seq | kseq (keyed + ordinal) | vmap | vseq (a self-value too) | omni
14
+ valueType:
15
+ type: string
16
+ description: the self-value's scalar type — null | boolean | integer | number | string | binary; absent when the node has no value facet
17
+ hasKeyed:
18
+ type: boolean
19
+ hasOrdinal:
20
+ type: boolean
21
+ path:
22
+ type: string
23
+ description: the node's colon path
24
+ format:
25
+ type: string
26
+ description: the node's format (a mime, an x-yamlover-* document format) when it has one
27
+ yo:
28
+ type: boolean
29
+ description: '`!!yo` — exempt from the enclosing schema'
30
+ concrete:
31
+ type: string
32
+ description: the storage the node lives in — yamlover | file/yamlover | dir/index.yo | dir/.yo | dir | …
33
+ hasChildren:
34
+ type: boolean
35
+ description: for a chapter or a task, whether it has a subchapter; else whether it owns visible members
36
+ title:
37
+ type: string
38
+ size:
39
+ type: integer
40
+ description: the byte size (binary nodes)
41
+ value:
42
+ description: the scalar self-value, for the link label (scalar and omni nodes); a long whole-file text is truncated
43
+ valueTruncated:
44
+ type: boolean
45
+ valueLength:
46
+ type: integer
47
+ count:
48
+ type: integer
49
+ description: the owned members — items and fields, reverse members excluded (container nodes)
50
+ color:
51
+ type: string
52
+ description: a tag's color (onto nodes)
53
+ preview:
54
+ type: string
55
+ description: the path of a fragment's PNG crop (fragment nodes)
56
+ flow:
57
+ type: string
58
+ description: 'a GLIMPSE of the node — its first element in one line of flow yamlover, nested first elements, `…` for the rest, long strings cut (every link marker built with the document in hand; flow-preview.ts)'
@@ -1,23 +1,34 @@
1
- # $defs/task — a unit of work (a ticket), or a study card. A task IS-A chapter: it EXTENDS
2
- # the chapter schema (`allOf: [*chapter]`), inheriting the FULLY-OMNI shape — the scalar
3
- # SELF-VALUE is the title (no `title:` key), `description` stays keyed — and the positional
4
- # body, except its subchapter recursion means SUBTASKS (a task tree). On top of that it adds
5
- # a few OPTIONAL structured fields for planning and automation.
6
- # It carries NO `state` field: a task's lifecycle position is a TAG APPLICATION
7
- # (`yamlover-annotations`) into a workflow taxonomy ($defs/workflow), so the whole tag / board
8
- # machinery is reused. Every planning field is optional — defaults-never-constraints: an
9
- # untouched task is just a titled note. Attach with !!<*yamlover:$defs:task>. Spec: ../TICKETS.md.
1
+ # $defs/task — a unit of work (a ticket), a study card, or a BOARD of such. A task IS-A chapter:
2
+ # it EXTENDS the chapter schema (`allOf: [*chapter]`), inheriting the FULLY-OMNI shape — the
3
+ # scalar SELF-VALUE is the title (no `title:` key), `description` stays keyed — and the
4
+ # positional body, and adds a few OPTIONAL planning fields. Defaults-never-constraints: an
5
+ # untouched task is just a titled note.
10
6
  #
11
- # `allOf: [*chapter]` extends: a task is a chapter plus more. Because task ⊆ chapter, the OWN
12
- # `others:` below — run before the inherited one — narrows the body union to (task | chunk),
13
- # intersecting chapter's (chapter | chunk): subtasks, not subchapters.
7
+ # SUBTASKS are the task's keyless body members that are tasks BY THEIR OWN DECLARATION: an inline
8
+ # container tagged `!!<*yamlover:$defs:task>`, a DIRECTORY task's `- *: slug.yo` file/dir member
9
+ # whose own document carries the task tag (or a keyed-only member the body never names — the
10
+ # chapter member law). An UNTAGGED container in a task body is a plain SUBCHAPTER, exactly as in
11
+ # a chapter — the body union is the inherited chapter one (`chapter | table | bullets | numbered
12
+ # | chunk`), never narrowed: a task is a section that can be tagged, not a body that turns
13
+ # every section into a ticket (rule changed 2026-08-29; before, the own `others:` narrowed the
14
+ # body to `task | chunk`). A pointer body element to a task ELSEWHERE (`- *..: slug.yo`,
15
+ # `- *:: …`) makes that task a subtask of this one too — a BLOCKER: the link is the DAG edge,
16
+ # the linked task's storage stays where it is (docs/documents/chapter/task). A task with subtasks IS a board: its lanes are the workflow states
17
+ # its subtasks carry, in workflow order; `workflow:` only adds empty lanes for states no subtask
18
+ # has yet; the UI's saved layout lives under the hidden `.yo: lanes:` overlay (machine-owned,
19
+ # POST /api/board, written on demand — never by opening a page).
20
+ #
21
+ # It carries NO `state` field: a task's lifecycle position is a TAG APPLICATION (a membership
22
+ # bookmark, `&::yamlover:ontos:workflow:dev:ready:-`) into a $defs/workflow, so the whole tag /
23
+ # board machinery is reused. The retired `!!<*yamlover:$defs:board>` tag still READS as this
24
+ # schema (engine/yed alias). Attach with !!<*yamlover:$defs:task>. Spec: ../TICKETS.md,
25
+ # docs/documents/chapter/task.
14
26
  allOf:
15
27
  - *:: yamlover: $defs: chapter
16
28
  type: variant
17
- others: # the body — SUBTASKS (the recursion) and chunks, interleaved
18
- anyOf:
19
- - *:: yamlover: $defs: task # a subtask — the task tree
20
- - *:: yamlover: $defs: chunk # a content block
29
+ # NO own `others:` — the body is the chapter's (allOf): subchapters, tagged blocks, chunks. A
30
+ # subtask enters ONLY by its explicit tag (a tagged element is exempt from the sweep — its tag
31
+ # decides, docs/documents/chapter/schema); the sweep never makes a task out of a section.
21
32
  members:
22
33
  priority: # free ordinal: low | normal | high | urgent (advisory)
23
34
  type: string
@@ -26,8 +37,6 @@ members:
26
37
  format: date-time
27
38
  assignee: # a name, or a pointer to a person / agent node
28
39
  type: string
29
- depends: # "blocked-by" edges → other tasks (ref; the dependency DAG)
30
- type: array
31
- others: *:: yamlover: $defs: task
32
40
  estimate: # free: "2h", "3pt"
33
41
  type: string
42
+ workflow: *:: yamlover: $defs: onto # optional ref → the workflow whose states add EMPTY lanes
@@ -15,4 +15,7 @@ value: # the workflow's description (it
15
15
  format: text/marklower
16
16
  members:
17
17
  initial: *:: yamlover: $defs: onto # ref → the start state
18
+ active: *:: yamlover: $defs: onto # ref → the state that says WORK IS HAPPENING: a parent whose
19
+ # subtask reached it (or any later state) is raised to it
20
+ # (docs/documents/chapter/task, the parent-follows law)
18
21
  others: *:: yamlover: $defs: onto # every other key is a STATE (a plain tag)
@@ -28,15 +28,21 @@ fifth tag: !!<*::yamlover:$defs:onto>
28
28
  workflow: Lifecycles — task/ticket state machines
29
29
  dev: !!<*yamlover:$defs:workflow> Software task lifecycle
30
30
  initial: *::yamlover:ontos:workflow:dev:backlog
31
+ active: *::yamlover:ontos:workflow:dev:in-progress
31
32
  backlog: Captured, not yet refined
32
33
  color: "#9399b2"
33
34
  next: *::yamlover:ontos:workflow:dev:ready
34
35
  ready: Refined, ready to pick up
35
36
  color: "#89b4fa"
36
37
  next: *::yamlover:ontos:workflow:dev:in-progress
37
- in-progress: Being worked
38
+ in-progress: Being worked
38
39
  color: "#fab387"
39
40
  next:
41
+ - *::yamlover:ontos:workflow:dev:review
42
+ - *::yamlover:ontos:workflow:dev:cancelled
43
+ review: Under review — tests, checks, a second look
44
+ color: "#f9e2af"
45
+ next:
40
46
  - *::yamlover:ontos:workflow:dev:done
41
47
  - *::yamlover:ontos:workflow:dev:cancelled
42
48
  done: Completed
@@ -1 +1 @@
1
- import{r as f,b as d,j as o}from"./index-DDCohDX6.js";import{StaticImageChunk as h,PanZoomImage as x}from"./imagemap-BHOOud3m.js";function E({node:t,label:a,decode:s,chunk:e}){const[r,i]=f.useState([]),[g,u]=f.useState(null);return f.useEffect(()=>{let n=!1;const c=[];return i([]),u(null),(async()=>{const l=await fetch(d(t.path)).then(m=>m.arrayBuffer()),p=await s(l);if(!n){for(const m of p)c.push(URL.createObjectURL(m));n||i(c)}})().catch(l=>!n&&u(String(l.message||l))),()=>{n=!0,c.forEach(URL.revokeObjectURL)}},[t.path]),g?o.jsxs("div",{className:"error",children:[a,": ",g]}):r.length?o.jsx(o.Fragment,{children:r.map((n,c)=>e?o.jsx(h,{src:n,path:t.path,onNavigate:e.onNavigate},c):o.jsx(x,{src:n,className:"filemap fileimagemap",fill:r.length===1},c))}):o.jsxs("div",{className:"loading",children:["decoding ",a,"…"]})}async function w(t,a,s){const e=document.createElement("canvas");e.width=a,e.height=s;const r=e.getContext("2d");if(!r)throw new Error("no 2d context");const i=r.createImageData(a,s);return i.data.set(t),r.putImageData(i,0,0),b(e)}function b(t){return new Promise((a,s)=>t.toBlob(e=>e?a(e):s(new Error("canvas export failed")),"image/png"))}export{E as D,b as c,w as r};
1
+ import{r as f,b as d,j as o}from"./index-0ALNONIF.js";import{StaticImageChunk as h,PanZoomImage as x}from"./imagemap-rdhcPlWy.js";function E({node:t,label:a,decode:s,chunk:e}){const[r,i]=f.useState([]),[g,u]=f.useState(null);return f.useEffect(()=>{let n=!1;const c=[];return i([]),u(null),(async()=>{const l=await fetch(d(t.path)).then(m=>m.arrayBuffer()),p=await s(l);if(!n){for(const m of p)c.push(URL.createObjectURL(m));n||i(c)}})().catch(l=>!n&&u(String(l.message||l))),()=>{n=!0,c.forEach(URL.revokeObjectURL)}},[t.path]),g?o.jsxs("div",{className:"error",children:[a,": ",g]}):r.length?o.jsx(o.Fragment,{children:r.map((n,c)=>e?o.jsx(h,{src:n,path:t.path,onNavigate:e.onNavigate},c):o.jsx(x,{src:n,className:"filemap fileimagemap",fill:r.length===1},c))}):o.jsxs("div",{className:"loading",children:["decoding ",a,"…"]})}async function w(t,a,s){const e=document.createElement("canvas");e.width=a,e.height=s;const r=e.getContext("2d");if(!r)throw new Error("no 2d context");const i=r.createImageData(a,s);return i.data.set(t),r.putImageData(i,0,0),b(e)}function b(t){return new Promise((a,s)=>t.toBlob(e=>e?a(e):s(new Error("canvas export failed")),"image/png"))}export{E as D,b as c,w as r};
@@ -1 +1 @@
1
- import{r as u,u as te,a as ne,e as oe,c as re,D as W,b as se,j as d,f as ae}from"./index-DDCohDX6.js";import{u as ce,a as ie}from"./paged-CKzW-eq6.js";const le=""+new URL("djvu-Ci0lFKxE.js",import.meta.url).href;let X=null;function ue(){return window.DjVu?Promise.resolve(window.DjVu):(X??=new Promise((s,c)=>{const i=document.createElement("script");i.src=le,i.onload=()=>window.DjVu?s(window.DjVu):c(new Error("DjVu failed to load")),i.onerror=()=>c(new Error("could not load djvu.js")),document.head.appendChild(i)}),X)}let v=null,Y=null,_=null;const m=new Map,S=new Map,de=6,Q=1500;async function he(s,c){const i=await ue();return v??=new i.Worker,c!==Y&&(Y=c,m.clear(),S.clear(),_=(async()=>(await v.createDocument(s),Number(await v.doc.getPagesQuantity().run())||0))()),_}async function ge(s,c){const i=Math.max(1,Math.round(s.height*c/s.width)),w=await createImageBitmap(s,{resizeWidth:c,resizeHeight:i,resizeQuality:"medium"}),g=document.createElement("canvas");g.width=c,g.height=i;const x=g.getContext("2d");return x.drawImage(w,0,0),w.close(),x.getImageData(0,0,c,i)}function fe(s){const c=m.get(s);if(c)return m.delete(s),m.set(s,c),Promise.resolve(c);const i=S.get(s);if(i)return i;const w=(async()=>{const[g,x,y,A]=await v.run(v.doc.getPage(s).getImageData(),v.doc.getPage(s).getNormalizedTextZones(),v.doc.getPage(s).getWidth(),v.doc.getPage(s).getHeight()),b=Number(y)||g.width,N=Number(A)||g.height,j={image:b>Q?await ge(g,Q):g,zones:Array.isArray(x)?x:[],w:b,h:N};for(S.delete(s),m.set(s,j);m.size>de;)m.delete(m.keys().next().value);return j})();return S.set(s,w),w}const p=s=>Number(s)||0;function pe({image:s}){const c=u.useRef(null);return u.useLayoutEffect(()=>{const i=c.current;i&&(i.width=s.width,i.height=s.height,i.getContext("2d")?.putImageData(s,0,0))},[s]),d.jsx("canvas",{className:"djvu-page",ref:c})}function xe({node:s}){const c=u.useRef(null),[i,w]=u.useState(0),[g,x]=u.useState(1),[y,A]=u.useState(0),[b,N]=u.useState(null),[f,j]=u.useState(null),[E,B]=u.useState(()=>new Set),[D,P]=u.useState(()=>new Map),T=u.useRef(new Map),V=te(s.path),{openCreate:H,openEdit:Z,palette:q,preview:R}=ne(V,s.path),O=V.annotations.filter(e=>e.selector?.type==="djvu").map(e=>({page:p(e.selector.page)||1,x:p(e.selector.x),y:p(e.selector.y),w:p(e.selector.w),h:p(e.selector.h),title:e.description,color:re(e),ann:oe(e)?e:void 0})),U=R?.color??W;if(R?.selector.type==="djvu"){const e=R.selector;O.push({page:p(e.page)||1,x:p(e.x),y:p(e.y),w:p(e.w),h:p(e.h),color:R.color})}const C=u.useRef(new Map),z=ce(c,()=>{const e=[];for(let n=1;n<=i;n++){const t=C.current.get(n);t&&e.push(t)}return e},i>0&&y>0);ie(c);const F=u.useRef(z);F.current=z,u.useLayoutEffect(()=>{z.restoreAnchor()},[g]),u.useEffect(()=>{const e=c.current;e&&!e.closest(".chunk-body")&&e.focus({preventScroll:!0})},[]),u.useLayoutEffect(()=>{const e=c.current;if(!e)return;const n=new ResizeObserver(([t])=>A(t.contentRect.width));return n.observe(e),()=>n.disconnect()},[]);const k=Math.min(y,1e3)*g;u.useEffect(()=>{const e=c.current;if(!e)return;let n=!1,t=0;const o=a=>{(a.ctrlKey||a.altKey||a.metaKey)&&(a.preventDefault(),n||(F.current.captureAnchor(),n=!0),clearTimeout(t),t=window.setTimeout(()=>n=!1,250),x(h=>Math.min(5,Math.max(.4,h*(a.deltaY<0?1.1:1/1.1)))))};return e.addEventListener("wheel",o,{passive:!1}),()=>{e.removeEventListener("wheel",o),clearTimeout(t)}},[]),u.useEffect(()=>{let e=!1;return w(0),N(null),B(new Set),P(new Map),C.current.clear(),T.current.clear(),(async()=>{const n=await fetch(se(s.path)).then(o=>o.arrayBuffer()),t=await he(n,s.path);e||w(t)})().catch(n=>!e&&N(String(n.message||n))),()=>{e=!0}},[s.path]),u.useEffect(()=>{if(!i)return;const e=new IntersectionObserver(n=>{B(t=>{const o=new Set(t);for(const a of n){const h=Number(a.target.dataset.page);a.isIntersecting?o.add(h):o.delete(h)}return o.size===t.size&&[...o].every(a=>t.has(a))?t:o})},{root:c.current,rootMargin:"2000px 0px"});for(const n of C.current.values())e.observe(n);return()=>e.disconnect()},[i,y>0]),u.useEffect(()=>{let e=!0;return E.forEach(n=>{D.has(n)||fe(n).then(t=>{T.current.set(n,{w:t.w,h:t.h}),e&&P(o=>new Map(o).set(n,t))}).catch(()=>{})}),P(n=>{let t=!1;const o=new Map(n);for(const a of o.keys())E.has(a)||(o.delete(a),t=!0);return t?o:n}),()=>{e=!1}},[E]);const G=()=>{const e=window.getSelection();if(!e||e.isCollapsed||!e.anchorNode)return;const t=(e.anchorNode.nodeType===1?e.anchorNode:e.anchorNode.parentElement)?.closest(".djvu-page-wrap");if(!t||!c.current?.contains(t))return;const o=Number(t.dataset.page),a=t.getBoundingClientRect(),h=a.width/(D.get(o)?.w||1);if(!h)return;const l=e.getRangeAt(0).getBoundingClientRect();l.width<2||l.height<2||H({type:"djvu",page:o,x:Math.round((l.left-a.left)/h),y:Math.round((l.top-a.top)/h),w:Math.round(l.width/h),h:Math.round(l.height/h)},{x:l.left,y:l.bottom+6})},I=(e,n)=>n.getBoundingClientRect().width/(D.get(e)?.w||1),J=(e,n)=>{const t=n.currentTarget.getBoundingClientRect(),o=I(e,n.currentTarget);j({page:e,x0:(n.clientX-t.left)/o,y0:(n.clientY-t.top)/o,x1:(n.clientX-t.left)/o,y1:(n.clientY-t.top)/o})},$=(e,n)=>{const t=n.currentTarget.getBoundingClientRect(),o=I(e,n.currentTarget);j(a=>a&&{...a,x1:(n.clientX-t.left)/o,y1:(n.clientY-t.top)/o})},ee=(e,n)=>{const t=f;if(j(null),!t||t.page!==e)return;const o=I(e,n.currentTarget),a=Math.min(t.x0,t.x1),h=Math.min(t.y0,t.y1),l=Math.abs(t.x1-t.x0),r=Math.abs(t.y1-t.y0);if(l*o<3||r*o<3)return;const M=n.currentTarget.getBoundingClientRect();H({type:"djvu",page:e,x:Math.round(a),y:Math.round(h),w:Math.round(l),h:Math.round(r)},{x:M.left+a*o,y:M.top+(h+r)*o+6})};return b?d.jsxs("div",{className:"error",children:["djvu: ",b]}):d.jsxs(d.Fragment,{children:[d.jsxs("div",{className:"filedjvu yo-zoomable",ref:c,tabIndex:0,onMouseUp:G,children:[i===0&&d.jsx("div",{className:"loading",children:"opening djvu…"}),y>0&&Array.from({length:i},(e,n)=>{const t=n+1,o=E.has(t)?D.get(t):void 0,a=T.current.get(t),h=k*(a?a.h/a.w:Math.SQRT2),l=o?k/o.w:0;return d.jsx("div",{className:"djvu-page-wrap","data-page":t,ref:r=>{r?C.current.set(t,r):C.current.delete(t)},style:{width:k,height:o?void 0:h},children:o?d.jsxs(d.Fragment,{children:[d.jsx(pe,{image:o.image}),o.zones.length>0?d.jsx("div",{className:"djvu-textlayer",children:o.zones.map((r,M)=>d.jsx("span",{style:{left:r.x*l,top:r.y*l,width:r.width*l,height:r.height*l,fontSize:r.height*l},children:r.text},M))}):d.jsx("div",{className:"djvu-marquee",onMouseDown:r=>J(t,r),onMouseMove:r=>$(t,r),onMouseUp:r=>ee(t,r),children:f?.page===t&&d.jsx("div",{className:"djvu-region",style:{left:Math.min(f.x0,f.x1)*l,top:Math.min(f.y0,f.y1)*l,width:Math.abs(f.x1-f.x0)*l,height:Math.abs(f.y1-f.y0)*l,borderColor:U,background:U+"2e"}})}),O.filter(r=>r.page===t).map((r,M)=>{const K=r.color||W;return d.jsx("div",{id:r.ann?.fragmentSlug?ae(s.path,r.ann.fragmentSlug):void 0,className:"djvu-region"+(r.ann?" editable":""),title:r.ann?r.title||"click to recolor or delete":r.title,onClick:r.ann?L=>{L.stopPropagation(),Z(r.ann,{x:L.clientX,y:L.clientY})}:void 0,style:{left:r.x*l,top:r.y*l,width:r.w*l,height:r.h*l,borderColor:K,background:K+"2e"}},M)})]}):d.jsx("div",{className:"djvu-placeholder",children:E.has(t)?"decoding…":""})},n)})]}),q]})}export{xe as DjvuView};
1
+ import{r as u,u as te,a as ne,e as oe,c as re,D as W,b as se,j as d,f as ae}from"./index-0ALNONIF.js";import{u as ce,a as ie}from"./paged-Cjy3svw2.js";const le=""+new URL("djvu-Ci0lFKxE.js",import.meta.url).href;let X=null;function ue(){return window.DjVu?Promise.resolve(window.DjVu):(X??=new Promise((s,c)=>{const i=document.createElement("script");i.src=le,i.onload=()=>window.DjVu?s(window.DjVu):c(new Error("DjVu failed to load")),i.onerror=()=>c(new Error("could not load djvu.js")),document.head.appendChild(i)}),X)}let v=null,Y=null,_=null;const m=new Map,S=new Map,de=6,Q=1500;async function he(s,c){const i=await ue();return v??=new i.Worker,c!==Y&&(Y=c,m.clear(),S.clear(),_=(async()=>(await v.createDocument(s),Number(await v.doc.getPagesQuantity().run())||0))()),_}async function ge(s,c){const i=Math.max(1,Math.round(s.height*c/s.width)),w=await createImageBitmap(s,{resizeWidth:c,resizeHeight:i,resizeQuality:"medium"}),g=document.createElement("canvas");g.width=c,g.height=i;const x=g.getContext("2d");return x.drawImage(w,0,0),w.close(),x.getImageData(0,0,c,i)}function fe(s){const c=m.get(s);if(c)return m.delete(s),m.set(s,c),Promise.resolve(c);const i=S.get(s);if(i)return i;const w=(async()=>{const[g,x,y,A]=await v.run(v.doc.getPage(s).getImageData(),v.doc.getPage(s).getNormalizedTextZones(),v.doc.getPage(s).getWidth(),v.doc.getPage(s).getHeight()),b=Number(y)||g.width,N=Number(A)||g.height,j={image:b>Q?await ge(g,Q):g,zones:Array.isArray(x)?x:[],w:b,h:N};for(S.delete(s),m.set(s,j);m.size>de;)m.delete(m.keys().next().value);return j})();return S.set(s,w),w}const p=s=>Number(s)||0;function pe({image:s}){const c=u.useRef(null);return u.useLayoutEffect(()=>{const i=c.current;i&&(i.width=s.width,i.height=s.height,i.getContext("2d")?.putImageData(s,0,0))},[s]),d.jsx("canvas",{className:"djvu-page",ref:c})}function xe({node:s}){const c=u.useRef(null),[i,w]=u.useState(0),[g,x]=u.useState(1),[y,A]=u.useState(0),[b,N]=u.useState(null),[f,j]=u.useState(null),[E,B]=u.useState(()=>new Set),[D,P]=u.useState(()=>new Map),T=u.useRef(new Map),V=te(s.path),{openCreate:H,openEdit:Z,palette:q,preview:R}=ne(V,s.path),O=V.annotations.filter(e=>e.selector?.type==="djvu").map(e=>({page:p(e.selector.page)||1,x:p(e.selector.x),y:p(e.selector.y),w:p(e.selector.w),h:p(e.selector.h),title:e.description,color:re(e),ann:oe(e)?e:void 0})),U=R?.color??W;if(R?.selector.type==="djvu"){const e=R.selector;O.push({page:p(e.page)||1,x:p(e.x),y:p(e.y),w:p(e.w),h:p(e.h),color:R.color})}const C=u.useRef(new Map),z=ce(c,()=>{const e=[];for(let n=1;n<=i;n++){const t=C.current.get(n);t&&e.push(t)}return e},i>0&&y>0);ie(c);const F=u.useRef(z);F.current=z,u.useLayoutEffect(()=>{z.restoreAnchor()},[g]),u.useEffect(()=>{const e=c.current;e&&!e.closest(".chunk-body")&&e.focus({preventScroll:!0})},[]),u.useLayoutEffect(()=>{const e=c.current;if(!e)return;const n=new ResizeObserver(([t])=>A(t.contentRect.width));return n.observe(e),()=>n.disconnect()},[]);const k=Math.min(y,1e3)*g;u.useEffect(()=>{const e=c.current;if(!e)return;let n=!1,t=0;const o=a=>{(a.ctrlKey||a.altKey||a.metaKey)&&(a.preventDefault(),n||(F.current.captureAnchor(),n=!0),clearTimeout(t),t=window.setTimeout(()=>n=!1,250),x(h=>Math.min(5,Math.max(.4,h*(a.deltaY<0?1.1:1/1.1)))))};return e.addEventListener("wheel",o,{passive:!1}),()=>{e.removeEventListener("wheel",o),clearTimeout(t)}},[]),u.useEffect(()=>{let e=!1;return w(0),N(null),B(new Set),P(new Map),C.current.clear(),T.current.clear(),(async()=>{const n=await fetch(se(s.path)).then(o=>o.arrayBuffer()),t=await he(n,s.path);e||w(t)})().catch(n=>!e&&N(String(n.message||n))),()=>{e=!0}},[s.path]),u.useEffect(()=>{if(!i)return;const e=new IntersectionObserver(n=>{B(t=>{const o=new Set(t);for(const a of n){const h=Number(a.target.dataset.page);a.isIntersecting?o.add(h):o.delete(h)}return o.size===t.size&&[...o].every(a=>t.has(a))?t:o})},{root:c.current,rootMargin:"2000px 0px"});for(const n of C.current.values())e.observe(n);return()=>e.disconnect()},[i,y>0]),u.useEffect(()=>{let e=!0;return E.forEach(n=>{D.has(n)||fe(n).then(t=>{T.current.set(n,{w:t.w,h:t.h}),e&&P(o=>new Map(o).set(n,t))}).catch(()=>{})}),P(n=>{let t=!1;const o=new Map(n);for(const a of o.keys())E.has(a)||(o.delete(a),t=!0);return t?o:n}),()=>{e=!1}},[E]);const G=()=>{const e=window.getSelection();if(!e||e.isCollapsed||!e.anchorNode)return;const t=(e.anchorNode.nodeType===1?e.anchorNode:e.anchorNode.parentElement)?.closest(".djvu-page-wrap");if(!t||!c.current?.contains(t))return;const o=Number(t.dataset.page),a=t.getBoundingClientRect(),h=a.width/(D.get(o)?.w||1);if(!h)return;const l=e.getRangeAt(0).getBoundingClientRect();l.width<2||l.height<2||H({type:"djvu",page:o,x:Math.round((l.left-a.left)/h),y:Math.round((l.top-a.top)/h),w:Math.round(l.width/h),h:Math.round(l.height/h)},{x:l.left,y:l.bottom+6})},I=(e,n)=>n.getBoundingClientRect().width/(D.get(e)?.w||1),J=(e,n)=>{const t=n.currentTarget.getBoundingClientRect(),o=I(e,n.currentTarget);j({page:e,x0:(n.clientX-t.left)/o,y0:(n.clientY-t.top)/o,x1:(n.clientX-t.left)/o,y1:(n.clientY-t.top)/o})},$=(e,n)=>{const t=n.currentTarget.getBoundingClientRect(),o=I(e,n.currentTarget);j(a=>a&&{...a,x1:(n.clientX-t.left)/o,y1:(n.clientY-t.top)/o})},ee=(e,n)=>{const t=f;if(j(null),!t||t.page!==e)return;const o=I(e,n.currentTarget),a=Math.min(t.x0,t.x1),h=Math.min(t.y0,t.y1),l=Math.abs(t.x1-t.x0),r=Math.abs(t.y1-t.y0);if(l*o<3||r*o<3)return;const M=n.currentTarget.getBoundingClientRect();H({type:"djvu",page:e,x:Math.round(a),y:Math.round(h),w:Math.round(l),h:Math.round(r)},{x:M.left+a*o,y:M.top+(h+r)*o+6})};return b?d.jsxs("div",{className:"error",children:["djvu: ",b]}):d.jsxs(d.Fragment,{children:[d.jsxs("div",{className:"filedjvu yo-zoomable",ref:c,tabIndex:0,onMouseUp:G,children:[i===0&&d.jsx("div",{className:"loading",children:"opening djvu…"}),y>0&&Array.from({length:i},(e,n)=>{const t=n+1,o=E.has(t)?D.get(t):void 0,a=T.current.get(t),h=k*(a?a.h/a.w:Math.SQRT2),l=o?k/o.w:0;return d.jsx("div",{className:"djvu-page-wrap","data-page":t,ref:r=>{r?C.current.set(t,r):C.current.delete(t)},style:{width:k,height:o?void 0:h},children:o?d.jsxs(d.Fragment,{children:[d.jsx(pe,{image:o.image}),o.zones.length>0?d.jsx("div",{className:"djvu-textlayer",children:o.zones.map((r,M)=>d.jsx("span",{style:{left:r.x*l,top:r.y*l,width:r.width*l,height:r.height*l,fontSize:r.height*l},children:r.text},M))}):d.jsx("div",{className:"djvu-marquee",onMouseDown:r=>J(t,r),onMouseMove:r=>$(t,r),onMouseUp:r=>ee(t,r),children:f?.page===t&&d.jsx("div",{className:"djvu-region",style:{left:Math.min(f.x0,f.x1)*l,top:Math.min(f.y0,f.y1)*l,width:Math.abs(f.x1-f.x0)*l,height:Math.abs(f.y1-f.y0)*l,borderColor:U,background:U+"2e"}})}),O.filter(r=>r.page===t).map((r,M)=>{const K=r.color||W;return d.jsx("div",{id:r.ann?.fragmentSlug?ae(s.path,r.ann.fragmentSlug):void 0,className:"djvu-region"+(r.ann?" editable":""),title:r.ann?r.title||"click to recolor or delete":r.title,onClick:r.ann?L=>{L.stopPropagation(),Z(r.ann,{x:L.clientX,y:L.clientY})}:void 0,style:{left:r.x*l,top:r.y*l,width:r.w*l,height:r.h*l,borderColor:K,background:K+"2e"}},M)})]}):d.jsx("div",{className:"djvu-placeholder",children:E.has(t)?"decoding…":""})},n)})]}),q]})}export{xe as DjvuView};