@devaccel-labs/app-comments-toolbar 0.0.0-stage → 1.1.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 (52) hide show
  1. package/README.md +35 -2
  2. package/dist/react.d.ts +1 -0
  3. package/dist/react.js +193 -0
  4. package/dist/server.d.ts +1 -0
  5. package/dist/server.js +87 -0
  6. package/dist/types/compiler/annotate.d.ts +10 -0
  7. package/dist/types/compiler/enabled.d.ts +14 -0
  8. package/dist/types/compiler/index.d.ts +2 -0
  9. package/dist/types/core/index.d.ts +2 -0
  10. package/dist/types/core/resolve.d.ts +29 -0
  11. package/dist/types/core/types.d.ts +65 -0
  12. package/dist/types/react.d.ts +2 -0
  13. package/dist/types/runtime/attachments.d.ts +73 -0
  14. package/dist/types/runtime/build-id.d.ts +6 -0
  15. package/dist/types/runtime/capture-ui.d.ts +35 -0
  16. package/dist/types/runtime/capture.d.ts +30 -0
  17. package/dist/types/runtime/client.d.ts +220 -0
  18. package/dist/types/runtime/dashboard.d.ts +28 -0
  19. package/dist/types/runtime/details.d.ts +43 -0
  20. package/dist/types/runtime/dock.d.ts +38 -0
  21. package/dist/types/runtime/edits.d.ts +20 -0
  22. package/dist/types/runtime/format.d.ts +15 -0
  23. package/dist/types/runtime/freshness.d.ts +18 -0
  24. package/dist/types/runtime/handoff.d.ts +15 -0
  25. package/dist/types/runtime/icons.d.ts +40 -0
  26. package/dist/types/runtime/idle.d.ts +6 -0
  27. package/dist/types/runtime/index.d.ts +2 -0
  28. package/dist/types/runtime/loader.d.ts +6 -0
  29. package/dist/types/runtime/members.d.ts +11 -0
  30. package/dist/types/runtime/mentions.d.ts +17 -0
  31. package/dist/types/runtime/page-image.d.ts +70 -0
  32. package/dist/types/runtime/placement.d.ts +43 -0
  33. package/dist/types/runtime/record.d.ts +33 -0
  34. package/dist/types/runtime/redact.d.ts +10 -0
  35. package/dist/types/runtime/related.d.ts +11 -0
  36. package/dist/types/runtime/render-page.d.ts +1 -0
  37. package/dist/types/runtime/replay.d.ts +42 -0
  38. package/dist/types/runtime/runtime-version.d.ts +7 -0
  39. package/dist/types/runtime/runtime.d.ts +14 -0
  40. package/dist/types/runtime/screenshot.d.ts +14 -0
  41. package/dist/types/runtime/session.d.ts +25 -0
  42. package/dist/types/runtime/styles.d.ts +5 -0
  43. package/dist/types/runtime/sync.d.ts +25 -0
  44. package/dist/types/runtime/toolbar.d.ts +22 -0
  45. package/dist/types/runtime/update.d.ts +13 -0
  46. package/dist/types/server/index.d.ts +14 -0
  47. package/dist/types/server.d.ts +2 -0
  48. package/dist/types/vite/plugin.d.ts +20 -0
  49. package/dist/types/vite.d.ts +1 -0
  50. package/dist/vite.d.ts +1 -0
  51. package/dist/vite.js +173 -0
  52. package/package.json +52 -4
package/README.md CHANGED
@@ -1,3 +1,36 @@
1
- # Temporary Holding Version
1
+ # @devaccel-labs/app-comments-toolbar
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The in-app comment toolbar that host apps (Officers Assist, JebelAI) load at runtime from the App comments
4
+ worker as `/v1/toolbar/1.js`. Ported from the comment-service repo (`packages/core` and `packages/react`) on
5
+ 2026-10-12; this is the source of record for the runtime.
6
+
7
+ - `src/core`: anchor model and resolution (`resolveAnchor`), DOM-free.
8
+ - `src/runtime`: the React runtime (`runtime.tsx` is the bundle entry), element capture and placement, sync,
9
+ attachments, page screenshots (`page-image.ts`), styles, and the live update (`update.ts`, `build-id.ts`).
10
+ `loader.tsx` is the host-side `<CommentToolbar>`.
11
+ - `src/compiler`, `src/vite`, `src/server`: the Vite plugin that stamps components with `data-cs-cmp` /
12
+ `data-cs-file`, and the same-origin session route. Entry points `src/react.ts`, `src/vite.ts`, `src/server.ts`.
13
+ - Tests: `tests/unit/app-comments-toolbar-*.spec.ts`.
14
+
15
+ ## How the toolbar ships
16
+
17
+ The toolbar is released by deploying MyStatusFlow, nothing else (ADR-APPC-008):
18
+
19
+ - `bun scripts/app-comments/build-toolbar.mjs` writes `workers/app-comments/toolbar-dist/v1/toolbar/` (checked in;
20
+ see `docs/policies/generated-files-policy.md`) with a build id hashed from the runtime's source, stamped into
21
+ the bundle and written to `build.json`. `bun scripts/dev/dev-app-comments.mjs` builds it too.
22
+ - `bun run deploy:staging` (and the CI deploy) rebuilds it as its own phase and deploys the worker; a worker's
23
+ served assets count toward the deploy's change detection.
24
+ - A toolbar that is open reads `/v1/toolbar/build.json` when its live socket reconnects (a deploy restarts it),
25
+ when its tab becomes visible, and every five minutes. A different id makes it import the new bundle and remount
26
+ itself in place, once nobody is mid-draft, mid-reply, recording or editing.
27
+
28
+ ## The npm package
29
+
30
+ Hosts install the package for the three things that are host-side by nature: the Vite plugin (build time), the
31
+ session route (server side) and the `<CommentToolbar>` loader. The package holds no runtime (since 1.1.0), so it
32
+ only changes when the loader contract (`runtimeVersion`) does. `bun run build` here writes `dist/`; publish with
33
+ `npm publish` from this directory. The `COMMENT_SERVICE_*` env names in hosts are unchanged.
34
+
35
+ `/v1` responses and the runtime's storage keys must stay compatible with whatever package version the hosts have
36
+ installed; the runtime itself is always the worker's current build.
@@ -0,0 +1 @@
1
+ export * from "./types/react.js";
package/dist/react.js ADDED
@@ -0,0 +1,193 @@
1
+ // src/runtime/loader.tsx
2
+ import { useEffect, useRef } from "react";
3
+
4
+ // src/runtime/handoff.ts
5
+ var key = "devaccel.comment.handoff";
6
+ var ttl = 30 * 60 * 1e3;
7
+ function readHandoff() {
8
+ try {
9
+ const value = JSON.parse(sessionStorage.getItem(key) ?? "null");
10
+ return value && Date.now() - value.at < ttl ? value : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function write(value) {
16
+ try {
17
+ if (value.invite || value.thread) sessionStorage.setItem(key, JSON.stringify(value));
18
+ else sessionStorage.removeItem(key);
19
+ } catch {
20
+ }
21
+ }
22
+ function linkParams() {
23
+ const url = new URL(window.location.href);
24
+ const hash = new URLSearchParams(url.hash.slice(1));
25
+ return {
26
+ url,
27
+ hash,
28
+ invite: url.searchParams.get("cs-invite") ?? hash.get("cs-invite"),
29
+ thread: url.searchParams.get("cs-thread") ?? hash.get("cs-thread")
30
+ };
31
+ }
32
+ function captureHandoff() {
33
+ if (typeof window === "undefined") return;
34
+ const { invite, thread } = linkParams();
35
+ if (!invite && !thread) return;
36
+ write({
37
+ ...readHandoff(),
38
+ ...invite ? { invite } : {},
39
+ ...thread ? { thread } : {},
40
+ at: Date.now()
41
+ });
42
+ }
43
+
44
+ // src/runtime/idle.ts
45
+ var FALLBACK_SLICE_MS = 8;
46
+ function whenIdle(task, timeout) {
47
+ if (typeof requestIdleCallback === "function") {
48
+ const id2 = requestIdleCallback(task, { timeout });
49
+ return () => cancelIdleCallback(id2);
50
+ }
51
+ const id = setTimeout(
52
+ () => {
53
+ const end = performance.now() + FALLBACK_SLICE_MS;
54
+ task({ didTimeout: false, timeRemaining: () => Math.max(0, end - performance.now()) });
55
+ },
56
+ Math.min(timeout, 200)
57
+ );
58
+ return () => clearTimeout(id);
59
+ }
60
+
61
+ // src/runtime/runtime-version.ts
62
+ var runtimeVersion = 1;
63
+
64
+ // src/runtime/session.ts
65
+ var EXPIRY_MARGIN_MS = 6e4;
66
+ var UNKNOWN_EXPIRY_MS = 5 * 6e4;
67
+ function readJwt(token) {
68
+ try {
69
+ const part = token.split(".")[1];
70
+ if (!part) return null;
71
+ const pad = part.length % 4 === 0 ? "" : "=".repeat(4 - part.length % 4);
72
+ return JSON.parse(atob(part.replaceAll("-", "+").replaceAll("_", "/") + pad));
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+ function sessionFrom(payload) {
78
+ const value = payload;
79
+ if (!value?.enabled || !value.apiUrl || !value.project || !value.environment || !value.hostToken)
80
+ return null;
81
+ return {
82
+ apiUrl: value.apiUrl,
83
+ project: value.project,
84
+ environment: value.environment,
85
+ hostToken: value.hostToken
86
+ };
87
+ }
88
+ async function fetchSession(path) {
89
+ const response = await fetch(path, {
90
+ headers: { accept: "application/json", "x-comment-service-origin": window.location.origin },
91
+ cache: "no-store"
92
+ });
93
+ return response.json();
94
+ }
95
+ function sessionSource(path, load = fetchSession, now = Date.now) {
96
+ let kept = null;
97
+ let pending = null;
98
+ return ({ refresh = false } = {}) => {
99
+ if (!refresh && kept && now() < kept.until) return Promise.resolve(kept.session);
100
+ if (pending) return pending;
101
+ pending = load(path).then((payload) => {
102
+ const session = sessionFrom(payload);
103
+ const expires = session ? readJwt(session.hostToken)?.exp : void 0;
104
+ const until = !session ? Infinity : expires ? expires * 1e3 - EXPIRY_MARGIN_MS : now() + UNKNOWN_EXPIRY_MS;
105
+ kept = { session, until };
106
+ return session;
107
+ }).finally(() => {
108
+ pending = null;
109
+ });
110
+ return pending;
111
+ };
112
+ }
113
+ var sources = /* @__PURE__ */ new Map();
114
+ function sessionFor(path) {
115
+ let source = sources.get(path);
116
+ if (!source) {
117
+ source = sessionSource(path);
118
+ sources.set(path, source);
119
+ }
120
+ return source;
121
+ }
122
+
123
+ // src/runtime/loader.tsx
124
+ var IDLE_TIMEOUT_MS = 3e3;
125
+ captureHandoff();
126
+ async function loadRuntime(apiUrl) {
127
+ const url = `${apiUrl.replace(/\/+$/, "")}/v1/toolbar/${runtimeVersion}.js`;
128
+ const remote = await import(
129
+ /* @vite-ignore */
130
+ /* webpackIgnore: true */
131
+ url
132
+ );
133
+ if (typeof remote.mount === "function") return remote;
134
+ throw new Error(`${url} has no mount export`);
135
+ }
136
+ function CommentToolbar({
137
+ sessionPath = "/api/comment-service/session",
138
+ pollMs,
139
+ position,
140
+ theme,
141
+ navigate
142
+ }) {
143
+ const handle = useRef(null);
144
+ const navigateRef = useRef(navigate);
145
+ const propsRef = useRef({});
146
+ useEffect(() => {
147
+ navigateRef.current = navigate;
148
+ propsRef.current = {
149
+ sessionPath,
150
+ pollMs,
151
+ position,
152
+ theme,
153
+ navigate: navigate ? (href) => navigateRef.current?.(href) : void 0,
154
+ getSession: sessionFor(sessionPath)
155
+ };
156
+ handle.current?.update(propsRef.current);
157
+ }, [navigate, pollMs, position, sessionPath, theme]);
158
+ useEffect(() => {
159
+ let cancelled = false;
160
+ let mounted = null;
161
+ let container = null;
162
+ const start = async () => {
163
+ const session = await sessionFor(sessionPath)().catch(() => null);
164
+ if (!session || cancelled) return;
165
+ const runtime = await loadRuntime(session.apiUrl);
166
+ if (cancelled) return;
167
+ container = document.createElement("div");
168
+ container.setAttribute("data-cs-runtime", "latest");
169
+ container.style.display = "contents";
170
+ document.body.append(container);
171
+ mounted = runtime.mount(container, propsRef.current);
172
+ handle.current = mounted;
173
+ };
174
+ const cancelIdle = whenIdle(() => {
175
+ start().catch(
176
+ (error) => console.warn("[comment-service] The toolbar could not start.", error)
177
+ );
178
+ }, IDLE_TIMEOUT_MS);
179
+ return () => {
180
+ cancelled = true;
181
+ cancelIdle();
182
+ handle.current = null;
183
+ queueMicrotask(() => {
184
+ mounted?.unmount();
185
+ container?.remove();
186
+ });
187
+ };
188
+ }, [sessionPath]);
189
+ return null;
190
+ }
191
+ export {
192
+ CommentToolbar
193
+ };
@@ -0,0 +1 @@
1
+ export * from "./types/server.js";
package/dist/server.js ADDED
@@ -0,0 +1,87 @@
1
+ // src/server/index.ts
2
+ function readCommentServiceConfig(env = process.env) {
3
+ const url = env.COMMENT_SERVICE_URL?.replace(/\/$/, "");
4
+ const project = env.COMMENT_SERVICE_PROJECT;
5
+ const environment = env.COMMENT_SERVICE_ENVIRONMENT;
6
+ const secret = env.COMMENT_SERVICE_SECRET;
7
+ if (!url || !project || !environment || !secret) return null;
8
+ const linked = (env.COMMENT_SERVICE_LINKED ?? "").split(/[\s,]+/).filter(Boolean);
9
+ return { url, project, environment, secret, linked };
10
+ }
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;
19
+ }
20
+ }
21
+ function requestOrigin(request) {
22
+ const declared = declaredOrigin(request);
23
+ if (declared) return declared;
24
+ const url = new URL(request.url);
25
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
26
+ if (!host || host.includes("/")) return url.origin;
27
+ const proto = (request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")).split(",")[0].trim();
28
+ return `${proto}://${host}`;
29
+ }
30
+ async function handleSessionRequest(request, config = readCommentServiceConfig()) {
31
+ const headers = { "cache-control": "no-store" };
32
+ if (!config) return Response.json({ enabled: false }, { headers });
33
+ const origin = requestOrigin(request);
34
+ let response;
35
+ try {
36
+ response = await fetch(`${config.url}/v1/host/session`, {
37
+ method: "POST",
38
+ headers: {
39
+ authorization: `Bearer ${config.secret}`,
40
+ "content-type": "application/json"
41
+ },
42
+ body: JSON.stringify({
43
+ project: config.project,
44
+ environment: config.environment,
45
+ origin,
46
+ linked: config.linked ?? []
47
+ })
48
+ });
49
+ } catch {
50
+ return Response.json(
51
+ { enabled: false, error: "Comment service is unreachable." },
52
+ { status: 502, headers }
53
+ );
54
+ }
55
+ if (!response.ok) {
56
+ const payload2 = await response.json().catch(() => null);
57
+ return Response.json(
58
+ {
59
+ enabled: false,
60
+ error: payload2?.error?.message ?? "Comment service rejected this environment.",
61
+ origin,
62
+ status: response.status
63
+ },
64
+ { status: 502, headers }
65
+ );
66
+ }
67
+ const payload = await response.json();
68
+ if (payload.unknownLinked?.length) {
69
+ console.warn(
70
+ `[comment-service] COMMENT_SERVICE_LINKED: no environment named ${payload.unknownLinked.join(", ")} in project ${config.project}.`
71
+ );
72
+ }
73
+ return Response.json(
74
+ {
75
+ enabled: true,
76
+ apiUrl: config.url,
77
+ project: config.project,
78
+ environment: config.environment,
79
+ hostToken: payload.token
80
+ },
81
+ { headers }
82
+ );
83
+ }
84
+ export {
85
+ handleSessionRequest,
86
+ readCommentServiceConfig
87
+ };
@@ -0,0 +1,10 @@
1
+ export declare function toProjectPath(filename: string, root: string): string;
2
+ /**
3
+ * Stamp the root node of each component of each component with a stable file + component name.
4
+ * Line numbers are intentionally not included: moving the component in the file
5
+ * or on the page must not change the stamp. A root that is itself a component
6
+ * (`<DropdownMenu>`, `<Menu.Root>`) gets the stamp as a prop: it reaches the DOM when
7
+ * the component spreads its props, and the toolbar reads it from React's tree when
8
+ * the component renders into a portal.
9
+ */
10
+ export declare function annotateSource(code: string, filename: string, root: string): string;
@@ -0,0 +1,14 @@
1
+ /** A host app that serves the toolbar sets this, in its env files, its Wrangler `vars`, or the build's environment. */
2
+ export declare const CONFIG_KEY = "COMMENT_SERVICE_URL";
3
+ /**
4
+ * Whether to stamp components. An explicit option or COMMENT_SERVICE_ENABLED wins; otherwise the dev server always
5
+ * stamps, and a build stamps only when it can see comment-service configuration.
6
+ */
7
+ export declare function stampingEnabled({ option, flag, command, configured, }: {
8
+ option: boolean | undefined;
9
+ flag: string | undefined;
10
+ command: "build" | "serve";
11
+ configured: () => boolean;
12
+ }): boolean;
13
+ /** Comment-service configuration a build can see: its environment, Vite env files for the mode, or Wrangler config. */
14
+ export declare function buildConfigured(root: string, mode: string, env?: Record<string, string | undefined>): boolean;
@@ -0,0 +1,2 @@
1
+ export { annotateSource, toProjectPath } from "./annotate.js";
2
+ export { CONFIG_KEY, buildConfigured, stampingEnabled } from "./enabled.js";
@@ -0,0 +1,2 @@
1
+ export { describeAnchor, dropAncestors, resolveAnchor, validateAnchor } from "./resolve.js";
2
+ export type { Anchor, Candidate, Resolution, ResolutionStatus } from "./types.js";
@@ -0,0 +1,29 @@
1
+ import type { Anchor, Candidate, Resolution } from "./types.js";
2
+ /**
3
+ * Drop elements that contain another match, so a wrapping div doesn't steal the button.
4
+ *
5
+ * Sorted by path, an element's descendants ("0/1/…") come directly after it: "/" sorts before every digit, so
6
+ * nothing else can sit between "0/1" and "0/1/4". One pass over the sorted list finds every ancestor, instead of
7
+ * comparing every pair; a page section with thousands of elements used to take seconds here.
8
+ */
9
+ export declare function dropAncestors(candidates: Candidate[]): Candidate[];
10
+ /**
11
+ * Resolve a stored anchor against the elements currently on the route.
12
+ *
13
+ * Order is deliberate:
14
+ * 1. An explicit id (`data-cs-anchor`, `data-testid`, stable `id`) wins.
15
+ * 2. Otherwise the match is component file + component name + role + label.
16
+ * 3. Sibling index and pixel position are ignored, so inserting, reordering,
17
+ * or moving the element (including out of a sheet) does not retarget the comment.
18
+ * 4. A tie is ambiguous. We do not fall back to "the first one" or the old index.
19
+ * 5. A unique component whose label changed is "drifted": still shown, marked uncertain.
20
+ * 6. A match that exists but is inside a closed dialog is "hidden", not deleted.
21
+ * 7. No match is "unavailable". The thread stays in the inbox.
22
+ *
23
+ * `candidates` may be a targeted subset of the page, as long as it holds every element with one of the anchor's
24
+ * explicit ids and every element owned by a component of the anchor's name. The drift check looks at everything
25
+ * inside the component's root, so such callers pass `inside` to supply those candidates on demand.
26
+ */
27
+ export declare function resolveAnchor(anchor: Anchor, candidates: Candidate[], inside?: (root: Candidate) => Candidate[]): Resolution;
28
+ export declare function describeAnchor(anchor: Anchor): string;
29
+ export declare function validateAnchor(input: unknown): Anchor | null;
@@ -0,0 +1,65 @@
1
+ /** Stable identity for a commented element. Pixel and DOM-index position are not part of the match. */
2
+ export type Anchor = {
3
+ v: 1;
4
+ /** Pathname only, no query or hash. */
5
+ route: string;
6
+ /**
7
+ * Dialog or sheet the element lived in when the comment was written.
8
+ * Used to explain a hidden thread. Not required to match after the element moves out of the overlay.
9
+ */
10
+ overlay: {
11
+ role: string;
12
+ name: string;
13
+ } | null;
14
+ /** Opt-in stable ids: data-cs-anchor, data-testid, or a human id. Highest priority. */
15
+ explicit: Array<{
16
+ attr: string;
17
+ value: string;
18
+ }>;
19
+ /** React component that owns the element. File path is relative and has no line number. */
20
+ component: {
21
+ name: string;
22
+ file: string | null;
23
+ } | null;
24
+ /** Component names from the tree, outer to inner. Diagnostic. */
25
+ stack: string[];
26
+ role: string | null;
27
+ accName: string | null;
28
+ text: string | null;
29
+ tag: string;
30
+ /** Sibling path inside the component. Stored for agents, never used to choose a match. */
31
+ intra: string | null;
32
+ };
33
+ export type Candidate = {
34
+ key: string;
35
+ route: string;
36
+ visible: boolean;
37
+ /** Index path from the document body. Used only to tell ancestors from descendants. */
38
+ path: string;
39
+ componentRoot: boolean;
40
+ overlay: {
41
+ role: string;
42
+ name: string;
43
+ } | null;
44
+ explicit: Array<{
45
+ attr: string;
46
+ value: string;
47
+ }>;
48
+ component: {
49
+ name: string;
50
+ file: string | null;
51
+ } | null;
52
+ stack: string[];
53
+ role: string | null;
54
+ accName: string | null;
55
+ text: string | null;
56
+ tag: string;
57
+ intra: string | null;
58
+ };
59
+ export type ResolutionStatus = "pinned" | "drifted" | "hidden" | "ambiguous" | "unavailable";
60
+ export type Resolution = {
61
+ status: ResolutionStatus;
62
+ key: string | null;
63
+ score: number;
64
+ reason: string;
65
+ };
@@ -0,0 +1,2 @@
1
+ export { CommentToolbar } from "./runtime/loader.js";
2
+ export type { ToolbarPosition, ToolbarProps, ToolbarTheme } from "./runtime/toolbar.js";
@@ -0,0 +1,73 @@
1
+ import { type ClipboardEvent } from "react";
2
+ import { type Attachment, type Capture } from "./client.js";
3
+ import type { Session } from "./session.js";
4
+ export type Pending = {
5
+ key: string;
6
+ name: string;
7
+ size: number;
8
+ contentType: string;
9
+ preview: string | null;
10
+ status: "uploading" | "ready" | "failed";
11
+ id?: string;
12
+ error?: string;
13
+ capture?: Capture;
14
+ };
15
+ export declare function formatSize(bytes: number): string;
16
+ /** Files attached to one draft. Each upload starts immediately so posting doesn't wait on the network. */
17
+ export declare function useAttachments(session: Session | null, memberToken: string | null, onError: (message: string) => void, environment?: string): {
18
+ items: Pending[];
19
+ add: (files: Array<{
20
+ blob: Blob;
21
+ name: string;
22
+ capture?: Capture;
23
+ }>) => void;
24
+ remove: (key: string) => void;
25
+ reset: () => void;
26
+ ids: string[];
27
+ uploading: boolean;
28
+ };
29
+ export declare function filesFromClipboard(event: ClipboardEvent): {
30
+ blob: Blob;
31
+ name: string;
32
+ }[];
33
+ export type PreviewFile = {
34
+ id: string;
35
+ name: string;
36
+ contentType: string;
37
+ url: string;
38
+ capture?: Capture;
39
+ };
40
+ /** Images, videos, and replays waiting in a draft, in tray order, for the preview beside the composer. */
41
+ export declare function pendingPreviews(items: Pending[]): PreviewFile[];
42
+ export declare function AttachmentTray({ items, activeKey, onPreview, onRemove, }: {
43
+ items: Pending[];
44
+ activeKey?: string | null;
45
+ onPreview?: (item: Pending) => void;
46
+ onRemove: (key: string) => void;
47
+ }): import("react/jsx-runtime").JSX.Element | null;
48
+ export declare function AttachButtons({ onFiles, onSnapshot, onRecord, onReplay, capturing, }: {
49
+ onFiles: (files: Array<{
50
+ blob: Blob;
51
+ name: string;
52
+ }>) => void;
53
+ onSnapshot: () => void;
54
+ onRecord: () => void;
55
+ onReplay: (() => void) | null;
56
+ capturing: boolean;
57
+ }): import("react/jsx-runtime").JSX.Element;
58
+ /** Images, videos, and replays in the order a thread shows them, so preview arrows follow the thread. */
59
+ export declare function visualAttachments(messages: Array<{
60
+ attachments?: Attachment[];
61
+ }> | undefined): Attachment[];
62
+ export declare function MessageAttachments({ attachments, activeId, onPreview, }: {
63
+ attachments?: Attachment[];
64
+ activeId?: string | null;
65
+ onPreview: (file: Attachment) => void;
66
+ }): import("react/jsx-runtime").JSX.Element | null;
67
+ /** The image, video, or replay open beside a thread. Arrows walk the thread's other previewable files. */
68
+ export declare function MediaPane({ files, fileId, onSelect, onClose, }: {
69
+ files: PreviewFile[];
70
+ fileId: string;
71
+ onSelect: (fileId: string) => void;
72
+ onClose: () => void;
73
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Identifies this build of the runtime. `scripts/app-comments/build-toolbar.mjs` defines it from a hash of the
3
+ * runtime's source and writes the same value to `/v1/toolbar/build.json` next to the bundle, so a running toolbar
4
+ * can tell when the worker started serving a newer build. "dev" when the bundle was built without it (tests).
5
+ */
6
+ export declare const runtimeBuild: string;
@@ -0,0 +1,35 @@
1
+ import type { Capture } from "./client.js";
2
+ type AddFiles = (files: Array<{
3
+ blob: Blob;
4
+ name: string;
5
+ capture?: Capture;
6
+ }>) => void;
7
+ /** Records the current tab, then asks before the file is attached. Nothing is uploaded here. */
8
+ export declare function useTabRecording(onError: (message: string) => void): {
9
+ phase: "idle" | "recording" | "review";
10
+ elapsed: number;
11
+ review: {
12
+ url: string;
13
+ blob: Blob;
14
+ } | null;
15
+ tooBig: boolean;
16
+ begin: (add: AddFiles) => Promise<void>;
17
+ stop: () => void | undefined;
18
+ cancel: () => void;
19
+ keep: () => void;
20
+ again: () => void;
21
+ };
22
+ export declare function RecordChrome({ phase, elapsed, review, tooBig, onStop, onCancel, onKeep, onAgain, }: {
23
+ phase: "recording" | "review";
24
+ elapsed: number;
25
+ review: {
26
+ url: string;
27
+ blob: Blob;
28
+ } | null;
29
+ tooBig: boolean;
30
+ onStop: () => void;
31
+ onCancel: () => void;
32
+ onKeep: () => void;
33
+ onAgain: () => void;
34
+ }): import("react/jsx-runtime").JSX.Element | null;
35
+ export {};
@@ -0,0 +1,30 @@
1
+ import type { Anchor, Candidate, Resolution } from "../core/index.js";
2
+ type Owned = {
3
+ component: {
4
+ name: string;
5
+ file: string | null;
6
+ } | null;
7
+ stack: string[];
8
+ componentRoot: boolean;
9
+ owner: Element | null;
10
+ };
11
+ /** Each field is computed when first read, so a resolve only pays for what the match looks at. */
12
+ export declare function candidateFromElement(element: Element, route: string, key: string, owned?: Owned, pathOf?: (element: Element) => string): Candidate;
13
+ export declare function captureAnchor(element: Element, route?: string): Anchor;
14
+ export type Resolved = {
15
+ resolution: Resolution;
16
+ element: Element | null;
17
+ };
18
+ /**
19
+ * Resolves anchors against the page as it is now, building candidates only for the elements that can decide the
20
+ * result: those carrying one of the anchor's explicit ids, those owned by a component of the anchor's name, and,
21
+ * when the label changed, those inside that component's root. Never walks the whole page for a stamped anchor.
22
+ * Reuse one resolver for anchors resolved together; make a new one once the page may have changed.
23
+ */
24
+ export declare function createResolver(route?: string): {
25
+ resolve(anchor: Anchor): Resolved;
26
+ };
27
+ export declare function resolveInDocument(anchor: Anchor, route?: string): Resolved;
28
+ export declare function elementAt(x: number, y: number): Element | null;
29
+ export declare function describeElement(element: Element): string;
30
+ export {};