@opengeni/react 3.8.0-canary.0 → 3.8.0-canary.1

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 (72) hide show
  1. package/README.md +6 -0
  2. package/dist/chat.js +1 -1
  3. package/dist/chunk-5M6VTFJ5.js +171 -0
  4. package/dist/chunk-5M6VTFJ5.js.map +1 -0
  5. package/dist/{chunk-IOLVN74D.js → chunk-6J56T3MG.js} +15 -1
  6. package/dist/chunk-6J56T3MG.js.map +1 -0
  7. package/dist/{chunk-BQGIXRKV.js → chunk-7BWFU6H4.js} +22 -4
  8. package/dist/chunk-7BWFU6H4.js.map +1 -0
  9. package/dist/{chunk-3HIF5IFC.js → chunk-7W6PVVTK.js} +63 -18
  10. package/dist/chunk-7W6PVVTK.js.map +1 -0
  11. package/dist/{chunk-ZFOFY5ST.js → chunk-NLTS7JHI.js} +16 -31
  12. package/dist/chunk-NLTS7JHI.js.map +1 -0
  13. package/dist/chunk-NOSYBS2D.js +208 -0
  14. package/dist/chunk-NOSYBS2D.js.map +1 -0
  15. package/dist/{chunk-F2CFHN3Y.js → chunk-YAIPFO5D.js} +2 -1
  16. package/dist/chunk-YAIPFO5D.js.map +1 -0
  17. package/dist/chunk-YBM6CQRU.js +292 -0
  18. package/dist/chunk-YBM6CQRU.js.map +1 -0
  19. package/dist/chunk-ZYRGEKWS.js +216 -0
  20. package/dist/chunk-ZYRGEKWS.js.map +1 -0
  21. package/dist/components/composer.d.ts +4 -2
  22. package/dist/components/message-timeline.d.ts +9 -2
  23. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  24. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  25. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  26. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  27. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  28. package/dist/components/timeline-annotations-dialog.d.ts +1 -1
  29. package/dist/components/timeline-annotations.d.ts +8 -2
  30. package/dist/composer.js +2 -2
  31. package/dist/hooks/use-composer.d.ts +2 -0
  32. package/dist/index.js +9 -6
  33. package/dist/index.js.map +1 -1
  34. package/dist/interaction.js +6 -6
  35. package/dist/machines.js +1 -1
  36. package/dist/{queue-surface-implementation-XL2A6PBM.js → queue-surface-implementation-LK3S4BIW.js} +5 -2
  37. package/dist/{queue-surface-implementation-XL2A6PBM.js.map → queue-surface-implementation-LK3S4BIW.js.map} +1 -1
  38. package/dist/session-ui.js +8 -5
  39. package/dist/session.js +1 -1
  40. package/dist/timeline-annotation-markers-DRSTLEM6.js +165 -0
  41. package/dist/timeline-annotation-markers-DRSTLEM6.js.map +1 -0
  42. package/dist/timeline-annotation-selection-IHLUGVND.js +236 -0
  43. package/dist/timeline-annotation-selection-IHLUGVND.js.map +1 -0
  44. package/dist/timeline-annotations-dialog-6Q53N5FG.js +262 -0
  45. package/dist/timeline-annotations-dialog-6Q53N5FG.js.map +1 -0
  46. package/package.json +2 -2
  47. package/src/components/chat-composer.tsx +5 -2
  48. package/src/components/composer.tsx +40 -5
  49. package/src/components/copy-button.tsx +1 -0
  50. package/src/components/machines-dashboard.tsx +1 -16
  51. package/src/components/message-timeline.tsx +357 -297
  52. package/src/components/timeline-annotation-chrome.tsx +183 -0
  53. package/src/components/timeline-annotation-layout.ts +268 -0
  54. package/src/components/timeline-annotation-markers.tsx +185 -0
  55. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  56. package/src/components/timeline-annotation-selection.tsx +99 -54
  57. package/src/components/timeline-annotation-shared.ts +245 -0
  58. package/src/components/timeline-annotations-dialog.tsx +172 -128
  59. package/src/components/timeline-annotations.tsx +178 -45
  60. package/src/hooks/use-composer.ts +17 -0
  61. package/styles/compiled.css +86 -22
  62. package/dist/chunk-3HIF5IFC.js.map +0 -1
  63. package/dist/chunk-BQGIXRKV.js.map +0 -1
  64. package/dist/chunk-F2CFHN3Y.js.map +0 -1
  65. package/dist/chunk-IOLVN74D.js.map +0 -1
  66. package/dist/chunk-W3OUAH6M.js +0 -170
  67. package/dist/chunk-W3OUAH6M.js.map +0 -1
  68. package/dist/chunk-ZFOFY5ST.js.map +0 -1
  69. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  70. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  71. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  72. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
@@ -0,0 +1,292 @@
1
+ import {
2
+ AnnotationAccentRow,
3
+ AnnotationNotePreview,
4
+ AnnotationQuoteSourceButton,
5
+ TimelineAnnotationSourceRootContext,
6
+ useTimelineAnnotationSourceRoot
7
+ } from "./chunk-5M6VTFJ5.js";
8
+ import {
9
+ ANNOTATION_CARD_STACK_SCROLL_AT
10
+ } from "./chunk-ZYRGEKWS.js";
11
+ import {
12
+ annotationDisplayOrdinal,
13
+ annotationHasNote,
14
+ resolveAnnotationRevealRoot
15
+ } from "./chunk-NOSYBS2D.js";
16
+ import {
17
+ cn
18
+ } from "./chunk-22KP6LR5.js";
19
+
20
+ // src/components/timeline-annotations.tsx
21
+ import { QuoteIcon, XIcon } from "lucide-react";
22
+ import {
23
+ Suspense,
24
+ lazy,
25
+ useEffect,
26
+ useLayoutEffect,
27
+ useRef,
28
+ useState
29
+ } from "react";
30
+ import { jsx, jsxs } from "react/jsx-runtime";
31
+ var TimelineAnnotationsDialog = lazy(() => import("./timeline-annotations-dialog-6Q53N5FG.js"));
32
+ function AnnotationSourceRootBridge({
33
+ triggerRef,
34
+ children
35
+ }) {
36
+ const inheritedRoot = useTimelineAnnotationSourceRoot();
37
+ const conversationRootRef = useRef(null);
38
+ if (!inheritedRoot) {
39
+ conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);
40
+ }
41
+ useLayoutEffect(() => {
42
+ if (inheritedRoot) return;
43
+ conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);
44
+ });
45
+ if (inheritedRoot) return children;
46
+ return /* @__PURE__ */ jsx(TimelineAnnotationSourceRootContext.Provider, { value: conversationRootRef, children });
47
+ }
48
+ function TimelineAnnotationCards({
49
+ annotations,
50
+ onRevealSource,
51
+ className
52
+ }) {
53
+ const [unavailableId, setUnavailableId] = useState(null);
54
+ if (annotations.length === 0) return null;
55
+ return /* @__PURE__ */ jsx(
56
+ "div",
57
+ {
58
+ "data-og-annotation-cards": "",
59
+ className: cn(
60
+ "grid gap-2",
61
+ annotations.length >= ANNOTATION_CARD_STACK_SCROLL_AT && "max-h-[min(28rem,55vh)] overflow-x-hidden overflow-y-auto overscroll-contain pr-1",
62
+ className
63
+ ),
64
+ children: annotations.map((annotation, index) => {
65
+ const ordinal = annotationDisplayOrdinal(annotation, index);
66
+ return /* @__PURE__ */ jsx("section", { "aria-label": `Annotation ${ordinal}`, children: /* @__PURE__ */ jsxs(AnnotationAccentRow, { children: [
67
+ /* @__PURE__ */ jsxs("p", { className: "mb-0.5 text-og-xs font-medium tabular-nums text-og-fg-subtle", children: [
68
+ "Annotation ",
69
+ ordinal
70
+ ] }),
71
+ /* @__PURE__ */ jsx(
72
+ AnnotationQuoteSourceButton,
73
+ {
74
+ annotation,
75
+ lines: 2,
76
+ onRevealSource,
77
+ onUnavailable: setUnavailableId
78
+ }
79
+ ),
80
+ unavailableId === annotation.id ? /* @__PURE__ */ jsx("p", { role: "status", className: "text-og-xs text-og-status-waiting", children: "Source is outside the loaded timeline window." }) : null,
81
+ /* @__PURE__ */ jsx(
82
+ AnnotationNotePreview,
83
+ {
84
+ note: annotation.note,
85
+ annotationId: annotation.id,
86
+ ordinal
87
+ }
88
+ )
89
+ ] }) }, annotation.id);
90
+ })
91
+ }
92
+ );
93
+ }
94
+ function TimelineAnnotationsChip({
95
+ annotations,
96
+ editable = false,
97
+ focusAnnotationId,
98
+ onFocusConsumed,
99
+ onUpdate,
100
+ onRemove,
101
+ onRevealSource,
102
+ className
103
+ }) {
104
+ const focusRequested = Boolean(
105
+ focusAnnotationId && annotations.some((item) => item.id === focusAnnotationId)
106
+ );
107
+ const [open, setOpen] = useState(focusRequested);
108
+ const triggerRef = useRef(null);
109
+ const openedFocusId = useRef(focusRequested ? focusAnnotationId ?? null : null);
110
+ useEffect(() => {
111
+ if (!focusRequested || !focusAnnotationId) {
112
+ if (!focusRequested) openedFocusId.current = null;
113
+ return;
114
+ }
115
+ if (openedFocusId.current === focusAnnotationId) return;
116
+ openedFocusId.current = focusAnnotationId;
117
+ setOpen(true);
118
+ }, [focusAnnotationId, focusRequested]);
119
+ if (annotations.length === 0) return null;
120
+ const countLabel = `${annotations.length} ${annotations.length === 1 ? "annotation" : "annotations"}`;
121
+ const incomplete = annotations.some((annotation) => !annotationHasNote(annotation.note));
122
+ const canClear = Boolean(editable && onRemove);
123
+ const dismiss = (restoreFocus) => {
124
+ setOpen(false);
125
+ onFocusConsumed?.();
126
+ if (restoreFocus) triggerRef.current?.focus();
127
+ };
128
+ return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex max-w-full items-center", className), children: [
129
+ /* @__PURE__ */ jsxs(
130
+ "span",
131
+ {
132
+ className: cn(
133
+ "inline-flex max-w-full items-center rounded-full border bg-og-surface-1 text-og-fg-muted",
134
+ incomplete ? "border-og-accent/45 bg-og-accent-soft/50" : "border-og-border"
135
+ ),
136
+ children: [
137
+ /* @__PURE__ */ jsxs(
138
+ "button",
139
+ {
140
+ ref: triggerRef,
141
+ type: "button",
142
+ className: cn(
143
+ "inline-flex min-h-8 min-w-0 max-w-full items-center gap-1.5 rounded-full border-0 bg-transparent py-1 text-og-sm font-medium outline-hidden transition hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]",
144
+ canClear ? "pr-1 pl-2.5" : "px-2.5"
145
+ ),
146
+ "aria-label": `Review ${countLabel}`,
147
+ "aria-haspopup": "dialog",
148
+ "aria-expanded": open,
149
+ onClick: () => setOpen((current) => !current),
150
+ children: [
151
+ /* @__PURE__ */ jsx(QuoteIcon, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": "true" }),
152
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: countLabel })
153
+ ]
154
+ }
155
+ ),
156
+ canClear ? /* @__PURE__ */ jsx(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border-0 bg-transparent text-og-fg-subtle outline-hidden hover:text-og-status-failed focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:size-11",
161
+ "aria-label": "Remove all annotations",
162
+ onClick: () => {
163
+ for (const annotation of annotations) onRemove?.(annotation.id);
164
+ },
165
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5", "aria-hidden": "true" })
166
+ }
167
+ ) : null
168
+ ]
169
+ }
170
+ ),
171
+ open ? /* @__PURE__ */ jsx(AnnotationSourceRootBridge, { triggerRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
172
+ TimelineAnnotationsDialog,
173
+ {
174
+ annotations,
175
+ editable,
176
+ focusAnnotationId,
177
+ onFocusConsumed,
178
+ onUpdate,
179
+ onRemove,
180
+ onRevealSource,
181
+ triggerRef,
182
+ countLabel,
183
+ onDismiss: dismiss
184
+ }
185
+ ) }) }) : null
186
+ ] });
187
+ }
188
+
189
+ // src/components/queue-draft-policy.ts
190
+ function requestQueueDraftEdit(composer, confirmReplacement, editImmediately) {
191
+ if (composer.hasDraftContent()) {
192
+ confirmReplacement();
193
+ } else {
194
+ editImmediately();
195
+ }
196
+ }
197
+
198
+ // src/components/queue-surface-state.tsx
199
+ import { Loader2Icon, RotateCwIcon } from "lucide-react";
200
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
201
+ function EmptyQueueStateSurface({ queue }) {
202
+ const hasError = Boolean(queue.error || queue.mutationError);
203
+ return /* @__PURE__ */ jsx2(
204
+ "div",
205
+ {
206
+ className: "og-root mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6",
207
+ "data-testid": "queue-surface",
208
+ children: /* @__PURE__ */ jsxs2("div", { className: "overflow-hidden rounded-lg border border-border bg-surface/80 shadow-sm", children: [
209
+ queue.stoppingPreviousAttempt ? /* @__PURE__ */ jsx2(QueueStoppingStatus, { queue, dividerAfter: hasError }) : null,
210
+ hasError ? /* @__PURE__ */ jsx2(QueueErrorAlert, { queue }) : null
211
+ ] })
212
+ }
213
+ );
214
+ }
215
+ function QueueStoppingStatus({
216
+ queue,
217
+ dividerAfter = false
218
+ }) {
219
+ return /* @__PURE__ */ jsxs2(
220
+ "div",
221
+ {
222
+ role: "status",
223
+ "aria-live": "polite",
224
+ className: `flex min-h-11 items-center gap-2.5 bg-status-waiting/[0.07] px-3 py-2 text-og-control text-fg ${dividerAfter ? "border-b border-status-waiting/20" : ""}`,
225
+ "data-testid": "stopping-previous-attempt",
226
+ children: [
227
+ /* @__PURE__ */ jsx2(
228
+ Loader2Icon,
229
+ {
230
+ "aria-hidden": "true",
231
+ className: "size-3.5 shrink-0 animate-spin text-status-waiting motion-reduce:animate-none"
232
+ }
233
+ ),
234
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0", children: [
235
+ /* @__PURE__ */ jsx2("span", { className: "font-medium", children: queue.effectiveControl?.state === "paused" ? "Stopping current attempt\u2026" : "Stopping previous attempt\u2026" }),
236
+ " ",
237
+ /* @__PURE__ */ jsx2("span", { className: "text-fg-muted", children: queue.effectiveControl?.state === "paused" ? "Queued work stays saved until you resume." : "Queued work is saved and starts automatically." })
238
+ ] })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ function QueueErrorAlert({
244
+ queue,
245
+ dividerBefore = false
246
+ }) {
247
+ return /* @__PURE__ */ jsx2("div", { className: `${dividerBefore ? "border-t border-border" : ""} p-2`, children: /* @__PURE__ */ jsxs2(
248
+ "div",
249
+ {
250
+ role: "alert",
251
+ className: "flex min-w-0 max-w-full flex-wrap items-start gap-2 rounded-md bg-status-failed/10 px-2 py-1.5 text-og-control text-status-failed",
252
+ children: [
253
+ /* @__PURE__ */ jsx2(
254
+ "span",
255
+ {
256
+ role: "region",
257
+ "aria-label": "Queue error details",
258
+ tabIndex: 0,
259
+ className: "max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-hidden [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40",
260
+ "data-testid": "queue-error-message",
261
+ dir: "auto",
262
+ children: (queue.mutationError ?? queue.error)?.message
263
+ }
264
+ ),
265
+ /* @__PURE__ */ jsx2(
266
+ "button",
267
+ {
268
+ type: "button",
269
+ onClick: () => {
270
+ queue.clearMutationError();
271
+ void queue.refresh();
272
+ },
273
+ "aria-label": "Dismiss queue error and retry",
274
+ title: "Retry loading the queue",
275
+ className: "ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-hidden transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]",
276
+ children: /* @__PURE__ */ jsx2(RotateCwIcon, { className: "size-3.5" })
277
+ }
278
+ )
279
+ ]
280
+ }
281
+ ) });
282
+ }
283
+
284
+ export {
285
+ TimelineAnnotationCards,
286
+ TimelineAnnotationsChip,
287
+ requestQueueDraftEdit,
288
+ EmptyQueueStateSurface,
289
+ QueueStoppingStatus,
290
+ QueueErrorAlert
291
+ };
292
+ //# sourceMappingURL=chunk-YBM6CQRU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/timeline-annotations.tsx","../src/components/queue-draft-policy.ts","../src/components/queue-surface-state.tsx"],"sourcesContent":["import type { TimelineAnnotationSource } from \"@opengeni/sdk\";\nimport { QuoteIcon, XIcon } from \"lucide-react\";\nimport {\n Suspense,\n lazy,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n AnnotationAccentRow,\n AnnotationNotePreview,\n AnnotationQuoteSourceButton,\n type TimelineAnnotationLike,\n} from \"./timeline-annotation-chrome\";\nimport { ANNOTATION_CARD_STACK_SCROLL_AT } from \"./timeline-annotation-layout\";\nimport {\n TimelineAnnotationSourceRootContext,\n useTimelineAnnotationSourceRoot,\n} from \"./timeline-annotation-reveal-context\";\nimport {\n annotationDisplayOrdinal,\n annotationHasNote,\n resolveAnnotationRevealRoot,\n} from \"./timeline-annotation-shared\";\n\nexport type { TimelineAnnotationLike } from \"./timeline-annotation-chrome\";\n\nconst TimelineAnnotationsDialog = lazy(() => import(\"./timeline-annotations-dialog\"));\n\nfunction AnnotationSourceRootBridge({\n triggerRef,\n children,\n}: {\n triggerRef: RefObject<HTMLElement | null>;\n children: ReactNode;\n}) {\n const inheritedRoot = useTimelineAnnotationSourceRoot();\n const conversationRootRef = useRef<HTMLElement | null>(null);\n if (!inheritedRoot) {\n conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);\n }\n useLayoutEffect(() => {\n if (inheritedRoot) return;\n conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);\n });\n if (inheritedRoot) return children;\n return (\n <TimelineAnnotationSourceRootContext.Provider value={conversationRootRef}>\n {children}\n </TimelineAnnotationSourceRootContext.Provider>\n );\n}\n\nexport function TimelineAnnotationCards({\n annotations,\n onRevealSource,\n className,\n}: {\n annotations: readonly TimelineAnnotationLike[];\n onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;\n className?: string | undefined;\n}) {\n const [unavailableId, setUnavailableId] = useState<string | null>(null);\n if (annotations.length === 0) return null;\n return (\n <div\n data-og-annotation-cards=\"\"\n className={cn(\n \"grid gap-2\",\n annotations.length >= ANNOTATION_CARD_STACK_SCROLL_AT &&\n \"max-h-[min(28rem,55vh)] overflow-x-hidden overflow-y-auto overscroll-contain pr-1\",\n className,\n )}\n >\n {annotations.map((annotation, index) => {\n const ordinal = annotationDisplayOrdinal(annotation, index);\n return (\n <section key={annotation.id} aria-label={`Annotation ${ordinal}`}>\n <AnnotationAccentRow>\n <p className=\"mb-0.5 text-og-xs font-medium tabular-nums text-og-fg-subtle\">\n Annotation {ordinal}\n </p>\n <AnnotationQuoteSourceButton\n annotation={annotation}\n lines={2}\n onRevealSource={onRevealSource}\n onUnavailable={setUnavailableId}\n />\n {unavailableId === annotation.id ? (\n <p role=\"status\" className=\"text-og-xs text-og-status-waiting\">\n Source is outside the loaded timeline window.\n </p>\n ) : null}\n <AnnotationNotePreview\n note={annotation.note}\n annotationId={annotation.id}\n ordinal={ordinal}\n />\n </AnnotationAccentRow>\n </section>\n );\n })}\n </div>\n );\n}\n\nexport function TimelineAnnotationsChip({\n annotations,\n editable = false,\n focusAnnotationId,\n onFocusConsumed,\n onUpdate,\n onRemove,\n onRevealSource,\n className,\n}: {\n annotations: readonly TimelineAnnotationLike[];\n editable?: boolean | undefined;\n focusAnnotationId?: string | null | undefined;\n onFocusConsumed?: (() => void) | undefined;\n onUpdate?: ((id: string, note: string) => void) | undefined;\n onRemove?: ((id: string) => void) | undefined;\n onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;\n className?: string | undefined;\n}) {\n const focusRequested = Boolean(\n focusAnnotationId && annotations.some((item) => item.id === focusAnnotationId),\n );\n const [open, setOpen] = useState(focusRequested);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const openedFocusId = useRef<string | null>(focusRequested ? (focusAnnotationId ?? null) : null);\n\n useEffect(() => {\n if (!focusRequested || !focusAnnotationId) {\n if (!focusRequested) openedFocusId.current = null;\n return;\n }\n if (openedFocusId.current === focusAnnotationId) return;\n openedFocusId.current = focusAnnotationId;\n setOpen(true);\n }, [focusAnnotationId, focusRequested]);\n\n if (annotations.length === 0) return null;\n const countLabel = `${annotations.length} ${annotations.length === 1 ? \"annotation\" : \"annotations\"}`;\n const incomplete = annotations.some((annotation) => !annotationHasNote(annotation.note));\n const canClear = Boolean(editable && onRemove);\n\n const dismiss = (restoreFocus: boolean) => {\n setOpen(false);\n onFocusConsumed?.();\n if (restoreFocus) triggerRef.current?.focus();\n };\n\n return (\n <span className={cn(\"inline-flex max-w-full items-center\", className)}>\n <span\n className={cn(\n \"inline-flex max-w-full items-center rounded-full border bg-og-surface-1 text-og-fg-muted\",\n incomplete ? \"border-og-accent/45 bg-og-accent-soft/50\" : \"border-og-border\",\n )}\n >\n <button\n ref={triggerRef}\n type=\"button\"\n className={cn(\n \"inline-flex min-h-8 min-w-0 max-w-full items-center gap-1.5 rounded-full border-0 bg-transparent py-1 text-og-sm font-medium outline-hidden transition hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]\",\n canClear ? \"pr-1 pl-2.5\" : \"px-2.5\",\n )}\n aria-label={`Review ${countLabel}`}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen((current) => !current)}\n >\n <QuoteIcon className=\"size-3.5 shrink-0 text-og-accent\" aria-hidden=\"true\" />\n <span className=\"min-w-0 truncate\">{countLabel}</span>\n </button>\n {canClear ? (\n <button\n type=\"button\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border-0 bg-transparent text-og-fg-subtle outline-hidden hover:text-og-status-failed focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:size-11\"\n aria-label=\"Remove all annotations\"\n onClick={() => {\n for (const annotation of annotations) onRemove?.(annotation.id);\n }}\n >\n <XIcon className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n ) : null}\n </span>\n {open ? (\n <AnnotationSourceRootBridge triggerRef={triggerRef}>\n <Suspense fallback={null}>\n <TimelineAnnotationsDialog\n annotations={annotations}\n editable={editable}\n focusAnnotationId={focusAnnotationId}\n onFocusConsumed={onFocusConsumed}\n onUpdate={onUpdate}\n onRemove={onRemove}\n onRevealSource={onRevealSource}\n triggerRef={triggerRef}\n countLabel={countLabel}\n onDismiss={dismiss}\n />\n </Suspense>\n </AnnotationSourceRootBridge>\n ) : null}\n </span>\n );\n}\n\nexport default TimelineAnnotationsChip;\n","import type { ComposerState } from \"../hooks/use-composer\";\n\n/**\n * Decide whether checking out a queued prompt would replace current composer\n * content. `hasDraftContent` reads the synchronous draft refs, so this stays\n * correct even when a controlled input update has not rendered yet.\n */\nexport function requestQueueDraftEdit(\n composer: Pick<ComposerState, \"hasDraftContent\">,\n confirmReplacement: () => void,\n editImmediately: () => void,\n): void {\n if (composer.hasDraftContent()) {\n confirmReplacement();\n } else {\n editImmediately();\n }\n}\n","import { Loader2Icon, RotateCwIcon } from \"lucide-react\";\n\nimport type { UseTurnQueueResult } from \"../hooks/use-turn-queue\";\n\ntype QueueStateProps = {\n queue: UseTurnQueueResult;\n};\n\nexport function EmptyQueueStateSurface({ queue }: QueueStateProps) {\n const hasError = Boolean(queue.error || queue.mutationError);\n return (\n <div\n className=\"og-root mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6\"\n data-testid=\"queue-surface\"\n >\n <div className=\"overflow-hidden rounded-lg border border-border bg-surface/80 shadow-sm\">\n {queue.stoppingPreviousAttempt ? (\n <QueueStoppingStatus queue={queue} dividerAfter={hasError} />\n ) : null}\n {hasError ? <QueueErrorAlert queue={queue} /> : null}\n </div>\n </div>\n );\n}\n\nexport function QueueStoppingStatus({\n queue,\n dividerAfter = false,\n}: QueueStateProps & { dividerAfter?: boolean }) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={`flex min-h-11 items-center gap-2.5 bg-status-waiting/[0.07] px-3 py-2 text-og-control text-fg ${\n dividerAfter ? \"border-b border-status-waiting/20\" : \"\"\n }`}\n data-testid=\"stopping-previous-attempt\"\n >\n <Loader2Icon\n aria-hidden=\"true\"\n className=\"size-3.5 shrink-0 animate-spin text-status-waiting motion-reduce:animate-none\"\n />\n <span className=\"min-w-0\">\n <span className=\"font-medium\">\n {queue.effectiveControl?.state === \"paused\"\n ? \"Stopping current attempt…\"\n : \"Stopping previous attempt…\"}\n </span>{\" \"}\n <span className=\"text-fg-muted\">\n {queue.effectiveControl?.state === \"paused\"\n ? \"Queued work stays saved until you resume.\"\n : \"Queued work is saved and starts automatically.\"}\n </span>\n </span>\n </div>\n );\n}\n\nexport function QueueErrorAlert({\n queue,\n dividerBefore = false,\n}: QueueStateProps & { dividerBefore?: boolean }) {\n return (\n <div className={`${dividerBefore ? \"border-t border-border\" : \"\"} p-2`}>\n <div\n role=\"alert\"\n className=\"flex min-w-0 max-w-full flex-wrap items-start gap-2 rounded-md bg-status-failed/10 px-2 py-1.5 text-og-control text-status-failed\"\n >\n <span\n role=\"region\"\n aria-label=\"Queue error details\"\n tabIndex={0}\n className=\"max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-hidden [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40\"\n data-testid=\"queue-error-message\"\n dir=\"auto\"\n >\n {(queue.mutationError ?? queue.error)?.message}\n </span>\n <button\n type=\"button\"\n onClick={() => {\n queue.clearMutationError();\n void queue.refresh();\n }}\n aria-label=\"Dismiss queue error and retry\"\n title=\"Retry loading the queue\"\n className=\"ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-hidden transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]\"\n >\n <RotateCwIcon className=\"size-3.5\" />\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -0,0 +1,216 @@
1
+ // src/components/timeline-annotation-layout.ts
2
+ var ANNOTATION_BADGE_GAP_PX = 32;
3
+ var ANNOTATION_BADGE_MIN_GAP_PX = 12;
4
+ var ANNOTATION_REVIEW_DIALOG_WIDTH_PX = 400;
5
+ var ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX = 32 * 16;
6
+ var ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX = 160;
7
+ var ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX = 80;
8
+ var ANNOTATION_REVIEW_MARGIN_PX = 12;
9
+ var ANNOTATION_NOTE_PREVIEW_CHARS = 280;
10
+ var ANNOTATION_CARD_STACK_SCROLL_AT = 4;
11
+ function clampAnnotationAxis(value, min, max) {
12
+ return Math.min(Math.max(value, min), Math.max(min, max));
13
+ }
14
+ function annotationBoxIntersects(a, b) {
15
+ return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;
16
+ }
17
+ function annotationViewportBox(viewportWidth, viewportHeight, scroller) {
18
+ const viewport = {
19
+ left: 0,
20
+ top: 0,
21
+ right: Math.max(1, viewportWidth),
22
+ bottom: Math.max(1, viewportHeight)
23
+ };
24
+ if (!scroller || scroller.right - scroller.left <= 0 || scroller.bottom - scroller.top <= 0) {
25
+ return viewport;
26
+ }
27
+ return {
28
+ left: Math.max(viewport.left, scroller.left),
29
+ top: Math.max(viewport.top, scroller.top),
30
+ right: Math.min(viewport.right, scroller.right),
31
+ bottom: Math.min(viewport.bottom, scroller.bottom)
32
+ };
33
+ }
34
+ function annotationNoteNeedsDisclosure(note) {
35
+ const trimmed = note.trim();
36
+ if (trimmed.length === 0) return false;
37
+ return trimmed.length > ANNOTATION_NOTE_PREVIEW_CHARS || trimmed.split("\n").length > 4;
38
+ }
39
+ function annotationAxisStops(min, max, gap) {
40
+ if (max <= min) return [min];
41
+ const stops = [];
42
+ for (let value = min; value <= max + 1e-6; value += gap) {
43
+ stops.push(value);
44
+ }
45
+ return stops;
46
+ }
47
+ function annotationPointKey(left, top) {
48
+ return `${Math.round(left * 100) / 100},${Math.round(top * 100) / 100}`;
49
+ }
50
+ function annotationPointFree(left, top, placed, gap) {
51
+ return placed.every((other) => Math.hypot(left - other.left, top - other.top) >= gap);
52
+ }
53
+ function annotationBadgePackGap(viewport, count, preferred = ANNOTATION_BADGE_GAP_PX) {
54
+ if (count <= 1) return preferred;
55
+ const width = Math.max(
56
+ 1,
57
+ viewport.right - ANNOTATION_REVIEW_MARGIN_PX - (viewport.left + ANNOTATION_REVIEW_MARGIN_PX)
58
+ );
59
+ const height = Math.max(1, viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX - (viewport.top + 8));
60
+ let gap = preferred;
61
+ const capacity = (step) => (Math.floor(width / step) + 1) * (Math.floor(height / step) + 1);
62
+ while (gap > ANNOTATION_BADGE_MIN_GAP_PX && capacity(gap) < count) {
63
+ gap -= 1;
64
+ }
65
+ return gap;
66
+ }
67
+ var ANNOTATION_BADGE_SHIFT_DIRS = [
68
+ [1, 0],
69
+ [1, 1],
70
+ [0, 1],
71
+ [-1, 1],
72
+ [-1, 0],
73
+ [-1, -1],
74
+ [0, -1],
75
+ [1, -1]
76
+ ];
77
+ function layoutAnnotationBadges(anchors, viewport, preferredGap = ANNOTATION_BADGE_GAP_PX) {
78
+ const minLeft = viewport.left + ANNOTATION_REVIEW_MARGIN_PX;
79
+ const maxLeft = viewport.right - ANNOTATION_REVIEW_MARGIN_PX;
80
+ const minTop = viewport.top + 8;
81
+ const maxTop = viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX;
82
+ const gap = annotationBadgePackGap(viewport, anchors.length, preferredGap);
83
+ const cells = annotationAxisStops(minLeft, maxLeft, gap).flatMap(
84
+ (left) => annotationAxisStops(minTop, maxTop, gap).map((top) => ({ left, top }))
85
+ );
86
+ const placed = [];
87
+ for (const anchor of [...anchors].sort((left, right) => left.ordinal - right.ordinal)) {
88
+ const originLeft = clampAnnotationAxis(anchor.left, minLeft, maxLeft);
89
+ const originTop = clampAnnotationAxis(anchor.top, minTop, maxTop);
90
+ const seen = /* @__PURE__ */ new Set();
91
+ const candidates = [];
92
+ const pushCandidate = (left, top) => {
93
+ if (left < minLeft || left > maxLeft || top < minTop || top > maxTop) return;
94
+ const key = annotationPointKey(left, top);
95
+ if (seen.has(key)) return;
96
+ seen.add(key);
97
+ candidates.push({ left, top });
98
+ };
99
+ pushCandidate(originLeft, originTop);
100
+ for (const [dx, dy] of ANNOTATION_BADGE_SHIFT_DIRS) {
101
+ const colliders = [...placed].sort(
102
+ (left, right) => right.left * dx + right.top * dy - (left.left * dx + left.top * dy)
103
+ );
104
+ for (const collider of colliders) {
105
+ pushCandidate(collider.left + dx * gap, collider.top + dy * gap);
106
+ }
107
+ }
108
+ const maxRadius = Math.ceil(Math.max(maxLeft - minLeft, maxTop - minTop) / Math.max(gap, 1)) + 1;
109
+ for (let radius = 1; radius <= maxRadius; radius++) {
110
+ const ring = [];
111
+ for (let dx = -radius; dx <= radius; dx++) {
112
+ for (let dy = -radius; dy <= radius; dy++) {
113
+ if (Math.max(Math.abs(dx), Math.abs(dy)) !== radius) continue;
114
+ ring.push([dx, dy]);
115
+ }
116
+ }
117
+ ring.sort((left, right) => {
118
+ const leftDir = (left[0] < 0 ? 2 : 0) + (left[1] < 0 ? 1 : 0);
119
+ const rightDir = (right[0] < 0 ? 2 : 0) + (right[1] < 0 ? 1 : 0);
120
+ if (leftDir !== rightDir) return leftDir - rightDir;
121
+ const hypot = Math.hypot(left[0], left[1]) - Math.hypot(right[0], right[1]);
122
+ if (hypot !== 0) return hypot;
123
+ if (left[0] !== right[0]) return right[0] - left[0];
124
+ return left[1] - right[1];
125
+ });
126
+ for (const [dx, dy] of ring) {
127
+ pushCandidate(originLeft + dx * gap, originTop + dy * gap);
128
+ }
129
+ }
130
+ for (const cell of cells) {
131
+ pushCandidate(cell.left, cell.top);
132
+ }
133
+ const free = candidates.find(
134
+ (point) => annotationPointFree(point.left, point.top, placed, gap)
135
+ );
136
+ let chosen = free ?? candidates[0];
137
+ if (!free && placed.length > 0) {
138
+ let bestMin = Number.NEGATIVE_INFINITY;
139
+ for (const point of candidates) {
140
+ const minDist = placed.reduce(
141
+ (current, other) => Math.min(current, Math.hypot(point.left - other.left, point.top - other.top)),
142
+ Number.POSITIVE_INFINITY
143
+ );
144
+ if (minDist > bestMin) {
145
+ bestMin = minDist;
146
+ chosen = point;
147
+ }
148
+ }
149
+ }
150
+ placed.push({ ...anchor, left: chosen.left, top: chosen.top });
151
+ }
152
+ return placed;
153
+ }
154
+ function scrollAnnotationRowIntoList(list, row) {
155
+ if (!list || !row || !list.contains(row)) return;
156
+ const listRect = list.getBoundingClientRect();
157
+ const rowRect = row.getBoundingClientRect();
158
+ if (rowRect.top < listRect.top) {
159
+ list.scrollTop -= listRect.top - rowRect.top;
160
+ return;
161
+ }
162
+ if (rowRect.bottom > listRect.bottom) {
163
+ list.scrollTop += rowRect.bottom - listRect.bottom;
164
+ }
165
+ }
166
+ function clampAnnotationDialogPlacement(input) {
167
+ const margin = ANNOTATION_REVIEW_MARGIN_PX;
168
+ const panelWidth = Math.min(
169
+ input.panelWidth ?? ANNOTATION_REVIEW_DIALOG_WIDTH_PX,
170
+ Math.max(1, input.viewportWidth - margin * 2)
171
+ );
172
+ const left = Math.min(
173
+ Math.max(margin, input.triggerLeft),
174
+ Math.max(margin, input.viewportWidth - panelWidth - margin)
175
+ );
176
+ const spaceBelow = input.viewportHeight - input.triggerBottom - margin;
177
+ const spaceAbove = input.triggerTop - margin;
178
+ const above = spaceBelow < Math.min(input.contentHeight, 200) && spaceAbove > spaceBelow;
179
+ const viewportBudget = Math.max(
180
+ ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX,
181
+ input.viewportHeight - margin * 2
182
+ );
183
+ const available = Math.max(
184
+ Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget),
185
+ above ? spaceAbove : spaceBelow
186
+ );
187
+ let maxHeight = Math.min(
188
+ Math.max(input.contentHeight, Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget)),
189
+ available,
190
+ Math.floor(input.viewportHeight * 0.7) || viewportBudget,
191
+ ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX,
192
+ viewportBudget
193
+ );
194
+ let top = above ? Math.max(margin, input.triggerTop - 8 - maxHeight) : Math.min(
195
+ input.triggerBottom + 8,
196
+ Math.max(margin, input.viewportHeight - margin - maxHeight)
197
+ );
198
+ top = Math.max(margin, top);
199
+ maxHeight = Math.min(
200
+ maxHeight,
201
+ Math.max(ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX, input.viewportHeight - margin - top)
202
+ );
203
+ return { left, top, maxHeight, above };
204
+ }
205
+
206
+ export {
207
+ ANNOTATION_REVIEW_DIALOG_WIDTH_PX,
208
+ ANNOTATION_CARD_STACK_SCROLL_AT,
209
+ annotationBoxIntersects,
210
+ annotationViewportBox,
211
+ annotationNoteNeedsDisclosure,
212
+ layoutAnnotationBadges,
213
+ scrollAnnotationRowIntoList,
214
+ clampAnnotationDialogPlacement
215
+ };
216
+ //# sourceMappingURL=chunk-ZYRGEKWS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/timeline-annotation-layout.ts"],"sourcesContent":["export type AnnotationBox = {\n left: number;\n right: number;\n top: number;\n bottom: number;\n};\n\nexport type AnnotationBadgeAnchor = {\n id: string;\n ordinal: number;\n left: number;\n top: number;\n incomplete: boolean;\n};\n\nexport const ANNOTATION_BADGE_GAP_PX = 32;\nexport const ANNOTATION_BADGE_MIN_GAP_PX = 12;\nexport const ANNOTATION_REVIEW_DIALOG_WIDTH_PX = 400;\nexport const ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX = 32 * 16;\nexport const ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX = 160;\nexport const ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX = 80;\nexport const ANNOTATION_REVIEW_MARGIN_PX = 12;\nexport const ANNOTATION_NOTE_PREVIEW_CHARS = 280;\nexport const ANNOTATION_CARD_STACK_SCROLL_AT = 4;\n\nfunction clampAnnotationAxis(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), Math.max(min, max));\n}\n\nexport function annotationBoxIntersects(a: AnnotationBox, b: AnnotationBox): boolean {\n return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;\n}\n\nexport function annotationViewportBox(\n viewportWidth: number,\n viewportHeight: number,\n scroller?: AnnotationBox | null,\n): AnnotationBox {\n const viewport: AnnotationBox = {\n left: 0,\n top: 0,\n right: Math.max(1, viewportWidth),\n bottom: Math.max(1, viewportHeight),\n };\n if (!scroller || scroller.right - scroller.left <= 0 || scroller.bottom - scroller.top <= 0) {\n return viewport;\n }\n return {\n left: Math.max(viewport.left, scroller.left),\n top: Math.max(viewport.top, scroller.top),\n right: Math.min(viewport.right, scroller.right),\n bottom: Math.min(viewport.bottom, scroller.bottom),\n };\n}\n\nexport function annotationNoteNeedsDisclosure(note: string): boolean {\n const trimmed = note.trim();\n if (trimmed.length === 0) return false;\n return trimmed.length > ANNOTATION_NOTE_PREVIEW_CHARS || trimmed.split(\"\\n\").length > 4;\n}\n\nfunction annotationAxisStops(min: number, max: number, gap: number): number[] {\n if (max <= min) return [min];\n const stops: number[] = [];\n for (let value = min; value <= max + 1e-6; value += gap) {\n stops.push(value);\n }\n return stops;\n}\n\nfunction annotationPointKey(left: number, top: number): string {\n return `${Math.round(left * 100) / 100},${Math.round(top * 100) / 100}`;\n}\n\nfunction annotationPointFree(\n left: number,\n top: number,\n placed: readonly AnnotationBadgeAnchor[],\n gap: number,\n): boolean {\n return placed.every((other) => Math.hypot(left - other.left, top - other.top) >= gap);\n}\n\nexport function annotationBadgePackGap(\n viewport: AnnotationBox,\n count: number,\n preferred = ANNOTATION_BADGE_GAP_PX,\n): number {\n if (count <= 1) return preferred;\n const width = Math.max(\n 1,\n viewport.right - ANNOTATION_REVIEW_MARGIN_PX - (viewport.left + ANNOTATION_REVIEW_MARGIN_PX),\n );\n const height = Math.max(1, viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX - (viewport.top + 8));\n let gap = preferred;\n const capacity = (step: number) =>\n (Math.floor(width / step) + 1) * (Math.floor(height / step) + 1);\n while (gap > ANNOTATION_BADGE_MIN_GAP_PX && capacity(gap) < count) {\n gap -= 1;\n }\n return gap;\n}\n\nconst ANNOTATION_BADGE_SHIFT_DIRS: Array<[number, number]> = [\n [1, 0],\n [1, 1],\n [0, 1],\n [-1, 1],\n [-1, 0],\n [-1, -1],\n [0, -1],\n [1, -1],\n];\n\nexport function layoutAnnotationBadges(\n anchors: readonly AnnotationBadgeAnchor[],\n viewport: AnnotationBox,\n preferredGap = ANNOTATION_BADGE_GAP_PX,\n): AnnotationBadgeAnchor[] {\n const minLeft = viewport.left + ANNOTATION_REVIEW_MARGIN_PX;\n const maxLeft = viewport.right - ANNOTATION_REVIEW_MARGIN_PX;\n const minTop = viewport.top + 8;\n const maxTop = viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX;\n const gap = annotationBadgePackGap(viewport, anchors.length, preferredGap);\n const cells = annotationAxisStops(minLeft, maxLeft, gap).flatMap((left) =>\n annotationAxisStops(minTop, maxTop, gap).map((top) => ({ left, top })),\n );\n const placed: AnnotationBadgeAnchor[] = [];\n for (const anchor of [...anchors].sort((left, right) => left.ordinal - right.ordinal)) {\n const originLeft = clampAnnotationAxis(anchor.left, minLeft, maxLeft);\n const originTop = clampAnnotationAxis(anchor.top, minTop, maxTop);\n const seen = new Set<string>();\n const candidates: Array<{ left: number; top: number }> = [];\n const pushCandidate = (left: number, top: number) => {\n if (left < minLeft || left > maxLeft || top < minTop || top > maxTop) return;\n const key = annotationPointKey(left, top);\n if (seen.has(key)) return;\n seen.add(key);\n candidates.push({ left, top });\n };\n pushCandidate(originLeft, originTop);\n for (const [dx, dy] of ANNOTATION_BADGE_SHIFT_DIRS) {\n const colliders = [...placed].sort(\n (left, right) => right.left * dx + right.top * dy - (left.left * dx + left.top * dy),\n );\n for (const collider of colliders) {\n pushCandidate(collider.left + dx * gap, collider.top + dy * gap);\n }\n }\n const maxRadius =\n Math.ceil(Math.max(maxLeft - minLeft, maxTop - minTop) / Math.max(gap, 1)) + 1;\n for (let radius = 1; radius <= maxRadius; radius++) {\n const ring: Array<[number, number]> = [];\n for (let dx = -radius; dx <= radius; dx++) {\n for (let dy = -radius; dy <= radius; dy++) {\n if (Math.max(Math.abs(dx), Math.abs(dy)) !== radius) continue;\n ring.push([dx, dy]);\n }\n }\n ring.sort((left, right) => {\n const leftDir = (left[0] < 0 ? 2 : 0) + (left[1] < 0 ? 1 : 0);\n const rightDir = (right[0] < 0 ? 2 : 0) + (right[1] < 0 ? 1 : 0);\n if (leftDir !== rightDir) return leftDir - rightDir;\n const hypot = Math.hypot(left[0], left[1]) - Math.hypot(right[0], right[1]);\n if (hypot !== 0) return hypot;\n if (left[0] !== right[0]) return right[0] - left[0];\n return left[1] - right[1];\n });\n for (const [dx, dy] of ring) {\n pushCandidate(originLeft + dx * gap, originTop + dy * gap);\n }\n }\n for (const cell of cells) {\n pushCandidate(cell.left, cell.top);\n }\n const free = candidates.find((point) =>\n annotationPointFree(point.left, point.top, placed, gap),\n );\n let chosen = free ?? candidates[0]!;\n if (!free && placed.length > 0) {\n let bestMin = Number.NEGATIVE_INFINITY;\n for (const point of candidates) {\n const minDist = placed.reduce(\n (current, other) =>\n Math.min(current, Math.hypot(point.left - other.left, point.top - other.top)),\n Number.POSITIVE_INFINITY,\n );\n if (minDist > bestMin) {\n bestMin = minDist;\n chosen = point;\n }\n }\n }\n placed.push({ ...anchor, left: chosen.left, top: chosen.top });\n }\n return placed;\n}\n\nexport function scrollAnnotationRowIntoList(\n list: HTMLElement | null | undefined,\n row: HTMLElement | null | undefined,\n): void {\n if (!list || !row || !list.contains(row)) return;\n const listRect = list.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n if (rowRect.top < listRect.top) {\n list.scrollTop -= listRect.top - rowRect.top;\n return;\n }\n if (rowRect.bottom > listRect.bottom) {\n list.scrollTop += rowRect.bottom - listRect.bottom;\n }\n}\n\nexport function clampAnnotationDialogPlacement(input: {\n triggerLeft: number;\n triggerTop: number;\n triggerBottom: number;\n contentHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n panelWidth?: number;\n}): {\n left: number;\n top: number;\n maxHeight: number;\n above: boolean;\n} {\n const margin = ANNOTATION_REVIEW_MARGIN_PX;\n const panelWidth = Math.min(\n input.panelWidth ?? ANNOTATION_REVIEW_DIALOG_WIDTH_PX,\n Math.max(1, input.viewportWidth - margin * 2),\n );\n const left = Math.min(\n Math.max(margin, input.triggerLeft),\n Math.max(margin, input.viewportWidth - panelWidth - margin),\n );\n const spaceBelow = input.viewportHeight - input.triggerBottom - margin;\n const spaceAbove = input.triggerTop - margin;\n const above = spaceBelow < Math.min(input.contentHeight, 200) && spaceAbove > spaceBelow;\n const viewportBudget = Math.max(\n ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX,\n input.viewportHeight - margin * 2,\n );\n const available = Math.max(\n Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget),\n above ? spaceAbove : spaceBelow,\n );\n let maxHeight = Math.min(\n Math.max(input.contentHeight, Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget)),\n available,\n Math.floor(input.viewportHeight * 0.7) || viewportBudget,\n ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX,\n viewportBudget,\n );\n let top = above\n ? Math.max(margin, input.triggerTop - 8 - maxHeight)\n : Math.min(\n input.triggerBottom + 8,\n Math.max(margin, input.viewportHeight - margin - maxHeight),\n );\n top = Math.max(margin, top);\n maxHeight = Math.min(\n maxHeight,\n Math.max(ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX, input.viewportHeight - margin - top),\n );\n return { left, top, maxHeight, above };\n}\n"],"mappings":";AAeO,IAAM,0BAA0B;AAChC,IAAM,8BAA8B;AACpC,IAAM,oCAAoC;AAC1C,IAAM,yCAAyC,KAAK;AACpD,IAAM,yCAAyC;AAC/C,IAAM,2CAA2C;AACjD,IAAM,8BAA8B;AACpC,IAAM,gCAAgC;AACtC,IAAM,kCAAkC;AAE/C,SAAS,oBAAoB,OAAe,KAAa,KAAqB;AAC5E,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC;AAC1D;AAEO,SAAS,wBAAwB,GAAkB,GAA2B;AACnF,SAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE;AACtF;AAEO,SAAS,sBACd,eACA,gBACA,UACe;AACf,QAAM,WAA0B;AAAA,IAC9B,MAAM;AAAA,IACN,KAAK;AAAA,IACL,OAAO,KAAK,IAAI,GAAG,aAAa;AAAA,IAChC,QAAQ,KAAK,IAAI,GAAG,cAAc;AAAA,EACpC;AACA,MAAI,CAAC,YAAY,SAAS,QAAQ,SAAS,QAAQ,KAAK,SAAS,SAAS,SAAS,OAAO,GAAG;AAC3F,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,KAAK,IAAI,SAAS,MAAM,SAAS,IAAI;AAAA,IAC3C,KAAK,KAAK,IAAI,SAAS,KAAK,SAAS,GAAG;AAAA,IACxC,OAAO,KAAK,IAAI,SAAS,OAAO,SAAS,KAAK;AAAA,IAC9C,QAAQ,KAAK,IAAI,SAAS,QAAQ,SAAS,MAAM;AAAA,EACnD;AACF;AAEO,SAAS,8BAA8B,MAAuB;AACnE,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,SAAS,iCAAiC,QAAQ,MAAM,IAAI,EAAE,SAAS;AACxF;AAEA,SAAS,oBAAoB,KAAa,KAAa,KAAuB;AAC5E,MAAI,OAAO,IAAK,QAAO,CAAC,GAAG;AAC3B,QAAM,QAAkB,CAAC;AACzB,WAAS,QAAQ,KAAK,SAAS,MAAM,MAAM,SAAS,KAAK;AACvD,UAAM,KAAK,KAAK;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAc,KAAqB;AAC7D,SAAO,GAAG,KAAK,MAAM,OAAO,GAAG,IAAI,GAAG,IAAI,KAAK,MAAM,MAAM,GAAG,IAAI,GAAG;AACvE;AAEA,SAAS,oBACP,MACA,KACA,QACA,KACS;AACT,SAAO,OAAO,MAAM,CAAC,UAAU,KAAK,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG;AACtF;AAEO,SAAS,uBACd,UACA,OACA,YAAY,yBACJ;AACR,MAAI,SAAS,EAAG,QAAO;AACvB,QAAM,QAAQ,KAAK;AAAA,IACjB;AAAA,IACA,SAAS,QAAQ,+BAA+B,SAAS,OAAO;AAAA,EAClE;AACA,QAAM,SAAS,KAAK,IAAI,GAAG,SAAS,SAAS,+BAA+B,SAAS,MAAM,EAAE;AAC7F,MAAI,MAAM;AACV,QAAM,WAAW,CAAC,UACf,KAAK,MAAM,QAAQ,IAAI,IAAI,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI;AAChE,SAAO,MAAM,+BAA+B,SAAS,GAAG,IAAI,OAAO;AACjE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,IAAM,8BAAuD;AAAA,EAC3D,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,IAAI,CAAC;AAAA,EACN,CAAC,IAAI,CAAC;AAAA,EACN,CAAC,IAAI,EAAE;AAAA,EACP,CAAC,GAAG,EAAE;AAAA,EACN,CAAC,GAAG,EAAE;AACR;AAEO,SAAS,uBACd,SACA,UACA,eAAe,yBACU;AACzB,QAAM,UAAU,SAAS,OAAO;AAChC,QAAM,UAAU,SAAS,QAAQ;AACjC,QAAM,SAAS,SAAS,MAAM;AAC9B,QAAM,SAAS,SAAS,SAAS;AACjC,QAAM,MAAM,uBAAuB,UAAU,QAAQ,QAAQ,YAAY;AACzE,QAAM,QAAQ,oBAAoB,SAAS,SAAS,GAAG,EAAE;AAAA,IAAQ,CAAC,SAChE,oBAAoB,QAAQ,QAAQ,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE;AAAA,EACvE;AACA,QAAM,SAAkC,CAAC;AACzC,aAAW,UAAU,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,UAAU,MAAM,OAAO,GAAG;AACrF,UAAM,aAAa,oBAAoB,OAAO,MAAM,SAAS,OAAO;AACpE,UAAM,YAAY,oBAAoB,OAAO,KAAK,QAAQ,MAAM;AAChE,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,aAAmD,CAAC;AAC1D,UAAM,gBAAgB,CAAC,MAAc,QAAgB;AACnD,UAAI,OAAO,WAAW,OAAO,WAAW,MAAM,UAAU,MAAM,OAAQ;AACtE,YAAM,MAAM,mBAAmB,MAAM,GAAG;AACxC,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,iBAAW,KAAK,EAAE,MAAM,IAAI,CAAC;AAAA,IAC/B;AACA,kBAAc,YAAY,SAAS;AACnC,eAAW,CAAC,IAAI,EAAE,KAAK,6BAA6B;AAClD,YAAM,YAAY,CAAC,GAAG,MAAM,EAAE;AAAA,QAC5B,CAAC,MAAM,UAAU,MAAM,OAAO,KAAK,MAAM,MAAM,MAAM,KAAK,OAAO,KAAK,KAAK,MAAM;AAAA,MACnF;AACA,iBAAW,YAAY,WAAW;AAChC,sBAAc,SAAS,OAAO,KAAK,KAAK,SAAS,MAAM,KAAK,GAAG;AAAA,MACjE;AAAA,IACF;AACA,UAAM,YACJ,KAAK,KAAK,KAAK,IAAI,UAAU,SAAS,SAAS,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,IAAI;AAC/E,aAAS,SAAS,GAAG,UAAU,WAAW,UAAU;AAClD,YAAM,OAAgC,CAAC;AACvC,eAAS,KAAK,CAAC,QAAQ,MAAM,QAAQ,MAAM;AACzC,iBAAS,KAAK,CAAC,QAAQ,MAAM,QAAQ,MAAM;AACzC,cAAI,KAAK,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,CAAC,MAAM,OAAQ;AACrD,eAAK,KAAK,CAAC,IAAI,EAAE,CAAC;AAAA,QACpB;AAAA,MACF;AACA,WAAK,KAAK,CAAC,MAAM,UAAU;AACzB,cAAM,WAAW,KAAK,CAAC,IAAI,IAAI,IAAI,MAAM,KAAK,CAAC,IAAI,IAAI,IAAI;AAC3D,cAAM,YAAY,MAAM,CAAC,IAAI,IAAI,IAAI,MAAM,MAAM,CAAC,IAAI,IAAI,IAAI;AAC9D,YAAI,YAAY,SAAU,QAAO,UAAU;AAC3C,cAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAC1E,YAAI,UAAU,EAAG,QAAO;AACxB,YAAI,KAAK,CAAC,MAAM,MAAM,CAAC,EAAG,QAAO,MAAM,CAAC,IAAI,KAAK,CAAC;AAClD,eAAO,KAAK,CAAC,IAAI,MAAM,CAAC;AAAA,MAC1B,CAAC;AACD,iBAAW,CAAC,IAAI,EAAE,KAAK,MAAM;AAC3B,sBAAc,aAAa,KAAK,KAAK,YAAY,KAAK,GAAG;AAAA,MAC3D;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,oBAAc,KAAK,MAAM,KAAK,GAAG;AAAA,IACnC;AACA,UAAM,OAAO,WAAW;AAAA,MAAK,CAAC,UAC5B,oBAAoB,MAAM,MAAM,MAAM,KAAK,QAAQ,GAAG;AAAA,IACxD;AACA,QAAI,SAAS,QAAQ,WAAW,CAAC;AACjC,QAAI,CAAC,QAAQ,OAAO,SAAS,GAAG;AAC9B,UAAI,UAAU,OAAO;AACrB,iBAAW,SAAS,YAAY;AAC9B,cAAM,UAAU,OAAO;AAAA,UACrB,CAAC,SAAS,UACR,KAAK,IAAI,SAAS,KAAK,MAAM,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA,UAC9E,OAAO;AAAA,QACT;AACA,YAAI,UAAU,SAAS;AACrB,oBAAU;AACV,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,EAAE,GAAG,QAAQ,MAAM,OAAO,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA,EAC/D;AACA,SAAO;AACT;AAEO,SAAS,4BACd,MACA,KACM;AACN,MAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,SAAS,GAAG,EAAG;AAC1C,QAAM,WAAW,KAAK,sBAAsB;AAC5C,QAAM,UAAU,IAAI,sBAAsB;AAC1C,MAAI,QAAQ,MAAM,SAAS,KAAK;AAC9B,SAAK,aAAa,SAAS,MAAM,QAAQ;AACzC;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,SAAS,QAAQ;AACpC,SAAK,aAAa,QAAQ,SAAS,SAAS;AAAA,EAC9C;AACF;AAEO,SAAS,+BAA+B,OAa7C;AACA,QAAM,SAAS;AACf,QAAM,aAAa,KAAK;AAAA,IACtB,MAAM,cAAc;AAAA,IACpB,KAAK,IAAI,GAAG,MAAM,gBAAgB,SAAS,CAAC;AAAA,EAC9C;AACA,QAAM,OAAO,KAAK;AAAA,IAChB,KAAK,IAAI,QAAQ,MAAM,WAAW;AAAA,IAClC,KAAK,IAAI,QAAQ,MAAM,gBAAgB,aAAa,MAAM;AAAA,EAC5D;AACA,QAAM,aAAa,MAAM,iBAAiB,MAAM,gBAAgB;AAChE,QAAM,aAAa,MAAM,aAAa;AACtC,QAAM,QAAQ,aAAa,KAAK,IAAI,MAAM,eAAe,GAAG,KAAK,aAAa;AAC9E,QAAM,iBAAiB,KAAK;AAAA,IAC1B;AAAA,IACA,MAAM,iBAAiB,SAAS;AAAA,EAClC;AACA,QAAM,YAAY,KAAK;AAAA,IACrB,KAAK,IAAI,wCAAwC,cAAc;AAAA,IAC/D,QAAQ,aAAa;AAAA,EACvB;AACA,MAAI,YAAY,KAAK;AAAA,IACnB,KAAK,IAAI,MAAM,eAAe,KAAK,IAAI,wCAAwC,cAAc,CAAC;AAAA,IAC9F;AAAA,IACA,KAAK,MAAM,MAAM,iBAAiB,GAAG,KAAK;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AACA,MAAI,MAAM,QACN,KAAK,IAAI,QAAQ,MAAM,aAAa,IAAI,SAAS,IACjD,KAAK;AAAA,IACH,MAAM,gBAAgB;AAAA,IACtB,KAAK,IAAI,QAAQ,MAAM,iBAAiB,SAAS,SAAS;AAAA,EAC5D;AACJ,QAAM,KAAK,IAAI,QAAQ,GAAG;AAC1B,cAAY,KAAK;AAAA,IACf;AAAA,IACA,KAAK,IAAI,0CAA0C,MAAM,iBAAiB,SAAS,GAAG;AAAA,EACxF;AACA,SAAO,EAAE,MAAM,KAAK,WAAW,MAAM;AACvC;","names":[]}
@@ -9,7 +9,7 @@ import type { PickerModelRow } from "../model-policy.js";
9
9
  import { OPEN_WORKSTREAM_CONTROL_EVENT } from "../workstream-control-event.js";
10
10
  export { OPEN_WORKSTREAM_CONTROL_EVENT };
11
11
  export type { ResponsiveBasis } from "../lib/composer-responsive-context.js";
12
- export type ComposerDelivery = Pick<ComposerState, "value" | "setValue" | "send" | "steer" | "sending" | "canSend" | "error" | "clearError">;
12
+ export type ComposerDelivery = Pick<ComposerState, "value" | "setValue" | "send" | "steer" | "sending" | "canSend" | "error" | "clearError" | "annotations" | "requestAnnotationReview">;
13
13
  export type ComposerDraftState = Pick<ComposerState, "draftConflict" | "draftSaving" | "resolveDraftConflict" | "restoredResources" | "removeRestoredResource">;
14
14
  export type ComposerControlState = Pick<ComposerState, "pause" | "pausing" | "resume" | "resumeScope" | "resuming">;
15
15
  export type ComposerControlLinks = {
@@ -32,6 +32,7 @@ export type ChatComposerMessages = {
32
32
  sendAndResumeAriaLabel: string;
33
33
  sendTitle: string;
34
34
  sendAndResumeTitle: string;
35
+ annotationNotesRequired: string;
35
36
  workspacePaused: string;
36
37
  pausedHere: string;
37
38
  parentBlocker: string;
@@ -82,7 +83,7 @@ export type ChatComposerMessages = {
82
83
  };
83
84
  export declare const defaultChatComposerMessages: ChatComposerMessages;
84
85
  export type ComposerSubmitMode = "queue" | "steer";
85
- export type ComposerSubmitBlocker = "disabled" | "attachment" | "sending" | "command" | "empty" | null;
86
+ export type ComposerSubmitBlocker = "disabled" | "attachment" | "sending" | "command" | "annotations" | "empty" | null;
86
87
  export type UseChatComposerControllerOptions = {
87
88
  delivery: ComposerDelivery;
88
89
  draft?: ComposerDraftState | undefined;
@@ -141,6 +142,7 @@ export declare function useChatComposerController({ delivery, draft, control, ef
141
142
  sendAndResumeAriaLabel: string;
142
143
  sendTitle: string;
143
144
  sendAndResumeTitle: string;
145
+ annotationNotesRequired: string;
144
146
  workspacePaused: string;
145
147
  pausedHere: string;
146
148
  parentBlocker: string;
@@ -9,6 +9,8 @@ export type MessageTimelineProps = {
9
9
  items?: TimelineItem[] | undefined;
10
10
  /** Current session status (reserved; tip "Working…" chrome removed for now). */
11
11
  status?: SessionStatus | null | undefined;
12
+ /** Host-owned controls beside Copy and the timestamp on settled message rows. */
13
+ renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
12
14
  /** Plug a markdown renderer for message bodies (e.g. streamdown). */
13
15
  renderMessageText?: ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
14
16
  /** Drill into a spawned worker session. */
@@ -67,6 +69,10 @@ export type MessageTimelineProps = {
67
69
  autoFollow?: boolean | undefined;
68
70
  /** Capture a same-row text selection into the host's canonical composer draft. */
69
71
  onAnnotate?: ((annotation: DraftTimelineAnnotation) => void) | undefined;
72
+ /** Composer draft quotes currently attached to the next send. */
73
+ draftAnnotations?: readonly DraftTimelineAnnotation[] | undefined;
74
+ /** Open the composer review list for one numbered draft badge. */
75
+ onDraftAnnotationSelect?: ((id: string) => void) | undefined;
70
76
  /** Older durable history exists above the current window (see useSessionEvents). */
71
77
  hasOlder?: boolean | undefined;
72
78
  /** An older window is being fetched; shows the quiet top shimmer. */
@@ -103,7 +109,7 @@ export type MessageTimelineProps = {
103
109
  * cards, goal markers, and status transitions. Owns stick-to-bottom scrolling
104
110
  * with a "jump to latest" affordance when the reader scrolls back.
105
111
  */
106
- export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, shouldRenderAuthNeeded, resolveProviderLogo, toolRegistry, loadRetainedScreenshot, loadRetainedArtifact, loadVideoArtifactPlayback, computeLabel, turnSummary, autoFollow, onAnnotate, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, trailingState, emptyState, className, }: MessageTimelineProps): import("react").JSX.Element;
112
+ export declare function MessageTimeline({ events, items, status: _status, renderMessageActions, renderMessageText, onOpenSession, onMemoryClick, onReconnect, shouldRenderAuthNeeded, resolveProviderLogo, toolRegistry, loadRetainedScreenshot, loadRetainedArtifact, loadVideoArtifactPlayback, computeLabel, turnSummary, autoFollow, onAnnotate, draftAnnotations, onDraftAnnotationSelect, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, trailingState, emptyState, className, }: MessageTimelineProps): import("react").JSX.Element;
107
113
  /**
108
114
  * Settled turns lift the final agent answer out as a sibling group. Include it
109
115
  * in "Copy turn" when present so the chip copies the full assistant reply.
@@ -115,8 +121,9 @@ export declare function trailingAgentTextAfterTurn(group: TimelineGroup, next: T
115
121
  * landmark, notice). Exported so the component demo draws the EXACT same rows as
116
122
  * the live app — no forked bubble/goal markup.
117
123
  */
118
- export declare function TimelineRow({ item, renderMessageText, onReconnect, resolveProviderLogo, onOpenSession, loadVideoArtifactPlayback, }: {
124
+ export declare function TimelineRow({ item, renderMessageActions, renderMessageText, onReconnect, resolveProviderLogo, onOpenSession, loadVideoArtifactPlayback, }: {
119
125
  item: TimelineItem;
126
+ renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
120
127
  renderMessageText?: ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
121
128
  onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
122
129
  resolveProviderLogo?: ((providerDomain: string) => string | null | undefined) | undefined;