@devaccel-labs/app-comments-toolbar 1.1.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 (214) hide show
  1. package/README.md +148 -30
  2. package/dist/react.js +417 -64
  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 +7 -0
  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 +1 -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 +2 -0
  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 -3
  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 +64 -11
  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/screen.d.ts +47 -0
  82. package/dist/types/runtime/screenshot.d.ts +60 -8
  83. package/dist/types/runtime/session-source.d.ts +45 -0
  84. package/dist/types/runtime/session.d.ts +20 -18
  85. package/dist/types/runtime/shield.d.ts +9 -0
  86. package/dist/types/runtime/shot.d.ts +45 -0
  87. package/dist/types/runtime/sketch.d.ts +26 -0
  88. package/dist/types/runtime/styles.d.ts +1 -1
  89. package/dist/types/runtime/sync.d.ts +1 -0
  90. package/dist/types/runtime/thread-model.d.ts +18 -0
  91. package/dist/types/runtime/ticket-form-model.d.ts +57 -0
  92. package/dist/types/runtime/ticket-form.d.ts +9 -0
  93. package/dist/types/runtime/toolbar.d.ts +37 -1
  94. package/dist/types/runtime/ui-scale.d.ts +17 -0
  95. package/dist/types/runtime/update.d.ts +32 -8
  96. package/dist/types/runtime/use-debug-context.d.ts +27 -0
  97. package/dist/types/server/index.d.ts +22 -0
  98. package/dist/types/server/support.d.ts +20 -0
  99. package/dist/types/server.d.ts +2 -0
  100. package/dist/types/vite/plugin.d.ts +15 -0
  101. package/dist/vite.js +121 -41
  102. package/package.json +24 -11
  103. package/src/capture/api.ts +128 -0
  104. package/src/capture/editor.tsx +178 -0
  105. package/src/capture/ignore.ts +38 -0
  106. package/src/capture/index.ts +27 -0
  107. package/src/capture/picker.tsx +171 -0
  108. package/src/capture/picture.ts +90 -0
  109. package/src/capture/surface.tsx +185 -0
  110. package/src/compiler/annotate.ts +202 -0
  111. package/src/compiler/babel.d.ts +11 -0
  112. package/src/compiler/enabled.ts +55 -0
  113. package/src/compiler/index.ts +2 -0
  114. package/src/core/index.ts +8 -0
  115. package/src/core/resolve.ts +307 -0
  116. package/src/core/types.ts +58 -0
  117. package/src/debug-context.ts +47 -0
  118. package/src/dictation.ts +17 -0
  119. package/src/embed/controller.ts +215 -0
  120. package/src/embed/index.ts +68 -0
  121. package/src/embed/options.ts +100 -0
  122. package/src/help/index.ts +38 -0
  123. package/src/help/panel-index.ts +17 -0
  124. package/src/help/panel-styles.ts +123 -0
  125. package/src/help/panel.ts +840 -0
  126. package/src/help/pill-styles.ts +75 -0
  127. package/src/help/pill.ts +338 -0
  128. package/src/help/react.tsx +143 -0
  129. package/src/help/requests.ts +111 -0
  130. package/src/help/types.ts +124 -0
  131. package/src/help/wire.ts +55 -0
  132. package/src/native-wire.ts +124 -0
  133. package/src/native.ts +282 -0
  134. package/src/react.ts +27 -0
  135. package/src/replay.ts +13 -0
  136. package/src/runtime/api.ts +531 -0
  137. package/src/runtime/attachment-model.ts +42 -0
  138. package/src/runtime/attachments.tsx +1260 -0
  139. package/src/runtime/boot.ts +95 -0
  140. package/src/runtime/browser-label.ts +31 -0
  141. package/src/runtime/build-id.ts +10 -0
  142. package/src/runtime/capture-ui.tsx +306 -0
  143. package/src/runtime/capture.ts +508 -0
  144. package/src/runtime/clarity-model.ts +634 -0
  145. package/src/runtime/clarity-ui.tsx +454 -0
  146. package/src/runtime/client.ts +151 -0
  147. package/src/runtime/composer-size.tsx +146 -0
  148. package/src/runtime/dashboard.tsx +1130 -0
  149. package/src/runtime/debug-context-core.ts +681 -0
  150. package/src/runtime/debug-context-host.ts +56 -0
  151. package/src/runtime/debug-context-model.ts +1319 -0
  152. package/src/runtime/debug-context-redact.ts +259 -0
  153. package/src/runtime/debug-context.ts +613 -0
  154. package/src/runtime/details-model.ts +30 -0
  155. package/src/runtime/details.tsx +395 -0
  156. package/src/runtime/dictation-engine.ts +616 -0
  157. package/src/runtime/dictation-model.ts +79 -0
  158. package/src/runtime/dictation-ui.tsx +363 -0
  159. package/src/runtime/dictation.ts +271 -0
  160. package/src/runtime/dock.ts +108 -0
  161. package/src/runtime/draft-dock.tsx +124 -0
  162. package/src/runtime/draft-model.ts +251 -0
  163. package/src/runtime/draft-store.ts +149 -0
  164. package/src/runtime/edits.tsx +165 -0
  165. package/src/runtime/font.ts +42 -0
  166. package/src/runtime/format.ts +129 -0
  167. package/src/runtime/freshness.ts +23 -0
  168. package/src/runtime/handoff.ts +76 -0
  169. package/src/runtime/host-watch.ts +64 -0
  170. package/src/runtime/icons.tsx +295 -0
  171. package/src/runtime/idle.ts +20 -0
  172. package/src/runtime/index.ts +11 -0
  173. package/src/runtime/ink.ts +6 -0
  174. package/src/runtime/kind-model.ts +20 -0
  175. package/src/runtime/live-model.ts +32 -0
  176. package/src/runtime/loader.tsx +87 -0
  177. package/src/runtime/markup-editor.tsx +1333 -0
  178. package/src/runtime/markup-model.ts +485 -0
  179. package/src/runtime/markup.ts +382 -0
  180. package/src/runtime/members.ts +58 -0
  181. package/src/runtime/mention-parse.ts +56 -0
  182. package/src/runtime/mentions.tsx +205 -0
  183. package/src/runtime/page-background.ts +51 -0
  184. package/src/runtime/page-image.ts +1592 -0
  185. package/src/runtime/placement.ts +336 -0
  186. package/src/runtime/record.ts +402 -0
  187. package/src/runtime/redact.ts +30 -0
  188. package/src/runtime/related.ts +34 -0
  189. package/src/runtime/render-page.ts +2 -0
  190. package/src/runtime/replay-model.ts +232 -0
  191. package/src/runtime/replay.ts +173 -0
  192. package/src/runtime/runtime-version.ts +7 -0
  193. package/src/runtime/runtime.tsx +51 -0
  194. package/src/runtime/screen.ts +182 -0
  195. package/src/runtime/screenshot.ts +260 -0
  196. package/src/runtime/session-source.ts +112 -0
  197. package/src/runtime/session.ts +108 -0
  198. package/src/runtime/shield.ts +34 -0
  199. package/src/runtime/shot.tsx +422 -0
  200. package/src/runtime/sketch.ts +139 -0
  201. package/src/runtime/styles.ts +1541 -0
  202. package/src/runtime/sync.ts +300 -0
  203. package/src/runtime/thread-model.ts +71 -0
  204. package/src/runtime/ticket-form-model.ts +128 -0
  205. package/src/runtime/ticket-form.tsx +342 -0
  206. package/src/runtime/toolbar.tsx +4427 -0
  207. package/src/runtime/ui-scale.ts +75 -0
  208. package/src/runtime/update.ts +98 -0
  209. package/src/runtime/use-debug-context.ts +81 -0
  210. package/src/server/index.ts +133 -0
  211. package/src/server/support.ts +499 -0
  212. package/src/server.ts +4 -0
  213. package/src/vite/plugin.ts +102 -0
  214. package/src/vite.ts +1 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The markup model: shapes in the image's own pixel space, sizes, colours, label layout, hit-testing, moves and
3
+ * undo history. DOM-free, so native clients draw the same shapes with their own renderer; the web toolbar's
4
+ * canvas renderer and flattening are in `./markup.ts`.
5
+ */
6
+ import { type Point } from "./ink.js";
7
+ export type { Point };
8
+ export type Box = {
9
+ x: number;
10
+ y: number;
11
+ w: number;
12
+ h: number;
13
+ };
14
+ export type MarkupTool = "select" | "pen" | "highlight" | "arrow" | "rect" | "ellipse" | "text";
15
+ export type MarkupSize = "s" | "m" | "l";
16
+ type ShapeBase = {
17
+ id: string;
18
+ color: string;
19
+ size: MarkupSize;
20
+ };
21
+ export type StrokeShape = ShapeBase & {
22
+ kind: "pen" | "highlight";
23
+ points: Point[];
24
+ };
25
+ export type ArrowShape = ShapeBase & {
26
+ kind: "arrow";
27
+ from: Point;
28
+ to: Point;
29
+ };
30
+ export type BoxShape = ShapeBase & {
31
+ kind: "rect" | "ellipse";
32
+ box: Box;
33
+ };
34
+ export type TextShape = ShapeBase & {
35
+ kind: "text";
36
+ at: Point;
37
+ text: string;
38
+ };
39
+ export type MarkupShape = StrokeShape | ArrowShape | BoxShape | TextShape;
40
+ /** Colours offered in the editor. The first is the toolbar's sketch ink. */
41
+ export declare const MARKUP_COLORS: ReadonlyArray<{
42
+ name: string;
43
+ value: string;
44
+ }>;
45
+ export declare const DEFAULT_INK = "#ff3b30";
46
+ export declare const DEFAULT_HIGHLIGHT = "#ffcc00";
47
+ /** Highlighter strokes are drawn at this opacity so the page under them stays readable. */
48
+ export declare const HIGHLIGHT_ALPHA = 0.38;
49
+ /** Most undo steps kept. */
50
+ export declare const HISTORY_LIMIT = 100;
51
+ /**
52
+ * Sizes in image pixels for one image. `unit` is max(1, longest side / 1000), so markup looks the same on a 1x
53
+ * screenshot and a 3x one. Pen and arrow strokes: s 2u, m 4u, l 8u. Highlighter: 5x the pen width. Text font:
54
+ * s 14u, m 20u, l 30u.
55
+ */
56
+ export type MarkupMetrics = {
57
+ unit: number;
58
+ stroke: (size: MarkupSize) => number;
59
+ highlight: (size: MarkupSize) => number;
60
+ font: (size: MarkupSize) => number;
61
+ };
62
+ /** Width in px of one line of text at `fontPx`, set in `markupFont(fontPx)`. */
63
+ export type MeasureText = (text: string, fontPx: number) => number;
64
+ /** Laid-out text label: the filled box in image px, its lines, and where each line's top-left sits. */
65
+ export type TextLayout = {
66
+ box: Box;
67
+ lines: string[];
68
+ font: number;
69
+ pad: number;
70
+ lineHeight: number;
71
+ };
72
+ export declare function markupMetrics(width: number, height: number): MarkupMetrics;
73
+ /** CSS font for labels: `600 <px>px` in the system UI stack. */
74
+ export declare function markupFont(px: number): string;
75
+ /**
76
+ * Label text for a fill: `#ffffff` while it keeps at least 3:1 contrast (WCAG AA for large bold text, which
77
+ * labels are), else whichever of white and `#1d1d1f` reads better. Red, blue, and purple keep white text;
78
+ * orange, green, yellow, and white get dark text.
79
+ */
80
+ export declare function contrastText(color: string): string;
81
+ /** Lays a label out: lines split on "\n", line height 1.25 em, padding 0.4 em, top-left at `shape.at`. */
82
+ export declare function textLayout(shape: TextShape, metrics: MarkupMetrics, measure: MeasureText): TextLayout;
83
+ /** The area a shape paints, stroke width included, in image px. */
84
+ export declare function shapeBounds(shape: MarkupShape, metrics: MarkupMetrics, measure: MeasureText): Box;
85
+ /**
86
+ * Id of the topmost shape under `point` (later shapes are on top), or null. Strokes and arrows hit within half
87
+ * their width plus `tolerance` of the line; rectangles and ellipses within that of their outline (not their
88
+ * inside, so a box drawn around something never hides it); text anywhere in its label box.
89
+ */
90
+ export declare function hitTest(shapes: MarkupShape[], point: Point, tolerance: number, metrics: MarkupMetrics, measure: MeasureText): string | null;
91
+ /** The same shape moved by (dx, dy); a new object, the input is left alone. */
92
+ export declare function moveShape<T extends MarkupShape>(shape: T, dx: number, dy: number): T;
93
+ /** The box between two drag points, any direction. `square` makes it w = h = the larger side, kept on the drag side. */
94
+ export declare function normalizeBox(a: Point, b: Point, square: boolean): Box;
95
+ /** `to` moved onto the nearest 45° ray from `from`, keeping its distance. */
96
+ export declare function snapAngle(from: Point, to: Point): Point;
97
+ /**
98
+ * Whether a just-drawn shape is worth keeping: a stroke must span `minSize` px in at least one direction, an
99
+ * arrow must be at least `minSize` long, a box must be at least `minSize` both ways, and text must have
100
+ * non-blank text.
101
+ */
102
+ export declare function isMeaningful(shape: MarkupShape, minSize: number): boolean;
103
+ /** The original's base name with `.png` or `.jpg` for the flattened type ("shot.jpeg" → "shot.png"). */
104
+ export declare function flattenedName(name: string, type: "image/png" | "image/jpeg"): string;
105
+ /** Undo history. `present` is what is on screen. */
106
+ export type MarkupHistory = {
107
+ past: MarkupShape[][];
108
+ present: MarkupShape[];
109
+ future: MarkupShape[][];
110
+ };
111
+ export declare function createHistory(shapes: MarkupShape[]): MarkupHistory;
112
+ /** Pushes `next` as a new step and clears redo; returns `history` itself when `next === history.present`. */
113
+ export declare function commitHistory(history: MarkupHistory, next: MarkupShape[]): MarkupHistory;
114
+ /** Returns `history` itself when there is nothing to undo. */
115
+ export declare function undoHistory(history: MarkupHistory): MarkupHistory;
116
+ /** Returns `history` itself when there is nothing to redo. */
117
+ export declare function redoHistory(history: MarkupHistory): MarkupHistory;
118
+ /** Width a stroke paints, before any hit tolerance. Highlighter is wider by design. */
119
+ export declare function strokeWidth(shape: StrokeShape, metrics: MarkupMetrics): number;
120
+ /** Arrow head length and half width, both from the shaft width and the image's unit. */
121
+ export declare function arrowHead(lineWidth: number, unit: number): {
122
+ length: number;
123
+ halfWidth: number;
124
+ };
125
+ /** A box as left/top with non-negative sides, so negative widths from odd input still draw. */
126
+ export declare function toRect(box: Box): Box;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Markup drawn over an attached image before it is sent: pen, highlighter, arrows, boxes, ellipses, and text
3
+ * labels. Shapes live in the image's own pixel space, so the same list draws the on-screen overlay (scaled to
4
+ * the pane) and the flattened file (at full size). This file is the canvas renderer and flattening; the
5
+ * DOM-free shape model is `./markup-model.ts`, re-exported here. Everything takes its canvas as an argument, so
6
+ * it runs under unit tests without a real canvas.
7
+ */
8
+ import { type Box, type MarkupMetrics, type MarkupShape, type MeasureText } from "./markup-model.js";
9
+ export * from "./markup-model.js";
10
+ /** Canvas area browsers reliably allocate (Safari's cap is 16 777 216 px); a bigger image is flattened smaller. */
11
+ export declare const MAX_CANVAS_PIXELS = 16000000;
12
+ /** The 2D context calls the renderer uses. A real CanvasRenderingContext2D satisfies it; tests pass a recorder. */
13
+ export type MarkupContext = Pick<CanvasRenderingContext2D, "save" | "restore" | "beginPath" | "closePath" | "moveTo" | "lineTo" | "quadraticCurveTo" | "arcTo" | "ellipse" | "rect" | "stroke" | "fill" | "fillText" | "measureText" | "drawImage" | "scale" | "setLineDash"> & {
14
+ strokeStyle: CanvasRenderingContext2D["strokeStyle"];
15
+ fillStyle: CanvasRenderingContext2D["fillStyle"];
16
+ lineWidth: number;
17
+ lineCap: CanvasLineCap;
18
+ lineJoin: CanvasLineJoin;
19
+ globalAlpha: number;
20
+ globalCompositeOperation: GlobalCompositeOperation;
21
+ font: string;
22
+ textBaseline: CanvasTextBaseline;
23
+ textAlign: CanvasTextAlign;
24
+ };
25
+ export type FlattenCanvas = {
26
+ width: number;
27
+ height: number;
28
+ getContext(kind: "2d"): MarkupContext | null;
29
+ toBlob(callback: (blob: Blob | null) => void, type?: string, quality?: number): void;
30
+ };
31
+ export type FlattenInput = {
32
+ /** The original image, already decoded. */
33
+ image: CanvasImageSource;
34
+ /** The image's natural size; shapes are in this space. */
35
+ width: number;
36
+ height: number;
37
+ shapes: MarkupShape[];
38
+ /** File name of the original; the result keeps its base name with the new extension. */
39
+ name: string;
40
+ maxBytes: number;
41
+ /** Test seam; defaults to a detached `<canvas>`. */
42
+ createCanvas?: (width: number, height: number) => FlattenCanvas;
43
+ };
44
+ /** Measures with `ctx.measureText`, or estimates 0.6 em per character when there is no canvas (jsdom). */
45
+ export declare function createMeasure(ctx: Pick<MarkupContext, "measureText" | "font"> | null): MeasureText;
46
+ /**
47
+ * Draws shapes in order on a context already scaled to image px. Pen strokes are smoothed through quadratic
48
+ * midpoints with round caps and joins; a one-point stroke draws a dot. Highlighter strokes draw the same way at
49
+ * `HIGHLIGHT_ALPHA` with normal ("source-over") compositing, so the overlay on screen matches the file. Arrows
50
+ * draw a shaft and a filled head sized from the stroke width (head length 4x width, min 10u), with the shaft
51
+ * stopping at the head's base.
52
+ * Rectangles have rounded corners (radius 1.5x width, clamped to half the short side). Text draws a filled
53
+ * rounded label (radius 0.3 em) in the shape colour with `contrastText` lines. Each shape restores the context.
54
+ */
55
+ export declare function drawShapes(ctx: MarkupContext, shapes: MarkupShape[], metrics: MarkupMetrics): void;
56
+ /** A dashed outline around `box` (padded by 4 screen px / `scale`), for the selected shape on screen. */
57
+ export declare function drawSelection(ctx: MarkupContext, box: Box, scale: number): void;
58
+ /**
59
+ * Draws the image and its shapes into one file. Tries PNG at full size (scaled down to fit
60
+ * `MAX_CANVAS_PIXELS`); if that is over `maxBytes`, JPEG at quality 0.92, 0.85, 0.75 on a white background;
61
+ * then halves the size and tries again, up to three times. Rejects with a readable Error when the canvas has no
62
+ * 2D context, `toBlob` gives null, or nothing fits.
63
+ */
64
+ export declare function flattenMarkup(input: FlattenInput): Promise<{
65
+ blob: Blob;
66
+ name: string;
67
+ }>;
68
+ /** Decodes an image URL. Rejects with a readable Error when it does not load. */
69
+ export declare function loadImage(url: string): Promise<HTMLImageElement>;
@@ -0,0 +1,18 @@
1
+ /** Mention parsing for the composer and message text. DOM-free, so native clients share it. */
2
+ import type { Member, Mentioned } from "./api.js";
3
+ /** The `@query` being typed just before `caret`, and where its `@` is, or null. */
4
+ export declare function mentionQueryAt(text: string, caret: number): {
5
+ query: string;
6
+ start: number;
7
+ } | null;
8
+ /** Members whose name contains, or whose email starts with, `query` (any case); never the writer. */
9
+ export declare function mentionMatches(members: readonly Member[], query: string, selfEmail: string | null | undefined): Member[];
10
+ /** `text` with the `@query` at `start` (up to `caret`) replaced by `@Name `, and the caret after it. */
11
+ export declare function insertMention(text: string, start: number, caret: number, member: Member): {
12
+ text: string;
13
+ caret: number;
14
+ };
15
+ /** Members picked while writing whose `@Name` is still in the text. */
16
+ export declare function mentionsIn(text: string, picked: Map<string, Member>): Member[];
17
+ /** Splits a message body around its mentions: odd entries are the `@Name` parts. */
18
+ export declare function mentionParts(body: string, mentions: readonly Mentioned[] | undefined): string[];
@@ -1,17 +1,17 @@
1
1
  import { type TextareaHTMLAttributes } from "react";
2
2
  import type { Member, Mentioned } from "./client.js";
3
+ export { mentionParts, mentionQueryAt, mentionsIn } from "./mention-parse.js";
3
4
  type Props = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "onChange"> & {
4
5
  value: string;
5
6
  onValueChange: (next: string) => void;
6
7
  members: Member[];
7
8
  selfEmail?: string;
8
9
  onMention: (member: Member) => void;
10
+ /** Grow the field to its text. The composer uses this so a long draft stays visible. */
11
+ grow?: boolean;
9
12
  };
10
- /** Members picked while writing whose `@Name` is still in the text. */
11
- export declare function mentionsIn(text: string, picked: Map<string, Member>): Member[];
12
13
  export declare function MessageText({ body, mentions }: {
13
14
  body: string;
14
15
  mentions?: Mentioned[];
15
16
  }): import("react/jsx-runtime").JSX.Element;
16
- export declare function MentionTextarea({ value, onValueChange, members, selfEmail, onMention, onKeyDown, ...rest }: Props): import("react/jsx-runtime").JSX.Element;
17
- export {};
17
+ export declare function MentionTextarea({ value, onValueChange, members, selfEmail, onMention, onKeyDown, grow, ...rest }: Props): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The colour behind the page, as the browser paints it: the root element's background, else the body's (which
3
+ * the browser carries out to the whole window), else the window's own colour for the page's colour scheme
4
+ * (white, or dark under `color-scheme: dark`). A partly transparent one is laid over the window's colour.
5
+ *
6
+ * Never transparent. Snapshots and recording frames are JPEG and video, which have no transparency: a
7
+ * transparent background comes out black, with the page's black text on it.
8
+ */
9
+ export declare function pageBackground(): string;
@@ -8,8 +8,27 @@
8
8
  * main-thread time per capture on an ordinary page and grew with the page. This one spends the main thread on
9
9
  * the clone walk and on the browser's own SVG layout, and nothing on the network once warmed.
10
10
  *
11
- * What is not carried over: cross-origin iframes (drawn blank), `<video>` frames (blank), styles that depend on
12
- * `:hover`/`:focus` (dropped, which is what a screenshot should show anyway).
11
+ * The copy is built to land every element exactly where the person sees it, whatever is scrolled:
12
+ *
13
+ * - A scrolled element's children are shifted by its scroll offset, the window's included, so the root is never
14
+ * transformed and never clips the part of the page that scrolled into view.
15
+ * - `position: fixed` and `position: sticky` elements are pinned at their on-screen box, since neither survives a
16
+ * scroll offset expressed as a translation (a transform re-anchors fixed descendants; a static image has nothing
17
+ * to stick to). Every box is measured while the page is copied, never when it is drawn: drawing waits for
18
+ * fonts and pictures, and a page that scrolled on in the meantime would put its header wherever it had got to.
19
+ * - What is in the browser's top layer (a modal dialog with its backdrop, an open popover) is drawn open, over
20
+ * everything else.
21
+ * - What is moving (a CSS animation or transition, a script animation, one driven by scrolling) is drawn at the
22
+ * frame it was on.
23
+ * - Replaced elements (images, video, canvas, iframes, plugins) keep their on-screen box and their own look
24
+ * (border, margin, fit) whatever happens to their pixels, so a slow or refused asset never reflows what
25
+ * follows it.
26
+ * - A classic (non-overlay) scrollbar's gutter is kept as padding, so content beside one is not laid out wider.
27
+ *
28
+ * What is not carried over: what the page itself may not read (a frame's or plugin's content, a closed shadow
29
+ * tree, a picture or video from another origin that sends no CORS header, a WebGL canvas that does not keep
30
+ * its drawing buffer): a blank box of the same size. And styles that depend on `:hover`/`:focus` (dropped,
31
+ * which is what a screenshot should show anyway).
13
32
  */
14
33
  export type PageImageOptions = {
15
34
  /** Device pixels per CSS pixel in the output. */
@@ -31,6 +50,10 @@ type Viewport = {
31
50
  width: number;
32
51
  height: number;
33
52
  };
53
+ type Scroll = {
54
+ left: number;
55
+ top: number;
56
+ };
34
57
  /** A data URL for `url`, or null when it can't be fetched in time. Never rejects. */
35
58
  export declare function assetDataUrl(url: string, timeout?: number): Promise<string | null>;
36
59
  /** Replaces every `url()` in `cssText` with a data URL. Unfetchable ones are left as they are. */
@@ -48,23 +71,53 @@ export declare function pageStyleText(doc?: Document): Promise<string>;
48
71
  /** Fetches fonts and CSS assets now, so the first capture pays nothing on the network. */
49
72
  export declare function prewarmPageImage(doc?: Document): void;
50
73
  /**
51
- * The elements that paint inside the viewport, plus every ancestor of one (so the tree above them survives).
52
- * Elements without a box (`display: none`, `<option>`, SVG `<defs>`, `display: contents` wrappers) are not
53
- * listed: they neither show nor hide anything by themselves, so the clone keeps them under a kept parent.
74
+ * The elements that paint inside the viewport, plus every ancestor of one. Elements without a box
75
+ * (`display: none`, `<option>`, SVG `<defs>`, `display: contents` wrappers) are not listed.
54
76
  */
55
- export declare function visibleSet(root: Element, viewport: Viewport): Set<Element>;
77
+ export declare function visibleSet(root: Element, viewport: Viewport, exclude?: (element: Element) => boolean): Set<Element>;
56
78
  export type ClonedPage = {
57
79
  root: Element;
58
80
  waits: Promise<unknown>[];
59
81
  elements: number;
82
+ /** Rules that belong after the page's own styles (see `renderSnapshot`). Empty for most pages. */
83
+ css: string;
84
+ /** Pins fixed and sticky elements where they are on screen. Call once `waits` have settled; runs once. */
85
+ finish: () => void;
86
+ };
87
+ /**
88
+ * A copy of `root` ready to serialize: `display: none` subtrees removed, offscreen images held by same-size
89
+ * placeholders, live form values, scroll offsets, canvases and visible images carried over. `waits` resolve
90
+ * when every visible image has its pixels. `scroll` is the window's offset, applied to the root's children.
91
+ *
92
+ * Everything the copy needs from the live page is read here, in one go. `finish` only rearranges the copy.
93
+ */
94
+ export declare function clonePage(root: Element, viewport: Viewport, exclude: (element: Element) => boolean, view?: Window, scroll?: Scroll): ClonedPage;
95
+ /**
96
+ * The SVG document that draws `clone` with `css` applied, `width`×`height` CSS pixels.
97
+ *
98
+ * Inside the image the `<svg>` is the root element, not the cloned `<html>`, so `rem` resolves against the
99
+ * svg's font size. `rootFontSize` (the live root's computed size) is set on it: a page that scales itself by
100
+ * a root font size set from an attribute or class on `<html>` would otherwise lay out at the browser default
101
+ * while fixed and sticky boxes stay pinned at their live, differently scaled coordinates.
102
+ */
103
+ export declare function pageSvg(clone: Element, css: string, width: number, height: number, rootFontSize?: string): string;
104
+ /** The page copied at one instant, with everything a later render needs. */
105
+ export type PageSnapshot = {
106
+ page: ClonedPage;
107
+ css: Promise<string>;
108
+ width: number;
109
+ height: number;
110
+ /** The live root's computed font size, which `rem` resolves against inside the image too. */
111
+ rootFontSize: string;
112
+ backgroundColor?: string | undefined;
60
113
  };
61
114
  /**
62
- * A copy of `root` ready to serialize: offscreen and non-painting subtrees removed, live form values, scroll
63
- * offsets, canvases and images carried over. `waits` resolve when every image has its pixels.
115
+ * Copies the visible page now, synchronously, so the picture shows the page exactly as it was at this moment
116
+ * however long drawing it then takes. Rendering is `renderSnapshot`.
64
117
  */
65
- export declare function clonePage(root: Element, viewport: Viewport, exclude: (element: Element) => boolean, view?: Window): ClonedPage;
66
- /** The SVG document that draws `clone` with `css` applied, `width`×`height` CSS pixels. */
67
- export declare function pageSvg(clone: Element, css: string, width: number, height: number): string;
118
+ export declare function snapshotPage(options: Omit<PageImageOptions, "scale" | "timeout">): PageSnapshot;
119
+ /** Draws a snapshot onto a canvas at `scale` device pixels per CSS pixel. Throws when it can't be done in time. */
120
+ export declare function renderSnapshot(snapshot: PageSnapshot, scale: number, timeout?: number): Promise<HTMLCanvasElement>;
68
121
  /** The visible page as a canvas. Throws when the page can't be drawn in time. */
69
122
  export declare function drawPage(options: PageImageOptions): Promise<HTMLCanvasElement>;
70
123
  export {};
@@ -1,16 +1,19 @@
1
1
  import type { Thread } from "./client.js";
2
2
  import type { PlacedStatus } from "./format.js";
3
+ import type { Here } from "./screen.js";
3
4
  export type Box = {
4
5
  top: number;
5
6
  left: number;
6
7
  width: number;
7
8
  height: number;
8
9
  };
10
+ /** `box` is the anchored element's box, set while the thread is placed. */
9
11
  export type Placed = {
10
12
  thread: Thread;
11
13
  status: PlacedStatus;
12
14
  top?: number;
13
15
  left?: number;
16
+ box?: Box;
14
17
  };
15
18
  export declare function overlayBounds(host: HTMLElement): {
16
19
  width: number;
@@ -26,13 +29,13 @@ export declare function placeAgainst(element: Element, host: HTMLElement): Box |
26
29
  export declare function useOverlayHost(enabled: boolean): HTMLElement | null;
27
30
  /**
28
31
  * Where each thread's anchor is on this page. Anchors are resolved only while `active` (the panel is open or this
29
- * page's pins are shown) and the tab is visible, only for open threads on this route, in idle-time slices. The page
32
+ * page's pins are shown) and the tab is visible, only for open threads on this screen, in idle-time slices. The page
30
33
  * is watched with a MutationObserver only while the panel is open or the page is small; otherwise clicks, keys,
31
34
  * focus and a slow re-check trigger re-resolves. Scrolling and resizing only re-measure elements already found.
32
35
  */
33
- export declare function usePlacement({ threads, route, host, active, panelOpen, visible, }: {
36
+ export declare function usePlacement({ threads, here, host, active, panelOpen, visible, }: {
34
37
  threads: Thread[];
35
- route: string;
38
+ here: Here;
36
39
  host: HTMLElement;
37
40
  active: boolean;
38
41
  panelOpen: boolean;
@@ -1,6 +1,8 @@
1
+ /** A recording stops itself after this long. */
2
+ export declare const RECORD_MAX_MS = 75000;
1
3
  export declare const FRAME_WIDTH = 1920;
2
4
  export declare const FRAME_HEIGHT = 1080;
3
- export declare function recordingMime(): string | null;
5
+ export declare function recordingMime(sound?: boolean): string | null;
4
6
  export declare function formatClock(ms: number): string;
5
7
  type FeaturePolicy = {
6
8
  allowsFeature(feature: string): boolean;
@@ -24,10 +26,24 @@ export type TabTake = {
24
26
  stop: () => void;
25
27
  cancel: () => void;
26
28
  finished: Promise<Blob | null>;
29
+ /** The mic in the take; mute it with `enabled`. Null when sound was off or the mic was refused. */
30
+ microphone: MediaStreamTrack | null;
31
+ };
32
+ /** Mute or unmute the mic in this take. Returns whether it is now on. */
33
+ export declare function toggleMicrophone(microphone: MediaStreamTrack): boolean;
34
+ export type RecordOptions = {
35
+ /** Record the mic too. */
36
+ sound: boolean;
37
+ /**
38
+ * Every second of the recording as it lands, in order; the first chunk carries the file's header, so the
39
+ * chunks so far always make a playable file. Lets a take be kept somewhere safe while it runs.
40
+ */
41
+ onChunk?: (chunk: Blob, index: number, mime: string) => void;
27
42
  };
28
43
  /**
29
44
  * Films this tab. When the page allows screen capture, the browser asks first.
30
45
  * When the page forbids it, the visible page is filmed instead, so the record button still works.
46
+ * With `sound`, the mic is asked for after the screen (the screen prompt needs the click's activation).
31
47
  */
32
- export declare function startTabRecording(onReady?: (take: TabTake) => void): Promise<TabTake>;
48
+ export declare function startTabRecording(options: RecordOptions, onReady?: (take: TabTake) => void): Promise<TabTake>;
33
49
  export {};
@@ -1,4 +1,4 @@
1
- import type { Thread } from "./client.js";
1
+ import type { Thread } from "./api.js";
2
2
  export type Related = {
3
3
  thread: Thread;
4
4
  because: string;
@@ -0,0 +1,80 @@
1
+ export type ReplayDraft = {
2
+ type: "navigate";
3
+ path: string;
4
+ } | {
5
+ type: "click";
6
+ label: string;
7
+ } | {
8
+ type: "input";
9
+ label: string;
10
+ redacted: boolean;
11
+ value?: string;
12
+ } | {
13
+ type: "console";
14
+ level: "error" | "warn";
15
+ message: string;
16
+ }
17
+ /** A request the page made that failed: `status` 0 means it never got an answer. */
18
+ | {
19
+ type: "network";
20
+ method: string;
21
+ path: string;
22
+ status: number;
23
+ }
24
+ /** Words the commenter said while a clip was recording, placed when that piece was spoken. */
25
+ | {
26
+ type: "say";
27
+ text: string;
28
+ };
29
+ export type ReplayEvent = ReplayDraft & {
30
+ t: number;
31
+ };
32
+ /** When the file goes with a recording: the clip's name and clock, so events sit on its timeline. */
33
+ export type ReplayClip = {
34
+ name: string;
35
+ startedAt: number;
36
+ durationMs: number;
37
+ };
38
+ export type ReplayFile = {
39
+ v: 1;
40
+ kind: "instant-replay";
41
+ url: string;
42
+ events: ReplayEvent[];
43
+ clip?: ReplayClip;
44
+ };
45
+ export type ReplayBufferOptions = {
46
+ now?: () => number;
47
+ /** How far back events are kept. */
48
+ windowMs?: number;
49
+ maxEvents?: number;
50
+ };
51
+ /**
52
+ * What the commenter did, as a rolling window. The default minute backs the instant replay; a recording keeps a
53
+ * wider one for the clip's whole length. Stays in memory until it is attached.
54
+ */
55
+ export declare class ReplayBuffer {
56
+ private items;
57
+ private readonly now;
58
+ private readonly windowMs;
59
+ private readonly maxEvents;
60
+ constructor(options?: ReplayBufferOptions | (() => number));
61
+ push(event: ReplayDraft): void;
62
+ /** An event that happened at `t`, not now: narration lands when it was spoken, not when it was written. */
63
+ pushAt(event: ReplayDraft, t: number): void;
64
+ /** `windowMs` keeps only the events of that last stretch. */
65
+ snapshot(url: string, clip?: ReplayClip, windowMs?: number): ReplayFile | null;
66
+ /** Every event still in the window, oldest first. */
67
+ events(): ReplayEvent[];
68
+ clear(): void;
69
+ get size(): number;
70
+ }
71
+ /** Accepts only the replay shape the toolbar writes. Anything else is dropped. */
72
+ export declare function parseReplay(value: unknown): ReplayFile | null;
73
+ /** The clip name a replay file goes with, by the stamp both carry: `recording-<stamp>.webm` ↔ `replay-<stamp>.json`. */
74
+ export declare function replayStamp(name: string): string | null;
75
+ /** One line of a replay event, as the viewers show it. */
76
+ export declare function replayLine(event: ReplayEvent): string;
77
+ /** Replaces secret query values in place. The page URL stored with a comment uses this too. */
78
+ export declare function redactSecretSearchParams(url: URL): void;
79
+ /** A console call's arguments as one redacted line. */
80
+ export declare function consoleText(args: unknown[]): string;
@@ -1,42 +1,9 @@
1
- export type ReplayDraft = {
2
- type: "navigate";
3
- path: string;
4
- } | {
5
- type: "click";
6
- label: string;
7
- } | {
8
- type: "input";
9
- label: string;
10
- redacted: boolean;
11
- value?: string;
12
- } | {
13
- type: "console";
14
- level: "error" | "warn";
15
- message: string;
16
- };
17
- export type ReplayEvent = ReplayDraft & {
18
- t: number;
19
- };
20
- export type ReplayFile = {
21
- v: 1;
22
- kind: "instant-replay";
23
- url: string;
24
- events: ReplayEvent[];
25
- };
26
- /** Rolling minute of what the commenter did. Stays in memory until they attach it. */
27
- export declare class ReplayBuffer {
28
- private now;
29
- private events;
30
- constructor(now?: () => number);
31
- push(event: ReplayDraft): void;
32
- snapshot(url: string): ReplayFile | null;
33
- clear(): void;
34
- get size(): number;
35
- }
36
- /** Accepts only the replay shape the toolbar writes. Anything else is dropped. */
37
- export declare function parseReplay(value: unknown): ReplayFile | null;
1
+ import { type ReplayBuffer } from "./replay-model.js";
2
+ export * from "./replay-model.js";
38
3
  /**
39
4
  * Records clicks, field changes, navigations, and console errors on this page.
40
5
  * Ignores the toolbar. Does not read network traffic. Call the return to stop and drop the buffer.
41
6
  */
42
- export declare function watchActivity(buffer: ReplayBuffer): () => void;
7
+ export declare function watchActivity(buffer: ReplayBuffer,
8
+ /** Requests that are the toolbar's own (the comment service): never part of what the person did. */
9
+ ignoreUrl?: (url: string) => boolean): () => void;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Which screen of the host app an address is, and so which threads' pins belong on the page. DOM-free.
3
+ *
4
+ * A thread stores the address it was left on (`url`: path, query and hash) next to its `route` (the path). Two
5
+ * addresses are one screen when they have the same path, the same hash route (a hash that starts with `#/` or
6
+ * `#!/`, as hash routers write them; any other hash is a place on the page and does not count), and the same
7
+ * values for every query parameter that names a screen.
8
+ *
9
+ * Without a rule from the host, every query parameter names a screen except the ones that never do: tracking
10
+ * (`utm_*`, `gclid`, …), paging, sorting, searching and filtering by their usual names, and the toolbar's own.
11
+ * A host whose screens differ by fewer parameters names them (`{ params: ["view"] }`), and one whose screens
12
+ * are not in the address that way names each screen itself with a function.
13
+ *
14
+ * A thread whose address is a bare path (no query, no hash route) was stored before addresses were kept, or was
15
+ * left where the path was all there was: it shows on every screen at that path, as every thread did before. A
16
+ * host function decides for those too.
17
+ */
18
+ /** What the host says identifies a screen: the query parameters that do, or a function that names it. */
19
+ export type ScreenRule = {
20
+ readonly params: readonly string[];
21
+ } | ((address: URL) => string);
22
+ /**
23
+ * The screen `url` is, as a string two addresses of one screen share. A host function that throws names
24
+ * nothing, and the address is read by the parameters instead.
25
+ */
26
+ export declare function screenKey(url: URL, rule?: ScreenRule | null): string;
27
+ /** True for an address that is only a path: no query and no hash route. */
28
+ export declare function isBarePath(address: string): boolean;
29
+ /** The address a thread was left on, as the toolbar reads it: its `url`, else its `route`. */
30
+ export type Placed = {
31
+ readonly route: string;
32
+ readonly url?: string | null;
33
+ };
34
+ export type Here = {
35
+ /** The page's path, which anchors are captured and resolved against. */
36
+ readonly route: string;
37
+ /** The screen the page is on. */
38
+ readonly key: string;
39
+ /** Whether `thread` was left on this screen, so its pin belongs here. */
40
+ has(thread: Placed): boolean;
41
+ };
42
+ /**
43
+ * The screen at `href` (the page's whole address), and which threads belong on it. A thread is only ever on
44
+ * a screen at its own path, because its anchor is found among that path's elements. One whose `url` is not a
45
+ * page of this app (a native app's screen) belongs here by its `route` alone, as before.
46
+ */
47
+ export declare function screenAt(href: string, rule?: ScreenRule | null): Here;