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.
- package/LICENSE +202 -0
- package/README.md +99 -2
- package/dist/assets/fonts/JuliaMono-Regular.ttf +0 -0
- package/dist/assets/fonts/JuliaMono-RegularItalic.ttf +0 -0
- package/dist/assets/fonts/JuliaMono-SemiBold.ttf +0 -0
- package/dist/assets/fonts/LICENSE-JuliaMono.txt +93 -0
- package/dist/assets/latex/visualproof.sty +308 -0
- package/dist/extractor/Extract.lean +1180 -0
- package/dist/extractor/Usage.lean +900 -0
- package/dist/mcp/visualproof-mcp.mjs +476 -0
- package/dist/server/access.js +95 -0
- package/dist/server/agent.js +3235 -0
- package/dist/server/agentdocs.js +35 -0
- package/dist/server/agentlibrary.js +202 -0
- package/dist/server/agentnotes.js +701 -0
- package/dist/server/agentviews.js +703 -0
- package/dist/server/askfollow.js +286 -0
- package/dist/server/boardspots.js +159 -0
- package/dist/server/dirs.js +175 -0
- package/dist/server/doctor.js +186 -0
- package/dist/server/events.js +198 -0
- package/dist/server/export.js +420 -0
- package/dist/server/extract.js +204 -0
- package/dist/server/fetchurl.js +321 -0
- package/dist/server/figures.js +158 -0
- package/dist/server/history.js +741 -0
- package/dist/server/historyapi.js +304 -0
- package/dist/server/hover.js +595 -0
- package/dist/server/identity.js +114 -0
- package/dist/server/invites.js +241 -0
- package/dist/server/layoutthread.js +51 -0
- package/dist/server/library.js +600 -0
- package/dist/server/libraryroute.js +211 -0
- package/dist/server/live.js +1101 -0
- package/dist/server/lostfound.js +199 -0
- package/dist/server/main.js +1394 -0
- package/dist/server/mcptools.js +497 -0
- package/dist/server/pastdoc.js +73 -0
- package/dist/server/pipeline.js +156 -0
- package/dist/server/poppler.js +251 -0
- package/dist/server/presence.js +252 -0
- package/dist/server/project.js +222 -0
- package/dist/server/prompt.js +53 -0
- package/dist/server/readonly.js +152 -0
- package/dist/server/readonlygate.js +123 -0
- package/dist/server/renames.js +268 -0
- package/dist/server/review.js +334 -0
- package/dist/server/reviewapi.js +188 -0
- package/dist/server/snapshot.js +49 -0
- package/dist/server/sourcestore.js +473 -0
- package/dist/server/status.js +360 -0
- package/dist/server/talk.js +476 -0
- package/dist/server/tokens.js +221 -0
- package/dist/server/usage.js +501 -0
- package/dist/server/versions.js +82 -0
- package/dist/server/view.js +157 -0
- package/dist/server/views.js +1167 -0
- package/dist/shared/askfollow.js +46 -0
- package/dist/shared/board.js +332 -0
- package/dist/shared/build.js +2 -0
- package/dist/shared/collab.js +148 -0
- package/dist/shared/comment.js +285 -0
- package/dist/shared/context.js +157 -0
- package/dist/shared/doc/authors.js +98 -0
- package/dist/shared/doc/build.js +358 -0
- package/dist/shared/doc/latex.js +455 -0
- package/dist/shared/doc/markdown.js +159 -0
- package/dist/shared/doc/model.js +37 -0
- package/dist/shared/doc/order.js +107 -0
- package/dist/shared/doc/proof.js +272 -0
- package/dist/shared/doc/sections.js +93 -0
- package/dist/shared/doc/settings.js +75 -0
- package/dist/shared/edgecodec.js +102 -0
- package/dist/shared/editor.js +64 -0
- package/dist/shared/excerpt.js +87 -0
- package/dist/shared/graph.js +16 -0
- package/dist/shared/items.js +92 -0
- package/dist/shared/keys.js +203 -0
- package/dist/shared/layout.js +926 -0
- package/dist/shared/lexer.js +130 -0
- package/dist/shared/library.js +711 -0
- package/dist/shared/lostfound.js +150 -0
- package/dist/shared/moves.js +243 -0
- package/dist/shared/notecard.js +295 -0
- package/dist/shared/noteplace.js +505 -0
- package/dist/shared/palette.js +46 -0
- package/dist/shared/pastgraph.js +95 -0
- package/dist/shared/places.js +80 -0
- package/dist/shared/projectnotes.js +244 -0
- package/dist/shared/query.js +810 -0
- package/dist/shared/review.js +642 -0
- package/dist/shared/reviewqueue.js +333 -0
- package/dist/shared/ring.js +366 -0
- package/dist/shared/scope.js +255 -0
- package/dist/shared/scopeedit.js +81 -0
- package/dist/shared/search.js +191 -0
- package/dist/shared/separate.js +178 -0
- package/dist/shared/sharesnap.js +280 -0
- package/dist/shared/sticky.js +501 -0
- package/dist/shared/timeline.js +73 -0
- package/dist/shared/titles.js +298 -0
- package/dist/shared/viewgraph.js +139 -0
- package/dist/shared/views.js +859 -0
- package/dist/web/bundle/app.js +48733 -0
- package/dist/web/bundle/edges.js +409 -0
- package/dist/web/bundle/layout.js +1010 -0
- package/dist/web/bundle/pdfworker.js +48322 -0
- package/dist/web/index.html +239 -0
- package/dist/web/src/motion.js +430 -0
- package/dist/web/src/past.js +224 -0
- package/dist/web/src/scene.js +787 -0
- package/dist/web/style.css +2312 -0
- package/docs/agent-api/README.md +91 -0
- package/docs/agent-api/advice.md +108 -0
- package/docs/agent-api/export.md +120 -0
- package/docs/agent-api/following.md +140 -0
- package/docs/agent-api/history.md +142 -0
- package/docs/agent-api/library.md +65 -0
- package/docs/agent-api/mcp.md +104 -0
- package/docs/agent-api/notes.md +93 -0
- package/docs/agent-api/reading.md +166 -0
- package/docs/agent-api/requests.md +94 -0
- package/docs/agent-api/talking.md +100 -0
- package/docs/agent-api/tcb.md +135 -0
- package/docs/agent-api/views.md +124 -0
- package/docs/agent-api/words.md +56 -0
- package/package.json +39 -4
- package/test/toolchains.json +5 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
// The exported document as LaTeX (ExportSpec decisions 6–8 and 13): the document model
|
|
2
|
+
// (shared/doc/model.ts) written as semantic macros and environments — what each thing is, a section,
|
|
3
|
+
// a declaration, its docstring, its code, a note and its figures — which the style file,
|
|
4
|
+
// assets/latex/visualproof.sty, typesets; a user replaces or extends that file to make the document
|
|
5
|
+
// their own. The model's order is the board's (the owner's decisions of 2026-10-12), so a section's
|
|
6
|
+
// own declarations and notes may follow one of its sections: they are headed again by the section's
|
|
7
|
+
// title, "continued", so that nothing reads as the section before's. Only the
|
|
8
|
+
// settings that are the document's own (paper, margins, sizes, whether the contents are printed)
|
|
9
|
+
// and VisualProof's palette (shared/palette.ts: the code's tokens, so that the document's code
|
|
10
|
+
// reads as the page's) are written here. The renderer numbers the sections (three levels; the
|
|
11
|
+
// paragraph-level headings below are not numbered), and writes the table of contents from them.
|
|
12
|
+
//
|
|
13
|
+
// Nothing that reaches the .tex is trusted: names, docstrings, comments, code and the title are
|
|
14
|
+
// what people, agents and source files said. Every one of them goes through `escapeLatex` (prose)
|
|
15
|
+
// or `escapeVerbatim` (code), which leave no character LaTeX would read as anything but text:
|
|
16
|
+
// `\input{/etc/passwd}` prints as written. The server compiles with -no-shell-escape and paranoid
|
|
17
|
+
// file access besides (server/export.ts), but the escaping is what keeps a document from doing
|
|
18
|
+
// anything at all.
|
|
19
|
+
//
|
|
20
|
+
// The .tex holds no time of its making (a past build's says when that build finished, which does not
|
|
21
|
+
// change): the same document asked for again is the same text, which the server's cache knows by its
|
|
22
|
+
// hash.
|
|
23
|
+
import { TOKEN_COLORS } from "../palette.js";
|
|
24
|
+
/** Code points the text font (Libertinus) may lack and JuliaMono has: superscripts and subscripts,
|
|
25
|
+
* letterlike symbols (ℕ ℝ), arrows, mathematical operators, technical symbols, enclosed
|
|
26
|
+
* alphanumerics, box drawing, blocks, geometric shapes, miscellaneous symbols and dingbats, the
|
|
27
|
+
* mathematical brackets (⟪ ⟫ ⦃ ⦄) and arrows, supplemental operators, and the mathematical
|
|
28
|
+
* alphanumerics (𝔽 𝒪). They are set in JuliaMono (`\vpSym`) wherever prose holds them. */
|
|
29
|
+
const SYMBOL_RANGES = [
|
|
30
|
+
[0x2070, 0x209f],
|
|
31
|
+
[0x2100, 0x214f],
|
|
32
|
+
[0x2190, 0x23ff],
|
|
33
|
+
[0x2460, 0x27bf],
|
|
34
|
+
[0x27c0, 0x2bff],
|
|
35
|
+
[0x1d400, 0x1d7ff],
|
|
36
|
+
];
|
|
37
|
+
const isSymbol = (cp) => SYMBOL_RANGES.some(([a, b]) => cp >= a && cp <= b);
|
|
38
|
+
/** Characters that are never text: C0 and C1 controls, and the line and paragraph separators. */
|
|
39
|
+
const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029]/g;
|
|
40
|
+
/** Prose, a name or a line of the head card, as LaTeX that prints exactly that text: the ten
|
|
41
|
+
* characters LaTeX gives a meaning to are spelt out, control characters dropped, line breaks made
|
|
42
|
+
* spaces, the ligatures Libertinus would make of `--` and backquotes broken, and symbols the text
|
|
43
|
+
* font lacks set in JuliaMono. */
|
|
44
|
+
export function escapeLatex(s) {
|
|
45
|
+
let out = "";
|
|
46
|
+
let sym = "";
|
|
47
|
+
const flush = () => {
|
|
48
|
+
if (sym)
|
|
49
|
+
out += `\\vpSym{${sym}}`;
|
|
50
|
+
sym = "";
|
|
51
|
+
};
|
|
52
|
+
for (const ch of String(s).replace(CONTROL, "").replace(/[\r\n\t]+/g, " ")) {
|
|
53
|
+
const cp = ch.codePointAt(0);
|
|
54
|
+
if (isSymbol(cp)) {
|
|
55
|
+
sym += ch;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
flush();
|
|
59
|
+
switch (ch) {
|
|
60
|
+
case "\\":
|
|
61
|
+
out += "\\textbackslash{}";
|
|
62
|
+
break;
|
|
63
|
+
case "{":
|
|
64
|
+
out += "\\{";
|
|
65
|
+
break;
|
|
66
|
+
case "}":
|
|
67
|
+
out += "\\}";
|
|
68
|
+
break;
|
|
69
|
+
case "$":
|
|
70
|
+
out += "\\$";
|
|
71
|
+
break;
|
|
72
|
+
case "&":
|
|
73
|
+
out += "\\&";
|
|
74
|
+
break;
|
|
75
|
+
case "#":
|
|
76
|
+
out += "\\#";
|
|
77
|
+
break;
|
|
78
|
+
case "%":
|
|
79
|
+
out += "\\%";
|
|
80
|
+
break;
|
|
81
|
+
case "_":
|
|
82
|
+
out += "\\_";
|
|
83
|
+
break;
|
|
84
|
+
case "^":
|
|
85
|
+
out += "\\textasciicircum{}";
|
|
86
|
+
break;
|
|
87
|
+
case "~":
|
|
88
|
+
out += "\\textasciitilde{}";
|
|
89
|
+
break;
|
|
90
|
+
case "`":
|
|
91
|
+
out += "\\textasciigrave{}";
|
|
92
|
+
break;
|
|
93
|
+
case "-":
|
|
94
|
+
// `--` would be a dash: the text keeps its hyphens
|
|
95
|
+
out += out.endsWith("-") ? "{}-" : "-";
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
out += ch;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
flush();
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/** A piece of code as the text of a line of `vpCode` (fvextra's Verbatim, its command characters
|
|
105
|
+
* `\ { }`): those three spelt out as macros, tabs made two spaces, control characters dropped.
|
|
106
|
+
* Everything else is printed as it is, in JuliaMono. */
|
|
107
|
+
export function escapeVerbatim(s) {
|
|
108
|
+
return String(s)
|
|
109
|
+
.replace(CONTROL, "")
|
|
110
|
+
.replace(/[\r\n]/g, "")
|
|
111
|
+
.replace(/\t/g, " ")
|
|
112
|
+
.replace(/[\\{}]/g, (c) => (c === "\\" ? "\\vpBS{}" : c === "{" ? "\\vpLB{}" : "\\vpRB{}"));
|
|
113
|
+
}
|
|
114
|
+
/** A full Lean name, breakable after each dot. */
|
|
115
|
+
export function escapeName(s) {
|
|
116
|
+
return String(s)
|
|
117
|
+
.split(".")
|
|
118
|
+
.map(escapeLatex)
|
|
119
|
+
.join(".\\vpBreak{}");
|
|
120
|
+
}
|
|
121
|
+
/** A file's path in the project, breakable after each slash. */
|
|
122
|
+
export function escapePath(s) {
|
|
123
|
+
return String(s)
|
|
124
|
+
.split("/")
|
|
125
|
+
.map(escapeLatex)
|
|
126
|
+
.join("/\\vpBreak{}");
|
|
127
|
+
}
|
|
128
|
+
/** An anchor as LaTeX labels and PDF destinations take it: letters, digits and `-` only, every
|
|
129
|
+
* other character spelt as its code, under a prefix of VisualProof's own: `vp:…`. A section's id
|
|
130
|
+
* starts `section:`, which no declaration's name does, so the two never meet. */
|
|
131
|
+
export function anchor(id) {
|
|
132
|
+
return "vp:" + [...String(id)].map((c) => (/[A-Za-z0-9-]/.test(c) ? c : `.${c.codePointAt(0).toString(16)}.`)).join("");
|
|
133
|
+
}
|
|
134
|
+
/** Text for a PDF bookmark: what a PDF outline shows, never markup. The characters LaTeX gives a
|
|
135
|
+
* meaning to are left out; the rest is kept, Unicode included. */
|
|
136
|
+
export function bookmarkText(s) {
|
|
137
|
+
return String(s).replace(CONTROL, "").replace(/[\\{}$&#^_%~`\r\n\t]/g, " ").replace(/\s+/g, " ").trim() || "?";
|
|
138
|
+
}
|
|
139
|
+
/** A count as the macros take it: a whole number, never negative. */
|
|
140
|
+
const int = (x) => (typeof x === "number" && Number.isFinite(x) && x > 0 ? Math.floor(x) : 0);
|
|
141
|
+
/** A colour of the palette as xcolor's HTML model takes it; grey for anything else. */
|
|
142
|
+
const hex = (c) => (/^#[0-9a-fA-F]{6}$/.test(c) ? c.slice(1).toUpperCase() : "AEAEB2");
|
|
143
|
+
/** The code's palette, as colour definitions after the style file's own. */
|
|
144
|
+
function palette() {
|
|
145
|
+
return Object.entries(TOKEN_COLORS).map(([k, c]) => `\\definecolor{vpTk${k}}{HTML}{${hex(c)}}`);
|
|
146
|
+
}
|
|
147
|
+
/** The macro each kind of token is printed with; plain text has none. */
|
|
148
|
+
const TOKEN_MACRO = {
|
|
149
|
+
keyword: "vpTkKeyword",
|
|
150
|
+
type: "vpTkType",
|
|
151
|
+
comment: "vpTkComment",
|
|
152
|
+
doc: "vpTkDoc",
|
|
153
|
+
string: "vpTkString",
|
|
154
|
+
number: "vpTkNumber",
|
|
155
|
+
attr: "vpTkAttr",
|
|
156
|
+
op: "vpTkOp",
|
|
157
|
+
sorry: "vpTkSorry",
|
|
158
|
+
elided: "vpTkElided",
|
|
159
|
+
};
|
|
160
|
+
/** One token of a line of code. A token's spaces stay outside its macro, so that a long comment
|
|
161
|
+
* or string can still be broken at them (fvextra breaks lines only between macros). */
|
|
162
|
+
function token([kind, text]) {
|
|
163
|
+
const macro = Object.hasOwn(TOKEN_MACRO, kind) ? TOKEN_MACRO[kind] : null;
|
|
164
|
+
if (!macro)
|
|
165
|
+
return escapeVerbatim(text);
|
|
166
|
+
return String(text)
|
|
167
|
+
.split(/( +)/)
|
|
168
|
+
.map((part) => (!part ? "" : /^ +$/.test(part) ? part : `\\${macro}{${escapeVerbatim(part)}}`))
|
|
169
|
+
.join("");
|
|
170
|
+
}
|
|
171
|
+
/** A block of code, in `vpCode`, without line numbers. How the proofs in it were found is not said:
|
|
172
|
+
* the export's notes say it (shared/doc/build.ts, `docNotes`). */
|
|
173
|
+
function code(c) {
|
|
174
|
+
if (!c.lines.length)
|
|
175
|
+
return "";
|
|
176
|
+
// a Verbatim environment reads up to its \end at the start of a line: nothing escaped can be one
|
|
177
|
+
return `\\begin{vpCode}\n${c.lines.map((l) => l.map(token).join("")).join("\n")}\n\\end{vpCode}`;
|
|
178
|
+
}
|
|
179
|
+
function inline(xs) {
|
|
180
|
+
return xs
|
|
181
|
+
.map((x) => {
|
|
182
|
+
// a declaration a note names, linked to where the document prints it, with its page
|
|
183
|
+
if (x.t === "decl")
|
|
184
|
+
return `\\vpMention{${anchor(x.id)}}{${escapeName(x.s)}}`;
|
|
185
|
+
if (x.t === "br")
|
|
186
|
+
return "\\newline ";
|
|
187
|
+
const t = escapeLatex(x.s);
|
|
188
|
+
return x.t === "code" ? `\\vpInlineCode{${t}}` : x.t === "em" ? `\\emph{${t}}` : x.t === "strong" ? `\\textbf{${t}}` : t;
|
|
189
|
+
})
|
|
190
|
+
.join("");
|
|
191
|
+
}
|
|
192
|
+
/** Prose (a docstring), block by block. */
|
|
193
|
+
function blocks(bs) {
|
|
194
|
+
return bs
|
|
195
|
+
.map((b) => {
|
|
196
|
+
if (b.t === "p")
|
|
197
|
+
return inline(b.inl);
|
|
198
|
+
if (b.t === "code")
|
|
199
|
+
return code(b.code);
|
|
200
|
+
return `\\begin{itemize}[nosep, leftmargin=1.2em]\n${b.items.map((it) => `\\item ${inline(it)}`).join("\n")}\n\\end{itemize}`;
|
|
201
|
+
})
|
|
202
|
+
.join("\n\n");
|
|
203
|
+
}
|
|
204
|
+
/** What is said under the code of a declaration of a past build whose text of then is not kept
|
|
205
|
+
* (ExportSpec decision 16): its statement as the build recorded it is printed instead. */
|
|
206
|
+
export const NOT_KEPT_NOTE = "Its source text at this build is not kept: printed is its statement, as the build recorded it.";
|
|
207
|
+
/** A declaration: its head, its full name and place, what it is, its code. `level`: its PDF
|
|
208
|
+
* bookmark's, one below its section's. */
|
|
209
|
+
function decl(d, level, refs = new Map()) {
|
|
210
|
+
const id = anchor(d.id);
|
|
211
|
+
const out = [];
|
|
212
|
+
out.push(`\\vpBookmark{${id}}{${int(level)}}{${escapeLatex(bookmarkText(d.short))}}`);
|
|
213
|
+
out.push(`\\begin{vpDecl}{${id}}{${escapeLatex(d.short)}}{${escapeLatex(d.kind)}}`);
|
|
214
|
+
out.push(`\\vpFullName{${escapeName(d.name)}}`);
|
|
215
|
+
out.push(`\\vpPlace{${escapeName(d.module)}}{${d.file ? escapePath(d.file) : ""}}{${d.line ? int(d.line) : ""}}`);
|
|
216
|
+
if (d.doc)
|
|
217
|
+
out.push(`\\begin{vpDocstring}`, blocks(d.doc), `\\end{vpDocstring}`);
|
|
218
|
+
out.push(code(d.code));
|
|
219
|
+
if (d.code.notKept)
|
|
220
|
+
out.push(`\\vpDeclNote{${escapeLatex(NOT_KEPT_NOTE)}}`);
|
|
221
|
+
if (d.papers)
|
|
222
|
+
out.push(papers(d.papers, refs));
|
|
223
|
+
// the notes attached to it, as its explanation
|
|
224
|
+
for (const e of d.notes ?? [])
|
|
225
|
+
out.push(note(e, refs));
|
|
226
|
+
out.push(`\\end{vpDecl}`);
|
|
227
|
+
return out.join("\n");
|
|
228
|
+
}
|
|
229
|
+
/** A note (the owner's decisions of 2026-10-12): its text as prose, then the images and clips it
|
|
230
|
+
* carries as figures, then the whole PDFs it carries as references. Who wrote it is not said: the
|
|
231
|
+
* author line names them already. */
|
|
232
|
+
function note(e, refs) {
|
|
233
|
+
const out = [`\\begin{vpNote}{${anchor(e.id)}}`];
|
|
234
|
+
if (e.text?.length)
|
|
235
|
+
out.push(blocks(e.text));
|
|
236
|
+
for (const f of e.figures ?? [])
|
|
237
|
+
out.push(figure(f, refs));
|
|
238
|
+
if (e.papers)
|
|
239
|
+
out.push(papers(e.papers, refs));
|
|
240
|
+
out.push(`\\end{vpNote}`);
|
|
241
|
+
return out.join("\n");
|
|
242
|
+
}
|
|
243
|
+
/** The resolution figures are drawn at for print, in dots per inch. */
|
|
244
|
+
export const PRINT_DPI = 300;
|
|
245
|
+
/** The widest a clip is asked to be drawn, in pixels: the renderer's widest. */
|
|
246
|
+
const CLIP_PX = 2000;
|
|
247
|
+
/** Figure `f`'s asset, the file it shows being of type `type`. */
|
|
248
|
+
export function figureAsset(f, type) {
|
|
249
|
+
const sha = f.file.replace(/[^0-9a-f]/g, "").slice(0, 64);
|
|
250
|
+
if (!f.region || !f.page)
|
|
251
|
+
return { name: `fig-${sha}.${type === "jpeg" ? "jpg" : "png"}`, file: f.file, type };
|
|
252
|
+
const px = f.size ? Math.min(CLIP_PX, Math.max(1, Math.ceil((f.size[0] / 72) * PRINT_DPI))) : CLIP_PX;
|
|
253
|
+
const r = f.region.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4));
|
|
254
|
+
return { name: `clip-${sha}-p${int(f.page)}-${r.join("-")}-w${px}.png`, file: f.file, type, page: int(f.page), region: f.region, px };
|
|
255
|
+
}
|
|
256
|
+
/** Every figure a document prints, each once, as what it is drawn from. */
|
|
257
|
+
export function figureAssets(doc) {
|
|
258
|
+
const types = new Map((doc.references ?? []).map((r) => [r.n, r.type]));
|
|
259
|
+
const out = new Map();
|
|
260
|
+
const walk = (xs) => {
|
|
261
|
+
for (const x of xs) {
|
|
262
|
+
const notes = x.t === "note" ? [x.note] : x.t === "decl" ? x.decl.notes ?? [] : [];
|
|
263
|
+
for (const e of notes)
|
|
264
|
+
for (const f of e.figures ?? []) {
|
|
265
|
+
const a = figureAsset(f, types.get(f.ref) ?? "png");
|
|
266
|
+
out.set(a.name, a);
|
|
267
|
+
}
|
|
268
|
+
if (x.t === "section")
|
|
269
|
+
walk(x.section.items);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
walk(doc.items);
|
|
273
|
+
return [...out.values()];
|
|
274
|
+
}
|
|
275
|
+
/** A figure: an image at its own size, a clip at the size the paper prints it (`size`), neither wider
|
|
276
|
+
* than the text; its caption the file's title and a clip's page, numbered as the references are. */
|
|
277
|
+
function figure(f, refs) {
|
|
278
|
+
const r = refs.get(f.ref);
|
|
279
|
+
const a = figureAsset(f, r?.type ?? "png");
|
|
280
|
+
const width = f.size && Number.isFinite(f.size[0]) && f.size[0] > 0 ? `${Math.round(f.size[0] * 10) / 10}pt` : "";
|
|
281
|
+
return `\\vpFigure{figures/${a.name}}{${width}}{${escapeLatex(r?.title ?? "")}}{${f.page ? int(f.page) : ""}}{${int(f.ref)}}`;
|
|
282
|
+
}
|
|
283
|
+
/** The places in references something is linked to (ExportSpec decision 17), one line each:
|
|
284
|
+
* "Reference: Theorem 3 [title, p. 12]", the title the cited file's, numbered as the list at the
|
|
285
|
+
* end. */
|
|
286
|
+
function papers(ps, refs) {
|
|
287
|
+
return ps
|
|
288
|
+
.map((p) => {
|
|
289
|
+
const r = refs.get(p.ref);
|
|
290
|
+
return `\\vpPaper{${escapeLatex(p.label)}}{${escapeLatex(r?.title ?? "")}}{${p.page ? int(p.page) : ""}}{${int(p.ref)}}`;
|
|
291
|
+
})
|
|
292
|
+
.join("\n");
|
|
293
|
+
}
|
|
294
|
+
/** The files the document cites, at its end: each one's title, where it came from — the address it
|
|
295
|
+
* was fetched from, else the name it was uploaded under — and its hash, which names it for good. */
|
|
296
|
+
function references(rs) {
|
|
297
|
+
const from = (r) => (r.url ? `\\vpUrl{${escapePath(r.url)}}` : r.name ? `uploaded as ${escapeLatex(r.name)}` : "uploaded");
|
|
298
|
+
const what = (r) => (r.type === "pdf" ? (r.pages ? `PDF, ${int(r.pages)} page${int(r.pages) === 1 ? "" : "s"}` : "PDF") : `${escapeLatex(r.type.toUpperCase())} image`);
|
|
299
|
+
return [`\\begin{vpReferences}`, ...rs.map((r) => `\\vpReference{${int(r.n)}}{${escapeLatex(r.title)}}{${from(r)}}{${what(r)}}{${escapeLatex(r.sha.slice(0, 16))}}`), `\\end{vpReferences}`].join("\n");
|
|
300
|
+
}
|
|
301
|
+
/** Each section's number, by id: `1`, `1.2`, `1.2.3` for the first three levels; a paragraph-level
|
|
302
|
+
* heading has none. */
|
|
303
|
+
function numbers(doc) {
|
|
304
|
+
const out = new Map();
|
|
305
|
+
const walk = (xs, prefix) => sectionsOf(xs).forEach((s, k) => {
|
|
306
|
+
const n = prefix ? `${prefix}.${k + 1}` : `${k + 1}`;
|
|
307
|
+
if (s.level < 4)
|
|
308
|
+
out.set(s.id, n);
|
|
309
|
+
walk(s.items, s.level < 3 ? n : "");
|
|
310
|
+
});
|
|
311
|
+
walk(doc.items, "");
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
/** The sections among `xs`, in order. */
|
|
315
|
+
const sectionsOf = (xs) => xs.flatMap((x) => (x.t === "section" ? [x.section] : []));
|
|
316
|
+
/** A section, and what it holds: its heading (the cluster's key beneath it), its declarations, its
|
|
317
|
+
* sections. `depth` counts from 1 at the top, for the PDF's bookmarks, which nest as the sections
|
|
318
|
+
* do below the paragraph level too. */
|
|
319
|
+
function section(s, depth, n, refs = new Map()) {
|
|
320
|
+
const id = anchor(s.id);
|
|
321
|
+
const level = Math.min(4, Math.max(1, int(s.level)));
|
|
322
|
+
const out = [`\\vpSection{${id}}{${level}}{${n.get(s.id) ?? ""}}{${escapeLatex(s.title)}}{${escapeName(s.key.replace("⟨external⟩", "External"))}}{${int(depth - 1)}}{${escapeLatex(bookmarkText(s.title))}}`];
|
|
323
|
+
// the cluster's papers, under its heading
|
|
324
|
+
if (s.papers)
|
|
325
|
+
out.push(`\\begin{vpSectionPapers}`, papers(s.papers, refs), `\\end{vpSectionPapers}`);
|
|
326
|
+
// what it holds, in the board's order: its own declarations and notes after one of its sections
|
|
327
|
+
// are headed by its title again, so that they do not read as that section's
|
|
328
|
+
let after = false;
|
|
329
|
+
for (const x of s.items) {
|
|
330
|
+
if (x.t === "section") {
|
|
331
|
+
out.push(section(x.section, depth + 1, n, refs));
|
|
332
|
+
after = true;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (after)
|
|
336
|
+
out.push(`\\vpSectionResume{${Math.min(4, level + 1)}}{${escapeLatex(s.title)}}`);
|
|
337
|
+
after = false;
|
|
338
|
+
out.push(x.t === "decl" ? decl(x.decl, depth, refs) : note(x.note, refs));
|
|
339
|
+
}
|
|
340
|
+
return out.join("\n");
|
|
341
|
+
}
|
|
342
|
+
/** The most declarations a document has for its contents to list them beside the sections. */
|
|
343
|
+
export const CONTENTS_DECLS = 60;
|
|
344
|
+
/** The table of contents (ExportSpec decision 8), where the settings print it: the sections, each
|
|
345
|
+
* with its number and page — every level where the document is short (at most CONTENTS_DECLS
|
|
346
|
+
* declarations), and then its declarations too, under their sections; else the first three
|
|
347
|
+
* levels. The style sets it in two columns, the body following on the same page. */
|
|
348
|
+
function contents(doc, n) {
|
|
349
|
+
const short = doc.count.declarations <= CONTENTS_DECLS;
|
|
350
|
+
const out = ["\\begin{vpContents}"];
|
|
351
|
+
const entry = (d, level) => out.push(`\\vpContentsDecl{${anchor(d.id)}}{${level}}{${escapeLatex(d.short)}}`);
|
|
352
|
+
const decls = (xs) => xs.flatMap((x) => (x.t === "decl" ? [x.decl] : []));
|
|
353
|
+
if (short)
|
|
354
|
+
for (const d of decls(doc.items))
|
|
355
|
+
entry(d, 1);
|
|
356
|
+
const walk = (xs) => {
|
|
357
|
+
for (const s of sectionsOf(xs)) {
|
|
358
|
+
if (!short && s.level > 3)
|
|
359
|
+
continue;
|
|
360
|
+
out.push(`\\vpContentsSection{${anchor(s.id)}}{${Math.min(4, Math.max(1, int(s.level)))}}{${n.get(s.id) ?? ""}}{${escapeLatex(s.title)}}`);
|
|
361
|
+
if (short)
|
|
362
|
+
for (const d of decls(s.items))
|
|
363
|
+
entry(d, Math.min(4, s.level) + 1);
|
|
364
|
+
walk(s.items);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
walk(doc.items);
|
|
368
|
+
// the files the references cite, at the end, under "References" (ExportSpec decision 17)
|
|
369
|
+
if (doc.references?.length)
|
|
370
|
+
out.push(`\\vpContentsSection{vp-references}{1}{}{References}`);
|
|
371
|
+
out.push("\\end{vpContents}");
|
|
372
|
+
return out.join("\n");
|
|
373
|
+
}
|
|
374
|
+
/** What the view holds from outside the project, in words: `300 declarations from outside the
|
|
375
|
+
* project (Mathlib 203, Init 97)`, the six largest libraries named. */
|
|
376
|
+
function outsideLine(o) {
|
|
377
|
+
const n = int(o.count);
|
|
378
|
+
const named = o.by.slice(0, 6).map((b) => `${escapeLatex(b.name)} ${int(b.count)}`);
|
|
379
|
+
const rest = o.by.slice(6).reduce((a, b) => a + int(b.count), 0);
|
|
380
|
+
if (rest)
|
|
381
|
+
named.push(`others ${rest}`);
|
|
382
|
+
return `${n} declaration${n === 1 ? "" : "s"} from outside the project${named.length ? ` (${named.join(", ")})` : ""}, not printed`;
|
|
383
|
+
}
|
|
384
|
+
/** `n` declarations, in words. */
|
|
385
|
+
const decls = (n) => `${int(n)} declaration${int(n) === 1 ? "" : "s"}`;
|
|
386
|
+
/** What the head card says of a past build's texts (ExportSpec decision 16): that they are the ones
|
|
387
|
+
* that build compiled, how many were matched to it by its places alone, and how many are not kept,
|
|
388
|
+
* their statements printed instead. */
|
|
389
|
+
function textLine(p) {
|
|
390
|
+
const out = ["as this build compiled it"];
|
|
391
|
+
if (int(p.places))
|
|
392
|
+
out.push(`for ${decls(p.places)} matched to the build by the places it recorded, its hash not being kept`);
|
|
393
|
+
if (int(p.notKept))
|
|
394
|
+
out.push(`not kept for ${decls(p.notKept)}, whose statement${int(p.notKept) === 1 ? " is" : "s are"} printed instead`);
|
|
395
|
+
return out.join("; ");
|
|
396
|
+
}
|
|
397
|
+
/** The head card (ExportSpec decision 8): the project, the build and its commit where known, the
|
|
398
|
+
* toolchain, the trusted boundary, and what the view holds from outside the project. Of a past
|
|
399
|
+
* build (decision 16): when it finished, its commit by its short hash (and whether its files were
|
|
400
|
+
* edited from it), and how its texts were had. */
|
|
401
|
+
function head(doc) {
|
|
402
|
+
const h = doc.head;
|
|
403
|
+
const row = (label, value) => `\\vpCardRow{${label}}{${value}}`;
|
|
404
|
+
const mono = (s) => `\\vpMono{${escapeLatex(s)}}`;
|
|
405
|
+
const p = h.past;
|
|
406
|
+
const c = h.commit;
|
|
407
|
+
const commit = !c
|
|
408
|
+
? []
|
|
409
|
+
: p
|
|
410
|
+
? [row("Commit", `${c.guessed ? "probably " : ""}${mono(c.hash.slice(0, 7))}${c.branch ? ` on ${mono(c.branch)}` : ""}${c.dirty ? ", edited" : ""}`)]
|
|
411
|
+
: [row("Commit", `${mono(c.hash)}${c.branch ? ` on ${mono(c.branch)}` : ""}`)];
|
|
412
|
+
const rows = [
|
|
413
|
+
row("Project", `${escapeLatex(h.project)}${h.roots.length ? ` (${h.roots.map(mono).join(", ")})` : ""}`),
|
|
414
|
+
row("Build", p ? `${p.approx ? "about " : ""}${escapeLatex(p.when)}` : h.fingerprint ? mono(h.fingerprint) : "not known"),
|
|
415
|
+
...commit,
|
|
416
|
+
...(p ? [row("Source text", escapeLatex(textLine(p)))] : []),
|
|
417
|
+
row("Toolchain", mono(h.toolchain)),
|
|
418
|
+
row("Trusted without review", h.trusted.prefixes.length ? h.trusted.prefixes.map((p) => (p === "@external" ? "everything outside the project" : mono(p))).join(", ") : "nothing by prefix"),
|
|
419
|
+
row("Standard axioms", h.trusted.standardAxioms.map(mono).join(", ")),
|
|
420
|
+
...(h.outside.count ? [row("Also on this view", outsideLine(h.outside))] : []),
|
|
421
|
+
];
|
|
422
|
+
return `\\begin{vpCard}\n${rows.join("\n")}\n\\end{vpCard}`;
|
|
423
|
+
}
|
|
424
|
+
/** The whole .tex. */
|
|
425
|
+
export function renderLatex(doc) {
|
|
426
|
+
const s = doc.settings;
|
|
427
|
+
const title = doc.head.title || doc.view.title;
|
|
428
|
+
const n = numbers(doc);
|
|
429
|
+
const out = [
|
|
430
|
+
"% Made by VisualProof (ExportSpec.md). Typeset with XeLaTeX, twice:",
|
|
431
|
+
"% xelatex doc.tex && xelatex doc.tex",
|
|
432
|
+
"% The look is visualproof.sty's: replace it, or extend it with a package of your own loaded",
|
|
433
|
+
"% after it. Everything below is the document's content, escaped.",
|
|
434
|
+
`\\documentclass[${s.fontSize === 10 || s.fontSize === 12 ? s.fontSize : 11}pt]{article}`,
|
|
435
|
+
"\\usepackage{visualproof}",
|
|
436
|
+
`\\vpSetup{paper=${s.paper === "letter" ? "letter" : "a4"}, margin=${int(s.margin) || 22}mm, codesize=${Number.isFinite(s.codeSize) ? Math.min(12, Math.max(6, s.codeSize)) : 8.5}pt}`,
|
|
437
|
+
...palette(),
|
|
438
|
+
`\\vpSetTitle{${escapeLatex(title)}}`,
|
|
439
|
+
`\\hypersetup{pdftitle={${escapeLatex(bookmarkText(title))}}${doc.head.author ? `, pdfauthor={${escapeLatex(bookmarkText(doc.head.author))}}` : ""}, pdfcreator={VisualProof}}`,
|
|
440
|
+
"\\begin{document}",
|
|
441
|
+
`\\vpTitle{${escapeLatex(title)}}{${escapeLatex(doc.head.author)}}`,
|
|
442
|
+
head(doc),
|
|
443
|
+
...(s.contents ? [contents(doc, n)] : []),
|
|
444
|
+
];
|
|
445
|
+
// the start — what sits in no cluster, the notes outside every cluster — under no heading, then
|
|
446
|
+
// the sections, all in the board's order
|
|
447
|
+
const refs = new Map((doc.references ?? []).map((r) => [r.n, r]));
|
|
448
|
+
for (const x of doc.items)
|
|
449
|
+
out.push(x.t === "decl" ? decl(x.decl, 0, refs) : x.t === "note" ? note(x.note, refs) : section(x.section, 1, n, refs));
|
|
450
|
+
// the files the references cite, at the end (ExportSpec decision 17)
|
|
451
|
+
if (doc.references?.length)
|
|
452
|
+
out.push(references(doc.references));
|
|
453
|
+
out.push("\\end{document}", "");
|
|
454
|
+
return out.join("\n");
|
|
455
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Prose for the exported document (shared/doc/model.ts): docstrings are Markdown, of which the
|
|
2
|
+
// document keeps what the page's panel renders (web/src/highlight.ts, renderDoc):
|
|
3
|
+
// paragraphs, code spans and fenced code blocks (highlighted as Lean), strong and emphasis — and
|
|
4
|
+
// lists, which docstrings use. Everything else stays text, as written: a `$…$` is not mathematics
|
|
5
|
+
// here, since no renderer may read what a writer typed as markup of its own. A note's text
|
|
6
|
+
// (`noteBlocks`) keeps what its card shows: its line breaks, and the declarations it names.
|
|
7
|
+
import { tokenizeLean, tokenLines } from "../lexer.js";
|
|
8
|
+
/** Lean source as a block of highlighted lines (shared/lexer.ts). */
|
|
9
|
+
export function codeOf(text, from = "source") {
|
|
10
|
+
const lines = tokenLines(tokenizeLean(text)).map((l) => l.map((t) => [t.kind, t.text]));
|
|
11
|
+
return { lines, proofElided: false, from };
|
|
12
|
+
}
|
|
13
|
+
/** A paragraph's text as inline pieces: `code`, **strong**, *emphasis* or _emphasis_. Markers that
|
|
14
|
+
* do not close are text. */
|
|
15
|
+
export function inlines(text) {
|
|
16
|
+
const out = [];
|
|
17
|
+
const push = (t, s) => {
|
|
18
|
+
if (!s)
|
|
19
|
+
return;
|
|
20
|
+
const last = out[out.length - 1];
|
|
21
|
+
if (t === "text" && last?.t === "text")
|
|
22
|
+
last.s += s;
|
|
23
|
+
else
|
|
24
|
+
out.push({ t, s });
|
|
25
|
+
};
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < text.length) {
|
|
28
|
+
const c = text[i];
|
|
29
|
+
if (c === "`") {
|
|
30
|
+
// a code span: as many backquotes close it as opened it
|
|
31
|
+
let n = 1;
|
|
32
|
+
while (text[i + n] === "`")
|
|
33
|
+
n++;
|
|
34
|
+
const fence = "`".repeat(n);
|
|
35
|
+
const end = text.indexOf(fence, i + n);
|
|
36
|
+
if (end > i) {
|
|
37
|
+
push("code", text.slice(i + n, end).replace(/^ (.*) $/s, "$1"));
|
|
38
|
+
i = end + n;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
push("text", fence);
|
|
42
|
+
i += n;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (c === "*" && text[i + 1] === "*") {
|
|
46
|
+
const end = text.indexOf("**", i + 2);
|
|
47
|
+
if (end > i + 2) {
|
|
48
|
+
push("strong", text.slice(i + 2, end));
|
|
49
|
+
i = end + 2;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// emphasis opens before a letter and closes after one: `a * b` and `snake_case` stay text
|
|
54
|
+
if ((c === "*" || c === "_") && /\S/.test(text[i + 1] ?? "") && (c === "*" || !/[\p{L}\p{N}]/u.test(text[i - 1] ?? ""))) {
|
|
55
|
+
const end = text.indexOf(c, i + 1);
|
|
56
|
+
if (end > i + 1 && /\S/.test(text[end - 1]) && (c === "*" || !/[\p{L}\p{N}]/u.test(text[end + 1] ?? ""))) {
|
|
57
|
+
push("em", text.slice(i + 1, end));
|
|
58
|
+
i = end + 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
push("text", c);
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** Markdown as blocks: fenced code (as Lean), lists of `-`, `*` or `+` items, paragraphs. Lines of a
|
|
68
|
+
* paragraph are joined with a space. */
|
|
69
|
+
export function parseMarkdown(md) {
|
|
70
|
+
const lines = md.replace(/\r\n?/g, "\n").split("\n");
|
|
71
|
+
const out = [];
|
|
72
|
+
let para = [];
|
|
73
|
+
let items = null;
|
|
74
|
+
const flush = () => {
|
|
75
|
+
if (para.length)
|
|
76
|
+
out.push({ t: "p", inl: inlines(para.join(" ").trim()) });
|
|
77
|
+
para = [];
|
|
78
|
+
if (items?.length)
|
|
79
|
+
out.push({ t: "list", items: items.map((x) => inlines(x.trim())) });
|
|
80
|
+
items = null;
|
|
81
|
+
};
|
|
82
|
+
for (let k = 0; k < lines.length; k++) {
|
|
83
|
+
const line = lines[k];
|
|
84
|
+
const fence = /^\s*(```+|~~~+)/.exec(line);
|
|
85
|
+
if (fence) {
|
|
86
|
+
flush();
|
|
87
|
+
const body = [];
|
|
88
|
+
for (k++; k < lines.length && !lines[k].trim().startsWith(fence[1]); k++)
|
|
89
|
+
body.push(lines[k]);
|
|
90
|
+
out.push({ t: "code", code: codeOf(body.join("\n")) });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!line.trim()) {
|
|
94
|
+
flush();
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const item = /^\s*[-*+]\s+(.*)$/.exec(line);
|
|
98
|
+
if (item) {
|
|
99
|
+
if (para.length)
|
|
100
|
+
flush();
|
|
101
|
+
(items ??= []).push(item[1]);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// a line under a list item, indented, goes on with it
|
|
105
|
+
if (items && /^\s+\S/.test(line)) {
|
|
106
|
+
items[items.length - 1] += " " + line.trim();
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (items)
|
|
110
|
+
flush();
|
|
111
|
+
para.push(line.trim());
|
|
112
|
+
}
|
|
113
|
+
flush();
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** A note's text as blocks (the owner's decisions of 2026-10-12): its little Markdown carried over as
|
|
117
|
+
* the page shows it — fenced code, `code`, **bold**, *italics* — paragraphs parted by a blank line
|
|
118
|
+
* and the line breaks within one kept, as the note's card keeps them (it lists nothing: a line that
|
|
119
|
+
* starts with `-` is that line). A full declaration's name in backticks that `mention` knows (the
|
|
120
|
+
* id of the entry the document prints it as) is a `decl`, which the renderer links to where it is
|
|
121
|
+
* printed; any other is code. */
|
|
122
|
+
export function noteBlocks(text, mention) {
|
|
123
|
+
const out = [];
|
|
124
|
+
const para = (lines) => {
|
|
125
|
+
const inl = [];
|
|
126
|
+
lines.forEach((line, k) => {
|
|
127
|
+
if (k)
|
|
128
|
+
inl.push({ t: "br", s: "" });
|
|
129
|
+
for (const x of inlines(line)) {
|
|
130
|
+
const id = x.t === "code" ? mention(x.s.trim()) : null;
|
|
131
|
+
inl.push(id ? { t: "decl", s: x.s.trim(), id } : x);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
if (inl.length)
|
|
135
|
+
out.push({ t: "p", inl });
|
|
136
|
+
};
|
|
137
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
138
|
+
let run = [];
|
|
139
|
+
for (let k = 0; k < lines.length; k++) {
|
|
140
|
+
const fence = /^\s*(```+|~~~+)/.exec(lines[k]);
|
|
141
|
+
if (fence) {
|
|
142
|
+
para(run);
|
|
143
|
+
run = [];
|
|
144
|
+
const body = [];
|
|
145
|
+
for (k++; k < lines.length && !lines[k].trim().startsWith(fence[1]); k++)
|
|
146
|
+
body.push(lines[k]);
|
|
147
|
+
out.push({ t: "code", code: codeOf(body.join("\n")) });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!lines[k].trim()) {
|
|
151
|
+
para(run);
|
|
152
|
+
run = [];
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
run.push(lines[k].trim());
|
|
156
|
+
}
|
|
157
|
+
para(run);
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The exported document's model (ExportSpec decision 6): what the document holds, independent of
|
|
2
|
+
// how it is rendered. The builder (shared/doc/build.ts) makes it from the graph, a view and the
|
|
3
|
+
// sources; a renderer turns it into something to read — the LaTeX renderer (shared/doc/latex.ts)
|
|
4
|
+
// today, an HTML one or another engine later, without touching the builder. It is plain JSON,
|
|
5
|
+
// versioned: the agents' API serves it as it is (GET /api/agent/export), and a renderer reads
|
|
6
|
+
// nothing else.
|
|
7
|
+
//
|
|
8
|
+
// It holds nothing of the review (ExportSpec decision 8): no status, no opinion, no progress. A
|
|
9
|
+
// document is about what the formalization says, and a reviewer's word on it is the page's. What
|
|
10
|
+
// explains it does belong (decision 17, AttachmentSpec decision 10): the places in papers a
|
|
11
|
+
// declaration or a cluster is linked to, and the files they cite; and, since the owner's decisions
|
|
12
|
+
// of 2026-10-12, the notes the view shows, as the document's prose, with the images and clips they
|
|
13
|
+
// carry as figures.
|
|
14
|
+
//
|
|
15
|
+
// Its order is the board's (the owner's decisions of 2026-10-12): what a section holds — its
|
|
16
|
+
// declarations, its notes and its sections — comes in one list, in the order the board draws them
|
|
17
|
+
// from the bottom up, whatever depends on what.
|
|
18
|
+
//
|
|
19
|
+
// Nothing in it is trusted: names, docstrings, comments and code are what people, agents and
|
|
20
|
+
// source files said, and a renderer escapes every one of them.
|
|
21
|
+
/** Bumped when the model changes shape: a renderer reads only the versions it knows. 2: proofs
|
|
22
|
+
* inside definitions left out. 3: nothing of the review, no context and no line numbers, the head
|
|
23
|
+
* card without times. 4: no figure and no legend; a definition's value always printed, the proofs
|
|
24
|
+
* in it found by reading its text where the usage pass's places cannot be used (`textCut`). 5: a
|
|
25
|
+
* declaration's `doc` its docstring alone; nothing of what it rests on. 6 (2026-10-09): the document
|
|
26
|
+
* of a view (`view`), in sections from its cluster structure, the outside world counted on the head
|
|
27
|
+
* card (`head.outside`); no targets' parts, preset, content, appendix or key marks. 7 (2026-10-09):
|
|
28
|
+
* the title the view's and the author line its people's (`head.title`, `head.author`), no longer
|
|
29
|
+
* settings. 8 (2026-10-10): the document of a view at a past build (`head.past`), its texts that
|
|
30
|
+
* build's, a declaration whose text of then is not kept printed as its statement (`code.notKept`),
|
|
31
|
+
* a text matched to its build by its places marked (`code.places`). 9 (2026-10-11): the papers a
|
|
32
|
+
* declaration and a section are linked to (`papers`), and the files they cite (`references`). 10
|
|
33
|
+
* (2026-10-12): the board's order, a section's declarations, notes and sections in one list
|
|
34
|
+
* (`items`), the document's first ones too; the notes the view shows (`NoteEntry`), a declaration's
|
|
35
|
+
* own under it (`DeclEntry.notes`), their images and clips as figures; a declaration a note names
|
|
36
|
+
* linked where the document prints it (an inline `decl`). */
|
|
37
|
+
export const DOC_VERSION = 10;
|