@devaccel-labs/app-comments-toolbar 1.0.0 → 1.3.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 (220) hide show
  1. package/README.md +152 -18
  2. package/dist/react.js +516 -61
  3. package/dist/server.js +342 -14
  4. package/dist/types/capture/api.d.ts +30 -0
  5. package/dist/types/capture/editor.d.ts +10 -0
  6. package/dist/types/capture/ignore.d.ts +19 -0
  7. package/dist/types/capture/index.d.ts +1 -0
  8. package/dist/types/capture/picker.d.ts +9 -0
  9. package/dist/types/capture/picture.d.ts +29 -0
  10. package/dist/types/capture/surface.d.ts +39 -0
  11. package/dist/types/core/index.d.ts +2 -2
  12. package/dist/types/core/resolve.d.ts +2 -1
  13. package/dist/types/core/types.d.ts +11 -0
  14. package/dist/types/debug-context.d.ts +7 -0
  15. package/dist/types/dictation.d.ts +9 -0
  16. package/dist/types/embed/controller.d.ts +61 -0
  17. package/dist/types/embed/index.d.ts +1 -0
  18. package/dist/types/embed/options.d.ts +35 -0
  19. package/dist/types/help/index.d.ts +1 -0
  20. package/dist/types/help/panel-index.d.ts +1 -0
  21. package/dist/types/help/panel-styles.d.ts +1 -0
  22. package/dist/types/help/panel.d.ts +4 -0
  23. package/dist/types/help/pill-styles.d.ts +11 -0
  24. package/dist/types/help/pill.d.ts +44 -0
  25. package/dist/types/help/react.d.ts +12 -0
  26. package/dist/types/help/requests.d.ts +2 -0
  27. package/dist/types/help/types.d.ts +119 -0
  28. package/dist/types/help/wire.d.ts +61 -0
  29. package/dist/types/native-wire.d.ts +63 -0
  30. package/dist/types/native.d.ts +43 -0
  31. package/dist/types/react.d.ts +8 -1
  32. package/dist/types/replay.d.ts +6 -0
  33. package/dist/types/runtime/api.d.ts +250 -0
  34. package/dist/types/runtime/attachment-model.d.ts +16 -0
  35. package/dist/types/runtime/attachments.d.ts +43 -7
  36. package/dist/types/runtime/boot.d.ts +39 -0
  37. package/dist/types/runtime/browser-label.d.ts +5 -0
  38. package/dist/types/runtime/build-id.d.ts +7 -0
  39. package/dist/types/runtime/capture-ui.d.ts +45 -21
  40. package/dist/types/runtime/clarity-model.d.ts +260 -0
  41. package/dist/types/runtime/clarity-ui.d.ts +62 -0
  42. package/dist/types/runtime/client.d.ts +6 -197
  43. package/dist/types/runtime/composer-size.d.ts +30 -0
  44. package/dist/types/runtime/dashboard.d.ts +17 -2
  45. package/dist/types/runtime/debug-context-core.d.ts +88 -0
  46. package/dist/types/runtime/debug-context-host.d.ts +39 -0
  47. package/dist/types/runtime/debug-context-model.d.ts +352 -0
  48. package/dist/types/runtime/debug-context-redact.d.ts +48 -0
  49. package/dist/types/runtime/debug-context.d.ts +36 -0
  50. package/dist/types/runtime/details-model.d.ts +7 -0
  51. package/dist/types/runtime/details.d.ts +7 -5
  52. package/dist/types/runtime/dictation-engine.d.ts +98 -0
  53. package/dist/types/runtime/dictation-model.d.ts +28 -0
  54. package/dist/types/runtime/dictation-ui.d.ts +56 -0
  55. package/dist/types/runtime/dictation.d.ts +66 -0
  56. package/dist/types/runtime/draft-dock.d.ts +32 -0
  57. package/dist/types/runtime/draft-model.d.ts +104 -0
  58. package/dist/types/runtime/draft-store.d.ts +15 -0
  59. package/dist/types/runtime/edits.d.ts +1 -1
  60. package/dist/types/runtime/font.d.ts +16 -0
  61. package/dist/types/runtime/format.d.ts +30 -1
  62. package/dist/types/runtime/host-watch.d.ts +20 -0
  63. package/dist/types/runtime/icons.d.ts +16 -2
  64. package/dist/types/runtime/index.d.ts +3 -1
  65. package/dist/types/runtime/ink.d.ts +8 -0
  66. package/dist/types/runtime/kind-model.d.ts +14 -0
  67. package/dist/types/runtime/live-model.d.ts +25 -0
  68. package/dist/types/runtime/loader.d.ts +5 -11
  69. package/dist/types/runtime/markup-editor.d.ts +54 -0
  70. package/dist/types/runtime/markup-model.d.ts +126 -0
  71. package/dist/types/runtime/markup.d.ts +69 -0
  72. package/dist/types/runtime/mention-parse.d.ts +18 -0
  73. package/dist/types/runtime/mentions.d.ts +4 -4
  74. package/dist/types/runtime/page-background.d.ts +9 -0
  75. package/dist/types/runtime/page-image.d.ts +123 -0
  76. package/dist/types/runtime/placement.d.ts +6 -3
  77. package/dist/types/runtime/record.d.ts +18 -2
  78. package/dist/types/runtime/related.d.ts +1 -1
  79. package/dist/types/runtime/replay-model.d.ts +80 -0
  80. package/dist/types/runtime/replay.d.ts +5 -38
  81. package/dist/types/runtime/runtime.d.ts +6 -0
  82. package/dist/types/runtime/screen.d.ts +47 -0
  83. package/dist/types/runtime/screenshot.d.ts +60 -6
  84. package/dist/types/runtime/session-source.d.ts +45 -0
  85. package/dist/types/runtime/session.d.ts +20 -18
  86. package/dist/types/runtime/shield.d.ts +9 -0
  87. package/dist/types/runtime/shot.d.ts +45 -0
  88. package/dist/types/runtime/sketch.d.ts +26 -0
  89. package/dist/types/runtime/styles.d.ts +1 -1
  90. package/dist/types/runtime/sync.d.ts +1 -0
  91. package/dist/types/runtime/thread-model.d.ts +18 -0
  92. package/dist/types/runtime/ticket-form-model.d.ts +57 -0
  93. package/dist/types/runtime/ticket-form.d.ts +9 -0
  94. package/dist/types/runtime/toolbar.d.ts +37 -1
  95. package/dist/types/runtime/ui-scale.d.ts +17 -0
  96. package/dist/types/runtime/update.d.ts +37 -0
  97. package/dist/types/runtime/use-debug-context.d.ts +27 -0
  98. package/dist/types/server/index.d.ts +22 -0
  99. package/dist/types/server/support.d.ts +20 -0
  100. package/dist/types/server.d.ts +2 -0
  101. package/dist/types/vite/plugin.d.ts +15 -0
  102. package/dist/vite.js +121 -41
  103. package/package.json +26 -13
  104. package/src/capture/api.ts +128 -0
  105. package/src/capture/editor.tsx +178 -0
  106. package/src/capture/ignore.ts +38 -0
  107. package/src/capture/index.ts +27 -0
  108. package/src/capture/picker.tsx +171 -0
  109. package/src/capture/picture.ts +90 -0
  110. package/src/capture/surface.tsx +185 -0
  111. package/src/compiler/annotate.ts +202 -0
  112. package/src/compiler/babel.d.ts +11 -0
  113. package/src/compiler/enabled.ts +55 -0
  114. package/src/compiler/index.ts +2 -0
  115. package/src/core/index.ts +8 -0
  116. package/src/core/resolve.ts +307 -0
  117. package/src/core/types.ts +58 -0
  118. package/src/debug-context.ts +47 -0
  119. package/src/dictation.ts +17 -0
  120. package/src/embed/controller.ts +215 -0
  121. package/src/embed/index.ts +68 -0
  122. package/src/embed/options.ts +100 -0
  123. package/src/help/index.ts +38 -0
  124. package/src/help/panel-index.ts +17 -0
  125. package/src/help/panel-styles.ts +123 -0
  126. package/src/help/panel.ts +840 -0
  127. package/src/help/pill-styles.ts +75 -0
  128. package/src/help/pill.ts +338 -0
  129. package/src/help/react.tsx +143 -0
  130. package/src/help/requests.ts +111 -0
  131. package/src/help/types.ts +124 -0
  132. package/src/help/wire.ts +55 -0
  133. package/src/native-wire.ts +124 -0
  134. package/src/native.ts +282 -0
  135. package/src/react.ts +27 -0
  136. package/src/replay.ts +13 -0
  137. package/src/runtime/api.ts +531 -0
  138. package/src/runtime/attachment-model.ts +42 -0
  139. package/src/runtime/attachments.tsx +1260 -0
  140. package/src/runtime/boot.ts +95 -0
  141. package/src/runtime/browser-label.ts +31 -0
  142. package/src/runtime/build-id.ts +10 -0
  143. package/src/runtime/capture-ui.tsx +306 -0
  144. package/src/runtime/capture.ts +508 -0
  145. package/src/runtime/clarity-model.ts +634 -0
  146. package/src/runtime/clarity-ui.tsx +454 -0
  147. package/src/runtime/client.ts +151 -0
  148. package/src/runtime/composer-size.tsx +146 -0
  149. package/src/runtime/dashboard.tsx +1130 -0
  150. package/src/runtime/debug-context-core.ts +681 -0
  151. package/src/runtime/debug-context-host.ts +56 -0
  152. package/src/runtime/debug-context-model.ts +1319 -0
  153. package/src/runtime/debug-context-redact.ts +259 -0
  154. package/src/runtime/debug-context.ts +613 -0
  155. package/src/runtime/details-model.ts +30 -0
  156. package/src/runtime/details.tsx +395 -0
  157. package/src/runtime/dictation-engine.ts +616 -0
  158. package/src/runtime/dictation-model.ts +79 -0
  159. package/src/runtime/dictation-ui.tsx +363 -0
  160. package/src/runtime/dictation.ts +271 -0
  161. package/src/runtime/dock.ts +108 -0
  162. package/src/runtime/draft-dock.tsx +124 -0
  163. package/src/runtime/draft-model.ts +251 -0
  164. package/src/runtime/draft-store.ts +149 -0
  165. package/src/runtime/edits.tsx +165 -0
  166. package/src/runtime/font.ts +42 -0
  167. package/src/runtime/format.ts +129 -0
  168. package/src/runtime/freshness.ts +23 -0
  169. package/src/runtime/handoff.ts +76 -0
  170. package/src/runtime/host-watch.ts +64 -0
  171. package/src/runtime/icons.tsx +295 -0
  172. package/src/runtime/idle.ts +20 -0
  173. package/src/runtime/index.ts +11 -0
  174. package/src/runtime/ink.ts +6 -0
  175. package/src/runtime/kind-model.ts +20 -0
  176. package/src/runtime/live-model.ts +32 -0
  177. package/src/runtime/loader.tsx +87 -0
  178. package/src/runtime/markup-editor.tsx +1333 -0
  179. package/src/runtime/markup-model.ts +485 -0
  180. package/src/runtime/markup.ts +382 -0
  181. package/src/runtime/members.ts +58 -0
  182. package/src/runtime/mention-parse.ts +56 -0
  183. package/src/runtime/mentions.tsx +205 -0
  184. package/src/runtime/page-background.ts +51 -0
  185. package/src/runtime/page-image.ts +1592 -0
  186. package/src/runtime/placement.ts +336 -0
  187. package/src/runtime/record.ts +402 -0
  188. package/src/runtime/redact.ts +30 -0
  189. package/src/runtime/related.ts +34 -0
  190. package/src/runtime/render-page.ts +2 -0
  191. package/src/runtime/replay-model.ts +232 -0
  192. package/src/runtime/replay.ts +173 -0
  193. package/src/runtime/runtime-version.ts +7 -0
  194. package/src/runtime/runtime.tsx +51 -0
  195. package/src/runtime/screen.ts +182 -0
  196. package/src/runtime/screenshot.ts +260 -0
  197. package/src/runtime/session-source.ts +112 -0
  198. package/src/runtime/session.ts +108 -0
  199. package/src/runtime/shield.ts +34 -0
  200. package/src/runtime/shot.tsx +422 -0
  201. package/src/runtime/sketch.ts +139 -0
  202. package/src/runtime/styles.ts +1541 -0
  203. package/src/runtime/sync.ts +300 -0
  204. package/src/runtime/thread-model.ts +71 -0
  205. package/src/runtime/ticket-form-model.ts +128 -0
  206. package/src/runtime/ticket-form.tsx +342 -0
  207. package/src/runtime/toolbar.tsx +4427 -0
  208. package/src/runtime/ui-scale.ts +75 -0
  209. package/src/runtime/update.ts +98 -0
  210. package/src/runtime/use-debug-context.ts +81 -0
  211. package/src/server/index.ts +133 -0
  212. package/src/server/support.ts +499 -0
  213. package/src/server.ts +4 -0
  214. package/src/vite/plugin.ts +102 -0
  215. package/src/vite.ts +1 -0
  216. package/dist/chunks/chunk-CSKXLF33.js +0 -153
  217. package/dist/chunks/chunk-HUM5FQII.js +0 -160
  218. package/dist/chunks/render-page-76MLIT6M.js +0 -1421
  219. package/dist/chunks/runtime-JX75TKER.js +0 -9267
  220. package/dist/chunks/screenshot-66ARTBGR.js +0 -12
package/dist/server.js CHANGED
@@ -1,3 +1,34 @@
1
+ // src/native-wire.ts
2
+ var APP_ORIGIN_HEADER = "x-comment-service-origin";
3
+ var SCHEME = /^[a-z][a-z0-9+.-]{1,39}$/;
4
+ var RESERVED = /* @__PURE__ */ new Set([
5
+ "http",
6
+ "https",
7
+ "ws",
8
+ "wss",
9
+ "file",
10
+ "data",
11
+ "blob",
12
+ "javascript",
13
+ "vbscript",
14
+ "about",
15
+ "mailto",
16
+ "tel",
17
+ "sms",
18
+ "intent",
19
+ "content",
20
+ "chrome",
21
+ "chrome-extension"
22
+ ]);
23
+ function isAppScheme(value) {
24
+ return SCHEME.test(value) && !RESERVED.has(value);
25
+ }
26
+ function appOriginScheme(value) {
27
+ const match = /^([a-z][a-z0-9+.-]{1,39}):\/\/app$/.exec(value);
28
+ const scheme = match?.[1];
29
+ return scheme && isAppScheme(scheme) ? scheme : null;
30
+ }
31
+
1
32
  // src/server/index.ts
2
33
  function readCommentServiceConfig(env = process.env) {
3
34
  const url = env.COMMENT_SERVICE_URL?.replace(/\/$/, "");
@@ -8,21 +39,13 @@ function readCommentServiceConfig(env = process.env) {
8
39
  const linked = (env.COMMENT_SERVICE_LINKED ?? "").split(/[\s,]+/).filter(Boolean);
9
40
  return { url, project, environment, secret, linked };
10
41
  }
11
- function declaredOrigin(request) {
12
- const value = request.headers.get("origin") ?? request.headers.get("x-comment-service-origin");
13
- if (!value) return null;
14
- try {
15
- const origin = new URL(value).origin;
16
- return origin === "null" ? null : origin;
17
- } catch {
18
- return null;
42
+ function requestedOrigin(request) {
43
+ const declared = request.headers.get(APP_ORIGIN_HEADER);
44
+ if (request.headers.get("origin") === null && declared !== null && appOriginScheme(declared) !== null) {
45
+ return declared;
19
46
  }
20
- }
21
- function requestOrigin(request) {
22
- const declared = declaredOrigin(request);
23
- if (declared) return declared;
24
47
  const url = new URL(request.url);
25
- const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
48
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? url.host;
26
49
  if (!host || host.includes("/")) return url.origin;
27
50
  const proto = (request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")).split(",")[0].trim();
28
51
  return `${proto}://${host}`;
@@ -30,7 +53,7 @@ function requestOrigin(request) {
30
53
  async function handleSessionRequest(request, config = readCommentServiceConfig()) {
31
54
  const headers = { "cache-control": "no-store" };
32
55
  if (!config) return Response.json({ enabled: false }, { headers });
33
- const origin = requestOrigin(request);
56
+ const origin = requestedOrigin(request);
34
57
  let response;
35
58
  try {
36
59
  response = await fetch(`${config.url}/v1/host/session`, {
@@ -81,7 +104,312 @@ async function handleSessionRequest(request, config = readCommentServiceConfig()
81
104
  { headers }
82
105
  );
83
106
  }
107
+
108
+ // src/help/wire.ts
109
+ var HELP_LIMITS = {
110
+ /** A request's own id: letters, digits, `.`, `_`, `:` and `-`. */
111
+ id: 100,
112
+ message: 4e3,
113
+ images: 3,
114
+ /** One picture, before base64. */
115
+ imageBytes: 5 * 1024 * 1024,
116
+ name: 200,
117
+ label: 200,
118
+ url: 1e3,
119
+ /** "Your requests" shows at most this many. */
120
+ listed: 50
121
+ };
122
+ var HELP_BODY_BYTES = Math.ceil(HELP_LIMITS.images * HELP_LIMITS.imageBytes * 4 / 3) + 256 * 1024;
123
+ var HELP_ID = /^[A-Za-z0-9._:-]+$/;
124
+ var HELP_IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp"];
125
+
126
+ // src/server/support.ts
127
+ var HEADERS = { "cache-control": "no-store", "x-content-type-options": "nosniff" };
128
+ var UNAVAILABLE = "Support requests are not available right now. Try again in a moment.";
129
+ var TOO_MANY = "Too many requests right now. Try again in a few minutes.";
130
+ var NOT_ACCEPTED = "The request could not be accepted. Check it and try again.";
131
+ var UNREADABLE = "The request could not be read.";
132
+ var TOO_LARGE = "That is too large to send. Remove a picture and try again.";
133
+ var NOT_A_PICTURE = "Only pictures can be attached: PNG, JPEG or WebP.";
134
+ var PICTURE_TOO_LARGE = "A picture is too large. Pictures can be up to 5 MB.";
135
+ var SERVICE = { name: 80, accountName: 120, id: 200, fieldValue: 200 };
136
+ var Refusal = class extends Error {
137
+ constructor(status, message) {
138
+ super(message);
139
+ this.status = status;
140
+ }
141
+ status;
142
+ };
143
+ function answer(body, status = 200, extra = {}) {
144
+ return Response.json(body, { status, headers: { ...HEADERS, ...extra } });
145
+ }
146
+ function failure(status, error, extra) {
147
+ const body = { error };
148
+ return answer(body, status, extra);
149
+ }
150
+ function storable(value) {
151
+ return value.replaceAll("\0", "");
152
+ }
153
+ function oneLine(value, limit) {
154
+ return typeof value === "string" ? storable(value).replace(/\s+/g, " ").trim().slice(0, limit) : "";
155
+ }
156
+ function isId(value) {
157
+ return typeof value === "string" && value.length > 0 && value.length <= SERVICE.id && !/\s/.test(value) && !value.includes("\0");
158
+ }
159
+ function note(op, reason) {
160
+ console.warn(`[comment-service] ${op} was refused: ${reason}.`);
161
+ }
162
+ async function bodyWithin(request, limit) {
163
+ if (Number(request.headers.get("content-length") ?? "0") > limit) return null;
164
+ if (!request.body) return new Uint8Array();
165
+ const reader = request.body.getReader();
166
+ const chunks = [];
167
+ let size = 0;
168
+ for (; ; ) {
169
+ const { done, value } = await reader.read();
170
+ if (done) break;
171
+ size += value.byteLength;
172
+ if (size > limit) {
173
+ await reader.cancel().catch(() => {
174
+ });
175
+ return null;
176
+ }
177
+ chunks.push(value);
178
+ }
179
+ const bytes = new Uint8Array(size);
180
+ let offset = 0;
181
+ for (const chunk of chunks) {
182
+ bytes.set(chunk, offset);
183
+ offset += chunk.byteLength;
184
+ }
185
+ return bytes;
186
+ }
187
+ function sniff(head) {
188
+ const starts = (...bytes) => bytes.every((byte, index) => head[index] === byte);
189
+ if (starts(137, 80, 78, 71, 13, 10, 26, 10)) return "image/png";
190
+ if (starts(255, 216, 255)) return "image/jpeg";
191
+ if (starts(82, 73, 70, 70) && head[8] === 87 && head[9] === 69 && head[10] === 66 && head[11] === 80) {
192
+ return "image/webp";
193
+ }
194
+ return null;
195
+ }
196
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
197
+ function picture(given, index, taken) {
198
+ const image = typeof given === "object" && given !== null ? given : {};
199
+ const data = image["data"];
200
+ if (typeof data !== "string" || data.length === 0 || data.length % 4 !== 0) {
201
+ throw new Refusal(400, NOT_A_PICTURE);
202
+ }
203
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
204
+ if (data.length / 4 * 3 - padding > HELP_LIMITS.imageBytes) {
205
+ throw new Refusal(413, PICTURE_TOO_LARGE);
206
+ }
207
+ if (!BASE64.test(data)) throw new Refusal(400, NOT_A_PICTURE);
208
+ const head = Uint8Array.from(atob(data.slice(0, 16)), (character) => character.charCodeAt(0));
209
+ const contentType = sniff(head);
210
+ if (!contentType || !HELP_IMAGE_TYPES.includes(contentType)) {
211
+ throw new Refusal(415, NOT_A_PICTURE);
212
+ }
213
+ const extension = contentType.slice("image/".length).replace("jpeg", "jpg");
214
+ const name = oneLine(image["name"], HELP_LIMITS.name) || `picture-${index + 1}.${extension}`;
215
+ const stem = name.replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 150) || `picture-${index + 1}`;
216
+ let id = stem;
217
+ for (let copy = 2; taken.has(id); copy += 1) id = `${stem}-${copy}`;
218
+ taken.add(id);
219
+ return { id, name, contentType, bytes: { $bytes: data } };
220
+ }
221
+ var OWNER_AS_IS = SERVICE.id - HELP_LIMITS.id - 1;
222
+ async function ownerOf(userId) {
223
+ if (userId.length <= OWNER_AS_IS && !userId.includes(":") && !userId.startsWith("~")) {
224
+ return userId;
225
+ }
226
+ const digest = new Uint8Array(
227
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(userId))
228
+ );
229
+ let binary = "";
230
+ for (const byte of digest) binary += String.fromCharCode(byte);
231
+ return `~${btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "")}`;
232
+ }
233
+ function reportIdOf(owner, id) {
234
+ return `${owner}:${id}`;
235
+ }
236
+ function reportOf(given, config, owner) {
237
+ if (typeof given !== "object" || given === null || Array.isArray(given)) {
238
+ throw new Refusal(400, UNREADABLE);
239
+ }
240
+ const sent = given;
241
+ const id = sent["id"];
242
+ if (typeof id !== "string" || id.length === 0 || id.length > HELP_LIMITS.id || !HELP_ID.test(id)) {
243
+ throw new Refusal(400, NOT_ACCEPTED);
244
+ }
245
+ const message = typeof sent["message"] === "string" ? storable(sent["message"]).trim() : "";
246
+ if (message.length === 0) throw new Refusal(400, "Write what you need help with.");
247
+ if (message.length > HELP_LIMITS.message) {
248
+ throw new Refusal(400, "That is too long. Shorten it to 4,000 characters.");
249
+ }
250
+ const images = sent["images"] ?? [];
251
+ if (!Array.isArray(images)) throw new Refusal(400, NOT_ACCEPTED);
252
+ if (images.length > HELP_LIMITS.images) {
253
+ throw new Refusal(400, `At most ${HELP_LIMITS.images} pictures can be sent.`);
254
+ }
255
+ const taken = /* @__PURE__ */ new Set();
256
+ const attachments = images.map((image, index) => picture(image, index, taken));
257
+ const fields = [];
258
+ const field = (label, value) => {
259
+ if (value) fields.push({ label, value: value.slice(0, SERVICE.fieldValue) });
260
+ };
261
+ const pointed = sent["pointed"];
262
+ if (typeof pointed === "object" && pointed !== null) {
263
+ field("Pointed at", oneLine(pointed.label, HELP_LIMITS.label));
264
+ }
265
+ const client = typeof sent["client"] === "object" && sent["client"] !== null ? sent["client"] : {};
266
+ const viewport = typeof client.viewport === "object" && client.viewport !== null ? client.viewport : {};
267
+ field("Browser", oneLine(client.userAgent, SERVICE.fieldValue));
268
+ if (Number.isFinite(viewport.width) && Number.isFinite(viewport.height)) {
269
+ field(
270
+ "Window",
271
+ `${Math.round(Number(viewport.width))} \xD7 ${Math.round(Number(viewport.height))}`
272
+ );
273
+ }
274
+ field("Language", oneLine(client.language, 40));
275
+ let pageUrl;
276
+ const page = sent["pageUrl"];
277
+ if (typeof page === "string" && page.length <= HELP_LIMITS.url) {
278
+ try {
279
+ const url = new URL(page);
280
+ if (url.protocol === "http:" || url.protocol === "https:") pageUrl = url.href;
281
+ } catch {
282
+ }
283
+ }
284
+ if (pageUrl && pageUrl.length > HELP_LIMITS.url) pageUrl = void 0;
285
+ const name = oneLine(config.reporter.name, SERVICE.name) || "Someone";
286
+ const account = config.account ? { id: config.account.id, name: oneLine(config.account.name, SERVICE.accountName) } : void 0;
287
+ return {
288
+ reportId: reportIdOf(owner, id),
289
+ reporter: { userId: config.reporter.userId, name },
290
+ ...account?.name ? { account } : {},
291
+ ...pageUrl ? { pageUrl } : {},
292
+ ...fields.length > 0 ? { fields } : {},
293
+ messages: [
294
+ {
295
+ id,
296
+ author: name,
297
+ role: "reporter",
298
+ body: message,
299
+ ...attachments.length > 0 ? { attachments } : {}
300
+ }
301
+ ]
302
+ };
303
+ }
304
+ async function callOp(config, op, input) {
305
+ const send = config.fetch ?? fetch;
306
+ let response;
307
+ try {
308
+ response = await send(`${config.serviceUrl.replace(/\/+$/, "")}/api/v1/ops/${op}`, {
309
+ method: "POST",
310
+ headers: {
311
+ authorization: `Bearer ${config.connectionToken}`,
312
+ "content-type": "application/json"
313
+ },
314
+ body: JSON.stringify({ project: config.project, input })
315
+ });
316
+ } catch {
317
+ note(op, "the service could not be reached");
318
+ throw new Refusal(502, UNAVAILABLE);
319
+ }
320
+ const payload = await response.json().catch(() => null);
321
+ if (!response.ok) {
322
+ note(
323
+ op,
324
+ `HTTP ${response.status}${typeof payload?.error?.code === "string" ? ` ${payload.error.code}` : ""}`
325
+ );
326
+ if (response.status === 413) throw new Refusal(413, TOO_LARGE);
327
+ if (response.status === 429) throw new Refusal(429, TOO_MANY);
328
+ throw new Refusal(502, UNAVAILABLE);
329
+ }
330
+ if (payload?.ok === true) return payload.value;
331
+ const reason = typeof payload?.reason === "string" ? payload.reason : "an answer that could not be read";
332
+ note(op, reason);
333
+ if (reason === "invalid_input") throw new Refusal(400, NOT_ACCEPTED);
334
+ if (reason === "rate_limited") throw new Refusal(429, TOO_MANY);
335
+ throw new Refusal(502, UNAVAILABLE);
336
+ }
337
+ async function file(request, config) {
338
+ if (!/^application\/json\b/i.test(request.headers.get("content-type") ?? "")) {
339
+ return failure(415, UNREADABLE);
340
+ }
341
+ const body = await bodyWithin(request, HELP_BODY_BYTES);
342
+ if (body === null) return failure(413, TOO_LARGE);
343
+ let sent;
344
+ try {
345
+ sent = JSON.parse(new TextDecoder().decode(body));
346
+ } catch {
347
+ return failure(400, UNREADABLE);
348
+ }
349
+ const report = reportOf(sent, config, await ownerOf(config.reporter.userId));
350
+ const filed = await callOp(config, "fileReport", report);
351
+ const refused = Array.isArray(filed?.refused) ? filed.refused.length : 0;
352
+ const receipt = refused === 0 ? {} : {
353
+ note: refused === 1 ? "1 picture could not be kept." : `${refused} pictures could not be kept.`
354
+ };
355
+ return answer(receipt);
356
+ }
357
+ function standingOf(report) {
358
+ const into = report.consolidated;
359
+ return typeof into === "object" && into !== null ? { stands: into, consolidated: true } : { stands: report, consolidated: false };
360
+ }
361
+ function statusOf(report) {
362
+ if (report.status === "resolved") return "resolved";
363
+ const ticket = report.ticket?.status;
364
+ return typeof ticket === "string" && ticket !== "" && ticket !== "new" ? "in_progress" : "received";
365
+ }
366
+ async function list(config) {
367
+ const mine = reportIdOf(await ownerOf(config.reporter.userId), "");
368
+ const read = await callOp(config, "reportsBy", {
369
+ reporterUserId: config.reporter.userId,
370
+ limit: HELP_LIMITS.listed
371
+ });
372
+ const requests = (Array.isArray(read?.reports) ? read.reports : []).filter(
373
+ (report) => typeof report?.reportId === "string" && typeof report.createdAt === "string"
374
+ ).map((report) => {
375
+ const reportId = report.reportId;
376
+ const { stands, consolidated } = standingOf(report);
377
+ const label = oneLine(stands.ticket?.statusLabel, 80);
378
+ return {
379
+ id: reportId.startsWith(mine) && reportId.length > mine.length ? reportId.slice(mine.length) : reportId,
380
+ ...Number.isInteger(report.ticket?.displaySeq) ? { reference: String(report.ticket?.displaySeq) } : {},
381
+ summary: oneLine(report.firstWords, 200) || oneLine(report.title, 200),
382
+ createdAt: report.createdAt,
383
+ status: statusOf(stands),
384
+ ...label ? { statusLabel: label } : {},
385
+ ...consolidated ? { consolidated: true } : {}
386
+ };
387
+ });
388
+ return answer({ requests });
389
+ }
390
+ async function handleSupportRequest(request, config) {
391
+ if (!isId(config?.reporter?.userId) || !config.serviceUrl || !config.connectionToken || !config.project || config.account !== void 0 && !isId(config.account?.id)) {
392
+ console.error(
393
+ "[comment-service] handleSupportRequest needs serviceUrl, connectionToken, project, reporter.userId and, when an account is given, its id."
394
+ );
395
+ return failure(500, UNAVAILABLE);
396
+ }
397
+ try {
398
+ switch (request.method) {
399
+ case "POST":
400
+ return await file(request, config);
401
+ case "GET":
402
+ return await list(config);
403
+ default:
404
+ return failure(405, "Use GET or POST.", { allow: "GET, POST" });
405
+ }
406
+ } catch (error) {
407
+ if (error instanceof Refusal) return failure(error.status, error.message);
408
+ throw error;
409
+ }
410
+ }
84
411
  export {
85
412
  handleSessionRequest,
413
+ handleSupportRequest,
86
414
  readCommentServiceConfig
87
415
  };
@@ -0,0 +1,30 @@
1
+ import { type CapturedImage } from "./picture.js";
2
+ import { type CaptureTheme } from "./surface.js";
3
+ export type { CapturedImage } from "./picture.js";
4
+ export type { CaptureTheme } from "./surface.js";
5
+ export type CaptureOptions = {
6
+ /** The look of the capture UI. `auto` (the default) follows the system's light or dark setting. */
7
+ theme?: CaptureTheme;
8
+ };
9
+ export type CapturedTarget = {
10
+ /** What a person would call it: the toolbar's own name for an anchor ("button › Save"). */
11
+ label: string;
12
+ /** The page's path. */
13
+ route: string;
14
+ /** The visible page with the element outlined, or null when the page could not be drawn. */
15
+ image: CapturedImage | null;
16
+ };
17
+ export type CommentToolCaptureApi = {
18
+ /**
19
+ * Takes a picture of the page as it is now and opens the toolbar's drawing editor over it. Resolves with
20
+ * the picture, drawn on or not, when the person presses Done; null when they close it. Rejects when the
21
+ * page could not be drawn.
22
+ */
23
+ screenshot(options?: CaptureOptions): Promise<CapturedImage | null>;
24
+ /**
25
+ * Lets the person point at one thing on the page, as the toolbar's "comment on this" does. Resolves with
26
+ * what they pointed at and a picture of the page with it outlined; null on Escape.
27
+ */
28
+ pick(options?: CaptureOptions): Promise<CapturedTarget | null>;
29
+ };
30
+ export declare function createCapture(): CommentToolCaptureApi;
@@ -0,0 +1,10 @@
1
+ import { type CapturedImage } from "./picture.js";
2
+ export type EditorProps = {
3
+ /** The picture to draw on, or null while the page is still being drawn. */
4
+ picture: CapturedImage | null;
5
+ /** Done: the picture as it now is. */
6
+ onDone: (image: CapturedImage) => void;
7
+ /** Cancel, or Escape with nothing drawn. */
8
+ onCancel: () => void;
9
+ };
10
+ export declare function Editor({ picture, onDone, onCancel }: EditorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What is not the page, to a capture: Comment Tool's own UI and the host's floating UI.
3
+ *
4
+ * The toolbar's root is `#cs-root`, and the toolbar leaves that id out of its pictures, keeps its presses and
5
+ * its focus from the page, and never offers it to point at. The capture script's root carries the same id on
6
+ * purpose (`./surface.tsx`), so every toolbar build treats it as its own, and this script treats the toolbar the
7
+ * same way: neither is in the other's pictures.
8
+ *
9
+ * A host marks its own floating UI (a help button, the panel that asked for the capture) with
10
+ * `data-cs-capture-ignore`. Only this script reads the attribute: the toolbar still pictures a marked element,
11
+ * because to a reviewer it is part of the app.
12
+ */
13
+ export declare const IGNORE_ATTRIBUTE = "data-cs-capture-ignore";
14
+ /** True for the root of a subtree a picture leaves out. */
15
+ export declare function leftOut(element: Element): boolean;
16
+ /** True when `target` is Comment Tool's UI or the host's marked UI, or inside either. */
17
+ export declare function notThePage(target: EventTarget | null): boolean;
18
+ /** The page's own element at a point of the viewport, as the toolbar's `elementAt` finds it. */
19
+ export declare function pageElementAt(x: number, y: number): Element | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export type PickerProps = {
2
+ /** The element the person clicked. */
3
+ onPick: (element: Element) => void;
4
+ /** Escape, or the hint's Cancel. */
5
+ onCancel: () => void;
6
+ };
7
+ export declare function Picker({ onPick, onCancel }: PickerProps): import("react/jsx-runtime").JSX.Element;
8
+ /** What stays on screen while the picture of a chosen element is drawn. */
9
+ export declare function Capturing(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { type PageShot } from "../runtime/screenshot.js";
2
+ /** What a host gets back for a picture. */
3
+ export type CapturedImage = {
4
+ blob: Blob;
5
+ name: string;
6
+ /** The picture's own size, in its pixels. */
7
+ width: number;
8
+ height: number;
9
+ /** True when the person drew on it. */
10
+ markedUp: boolean;
11
+ };
12
+ /**
13
+ * Copies the visible page now, synchronously, as the toolbar's `takeShot` does, with `highlight`'s box measured
14
+ * at the same moment. Comment Tool's UI and the host's marked UI are left out.
15
+ */
16
+ export declare function shootPage(highlight?: Element | null, timeout?: number): PageShot;
17
+ /**
18
+ * Draws `shot` into a file: the whole visible page as a JPEG, the framed element outlined when there is one.
19
+ * Null when the page could not be drawn.
20
+ */
21
+ export declare function pictureOf(shot: PageShot): Promise<CapturedImage | null>;
22
+ /** The size of an encoded picture, or `fallback` where the browser cannot decode it. */
23
+ export declare function sizeOf(blob: Blob, fallback: {
24
+ width: number;
25
+ height: number;
26
+ }): Promise<{
27
+ width: number;
28
+ height: number;
29
+ }>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Where the capture script draws its own UI: a root of the toolbar's kind, mounted for one capture and removed
3
+ * when it ends.
4
+ *
5
+ * The root is `<div id="cs-root">`, the toolbar's id, on purpose. The toolbar's stylesheet is written against
6
+ * that id, so the editor and the picker look as they do in the toolbar with no second copy of the CSS. And
7
+ * every toolbar build already treats that id as Comment Tool's own UI: it leaves it out of pictures and
8
+ * recordings drawn from the page, keeps its presses from the page, and does not read keys typed in it as page
9
+ * shortcuts. So a toolbar on the same page needs no change to live beside this script.
10
+ *
11
+ * While both are on the page there are two elements with that id. The toolbar's is in `<body>` and this one
12
+ * sits after `<body>`, so `document.getElementById("cs-root")` still finds the toolbar's, whichever came
13
+ * first.
14
+ *
15
+ * The one exception is a page with a modal `<dialog>` open. The browser draws that above everything else and
16
+ * makes everything outside it inert, so the root goes inside the dialog, as the toolbar's own does
17
+ * (`../runtime/placement.ts`). It is still fixed to the window, unless the dialog's own CSS makes it the
18
+ * containing block of fixed-position content (a transform, a filter), in which case it fills the dialog.
19
+ */
20
+ import { type ReactNode } from "react";
21
+ export type CaptureTheme = "light" | "dark" | "auto";
22
+ /**
23
+ * Tells a screen reader what opened. The region is on the page first and gets its text a moment later: a live
24
+ * region that arrives already filled is not read by every screen reader.
25
+ */
26
+ export declare function Announce({ text }: {
27
+ text: string;
28
+ }): import("react/jsx-runtime").JSX.Element;
29
+ export type Surface = {
30
+ /** Draws `node` in the root, in place of what was there. */
31
+ show(node: ReactNode): void;
32
+ /** Removes the root and gives the focus back to what had it. Safe to call twice. */
33
+ close(): void;
34
+ };
35
+ /**
36
+ * Mounts an empty root for one capture. `onLost` is called when the page takes the root away while it is
37
+ * open: the modal dialog it was mounted in has closed.
38
+ */
39
+ export declare function openSurface(theme: CaptureTheme, onLost: () => void): Surface;
@@ -1,2 +1,2 @@
1
- export { describeAnchor, dropAncestors, resolveAnchor, validateAnchor } from "./resolve.js";
2
- export type { Anchor, Candidate, Resolution, ResolutionStatus } from "./types.js";
1
+ export { describeAnchor, dropAncestors, resolveAnchor, validateAnchor, validateSketch, } from "./resolve.js";
2
+ export type { Anchor, Candidate, Resolution, ResolutionStatus, Sketch } from "./types.js";
@@ -1,4 +1,4 @@
1
- import type { Anchor, Candidate, Resolution } from "./types.js";
1
+ import type { Anchor, Candidate, Resolution, Sketch } from "./types.js";
2
2
  /**
3
3
  * Drop elements that contain another match, so a wrapping div doesn't steal the button.
4
4
  *
@@ -27,3 +27,4 @@ export declare function dropAncestors(candidates: Candidate[]): Candidate[];
27
27
  export declare function resolveAnchor(anchor: Anchor, candidates: Candidate[], inside?: (root: Candidate) => Candidate[]): Resolution;
28
28
  export declare function describeAnchor(anchor: Anchor): string;
29
29
  export declare function validateAnchor(input: unknown): Anchor | null;
30
+ export declare function validateSketch(input: unknown): Sketch | null;
@@ -29,6 +29,17 @@ export type Anchor = {
29
29
  tag: string;
30
30
  /** Sibling path inside the component. Stored for agents, never used to choose a match. */
31
31
  intra: string | null;
32
+ /** A freehand drawing the comment was left with. Never used to choose a match. */
33
+ sketch?: Sketch;
34
+ };
35
+ /**
36
+ * Strokes drawn on the page, in CSS pixels from the top-left corner of the anchored element.
37
+ * Each path is a flat list of x, y pairs. `w` and `h` are the element's size when it was drawn.
38
+ */
39
+ export type Sketch = {
40
+ w: number;
41
+ h: number;
42
+ paths: number[][];
32
43
  };
33
44
  export type Candidate = {
34
45
  key: string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The debug context file a comment carries (`context-<stamp>.json`), for an inbox, a destination and the
3
+ * service: read it back safely, redact it again, and turn it into the brief a coding agent reads. DOM-free, so
4
+ * a Worker can import it. Source only, like `./replay`: not built into `dist`.
5
+ */
6
+ export { DEBUG_CONTEXT_MAX_BYTES, DEBUG_CONTEXT_PREFIX, DEBUG_SIGNALS, DEFAULT_SIGNALS, OPT_IN_SIGNALS, debugContextBrief, debugContextBytes, debugContextKey, debugContextName, fitDebugContext, isDebugContextName, liveDebugOptions, newReportId, resolveDebugContext, sanitizeDebugContext, type BriefMeta, type DebugAudience, type DebugContextConfig, type DebugContextFile, type DebugContextOptions, type DebugError, type DebugRequest, type DebugSignal, type DebugStep, type HostBuild, type HostContext, type QuerySummary, type RealtimeState, type ResolvedDebugContext, } from "./runtime/debug-context-model.js";
7
+ export { ID_HEADERS, idHeaderNames, newTally, scrubId, scrubText, scrubUrl, scrubValue, type JsonBag, type RedactionTally, } from "./runtime/debug-context-redact.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The toolbar's dictation engine, for a connection's own composers (an inbox's reply box). The
3
+ * `./dictation` export points at this source for the monorepo only: it is not built into `dist`, so the npm
4
+ * package does not ship it. `ApiError` is what a `transcribe` callback throws so the engine can tell a retry from a
5
+ * stop (`rate_limited`, `dictation_unavailable`, 401, 403).
6
+ */
7
+ export { Dictation, dictationSupported, idleDictation, MAX_TAKE_MS, voiceRecordingSupported, type DictationDeps, type DictationState, } from "./runtime/dictation-engine.js";
8
+ export { dictationPrompt, insertDictated } from "./runtime/dictation.js";
9
+ export { ApiError } from "./runtime/client.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The script-tag loader's state: one toolbar per page, brought up by the same steps as `<CommentToolbar>`
3
+ * (`../runtime/boot.ts`), and the three things a single-page app may ask of it afterwards.
4
+ *
5
+ * ## Which service the toolbar may come from
6
+ *
7
+ * `<CommentToolbar>` imports the toolbar from the service the host's own session route names (`apiUrl`), and
8
+ * so does this loader: nothing on the page, and no attribute of the tag, can name another one. The loader also
9
+ * knows where it was itself loaded from. The two are the same service in every ordinary install, so a session
10
+ * that names another origin than the loader's own is refused: nothing is imported and nothing mounts. The one
11
+ * way to allow it is for the session route to say so itself, by naming the loader's origin in `loaderOrigin`.
12
+ *
13
+ * ## Following the host's sign-in
14
+ *
15
+ * While the toolbar is on the page it follows the host's sign-in itself (`ToolbarApp`). While it is not, because
16
+ * nobody was signed in when the page loaded, this loader asks the session route again at the same moments
17
+ * (`watchHost`), so a sign-in that loads no page still brings the toolbar. Not after the host's own `unmount()`.
18
+ */
19
+ import { type Runtime, type Wait } from "../runtime/boot.js";
20
+ import type { ScreenRule } from "../runtime/screen.js";
21
+ import type { GetSession } from "../runtime/session-source.js";
22
+ import type { ToolbarPosition, ToolbarTheme } from "../runtime/toolbar.js";
23
+ import type { DebugContextConfig } from "../runtime/debug-context-model.js";
24
+ import { type EmbedOptions } from "./options.js";
25
+ /** What `window.CommentTool` is. */
26
+ export type CommentToolApi = {
27
+ /**
28
+ * Asks the session route again. Mounts the toolbar when comments are now on and it is not on the page,
29
+ * removes it when they are now off, and has a toolbar that stays ask again too. Resolves once that is done.
30
+ * Call it after a sign-in or a sign-out that did not reload the page.
31
+ */
32
+ refresh(): Promise<void>;
33
+ /** Removes the toolbar. `refresh()` brings it back. */
34
+ unmount(): void;
35
+ /**
36
+ * `navigate` is the router's client-side navigation, as the component's `navigate` prop (null takes it away
37
+ * again); `position`, `theme` and `screen` are the component's props of those names (null takes `screen`
38
+ * away). A wrong value throws a TypeError and changes nothing.
39
+ */
40
+ configure(options: {
41
+ navigate?: ((href: string) => void) | null;
42
+ position?: ToolbarPosition;
43
+ theme?: ToolbarTheme;
44
+ screen?: ScreenRule | null;
45
+ /** The component's `debugContext` prop: `true`, `false`, or its options. Null, or any other value, turns it off. */
46
+ debugContext?: DebugContextConfig | null;
47
+ }): void;
48
+ };
49
+ export type EmbedInput = {
50
+ /** The origin `embed.js` was loaded from. */
51
+ serviceOrigin: string;
52
+ options: EmbedOptions;
53
+ /** The page-wide session source for a path. Default: the one the toolbar itself uses. */
54
+ sessions?: (path: string) => GetSession;
55
+ load?: (apiUrl: string) => Promise<Runtime>;
56
+ /** When the first attempt starts. Default: when the page is idle. */
57
+ wait?: Wait;
58
+ };
59
+ export declare function createEmbed(input: EmbedInput): CommentToolApi & {
60
+ start(): void;
61
+ };
@@ -0,0 +1 @@
1
+ export {};