visualproof 0.0.0-stage → 0.1.0

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 (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +99 -2
  3. package/dist/assets/fonts/JuliaMono-Regular.ttf +0 -0
  4. package/dist/assets/fonts/JuliaMono-RegularItalic.ttf +0 -0
  5. package/dist/assets/fonts/JuliaMono-SemiBold.ttf +0 -0
  6. package/dist/assets/fonts/LICENSE-JuliaMono.txt +93 -0
  7. package/dist/assets/latex/visualproof.sty +308 -0
  8. package/dist/extractor/Extract.lean +1180 -0
  9. package/dist/extractor/Usage.lean +900 -0
  10. package/dist/mcp/visualproof-mcp.mjs +476 -0
  11. package/dist/server/access.js +95 -0
  12. package/dist/server/agent.js +3235 -0
  13. package/dist/server/agentdocs.js +35 -0
  14. package/dist/server/agentlibrary.js +202 -0
  15. package/dist/server/agentnotes.js +701 -0
  16. package/dist/server/agentviews.js +703 -0
  17. package/dist/server/askfollow.js +286 -0
  18. package/dist/server/boardspots.js +159 -0
  19. package/dist/server/dirs.js +175 -0
  20. package/dist/server/doctor.js +186 -0
  21. package/dist/server/events.js +198 -0
  22. package/dist/server/export.js +420 -0
  23. package/dist/server/extract.js +204 -0
  24. package/dist/server/fetchurl.js +321 -0
  25. package/dist/server/figures.js +158 -0
  26. package/dist/server/history.js +741 -0
  27. package/dist/server/historyapi.js +304 -0
  28. package/dist/server/hover.js +595 -0
  29. package/dist/server/identity.js +114 -0
  30. package/dist/server/invites.js +241 -0
  31. package/dist/server/layoutthread.js +51 -0
  32. package/dist/server/library.js +600 -0
  33. package/dist/server/libraryroute.js +211 -0
  34. package/dist/server/live.js +1101 -0
  35. package/dist/server/lostfound.js +199 -0
  36. package/dist/server/main.js +1394 -0
  37. package/dist/server/mcptools.js +497 -0
  38. package/dist/server/pastdoc.js +73 -0
  39. package/dist/server/pipeline.js +156 -0
  40. package/dist/server/poppler.js +251 -0
  41. package/dist/server/presence.js +252 -0
  42. package/dist/server/project.js +222 -0
  43. package/dist/server/prompt.js +53 -0
  44. package/dist/server/readonly.js +152 -0
  45. package/dist/server/readonlygate.js +123 -0
  46. package/dist/server/renames.js +268 -0
  47. package/dist/server/review.js +334 -0
  48. package/dist/server/reviewapi.js +188 -0
  49. package/dist/server/snapshot.js +49 -0
  50. package/dist/server/sourcestore.js +473 -0
  51. package/dist/server/status.js +360 -0
  52. package/dist/server/talk.js +476 -0
  53. package/dist/server/tokens.js +221 -0
  54. package/dist/server/usage.js +501 -0
  55. package/dist/server/versions.js +82 -0
  56. package/dist/server/view.js +157 -0
  57. package/dist/server/views.js +1167 -0
  58. package/dist/shared/askfollow.js +46 -0
  59. package/dist/shared/board.js +332 -0
  60. package/dist/shared/build.js +2 -0
  61. package/dist/shared/collab.js +148 -0
  62. package/dist/shared/comment.js +285 -0
  63. package/dist/shared/context.js +157 -0
  64. package/dist/shared/doc/authors.js +98 -0
  65. package/dist/shared/doc/build.js +358 -0
  66. package/dist/shared/doc/latex.js +455 -0
  67. package/dist/shared/doc/markdown.js +159 -0
  68. package/dist/shared/doc/model.js +37 -0
  69. package/dist/shared/doc/order.js +107 -0
  70. package/dist/shared/doc/proof.js +272 -0
  71. package/dist/shared/doc/sections.js +93 -0
  72. package/dist/shared/doc/settings.js +75 -0
  73. package/dist/shared/edgecodec.js +102 -0
  74. package/dist/shared/editor.js +64 -0
  75. package/dist/shared/excerpt.js +87 -0
  76. package/dist/shared/graph.js +16 -0
  77. package/dist/shared/items.js +92 -0
  78. package/dist/shared/keys.js +203 -0
  79. package/dist/shared/layout.js +926 -0
  80. package/dist/shared/lexer.js +130 -0
  81. package/dist/shared/library.js +711 -0
  82. package/dist/shared/lostfound.js +150 -0
  83. package/dist/shared/moves.js +243 -0
  84. package/dist/shared/notecard.js +295 -0
  85. package/dist/shared/noteplace.js +505 -0
  86. package/dist/shared/palette.js +46 -0
  87. package/dist/shared/pastgraph.js +95 -0
  88. package/dist/shared/places.js +80 -0
  89. package/dist/shared/projectnotes.js +244 -0
  90. package/dist/shared/query.js +810 -0
  91. package/dist/shared/review.js +642 -0
  92. package/dist/shared/reviewqueue.js +333 -0
  93. package/dist/shared/ring.js +366 -0
  94. package/dist/shared/scope.js +255 -0
  95. package/dist/shared/scopeedit.js +81 -0
  96. package/dist/shared/search.js +191 -0
  97. package/dist/shared/separate.js +178 -0
  98. package/dist/shared/sharesnap.js +280 -0
  99. package/dist/shared/sticky.js +501 -0
  100. package/dist/shared/timeline.js +73 -0
  101. package/dist/shared/titles.js +298 -0
  102. package/dist/shared/viewgraph.js +139 -0
  103. package/dist/shared/views.js +859 -0
  104. package/dist/web/bundle/app.js +48733 -0
  105. package/dist/web/bundle/edges.js +409 -0
  106. package/dist/web/bundle/layout.js +1010 -0
  107. package/dist/web/bundle/pdfworker.js +48322 -0
  108. package/dist/web/index.html +239 -0
  109. package/dist/web/src/motion.js +430 -0
  110. package/dist/web/src/past.js +224 -0
  111. package/dist/web/src/scene.js +787 -0
  112. package/dist/web/style.css +2312 -0
  113. package/docs/agent-api/README.md +91 -0
  114. package/docs/agent-api/advice.md +108 -0
  115. package/docs/agent-api/export.md +120 -0
  116. package/docs/agent-api/following.md +140 -0
  117. package/docs/agent-api/history.md +142 -0
  118. package/docs/agent-api/library.md +65 -0
  119. package/docs/agent-api/mcp.md +104 -0
  120. package/docs/agent-api/notes.md +93 -0
  121. package/docs/agent-api/reading.md +166 -0
  122. package/docs/agent-api/requests.md +94 -0
  123. package/docs/agent-api/talking.md +100 -0
  124. package/docs/agent-api/tcb.md +135 -0
  125. package/docs/agent-api/views.md +124 -0
  126. package/docs/agent-api/words.md +56 -0
  127. package/package.json +39 -4
  128. package/test/toolchains.json +5 -0
@@ -0,0 +1,701 @@
1
+ // Agents' notes (AgentSpec decision 5; AttachmentSpec decisions 13–20, the owner's of 2026-10-11): an
2
+ // agent points people at something by writing a note, as people do from the canvas's menu. A note is
3
+ // the project's unless the agent names a view, whose own note it then is:
4
+ //
5
+ // - a project note is an item (the owner's decision of 2026-10-11): the Project shows every one, any
6
+ // other view those that follow from its scope (in a cluster on its board, on the ring about a
7
+ // cluster under the other clustering that is on it, or carrying a file linked to what it holds) and
8
+ // those added to it, less those taken out of it (shared/items.ts; server/agentviews.ts,
9
+ // `views/items`). It has one place per clustering: in a cluster by key and clustering, at a spot as
10
+ // a fraction of the cluster's radius; beside a declaration, in its cluster under the clustering
11
+ // given; or on the ring around the board (shared/projectnotes.ts, shared/noteplace.ts). One written
12
+ // with `in`, a view's id, is added to that view, as a note a person writes on a board is;
13
+ // - a view note is on that view's board alone, placed as it always was: beside a declaration, in a
14
+ // cluster at an offset, or at a spot of the board (shared/sticky.ts).
15
+ //
16
+ // Either is written through the path a page's takes (server/views.ts), so that its author is
17
+ // attributed as a pin's is and the timeline records it. At level advise an agent writes notes, and
18
+ // edits, moves and deletes its own; at full it also edits, moves and deletes anyone's, as a person
19
+ // can (AgentSpec decision 9). A note is made the other kind by an edit (`kind`), keeping its text,
20
+ // its author and where it is on the board it is switched on. Writing or editing a note can give what
21
+ // it carries (`carries`: files of the library and clips of their pages, AttachmentSpec decision 12),
22
+ // checked against the library as a page's are (server/views.ts). Where its card hangs from its icon
23
+ // (`card`, CanvasSpec "Notes") is read in the listings and set by `note/move` or `note/edit`, at the
24
+ // levels moving a note has. A note of either kind is attached to a declaration (`decl`, the owner's
25
+ // decision of 2026-10-12) by `note` or `note/move`, and detached by `note/move` (`decl` null, or a
26
+ // place given): attached, it is at that declaration under every clustering, wherever a board holds
27
+ // it, its place beside the declaration being where it falls back to when the declaration is gone.
28
+ //
29
+ // The endpoints are server/agent.ts's, in its shape (server/agentviews.ts, AgentEndpoint); the board
30
+ // a note is put on is read here as the pages lay it out (shared/board.ts), at rest, its pins where
31
+ // they were dropped and its hidden levels out.
32
+ import { boardLayout, boardSet, wholeLayout } from "../shared/board.js";
33
+ import { clusterKeys, innerKeys } from "../shared/layout.js";
34
+ import { aboutMembers, itemSpot, itemWhy, linkedOn, linksByFile, noteBoard, placeIn, spotIn, viewSpot } from "../shared/noteplace.js";
35
+ import { invalidProjectNoteOps, MAX_PROJECT_NOTES } from "../shared/projectnotes.js";
36
+ import { aboutAngle, angleOf, ringAround, ringRadians } from "../shared/ring.js";
37
+ import { fileOf } from "../shared/library.js";
38
+ import { declSpot, POINT_UNITS } from "../shared/notecard.js";
39
+ import { boardClusters, CARD_REACH, cleanCarries, DECL_AT, DECL_FAR, DECL_NEAR, invalidCard, invalidCarries, invalidNoteText, keptDecl, MAX_CARRIES, MAX_NOTES, newNoteId, nodeSpot, placeAt, placeIn as viewPlaceIn } from "../shared/sticky.js";
40
+ import { fixedScope, PROJECT, viewTitle } from "../shared/views.js";
41
+ import { fail } from "./agent.js";
42
+ import { graphOf, titleContext } from "./agentviews.js";
43
+ const ok = (body) => ({ status: 200, body });
44
+ const CARRY_HINT = 'carries: [{"file": "<sha256>"}] for an image or a whole PDF, [{"file": "<sha256>", "page": 3, "region": [0.1, 0.2, 0.9, 0.45]}] for a clip of a page (fractions from its top left); GET /api/agent/library lists the files.';
45
+ /** how far beside a declaration's point a note goes, in the board's units (a point's radius is 5) */
46
+ const BESIDE = [9, -9];
47
+ const MODES = ["namespace", "module"];
48
+ const round3 = (v) => Math.round(v * 1000) / 1000;
49
+ /** A project note's place under a clustering, as agents read it. */
50
+ function placeShown(p) {
51
+ if (!p)
52
+ return null;
53
+ if ("ring" in p)
54
+ return { ring: p.ring };
55
+ return { cluster: p.in[0][0], at: [p.in[0][1], p.in[0][2]], around: p.in.slice(1).map((e) => e[0]) };
56
+ }
57
+ /** Where a note is on a board, as agents read it. */
58
+ function spotShown(s, B) {
59
+ if (!s)
60
+ return null;
61
+ if (s.at === "decl")
62
+ return { decl: s.name, at: s.d };
63
+ if (s.at === "cluster")
64
+ return { cluster: B.L.clusters[s.c].key, at: s.f.map(round3) };
65
+ if (s.at === "offset")
66
+ return { cluster: B.L.clusters[s.c].key, offset: s.d };
67
+ if (s.at === "world")
68
+ return { spot: [s.x, s.y] };
69
+ const about = !s.about ? null : "links" in s.about ? { links: s.about.links } : { clustering: s.about.mode, cluster: s.about.key };
70
+ return { ring: s.angle, about };
71
+ }
72
+ /** what `why` says of a note of the project on a board, for an agent */
73
+ const WHY_WORDS = {
74
+ project: "the Project holds every note",
75
+ decl: "it is attached to a declaration on this board",
76
+ cluster: "its cluster is on this board",
77
+ ring: "at the top level here, about its cluster under the other clustering, which is on this board",
78
+ link: "it carries a file linked to what this board holds",
79
+ added: "it was added to this view",
80
+ };
81
+ /** The agents' endpoints about notes. `refuse` says why an agent may not do what needs `need`. */
82
+ export function noteEndpoints(world, refuse) {
83
+ const body = (a) => (a.body && typeof a.body === "object" && !Array.isArray(a.body) ? a.body : {});
84
+ /** Writes say who makes them, and that it is an agent; then what their level allows. */
85
+ const who = (a, need) => {
86
+ if (a.caller.kind !== "agent")
87
+ return fail(400, "This API records what agents do: say you are one.", "Send X-VisualProof-Kind: agent with every request, beside X-VisualProof-Name.");
88
+ if (!a.caller.name)
89
+ return fail(401, "Say who you are first.", "Send X-VisualProof-Name: <your name>, e.g. Claude.", { who: true });
90
+ return refuse(a.caller, need);
91
+ };
92
+ /** A note the agent wrote is its own to change at level advise; anyone else's needs full. */
93
+ const mayChange = (a, n) => who(a, n.agent && n.by === a.caller.name ? "advise" : "full");
94
+ const viewOf = (id) => {
95
+ const key = id === undefined || id === null || id === "" ? PROJECT : id;
96
+ return (typeof key === "string" && world.views.book.get(key)) || fail(404, `There is no view ${String(key)}.`, "GET /api/agent/views lists them; leave view out for the Project.");
97
+ };
98
+ const graph = () => graphOf(world) ?? fail(503, "The graph has not been read yet.", "The server is reading the project; ask again in a few seconds.", { retryAfter: 5 }, { "retry-after": "5" });
99
+ /** whether a key is a cluster of the graph on show by a clustering, and whether a name is one of
100
+ * its declarations (where a note attached to one falls back to its places when it is gone) */
101
+ const keys = new WeakMap();
102
+ const exists = (g) => {
103
+ const f = (m, key) => {
104
+ const k = keys.get(g) ?? {};
105
+ keys.set(g, k);
106
+ return (k[m] ??= clusterKeys(m, g.nodes)).has(key);
107
+ };
108
+ f.decl = (name) => g.byName.has(name);
109
+ return f;
110
+ };
111
+ /** View `v`'s board by `mode` (its clustering by default), at rest, under the levels it hides. */
112
+ const boardOf = (v, mode = v.clustering) => {
113
+ const G = graph();
114
+ if ("status" in G)
115
+ return G;
116
+ const bs = boardSet(G.g, world.review.data, v.scope);
117
+ const hidden = v.hidden[mode];
118
+ const L = bs.whole ? wholeLayout(G.meta.layouts[mode], G.g.nodes, mode, hidden) : boardLayout(bs.sub.graph, mode, bs.key, world.views.book.memoryOf(v.id, mode), hidden).layout;
119
+ const g = bs.whole ? G.g : bs.sub.graph;
120
+ const clusters = boardClusters(L, v.pins[mode], v.folds[mode]);
121
+ const discs = clusters.flatMap((c) => (c.parent < 0 ? [c] : []));
122
+ L.cluster.forEach((c, i) => {
123
+ if (c < 0)
124
+ discs.push({ x: L.x[i], y: L.y[i], r: 8 });
125
+ });
126
+ return { v, L, g, mode, clusters, nb: noteBoard(L, mode, g.nodes), ring: ringAround(discs) };
127
+ };
128
+ /** The note `id`: the project's, or view `view`'s own; with `view` given, that view's first. */
129
+ const find = (id, view) => {
130
+ const p = world.views.book.notes.find((n) => n.id === id);
131
+ const v = view === undefined || view === null || view === "" ? null : viewOf(view);
132
+ if (v && !("id" in v))
133
+ return v;
134
+ const own = (v ?? world.views.book.get(PROJECT)).notes?.find((n) => n.id === id);
135
+ if (own && (v || !p))
136
+ return { kind: "view", note: own, v: v ?? world.views.book.get(PROJECT) };
137
+ if (p)
138
+ return { kind: "project", note: p };
139
+ return fail(404, `There is no note ${String(id).slice(0, 40)}${v ? ` on view ${v.id}` : ""}.`, "GET /api/agent/notes lists them (view=… for a view's own).");
140
+ };
141
+ /** Whether board `B` shows project note `n`, and why (shared/noteplace.ts, itemWhy), and where it
142
+ * is there; null where it does not show it. */
143
+ const projectHere = (n, B) => {
144
+ const ex = exists(graphOf(world).g);
145
+ const links = linksByFile(world.review.data.links);
146
+ const why = itemWhy(n, { all: fixedScope(B.v), items: B.v.items }, B.nb, ex, links);
147
+ return why ? { why, spot: itemSpot(n, B.nb, ex, linkedOn(n, B.nb, links)) } : null;
148
+ };
149
+ /** What a note carries as the agents' API tells it: each file or clip with what it is (an image, a
150
+ * PDF, a clip of a page) and the file's title, or that the library has no record of it now. */
151
+ const carriesShown = (cs) => (cs ?? []).map((c) => {
152
+ const f = fileOf(world.review.data, c.file);
153
+ const what = !f ? "unknown" : f.type !== "pdf" ? "image" : c.region ? "clip" : "pdf";
154
+ return { ...c, what, title: f?.title ?? null, ...(f?.archived ? { archived: true } : {}) };
155
+ });
156
+ /** A note as the agents' API tells it: its kind, who wrote it, what it carries, its places, and
157
+ * where it is on board `B` (null where that board does not show it). */
158
+ const shown = (kind, n, B, view) => {
159
+ const base = {
160
+ id: n.id,
161
+ kind,
162
+ text: n.text,
163
+ open: n.open,
164
+ by: n.by,
165
+ agent: !!n.agent,
166
+ at: n.at,
167
+ ...(n.edited ? { edited: { by: n.edited.by, agent: !!n.edited.agent, at: n.edited.at } } : {}),
168
+ carries: carriesShown(n.carries),
169
+ // where its card hangs from its icon, in pixels of the screen; null where it hangs by default
170
+ card: n.card ?? null,
171
+ // the declaration it is attached to, its icon at `at` from its point in the board's units;
172
+ // null where it is attached to none
173
+ decl: n.decl ?? null,
174
+ };
175
+ if (kind === "project") {
176
+ const p = n;
177
+ const h = B ? projectHere(p, B) : null;
178
+ return { ...base, places: Object.fromEntries(MODES.map((m) => [m, placeShown(p.places[m])])), here: h && B ? spotShown(h.spot, B) : null, why: h ? h.why : null };
179
+ }
180
+ const s = n;
181
+ const index = new Map(B?.L.clusters.map((c, k) => [c.key, k]) ?? []);
182
+ return { ...base, view, clustering: s.place.mode, here: B ? spotShown(viewSpot(s, B.nb, (key) => index.get(key) ?? -1), B) : null };
183
+ };
184
+ /** Apply operations to view `v`'s notes, or the project's, as the agent's, through the path a
185
+ * page's take. */
186
+ const applyView = (a, v, ops) => {
187
+ const err = world.views.arrange(v.id, ops, null, a.caller.name, true);
188
+ return err ? fail(400, err, CARRY_HINT) : null;
189
+ };
190
+ const applyProject = (a, ops, written = null) => {
191
+ const why = invalidProjectNoteOps(ops);
192
+ if (why)
193
+ return fail(400, `The note cannot be taken: ${why}.`, "A ring's angle is in degrees (within ±3600); a spot, a fraction of the radius.");
194
+ const err = world.views.arrangeNotes(ops, null, a.caller.name, true, null, written);
195
+ return err ? fail(400, err, CARRY_HINT) : null;
196
+ };
197
+ /** Where a request says a note's card hangs from its icon: an offset in pixels of the screen, null
198
+ * for the default, undefined where it says nothing; or the answer that refuses it. */
199
+ const cardOf = (x) => {
200
+ if (x === undefined)
201
+ return undefined;
202
+ if (x === null)
203
+ return null;
204
+ return invalidCard(x) ? fail(400, `card is [x, y], the card's centre from the icon's in pixels of the screen (x right, y down), each within ±${CARD_REACH}; null hangs it as by default.`, "") : [Math.round(x[0]), Math.round(x[1])];
205
+ };
206
+ /** What a request says a note carries, checked as to its shape (the library checks the files):
207
+ * the list, undefined where it says nothing, or the answer that refuses it. A file alone may be
208
+ * given by its hash. */
209
+ const carriesOf = (x) => {
210
+ if (x === undefined)
211
+ return undefined;
212
+ const list = Array.isArray(x) ? x.map((c) => (typeof c === "string" ? { file: c } : c)) : x;
213
+ const why = invalidCarries(list);
214
+ if (why)
215
+ return fail(400, why.startsWith("carries") ? `${why}: carries is a list of at most ${MAX_CARRIES} files of the library and clips of their pages.` : why, CARRY_HINT);
216
+ return cleanCarries(list).carries ?? [];
217
+ };
218
+ const modeOf = (x, dflt) => x === undefined || x === null || x === "" ? dflt : MODES.includes(x) ? x : fail(400, "clustering is namespace or module.", "");
219
+ /** What a request says a note is attached to: a declaration of the graph on show by its full name,
220
+ * or {name, at} with its icon's offset from the point in the board's units (DECL_AT by default);
221
+ * null for none, undefined where it says nothing; or the answer that refuses it. */
222
+ const declIn = (x) => {
223
+ if (x === undefined)
224
+ return undefined;
225
+ if (x === null)
226
+ return null;
227
+ const o = (typeof x === "string" ? { name: x } : x);
228
+ const hint = `decl is a declaration's full name, or {"name": …, "at": [x, y]} with its icon's offset from the declaration's point in the board's units, where a declaration's point is ${POINT_UNITS} across (x right, y down, between ${DECL_NEAR} and ${DECL_FAR} away; default [${DECL_AT.join(", ")}]); null detaches it.`;
229
+ if (!o || typeof o !== "object" || typeof o.name !== "string" || !o.name)
230
+ return fail(400, "decl names a declaration.", hint);
231
+ const at = o.at === undefined ? DECL_AT : o.at;
232
+ if (!Array.isArray(at) || at.length !== 2 || !at.every((v) => typeof v === "number" && Number.isFinite(v)))
233
+ return fail(400, "decl.at is [x, y], two finite numbers.", hint);
234
+ const G = graph();
235
+ if ("status" in G)
236
+ return G;
237
+ if (!G.g.byName.has(o.name))
238
+ return fail(404, `There is no declaration ${o.name}.`, "GET /api/agent/search finds one.");
239
+ return { name: o.name, at: keptDecl(at) };
240
+ };
241
+ /** The places of a note of the project attached to declaration `name`, where it falls back to: beside
242
+ * it under both clusterings, in its cluster on the Project. */
243
+ const besides = (name) => {
244
+ const places = {};
245
+ for (const m of MODES) {
246
+ const p = projectPlace({ name }, m);
247
+ if (p && "status" in p)
248
+ return p;
249
+ if (p)
250
+ places[m] = p;
251
+ }
252
+ return places;
253
+ };
254
+ const pointOf = (x) => {
255
+ if (x === undefined)
256
+ return null;
257
+ return Array.isArray(x) && x.length === 2 && x.every((v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) < 1e7) ? x : fail(400, "at is [x, y], two finite numbers.", "For a note of the project, a fraction of the cluster's radius ([0.5, -0.3]); for a view's note, an offset in the cluster or a spot of the board.");
258
+ };
259
+ /** A project note's place under `mode`, from what the request says: a cluster by key (with `at`, a
260
+ * fraction of its radius), beside a declaration (in its cluster), or on the ring (`ring`: an
261
+ * angle in degrees clockwise from the top, or true for wherever the ring puts it). Undefined:
262
+ * no place there (the ring, about what it is about). */
263
+ const projectPlace = (b, mode) => {
264
+ const G = graph();
265
+ if ("status" in G)
266
+ return G;
267
+ if (b.ring !== undefined) {
268
+ if (b.ring === true)
269
+ return undefined;
270
+ if (typeof b.ring !== "number" || !Number.isFinite(b.ring))
271
+ return fail(400, "ring is an angle in degrees, clockwise from the top (0 at the top, 90 on the right), or true for wherever the ring puts it.", "");
272
+ return { ring: b.ring };
273
+ }
274
+ const at = pointOf(b.at);
275
+ if (at && !Array.isArray(at))
276
+ return at;
277
+ // laid out as the Project shows it by that clustering, which holds everything
278
+ const B = boardOf(world.views.book.project(), mode);
279
+ if ("status" in B)
280
+ return B;
281
+ if (typeof b.name === "string") {
282
+ const i = B.g.byName.get(b.name);
283
+ if (i === undefined)
284
+ return fail(404, `There is no declaration ${b.name}.`, "GET /api/agent/search finds one.");
285
+ const key = innerKeys(mode, B.g.nodes)[i];
286
+ if (!key)
287
+ return undefined;
288
+ const [x, y] = nodeSpot(B.L, B.v.pins[mode], B.clusters, i, b.name);
289
+ return spotIn(key, [x + BESIDE[0], y + BESIDE[1]], B.clusters, B.nb) ?? undefined;
290
+ }
291
+ if (typeof b.cluster === "string") {
292
+ if (!exists(G.g)(mode, b.cluster))
293
+ return fail(404, `There is no cluster ${b.cluster} by ${mode}.`, `GET /api/agent/views/view?id=project&mode=${mode} lists the clusters; a folder's key is its modules' path with dots (Mathlib.NumberTheory.FLT), a namespace's its name.`);
294
+ // a fraction of the radius, brought inside the circle as the page brings a spot let go
295
+ const [fx, fy] = at ?? [0, 0];
296
+ const len = Math.hypot(fx, fy);
297
+ const f = len > 1 ? [round3(fx / len), round3(fy / len)] : [fx, fy];
298
+ const c = B.nb.stands.get(b.cluster);
299
+ // the spot in the circle drawn for it, and in every one around it
300
+ const C = c !== undefined && c >= 0 ? B.clusters[c] : null;
301
+ const spot = C ? [C.x + f[0] * C.r, C.y + f[1] * C.r] : [0, 0];
302
+ const p = C ? spotIn(b.cluster, spot, B.clusters, B.nb) : null;
303
+ const chain = p && "in" in p ? p.in : [[b.cluster, f[0], f[1]]];
304
+ chain[0] = [b.cluster, f[0], f[1]];
305
+ return { in: chain };
306
+ }
307
+ return fail(400, "Say where: cluster (with clustering, and at a fraction of its radius), name (beside a declaration, in its cluster), or ring (an angle, or true).", 'e.g. {"text": "…", "cluster": "Fermat42", "clustering": "namespace"}.');
308
+ };
309
+ /** A view note's place on its view's board, from what the request says: beside a declaration, in a
310
+ * cluster at an offset, or at a spot of the board — as it always was. */
311
+ const viewPlace = (b, B) => {
312
+ const { L, g, mode, clusters } = B;
313
+ const at = pointOf(b.at);
314
+ if (at && !Array.isArray(at))
315
+ return at;
316
+ if (typeof b.name === "string") {
317
+ const i = g.byName.get(b.name);
318
+ if (i === undefined)
319
+ return fail(404, `${b.name} is not on view ${B.v.id}.`, "A note beside a declaration is on a view that holds it.");
320
+ const [x, y] = nodeSpot(L, B.v.pins[mode], clusters, i, b.name);
321
+ const spot = [x + BESIDE[0], y + BESIDE[1]];
322
+ return L.cluster[i] >= 0 ? viewPlaceIn(mode, spot, clusters, L.cluster[i]) : placeAt(mode, spot, []);
323
+ }
324
+ if (typeof b.cluster === "string") {
325
+ const c = L.clusters.findIndex((x) => x.key === b.cluster);
326
+ if (c < 0)
327
+ return fail(404, `View ${B.v.id} has no cluster ${b.cluster} by ${mode}.`, "GET /api/agent/views/view?id=… lists its clusters.");
328
+ const [dx, dy] = at ?? [0, 0];
329
+ return viewPlaceIn(mode, [clusters[c].x + dx, clusters[c].y + dy], clusters, c);
330
+ }
331
+ if (at)
332
+ return placeAt(mode, at, clusters);
333
+ return fail(400, "Say where: name (beside a declaration), cluster (with at, an offset in it), or at (a spot of the board).", 'e.g. {"text": "…", "view": "…", "name": "Nat.exists_infinite_primes"}.');
334
+ };
335
+ /** Where a note is at rest on board `B`, in the board's world: what a switch to the other kind keeps
336
+ * (its spot in its cluster, else its point on the ring). */
337
+ const restOn = (kind, n, B) => {
338
+ const index = new Map(B.L.clusters.map((c, k) => [c.key, k]));
339
+ const s = kind === "project" ? projectHere(n, B)?.spot : viewSpot(n, B.nb, (key) => index.get(key) ?? -1);
340
+ if (!s)
341
+ return null;
342
+ // attached to a declaration: at its offset from the point, a place on the board, as the page
343
+ // draws it at rest
344
+ if (s.at === "decl") {
345
+ if (s.j < 0)
346
+ return null;
347
+ const [x, y] = B.L.cluster[s.j] >= 0 ? nodeSpot(B.L, B.v.pins[B.mode], B.clusters, s.j, s.name) : [B.L.x[s.j], B.L.y[s.j]];
348
+ return declSpot([x, y], s.d);
349
+ }
350
+ if (s.at === "cluster")
351
+ return [B.clusters[s.c].x + s.f[0] * B.clusters[s.c].r, B.clusters[s.c].y + s.f[1] * B.clusters[s.c].r];
352
+ if (s.at === "offset")
353
+ return [B.clusters[s.c].x + s.d[0], B.clusters[s.c].y + s.d[1]];
354
+ if (s.at === "world")
355
+ return [s.x, s.y];
356
+ if (!B.ring)
357
+ return null;
358
+ let a = s.angle !== null ? ringRadians(s.angle) : null;
359
+ if (a === null && s.about) {
360
+ const js = aboutMembers(B.nb, s.about);
361
+ if (js.length) {
362
+ let x = 0, y = 0;
363
+ for (const j of js) {
364
+ const p = B.L.cluster[j] >= 0 ? nodeSpot(B.L, B.v.pins[B.mode], B.clusters, j, B.g.nodes[j].name) : [B.L.x[j], B.L.y[j]];
365
+ x += p[0];
366
+ y += p[1];
367
+ }
368
+ a = aboutAngle(B.ring, [x / js.length, y / js.length]);
369
+ }
370
+ }
371
+ a ??= -Math.PI / 2;
372
+ return [B.ring.x + B.ring.r * Math.cos(a), B.ring.y + B.ring.r * Math.sin(a)];
373
+ };
374
+ const list = (a) => {
375
+ const v = viewOf(a.query.get("view"));
376
+ if (!("id" in v))
377
+ return v;
378
+ const B = boardOf(v);
379
+ if ("status" in B)
380
+ return B;
381
+ const all = a.query.get("all") === "1" || a.query.get("all") === "true";
382
+ const project = world.views.book.notes.filter((n) => all || projectHere(n, B) !== null);
383
+ return ok({
384
+ view: v.id,
385
+ title: viewTitle(v, titleContext(world)),
386
+ ...(v.items ? { items: v.items } : {}),
387
+ notes: [...project.map((n) => shown("project", n, B)), ...(v.notes ?? []).map((n) => shown("view", n, B, v.id))],
388
+ note: `kind project: a note of the project, an item: the Project shows every one, any other view what follows from its scope and what was added to it, less what was taken out (POST /api/agent/views/items). why: ${Object.entries(WHY_WORDS).map(([k, w]) => `${k}, ${w}`).join("; ")}. places: its place per clustering (a cluster with its spot as a fraction of the radius, or ring: an angle in degrees clockwise from the top; null: none yet, on the ring about its cluster under the other clustering); here: where it is on this view's board; card: where its card hangs from its icon, in pixels of the screen (null: by default). kind view: this view's own note, here on its board. ` +
389
+ (all ? "all: every note of the project, here and why null where this board does not show it." : "Only the project's notes this board shows are listed: all=1 lists every one."),
390
+ });
391
+ };
392
+ const add = (a) => {
393
+ const no = who(a, "advise");
394
+ if (no)
395
+ return no;
396
+ const b = body(a);
397
+ const text = typeof b.text === "string" ? b.text.trim() : "";
398
+ const bad = invalidNoteText(text);
399
+ if (bad)
400
+ return fail(400, bad, 'Send {text, …where}: what people should look at there, e.g. "the swap is applied twice here"; at most 2,000 characters, with **bold**, *italics*, `code`, and full names in backticks as links.');
401
+ const carried = carriesOf(b.carries);
402
+ if (carried && !Array.isArray(carried))
403
+ return carried;
404
+ // attached to a declaration: that is where it is, under every clustering
405
+ const decl = declIn(b.decl);
406
+ if (decl && "status" in decl)
407
+ return decl;
408
+ if (decl && placing(b))
409
+ return fail(400, "Say where once: decl attaches the note to a declaration, its place beside it; cluster, name, ring and at place it elsewhere.", "");
410
+ const stamp = { open: b.open !== false, by: a.caller.name, agent: true, at: new Date().toISOString(), ...(carried?.length ? { carries: carried } : {}), ...(decl ? { decl: decl } : {}) };
411
+ // a view named: that view's own note, on its board alone
412
+ if (b.view !== undefined && b.view !== null && b.view !== "") {
413
+ const v = viewOf(b.view);
414
+ if (!("id" in v))
415
+ return v;
416
+ if ((v.notes?.length ?? 0) >= MAX_NOTES)
417
+ return fail(409, `View ${v.id} holds ${MAX_NOTES} notes already.`, "Delete one first, or write a note of the project (leave view out).");
418
+ const B = boardOf(v);
419
+ if ("status" in B)
420
+ return B;
421
+ const place = viewPlace(decl ? { name: decl.name } : b, B);
422
+ if ("status" in place)
423
+ return place;
424
+ const card = cardOf(b.card);
425
+ if (card && !Array.isArray(card))
426
+ return card;
427
+ const note = { id: newNoteId(), text, place, ...stamp, ...(card ? { card: card } : {}) };
428
+ const err = applyView(a, v, [{ op: "note", note }]);
429
+ if (err)
430
+ return err;
431
+ const made = world.views.book.get(v.id)?.notes?.find((n) => n.id === note.id) ?? note;
432
+ return ok({ ok: true, ...shown("view", made, B, v.id) });
433
+ }
434
+ if (world.views.book.notes.length >= MAX_PROJECT_NOTES)
435
+ return fail(409, `The project holds ${MAX_PROJECT_NOTES} notes already.`, "Delete one first (yours at advise, anyone's at full).");
436
+ // written in a view: added to it, as a note people write on a board is (the owner's decision of
437
+ // 2026-10-11)
438
+ const written = b.in === undefined || b.in === null || b.in === "" ? null : viewOf(b.in);
439
+ if (written && !("id" in written))
440
+ return written;
441
+ const mode = modeOf(b.clustering, "namespace");
442
+ if (typeof mode !== "string")
443
+ return mode;
444
+ // attached, it falls back to beside the declaration under both clusterings
445
+ const places = decl ? besides(decl.name) : null;
446
+ if (places && "status" in places)
447
+ return places;
448
+ const place = decl ? undefined : projectPlace(b, mode);
449
+ if (place && "status" in place)
450
+ return place;
451
+ const card = cardOf(b.card);
452
+ if (card && !Array.isArray(card))
453
+ return card;
454
+ const note = { id: newNoteId(), text, places: places ?? (place ? { [mode]: place } : {}), ...stamp, ...(card ? { card: card } : {}) };
455
+ const err = applyProject(a, [{ op: "pnote", note }], written ? written.id : null);
456
+ if (err)
457
+ return err;
458
+ const made = world.views.book.notes.find((n) => n.id === note.id) ?? note;
459
+ const B = boardOf(written ?? world.views.book.project());
460
+ return ok({ ok: true, ...shown("project", made, "status" in B ? null : B), ...(written ? { in: written.id } : {}) });
461
+ };
462
+ const edit = (a) => {
463
+ const b = body(a);
464
+ const f = find(b.id, b.view);
465
+ if ("status" in f)
466
+ return f;
467
+ const no = mayChange(a, f.note);
468
+ if (no)
469
+ return no;
470
+ let text;
471
+ if (b.text !== undefined) {
472
+ text = typeof b.text === "string" ? b.text.trim() : "";
473
+ const bad = invalidNoteText(text);
474
+ if (bad)
475
+ return fail(400, bad, "At most 2,000 characters, plain text with simple Markdown.");
476
+ }
477
+ const carries = carriesOf(b.carries);
478
+ if (carries && !Array.isArray(carries))
479
+ return carries;
480
+ if (b.open !== undefined && typeof b.open !== "boolean")
481
+ return fail(400, "open is true (its text shown) or false (collapsed).", "");
482
+ if (b.kind !== undefined && b.kind !== "project" && b.kind !== "view")
483
+ return fail(400, "kind is project (the Project, and every view whose scope gives it or that it was added to) or view (that view's own).", "");
484
+ const card = cardOf(b.card);
485
+ if (card && !Array.isArray(card))
486
+ return card;
487
+ const switching = b.kind !== undefined && b.kind !== f.kind;
488
+ if (text === undefined && carries === undefined && b.open === undefined && card === undefined && !switching)
489
+ return fail(400, "Send text, carries, open, card, kind, or several.", "POST /api/agent/note/move moves a note.");
490
+ // its text and what it carries are one edit: the text as it is where only what it carries changes
491
+ const edit = text !== undefined || carries !== undefined ? { id: f.note.id, text: text ?? f.note.text, ...(carries !== undefined ? { carries } : {}) } : null;
492
+ // made the other kind, where it is on the board of the view it becomes or leaves: worked out
493
+ // first, so that a switch refused changes nothing
494
+ let to = null;
495
+ if (switching) {
496
+ if (f.kind === "project" && (b.view === undefined || b.view === null || b.view === ""))
497
+ return fail(400, "Say whose it becomes: view, the view's id.", "");
498
+ const v = f.kind === "view" ? f.v : viewOf(b.view);
499
+ if (!("id" in v))
500
+ return v;
501
+ const B = boardOf(v);
502
+ if ("status" in B)
503
+ return B;
504
+ const at = restOn(f.kind, f.note, B);
505
+ if (!at)
506
+ return fail(409, `View ${v.id}'s board does not show this note: it cannot be made that view's where it is.`, "Move it onto that board first, or write the view's note anew.");
507
+ to = { v, B, place: f.kind === "project" ? placeAt(B.mode, at, B.clusters) : placeIn(at, B.clusters, B.nb) ?? (B.ring ? { ring: angleOf(B.ring, at[0], at[1]) } : { ring: 0 }) };
508
+ }
509
+ if (f.kind === "project") {
510
+ const ops = [];
511
+ if (edit)
512
+ ops.push({ op: "pnote-edit", ...edit });
513
+ if (b.open !== undefined)
514
+ ops.push({ op: "pnote-open", id: f.note.id, open: b.open });
515
+ if (card !== undefined)
516
+ ops.push({ op: "pnote-card", id: f.note.id, card: card });
517
+ const err = ops.length ? applyProject(a, ops) : null;
518
+ if (err)
519
+ return err;
520
+ }
521
+ else {
522
+ const ops = [];
523
+ if (edit)
524
+ ops.push({ op: "note-edit", ...edit });
525
+ if (b.open !== undefined)
526
+ ops.push({ op: "note-open", id: f.note.id, open: b.open });
527
+ if (card !== undefined)
528
+ ops.push({ op: "note-card", id: f.note.id, card: card });
529
+ const err = ops.length ? applyView(a, f.v, ops) : null;
530
+ if (err)
531
+ return err;
532
+ }
533
+ if (!to) {
534
+ const now = find(f.note.id, f.kind === "view" ? f.v.id : undefined);
535
+ if ("status" in now)
536
+ return now;
537
+ const B = boardOf(now.kind === "view" ? now.v : world.views.book.project());
538
+ return ok({ ok: true, ...shown(now.kind, now.note, "status" in B ? null : B, now.kind === "view" ? now.v.id : undefined) });
539
+ }
540
+ const err = world.views.switchNote(f.note.id, to.v.id, f.kind === "project" ? "view" : "project", to.place, to.B.mode, null, a.caller.name, true);
541
+ if (err)
542
+ return fail(400, err, "");
543
+ const now = find(f.note.id, f.kind === "project" ? to.v.id : undefined);
544
+ if ("status" in now)
545
+ return now;
546
+ return ok({ ok: true, ...shown(now.kind, now.note, to.B, now.kind === "view" ? to.v.id : undefined) });
547
+ };
548
+ const move = (a) => {
549
+ const b = body(a);
550
+ const f = find(b.id, b.view);
551
+ if ("status" in f)
552
+ return f;
553
+ const no = mayChange(a, f.note);
554
+ if (no)
555
+ return no;
556
+ // where its card hangs, with its place or alone: shared as its place is, and no event
557
+ const card = cardOf(b.card);
558
+ if (card && !Array.isArray(card))
559
+ return card;
560
+ // attached to a declaration (decl), detached (decl null, or detach), or neither said
561
+ const decl = b.detach === true && b.decl === undefined ? null : declIn(b.decl);
562
+ if (decl && "status" in decl)
563
+ return decl;
564
+ const moving = placing(b);
565
+ if (decl && moving)
566
+ return fail(400, "Say where once: decl attaches the note to a declaration, its place beside it; cluster, name, ring and at place it elsewhere, and detach it.", "");
567
+ // placed elsewhere, an attached note is detached: its place is where it was put
568
+ const detach = decl === null || (moving && !!f.note.decl);
569
+ const declOp = decl ? decl : detach ? null : undefined;
570
+ if (f.kind === "view") {
571
+ const B = boardOf(f.v);
572
+ if ("status" in B)
573
+ return B;
574
+ const ops = [];
575
+ if (declOp !== undefined)
576
+ ops.push({ op: "note-decl", id: f.note.id, decl: declOp });
577
+ if (decl || moving || (card === undefined && declOp === undefined)) {
578
+ const place = viewPlace(decl ? { name: decl.name } : b, B);
579
+ if ("status" in place)
580
+ return place;
581
+ ops.push({ op: "note-move", id: f.note.id, place });
582
+ }
583
+ if (card !== undefined)
584
+ ops.push({ op: "note-card", id: f.note.id, card: card });
585
+ const err = applyView(a, f.v, ops);
586
+ if (err)
587
+ return err;
588
+ return ok({ ok: true, ...shown("view", f.v.notes.find((n) => n.id === f.note.id), B, f.v.id) });
589
+ }
590
+ const ops = [];
591
+ const mode = modeOf(b.clustering, "namespace");
592
+ if (typeof mode !== "string")
593
+ return mode;
594
+ if (declOp !== undefined)
595
+ ops.push({ op: "pnote-decl", id: f.note.id, decl: declOp });
596
+ if (decl) {
597
+ // attached: it falls back to beside the declaration under both clusterings
598
+ const places = besides(decl.name);
599
+ if ("status" in places)
600
+ return places;
601
+ for (const m of MODES)
602
+ if (places[m])
603
+ ops.push({ op: "pnote-move", id: f.note.id, mode: m, place: places[m] });
604
+ }
605
+ else if (moving || (card === undefined && declOp === undefined)) {
606
+ const place = projectPlace(b, mode);
607
+ if (place && "status" in place)
608
+ return place;
609
+ ops.push({ op: "pnote-move", id: f.note.id, mode, place: place ?? null });
610
+ }
611
+ if (card !== undefined)
612
+ ops.push({ op: "pnote-card", id: f.note.id, card: card });
613
+ const err = applyProject(a, ops);
614
+ if (err)
615
+ return err;
616
+ const B = boardOf(world.views.book.project());
617
+ const said = decl
618
+ ? `Attached to ${decl.name} under every clustering, wherever a board holds it; beside it in its clusters is where it falls back to.`
619
+ : decl === null && !moving
620
+ ? "Detached: it is at its place under each clustering again."
621
+ : moving || card === undefined
622
+ ? `Moved under the ${mode} clustering alone: its place under the other is as it was.${detach ? " Detached from the declaration it was attached to." : ""}`
623
+ : null;
624
+ return ok({ ok: true, ...shown("project", world.views.book.notes.find((n) => n.id === f.note.id), "status" in B ? null : B), ...(said ? { note: said } : {}) });
625
+ };
626
+ const remove = (a) => {
627
+ const b = body(a);
628
+ const f = find(b.id, b.view);
629
+ if ("status" in f)
630
+ return f;
631
+ const no = mayChange(a, f.note);
632
+ if (no)
633
+ return no;
634
+ if (f.kind === "project") {
635
+ const err = applyProject(a, [{ op: "pnote-delete", id: f.note.id }]);
636
+ if (err)
637
+ return err;
638
+ }
639
+ else {
640
+ const err = applyView(a, f.v, [{ op: "note-delete", id: f.note.id }]);
641
+ if (err)
642
+ return err;
643
+ }
644
+ return ok({ ok: true, kind: f.kind, ...(f.kind === "view" ? { view: f.v.id } : {}), deleted: f.note.id, text: f.note.text, note: "Deleted for everyone; the timeline keeps its text and says you did it." });
645
+ };
646
+ /** Whether a request places a note somewhere other than a declaration it is attached to. */
647
+ function placing(b) {
648
+ return b.cluster !== undefined || b.name !== undefined || b.ring !== undefined || b.at !== undefined;
649
+ }
650
+ const where = {
651
+ clustering: "namespace (default) or module: the clustering a note of the project is placed under; it has no place under the other until someone puts it there",
652
+ cluster: "a cluster's key by that clustering — with at: [x, y], the spot as a fraction of its radius (default [0, 0], its centre) — or —",
653
+ name: "a declaration: the note goes in its cluster by that clustering, just beside its point — or —",
654
+ ring: "an angle in degrees clockwise from the top (0 at the top, 90 on the right): on the ring around the board, at the top level; true: no place by that clustering, so the ring puts it at the angle of its cluster under the other — or —",
655
+ decl: `a declaration's full name, or {name, at}: the note is attached to it — its place under every clustering, shown wherever a board holds it, a thin dashed leader from its icon to the point — its icon at \`at\` from the point, a place on the board in its units, where a declaration's point is ${POINT_UNITS} across, so that it comes closer on the screen as the zoom goes out (x right, y down, between ${DECL_NEAR} and ${DECL_FAR} away; default [${DECL_AT.join(", ")}]); where the declaration is gone with no rename, it falls back to beside it in its cluster`,
656
+ };
657
+ return [
658
+ {
659
+ method: "GET", path: "/api/agent/notes", about: "The notes a view's board shows — of the project's, every one on the Project, elsewhere those its scope gives and those added to it, less those taken out; and the view's own — with where each is, why it shows, who wrote it, its kind and where its card hangs.",
660
+ params: { view: "a view's id (default: the Project)", all: "1: every note of the project, shown on that board or not" },
661
+ returns: "{view, title, items?: {add, remove} (the view's lists), notes: [{id, kind: project|view, text, open, by, agent, at, edited?, carries, card: [x, y] | null, decl: {name, at} | null, places? and why? (project: project, decl, cluster, ring, link or added; null where this board does not show it), view? (view), here}], note}",
662
+ run: (a) => list(a),
663
+ },
664
+ {
665
+ method: "POST", path: "/api/agent/note", brief: "a note that points people at something", needs: "advise",
666
+ about: "Point people at something: a note, marked as yours. Without view, a note of the project — an item: on the Project, and on every view whose scope gives it (attached to a declaration it holds, in its cluster, on the ring about its cluster under the other clustering, or carrying a file linked to what the view holds) or that it is added to (in: the view you write it in) — attached to a declaration (decl), placed by cluster, beside a declaration, or on the ring. With view, that view's own note, on its board alone — attached to a declaration, beside one, in a cluster at an offset, or at a spot of the board. Activity records it. It moves nobody's view.",
667
+ params: {
668
+ text: "what to look at there (at most 2,000 characters): plain text with **bold**, *italics* and `code`; a full name in backticks becomes a link",
669
+ view: "a view's id: the note is that view's own; leave it out for a note of the project",
670
+ in: "a note of the project: the view you write it in, which it is added to, so that it shows there wherever it is put (as a note a person writes on a board is)",
671
+ ...where,
672
+ at: "with cluster: for a note of the project, a fraction of the radius; for a view's note, an offset in the cluster, or alone a spot of its board",
673
+ open: "true (default): its text shown; false: collapsed to its first words",
674
+ carries: `what it carries besides its text, at most ${MAX_CARRIES}: files of the library — {file: <sha256>} for an image or a whole PDF (a hash alone does too) — and clips, {file, page, region: [left, top, right, bottom]} in fractions of the page from its top left; GET /api/agent/library lists the files, library/text gives a page's words with their boxes`,
675
+ card: `where its card hangs from its icon: [x, y], the card's centre from the icon's in pixels of the screen (x right, y down), each within ±${CARD_REACH}; left out, by default (up and to the right, outward on the ring)`,
676
+ },
677
+ returns: "{ok, id, kind, text, open, by, agent, at, carries: [{file, page?, region?, what: image|pdf|clip, title}], card, decl, places? and why? | view?, here, in?}",
678
+ run: (a) => add(a),
679
+ },
680
+ {
681
+ method: "POST", path: "/api/agent/note/edit", needs: "advise",
682
+ about: "Edit a note — your own at advise, anyone's at full: its text, what it carries, whether it shows its text, where its card hangs, or its kind (kind view with view: only in that view; kind project: the project's again, added to that view), keeping its text, author, what it carries and where it is on that view's board. Activity records an edit of its text or of what it carries.",
683
+ params: { id: "the note's id (GET /api/agent/notes)", view: "the view whose note it is, or becomes", text: "its new text", carries: "what it carries from now on, the whole list as for note ([] for nothing): what it carries already stays as it is, a file or a clip added must be in the library and not archived", open: "true: its text shown; false: collapsed", card: `[x, y]: where its card hangs from its icon, in pixels of the screen (each within ±${CARD_REACH}); null: by default`, kind: "project or view" },
684
+ returns: "{ok, id, kind, text, open, …}",
685
+ run: (a) => edit(a),
686
+ },
687
+ {
688
+ method: "POST", path: "/api/agent/note/move", needs: "advise",
689
+ about: "Move a note — your own at advise, anyone's at full. A note of the project moves under one clustering alone, on every board of that clustering; a view's note moves on its board. decl attaches either kind to a declaration, its place under every clustering; decl null detaches it, and so does any other place given. Its card, where it hangs from its icon, moves with card, alone or with its place: shared as its place is, no event.",
690
+ params: { id: "the note's id", view: "for a view's note: its view", ...where, decl: `${where.decl}; null: detached, at its place under each clustering again`, detach: "true: as decl null", at: "with cluster: a fraction of the radius (a view's note: an offset), or alone a spot of a view's board", card: `[x, y]: where its card hangs from its icon, in pixels of the screen (each within ±${CARD_REACH}); null: by default; alone, the note stays where it is` },
691
+ returns: "{ok, id, kind, decl, places? | view?, here, note?}",
692
+ run: (a) => move(a),
693
+ },
694
+ {
695
+ method: "POST", path: "/api/agent/note/delete", needs: "advise", about: "Delete a note for everyone — your own at advise, anyone's at full. Activity records it, with its text.",
696
+ params: { id: "the note's id", view: "for a view's note: its view" },
697
+ returns: "{ok, kind, view?, deleted, text, note}",
698
+ run: (a) => remove(a),
699
+ },
700
+ ];
701
+ }