visualproof 0.1.0 → 0.2.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.
- package/dist/assets/webfonts/LICENSE-Jost.txt +93 -0
- package/dist/assets/webfonts/LICENSE-SourceSans3.txt +93 -0
- package/dist/assets/webfonts/LICENSE-SourceSerif4.txt +93 -0
- package/dist/assets/webfonts/jost-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/jost-latin-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-greek-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-greek-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-greek-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-greek-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-wght-normal.woff2 +0 -0
- package/dist/server/agent.js +41 -6
- package/dist/server/history.js +39 -0
- package/dist/server/historyapi.js +2 -2
- package/dist/server/hover.js +3 -3
- package/dist/server/layoutthread.js +1 -1
- package/dist/server/main.js +15 -3
- package/dist/server/mcptools.js +2 -3
- package/dist/server/pastdoc.js +1 -1
- package/dist/server/pipeline.js +5 -2
- package/dist/server/readonlygate.js +1 -1
- package/dist/server/snapshot.js +3 -2
- package/dist/server/usage.js +2 -2
- package/dist/server/view.js +7 -0
- package/dist/server/views.js +1 -1
- package/dist/shared/collab.js +94 -1
- package/dist/shared/doc/order.js +17 -0
- package/dist/shared/doc/outline.js +45 -0
- package/dist/shared/keys.js +1 -1
- package/dist/shared/moves.js +2 -1
- package/dist/shared/pastgraph.js +9 -1
- package/dist/shared/query.js +21 -13
- package/dist/shared/sharesnap.js +2 -1
- package/dist/shared/titles.js +2 -0
- package/dist/shared/viewgraph.js +18 -0
- package/dist/web/bundle/app.js +7118 -3180
- package/dist/web/bundle/edges.js +3 -5
- package/dist/web/index.html +24 -6
- package/dist/web/src/scene.js +17 -0
- package/dist/web/style.css +248 -12
- package/docs/agent-api/following.md +2 -2
- package/docs/agent-api/views.md +3 -2
- package/package.json +1 -1
- package/test/toolchains.json +2 -2
package/dist/server/pipeline.js
CHANGED
|
@@ -13,6 +13,9 @@ import { buildViewOffThread } from "./view.js";
|
|
|
13
13
|
import { cacheHome } from "./dirs.js";
|
|
14
14
|
/** the tool's own folder, `src/`; the repository is above it */
|
|
15
15
|
export const SRC = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
+
/** the fonts the page draws an artwork's names and title in (ExportSpec decision 19): Source Sans 3
|
|
17
|
+
* and Source Serif 4, their Latin and Greek, and Jost, its Latin, under the SIL Open Font License */
|
|
18
|
+
export const WEBFONTS = join(SRC, "assets", "webfonts");
|
|
16
19
|
export const REPO = resolve(SRC, "..");
|
|
17
20
|
export const WEB = join(SRC, "web");
|
|
18
21
|
/** The page's bundle made when the package was built (scripts/build.mjs), beside the page: an
|
|
@@ -38,8 +41,8 @@ export function bundleDir() {
|
|
|
38
41
|
* views from an older server are rebuilt rather than served. 7: a notation unit's statement is
|
|
39
42
|
* its command's text. 8: notation units carry their Lean constant, display form, what they
|
|
40
43
|
* interpret and their rules. 9: a node's hash is its version, beside the kernel's
|
|
41
|
-
* (server/versions.ts). */
|
|
42
|
-
const VIEW_VERSION =
|
|
44
|
+
* (server/versions.ts). 10: the project's git source and commit (server/history.ts, gitSource). */
|
|
45
|
+
const VIEW_VERSION = 10;
|
|
43
46
|
function loadMemory(siteDir) {
|
|
44
47
|
try {
|
|
45
48
|
const mem = JSON.parse(readFileSync(join(siteDir, "layout-memory.json"), "utf8"));
|
|
@@ -7,7 +7,7 @@ export function readOnlyAllows(method, path) {
|
|
|
7
7
|
const read = method === "GET" || method === "HEAD";
|
|
8
8
|
if (read) {
|
|
9
9
|
// the page and its files
|
|
10
|
-
if (path === "/" || path === "/index.html" || path === "/style.css" || /^\/(app|layout|edges|pdfworker)\.js(\.map)?$/.test(path) || path.startsWith("/pdfjs/"))
|
|
10
|
+
if (path === "/" || path === "/index.html" || path === "/style.css" || /^\/(app|layout|edges|pdfworker)\.js(\.map)?$/.test(path) || path.startsWith("/pdfjs/") || path.startsWith("/fonts/"))
|
|
11
11
|
return true;
|
|
12
12
|
// the graph, its sources and Lean's answers, the review, what happened, the views, the history,
|
|
13
13
|
// the library, the printed document's state and files, who the reader is and the link they
|
package/dist/server/snapshot.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
6
|
-
import { WEB, bundleDir } from "./pipeline.js";
|
|
6
|
+
import { WEB, WEBFONTS, bundleDir } from "./pipeline.js";
|
|
7
7
|
import { lakeTextHash } from "./status.js";
|
|
8
8
|
/** Text that sits inside a <script> element must not close it. */
|
|
9
9
|
function scriptSafe(s) {
|
|
@@ -28,7 +28,8 @@ export function writeSnapshot(project, prep, out, label, review, views) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const b64 = (buf) => buf.toString("base64");
|
|
31
|
-
|
|
31
|
+
// the artwork's fonts within the page, which has no server to ask for them
|
|
32
|
+
const css = readFileSync(join(WEB, "style.css"), "utf8").replace(/url\("\/fonts\/([a-z0-9-]+\.woff2)"\)/g, (_, f) => `url("data:font/woff2;base64,${b64(readFileSync(join(WEBFONTS, f)))}")`);
|
|
32
33
|
const js = readFileSync(join(bundleDir(), "app.js"), "utf8").replace(/\n\/\/# sourceMappingURL=.*$/m, "");
|
|
33
34
|
const info = { createdAt: new Date().toISOString(), label };
|
|
34
35
|
const html = readFileSync(join(WEB, "index.html"), "utf8")
|
package/dist/server/usage.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// One process per module: a process cannot import twice. `jobs` run at once; import is bound by
|
|
13
13
|
// memory bandwidth, and each worker holds hundreds of MB of its own besides the `.olean` files they
|
|
14
|
-
// all map (on
|
|
14
|
+
// all map (on Sphere-Packing-Lean about 150 MB of its own, 1.2 to 1.6 GB resident with them). A module's result is cached in the
|
|
15
15
|
// project's cache directory under a key
|
|
16
16
|
// made of its `.olean` hash, the text it was compiled from, the options Lake compiled it with, and
|
|
17
17
|
// the keys of the project modules it imports: after a build only the modules whose key changed run
|
|
@@ -25,7 +25,7 @@ import { SITE_BODY, SITE_GRAMMAR, SITE_PROOF, SITE_TCB } from "../shared/graph.j
|
|
|
25
25
|
import { USAGE, USAGE_PROGRAM, leanProgram, prune } from "./extract.js";
|
|
26
26
|
import { compiledTextHash, lakeTextHash } from "./status.js";
|
|
27
27
|
/** How many workers run at once unless `--jobs` says otherwise: past about a quarter of the cores,
|
|
28
|
-
* more workers gain less and less (
|
|
28
|
+
* more workers gain less and less (Sphere-Packing-Lean's cold pass on 32 cores: 320 s with 4, 195 s with
|
|
29
29
|
* 8, 148 s with 16). */
|
|
30
30
|
export const DEFAULT_JOBS = 8;
|
|
31
31
|
/** A worker still running after this long is stopped, and its module reported as failed. */
|
package/dist/server/view.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { LAYOUT_VERSION } from "../shared/graph.js";
|
|
6
|
+
import { gitSource } from "./history.js";
|
|
6
7
|
import { layOutHere, layOutOffThread } from "./layoutthread.js";
|
|
7
8
|
import { compiledTextHash } from "./status.js";
|
|
8
9
|
import { versionBase, versions } from "./versions.js";
|
|
@@ -74,6 +75,11 @@ function viewParts(p, ex) {
|
|
|
74
75
|
});
|
|
75
76
|
return { nodes, edges };
|
|
76
77
|
}
|
|
78
|
+
/** The project's git source and commit as it is read, where it is a git repository. */
|
|
79
|
+
function gitOf(root) {
|
|
80
|
+
const git = gitSource(root);
|
|
81
|
+
return git ? { git } : {};
|
|
82
|
+
}
|
|
77
83
|
function assemble(p, ex, nodes, edges, layouts) {
|
|
78
84
|
const g = ex.graph;
|
|
79
85
|
const meta = {
|
|
@@ -87,6 +93,7 @@ function assemble(p, ex, nodes, edges, layouts) {
|
|
|
87
93
|
toolchain: p.toolchain,
|
|
88
94
|
extractedAt: ex.at,
|
|
89
95
|
extractMs: g.stats.ms,
|
|
96
|
+
...gitOf(p.root),
|
|
90
97
|
},
|
|
91
98
|
modules: g.modules.map((m) => {
|
|
92
99
|
const file = p.files.get(m.name) ?? null;
|
package/dist/server/views.js
CHANGED
|
@@ -59,7 +59,7 @@ const HOLD_MS = 60_000;
|
|
|
59
59
|
/** the most views one private page holds */
|
|
60
60
|
const MAX_HOLDS = 100;
|
|
61
61
|
/** The most a board's layout memory may be when a page hands it over: room for a board of a large
|
|
62
|
-
* project's declarations many times over (
|
|
62
|
+
* project's declarations many times over (Sphere-Packing-Lean's whole board, 7,851 declarations, is
|
|
63
63
|
* 1.8 MB; 50 MB is the owner's decision of 2026-10-14). */
|
|
64
64
|
export const MAX_MEMORY_BYTES = 50 << 20;
|
|
65
65
|
const isPins = (p) => {
|
package/dist/shared/collab.js
CHANGED
|
@@ -63,6 +63,98 @@ export function colorOf(name) {
|
|
|
63
63
|
/** the most source windows a reader pins at once (web/src/sources.ts): a few declarations side by
|
|
64
64
|
* side, not a wall of them */
|
|
65
65
|
export const MAX_PINNED = 6;
|
|
66
|
+
/** An artwork's ways of being drawn (ArtworkLook `style`). */
|
|
67
|
+
export const ART_STYLES = ["board", "cells", "stars"];
|
|
68
|
+
/** the title's size, in hundredths of the picture's height: the least, the most, the first */
|
|
69
|
+
export const TITLE_SIZE = { least: 3, most: 16, first: 7 };
|
|
70
|
+
/** the foot's size, in hundredths of the picture's height: the least, the most, the first */
|
|
71
|
+
export const FOOT_SIZE = { least: 0.8, most: 4, first: 1.6 };
|
|
72
|
+
/** the most characters a title, or the line under it, has */
|
|
73
|
+
export const TITLE_CHARS = 120;
|
|
74
|
+
/** An artwork's title or its line as given: the reader's words, at most so long, or null (the
|
|
75
|
+
* project's own). */
|
|
76
|
+
export function cleanTitle(v) {
|
|
77
|
+
return typeof v === "string" ? v.replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, TITLE_CHARS) : null;
|
|
78
|
+
}
|
|
79
|
+
/** An artwork's title size as given, within its bounds; the first where it is none. */
|
|
80
|
+
export function cleanTitleSize(v) {
|
|
81
|
+
return typeof v === "number" && Number.isFinite(v) ? Math.min(TITLE_SIZE.most, Math.max(TITLE_SIZE.least, Math.round(v * 2) / 2)) : TITLE_SIZE.first;
|
|
82
|
+
}
|
|
83
|
+
/** An artwork's foot size as given, within its bounds, to a tenth; the first where it is none. */
|
|
84
|
+
export function cleanFootSize(v) {
|
|
85
|
+
return typeof v === "number" && Number.isFinite(v) ? Math.min(FOOT_SIZE.most, Math.max(FOOT_SIZE.least, Math.round(v * 10) / 10)) : FOOT_SIZE.first;
|
|
86
|
+
}
|
|
87
|
+
/** What a name of an artwork's names (ArtworkLook `names`) names: a cluster, by its key, or a
|
|
88
|
+
* declaration, by its full name. */
|
|
89
|
+
export function artNameId(kind, s) {
|
|
90
|
+
return `${kind === "cluster" ? "c" : "d"}:${s}`;
|
|
91
|
+
}
|
|
92
|
+
/** The most an artwork's names hidden, its names shown, and its targets chosen each take in what
|
|
93
|
+
* presence carries, in bytes of their JSON: a heartbeat is at most 16 KB (server/main.ts), and the
|
|
94
|
+
* rest of where a reader is has room beside them. Names by selection are kept as what differs from
|
|
95
|
+
* As shown, so that a few names hidden or added are a few names carried. */
|
|
96
|
+
export const ART_NAMES_BYTES = 4500;
|
|
97
|
+
export const ART_TARGETS_BYTES = 2500;
|
|
98
|
+
/** What `x` adds to a list's JSON: itself, quoted, and a comma. */
|
|
99
|
+
const jsonBytes = (x) => new TextEncoder().encode(JSON.stringify(x)).length + 1;
|
|
100
|
+
/** A list of names as given, checked: strings, each once, none empty or longer than a name is (1,000
|
|
101
|
+
* characters), each as `ok` says; kept in their order while their JSON stays within `budget` bytes,
|
|
102
|
+
* the rest left out. */
|
|
103
|
+
export function cleanNames(v, budget, ok = () => true) {
|
|
104
|
+
const out = [];
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
let used = 2;
|
|
107
|
+
for (const x of Array.isArray(v) ? v : []) {
|
|
108
|
+
if (typeof x !== "string" || !x || x.length > 1000 || seen.has(x) || !ok(x))
|
|
109
|
+
continue;
|
|
110
|
+
const b = jsonBytes(x);
|
|
111
|
+
if (used + b > budget)
|
|
112
|
+
break;
|
|
113
|
+
used += b;
|
|
114
|
+
seen.add(x);
|
|
115
|
+
out.push(x);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/** a name of an artwork's names: a cluster's or a declaration's (artNameId) */
|
|
120
|
+
export const isArtNameId = (s) => /^[cd]:./.test(s);
|
|
121
|
+
/** the most pixels a side of an artwork has */
|
|
122
|
+
export const ART_MAX_SIDE = 16384;
|
|
123
|
+
/** A place with no artwork in it: an agent's (ExportSpec decision 19, agents have no artwork). */
|
|
124
|
+
export function withoutArtwork(v) {
|
|
125
|
+
if (!v || !("artwork" in v))
|
|
126
|
+
return v;
|
|
127
|
+
const { artwork: _artwork, ...rest } = v;
|
|
128
|
+
return rest;
|
|
129
|
+
}
|
|
130
|
+
/** An artwork's settings as given, checked; null where they are not. */
|
|
131
|
+
export function cleanArtwork(v) {
|
|
132
|
+
if (!v || typeof v !== "object")
|
|
133
|
+
return null;
|
|
134
|
+
const o = v;
|
|
135
|
+
const one = (xs, x) => (xs.includes(x) ? x : null);
|
|
136
|
+
const side = (x) => (Number.isInteger(x) && x >= 16 && x <= ART_MAX_SIDE ? x : null);
|
|
137
|
+
const r = Array.isArray(o.region) && o.region.length === 4 && o.region.every((n) => typeof n === "number" && Number.isFinite(n) && Math.abs(n) < 1e9) ? o.region : null;
|
|
138
|
+
const w = side(o.w), h = side(o.h), scheme = one(SCHEMES, o.scheme), theme = one(["light", "dark"], o.theme);
|
|
139
|
+
const background = one(["theme", "transparent"], o.background), labels = one(["board", "clusters", "none", "chosen"], o.labels);
|
|
140
|
+
const edges = one(["all", "selection", "none"], o.edges), targets = one(["marked", "plain", "highlight", "chosen"], o.targets);
|
|
141
|
+
if (!r || r[2] <= 0 || r[3] <= 0 || !w || !h || !scheme || !theme || !background || !labels || !edges || !targets)
|
|
142
|
+
return null;
|
|
143
|
+
// (a proof edges' switch, said until 2026-10-15, is no more: an artwork draws none)
|
|
144
|
+
if (typeof o.notes !== "boolean" || typeof o.selection !== "boolean")
|
|
145
|
+
return null;
|
|
146
|
+
// the source, said since 2026-10-15: off where a page does not say it. The names and targets chosen
|
|
147
|
+
// and the key marks' switch, said since the evening of that day: none chosen where a page does not
|
|
148
|
+
// say them, and the key marks as the targets drew them before (with their stars, unless As any)
|
|
149
|
+
const namesFrom = o.namesFrom === "none" ? "none" : "board";
|
|
150
|
+
const hiddenNames = cleanNames(o.hiddenNames, ART_NAMES_BYTES, isArtNameId), shownNames = cleanNames(o.shownNames, ART_NAMES_BYTES, isArtNameId);
|
|
151
|
+
const shownTargets = cleanNames(o.shownTargets, ART_TARGETS_BYTES);
|
|
152
|
+
const keys = typeof o.keys === "boolean" ? o.keys : targets !== "plain";
|
|
153
|
+
// the style and the title, said since that night: as the board, the project's own; the foot's
|
|
154
|
+
// size, said since later that night: the first
|
|
155
|
+
const style = one(ART_STYLES, o.style) ?? "board";
|
|
156
|
+
return { w, h, scheme, theme, background, labels, namesFrom, hiddenNames, shownNames, edges, notes: o.notes, targets, shownTargets, keys, selection: o.selection, source: o.source === true, style, title: cleanTitle(o.title), subtitle: cleanTitle(o.subtitle), titleSize: cleanTitleSize(o.titleSize), footSize: cleanFootSize(o.footSize), border: o.border === true, region: [...r] };
|
|
157
|
+
}
|
|
66
158
|
/** The legend's schemes (CanvasSpec, "Colour schemes"). */
|
|
67
159
|
export const SCHEMES = ["kind", "status"];
|
|
68
160
|
/** Activity's filters: the kind of event, and whose (web/src/activity.ts). */
|
|
@@ -143,6 +235,7 @@ export function cleanView(v) {
|
|
|
143
235
|
...(typeof lost === "boolean" ? { lostFound: { open: lost } } : {}),
|
|
144
236
|
...(scheme ? { scheme } : {}),
|
|
145
237
|
...(typeof o.edges === "boolean" ? { edges: o.edges } : {}),
|
|
146
|
-
...(typeof o.
|
|
238
|
+
...(typeof o.linear === "boolean" ? { linear: o.linear } : {}),
|
|
239
|
+
...(o.artwork === null ? { artwork: null } : cleanArtwork(o.artwork) ? { artwork: cleanArtwork(o.artwork) } : {}),
|
|
147
240
|
};
|
|
148
241
|
}
|
package/dist/shared/doc/order.js
CHANGED
|
@@ -28,6 +28,23 @@ export function boardOrder(xs, at) {
|
|
|
28
28
|
});
|
|
29
29
|
return keyed.map((e) => e.x);
|
|
30
30
|
}
|
|
31
|
+
/** Where `a` goes on the board to come between `prev` and `next` in the board's order (`prev` read
|
|
32
|
+
* before it, so lower; `next` after, higher), all three of one cluster: the linear form's drop
|
|
33
|
+
* (ExportSpec decision 18, web/src/linear.ts). At a height between theirs where they are across,
|
|
34
|
+
* where `a` is across; on their row, between them, where they are level; below the first, above
|
|
35
|
+
* the last, clear of it. Null where neither is given. */
|
|
36
|
+
export function spotBetween(a, prev, next) {
|
|
37
|
+
if (prev && next) {
|
|
38
|
+
if (Math.abs(prev.y - next.y) >= 1)
|
|
39
|
+
return [a.x, (prev.y + next.y) / 2];
|
|
40
|
+
return [(prev.x + next.x) / 2, prev.y];
|
|
41
|
+
}
|
|
42
|
+
if (next)
|
|
43
|
+
return [a.x, next.y + next.r + a.r + 2.5];
|
|
44
|
+
if (prev)
|
|
45
|
+
return [a.x, prev.y - prev.r - a.r - 2.5];
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
31
48
|
/** The outline of the document of `plan` (of graph `g`) in the order of the board `spots` describes. */
|
|
32
49
|
export function orderDoc(plan, g, spots) {
|
|
33
50
|
// the declarations the document prints, by name: where an attached note goes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The document's outline (ExportSpec decision 18, the linear form): what the document of a view
|
|
2
|
+
// prints, in its order, without the text — its sections, numbered as the PDF numbers them, the
|
|
3
|
+
// declarations and the notes in each, and the notes attached to a declaration after it. The page
|
|
4
|
+
// draws the board as this outline (web/src/linear.ts); it is made from the same plan and the same
|
|
5
|
+
// order the document is (shared/doc/sections.ts, shared/doc/order.ts), so that the two agree.
|
|
6
|
+
import { orderDoc } from "./order.js";
|
|
7
|
+
/** A note's first words: its first line, Markdown marks taken out, at most 60 characters. */
|
|
8
|
+
export function firstWords(text) {
|
|
9
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
10
|
+
const plain = line.replace(/[*_`#>]+/g, "").replace(/\s+/g, " ").trim();
|
|
11
|
+
return plain.length > 60 ? `${plain.slice(0, 59).trimEnd()}…` : plain;
|
|
12
|
+
}
|
|
13
|
+
/** The outline of the document of `plan` (of graph `g`) in the order of the board `spots` gives. */
|
|
14
|
+
export function outlineOf(plan, g, spots) {
|
|
15
|
+
const order = orderDoc(plan, g, spots);
|
|
16
|
+
const note = (k) => {
|
|
17
|
+
const n = spots.notes[k];
|
|
18
|
+
return { t: "note", id: n.note.id, kind: n.kind, words: firstWords(n.note.text) };
|
|
19
|
+
};
|
|
20
|
+
const item = (p, prefix, at) => {
|
|
21
|
+
if (p.t === "note")
|
|
22
|
+
return note(p.k);
|
|
23
|
+
if (p.t === "decl") {
|
|
24
|
+
const ks = order.attached.get(p.i);
|
|
25
|
+
return { t: "decl", name: g.nodes[p.i].name, ...(ks?.length ? { notes: ks.map(note) } : {}) };
|
|
26
|
+
}
|
|
27
|
+
return section(p.s, prefix, at);
|
|
28
|
+
};
|
|
29
|
+
// numbered as the PDF numbers its headings (shared/doc/latex.ts): the first three levels, each
|
|
30
|
+
// within the one above; a paragraph-level heading has none
|
|
31
|
+
const section = (o, prefix, at) => {
|
|
32
|
+
const p = o.plan;
|
|
33
|
+
at.k++;
|
|
34
|
+
const number = p.level < 4 ? (prefix ? `${prefix}.${at.k}` : `${at.k}`) : null;
|
|
35
|
+
const inner = { k: 0 };
|
|
36
|
+
const items = o.items.map((x) => item(x, p.level < 3 ? (number ?? "") : "", inner));
|
|
37
|
+
return { t: "section", key: p.key, keys: [...p.keys], title: p.title, number, level: p.level, items };
|
|
38
|
+
};
|
|
39
|
+
const top = { k: 0 };
|
|
40
|
+
return {
|
|
41
|
+
items: order.items.map((x) => item(x, "", top)),
|
|
42
|
+
count: { declarations: plan.count, sections: plan.sectionCount },
|
|
43
|
+
...(spots.fresh ? { fresh: true } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/shared/keys.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// A review holds many members and a target a dozen key declarations: after one walk over the
|
|
23
23
|
// target's TCB, each walk reads the edges of the members and of what it passes through, never the
|
|
24
|
-
// whole graph's (
|
|
24
|
+
// whole graph's (Sphere-Packing-Lean's `Dim24.MainTheorem`: its TCB of 226 in 72 ms, its keys in 1 ms).
|
|
25
25
|
import { keyMarks, statusOf } from "./review.js";
|
|
26
26
|
import { TCB_SITES } from "./reviewqueue.js";
|
|
27
27
|
/** The key declarations of `root` among the members of its review (`order`, the queue's order —
|
package/dist/shared/moves.js
CHANGED
package/dist/shared/pastgraph.js
CHANGED
|
@@ -21,6 +21,13 @@ export const PAST_GHOST = 1;
|
|
|
21
21
|
export const PAST_CHANGED = 4;
|
|
22
22
|
/** renamed since: shown under its name then */
|
|
23
23
|
export const PAST_RENAMED = 8;
|
|
24
|
+
/** The git source of a past build: the project's source, the build's own commit; none where the
|
|
25
|
+
* build's commit is not known, or only guessed. */
|
|
26
|
+
function gitThen(now, commit) {
|
|
27
|
+
if (!commit || commit.guessed)
|
|
28
|
+
return { git: undefined };
|
|
29
|
+
return { git: { source: now?.source ?? null, commit: commit.hash, branch: commit.branch, ...(commit.dirty ? { dirty: true } : {}) } };
|
|
30
|
+
}
|
|
24
31
|
/** The past graph of snapshot `snap` (its declarations and its edges, as /graph's parts give them),
|
|
25
32
|
* marked by `diff` (from it to the latest), with the project's description of `now`. */
|
|
26
33
|
export function pastGraph(snap, edges, now, diff) {
|
|
@@ -33,7 +40,8 @@ export function pastGraph(snap, edges, now, diff) {
|
|
|
33
40
|
const meta = {
|
|
34
41
|
schema: 1,
|
|
35
42
|
layoutVersion: now.meta.layoutVersion,
|
|
36
|
-
|
|
43
|
+
// the commit of that build, where it is known; the source is the project's now
|
|
44
|
+
project: { ...now.meta.project, extractedAt: snap.snapshot.at, ...gitThen(now.meta.project.git, snap.snapshot.commit) },
|
|
37
45
|
modules: snap.modules.map((m) => ({ name: m.name, file: null, imports: m.imports })),
|
|
38
46
|
nodes,
|
|
39
47
|
projectCount: P,
|
package/dist/shared/query.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// The text form, left to right:
|
|
10
10
|
//
|
|
11
|
-
// all | selection | decl(A, B) | tcb(A) | uses(A, B) | usedBy(selection)
|
|
11
|
+
// all | selection | decl(A, B) | tcb(A) | uses(A, B) | usedBy(selection) | refs(A)
|
|
12
12
|
// tcb(ns(Fermat42) except external) a closure of the set a query holds
|
|
13
13
|
// ns(Fermat42) declarations whose name is under the prefix
|
|
14
14
|
// file(Mathlib.NumberTheory) declarations of the modules under the prefix
|
|
@@ -27,7 +27,7 @@ import { adviceOf, keyMarks, STANDARD_AXIOMS, statusOf, trusted, under } from ".
|
|
|
27
27
|
export const KINDS = ["theorem", "def", "abbrev", "instance", "structure", "class", "inductive", "opaque", "axiom", "quot", "constructor", "recursor", "notation", "unknown"];
|
|
28
28
|
export const STATUSES = ["unreviewed", "approved", "commented", "stale"];
|
|
29
29
|
const VERDICTS = ["approve", "comment"];
|
|
30
|
-
const CLOSURES = ["tcb", "uses", "usedBy"];
|
|
30
|
+
const CLOSURES = ["tcb", "uses", "usedBy", "refs"];
|
|
31
31
|
const PLAIN = ["all", "selection", "trusted", "external", "sorry", "nonstandard"];
|
|
32
32
|
const COMBINATORS = ["and", "or", "except"];
|
|
33
33
|
/** words for the same thing, read as the first */
|
|
@@ -38,10 +38,11 @@ export const LIGHT = {
|
|
|
38
38
|
plain: { op: "selection" },
|
|
39
39
|
tcb: { op: "tcb", of: "selection" },
|
|
40
40
|
deps: { op: "uses", of: "selection" },
|
|
41
|
+
refs: { op: "refs", of: "selection" },
|
|
41
42
|
apps: { op: "usedBy", of: "selection" },
|
|
42
43
|
};
|
|
43
|
-
/** Which
|
|
44
|
-
const CLOSURE_MODE = { tcb: "tcb", uses: "deps", usedBy: "apps" };
|
|
44
|
+
/** Which walk of the graph each closure word is (shared/viewgraph.ts); `refs` is one step. */
|
|
45
|
+
const CLOSURE_MODE = { tcb: "tcb", uses: "deps", usedBy: "apps", refs: "refs" };
|
|
45
46
|
// ---------------------------------------------------------------- how large a query may be
|
|
46
47
|
/** How large a query may be: one set of bounds for its text and its JSON form, checked when either
|
|
47
48
|
* is read (parseQuery, cleanQuery), so that what one form takes the other takes too — a scope
|
|
@@ -61,7 +62,7 @@ export const QUERY_LIMITS = {
|
|
|
61
62
|
names: 2_000,
|
|
62
63
|
/** characters of one name or prefix */
|
|
63
64
|
name: 2_000,
|
|
64
|
-
/** closures (tcb, uses, usedBy), each a walk of the graph; each target of a `key(…)` counts as
|
|
65
|
+
/** closures (tcb, uses, usedBy, refs), each a walk of the graph; each target of a `key(…)` counts as
|
|
65
66
|
* one too, since its review set is walked to keep only the marks still in it. `key(targets)`
|
|
66
67
|
* counts as one: how many targets there are is the project's, not the query's, and every
|
|
67
68
|
* `key(targets)` of a query shares the same walks (evaluateIn) */
|
|
@@ -88,6 +89,7 @@ export function beyondLimits(q) {
|
|
|
88
89
|
case "tcb":
|
|
89
90
|
case "uses":
|
|
90
91
|
case "usedBy":
|
|
92
|
+
case "refs":
|
|
91
93
|
closures++;
|
|
92
94
|
if (x.of !== "selection" && !Array.isArray(x.of)) {
|
|
93
95
|
terms++;
|
|
@@ -131,7 +133,7 @@ export function beyondLimits(q) {
|
|
|
131
133
|
if (longest > L.name)
|
|
132
134
|
return `a name in the query is longer than ${L.name.toLocaleString("en")} characters`;
|
|
133
135
|
if (closures > L.closures)
|
|
134
|
-
return `the query has ${closures} closures (tcb, uses, usedBy), more than ${L.closures}`;
|
|
136
|
+
return `the query has ${closures} closures (tcb, uses, usedBy, refs), more than ${L.closures}`;
|
|
135
137
|
const length = printQuery(q).length;
|
|
136
138
|
if (length > L.text)
|
|
137
139
|
return `the query is ${length.toLocaleString("en")} characters long as printed, more than ${L.text.toLocaleString("en")}`;
|
|
@@ -286,7 +288,7 @@ function parse(text) {
|
|
|
286
288
|
}
|
|
287
289
|
const w = keyword(t);
|
|
288
290
|
if (w === null)
|
|
289
|
-
throw new QueryError(t.t === "string" ? "a quoted name stands only inside decl(…), tcb(…), uses(…) or
|
|
291
|
+
throw new QueryError(t.t === "string" ? "a quoted name stands only inside decl(…), tcb(…), uses(…), usedBy(…) or refs(…)" : `${t.t} is out of place`, t.at);
|
|
290
292
|
if (++terms > QUERY_LIMITS.terms)
|
|
291
293
|
throw new QueryError(`the query has more than ${QUERY_LIMITS.terms.toLocaleString("en")} parts`, t.at);
|
|
292
294
|
k++;
|
|
@@ -338,7 +340,7 @@ function parse(text) {
|
|
|
338
340
|
return { op: "status", statuses: choose(args(), STATUSES, "status") };
|
|
339
341
|
if (w === "advice")
|
|
340
342
|
return { op: "advice", verdicts: choose(args(), VERDICTS, "advice") };
|
|
341
|
-
throw new QueryError(`${text1(t)} is not a word of the query language (all, tcb, uses, usedBy, decl, key, ns, file, trusted, external, kind, status, advice, sorry, nonstandard)`, t.at);
|
|
343
|
+
throw new QueryError(`${text1(t)} is not a word of the query language (all, tcb, uses, usedBy, refs, decl, key, ns, file, trusted, external, kind, status, advice, sorry, nonstandard)`, t.at);
|
|
342
344
|
};
|
|
343
345
|
const expr = () => {
|
|
344
346
|
let q = atom();
|
|
@@ -376,7 +378,8 @@ export function printQuery(q) {
|
|
|
376
378
|
return `key(${q.of === "targets" ? "targets" : q.of.map((t) => (t === "targets" ? JSON.stringify(t) : arg(t))).join(", ")})`;
|
|
377
379
|
case "tcb":
|
|
378
380
|
case "uses":
|
|
379
|
-
case "usedBy":
|
|
381
|
+
case "usedBy":
|
|
382
|
+
case "refs": {
|
|
380
383
|
if (q.of === "selection" || Array.isArray(q.of))
|
|
381
384
|
return `${q.op}(${q.of === "selection" ? "selection" : q.of.map(arg).join(", ")})`;
|
|
382
385
|
// a query of one plain word would read as a name
|
|
@@ -461,7 +464,8 @@ function readJson(x, why = false) {
|
|
|
461
464
|
return names(o.of) ? { op: "key", of: [...o.of] } : null;
|
|
462
465
|
case "tcb":
|
|
463
466
|
case "uses":
|
|
464
|
-
case "usedBy":
|
|
467
|
+
case "usedBy":
|
|
468
|
+
case "refs": {
|
|
465
469
|
if (o.of === "selection")
|
|
466
470
|
return { op: o.op, of: "selection" };
|
|
467
471
|
if (o.of && typeof o.of === "object" && !Array.isArray(o.of)) {
|
|
@@ -531,7 +535,7 @@ export function namesIn(q) {
|
|
|
531
535
|
x.names.forEach((n) => out.add(n));
|
|
532
536
|
else if (x.op === "key" && x.of !== "targets")
|
|
533
537
|
x.of.forEach((n) => out.add(n));
|
|
534
|
-
else if (x.op === "tcb" || x.op === "uses" || x.op === "usedBy") {
|
|
538
|
+
else if (x.op === "tcb" || x.op === "uses" || x.op === "usedBy" || x.op === "refs") {
|
|
535
539
|
if (Array.isArray(x.of))
|
|
536
540
|
x.of.forEach((n) => out.add(n));
|
|
537
541
|
else if (x.of !== "selection")
|
|
@@ -549,7 +553,7 @@ export function namesIn(q) {
|
|
|
549
553
|
export function readsReview(q) {
|
|
550
554
|
if (q.op === "trusted" || q.op === "status" || q.op === "advice" || q.op === "key")
|
|
551
555
|
return true;
|
|
552
|
-
if ((q.op === "tcb" || q.op === "uses" || q.op === "usedBy") && q.of !== "selection" && !Array.isArray(q.of))
|
|
556
|
+
if ((q.op === "tcb" || q.op === "uses" || q.op === "usedBy" || q.op === "refs") && q.of !== "selection" && !Array.isArray(q.of))
|
|
553
557
|
return readsReview(q.of);
|
|
554
558
|
if (q.op === "and" || q.op === "or" || q.op === "except")
|
|
555
559
|
return readsReview(q.a) || readsReview(q.b);
|
|
@@ -563,6 +567,7 @@ export function withSelection(q, names) {
|
|
|
563
567
|
case "tcb":
|
|
564
568
|
case "uses":
|
|
565
569
|
case "usedBy":
|
|
570
|
+
case "refs":
|
|
566
571
|
if (q.of === "selection")
|
|
567
572
|
return { op: q.op, of: [...names] };
|
|
568
573
|
return Array.isArray(q.of) ? q : { op: q.op, of: withSelection(q.of, names) };
|
|
@@ -647,6 +652,8 @@ export function describe(q) {
|
|
|
647
652
|
return `TCB of ${roots(q.of)}`;
|
|
648
653
|
case "uses":
|
|
649
654
|
return `what ${roots(q.of)} uses`;
|
|
655
|
+
case "refs":
|
|
656
|
+
return `what ${roots(q.of)} uses directly`;
|
|
650
657
|
case "usedBy":
|
|
651
658
|
return `what uses ${roots(q.of)}`;
|
|
652
659
|
case "ns":
|
|
@@ -759,7 +766,8 @@ function evaluateIn(q, ctx, walks) {
|
|
|
759
766
|
return out;
|
|
760
767
|
case "tcb":
|
|
761
768
|
case "uses":
|
|
762
|
-
case "usedBy":
|
|
769
|
+
case "usedBy":
|
|
770
|
+
case "refs": {
|
|
763
771
|
const key = `${q.op}\u0000${q.of === "selection" ? "\u0001" : Array.isArray(q.of) ? JSON.stringify(q.of) : printQuery(q.of)}`;
|
|
764
772
|
let walked = walks.get(key);
|
|
765
773
|
if (!walked) {
|
package/dist/shared/sharesnap.js
CHANGED
|
@@ -105,7 +105,8 @@ export const SNAPSHOT_FORMS = {
|
|
|
105
105
|
lostFound: same("lostFound"),
|
|
106
106
|
colouring: same("scheme"),
|
|
107
107
|
edges: same("edges"),
|
|
108
|
-
|
|
108
|
+
linear: same("linear"),
|
|
109
|
+
artwork: same("artwork"),
|
|
109
110
|
};
|
|
110
111
|
/** The snapshot of where a page is (`v`, as presence carries it: the registry's parts, read by the
|
|
111
112
|
* page), as a link carries it after `snap=`. */
|
package/dist/shared/titles.js
CHANGED
|
@@ -224,6 +224,8 @@ function said(q, N) {
|
|
|
224
224
|
return `TCB of ${roots(q.of, N)}`;
|
|
225
225
|
case "uses":
|
|
226
226
|
return `what ${roots(q.of, N)} uses`;
|
|
227
|
+
case "refs":
|
|
228
|
+
return `what ${roots(q.of, N)} uses directly`;
|
|
227
229
|
case "usedBy":
|
|
228
230
|
return `what uses ${roots(q.of, N)}`;
|
|
229
231
|
case "ns":
|
package/dist/shared/viewgraph.js
CHANGED
|
@@ -9,6 +9,7 @@ export const MODE_SITES = {
|
|
|
9
9
|
plain: ALL_SITES,
|
|
10
10
|
tcb: SITE_STMT | SITE_BODY,
|
|
11
11
|
deps: ALL_SITES,
|
|
12
|
+
refs: ALL_SITES,
|
|
12
13
|
apps: ALL_SITES,
|
|
13
14
|
};
|
|
14
15
|
export class Graph {
|
|
@@ -69,6 +70,20 @@ export class Graph {
|
|
|
69
70
|
isExt(i) {
|
|
70
71
|
return i >= this.projectCount;
|
|
71
72
|
}
|
|
73
|
+
/** `from` and what is one edge from it, along edges whose sites meet `sites`. */
|
|
74
|
+
step(from, sites, dir) {
|
|
75
|
+
const seen = new Uint8Array(this.n);
|
|
76
|
+
const start = dir === "out" ? this.outStart : this.inStart;
|
|
77
|
+
const adj = dir === "out" ? this.outDst : this.inSrc;
|
|
78
|
+
const site = dir === "out" ? this.outSite : this.inSite;
|
|
79
|
+
for (const u of from) {
|
|
80
|
+
seen[u] = 1;
|
|
81
|
+
for (let e = start[u]; e < start[u + 1]; e++)
|
|
82
|
+
if (site[e] & sites)
|
|
83
|
+
seen[adj[e]] = 1;
|
|
84
|
+
}
|
|
85
|
+
return seen;
|
|
86
|
+
}
|
|
72
87
|
/** Everything reachable from `from` (included) along edges whose sites meet `sites`. */
|
|
73
88
|
reach(from, sites, dir) {
|
|
74
89
|
const seen = new Uint8Array(this.n);
|
|
@@ -108,6 +123,9 @@ export class Graph {
|
|
|
108
123
|
return this.reach(focus, MODE_SITES.tcb, "out");
|
|
109
124
|
case "deps":
|
|
110
125
|
return this.reach(focus, ALL_SITES, "out");
|
|
126
|
+
// Direct: what it uses directly, one step (ViewSpec decision 20, the owner's decision of 2026-10-14)
|
|
127
|
+
case "refs":
|
|
128
|
+
return this.step(focus, ALL_SITES, "out");
|
|
111
129
|
case "apps":
|
|
112
130
|
return this.reach(focus, ALL_SITES, "in");
|
|
113
131
|
}
|