visualproof 0.2.0 → 0.4.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 (96) hide show
  1. package/README.md +88 -33
  2. package/dist/assets/tips/phone-fingers.light.mp4 +0 -0
  3. package/dist/assets/tips/phone-fingers.light.webp +0 -0
  4. package/dist/assets/tips/tour-agent.webp +0 -0
  5. package/dist/assets/tips/tour-arrange.webp +0 -0
  6. package/dist/assets/tips/tour-artwork.webp +0 -0
  7. package/dist/assets/tips/tour-checks.webp +0 -0
  8. package/dist/assets/tips/tour-export.webp +0 -0
  9. package/dist/assets/tips/tour-keys.webp +0 -0
  10. package/dist/assets/tips/tour-library.webp +0 -0
  11. package/dist/assets/tips/tour-project.webp +0 -0
  12. package/dist/assets/tips/tour-review.webp +0 -0
  13. package/dist/assets/tips/tour-share.webp +0 -0
  14. package/dist/assets/tips/tour-source.webp +0 -0
  15. package/dist/assets/tips/tour-target.webp +0 -0
  16. package/dist/assets/tips/tour-view.webp +0 -0
  17. package/dist/assets/tips/tour.json +289 -0
  18. package/dist/assets/tips/tour.webp +0 -0
  19. package/dist/assets/webfonts/LICENSE-Noto.txt +95 -0
  20. package/dist/assets/webfonts/metrics.json +1 -0
  21. package/dist/assets/webfonts/ttf/jost-400-normal.ttf +0 -0
  22. package/dist/assets/webfonts/ttf/jost-500-normal.ttf +0 -0
  23. package/dist/assets/webfonts/ttf/noto-sans-math.ttf +0 -0
  24. package/dist/assets/webfonts/ttf/noto-sans.ttf +0 -0
  25. package/dist/assets/webfonts/ttf/source-sans-3-400-italic.ttf +0 -0
  26. package/dist/assets/webfonts/ttf/source-sans-3-400-normal.ttf +0 -0
  27. package/dist/assets/webfonts/ttf/source-sans-3-500-normal.ttf +0 -0
  28. package/dist/assets/webfonts/ttf/source-sans-3-600-normal.ttf +0 -0
  29. package/dist/assets/webfonts/ttf/source-sans-3-700-normal.ttf +0 -0
  30. package/dist/assets/webfonts/ttf/source-serif-4-400-italic.ttf +0 -0
  31. package/dist/assets/webfonts/ttf/source-serif-4-600-italic.ttf +0 -0
  32. package/dist/mcp/visualproof-mcp.mjs +3 -1
  33. package/dist/server/agent.js +94 -91
  34. package/dist/server/agentdocs.js +1 -1
  35. package/dist/server/artcmd.js +335 -0
  36. package/dist/server/children.js +85 -0
  37. package/dist/server/doctor.js +105 -42
  38. package/dist/server/events.js +10 -0
  39. package/dist/server/export.js +11 -4
  40. package/dist/server/extract.js +3 -2
  41. package/dist/server/figures.js +3 -2
  42. package/dist/server/film.js +380 -0
  43. package/dist/server/history.js +11 -1
  44. package/dist/server/hover.js +8 -5
  45. package/dist/server/live.js +72 -9
  46. package/dist/server/main.js +352 -23
  47. package/dist/server/mcptools.js +4 -1
  48. package/dist/server/media.js +114 -0
  49. package/dist/server/pipeline.js +21 -10
  50. package/dist/server/poppler.js +3 -2
  51. package/dist/server/readonlygate.js +4 -4
  52. package/dist/server/review.js +4 -1
  53. package/dist/server/reviewapi.js +35 -1
  54. package/dist/server/snapshot.js +10 -1
  55. package/dist/server/sourcestore.js +3 -2
  56. package/dist/server/status.js +3 -2
  57. package/dist/server/supervise.js +219 -0
  58. package/dist/server/updates.js +951 -0
  59. package/dist/server/usage.js +3 -1
  60. package/dist/server/version.js +183 -0
  61. package/dist/server/view.js +0 -7
  62. package/dist/server/views.js +24 -0
  63. package/dist/shared/artfonts.js +81 -0
  64. package/dist/shared/artrecipe.js +316 -0
  65. package/dist/shared/collab.js +7 -2
  66. package/dist/shared/declfind.js +95 -0
  67. package/dist/shared/lostfound.js +1 -0
  68. package/dist/shared/pastgraph.js +6 -6
  69. package/dist/shared/review.js +62 -5
  70. package/dist/shared/timeline.js +5 -0
  71. package/dist/web/bundle/app.js +3006 -413
  72. package/dist/web/bundle/edges.js +1 -1
  73. package/dist/web/index.html +13 -1
  74. package/dist/web/src/agentmark.js +14 -0
  75. package/dist/web/src/artwork.js +1000 -0
  76. package/dist/web/src/bundles.js +151 -0
  77. package/dist/web/src/canvas.js +3716 -0
  78. package/dist/web/src/drag.js +61 -0
  79. package/dist/web/src/edgequeue.js +204 -0
  80. package/dist/web/src/gl/gl.js +123 -0
  81. package/dist/web/src/gl/renderer.js +451 -0
  82. package/dist/web/src/halo.js +95 -0
  83. package/dist/web/src/highlight.js +108 -0
  84. package/dist/web/src/overview.js +393 -0
  85. package/dist/web/src/reviewqueue.js +250 -0
  86. package/dist/web/src/selection.js +272 -0
  87. package/dist/web/src/status.js +80 -0
  88. package/dist/web/src/visitors.js +318 -0
  89. package/dist/web/src/visits.js +174 -0
  90. package/dist/web/style.css +196 -1
  91. package/docs/agent-api/README.md +1 -1
  92. package/docs/agent-api/export.md +41 -2
  93. package/docs/agent-api/history.md +5 -3
  94. package/docs/agent-api/mcp.md +1 -1
  95. package/docs/agent-api/reading.md +2 -1
  96. package/package.json +5 -1
@@ -39,6 +39,8 @@ import { join } from "node:path";
39
39
  import { PROJECT, viewTitle } from "../shared/views.js";
40
40
  import { boardSet } from "../shared/board.js";
41
41
  import { derivesOn, existsIn } from "../shared/noteplace.js";
42
+ import { packageVersion } from "./version.js";
43
+ import { withoutReasons } from "../shared/timeline.js";
42
44
  const TICK = 1000;
43
45
  /** outputs quiet this long, and no compile running: the build is over */
44
46
  const SETTLE = 2500;
@@ -58,8 +60,6 @@ export const MAX_BUFFERED = 4 << 20;
58
60
  const PRESENCE_EVERY = 500;
59
61
  /** The graph's modules and their files (meta.modules), per graph read (agentviews.ts, graphOf). */
60
62
  const moduleFiles = new WeakMap();
61
- /** What a stream's page alone is told of it (`hello`). */
62
- const helloOf = (st) => ({ id: st.id, token: st.token, ...(st.private ? { private: true } : {}) });
63
63
  export class Live {
64
64
  project;
65
65
  /** the site directory and the project's caches (server/dirs.ts) */
@@ -85,6 +85,15 @@ export class Live {
85
85
  /** review changes not yet sent: those made in one turn of the event loop go out as one */
86
86
  reviewPending = [];
87
87
  version = 0;
88
+ /** this server's process, as its pages know it (UpdateSpec decision 8): the graph's versions are
89
+ * counted again by another */
90
+ instance = randomBytes(6).toString("base64url");
91
+ /** the VisualProof version this server runs, read as it started */
92
+ toolVersion = packageVersion();
93
+ /** what a page is told of VisualProof's releases (server/updates.ts): everything, or the version
94
+ * alone (`full` false: a read-only reader, an agent); null where nobody follows them (a test's
95
+ * server) */
96
+ updateStatus = null;
88
97
  /** the review's logs (ReviewSpec.md, server/review.ts) */
89
98
  review;
90
99
  /** who has a page open (CollabSpec.md) */
@@ -140,7 +149,7 @@ export class Live {
140
149
  this.editor = editor;
141
150
  this.usage = new UsagePass(project, dirs, saved ? 0 : jobs, (l) => this.say(l));
142
151
  this.tracker = new BuildTracker(project);
143
- this.timeline = new Timeline(dirs.site, (e) => this.send("activity", e));
152
+ this.timeline = new Timeline(dirs.site, (e) => this.sendActivity(e));
144
153
  this.tokens = new AgentTokens(dirs.site);
145
154
  this.invites = new Invites(dirs.site);
146
155
  // every page knows the names the people invited by a link go by, so that what each did shows
@@ -304,7 +313,7 @@ export class Live {
304
313
  // a private reader cannot be followed: what they asked is withdrawn
305
314
  if (priv)
306
315
  this.asks.withdraw(s.token);
307
- this.write(s.res, "hello", helloOf(s));
316
+ this.write(s.res, "hello", this.helloOf(s));
308
317
  if (s.session) {
309
318
  if (priv)
310
319
  this.views.hold(s.session, was ? [...(was.view ? [was.view] : []), ...was.tabs] : []);
@@ -660,7 +669,7 @@ export class Live {
660
669
  written = found.written;
661
670
  return found;
662
671
  };
663
- const r = this.history.record(ex.graph, { at: buildTime(this.project), fingerprint: ex.fingerprint, commit, toolchain: this.project.toolchain, missing, sources });
672
+ const r = this.history.record(ex.graph, { at: buildTime(this.project), fingerprint: ex.fingerprint, commit, toolchain: this.project.toolchain, missing, sources, tool: this.toolVersion });
664
673
  if (!r.written)
665
674
  return renamed;
666
675
  const s = r.info;
@@ -913,6 +922,12 @@ export class Live {
913
922
  }
914
923
  return made;
915
924
  }
925
+ /** The holder of a personal link in force who goes by `name` (CollabSpec decision 9): whom an
926
+ * opinion recorded for that name is for (ReviewSpec decision 8). */
927
+ invited(name) {
928
+ const r = this.invites.holder(name);
929
+ return r ? { id: r.id, name: r.name } : undefined;
930
+ }
916
931
  /** The usage pass's contexts of a module (shared/context.ts), when the graph on show has them. */
917
932
  contextsOf(module) {
918
933
  return this.graph?.prep.usage?.contexts[module] ?? null;
@@ -950,7 +965,53 @@ export class Live {
950
965
  if (!this.graph)
951
966
  return null;
952
967
  const { prep: _, ...info } = this.graph;
953
- return info;
968
+ return { ...info, instance: this.instance };
969
+ }
970
+ /** What a stream's page alone is told of it (`hello`), and which server it is (UpdateSpec
971
+ * decision 8): its version, and its process. */
972
+ helloOf(st) {
973
+ return { id: st.id, token: st.token, ...(st.private ? { private: true } : {}), server: { version: this.toolVersion, instance: this.instance } };
974
+ }
975
+ /** How many pages are open on the server (their event streams), read-only readers' too. */
976
+ get pages() {
977
+ return this.streams.size;
978
+ }
979
+ /** Whether the server is working on the build (an extraction, a re-layout, the usage pass):
980
+ * no moment to restart in (UpdateSpec decision 6). */
981
+ get busy() {
982
+ return this.extracting || this.usage.status.state === "running";
983
+ }
984
+ /** What happened, to every page: a version set aside, without why, to those not told all
985
+ * (UpdateSpec decision 11). */
986
+ sendActivity(e) {
987
+ if (e.kind !== "tool" || !e.setAside)
988
+ return this.send("activity", e);
989
+ const full = `event: activity\ndata: ${JSON.stringify(e)}\n\n`;
990
+ const slim = `event: activity\ndata: ${JSON.stringify(withoutReasons(e))}\n\n`;
991
+ for (const st of [...this.streams.values()])
992
+ this.push(st, st.toldAll ? full : slim);
993
+ }
994
+ /** What is known of VisualProof's releases changed: each page is told what it may know of it
995
+ * (`toldAll`, as GET /api/updates says it). */
996
+ updatesChanged() {
997
+ const status = this.updateStatus;
998
+ if (!status)
999
+ return;
1000
+ const full = `event: updates\ndata: ${JSON.stringify(status(true))}\n\n`;
1001
+ const slim = `event: updates\ndata: ${JSON.stringify(status(false))}\n\n`;
1002
+ for (const st of [...this.streams.values()])
1003
+ this.push(st, st.toldAll ? full : slim);
1004
+ }
1005
+ /** The server stops (UpdateSpec decision 9): every page is told why — a restart into another
1006
+ * version (`update`), or a stop (`bye`) — and its stream ended, so that it comes back to the
1007
+ * server that answers next. */
1008
+ end(why) {
1009
+ const text = "update" in why ? `event: update\ndata: ${JSON.stringify(why.update)}\n\n` : `event: bye\ndata: ${JSON.stringify({ why: "stopped" })}\n\n`;
1010
+ for (const st of [...this.streams.values()]) {
1011
+ this.streams.delete(st.res);
1012
+ this.byToken.delete(st.token);
1013
+ st.res.end(text);
1014
+ }
954
1015
  }
955
1016
  /** What /api/status says: enough for the loader and for a page that just connected. */
956
1017
  status() {
@@ -975,7 +1036,7 @@ export class Live {
975
1036
  * it), `person` the personal link it came by. The stream is named by the server: the page is
976
1037
  * told its id, which everyone sees, and a secret for its heartbeats (`hello`); it is gone from
977
1038
  * presence when its stream closes. */
978
- subscribe(res, session = null, person = null, readOnly = null) {
1039
+ subscribe(res, session = null, person = null, readOnly = null, toldAll = readOnly === null) {
979
1040
  if (session) {
980
1041
  // a page that opens one more than a page needs (a reload whose old stream lingers) loses
981
1042
  // its oldest
@@ -995,12 +1056,14 @@ export class Live {
995
1056
  connection: "keep-alive",
996
1057
  });
997
1058
  res.write("retry: 2000\n\n");
998
- const st = { res, id: randomBytes(9).toString("base64url"), token: randomBytes(24).toString("base64url"), session, private: false, person, readOnly };
1059
+ const st = { res, id: randomBytes(9).toString("base64url"), token: randomBytes(24).toString("base64url"), session, private: false, person, readOnly, toldAll };
999
1060
  this.streams.set(res, st);
1000
1061
  this.byToken.set(st.token, st);
1001
1062
  this.presence.open(st.id);
1002
1063
  res.on("close", () => this.close(st));
1003
- this.write(res, "hello", helloOf(st));
1064
+ this.write(res, "hello", this.helloOf(st));
1065
+ if (this.updateStatus)
1066
+ this.write(res, "updates", this.updateStatus(st.toldAll));
1004
1067
  this.write(res, "presence", this.presence.list());
1005
1068
  this.write(res, "private", { count: this.presence.privateCount() });
1006
1069
  this.write(res, "people", this.invites.people());