@orchestraworks/cli 0.8.0 → 0.9.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 (117) hide show
  1. package/README.md +1 -1
  2. package/dist/artifacts/prelude.js +416 -88
  3. package/dist/cli/api/execute.d.ts +2 -5
  4. package/dist/cli/api/execute.js +67 -40
  5. package/dist/cli/api/raw.d.ts +3 -1
  6. package/dist/cli/api/raw.js +33 -6
  7. package/dist/cli/api/register.js +6 -1
  8. package/dist/cli/preview.d.ts +5 -4
  9. package/dist/cli/preview.js +15 -9
  10. package/dist/cli/upload.d.ts +26 -0
  11. package/dist/cli/upload.js +64 -16
  12. package/dist/generated/cli-commands.d.ts +5 -0
  13. package/dist/generated/cli-commands.json +31100 -17245
  14. package/dist/generated/cli-dispatch.js +99 -0
  15. package/dist/generated/managed-skills.js +1 -0
  16. package/node_modules/@orchestraworks/client/dist/apis/ArtifactsApi.d.ts +3 -3
  17. package/node_modules/@orchestraworks/client/dist/apis/ArtifactsApi.js +2 -2
  18. package/node_modules/@orchestraworks/client/dist/apis/FilesApi.d.ts +292 -2
  19. package/node_modules/@orchestraworks/client/dist/apis/FilesApi.js +988 -30
  20. package/node_modules/@orchestraworks/client/dist/esm/apis/ArtifactsApi.d.ts +3 -3
  21. package/node_modules/@orchestraworks/client/dist/esm/apis/ArtifactsApi.js +2 -2
  22. package/node_modules/@orchestraworks/client/dist/esm/apis/FilesApi.d.ts +292 -2
  23. package/node_modules/@orchestraworks/client/dist/esm/apis/FilesApi.js +986 -28
  24. package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.d.ts +12 -53
  25. package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.js +7 -29
  26. package/node_modules/@orchestraworks/client/dist/esm/models/Comment.d.ts +2 -2
  27. package/node_modules/@orchestraworks/client/dist/esm/models/Comment.js +3 -3
  28. package/node_modules/@orchestraworks/client/dist/esm/models/CommentSummary.d.ts +2 -2
  29. package/node_modules/@orchestraworks/client/dist/esm/models/CommentSummary.js +3 -3
  30. package/node_modules/@orchestraworks/client/dist/esm/models/CopyFileRequest.d.ts +38 -0
  31. package/node_modules/@orchestraworks/client/dist/esm/models/CopyFileRequest.js +47 -0
  32. package/node_modules/@orchestraworks/client/dist/esm/models/CreateArtifactRequest.d.ts +7 -7
  33. package/node_modules/@orchestraworks/client/dist/esm/models/CreateAuthHandoffRequest.d.ts +1 -1
  34. package/node_modules/@orchestraworks/client/dist/esm/models/CreateFileRequest.d.ts +44 -2
  35. package/node_modules/@orchestraworks/client/dist/esm/models/CreateFileRequest.js +14 -0
  36. package/node_modules/@orchestraworks/client/dist/esm/models/CreateRoomTokenRequest.d.ts +1 -1
  37. package/node_modules/@orchestraworks/client/dist/esm/models/FileMatch.d.ts +38 -0
  38. package/node_modules/@orchestraworks/client/dist/esm/models/FileMatch.js +47 -0
  39. package/node_modules/@orchestraworks/client/dist/esm/models/FileVersion.d.ts +101 -0
  40. package/node_modules/@orchestraworks/client/dist/esm/models/FileVersion.js +83 -0
  41. package/node_modules/@orchestraworks/client/dist/esm/models/ModelFile.d.ts +69 -0
  42. package/node_modules/@orchestraworks/client/dist/esm/models/ModelFile.js +36 -0
  43. package/node_modules/@orchestraworks/client/dist/esm/models/PageArtifact.d.ts +51 -0
  44. package/node_modules/@orchestraworks/client/dist/esm/models/{PageArtifactSummary.js → PageArtifact.js} +11 -11
  45. package/node_modules/@orchestraworks/client/dist/esm/models/PageFileVersion.d.ts +51 -0
  46. package/node_modules/@orchestraworks/client/dist/esm/models/PageFileVersion.js +56 -0
  47. package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoom.d.ts +2 -2
  48. package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoom.js +2 -2
  49. package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoomRequest.d.ts +3 -3
  50. package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoomRequest.js +2 -2
  51. package/node_modules/@orchestraworks/client/dist/esm/models/SelectorAnchor.d.ts +24 -0
  52. package/node_modules/@orchestraworks/client/dist/esm/models/SelectorAnchor.js +8 -0
  53. package/node_modules/@orchestraworks/client/dist/esm/models/SnapshotAnchor.d.ts +1 -1
  54. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateArtifactRequest.d.ts +0 -8
  55. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateArtifactRequest.js +0 -2
  56. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateFileRequest.d.ts +40 -0
  57. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateFileRequest.js +43 -0
  58. package/node_modules/@orchestraworks/client/dist/esm/models/WorkItem.d.ts +3 -3
  59. package/node_modules/@orchestraworks/client/dist/esm/models/WorkItem.js +3 -3
  60. package/node_modules/@orchestraworks/client/dist/esm/models/WriteFileDataRequest.d.ts +50 -0
  61. package/node_modules/@orchestraworks/client/dist/esm/models/WriteFileDataRequest.js +51 -0
  62. package/node_modules/@orchestraworks/client/dist/esm/models/index.d.ts +7 -3
  63. package/node_modules/@orchestraworks/client/dist/esm/models/index.js +7 -3
  64. package/node_modules/@orchestraworks/client/dist/models/Artifact.d.ts +12 -53
  65. package/node_modules/@orchestraworks/client/dist/models/Artifact.js +8 -30
  66. package/node_modules/@orchestraworks/client/dist/models/Comment.d.ts +2 -2
  67. package/node_modules/@orchestraworks/client/dist/models/Comment.js +3 -3
  68. package/node_modules/@orchestraworks/client/dist/models/CommentSummary.d.ts +2 -2
  69. package/node_modules/@orchestraworks/client/dist/models/CommentSummary.js +3 -3
  70. package/node_modules/@orchestraworks/client/dist/models/CopyFileRequest.d.ts +38 -0
  71. package/node_modules/@orchestraworks/client/dist/models/CopyFileRequest.js +54 -0
  72. package/node_modules/@orchestraworks/client/dist/models/CreateArtifactRequest.d.ts +7 -7
  73. package/node_modules/@orchestraworks/client/dist/models/CreateAuthHandoffRequest.d.ts +1 -1
  74. package/node_modules/@orchestraworks/client/dist/models/CreateFileRequest.d.ts +44 -2
  75. package/node_modules/@orchestraworks/client/dist/models/CreateFileRequest.js +14 -0
  76. package/node_modules/@orchestraworks/client/dist/models/CreateRoomTokenRequest.d.ts +1 -1
  77. package/node_modules/@orchestraworks/client/dist/models/FileMatch.d.ts +38 -0
  78. package/node_modules/@orchestraworks/client/dist/models/FileMatch.js +54 -0
  79. package/node_modules/@orchestraworks/client/dist/models/FileVersion.d.ts +101 -0
  80. package/node_modules/@orchestraworks/client/dist/models/FileVersion.js +91 -0
  81. package/node_modules/@orchestraworks/client/dist/models/ModelFile.d.ts +69 -0
  82. package/node_modules/@orchestraworks/client/dist/models/ModelFile.js +37 -1
  83. package/node_modules/@orchestraworks/client/dist/models/PageArtifact.d.ts +51 -0
  84. package/node_modules/@orchestraworks/client/dist/models/{PageArtifactSummary.js → PageArtifact.js} +16 -16
  85. package/node_modules/@orchestraworks/client/dist/models/PageFileVersion.d.ts +51 -0
  86. package/node_modules/@orchestraworks/client/dist/models/PageFileVersion.js +63 -0
  87. package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoom.d.ts +2 -2
  88. package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoom.js +2 -2
  89. package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoomRequest.d.ts +3 -3
  90. package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoomRequest.js +2 -2
  91. package/node_modules/@orchestraworks/client/dist/models/SelectorAnchor.d.ts +24 -0
  92. package/node_modules/@orchestraworks/client/dist/models/SelectorAnchor.js +8 -0
  93. package/node_modules/@orchestraworks/client/dist/models/SnapshotAnchor.d.ts +1 -1
  94. package/node_modules/@orchestraworks/client/dist/models/UpdateArtifactRequest.d.ts +0 -8
  95. package/node_modules/@orchestraworks/client/dist/models/UpdateArtifactRequest.js +0 -2
  96. package/node_modules/@orchestraworks/client/dist/models/UpdateFileRequest.d.ts +40 -0
  97. package/node_modules/@orchestraworks/client/dist/models/UpdateFileRequest.js +50 -0
  98. package/node_modules/@orchestraworks/client/dist/models/WorkItem.d.ts +3 -3
  99. package/node_modules/@orchestraworks/client/dist/models/WorkItem.js +3 -3
  100. package/node_modules/@orchestraworks/client/dist/models/WriteFileDataRequest.d.ts +50 -0
  101. package/node_modules/@orchestraworks/client/dist/models/WriteFileDataRequest.js +58 -0
  102. package/node_modules/@orchestraworks/client/dist/models/index.d.ts +7 -3
  103. package/node_modules/@orchestraworks/client/dist/models/index.js +7 -3
  104. package/node_modules/@orchestraworks/client/package.json +1 -1
  105. package/package.json +4 -4
  106. package/templates/engineering-team/package.json +1 -1
  107. package/templates/todo-factory/package.json +1 -1
  108. package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.d.ts +0 -139
  109. package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.js +0 -95
  110. package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactVersion.d.ts +0 -110
  111. package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactVersion.js +0 -77
  112. package/node_modules/@orchestraworks/client/dist/esm/models/PageArtifactSummary.d.ts +0 -51
  113. package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.d.ts +0 -139
  114. package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.js +0 -103
  115. package/node_modules/@orchestraworks/client/dist/models/ArtifactVersion.d.ts +0 -110
  116. package/node_modules/@orchestraworks/client/dist/models/ArtifactVersion.js +0 -84
  117. package/node_modules/@orchestraworks/client/dist/models/PageArtifactSummary.d.ts +0 -51
package/README.md CHANGED
@@ -20,7 +20,7 @@ The full guide to every construct, secrets, connections and troubleshooting is [
20
20
  ```bash
21
21
  npm install -g @orchestraworks/cli # the CLI, on your path as `orchestra`
22
22
  npm install @orchestraworks/cli # the current release
23
- npm install @orchestraworks/cli@0.8.0 # this exact release
23
+ npm install @orchestraworks/cli@0.9.0 # this exact release
24
24
  ```
25
25
 
26
26
  Then `import { Factory, Agent, … } from "@orchestraworks/cli"` normally — the package `exports` entry resolves
@@ -2,13 +2,13 @@
2
2
  * The runtime a published artifact page gets. It is served with the page on the artifacts host and by
3
3
  * `orchestra artifacts preview` from the same file, so a page behaves identically in both.
4
4
  *
5
- * The page never holds a credential and never calls the API. Everything goes to the console frame that
6
- * embeds it, over postMessage, and the console calls the API with the viewer's own session. A page opened
7
- * at its own address has no console to talk to, so it is read only: `sf.viewer` is null, the comments
8
- * overlay is not drawn, and every `sf.data` call rejects.
5
+ * The page never calls the API; it holds no credential but the short-lived links to what it loads, each to bytes
6
+ * its viewer may read. It talks over postMessage to the console frame embedding it, which calls
7
+ * the API with the viewer's session. Opened at its own address, it is read only: `sf.viewer` is null, no comments
8
+ * overlay is drawn, every `sf.data` call rejects, and a link to a library file follows its own address.
9
9
  *
10
- * All three `sf.data` calls answer a promise, including `subscribe`, which resolves to the function that
11
- * stops the subscription. Uniform on purpose: outside the console all three reject the same way.
10
+ * All three `sf.data` calls answer a promise, `subscribe` too, resolving to the function that stops it, so
11
+ * outside the console all three reject the same way.
12
12
  */
13
13
  (function () {
14
14
  "use strict";
@@ -24,13 +24,27 @@
24
24
  var QUOTE_LIMIT = 512;
25
25
  var CONTEXT_LIMIT = 64;
26
26
  var SELECTOR_LIMIT = 1024;
27
+ var ELEMENT_ID_LIMIT = 1024;
28
+ var AGREEMENT_FLOOR = 32;
29
+ var EDIT_CONTEXT_FLOOR = 16;
30
+ var EDIT_SPAN_LIMIT = 512;
31
+ var REDRAW_INTERVAL_MS = 200;
32
+ // Struck copies of removed blocks, and the bar listing them, repeat what the page used to say: their text
33
+ // is never the page's, whatever their style.
34
+ var NOT_PAGE_TEXT = ".sf-removed, #sf-render-changes-bar";
35
+ var VISIBILITY = { visibilityProperty: true, checkVisibilityCSS: true };
27
36
 
28
37
  var pending = {};
29
38
  var callCounter = 0;
30
39
  var dataListeners = [];
31
40
  var comments = [];
41
+ var shownVersion = null;
32
42
  var commentMode = false;
33
43
  var overlay = null;
44
+ var drawn = {};
45
+ var lastReport = null;
46
+ var lastDraw = 0;
47
+ var redrawTimer = null;
34
48
 
35
49
  function call(name, args) {
36
50
  if (!insideConsole) {
@@ -106,40 +120,148 @@
106
120
 
107
121
  function selectorAnchorAt(element, clientX, clientY) {
108
122
  var box = element.getBoundingClientRect();
109
- return {
123
+ var anchor = {
110
124
  kind: "selector",
111
125
  path: selectorOf(element),
112
126
  x: box.width > 0 ? percentage(((clientX - box.left) / box.width) * 100) : 0,
113
127
  y: box.height > 0 ? percentage(((clientY - box.top) / box.height) * 100) : 0
114
128
  };
129
+ var id = element.getAttribute("id") || "";
130
+ if (/\S/.test(id) && id.length <= ELEMENT_ID_LIMIT && elementsCarrying(id).length === 1) {
131
+ anchor.elementId = id;
132
+ }
133
+ var whole = document.createRange();
134
+ whole.selectNodeContents(element);
135
+ var text = textAnchorOfRange(whole);
136
+ if (text) {
137
+ anchor.quote = text.quote;
138
+ anchor.prefix = text.prefix;
139
+ anchor.suffix = text.suffix;
140
+ }
141
+ return anchor;
142
+ }
143
+
144
+ function elementsCarrying(id) {
145
+ return Array.prototype.filter.call(document.querySelectorAll("[id]"), function (element) {
146
+ return element.getAttribute("id") === id;
147
+ });
115
148
  }
116
149
 
117
150
  function percentage(value) {
118
151
  return Math.max(0, Math.min(100, Math.round(value * 100) / 100));
119
152
  }
120
153
 
121
- /** How far into the page's text one boundary of a range sits. */
122
- function offsetOf(container, offset) {
123
- var upTo = document.createRange();
124
- upTo.selectNodeContents(document.body);
125
- upTo.setEnd(container, offset);
126
- return upTo.toString().length;
154
+ function eachPageTextNode(root, visit) {
155
+ if (root.nodeType === 3) {
156
+ visit(root);
157
+ return;
158
+ }
159
+ var walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
160
+ acceptNode: function (node) {
161
+ if (node.nodeType === 3) {
162
+ return NodeFilter.FILTER_ACCEPT;
163
+ }
164
+ return node.matches(NOT_PAGE_TEXT) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_SKIP;
165
+ }
166
+ });
167
+ while (walker.nextNode()) {
168
+ visit(walker.currentNode);
169
+ }
170
+ }
171
+
172
+ /** Whether an element shows. A browser that cannot say counts everything as shown. */
173
+ function isShown(element) {
174
+ if (!element) {
175
+ return false;
176
+ }
177
+ return typeof element.checkVisibility !== "function" || element.checkVisibility(VISIBILITY);
178
+ }
179
+
180
+ function hasArea(box) {
181
+ return box.width > 0 && box.height > 0;
182
+ }
183
+
184
+ function pageTexts() {
185
+ var all = { text: "", nodes: [] };
186
+ var visible = { text: "", nodes: [] };
187
+ if (document.body) {
188
+ var shownParents = new Map();
189
+ eachPageTextNode(document.body, function (node) {
190
+ var value = node.nodeValue || "";
191
+ var parent = node.parentElement;
192
+ if (!shownParents.has(parent)) {
193
+ shownParents.set(parent, isShown(parent));
194
+ }
195
+ var shown = shownParents.get(parent);
196
+ append(all, node, value, shown);
197
+ if (shown) {
198
+ append(visible, node, value, true);
199
+ }
200
+ });
201
+ }
202
+ return { all: all, visible: visible };
203
+ }
204
+
205
+ function append(strand, node, value, shown) {
206
+ var from = strand.text.length;
207
+ strand.nodes.push({ node: node, from: from, to: from + value.length, shown: shown });
208
+ strand.text += value;
209
+ }
210
+
211
+ /** How far into the visible text one boundary of a range sits: the visible characters before it. */
212
+ function visibleOffset(visible, container, offset) {
213
+ var point = document.createRange();
214
+ point.setStart(container, offset);
215
+ for (var index = 0; index < visible.nodes.length; index += 1) {
216
+ var entry = visible.nodes[index];
217
+ if (entry.node === container) {
218
+ return entry.from + offset;
219
+ }
220
+ if (point.comparePoint(entry.node, 0) >= 0) {
221
+ return entry.from;
222
+ }
223
+ }
224
+ return visible.text.length;
225
+ }
226
+
227
+ // Walks the selection's common ancestor rather than building the page's text, because this runs on each
228
+ // change of the selection.
229
+ function selectionHasWords(range) {
230
+ var root = range.commonAncestorContainer;
231
+ var element = root.nodeType === 1 ? root : root.parentElement;
232
+ if (!element || element.closest(NOT_PAGE_TEXT)) {
233
+ return false;
234
+ }
235
+ var found = false;
236
+ eachPageTextNode(root, function (node) {
237
+ if (found || !range.intersectsNode(node) || !isShown(node.parentElement)) {
238
+ return;
239
+ }
240
+ var value = node.nodeValue || "";
241
+ var from = node === range.startContainer ? range.startOffset : 0;
242
+ var to = node === range.endContainer ? range.endOffset : value.length;
243
+ found = /\S/.test(value.slice(from, to));
244
+ });
245
+ return found;
127
246
  }
128
247
 
129
- /**
130
- * The quote plus a little of the text on each side of THAT SPAN, which is what lets the console find the
131
- * same passage again — a page saying "wrong" four times has four of them, and the context is what names
132
- * which. The one string this works in is the page's own `textContent`, the text nodes in reading order,
133
- * which is what the resolver below walks.
134
- */
135
248
  function textAnchorOfSelection(selection) {
136
249
  if (!document.body || selection.rangeCount === 0) {
137
250
  return null;
138
251
  }
139
- var range = selection.getRangeAt(0);
140
- var text = document.body.textContent || "";
141
- var from = offsetOf(range.startContainer, range.startOffset);
142
- var selected = text.slice(from, offsetOf(range.endContainer, range.endOffset));
252
+ return textAnchorOfRange(selection.getRangeAt(0));
253
+ }
254
+
255
+ /**
256
+ * The quote plus a little of the text on each side of THAT SPAN, which is what lets the console find the
257
+ * same passage again — a page saying "wrong" four times has four of them, and the context is what names
258
+ * which.
259
+ */
260
+ function textAnchorOfRange(range) {
261
+ var visible = pageTexts().visible;
262
+ var text = visible.text;
263
+ var from = visibleOffset(visible, range.startContainer, range.startOffset);
264
+ var selected = text.slice(from, visibleOffset(visible, range.endContainer, range.endOffset));
143
265
  var quote = selected.trim().slice(0, QUOTE_LIMIT);
144
266
  if (quote === "") {
145
267
  return null;
@@ -274,6 +396,49 @@
274
396
  }
275
397
  }
276
398
 
399
+ /**
400
+ * A link the platform marked with a library path opens that file or folder in the console, since inside this frame
401
+ * its address names nothing. A Ctrl, Cmd or Shift click, or a middle click, asks the console for a new tab. An Alt
402
+ * click, another button, or a click the page's own script handled goes to the browser.
403
+ *
404
+ * The platform marks every relative link that names a library file or folder the viewer may read, so an unmarked
405
+ * one to another path names nothing this host serves, and following it would blank the frame: a primary click on it
406
+ * does nothing. A link with a scheme, a `//` host, a fragment or a query alone goes to the browser.
407
+ */
408
+ function onLinkClick(event) {
409
+ // A middle click arrives as `auxclick` alone in current browsers, so `click` takes the primary button only.
410
+ var middle = event.type === "auxclick";
411
+ if (event.defaultPrevented || event.altKey || event.button !== (middle ? 1 : 0)) {
412
+ return;
413
+ }
414
+ var link = event.target && event.target.closest ? event.target.closest("a") : null;
415
+ if (!link) {
416
+ return;
417
+ }
418
+ var path = link.getAttribute("data-sf-path");
419
+ if (path === null) {
420
+ if (!middle && leavesThePagePath(link.getAttribute("href"))) {
421
+ event.preventDefault();
422
+ }
423
+ return;
424
+ }
425
+ event.preventDefault();
426
+ window.parent.postMessage(
427
+ {
428
+ sf: 1,
429
+ event: "open",
430
+ path: path,
431
+ newTab: middle || event.metaKey || event.ctrlKey || event.shiftKey
432
+ },
433
+ consoleOrigin
434
+ );
435
+ }
436
+
437
+ function leavesThePagePath(href) {
438
+ var reference = href === null ? "" : href.trim();
439
+ return reference !== "" && !/^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|\/\/|[#?])/.test(reference);
440
+ }
441
+
277
442
  function onSelectionChange() {
278
443
  if (!overlay || readOnly) {
279
444
  return;
@@ -283,8 +448,9 @@
283
448
  overlay.selectionToolbar.hidden = true;
284
449
  return;
285
450
  }
286
- var box = selection.getRangeAt(0).getBoundingClientRect();
287
- if (box.width === 0 && box.height === 0) {
451
+ var range = selection.getRangeAt(0);
452
+ var box = range.getBoundingClientRect();
453
+ if ((box.width === 0 && box.height === 0) || !selectionHasWords(range)) {
288
454
  overlay.selectionToolbar.hidden = true;
289
455
  return;
290
456
  }
@@ -307,57 +473,164 @@
307
473
  function refreshComments() {
308
474
  return call("comments.list", {}).then(function (result) {
309
475
  comments = (result && result.comments) || [];
476
+ shownVersion = result && typeof result.versionNumber === "number" ? result.versionNumber : null;
310
477
  drawPins();
311
478
  return comments;
312
479
  });
313
480
  }
314
481
 
482
+ function requestDraw() {
483
+ if (redrawTimer !== null) {
484
+ return;
485
+ }
486
+ redrawTimer = setTimeout(drawPins, Math.max(0, lastDraw + REDRAW_INTERVAL_MS - Date.now()));
487
+ }
488
+
315
489
  function drawPins() {
316
490
  if (!overlay) {
317
491
  return;
318
492
  }
319
- overlay.pins.textContent = "";
493
+ if (redrawTimer !== null) {
494
+ clearTimeout(redrawTimer);
495
+ redrawTimer = null;
496
+ }
497
+ lastDraw = Date.now();
498
+ if (document.body && !overlay.host.isConnected) {
499
+ document.body.appendChild(overlay.host);
500
+ }
501
+ var texts = null;
502
+ var textsOnce = function () {
503
+ texts = texts || pageTexts();
504
+ return texts;
505
+ };
506
+ var kept = {};
507
+ var lost = [];
508
+ var changed = [];
320
509
  comments.forEach(function (comment, index) {
321
- var position = positionOf(comment.anchor);
510
+ var position = positionOf(comment, textsOnce);
322
511
  if (!position) {
512
+ if (comment.anchor && (comment.anchor.kind === "selector" || comment.anchor.kind === "text")) {
513
+ lost.push(comment.id);
514
+ }
323
515
  return;
324
516
  }
325
- var pin = document.createElement("button");
326
- pin.className = "pin";
327
- pin.type = "button";
517
+ if (position.changed) {
518
+ changed.push(comment.id);
519
+ }
520
+ // A pin still drawn keeps its button, so a press and its release on a changing page reach one button.
521
+ var pin = drawn[comment.id] ? drawn[comment.id].button : newPin(comment.id);
522
+ kept[comment.id] = { button: pin, comment: comment };
328
523
  pin.textContent = String(index + 1);
329
524
  pin.setAttribute("data-status", comment.status || "OPEN");
330
525
  pin.style.left = position.left + "px";
331
526
  pin.style.top = position.top + "px";
332
- pin.addEventListener("click", function () {
333
- openPanel(comment);
334
- });
335
- overlay.pins.appendChild(pin);
336
527
  });
528
+ Object.keys(drawn).forEach(function (commentId) {
529
+ if (!kept[commentId]) {
530
+ drawn[commentId].button.remove();
531
+ }
532
+ });
533
+ drawn = kept;
534
+ report(lost, changed);
337
535
  }
338
536
 
339
- /** Document coordinates, so a scroll needs no repositioning; only a resize does. */
340
- function positionOf(anchor) {
537
+ function newPin(commentId) {
538
+ var pin = document.createElement("button");
539
+ pin.className = "pin";
540
+ pin.type = "button";
541
+ pin.addEventListener("click", function () {
542
+ if (drawn[commentId]) {
543
+ openPanel(drawn[commentId].comment);
544
+ }
545
+ });
546
+ overlay.pins.appendChild(pin);
547
+ return pin;
548
+ }
549
+
550
+ function report(lost, changed) {
551
+ var message = { sf: 1, event: "pins", lost: lost, changed: changed };
552
+ var said = JSON.stringify(message);
553
+ if (said === lastReport) {
554
+ return;
555
+ }
556
+ lastReport = said;
557
+ window.parent.postMessage(message, consoleOrigin);
558
+ }
559
+
560
+ function fromAnotherVersion(comment) {
561
+ return (
562
+ typeof comment.versionNumber === "number" &&
563
+ typeof shownVersion === "number" &&
564
+ comment.versionNumber !== shownVersion
565
+ );
566
+ }
567
+
568
+ /** Document coordinates, so a scroll needs no repositioning. */
569
+ function positionOf(comment, textsOnce) {
570
+ var anchor = comment.anchor;
341
571
  if (!anchor) {
342
572
  return null;
343
573
  }
344
574
  if (anchor.kind === "selector") {
345
- var element = queryFirst(anchor.path);
346
- if (!element) {
575
+ var found = fromAnotherVersion(comment)
576
+ ? elementElsewhere(anchor, textsOnce())
577
+ : { element: queryFirst(anchor.path), changed: false };
578
+ if (!found || !shownWithBox(found.element)) {
347
579
  return null;
348
580
  }
349
- var box = element.getBoundingClientRect();
581
+ var box = found.element.getBoundingClientRect();
350
582
  return {
351
583
  left: box.left + window.scrollX + (box.width * (anchor.x || 0)) / 100,
352
- top: box.top + window.scrollY + (box.height * (anchor.y || 0)) / 100
584
+ top: box.top + window.scrollY + (box.height * (anchor.y || 0)) / 100,
585
+ changed: found.changed
353
586
  };
354
587
  }
355
588
  if (anchor.kind === "text") {
356
- return positionOfQuote(anchor);
589
+ var match = rangeOfAnchor(anchor, fromAnotherVersion(comment), textsOnce());
590
+ if (!match) {
591
+ return null;
592
+ }
593
+ var quoted = match.range.getBoundingClientRect();
594
+ return {
595
+ left: quoted.left + window.scrollX,
596
+ top: quoted.top + window.scrollY,
597
+ changed: match.changed
598
+ };
357
599
  }
358
600
  return null;
359
601
  }
360
602
 
603
+ function shownWithBox(element) {
604
+ return isShown(element) && hasArea(element.getBoundingClientRect());
605
+ }
606
+
607
+ function elementElsewhere(anchor, texts) {
608
+ var carriers = anchor.elementId ? elementsCarrying(anchor.elementId) : [];
609
+ if (
610
+ carriers.length === 1 &&
611
+ shownWithBox(carriers[0]) &&
612
+ (!anchor.quote || visibleTextOf(carriers[0], texts.visible).indexOf(anchor.quote) >= 0)
613
+ ) {
614
+ return { element: carriers[0], changed: false };
615
+ }
616
+ var match = rangeOfAnchor(anchor, true, texts);
617
+ if (!match) {
618
+ return null;
619
+ }
620
+ var holder = match.range.commonAncestorContainer;
621
+ return { element: holder.nodeType === 1 ? holder : holder.parentElement, changed: match.changed };
622
+ }
623
+
624
+ function visibleTextOf(element, visible) {
625
+ var text = "";
626
+ visible.nodes.forEach(function (entry) {
627
+ if (element.contains(entry.node)) {
628
+ text += visible.text.slice(entry.from, entry.to);
629
+ }
630
+ });
631
+ return text;
632
+ }
633
+
361
634
  function queryFirst(path) {
362
635
  if (!path) {
363
636
  return null;
@@ -370,70 +643,98 @@
370
643
  }
371
644
  }
372
645
 
373
- function positionOfQuote(anchor) {
374
- var range = rangeOfAnchor(anchor);
375
- if (!range) {
646
+ function rangeOfAnchor(anchor, anotherVersion, texts) {
647
+ if (!anchor || !anchor.quote) {
376
648
  return null;
377
649
  }
378
- var box = range.getBoundingClientRect();
379
- return { left: box.left + window.scrollX, top: box.top + window.scrollY };
650
+ var length = anchor.quote.length;
651
+ if (anotherVersion) {
652
+ var whole = length + (anchor.prefix || "").length + (anchor.suffix || "").length;
653
+ var floor = Math.min(AGREEMENT_FLOOR, whole);
654
+ var ranked = rankedOccurrences(texts.visible.text, anchor);
655
+ for (var index = 0; index < ranked.length && length + ranked[index].agreed >= floor; index += 1) {
656
+ var candidate = spanOf(texts.visible, ranked[index].at, length);
657
+ if (candidate && hasArea(candidate.range.getBoundingClientRect())) {
658
+ return { range: candidate.range, changed: false };
659
+ }
660
+ }
661
+ return betweenContext(texts.visible, anchor);
662
+ }
663
+ var strand = texts.all;
664
+ var best = rankedOccurrences(strand.text, anchor)[0];
665
+ // A quote missing from all the text came from a selection across hidden text, which only the visible
666
+ // text holds.
667
+ if (!best) {
668
+ strand = texts.visible;
669
+ best = rankedOccurrences(strand.text, anchor)[0];
670
+ }
671
+ if (!best) {
672
+ return betweenContext(texts.visible, anchor);
673
+ }
674
+ var span = spanOf(strand, best.at, length);
675
+ if (!span || !span.first.shown || !span.last.shown) {
676
+ return null;
677
+ }
678
+ return hasArea(span.range.getBoundingClientRect()) ? { range: span.range, changed: false } : null;
380
679
  }
381
680
 
382
- /**
383
- * The anchored passage as a range over the page, found across whatever elements it runs through: a
384
- * selection picks up formatting on the way, so "Hello world" can be two text nodes with a <strong>
385
- * between them and no single node holds the whole quote. The walk concatenates the text in reading order
386
- * and maps the match back onto the nodes it spans.
387
- */
388
- function rangeOfAnchor(anchor) {
389
- if (!anchor || !anchor.quote || !document.body) {
681
+ function betweenContext(strand, anchor) {
682
+ var prefix = anchor.prefix || "";
683
+ var suffix = anchor.suffix || "";
684
+ if (wordLength(prefix) < EDIT_CONTEXT_FLOOR || wordLength(suffix) < EDIT_CONTEXT_FLOOR) {
390
685
  return null;
391
686
  }
392
- var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
393
- var nodes = [];
394
- var text = "";
395
- while (walker.nextNode()) {
396
- nodes.push({ node: walker.currentNode, from: text.length });
397
- text += walker.currentNode.nodeValue || "";
398
- }
399
- var at = occurrenceOf(text, anchor);
400
- if (at < 0) {
401
- return null;
687
+ var text = strand.text;
688
+ for (var at = text.indexOf(prefix); at >= 0; at = text.indexOf(prefix, at + 1)) {
689
+ var start = at + prefix.length;
690
+ var end = text.slice(start, start + EDIT_SPAN_LIMIT + suffix.length).indexOf(suffix);
691
+ if (end < 0) {
692
+ continue;
693
+ }
694
+ var between = text.slice(start, start + end);
695
+ var trimmed = between.trim();
696
+ if (trimmed === "") {
697
+ continue;
698
+ }
699
+ var lead = between.length - between.replace(/^\s+/, "").length;
700
+ var span = spanOf(strand, start + lead, trimmed.length);
701
+ if (span && hasArea(span.range.getBoundingClientRect())) {
702
+ return { range: span.range, changed: true };
703
+ }
402
704
  }
403
- var end = at + anchor.quote.length;
705
+ return null;
706
+ }
707
+
708
+ function wordLength(value) {
709
+ return value.replace(/\s/g, "").length;
710
+ }
711
+
712
+ function spanOf(strand, at, length) {
713
+ var end = at + length;
404
714
  var range = document.createRange();
405
- var started = false;
406
- for (var index = 0; index < nodes.length; index += 1) {
407
- var entry = nodes[index];
408
- var to = entry.from + (entry.node.nodeValue || "").length;
409
- if (!started && at < to) {
715
+ var first = null;
716
+ for (var index = 0; index < strand.nodes.length; index += 1) {
717
+ var entry = strand.nodes[index];
718
+ if (first === null && at < entry.to) {
410
719
  range.setStart(entry.node, at - entry.from);
411
- started = true;
720
+ first = entry;
412
721
  }
413
- if (started && end <= to) {
722
+ if (first !== null && end <= entry.to) {
414
723
  range.setEnd(entry.node, end - entry.from);
415
- return range;
724
+ return { range: range, first: first, last: entry };
416
725
  }
417
726
  }
418
727
  return null;
419
728
  }
420
729
 
421
- /**
422
- * Which occurrence of the quote this anchor meant: the one whose neighbouring text agrees with the
423
- * recorded context for the most characters, and the earliest of those on a tie — which is also the answer
424
- * for an anchor carrying no context, and for a page that has changed until no context matches.
425
- */
426
- function occurrenceOf(text, anchor) {
427
- var best = -1;
428
- var bestAgreement = -1;
730
+ function rankedOccurrences(text, anchor) {
731
+ var found = [];
429
732
  for (var at = text.indexOf(anchor.quote); at >= 0; at = text.indexOf(anchor.quote, at + 1)) {
430
- var agreed = agreement(text, at, anchor);
431
- if (agreed > bestAgreement) {
432
- best = at;
433
- bestAgreement = agreed;
434
- }
733
+ found.push({ at: at, agreed: agreement(text, at, anchor) });
435
734
  }
436
- return best;
735
+ return found.sort(function (one, other) {
736
+ return other.agreed - one.agreed || one.at - other.at;
737
+ });
437
738
  }
438
739
 
439
740
  /** How many characters of the anchor's context the text around this occurrence actually matches. */
@@ -514,13 +815,40 @@
514
815
  }
515
816
  }
516
817
 
818
+ function watchForChanges() {
819
+ window.addEventListener("resize", requestDraw);
820
+ window.addEventListener("load", requestDraw);
821
+ if (typeof ResizeObserver === "function") {
822
+ new ResizeObserver(requestDraw).observe(document.documentElement);
823
+ }
824
+ if (typeof MutationObserver === "function") {
825
+ // Without a shadow root the pins live in the host; their changes must not ask for another draw.
826
+ new MutationObserver(function (records) {
827
+ for (var index = 0; index < records.length; index += 1) {
828
+ if (!overlay.host.contains(records[index].target)) {
829
+ requestDraw();
830
+ return;
831
+ }
832
+ }
833
+ }).observe(document.documentElement, {
834
+ childList: true,
835
+ subtree: true,
836
+ characterData: true,
837
+ attributes: true
838
+ });
839
+ }
840
+ }
841
+
517
842
  function startOverlay() {
518
843
  overlay = buildOverlay();
519
844
  overlay.selectionButton.addEventListener("click", onSelectionComment);
520
845
  document.addEventListener("click", onDocumentClick, true);
846
+ // In the bubbling phase, so a click comment mode takes in the capturing phase never opens a link.
847
+ document.addEventListener("click", onLinkClick);
848
+ document.addEventListener("auxclick", onLinkClick);
521
849
  document.addEventListener("keydown", onKeyDown);
522
850
  document.addEventListener("selectionchange", onSelectionChange);
523
- window.addEventListener("resize", drawPins);
851
+ watchForChanges();
524
852
  refreshComments();
525
853
  }
526
854
 
@@ -4,11 +4,6 @@ import type { ErrorContext } from "../core/errors.js";
4
4
  import { type CliIo } from "../core/output.js";
5
5
  import { type ResponseSchema } from "../core/redaction.js";
6
6
  import type { Profile } from "../core/profile.js";
7
- /**
8
- * The schema the reader and the redactor are both handed — `orchestra api` included, so the raw caller and the
9
- * typed command cannot disagree about which responses are the bare content.
10
- */
11
- export declare function responseSchemaOf(row: CommandRow | undefined): ResponseSchema | undefined;
12
7
  /** Named so the error mapper prints the sentence and nothing else — no platform framing, no stack. */
13
8
  export declare class CommandUsageError extends Error {
14
9
  constructor(message: string);
@@ -23,6 +18,8 @@ export interface ConventionValues {
23
18
  outputFile?: string;
24
19
  /** A local path whose bytes this create stores first and then names. */
25
20
  file?: string;
21
+ /** Asks for the content as Markdown, on a read whose response declares it. */
22
+ markdown?: boolean;
26
23
  /** An exact alias of level `never`. */
27
24
  approveSecrets?: boolean;
28
25
  requireApproval?: string;