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.
Files changed (50) hide show
  1. package/dist/assets/webfonts/LICENSE-Jost.txt +93 -0
  2. package/dist/assets/webfonts/LICENSE-SourceSans3.txt +93 -0
  3. package/dist/assets/webfonts/LICENSE-SourceSerif4.txt +93 -0
  4. package/dist/assets/webfonts/jost-latin-ext-wght-normal.woff2 +0 -0
  5. package/dist/assets/webfonts/jost-latin-wght-normal.woff2 +0 -0
  6. package/dist/assets/webfonts/source-sans-3-greek-wght-italic.woff2 +0 -0
  7. package/dist/assets/webfonts/source-sans-3-greek-wght-normal.woff2 +0 -0
  8. package/dist/assets/webfonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
  9. package/dist/assets/webfonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
  10. package/dist/assets/webfonts/source-sans-3-latin-wght-italic.woff2 +0 -0
  11. package/dist/assets/webfonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  12. package/dist/assets/webfonts/source-serif-4-greek-wght-italic.woff2 +0 -0
  13. package/dist/assets/webfonts/source-serif-4-greek-wght-normal.woff2 +0 -0
  14. package/dist/assets/webfonts/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
  15. package/dist/assets/webfonts/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
  16. package/dist/assets/webfonts/source-serif-4-latin-wght-italic.woff2 +0 -0
  17. package/dist/assets/webfonts/source-serif-4-latin-wght-normal.woff2 +0 -0
  18. package/dist/server/agent.js +41 -6
  19. package/dist/server/history.js +39 -0
  20. package/dist/server/historyapi.js +2 -2
  21. package/dist/server/hover.js +3 -3
  22. package/dist/server/layoutthread.js +1 -1
  23. package/dist/server/main.js +15 -3
  24. package/dist/server/mcptools.js +2 -3
  25. package/dist/server/pastdoc.js +1 -1
  26. package/dist/server/pipeline.js +5 -2
  27. package/dist/server/readonlygate.js +1 -1
  28. package/dist/server/snapshot.js +3 -2
  29. package/dist/server/usage.js +2 -2
  30. package/dist/server/view.js +7 -0
  31. package/dist/server/views.js +1 -1
  32. package/dist/shared/collab.js +94 -1
  33. package/dist/shared/doc/order.js +17 -0
  34. package/dist/shared/doc/outline.js +45 -0
  35. package/dist/shared/keys.js +1 -1
  36. package/dist/shared/moves.js +2 -1
  37. package/dist/shared/pastgraph.js +9 -1
  38. package/dist/shared/query.js +21 -13
  39. package/dist/shared/sharesnap.js +2 -1
  40. package/dist/shared/titles.js +2 -0
  41. package/dist/shared/viewgraph.js +18 -0
  42. package/dist/web/bundle/app.js +7118 -3180
  43. package/dist/web/bundle/edges.js +3 -5
  44. package/dist/web/index.html +24 -6
  45. package/dist/web/src/scene.js +17 -0
  46. package/dist/web/style.css +248 -12
  47. package/docs/agent-api/following.md +2 -2
  48. package/docs/agent-api/views.md +3 -2
  49. package/package.json +1 -1
  50. package/test/toolchains.json +2 -2
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Jost Project Authors (https://github.com/indestructible-type/Jost) Jost-Italic[wght].ttf: Copyright 2020 The Jost Project Authors (https://github.com/indestructible-type/Jost)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Google Inc.
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Google Inc.
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -47,6 +47,8 @@ import { authorLine, authorsOf, reviewTraces, viewTraces } from "../shared/doc/a
47
47
  import { itemsShownOn } from "../shared/noteplace.js";
48
48
  import { buildDoc, DocInputError, docNotes } from "../shared/doc/build.js";
49
49
  import { boardSpots } from "./boardspots.js";
50
+ import { outlineOf } from "../shared/doc/outline.js";
51
+ import { planDoc } from "../shared/doc/sections.js";
50
52
  import { docFiles, figureFiles, figureNotes } from "./figures.js";
51
53
  import { renderLatex } from "../shared/doc/latex.js";
52
54
  import { readSettings, SETTINGS_HELP } from "../shared/doc/settings.js";
@@ -717,6 +719,39 @@ export class AgentApi {
717
719
  const tv = this.titleVersion(rec, at, version);
718
720
  return ok({ view: rec.id, title: docTitle({ ...base, ...(tv !== undefined ? { version: tv } : {}) }, titleContext(this.world)), author: authorLine(authors), authors });
719
721
  }
722
+ /** The outline of the document of view `view` at the build `version` names (ExportSpec decision
723
+ * 18; left out, the view's own): its sections, declarations and notes in the document's order,
724
+ * without their text, from the board as its page draws it (as `document` reads it). What the
725
+ * page's linear form draws (GET /api/export/outline); a view too large to print has one too. */
726
+ exportOutline(view, version) {
727
+ const rec = view ? this.world.views?.book.get(view) : undefined;
728
+ if (!rec)
729
+ return fail(404, `There is no view ${view.slice(0, 200)}.`, "GET /api/agent/views lists them.");
730
+ const at = this.exportAt(rec, version);
731
+ if (at !== null && typeof at === "object")
732
+ return at;
733
+ const v = this.graph();
734
+ if (!(v instanceof View))
735
+ return v;
736
+ const notes = this.world.views?.book.notes ?? [];
737
+ const memory = this.world.views?.book.memoryOf(rec.id, rec.clustering) ?? null;
738
+ const whole = v.meta.layouts?.[rec.clustering];
739
+ let g = v.g;
740
+ let review = this.world.review.data;
741
+ let past;
742
+ if (at !== null) {
743
+ const b = this.pasts.get(at, v.g, v.version, (m) => v.modules.get(m)?.file ?? null);
744
+ if (!b)
745
+ return fail(404, `There is no snapshot ${at}.`, "GET /api/agent/history lists them.");
746
+ past = { snapshot: at, nowName: b.past.nowName, now: v.g, nowReview: review };
747
+ g = b.past.graph;
748
+ review = reviewThen(review, b.past.nowName);
749
+ }
750
+ const set = evaluate(rec.scope, { g, review });
751
+ const plan = planDoc(g, set, rec.clustering, rec.hidden[rec.clustering] ?? []);
752
+ const board = boardSpots({ g, review, rec, memory, whole, notes, ...(past ? { past } : {}) });
753
+ return ok({ view: rec.id, ...(at !== null ? { version: at } : {}), outline: outlineOf(plan, g, board) });
754
+ }
720
755
  /** The exported document's model (ExportSpec decision 6) of view `view` with `settings` at the
721
756
  * build `version` names (decision 16; left out, the view's own), as a request sends them: what
722
757
  * the page's Export and an agent typeset (POST /api/export, POST /api/agent/export), and what
@@ -956,7 +991,7 @@ export class AgentApi {
956
991
  people: out,
957
992
  privateCount: P.privateCount(),
958
993
  agents,
959
- note: "privateCount: how many people are here privately — not listed, not followable, nothing known of where they are. Each person's place is that of their liveliest page: the view, the tool (how a click selects), what is selected (a query), the declaration their panel tells of, whether its source window is open (and collapsed), the source windows they pinned (pinned: declaration, collapsed), and, where their page says them, Activity (open, kind, who, mine: whose targets), the lost-and-found (lostAndFound.open), the colouring (kind or status), edges and proofEdges — all of which those following them see too. idle: no input for five minutes, or the page in the background. verified: invited by a personal link, so the name is theirs. To look where someone looks, POST /api/agent/view with their view and declaration; ?wait=<s>&since=<rev> waits for the next change (at most 60 s).",
994
+ note: "privateCount: how many people are here privately — not listed, not followable, nothing known of where they are. Each person's place is that of their liveliest page: the view, the tool (how a click selects), what is selected (a query), the declaration their panel tells of, whether its source window is open (and collapsed), the source windows they pinned (pinned: declaration, collapsed), and, where their page says them, Activity (open, kind, who, mine: whose targets), the lost-and-found (lostAndFound.open), the colouring (kind or status), edges — all of which those following them see too. idle: no input for five minutes, or the page in the background. verified: invited by a personal link, so the name is theirs. To look where someone looks, POST /api/agent/view with their view and declaration; ?wait=<s>&since=<rev> waits for the next change (at most 60 s).",
960
995
  });
961
996
  }
962
997
  /** A page's or an agent's place, as the companion tells it (and a conversation's message, where
@@ -986,7 +1021,7 @@ export class AgentApi {
986
1021
  : v.lostFound?.open
987
1022
  ? ", with the lost-and-found open"
988
1023
  : "";
989
- const drawn = (v.scheme ? `, coloured by ${v.scheme}` : "") + (v.edges === false ? ", edges hidden" : "") + (v.proofEdges ? ", proof edges shown" : "");
1024
+ const drawn = (v.scheme ? `, coloured by ${v.scheme}` : "") + (v.edges === false ? ", edges hidden" : "");
990
1025
  return {
991
1026
  view: v.view ?? PROJECT,
992
1027
  viewTitle: title,
@@ -1004,7 +1039,6 @@ export class AgentApi {
1004
1039
  ...(v.lostFound ? { lostAndFound: { open: v.lostFound.open } } : {}),
1005
1040
  ...(v.scheme ? { colouring: v.scheme } : {}),
1006
1041
  ...(v.edges !== undefined ? { edges: v.edges } : {}),
1007
- ...(v.proofEdges !== undefined ? { proofEdges: v.proofEdges } : {}),
1008
1042
  inWords: words + then + shownBy + drawn + ".",
1009
1043
  };
1010
1044
  }
@@ -2519,7 +2553,8 @@ export class AgentApi {
2519
2553
  place[k] = b[k];
2520
2554
  return null;
2521
2555
  };
2522
- const bad = bool("sourceCollapsed") ?? bool("edges") ?? bool("proofEdges");
2556
+ // (proofEdges, said until 2026-10-15, is no more: left as it is, as any word not known)
2557
+ const bad = bool("sourceCollapsed") ?? bool("edges");
2523
2558
  if (bad)
2524
2559
  return bad;
2525
2560
  if (b.lostFound !== undefined) {
@@ -2744,7 +2779,7 @@ export class AgentApi {
2744
2779
  {
2745
2780
  method: "GET", path: "/api/agent/companion", brief: "who is on the page, and where each one looks", about: "Who is on the page now and where each one is — every person (the place of their liveliest page: view, tool, selection, the declaration in their panel, the source window and the pinned ones, Activity, the lost-and-found, the colouring and the edges, idle or not) and the agents present — to follow the people you help; privateCount says how many people are here privately, who are not listed and cannot be followed. With wait, the answer waits until someone comes, goes or moves.",
2746
2781
  params: { wait: "optional: seconds to wait for a change (at most 60)", since: "optional: the rev of an earlier answer; with wait, the answer comes as soon as the revision is another" },
2747
- returns: "{rev, changed?, now, sharedBy?: {name, here}, sharedByNote?, people: [{name, idle, pages, place: {view, viewTitle, targets?, scope?, mode, tool, selection, declaration, source, sourceCollapsed, pinned: [{declaration, collapsed}], history?, activity?: {open, kind, who, mine?}, lostAndFound?: {open}, colouring?, edges?, proofEdges?, inWords}}], privateCount, agents: [{name, idle, state?, status?, lastSeen?, you?, place}], note}",
2782
+ returns: "{rev, changed?, now, sharedBy?: {name, here}, sharedByNote?, people: [{name, idle, pages, place: {view, viewTitle, targets?, scope?, mode, tool, selection, declaration, source, sourceCollapsed, pinned: [{declaration, collapsed}], history?, activity?: {open, kind, who, mine?}, lostAndFound?: {open}, colouring?, edges?, inWords}}], privateCount, agents: [{name, idle, state?, status?, lastSeen?, you?, place}], note}",
2748
2783
  run: (a) => this.companion(a),
2749
2784
  },
2750
2785
  {
@@ -2881,7 +2916,7 @@ export class AgentApi {
2881
2916
  },
2882
2917
  {
2883
2918
  method: "POST", path: "/api/agent/view/link", about: "A link to what you look at, or to a place you give, that opens exactly that for whoever opens it, as a person's Share makes with Exactly what I see (the owner's decision of 2026-10-12): the address, and after it a snapshot of everything followed — the view, the build in the Time Machine, the region of the board, the tool, the selection, the focus, the panel, the source windows, the reference window, Activity, the lost-and-found, the colouring, the edges. Fields you send are taken over your own place (as POST view takes them); nothing moves, you neither. The page that opens it applies it once, and says in a line what is no longer there. It holds no key: it opens for whoever is let in already, the people on the page.",
2884
- params: { "view, review, sel, selection, mode, source, focus, paper": "as POST view: over your own place", sourceCollapsed: "true: the source window following sel is collapsed to its pill", pinned: `source windows pinned, [{name, collapsed?}], at most ${MAX_PINNED}, the first pinned first`, history: "a snapshot's id (GET /api/agent/history): the Time Machine at that build; null for none", region: "[x, y, w, h]: the region of the board framed — its centre, width and height in the board's world — fitted to the opener's screen; left out, framed as for any link", activity: "{open, kind?, who?, mine?}: Activity open or closed, its filters", lostFound: "true: the lost-and-found open", scheme: "kind or status: the colouring", edges: "edges drawn", proofEdges: "proof edges drawn", exact: "false: only the view, as Share's switch turned off makes it (the address alone)" },
2919
+ params: { "view, review, sel, selection, mode, source, focus, paper": "as POST view: over your own place", sourceCollapsed: "true: the source window following sel is collapsed to its pill", pinned: `source windows pinned, [{name, collapsed?}], at most ${MAX_PINNED}, the first pinned first`, history: "a snapshot's id (GET /api/agent/history): the Time Machine at that build; null for none", region: "[x, y, w, h]: the region of the board framed — its centre, width and height in the board's world — fitted to the opener's screen; left out, framed as for any link", activity: "{open, kind?, who?, mine?}: Activity open or closed, its filters", lostFound: "true: the lost-and-found open", scheme: "kind or status: the colouring", edges: "edges drawn", exact: "false: only the view, as Share's switch turned off makes it (the address alone)" },
2885
2920
  returns: "{ok, link, exact, carries: what the link opens on, length, note, temporary?}",
2886
2921
  run: (a) => this.viewLink(a),
2887
2922
  },
@@ -70,6 +70,45 @@ export function commitNow(root) {
70
70
  const status = git(root, "status", "--porcelain", "--untracked-files=no", "--", "*.lean");
71
71
  return { hash, branch: branch && branch !== "HEAD" ? branch : null, ...(status ? { dirty: true } : {}) };
72
72
  }
73
+ /** The project's git source and the commit checked out, as a page may show them (an artwork's foot,
74
+ * ExportSpec decision 19): the remote's address (`origin`, else the first) with no user name or
75
+ * password (sourceAddress); null where the project is not a git repository. Read-only, as `git`. */
76
+ export function gitSource(root) {
77
+ const c = commitNow(root);
78
+ if (!c)
79
+ return null;
80
+ const remotes = (git(root, "remote") ?? "").split("\n").filter(Boolean);
81
+ const name = remotes.includes("origin") ? "origin" : remotes[0];
82
+ const url = name ? git(root, "remote", "get-url", name) : null;
83
+ return { source: url ? sourceAddress(url) : null, commit: c.hash, branch: c.branch, ...(c.dirty ? { dirty: true } : {}) };
84
+ }
85
+ /** A remote's address as a page shows it: its host and path, without the scheme, a user name, a
86
+ * password, a port or `.git` — `git@github.com:A/B.git` and `https://user:token@github.com/A/B`
87
+ * both read `github.com/A/B`. Null for a path on this machine, or what is no address. */
88
+ export function sourceAddress(url) {
89
+ const u = url.trim();
90
+ let host, path;
91
+ // scp-like, [user@]host:path, but not a Windows drive
92
+ const scp = /^(?:[^@/\s]+@)?([A-Za-z0-9.-]+):(?!\/\/)(\S+)$/.exec(u);
93
+ if (scp && !/^[A-Za-z]:[\\/]/.test(u))
94
+ [host, path] = [scp[1], scp[2]];
95
+ else {
96
+ let p;
97
+ try {
98
+ p = new URL(u);
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ if (!["https:", "http:", "ssh:", "git:"].includes(p.protocol) || !p.hostname)
104
+ return null;
105
+ [host, path] = [p.hostname, decodeURIComponent(p.pathname)];
106
+ }
107
+ path = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
108
+ if (!/^[A-Za-z0-9.-]+$/.test(host) || !path || !/^[\w.~/-]+$/.test(path))
109
+ return null;
110
+ return `${host}/${path}`;
111
+ }
73
112
  /** The last commit made before `at`: what an imported extraction was probably built from. */
74
113
  function commitBefore(root, at) {
75
114
  const hash = git(root, "log", "-1", "--format=%H", `--before=${at}`);
@@ -8,7 +8,7 @@
8
8
  // or an agent at level full.
9
9
  //
10
10
  // The Time Machine steps from build to build, and a snapshot's graph is large (on a large project,
11
- // megabytes of JSON and a noticeable time to make and compress: the showcase's, 7,851 declarations, is 13.7 MB, made
11
+ // megabytes of JSON and a noticeable time to make and compress: Sphere-Packing-Lean's, 7,851 declarations, is 13.7 MB, made
12
12
  // in 0.6 s): the page takes it in two parts, its declarations as JSON and its
13
13
  // edges packed as the graph on show's are (shared/edgecodec.ts), each compressed once and kept for
14
14
  // the last few snapshots asked, under the renames in force, and tagged so that a page that has one
@@ -21,7 +21,7 @@ const no = (status, error) => ({ status, body: { error } });
21
21
  const ok = (body) => ({ status: 200, body });
22
22
  /** the parts of the last snapshots asked, per history, by snapshot, build read and renames */
23
23
  const kept = new WeakMap();
24
- /** how many snapshots' parts are kept: about a megabyte each, compressed, on a large project (the showcase's:
24
+ /** how many snapshots' parts are kept: about a megabyte each, compressed, on a large project (Sphere-Packing-Lean's:
25
25
  * 0.7 MB of declarations, 0.5 MB of edges) */
26
26
  const KEEP_PARTS = 8;
27
27
  /** what changed between two snapshots, as answered last (a step on the Time Machine's timeline asks
@@ -15,7 +15,7 @@
15
15
  // (a diagnostic), not as a hover.
16
16
  //
17
17
  // Lean runs each open file in a worker process of its own, which can die (a worker on a large
18
- // project holds gigabytes: 4.1 GB for the showcase's main theorem's module; and the kernel's OOM killer picks the
18
+ // project holds gigabytes: 4.1 GB for Sphere-Packing-Lean's main theorem's module; and the kernel's OOM killer picks the
19
19
  // largest). Lean then says the file's progress
20
20
  // is a fatal error, and starts the worker again only when the document changes: until then every
21
21
  // question about it would wait forever. So a crashed document is closed and opened again here, as a
@@ -38,8 +38,8 @@ const IDLE_MS = 10 * 60 * 1000;
38
38
  const REQUEST_MS = 5 * 60 * 1000;
39
39
  /** a document still being elaborated that Lean has said nothing about for this long has stopped,
40
40
  * whatever Lean says: it is opened again, as a crashed one (loading a large project's imports can
41
- * take minutes on a machine short of memory, in which Lean says nothing; on an idle one the
42
- * showcase's main theorem's module answers in 2 s) */
41
+ * take minutes on a machine short of memory, in which Lean says nothing; on an idle one
42
+ * Sphere-Packing-Lean's main theorem's module answers in 2 s) */
43
43
  const STALL_MS = 10 * 60 * 1000;
44
44
  /** crashes in a row (the document never elaborated to its end between them) after which a document
45
45
  * is not opened again until GIVE_UP_MS have passed */
@@ -1,5 +1,5 @@
1
1
  // The graph's layouts, off the server's thread: computeLayout then stabilize, for each clustering in
2
- // a worker of its own, both at once. Laid out on the server's thread, the showcase's graph held
2
+ // a worker of its own, both at once. Laid out on the server's thread, Sphere-Packing-Lean's graph held
3
3
  // every request for seconds (2.5 s a clustering, more against a memory far off); a worker holds
4
4
  // none. A worker is given what a layout reads of each declaration, the edges and the memory, as
5
5
  // the page's own (web/src/layoutworker.ts), and gives the layout back. This module is its own
@@ -7,7 +7,7 @@ import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
7
7
  import { createServer } from "node:http";
8
8
  import { extname, join, resolve } from "node:path";
9
9
  import { gunzipSync, gzipSync } from "node:zlib";
10
- import { WEB, bundleDir, bundleDirIn, ensureBundle, prepare, useBundleDir } from "./pipeline.js";
10
+ import { WEB, WEBFONTS, bundleDir, bundleDirIn, ensureBundle, prepare, useBundleDir } from "./pipeline.js";
11
11
  import { SiteBusy, cacheHome, dataHome, lockSite, openSite, projectDirs } from "./dirs.js";
12
12
  import { ProjectError, openProject, targetsInWords } from "./project.js";
13
13
  import { Live } from "./live.js";
@@ -16,7 +16,7 @@ import { isDeclName, LeanHover } from "./hover.js";
16
16
  import { Identities, layoutAuthor } from "./identity.js";
17
17
  import { LAYOUT_VERSION } from "../shared/graph.js";
18
18
  import { MAX_MEMORY_BYTES, Views } from "./views.js";
19
- import { cleanView, defaultAgentName, validSession } from "../shared/collab.js";
19
+ import { cleanView, defaultAgentName, validSession, withoutArtwork } from "../shared/collab.js";
20
20
  import { writeSnapshot } from "./snapshot.js";
21
21
  import { lakeTextHash } from "./status.js";
22
22
  import { invalidTemplate } from "../shared/editor.js";
@@ -265,6 +265,11 @@ export class App {
265
265
  async export(req, res, url, agent, readOnly = false) {
266
266
  const path = url.pathname;
267
267
  const api = this.agents;
268
+ // the document's outline, which the linear form draws: nothing is typeset for it
269
+ if (path === "/api/export/outline") {
270
+ const o = api.exportOutline(url.searchParams.get("view") ?? "", url.searchParams.get("version") ?? undefined);
271
+ return sendJson(res, o.body, o.status, o.headers);
272
+ }
268
273
  if (!this.exporter)
269
274
  return sendJson(res, { error: "This server does not export documents." }, 404);
270
275
  let a;
@@ -817,6 +822,9 @@ export class App {
817
822
  return sendFile(req, res, join(WEB, "index.html"));
818
823
  if (path === "/style.css")
819
824
  return sendFile(req, res, join(WEB, "style.css"));
825
+ // the fonts an artwork is drawn in (ExportSpec decision 19)
826
+ if (FONT_PATH.test(path))
827
+ return sendFile(req, res, join(WEBFONTS, path.slice("/fonts/".length)));
820
828
  // the page, and its workers: the one that lays out the Time Machine's past graphs
821
829
  // (web/src/layoutworker.ts), the one that chooses the overview's edges (web/src/edgeworker.ts),
822
830
  // and pdf.js's, which reads the library's PDFs for the reference window (AttachmentSpec decision 11)
@@ -937,8 +945,9 @@ export class App {
937
945
  // its own event stream only, by the secret the stream was given (`hello`)
938
946
  const name = this.ids.who(req);
939
947
  const priv = !agent && b.private === true;
948
+ // agents have no artwork (ExportSpec decision 19): the page draws it
940
949
  if (agent && name)
941
- live.presence.agentBeat(name, cleanView(b.view));
950
+ live.presence.agentBeat(name, withoutArtwork(cleanView(b.view)));
942
951
  else if (!live.beat(b.token, priv ? null : name, priv ? null : cleanView(b.view), !priv && b.idle === true, priv, !!this.ids.person(req)))
943
952
  return sendJson(res, { error: "This heartbeat is not from an open event stream of this page's: it is sent with the token the stream says hello with.", stream: false }, 403);
944
953
  return sendJson(res, { ok: true });
@@ -1080,7 +1089,10 @@ export class App {
1080
1089
  }
1081
1090
  }
1082
1091
  }
1092
+ /** an artwork's font file (assets/webfonts/) */
1093
+ const FONT_PATH = /^\/fonts\/(source-sans-3|source-serif-4|jost)-[a-z-]+\.woff2$/;
1083
1094
  const TYPES = {
1095
+ ".woff2": "font/woff2",
1084
1096
  ".html": "text/html; charset=utf-8",
1085
1097
  ".css": "text/css; charset=utf-8",
1086
1098
  ".js": "text/javascript; charset=utf-8",
@@ -149,7 +149,7 @@ export const TOOLS = [
149
149
  default: "move",
150
150
  actions: {
151
151
  move: { method: "POST", path: "/api/agent/view", says: "go to a view, a target's view, a declaration, its source; fields left out keep their value", args: ["view", "review", "sel", "selection", "mode", "source", "paper", "focus", "leave"] },
152
- link: { method: "POST", path: "/api/agent/view/link", says: "a link that opens exactly what you look at, or a place you describe, to hand people in a reply", args: ["view", "review", "sel", "selection", "mode", "source", "paper", "focus", "sourceCollapsed", "pinned", "history", "region", "activity", "lostFound", "scheme", "edges", "proofEdges", "exact"] },
152
+ link: { method: "POST", path: "/api/agent/view/link", says: "a link that opens exactly what you look at, or a place you describe, to hand people in a reply", args: ["view", "review", "sel", "selection", "mode", "source", "paper", "focus", "sourceCollapsed", "pinned", "history", "region", "activity", "lostFound", "scheme", "edges", "exact"] },
153
153
  ask_follow: { method: "POST", path: "/api/agent/ask-follow", says: "ask the people on the page to follow you first", args: ["who", "wait"] },
154
154
  },
155
155
  args: {
@@ -157,7 +157,7 @@ export const TOOLS = [
157
157
  review: str("a target: be on its view"),
158
158
  sel: str("the declaration you look at"),
159
159
  selection: str("what you select, as a query: decl(A.b) or tcb(A.c)"),
160
- mode: str("how you select: plain; Rests on's deps, tcb, keys; apps", ["plain", "deps", "tcb", "keys", "apps"]),
160
+ mode: str("how you select: plain; Rests on's deps, tcb, keys, refs (what it uses directly); apps", ["plain", "deps", "tcb", "keys", "refs", "apps"]),
161
161
  source: bool("the source window open on sel"),
162
162
  paper: PAPER,
163
163
  focus: str("your focus on the view: a target, \"none\", or \"view\" for the view's own"),
@@ -170,7 +170,6 @@ export const TOOLS = [
170
170
  lostFound: bool("link: the lost-and-found open"),
171
171
  scheme: str("link: the colouring", ["kind", "status"]),
172
172
  edges: bool("link: edges drawn"),
173
- proofEdges: bool("link: proof edges drawn"),
174
173
  exact: bool("link: false for the view's address alone"),
175
174
  who: str("ask_follow: a person's name; left out, everyone"),
176
175
  wait: bool("ask_follow: false answers at once (default: wait for them, at most 30 s)"),
@@ -14,7 +14,7 @@ import { pastGraph } from "../shared/pastgraph.js";
14
14
  import { pageGraph } from "./history.js";
15
15
  import { MOST_DIFF, snapshotDiff } from "./historyapi.js";
16
16
  /** how many past builds are kept: a past graph and the texts read for a large view are large on a
17
- * large project (the showcase's past graph: 13.7 MB of JSON) */
17
+ * large project (Sphere-Packing-Lean's past graph: 13.7 MB of JSON) */
18
18
  const KEEP = 2;
19
19
  export class PastBuilds {
20
20
  w;