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,150 @@
1
+ // The lost-and-found (HistorySpec decisions 12–14, the owner's of 2026-10-09): what people and agents
2
+ // said and marked of a declaration the graph no longer has — an opinion with its comment, a target
3
+ // mark, a key mark, a pin on a view, a note's mention of it — that no rename in force carried to a
4
+ // new name. Nothing is deleted when a declaration goes: what was said of it waits here, under the
5
+ // name it was said of, until someone attaches it to a declaration of the graph (a move, HistorySpec
6
+ // decision 9) or archives it, which only hides it.
7
+ //
8
+ // The entries are computed, never stored: from the review and the views as they are, against the
9
+ // graph on show. Each is keyed by what it is and where, so that the same thing is the same entry on
10
+ // every page and in the archive (server/lostfound.ts), which is the one thing kept:
11
+ //
12
+ // opinion:<name>#<by>@<at> an opinion (the reference comments use, shared/comment.ts)
13
+ // target:<name> a target mark
14
+ // key:<target>#<name> a key mark, lost where either name is gone
15
+ // pin:<view>:<mode>:<name> a declaration's pin on a view, under one way of clustering
16
+ // note:<view>:<note id>:<name> a note's mention of a full name in backticks
17
+ // link:<link id> a link into the library (AttachmentSpec decision 6), its
18
+ // declaration gone, or its cluster (listed as `ns(P)`, `file(P)`)
19
+ // move:<migration id> a migration (server/renames.ts), listed beside the lost
20
+ //
21
+ // This file is what the server and the pages share: the entries, their keys, and the shape of the
22
+ // answer the server gives (GET /api/history/lost).
23
+ import { opinionRef } from "./comment.js";
24
+ import { CLUSTER_MODES } from "./places.js";
25
+ import { viewTitle } from "./views.js";
26
+ /** The order the kinds are listed in, under one name. */
27
+ const KIND_ORDER = ["target", "key", "opinion", "link", "note", "pin"];
28
+ /** A name as a note writes one in full between backticks: a dotted or plain Lean name, nothing
29
+ * that reads as code (spaces, operators). */
30
+ const NAME_LIKE = /^[\p{L}_«][^\s`(){}[\],;:=+*<>|&^%$#@~"/\\]*$/u;
31
+ export const lostKey = {
32
+ opinion: (name, by, at) => opinionRef(name, by, at),
33
+ target: (name) => `target:${name}`,
34
+ key: (target, name) => `key:${target}#${name}`,
35
+ pin: (view, mode, name) => `pin:${view}:${mode}:${name}`,
36
+ note: (view, note, name) => `note:${view}:${note}:${name}`,
37
+ link: (id) => `link:${id}`,
38
+ move: (id) => `move:${id}`,
39
+ };
40
+ /** Every entry of the lost-and-found: what the review (`d`) and the views hold of a name the graph
41
+ * on show does not have (`has`). A note's mention counts where its name is not in the graph, no
42
+ * rename in force leads from it to one that is (`d.renamed`), and it was a declaration (`known`:
43
+ * the history had it; without a history, a dotted name). `pinWho` says who dropped a pin, where
44
+ * the caller knows (the server does; pages are not told); `titleOf` what a view is called (the
45
+ * server's titles know the graph's names). A view kept at a past build is a board of that build
46
+ * (HistorySpec decision 19): a pin or a mention of a declaration its build had is not lost
47
+ * (`atBuild`). Sorted by name, then kind, then time. */
48
+ export function lostEntries(d, views, has, known = (n) => n.includes("."), pinWho = () => null, titleOf = (v) => viewTitle(v), atBuild = () => false, notes = [], hasCluster = null) {
49
+ const out = [];
50
+ const who = (x) => ({
51
+ by: x.by,
52
+ ...(x.agent ? { agent: true } : {}),
53
+ ...(x.person ? { person: x.person } : {}),
54
+ at: x.at,
55
+ });
56
+ for (const [name, ops] of Object.entries(d.opinions)) {
57
+ if (has(name))
58
+ continue;
59
+ for (const o of ops) {
60
+ // an archiving made while the writer went by another name is kept under that name's key
61
+ const was = o.person && Object.hasOwn(d.people, o.person) ? d.people[o.person].was.filter((n) => n !== o.by) : [];
62
+ out.push({
63
+ key: lostKey.opinion(name, o.by, o.at),
64
+ kind: "opinion",
65
+ name,
66
+ ...who(o),
67
+ verdict: o.verdict,
68
+ ...(o.comment ? { comment: o.comment } : {}),
69
+ ...(was.length ? { aliases: was.map((n) => lostKey.opinion(name, n, o.at)) } : {}),
70
+ });
71
+ }
72
+ }
73
+ for (const [name, t] of Object.entries(d.targets))
74
+ if (!has(name))
75
+ out.push({ key: lostKey.target(name), kind: "target", name, ...who(t) });
76
+ // a key mark is lost where the declaration marked is gone (as a member), or its target (as a
77
+ // target's own); it is listed under the member's name where that is gone
78
+ for (const [target, marks] of Object.entries(d.keys))
79
+ for (const [member, m] of Object.entries(marks))
80
+ if (!has(member) || !has(target))
81
+ out.push({ key: lostKey.key(target, member), kind: "key", name: has(member) ? target : member, ...who(m), target, member });
82
+ // a link whose declaration is gone (a move would have carried it), or whose cluster is: listed
83
+ // under the declaration's name, or under the cluster as its subject names it
84
+ for (const l of d.links ?? []) {
85
+ const m = /^(decl|ns|file)\((.+)\)$/s.exec(l.subject);
86
+ if (!m)
87
+ continue;
88
+ const gone = m[1] === "decl" ? !has(m[2]) : hasCluster !== null && !hasCluster(m[1], m[2]);
89
+ if (!gone)
90
+ continue;
91
+ const f = d.files && Object.hasOwn(d.files, l.file) ? d.files[l.file] : null;
92
+ out.push({ key: lostKey.link(l.id), kind: "link", name: m[1] === "decl" ? m[2] : l.subject, ...who(l), link: l.id, label: l.label, ...(f ? { fileTitle: f.title } : {}) });
93
+ }
94
+ // the names each name became, through the renames in force: a note may name one before its rename
95
+ const ahead = new Map();
96
+ for (const [to, from] of Object.entries(d.renamed ?? {}))
97
+ ahead.set(from, to);
98
+ const leads = (name) => {
99
+ let n = name;
100
+ for (let k = 0; k < 1000 && ahead.has(n); k++) {
101
+ n = ahead.get(n);
102
+ if (has(n))
103
+ return true;
104
+ }
105
+ return false;
106
+ };
107
+ /** the mentions in a note's text of names that are gone, each an entry */
108
+ const mentions = (n, view, title, gone) => {
109
+ const names = new Set();
110
+ for (const m of n.text.matchAll(/`([^`\n]+)`/g))
111
+ names.add(m[1]);
112
+ for (const name of names) {
113
+ if (!NAME_LIKE.test(name) || has(name) || leads(name) || !known(name) || !gone(name))
114
+ continue;
115
+ out.push({ key: lostKey.note(view, n.id, name), kind: "note", name, ...who(n), view, title, note: n.id, text: n.text });
116
+ }
117
+ };
118
+ // the project's notes (AttachmentSpec decision 13): view "", on no board of a past build
119
+ for (const n of notes)
120
+ mentions(n, "", "", () => true);
121
+ for (const v of views) {
122
+ const title = titleOf(v);
123
+ for (const mode of CLUSTER_MODES)
124
+ for (const name of Object.keys(v.pins[mode].nodes)) {
125
+ if (has(name) || atBuild(v, name))
126
+ continue;
127
+ const w = pinWho(v.id, mode, name);
128
+ out.push({ key: lostKey.pin(v.id, mode, name), kind: "pin", name, by: w?.by ?? null, ...(w?.agent ? { agent: true } : {}), at: w?.at ?? "", view: v.id, title, mode });
129
+ }
130
+ for (const n of v.notes ?? [])
131
+ mentions(n, v.id, title, (name) => !atBuild(v, name));
132
+ }
133
+ const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
134
+ return out.sort((a, b) => cmp(a.name, b.name) || KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || cmp(a.at, b.at) || cmp(a.key, b.key));
135
+ }
136
+ /** What a migration carried, as words: "2 opinions, a target mark, 3 pins". */
137
+ export function carriedText(c) {
138
+ const n = (k, one, many = `${one}s`) => (k ? (k === 1 ? `1 ${one}` : `${k} ${many}`) : "");
139
+ const parts = [
140
+ n(c.opinions, "opinion"),
141
+ n(c.targets, "target mark"),
142
+ n(c.keys, "key mark"),
143
+ n(c.links ?? 0, "link"),
144
+ n(c.reviewViews, "target's view", "targets' views"),
145
+ n(c.pins, "pin"),
146
+ n(c.notes, "note"),
147
+ n(c.scopes, "view's scope", "views' scopes"),
148
+ ].filter(Boolean);
149
+ return parts.length ? parts.join(", ") : "nothing said of it: its identity alone";
150
+ }
@@ -0,0 +1,243 @@
1
+ // Moves (HistorySpec.md, the owner's decisions of 2026-10-09): what was said about a declaration
2
+ // follows it to another name. A move is an event of the review's logs, appended like any other and
3
+ // never rewritten: replayed, it carries the opinions given on `from` (their comments with them), its
4
+ // target mark and the key marks on it — as a declaration of a target's review, and as a target —
5
+ // over to `to`. Each thing carried keeps the names it was on before (`was`), so that undoing a move,
6
+ // an un-move (`on: false`), takes back exactly what that move carried and leaves what was said of
7
+ // `to` since. A move the server found as a rename, or one a person made onto a declaration that
8
+ // appeared as the other went, also says that `to` *is* `from` renamed (`rename`): the version of
9
+ // whatever refers to it is then made with `from`'s identity (TCBSpec decision 3), so that renaming
10
+ // it stales nothing. Since 2026-10-11 a move carries the links on the declaration too
11
+ // (AttachmentSpec decision 6): a link on `decl(from)` is on `decl(to)` after it, its `was` saying
12
+ // where it was. Moving a note's mention, a pin or a view's scope is the views' business
13
+ // (server/views.ts); this file is what the server and the pages replay.
14
+ //
15
+ // Declarations are keyed by name; every object keyed by names here has no prototype, as in
16
+ // shared/review.ts.
17
+ /** A declaration as a link's subject names it (shared/library.ts, declSubject). */
18
+ const declOf = (name) => `decl(${name})`;
19
+ const own = (o, k) => Object.hasOwn(o, k);
20
+ const noProto = () => Object.create(null);
21
+ const byTime = (xs) => xs.sort((a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : 0));
22
+ /** One move replayed onto a review. */
23
+ export function applyMove(d, e) {
24
+ if (e.from === e.to)
25
+ return;
26
+ if (e.on)
27
+ carry(d, e.from, e.to);
28
+ else
29
+ takeBack(d, e.from, e.to);
30
+ if (!e.rename)
31
+ return;
32
+ const links = (d.renamed ??= noProto());
33
+ if (e.on) {
34
+ // renamed back (`to` is where `from` came from): `to` is itself again, and `from` is no one's
35
+ if (identityIn(links, e.from) === e.to) {
36
+ delete links[e.to];
37
+ delete links[e.from];
38
+ }
39
+ else
40
+ links[e.to] = e.from;
41
+ }
42
+ else if (own(links, e.to) && links[e.to] === e.from)
43
+ delete links[e.to];
44
+ }
45
+ function carry(d, from, to) {
46
+ const moved = (x) => ({ ...x, was: [...(x.was ?? []), from] });
47
+ if (own(d.opinions, from)) {
48
+ const os = d.opinions[from].map(moved);
49
+ delete d.opinions[from];
50
+ d.opinions[to] = byTime([...(own(d.opinions, to) ? d.opinions[to] : []), ...os]);
51
+ }
52
+ if (own(d.targets, from)) {
53
+ if (!own(d.targets, to))
54
+ d.targets[to] = moved(d.targets[from]);
55
+ delete d.targets[from];
56
+ }
57
+ // a key mark on it, in each target's review
58
+ for (const t of Object.keys(d.keys)) {
59
+ const ks = d.keys[t];
60
+ if (!own(ks, from))
61
+ continue;
62
+ if (!own(ks, to))
63
+ ks[to] = moved(ks[from]);
64
+ delete ks[from];
65
+ }
66
+ // its own key marks, as a target
67
+ if (own(d.keys, from)) {
68
+ const ks = (own(d.keys, to) ? d.keys[to] : (d.keys[to] = noProto()));
69
+ for (const [n, m] of Object.entries(d.keys[from]))
70
+ if (n !== to && !own(ks, n))
71
+ ks[n] = { ...m, wasOf: [...(m.wasOf ?? []), from] };
72
+ delete d.keys[from];
73
+ }
74
+ // the links on it, in the order they were made
75
+ if (d.links)
76
+ d.links = d.links.map((l) => (l.subject === declOf(from) ? { ...moved(l), subject: declOf(to) } : l));
77
+ }
78
+ function takeBack(d, from, to) {
79
+ const came = (x) => x.was?.[x.was.length - 1] === from;
80
+ const back = (x) => {
81
+ const was = x.was.slice(0, -1);
82
+ const y = { ...x };
83
+ if (was.length)
84
+ y.was = was;
85
+ else
86
+ delete y.was;
87
+ return y;
88
+ };
89
+ if (own(d.opinions, to)) {
90
+ const all = d.opinions[to];
91
+ const os = all.filter(came);
92
+ if (os.length) {
93
+ const stay = all.filter((o) => !came(o));
94
+ if (stay.length)
95
+ d.opinions[to] = stay;
96
+ else
97
+ delete d.opinions[to];
98
+ d.opinions[from] = byTime([...(own(d.opinions, from) ? d.opinions[from] : []), ...os.map(back)]);
99
+ }
100
+ }
101
+ if (own(d.targets, to) && came(d.targets[to])) {
102
+ if (!own(d.targets, from))
103
+ d.targets[from] = back(d.targets[to]);
104
+ delete d.targets[to];
105
+ }
106
+ for (const t of Object.keys(d.keys)) {
107
+ const ks = d.keys[t];
108
+ if (!own(ks, to) || !came(ks[to]))
109
+ continue;
110
+ if (!own(ks, from))
111
+ ks[from] = back(ks[to]);
112
+ delete ks[to];
113
+ }
114
+ if (own(d.keys, to)) {
115
+ const ks = d.keys[to];
116
+ const marks = Object.entries(ks).filter(([, m]) => m.wasOf?.[m.wasOf.length - 1] === from);
117
+ if (marks.length) {
118
+ const into = own(d.keys, from) ? d.keys[from] : (d.keys[from] = noProto());
119
+ for (const [n, m] of marks) {
120
+ const wasOf = m.wasOf.slice(0, -1);
121
+ const y = { ...m };
122
+ if (wasOf.length)
123
+ y.wasOf = wasOf;
124
+ else
125
+ delete y.wasOf;
126
+ if (!own(into, n))
127
+ into[n] = y;
128
+ delete ks[n];
129
+ }
130
+ if (!Object.keys(ks).length)
131
+ delete d.keys[to];
132
+ }
133
+ }
134
+ if (d.links)
135
+ d.links = d.links.map((l) => (l.subject === declOf(to) && came(l) ? { ...back(l), subject: declOf(from) } : l));
136
+ }
137
+ /** The identity of a unit's name under the renames in force (`renamed`: `to` → `from`): the first
138
+ * name it had, followed back through each rename. */
139
+ export function identityIn(renamed, name) {
140
+ if (!renamed)
141
+ return name;
142
+ let n = name;
143
+ // a chain is as long as the renames of one declaration; a loop cannot be made (applyMove), but a
144
+ // log edited by hand could hold one
145
+ for (let k = 0; k < 1000 && own(renamed, n); k++) {
146
+ const p = renamed[n];
147
+ if (p === name)
148
+ break;
149
+ n = p;
150
+ }
151
+ return n;
152
+ }
153
+ /** Whether a move would change nothing: nothing to carry or take back, and no identity to link or
154
+ * unlink. Such a request writes nothing. */
155
+ export function moveIsIdle(d, e) {
156
+ if (e.from === e.to)
157
+ return true;
158
+ const came = (x) => x.was?.[x.was.length - 1] === e.from;
159
+ const links = d.renamed ?? noProto();
160
+ if (e.on) {
161
+ const carries = own(d.opinions, e.from) || own(d.targets, e.from) || own(d.keys, e.from) || Object.values(d.keys).some((ks) => own(ks, e.from)) || !!d.links?.some((l) => l.subject === declOf(e.from));
162
+ return !carries && (!e.rename || (own(links, e.to) && links[e.to] === e.from));
163
+ }
164
+ const back = (own(d.opinions, e.to) && d.opinions[e.to].some(came)) ||
165
+ (own(d.targets, e.to) && came(d.targets[e.to])) ||
166
+ Object.values(d.keys).some((ks) => own(ks, e.to) && came(ks[e.to])) ||
167
+ (own(d.keys, e.to) && Object.values(d.keys[e.to]).some((m) => m.wasOf?.[m.wasOf.length - 1] === e.from)) ||
168
+ !!d.links?.some((l) => l.subject === declOf(e.to) && came(l));
169
+ return !back && !(e.rename && own(links, e.to) && links[e.to] === e.from);
170
+ }
171
+ /** A move's line, read: the event, or why it is not one. */
172
+ export function readMove(o, at, by, agent) {
173
+ const str = (v, max) => typeof v === "string" && v.length > 0 && v.length <= max;
174
+ if (!str(o.from, 2000))
175
+ return "from";
176
+ if (!str(o.to, 2000))
177
+ return "to";
178
+ if (o.from === o.to)
179
+ return "a move to the same name";
180
+ if (typeof o.on !== "boolean")
181
+ return "on";
182
+ if (o.rename !== undefined && o.rename !== true)
183
+ return "rename";
184
+ if (o.auto !== undefined && !str(o.auto, 200))
185
+ return "auto";
186
+ if (o.snap !== undefined && !(Number.isInteger(o.snap) && o.snap > 0))
187
+ return "snap";
188
+ return {
189
+ op: "move",
190
+ at,
191
+ by,
192
+ from: o.from,
193
+ to: o.to,
194
+ on: o.on,
195
+ ...(o.rename ? { rename: true } : {}),
196
+ ...(o.auto !== undefined ? { auto: o.auto } : {}),
197
+ ...(o.snap !== undefined ? { snap: o.snap } : {}),
198
+ ...(agent ? { agent: true } : {}),
199
+ };
200
+ }
201
+ /** A view's scope with declaration `from` named `to` instead: in `decl(…)`, the roots of a closure,
202
+ * and the targets of `key(…)`. The same object where it does not name it. */
203
+ export function renameInQuery(q, from, to) {
204
+ const names = (xs) => (xs.includes(from) ? [...new Set(xs.map((x) => (x === from ? to : x)))] : xs);
205
+ switch (q.op) {
206
+ case "decl": {
207
+ const n = names(q.names);
208
+ return n === q.names ? q : { ...q, names: n };
209
+ }
210
+ case "tcb":
211
+ case "uses":
212
+ case "usedBy": {
213
+ if (q.of === "selection")
214
+ return q;
215
+ if (Array.isArray(q.of)) {
216
+ const n = names(q.of);
217
+ return n === q.of ? q : { ...q, of: n };
218
+ }
219
+ const of = renameInQuery(q.of, from, to);
220
+ return of === q.of ? q : { ...q, of };
221
+ }
222
+ case "key": {
223
+ if (q.of === "targets")
224
+ return q;
225
+ const n = names(q.of);
226
+ return n === q.of ? q : { ...q, of: n };
227
+ }
228
+ case "and":
229
+ case "or":
230
+ case "except": {
231
+ const a = renameInQuery(q.a, from, to);
232
+ const b = renameInQuery(q.b, from, to);
233
+ return a === q.a && b === q.b ? q : { ...q, a, b };
234
+ }
235
+ default:
236
+ return q;
237
+ }
238
+ }
239
+ /** A note's text with declaration `from`, written in full between backticks, named `to` instead
240
+ * (`` `A.f` `` → `` `A.g` ``); the same text where it does not mention it so. */
241
+ export function renameInNote(text, from, to) {
242
+ return text.includes("`" + from + "`") ? text.split("`" + from + "`").join("`" + to + "`") : text;
243
+ }