visualproof 0.3.0 → 0.4.1

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 (49) hide show
  1. package/README.md +52 -11
  2. package/dist/assets/tips/phone-fingers.light.mp4 +0 -0
  3. package/dist/assets/tips/phone-fingers.light.webp +0 -0
  4. package/dist/assets/tips/tour-agent.webp +0 -0
  5. package/dist/assets/tips/tour-arrange.webp +0 -0
  6. package/dist/assets/tips/tour-artwork.webp +0 -0
  7. package/dist/assets/tips/tour-checks.webp +0 -0
  8. package/dist/assets/tips/tour-export.webp +0 -0
  9. package/dist/assets/tips/tour-keys.webp +0 -0
  10. package/dist/assets/tips/tour-library.webp +0 -0
  11. package/dist/assets/tips/tour-project.webp +0 -0
  12. package/dist/assets/tips/tour-review.webp +0 -0
  13. package/dist/assets/tips/tour-share.webp +0 -0
  14. package/dist/assets/tips/tour-source.webp +0 -0
  15. package/dist/assets/tips/tour-target.webp +0 -0
  16. package/dist/assets/tips/tour-view.webp +0 -0
  17. package/dist/assets/tips/tour.json +289 -0
  18. package/dist/assets/tips/tour.webp +0 -0
  19. package/dist/mcp/visualproof-mcp.mjs +3 -1
  20. package/dist/server/agent.js +10 -5
  21. package/dist/server/children.js +85 -0
  22. package/dist/server/doctor.js +105 -42
  23. package/dist/server/events.js +10 -0
  24. package/dist/server/export.js +11 -4
  25. package/dist/server/extract.js +3 -2
  26. package/dist/server/figures.js +3 -2
  27. package/dist/server/film.js +380 -0
  28. package/dist/server/history.js +11 -1
  29. package/dist/server/hover.js +8 -5
  30. package/dist/server/live.js +66 -9
  31. package/dist/server/main.js +217 -16
  32. package/dist/server/media.js +114 -0
  33. package/dist/server/pipeline.js +6 -1
  34. package/dist/server/poppler.js +3 -2
  35. package/dist/server/readonlygate.js +4 -4
  36. package/dist/server/sourcestore.js +3 -2
  37. package/dist/server/status.js +3 -2
  38. package/dist/server/supervise.js +219 -0
  39. package/dist/server/updates.js +951 -0
  40. package/dist/server/usage.js +3 -1
  41. package/dist/server/version.js +183 -0
  42. package/dist/shared/timeline.js +5 -0
  43. package/dist/web/bundle/app.js +2299 -255
  44. package/dist/web/index.html +13 -1
  45. package/dist/web/src/canvas.js +22 -0
  46. package/dist/web/style.css +189 -1
  47. package/docs/agent-api/export.md +2 -2
  48. package/docs/agent-api/history.md +3 -2
  49. package/package.json +3 -1
package/README.md CHANGED
@@ -9,6 +9,10 @@
9
9
  </p>
10
10
  <p align="center"><sub>Five Lean codebases, drawn by <code>visualproof artwork</code>.</sub></p>
11
11
 
12
+ https://github.com/user-attachments/assets/98e435d6-f493-437d-b342-1833819c0b77
13
+
14
+ <p align="center"><sub>A review of Sphere-Packing-Lean in 74 seconds. The whole tour — four minutes in thirteen chapters, with captions — is in the page under <b>?</b> → Tips, and <a href="https://visualproof-demo.yingte.io/#tips">on the live demo</a>.</sub></p>
15
+
12
16
  [![VisualProof on flt-regular, from the live demo: the TCB of flt_regular, its key declarations approved, an approval recorded at a meeting, the review plan pinned beside it](docs/images/board.webp)](https://visualproof-demo.yingte.io)
13
17
 
14
18
  <p align="center"><b>Live demo: <a href="https://visualproof-demo.yingte.io">https://visualproof-demo.yingte.io</a></b><br><sub>flt-regular in the middle of a review by three people and an AI assistant, read-only</sub></p>
@@ -20,6 +24,12 @@ Set up in your Lean codebase with agent prompt:
20
24
  Set up VisualProof (https://github.com/LucianoXu/VisualProof) for my Lean project, and give me the address to open.
21
25
  ```
22
26
 
27
+ Or have your codebase drawn as a poster:
28
+ ```text
29
+ Draw my Lean project as a poster with VisualProof (https://github.com/LucianoXu/VisualProof): pick its main theorem, light up what its statement rests on, and give me the PNG and the SVG.
30
+ ```
31
+ What the agent does, step by step: [A poster from the command line](docs/MANUAL.md#a-poster-from-the-command-line).
32
+
23
33
  Set up manually:
24
34
  ```bash
25
35
  npm install --global visualproof
@@ -27,7 +37,9 @@ cd my-lean-project && lake build
27
37
  visualproof serve .
28
38
  ```
29
39
 
30
- It needs Node, elan and Poppler ([Requirements](#requirements)).
40
+ It needs Node, elan and Poppler ([Requirements](#requirements)). On the first visit the page offers
41
+ a short tour of your own project; `?` holds the tips — a film of a review on Sphere-Packing-Lean in
42
+ thirteen scenes, with captions, and a row for each thing it does not show — and every key.
31
43
 
32
44
  ## See what a Lean theorem actually rests on
33
45
 
@@ -38,16 +50,26 @@ keep a record of the review.
38
50
 
39
51
  On [flt-regular](https://github.com/leanprover-community/flt-regular), Fermat's Last Theorem for
40
52
  regular primes. Of the project's
41
- 273 declarations, two light up besides the theorem, `IsRegularPrime` and `IsRegularNumber`: with
42
- the Mathlib definitions they use, they are what you need to read to believe what it says.
53
+ 273 declarations, two light up besides the theorem, `IsRegularPrime` and `IsRegularNumber`: they
54
+ are what you need to read to believe what it says. The Mathlib definitions they use light up too,
55
+ trusted and assumed as Mathlib is, unless a review chooses to read them: the walk stops at the
56
+ project's edge.
57
+
58
+ <p align="center">
59
+ <img src="docs/images/tcb-view-theorem.webp" width="32%" alt="flt_regular alone, lit on the board">
60
+ <img src="docs/images/tcb-view-tcb.webp" width="32%" alt="the TCB of flt_regular: IsRegularPrime, IsRegularNumber and the Mathlib definitions they use, lit with the edges between them">
61
+ <img src="docs/images/tcb-view-keys.webp" width="32%" alt="the key declarations of flt_regular, IsRegularPrime and FermatLastTheoremFor, joined by dashed edges">
62
+ </p>
63
+ <p align="center"><sub>The theorem alone · what its statement rests on, its TCB · the few the review marked key, read first.<br>This review no longer trusts <code>Mathlib.NumberTheory</code>, so the definitions it holds that the statement means, <code>FermatLastTheoremFor</code> among them, are read too, and can be key.</sub></p>
43
64
 
44
65
  ## What it does
45
66
 
46
67
  - **The declaration graph** of a built Lean project, clustered by namespace or by file, and updated
47
68
  live as you rebuild.
48
- - **The TCB of any theorem**: the definitions its statement rests on, followed through their bodies,
49
- instances and notation, and the axioms behind its proof. What lies only inside proofs is left
50
- out, since Lean has checked it.
69
+ - **The TCB of any theorem**: the project's definitions its statement rests on, followed through
70
+ their bodies, instances and notation, and the axioms its statement and proof use. What lies only
71
+ inside proofs is left out, since Lean has checked it; what lies outside the project, Mathlib's
72
+ definitions, is lit but trusted and assumed.
51
73
  - **A review together**: approve or comment on each declaration, mark targets and their key
52
74
  declarations, leave notes, link papers to declarations, and see what changed since an opinion was
53
75
  given. Everyone on the page sees it live.
@@ -57,7 +79,8 @@ the Mathlib definitions they use, they are what you need to read to believe what
57
79
  - **A printed document** of what a view holds: definitions in full, proofs left out, typeset with
58
80
  XeLaTeX.
59
81
  - **A picture of the board**, PNG or SVG up to poster size: from the page, from an agent's link, or
60
- without a browser (`visualproof artwork <project> --recipe recipe.json`).
82
+ without a browser (`visualproof artwork <project> --recipe recipe.json`,
83
+ [step by step](docs/MANUAL.md#a-poster-from-the-command-line)).
61
84
 
62
85
  ## Requirements
63
86
 
@@ -80,8 +103,10 @@ places:
80
103
  `~/.local/share/visualproof/sites/<project>-<hash>/` (on macOS, under
81
104
  `~/Library/Application Support/visualproof/`). It cannot be derived again and holds secrets, so
82
105
  back it up and keep it out of git.
83
- - **The cache** (`--cache <dir>`) holds what a rebuild makes again. By default it is
84
- `~/.cache/visualproof/` (on macOS, `~/Library/Caches/visualproof/`).
106
+ - **The cache** (`--cache <dir>`) holds what a rebuild makes again, and the patch releases a server
107
+ took (`versions/`, with `updates.json`, the last check: code the server runs, so the cache is the
108
+ server's user's alone). By default it is `~/.cache/visualproof/` (on macOS,
109
+ `~/Library/Caches/visualproof/`).
85
110
 
86
111
  ## Reviewing with others
87
112
 
@@ -97,10 +122,26 @@ project with a read-only link (nothing changes, nobody sees the reader), or give
97
122
  token and a prompt. To put a project online for anyone, read-only, add `--public`. The server
98
123
  speaks plain HTTP: beyond a trusted network, put a TLS proxy in front. [SECURITY.md](SECURITY.md) says what the server trusts and what it exposes.
99
124
 
125
+ ## Updating
126
+
127
+ A server keeps itself up to date with patch releases (`--updates auto`, the default): it asks npm
128
+ once a day and takes a newer release of the same minor version, 0.4.1 after 0.4.0, at a quiet
129
+ moment (nobody on a page and nothing running, a day later at the latest); the pages left open
130
+ reload where they were. A newer minor version, which may change the command line or the site
131
+ folder, is only announced — in the server's log, by `visualproof doctor` and in the About of the
132
+ page's `?` — with the command it prints to install it, e.g.
133
+ `npm install --global visualproof@0.5.0`; once it is installed, the server restarts into it by
134
+ itself within ten minutes. `--updates notify` announces patch releases too, and `--updates off`
135
+ never asks npm. 0.3.0 has no updater: upgrade it to 0.4.0 by hand
136
+ ([the manual](docs/MANUAL.md#updates)).
137
+
100
138
  ## Documentation
101
139
 
102
- - [The manual](docs/MANUAL.md): every part of the page, the review, views, history, the printed
103
- document, collaboration, and how the TCB is computed.
140
+ - **In the page**: the tour, offered on the first visit, and the tips under `?`: a film of a review
141
+ in thirteen scenes, each with a few words, and a row for the rest. They are the documentation to
142
+ start with.
143
+ - [The manual](docs/MANUAL.md): how to start, the concepts, then every part of the page, the review,
144
+ views, history, the printed document, collaboration, and how the TCB is computed.
104
145
  - [The agents' API](docs/agent-api/README.md), which the server also serves to agents, topic by topic.
105
146
  - [SPEC/](SPEC/): the specification, and the design decisions behind each part.
106
147
  - [CONTRIBUTING.md](CONTRIBUTING.md), for working on VisualProof itself.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,289 @@
1
+ {
2
+ "film": {
3
+ "file": "tour-9d4241345aaf.mp4",
4
+ "sha256": "9d4241345aaf68755b689fd0ba6b3139a2b077a68996361445b8098398079620",
5
+ "size": 53706647,
6
+ "width": 2560,
7
+ "height": 1600,
8
+ "duration": 238.96666666666667
9
+ },
10
+ "chapters": [
11
+ {
12
+ "id": "target",
13
+ "start": 0,
14
+ "end": 19.466666666666665
15
+ },
16
+ {
17
+ "id": "view",
18
+ "start": 19.466666666666665,
19
+ "end": 31.833333333333332
20
+ },
21
+ {
22
+ "id": "arrange",
23
+ "start": 31.833333333333332,
24
+ "end": 53.666666666666664
25
+ },
26
+ {
27
+ "id": "source",
28
+ "start": 53.666666666666664,
29
+ "end": 66.06666666666666
30
+ },
31
+ {
32
+ "id": "keys",
33
+ "start": 66.06666666666666,
34
+ "end": 92.8
35
+ },
36
+ {
37
+ "id": "checks",
38
+ "start": 92.8,
39
+ "end": 111.3
40
+ },
41
+ {
42
+ "id": "review",
43
+ "start": 111.3,
44
+ "end": 129.73333333333332
45
+ },
46
+ {
47
+ "id": "project",
48
+ "start": 129.73333333333332,
49
+ "end": 138.66666666666666
50
+ },
51
+ {
52
+ "id": "share",
53
+ "start": 138.66666666666666,
54
+ "end": 152
55
+ },
56
+ {
57
+ "id": "agent",
58
+ "start": 152,
59
+ "end": 171.33333333333334
60
+ },
61
+ {
62
+ "id": "library",
63
+ "start": 171.33333333333334,
64
+ "end": 199.73333333333332
65
+ },
66
+ {
67
+ "id": "export",
68
+ "start": 199.73333333333332,
69
+ "end": 219.23333333333332
70
+ },
71
+ {
72
+ "id": "artwork",
73
+ "start": 219.23333333333332,
74
+ "end": 238.96666666666667
75
+ }
76
+ ],
77
+ "captions": [
78
+ {
79
+ "start": 4.1,
80
+ "end": 7.9,
81
+ "text": "Search the theorem to review: press /."
82
+ },
83
+ {
84
+ "start": 7.9,
85
+ "end": 11.9,
86
+ "text": "There are two MainTheorems: take the one in Dim24."
87
+ },
88
+ {
89
+ "start": 11.9,
90
+ "end": 15.866666666666667,
91
+ "text": "Mark it as the target of the review."
92
+ },
93
+ {
94
+ "start": 15.866666666666667,
95
+ "end": 19.466666666666665,
96
+ "text": "The side panel lists the targets, with their progress."
97
+ },
98
+ {
99
+ "start": 19.466666666666665,
100
+ "end": 25.333333333333332,
101
+ "text": "Rests on · Uses: everything the theorem uses."
102
+ },
103
+ {
104
+ "start": 25.333333333333332,
105
+ "end": 28.833333333333332,
106
+ "text": "Zoom in: a view of just that, in a tab of its own."
107
+ },
108
+ {
109
+ "start": 28.833333333333332,
110
+ "end": 31.833333333333332,
111
+ "text": "The view is laid out for what it holds alone."
112
+ },
113
+ {
114
+ "start": 34.13333333333333,
115
+ "end": 39.5,
116
+ "text": "Drag a cluster with Ctrl held: it stays there, for everyone."
117
+ },
118
+ {
119
+ "start": 39.5,
120
+ "end": 46.53333333333333,
121
+ "text": "Or let the lock go with l, and drag without Ctrl."
122
+ },
123
+ {
124
+ "start": 46.53333333333333,
125
+ "end": 53.666666666666664,
126
+ "text": "Double-click a circle to fold it to a point; again to open."
127
+ },
128
+ {
129
+ "start": 53.666666666666664,
130
+ "end": 58.7,
131
+ "text": "Space opens the source of what is selected."
132
+ },
133
+ {
134
+ "start": 58.7,
135
+ "end": 62.5,
136
+ "text": "Rest on a name: Lean says what it is."
137
+ },
138
+ {
139
+ "start": 62.5,
140
+ "end": 66.06666666666666,
141
+ "text": "Click it to go there: its source, its panel."
142
+ },
143
+ {
144
+ "start": 66.06666666666666,
145
+ "end": 69.13333333333334,
146
+ "text": "Keys: the definitions that carry what it says."
147
+ },
148
+ {
149
+ "start": 69.13333333333334,
150
+ "end": 84.53333333333333,
151
+ "text": "Mark each as a key with the mouse, in its panel."
152
+ },
153
+ {
154
+ "start": 84.53333333333333,
155
+ "end": 92.8,
156
+ "text": "Rests on · Keys lights them, joined to the target."
157
+ },
158
+ {
159
+ "start": 93.4,
160
+ "end": 96.1,
161
+ "text": "Selection: the statement alone."
162
+ },
163
+ {
164
+ "start": 96.1,
165
+ "end": 98.9,
166
+ "text": "Least effort — you trust every name in it."
167
+ },
168
+ {
169
+ "start": 98.9,
170
+ "end": 101.36666666666666,
171
+ "text": "Keys: the few that carry the meaning."
172
+ },
173
+ {
174
+ "start": 101.36666666666666,
175
+ "end": 103.43333333333334,
176
+ "text": "Little effort, good confidence."
177
+ },
178
+ {
179
+ "start": 103.43333333333334,
180
+ "end": 105.63333333333334,
181
+ "text": "TCB: everything it rests on."
182
+ },
183
+ {
184
+ "start": 105.63333333333334,
185
+ "end": 107.63333333333334,
186
+ "text": "Most effort, most confidence."
187
+ },
188
+ {
189
+ "start": 107.63333333333334,
190
+ "end": 111.3,
191
+ "text": "Choose how far to read: the counts say what each costs."
192
+ },
193
+ {
194
+ "start": 111.3,
195
+ "end": 116.6,
196
+ "text": "Read a definition, then say what you think of it."
197
+ },
198
+ {
199
+ "start": 116.6,
200
+ "end": 125.96666666666667,
201
+ "text": "Approve it, with why: the comment stays with it."
202
+ },
203
+ {
204
+ "start": 125.96666666666667,
205
+ "end": 129.73333333333332,
206
+ "text": "The target's progress counts your approval."
207
+ },
208
+ {
209
+ "start": 129.73333333333332,
210
+ "end": 135.03333333333333,
211
+ "text": "The Project tab: the whole codebase."
212
+ },
213
+ {
214
+ "start": 135.03333333333333,
215
+ "end": 138.66666666666666,
216
+ "text": "Every board shows the same review."
217
+ },
218
+ {
219
+ "start": 138.66666666666666,
220
+ "end": 145.56666666666666,
221
+ "text": "Share → AI agent: a token, and a prompt for it."
222
+ },
223
+ {
224
+ "start": 145.56666666666666,
225
+ "end": 148.76666666666668,
226
+ "text": "Paste the prompt into your agent: it joins here."
227
+ },
228
+ {
229
+ "start": 148.76666666666668,
230
+ "end": 152,
231
+ "text": "The token is the agent's own: revoke it here."
232
+ },
233
+ {
234
+ "start": 152.7,
235
+ "end": 160.13333333333333,
236
+ "text": "Click the agent to follow it, and ask."
237
+ },
238
+ {
239
+ "start": 160.13333333333333,
240
+ "end": 165.83333333333334,
241
+ "text": "It reads the code, and takes your view along."
242
+ },
243
+ {
244
+ "start": 165.83333333333334,
245
+ "end": 171.33333333333334,
246
+ "text": "Its answer names what it explains."
247
+ },
248
+ {
249
+ "start": 172.13333333333333,
250
+ "end": 178.23333333333332,
251
+ "text": "Library: add a paper about the code."
252
+ },
253
+ {
254
+ "start": 178.23333333333332,
255
+ "end": 191.53333333333333,
256
+ "text": "Link it to the theorem, at its page."
257
+ },
258
+ {
259
+ "start": 191.53333333333333,
260
+ "end": 199.73333333333332,
261
+ "text": "Pin a note on the target: where to start."
262
+ },
263
+ {
264
+ "start": 199.73333333333332,
265
+ "end": 207.66666666666666,
266
+ "text": "Export prints a view: here, the target's TCB."
267
+ },
268
+ {
269
+ "start": 207.66666666666666,
270
+ "end": 212.26666666666668,
271
+ "text": "Drag a section to change the document's order."
272
+ },
273
+ {
274
+ "start": 212.26666666666668,
275
+ "end": 219.23333333333332,
276
+ "text": "Typeset: the PDF follows the order you set."
277
+ },
278
+ {
279
+ "start": 219.23333333333332,
280
+ "end": 224,
281
+ "text": "Artwork: the board as a picture to keep."
282
+ },
283
+ {
284
+ "start": 224,
285
+ "end": 235.36666666666667,
286
+ "text": "Frame it, then save it as PNG or SVG."
287
+ }
288
+ ]
289
+ }
Binary file
@@ -41,7 +41,9 @@ import { readFileSync, writeFileSync } from "node:fs";
41
41
  import { basename, isAbsolute } from "node:path";
42
42
  import { createInterface } from "node:readline";
43
43
 
44
- const VERSION = "0.5.0";
44
+ // the version of the VisualProof server it came from: the server writes its own here as it serves
45
+ // the file (GET /api/agent/mcp)
46
+ const VERSION = "0.0.0";
45
47
  /** the MCP revisions this server speaks, newest first; a client asking for another (a newer one,
46
48
  * 2026-07-28 among them, whose changes it does not make) is answered with the newest of these */
47
49
  const REVISIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
@@ -29,6 +29,9 @@
29
29
  import { historyEndpoints, historyWorld } from "./historyapi.js";
30
30
  import { PastBuilds } from "./pastdoc.js";
31
31
  import { spawnSync } from "node:child_process";
32
+ import { packageVersion } from "./version.js";
33
+ import { withoutReasons } from "../shared/timeline.js";
34
+ import { childEnv } from "./children.js";
32
35
  import { readFileSync } from "node:fs";
33
36
  import { join } from "node:path";
34
37
  import { gunzipSync } from "node:zlib";
@@ -817,7 +820,7 @@ export class AgentApi {
817
820
  const advise = !this.refuse(me, "advise");
818
821
  const byKey = new Map(this.endpoints.map((e) => [`${e.method} ${e.path}`, e]));
819
822
  return ok({
820
- about: "VisualProof shows a Lean project's declarations, the TCB of its theorems (what a theorem's statement rests on) and the review people make of it. This is its API for AI agents.",
823
+ about: `VisualProof ${packageVersion()} shows a Lean project's declarations, the TCB of its theorems (what a theorem's statement rests on) and the review people make of it. This is its API for AI agents.`,
821
824
  you: {
822
825
  name: me.name,
823
826
  kind: me.kind,
@@ -1160,7 +1163,9 @@ export class AgentApi {
1160
1163
  if (typeof t !== "string")
1161
1164
  return t;
1162
1165
  try {
1163
- return { status: 200, body: null, text: { type: "text/javascript; charset=utf-8", body: readFileSync(MCP_FILE, "utf8") } };
1166
+ // the file carries the version of the server it came from (UpdateSpec decision 11)
1167
+ const body = readFileSync(MCP_FILE, "utf8").replace(/^const VERSION = "[^"\n]*";$/m, `const VERSION = ${JSON.stringify(packageVersion())};`);
1168
+ return { status: 200, body: null, text: { type: "text/javascript; charset=utf-8", body } };
1164
1169
  }
1165
1170
  catch {
1166
1171
  return fail(404, "The MCP server is not beside this server.", "Use the HTTP API (GET /api/agent).");
@@ -2021,7 +2026,7 @@ export class AgentApi {
2021
2026
  out.push({ ...rest, ...who, moved: ops.flatMap((o) => (o.op === "clear" ? [] : [o.key])).slice(0, 100), undoneBy: undone.get(e.id) ?? null });
2022
2027
  }
2023
2028
  else
2024
- out.push({ ...e, ...who });
2029
+ out.push({ ...withoutReasons(e), ...who });
2025
2030
  }
2026
2031
  const about = (a.query.get("name") ?? "").trim();
2027
2032
  // a note is about the declarations it names; a link, about the declaration it is on, or was on
@@ -2895,7 +2900,7 @@ export class AgentApi {
2895
2900
  {
2896
2901
  method: "GET", path: "/api/agent/activity", brief: "the timeline", about: "The timeline, oldest first: opinions and advice, targets and key marks, files of the library added, retitled, archived or restored and links made, edited or removed (all from the review's logs), builds, TCB changes, targets taken off, prefixes trusted or no longer, views saved, renamed, rescoped, deleted or laid out afresh, notes added to a view or taken out of it, notes, layouts and their undoing.",
2897
2902
  params: { since: "an ISO time (e.g. the `now` of your last answer) or an event id: what came after", name: "optional: only what is about this declaration (opinions, advice, notes that name it, key marks, links on it; a target's own events)", limit: "default 100, at most 1000 (the newest)" },
2898
- returns: "{now, events: [{at, kind, by?, agent?, …}] oldest first, more}; kinds: opinion, target, key (a key declaration as marked now: target, name), build, tcb, untarget, trust, untrust, view (a view saved, renamed, rescoped, deleted or laid out afresh: what, view, name, was?, scope; or a note of the project added to it or taken out of it: what items, note, text, shown), note (a note on a view written, edited or deleted: what, view, title, note, text), file (a file of the library: what is added, edited — its title, wasTitle the one before —, archived or restored; sha, title), link (what is made, edited or removed; id, subject, label, file, and an edit's changed (subject, label, place), wasSubject?, wasLabel?, auto? where VisualProof carried it with a renamed cluster), layout (with moved and undoneBy), undo; relayout only in a timeline kept from before views",
2903
+ returns: "{now, events: [{at, kind, by?, agent?, …}] oldest first, more}; kinds: opinion, target, key (a key declaration as marked now: target, name), build, tcb, untarget, trust, untrust, view (a view saved, renamed, rescoped, deleted or laid out afresh: what, view, name, was?, scope; or a note of the project added to it or taken out of it: what items, note, text, shown), note (a note on a view written, edited or deleted: what, view, title, note, text), file (a file of the library: what is added, edited — its title, wasTitle the one before —, archived or restored; sha, title), link (what is made, edited or removed; id, subject, label, file, and an edit's changed (subject, label, place), wasSubject?, wasLabel?, auto? where VisualProof carried it with a renamed cluster), layout (with moved and undoneBy), undo, tool (the VisualProof version serving the site changed: from, to, setAside?); relayout only in a timeline kept from before views",
2899
2904
  run: (a) => this.activity(a),
2900
2905
  },
2901
2906
  {
@@ -3256,7 +3261,7 @@ export function commitOf(root) {
3256
3261
  if (was && now - was.at < 10_000)
3257
3262
  return was.commit;
3258
3263
  const git = (...args) => {
3259
- const r = spawnSync("git", ["-C", root, ...args], { encoding: "utf8", timeout: 5000, env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }, stdio: ["ignore", "pipe", "ignore"] });
3264
+ const r = spawnSync("git", ["-C", root, ...args], { encoding: "utf8", timeout: 5000, env: childEnv({ GIT_OPTIONAL_LOCKS: "0" }), stdio: ["ignore", "pipe", "ignore"] });
3260
3265
  return r.status === 0 ? r.stdout.trim() : null;
3261
3266
  };
3262
3267
  const hash = git("rev-parse", "HEAD");
@@ -0,0 +1,85 @@
1
+ // The programs the server runs and waits for — the extractor and the Lean programs' build, the
2
+ // usage pass's `lake env`, Lean's language server, XeLaTeX, ImageMagick, Poppler, git, ps — kept
3
+ // here while they run, so that a server stopping stops them (UpdateSpec decision 9) rather than
4
+ // leave them working for nobody. Those that start programs of their own (Lake starts Lean, Lean's
5
+ // server its workers, XeLaTeX kpathsea's, ImageMagick its delegates) run in a process group of
6
+ // their own (`grouped`, spawn's `detached`), and are stopped with all they started; what is left
7
+ // at the server's exit is ended at once. The usage pass's workers have a stop of their own
8
+ // (server/usage.ts).
9
+ const running = new Set();
10
+ /** the children that lead a process group of their own */
11
+ const groups = new WeakSet();
12
+ /** spawn's `detached` for a program run as a group (`grouped`): its own process group, where the
13
+ * platform has them */
14
+ export const DETACH = process.platform !== "win32";
15
+ /** Keep `child` as `tracked` does, as the leader of a process group of its own: spawned with
16
+ * `detached: DETACH`, and stopped with every program it started. */
17
+ export function grouped(child) {
18
+ if (DETACH)
19
+ groups.add(child);
20
+ return tracked(child);
21
+ }
22
+ /** End `child` with `signal`: where it leads a process group, the whole group. */
23
+ export function end(child, signal = "SIGTERM") {
24
+ if (groups.has(child) && child.pid && child.exitCode === null) {
25
+ try {
26
+ process.kill(-child.pid, signal);
27
+ return;
28
+ }
29
+ catch {
30
+ // the group is gone: the child alone, if it is not
31
+ }
32
+ }
33
+ child.kill(signal);
34
+ }
35
+ // what still runs as the server's process ends goes with it
36
+ process.once("exit", () => {
37
+ for (const c of running)
38
+ end(c, "SIGKILL");
39
+ });
40
+ /** Keep `child` until it exits; the same child, for `spawn(…)` to be wrapped in place. */
41
+ export function tracked(child) {
42
+ running.add(child);
43
+ const gone = () => running.delete(child);
44
+ child.once("exit", gone);
45
+ child.once("error", gone);
46
+ return child;
47
+ }
48
+ /** The environment a program the server runs is given: the server's own, without what is the
49
+ * server's alone (`VISUALPROOF_*`: the supervisor's word to it, its test hooks; UpdateSpec decision
50
+ * 9), with `extra` over it. */
51
+ export function childEnv(extra = {}) {
52
+ const env = {};
53
+ for (const [k, v] of Object.entries(process.env))
54
+ if (!k.startsWith("VISUALPROOF_"))
55
+ env[k] = v;
56
+ return { ...env, ...extra };
57
+ }
58
+ /** How many tracked programs run now. */
59
+ export function childCount() {
60
+ return running.size;
61
+ }
62
+ /** Ask every tracked program to end (SIGTERM), and end those still there after `graceMs`
63
+ * (SIGKILL). Resolves once none runs, or at the end of the grace; the process waits for it. */
64
+ export function stopChildren(graceMs = 2000) {
65
+ if (!running.size)
66
+ return Promise.resolve();
67
+ for (const c of running)
68
+ end(c, "SIGTERM");
69
+ return new Promise((resolve) => {
70
+ const t0 = Date.now();
71
+ const tick = setInterval(() => {
72
+ if (!running.size) {
73
+ clearInterval(tick);
74
+ return resolve();
75
+ }
76
+ if (Date.now() - t0 >= graceMs) {
77
+ for (const c of running)
78
+ end(c, "SIGKILL");
79
+ running.clear();
80
+ clearInterval(tick);
81
+ resolve();
82
+ }
83
+ }, 50);
84
+ });
85
+ }