@opengeni/react 7.4.0 → 7.5.0-canary.36919050519001
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.
- package/README.md +124 -27
- package/dist/artifacts-document.js +3 -2
- package/dist/artifacts-presentation.js +3 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +516 -1413
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-2E6R2Y4W.js → browser-viewer-Z7S7QEJL.js} +6 -5
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/{chunk-Y3QTZN2F.js → chunk-2VFNCX4E.js} +6 -6
- package/dist/{chunk-UDD6DLLT.js → chunk-3IJJEYW4.js} +15 -3
- package/dist/chunk-3IJJEYW4.js.map +1 -0
- package/dist/chunk-4RVYOY4O.js +379 -0
- package/dist/chunk-4RVYOY4O.js.map +1 -0
- package/dist/chunk-5CVXZA3Y.js +292 -0
- package/dist/chunk-5CVXZA3Y.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-5J4FOW6Q.js} +2 -10
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/{chunk-KVERXCFY.js → chunk-5KYZIQFY.js} +40 -3
- package/dist/chunk-5KYZIQFY.js.map +1 -0
- package/dist/{chunk-KNDIXZN6.js → chunk-7UAMYXJY.js} +25 -18
- package/dist/{chunk-KNDIXZN6.js.map → chunk-7UAMYXJY.js.map} +1 -1
- package/dist/{chunk-SEEVCT36.js → chunk-A4UJB2MM.js} +2 -2
- package/dist/{chunk-X4HIN6TV.js → chunk-AFLPWUQ7.js} +71 -162
- package/dist/chunk-AFLPWUQ7.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/{chunk-XEJEOAOV.js → chunk-B7WMS3CS.js} +51 -40
- package/dist/chunk-B7WMS3CS.js.map +1 -0
- package/dist/{chunk-S5JQ4R22.js → chunk-BDKR6BAR.js} +54 -24
- package/dist/chunk-BDKR6BAR.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-DYWBUX5Q.js} +24 -12
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-YZ3YYMOM.js → chunk-E3G7SAYZ.js} +252 -624
- package/dist/chunk-E3G7SAYZ.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/{chunk-5AZSK6MV.js → chunk-LWNLETQT.js} +5 -5
- package/dist/{chunk-USXVQACU.js → chunk-M7LBYP63.js} +5 -4
- package/dist/{chunk-USXVQACU.js.map → chunk-M7LBYP63.js.map} +1 -1
- package/dist/{chunk-L4OSXQ6H.js → chunk-NBOHNRLJ.js} +1289 -1060
- package/dist/chunk-NBOHNRLJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-3E2AITTZ.js → chunk-WUISZ3WU.js} +32 -28
- package/dist/{chunk-3E2AITTZ.js.map → chunk-WUISZ3WU.js.map} +1 -1
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-DUEBTL3N.js → chunk-Y2PRMYLC.js} +4 -3
- package/dist/{chunk-DUEBTL3N.js.map → chunk-Y2PRMYLC.js.map} +1 -1
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/{chunk-V2ABIVYJ.js → chunk-ZXSEDZP7.js} +70 -6
- package/dist/chunk-ZXSEDZP7.js.map +1 -0
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
- package/dist/components/artifacts/index.d.ts +7 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/browser-select-control.d.ts +3 -1
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +20 -3
- package/dist/components/open-geni-links.d.ts +16 -0
- package/dist/components/session-conversation.d.ts +20 -1
- package/dist/components/timeline-pill-geometry.d.ts +8 -0
- package/dist/components/timeline-pill-placement.d.ts +24 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/composer.js +5 -5
- package/dist/{computer-viewer-WMJE6OA3.js → computer-viewer-KJWLH3GI.js} +7 -5
- package/dist/connect.js +7 -6
- package/dist/connect.js.map +1 -1
- package/dist/conversation-timeline.d.ts +1 -1
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +26 -0
- package/dist/desktop.js.map +1 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +1 -1
- package/dist/hooks/use-composer.d.ts +4 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +460 -1386
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +8 -6
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-QB4EUCW5.js → latest-question-IS445HOW.js} +3 -3
- package/dist/lib/format.d.ts +6 -1
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/machines.js +2 -2
- package/dist/{markdown-table-layout-PFUEZ2U4.js → markdown-table-layout-QXSLK4BY.js} +37 -5
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/{queue-surface-implementation-IM2HD3SK.js → queue-surface-implementation-2CAQAIHM.js} +2 -2
- package/dist/realtime.js +3 -3
- package/dist/{sandbox-files-PXPIZAFG.js → sandbox-files-AEJTWGT4.js} +8 -5
- package/dist/sandbox-files-AEJTWGT4.js.map +1 -0
- package/dist/session-ui.d.ts +9 -2
- package/dist/session-ui.js +39 -20
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +4 -4
- package/dist/sites.js +4 -289
- package/dist/sites.js.map +1 -1
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/genie-loading.d.ts +3 -1
- package/dist/timeline/types.d.ts +8 -1
- package/dist/{timeline-annotation-selection-ODLVF4OI.js → timeline-annotation-selection-DMLMM2TX.js} +6 -1
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-pill-geometry-Z23V243M.js +110 -0
- package/dist/timeline-pill-geometry-Z23V243M.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +1073 -0
- package/dist/usage.js.map +1 -0
- package/package.json +19 -3
- package/src/artifacts.ts +37 -0
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +3 -1
- package/src/components/artifacts/editable-artifact-view.tsx +187 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +34 -36
- package/src/components/artifacts/editable-presentation.tsx +21 -16
- package/src/components/artifacts/index.ts +41 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/browser-select-control.tsx +4 -1
- package/src/components/browser-viewer.tsx +81 -22
- package/src/components/code-editor.tsx +13 -16
- package/src/components/computer-viewer.tsx +51 -23
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/markdown-table-layout.ts +49 -5
- package/src/components/markdown.tsx +5 -2
- package/src/components/message-timeline.tsx +557 -516
- package/src/components/open-geni-links.tsx +24 -0
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/session-chrome.tsx +22 -3
- package/src/components/session-conversation.tsx +195 -102
- package/src/components/timeline-annotation-selection.tsx +7 -0
- package/src/components/timeline-pill-geometry.ts +169 -0
- package/src/components/timeline-pill-placement.ts +99 -0
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/conversation-timeline.ts +10 -2
- package/src/desktop.ts +24 -0
- package/src/editor.ts +13 -0
- package/src/hooks/use-available-models.ts +1 -1
- package/src/hooks/use-composer.ts +57 -2
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-usage.ts +100 -0
- package/src/index.ts +22 -0
- package/src/lib/format.ts +32 -6
- package/src/lib/workbench-peers.ts +62 -0
- package/src/session-ui.ts +22 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +5 -3
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/genie-loading.tsx +16 -6
- package/src/timeline/projection.ts +78 -4
- package/src/timeline/types.ts +8 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +50 -0
- package/styles/compiled.css +325 -0
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-KVERXCFY.js.map +0 -1
- package/dist/chunk-L4OSXQ6H.js.map +0 -1
- package/dist/chunk-S5JQ4R22.js.map +0 -1
- package/dist/chunk-UDD6DLLT.js.map +0 -1
- package/dist/chunk-UGMKPUEM.js +0 -299
- package/dist/chunk-UGMKPUEM.js.map +0 -1
- package/dist/chunk-V2ABIVYJ.js.map +0 -1
- package/dist/chunk-X4HIN6TV.js.map +0 -1
- package/dist/chunk-XEJEOAOV.js.map +0 -1
- package/dist/chunk-YZ3YYMOM.js.map +0 -1
- package/dist/markdown-table-layout-PFUEZ2U4.js.map +0 -1
- package/dist/timeline-annotation-selection-ODLVF4OI.js.map +0 -1
- /package/dist/{browser-viewer-2E6R2Y4W.js.map → browser-viewer-Z7S7QEJL.js.map} +0 -0
- /package/dist/{computer-viewer-WMJE6OA3.js.map → chat-interactive-block-UGBTNB3L.js.map} +0 -0
- /package/dist/{chunk-Y3QTZN2F.js.map → chunk-2VFNCX4E.js.map} +0 -0
- /package/dist/{chunk-SEEVCT36.js.map → chunk-A4UJB2MM.js.map} +0 -0
- /package/dist/{chunk-5AZSK6MV.js.map → chunk-LWNLETQT.js.map} +0 -0
- /package/dist/{sandbox-files-PXPIZAFG.js.map → computer-viewer-KJWLH3GI.js.map} +0 -0
- /package/dist/{latest-question-QB4EUCW5.js.map → latest-question-IS445HOW.js.map} +0 -0
- /package/dist/{queue-surface-implementation-IM2HD3SK.js.map → queue-surface-implementation-2CAQAIHM.js.map} +0 -0
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
type OlderHistoryLoadReceipt,
|
|
64
64
|
type OlderHistoryLoader,
|
|
65
65
|
} from "../older-history";
|
|
66
|
-
import { Markdown } from "./markdown";
|
|
66
|
+
import { Markdown, type MarkdownProps } from "./markdown";
|
|
67
67
|
import { OpenGeniLinkProvider, type OpenGeniLinkResolver } from "./open-geni-links";
|
|
68
68
|
import {
|
|
69
69
|
TimelineBeforeLayout,
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
type TimelineAnchor,
|
|
74
74
|
} from "./timeline-anchor";
|
|
75
75
|
import { useReadingProgress } from "./timeline-reading-progress";
|
|
76
|
+
import { type TimelinePillPlacement, useTimelinePillPlacement } from "./timeline-pill-placement";
|
|
76
77
|
import {
|
|
77
78
|
animateTimelineSettlement,
|
|
78
79
|
captureTimelineSettlement,
|
|
@@ -142,6 +143,12 @@ import {
|
|
|
142
143
|
import { SESSION_STATUS_META, StatusDot } from "./session-status";
|
|
143
144
|
import { TimelineComputeLabelProvider } from "../timeline/compute-label";
|
|
144
145
|
import { EntranceAnimationProvider, useEntranceAnimation } from "../timeline/entrance";
|
|
146
|
+
import {
|
|
147
|
+
AllowanceExhaustedRow,
|
|
148
|
+
AllowancePresentationProvider,
|
|
149
|
+
type RenderAllowanceExhausted,
|
|
150
|
+
} from "../timeline/allowance-exhausted-row";
|
|
151
|
+
import type { AllowanceLabels } from "../usage/allowance-copy";
|
|
145
152
|
import { SeenActivityIdsProvider } from "../timeline/seen-activity-ids";
|
|
146
153
|
import { TimelineAnnotationCards } from "./timeline-annotations";
|
|
147
154
|
import { TooltipProvider } from "./tooltip";
|
|
@@ -170,6 +177,11 @@ export type MessageTimelineProps = {
|
|
|
170
177
|
context: { searchTarget: TimelineSearchTarget | null },
|
|
171
178
|
) => ReactNode)
|
|
172
179
|
| undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Render assistant `opengeni-html` / `opengeni-site` fences in the default
|
|
182
|
+
* message renderer (`SessionConversation` supplies the inline Site preview).
|
|
183
|
+
*/
|
|
184
|
+
renderInteractiveBlock?: MarkdownProps["renderInteractiveBlock"];
|
|
173
185
|
/** Drill into a spawned worker session. */
|
|
174
186
|
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
175
187
|
/**
|
|
@@ -191,6 +203,15 @@ export type MessageTimelineProps = {
|
|
|
191
203
|
onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
|
|
192
204
|
/** Host-owned inline connection setup. Return undefined to use the default recovery card. */
|
|
193
205
|
renderAuthNeeded?: ((item: AuthNeededItem) => ReactNode | undefined) | undefined;
|
|
206
|
+
/**
|
|
207
|
+
* Replace the "usage limit reached" row shown when a workspace or member
|
|
208
|
+
* usage allowance stops work (an `allowance_exhausted` refusal). Receives the
|
|
209
|
+
* typed refusal (scope, reset time) and the default row; return undefined to
|
|
210
|
+
* keep the default. Use it to point people at your own plan or admin page.
|
|
211
|
+
*/
|
|
212
|
+
renderAllowanceExhausted?: RenderAllowanceExhausted | undefined;
|
|
213
|
+
/** Replace the words of the default "usage limit reached" row. */
|
|
214
|
+
allowanceExhaustedLabels?: Partial<AllowanceLabels> | undefined;
|
|
194
215
|
/**
|
|
195
216
|
* Decide which durable authentication notices this timeline presents.
|
|
196
217
|
* Defaults to showing every notice. Embedded hosts can suppress notices for
|
|
@@ -268,8 +289,8 @@ export type MessageTimelineProps = {
|
|
|
268
289
|
* scrolls the in-memory window.
|
|
269
290
|
*/
|
|
270
291
|
onJumpToLatest?: (() => void | Promise<void>) | undefined;
|
|
271
|
-
/**
|
|
272
|
-
*
|
|
292
|
+
/** @deprecated Retained for source compatibility; contextual prompt navigation
|
|
293
|
+
* uses only mounted prompts and never invokes this global resolver. */
|
|
273
294
|
onJumpToLatestQuestion?: (() => Promise<number | null>) | undefined;
|
|
274
295
|
/** Host-owned content appended after timeline groups, such as startup progress. */
|
|
275
296
|
trailingState?: ReactNode | undefined;
|
|
@@ -311,6 +332,20 @@ const PIN_THRESHOLD_PX = 48;
|
|
|
311
332
|
const JUMP_TO_LATEST_CATCHUP_DEBT_PX = 240;
|
|
312
333
|
/** Breathing room above the question when following stops at an answer. */
|
|
313
334
|
const QUESTION_NAV_MARGIN_PX = 12;
|
|
335
|
+
// Floating pills rest at these spots and slide sideways, in this order, when
|
|
336
|
+
// their resting spot would cover a control (see useTimelinePillPlacement).
|
|
337
|
+
const JUMP_PILL_PLACEMENTS: readonly TimelinePillPlacement[] = ["center", "end", "start"];
|
|
338
|
+
const QUESTION_PILL_PLACEMENTS: readonly TimelinePillPlacement[] = ["end", "center", "start"];
|
|
339
|
+
const JUMP_PILL_PLACEMENT_CLASS: Record<TimelinePillPlacement, string> = {
|
|
340
|
+
center: "inset-x-0 mx-auto",
|
|
341
|
+
end: "right-4 sm:right-6",
|
|
342
|
+
start: "left-4 sm:left-6",
|
|
343
|
+
};
|
|
344
|
+
const QUESTION_PILL_PLACEMENT_CLASS: Record<TimelinePillPlacement, string> = {
|
|
345
|
+
center: "justify-center",
|
|
346
|
+
end: "justify-end",
|
|
347
|
+
start: "justify-start",
|
|
348
|
+
};
|
|
314
349
|
/**
|
|
315
350
|
* Prefetch older history when the top sentinel is this far from the viewport.
|
|
316
351
|
* After a page loads we stay cool until the reader leaves this band (scrolls
|
|
@@ -444,7 +479,7 @@ function contentTopOf(node: HTMLElement, groupKey: string): number | null {
|
|
|
444
479
|
}
|
|
445
480
|
|
|
446
481
|
/** The question the reader is inside of, once its start has scrolled out of view. */
|
|
447
|
-
type QuestionNav = { key: string
|
|
482
|
+
type QuestionNav = { key: string };
|
|
448
483
|
|
|
449
484
|
/** How far a question's start must be above the viewport before navigation shows. */
|
|
450
485
|
const QUESTION_NAV_HIDDEN_PX = 24;
|
|
@@ -452,11 +487,16 @@ const QUESTION_NAV_HIDDEN_PX = 24;
|
|
|
452
487
|
function readQuestionNav(node: HTMLElement): QuestionNav | null {
|
|
453
488
|
const view = node.getBoundingClientRect();
|
|
454
489
|
const prompts = [...node.querySelectorAll<HTMLElement>("[data-og-prompt]")];
|
|
455
|
-
|
|
490
|
+
// The last prompt before the reading position owns the response/work in view.
|
|
491
|
+
// A newer prompt farther down the conversation is not a navigation target.
|
|
492
|
+
const middle = view.top + view.height / 2;
|
|
493
|
+
const prompt = prompts
|
|
494
|
+
.reverse()
|
|
495
|
+
.find((candidate) => candidate.getBoundingClientRect().top <= middle);
|
|
456
496
|
const key = prompt?.dataset.ogGroupKey;
|
|
457
497
|
if (!prompt || !key) return null;
|
|
458
|
-
const
|
|
459
|
-
if (top >= view.top - QUESTION_NAV_HIDDEN_PX
|
|
498
|
+
const bounds = prompt.getBoundingClientRect();
|
|
499
|
+
if (bounds.top >= view.top - QUESTION_NAV_HIDDEN_PX || bounds.bottom > view.top) {
|
|
460
500
|
return null;
|
|
461
501
|
}
|
|
462
502
|
return { key };
|
|
@@ -482,6 +522,7 @@ function cssEscapeAttribute(value: string): string {
|
|
|
482
522
|
*/
|
|
483
523
|
export function MessageTimeline({
|
|
484
524
|
resolveLink,
|
|
525
|
+
renderInteractiveBlock,
|
|
485
526
|
userMessageDisclosureLabels,
|
|
486
527
|
searchTarget,
|
|
487
528
|
events,
|
|
@@ -493,6 +534,8 @@ export function MessageTimeline({
|
|
|
493
534
|
onMemoryClick,
|
|
494
535
|
onReconnect,
|
|
495
536
|
renderAuthNeeded,
|
|
537
|
+
renderAllowanceExhausted,
|
|
538
|
+
allowanceExhaustedLabels,
|
|
496
539
|
shouldRenderAuthNeeded,
|
|
497
540
|
resolveProviderLogo,
|
|
498
541
|
toolRegistry = defaultToolRegistry,
|
|
@@ -515,7 +558,6 @@ export function MessageTimeline({
|
|
|
515
558
|
loadingNewer = false,
|
|
516
559
|
onLoadNewer,
|
|
517
560
|
onJumpToLatest,
|
|
518
|
-
onJumpToLatestQuestion,
|
|
519
561
|
trailingState,
|
|
520
562
|
emptyState,
|
|
521
563
|
className,
|
|
@@ -555,6 +597,9 @@ export function MessageTimeline({
|
|
|
555
597
|
<Markdown
|
|
556
598
|
searchTarget={searchItem?.id === item.id ? searchTarget : null}
|
|
557
599
|
streaming={item.kind === "agent-message" && item.streaming}
|
|
600
|
+
renderInteractiveBlock={
|
|
601
|
+
item.kind === "agent-message" ? renderInteractiveBlock : undefined
|
|
602
|
+
}
|
|
558
603
|
>
|
|
559
604
|
{text}
|
|
560
605
|
</Markdown>
|
|
@@ -567,7 +612,7 @@ export function MessageTimeline({
|
|
|
567
612
|
body
|
|
568
613
|
);
|
|
569
614
|
},
|
|
570
|
-
[renderMessageText, searchItem?.id, searchTarget],
|
|
615
|
+
[renderInteractiveBlock, renderMessageText, searchItem?.id, searchTarget],
|
|
571
616
|
);
|
|
572
617
|
// Event-window identity is independent of projected rows (partial messages
|
|
573
618
|
// can acquire a different first-delta id when older text arrives).
|
|
@@ -700,10 +745,22 @@ export function MessageTimeline({
|
|
|
700
745
|
const resizeFollowRafRef = useRef<number | null>(null);
|
|
701
746
|
const questionNavFrameRef = useRef<number | null>(null);
|
|
702
747
|
const [questionNav, setQuestionNav] = useState<QuestionNav | null>(null);
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
const
|
|
748
|
+
const jumpPillRef = useRef<HTMLButtonElement | null>(null);
|
|
749
|
+
const questionPillRef = useRef<HTMLDivElement | null>(null);
|
|
750
|
+
const jumpPillShown = autoFollow && (!pinned || hasNewer || canSkipTipCatchup);
|
|
751
|
+
const jumpPillPlacement = useTimelinePillPlacement({
|
|
752
|
+
pillRef: jumpPillRef,
|
|
753
|
+
scrollerRef: scrollRef,
|
|
754
|
+
active: jumpPillShown,
|
|
755
|
+
preference: JUMP_PILL_PLACEMENTS,
|
|
756
|
+
});
|
|
757
|
+
const questionPillPlacement = useTimelinePillPlacement({
|
|
758
|
+
pillRef: questionPillRef,
|
|
759
|
+
scrollerRef: scrollRef,
|
|
760
|
+
active: questionNav !== null,
|
|
761
|
+
preference: QUESTION_PILL_PLACEMENTS,
|
|
762
|
+
allowDownwardFallback: true,
|
|
763
|
+
});
|
|
707
764
|
const firstGroupKey = allGroups[0] ? timelineGroupKey(allGroups[0]) : null;
|
|
708
765
|
// Content stays invisible until the tip is hard-parked across a short
|
|
709
766
|
// post-commit settle (two rAFs). That absorbs sync late layout while hidden
|
|
@@ -1270,19 +1327,10 @@ export function MessageTimeline({
|
|
|
1270
1327
|
questionNavFrameRef.current = requestFrame(() => {
|
|
1271
1328
|
questionNavFrameRef.current = null;
|
|
1272
1329
|
const node = scrollRef.current;
|
|
1273
|
-
const next =
|
|
1274
|
-
? onJumpToLatestQuestion
|
|
1275
|
-
? { key: null }
|
|
1276
|
-
: null
|
|
1277
|
-
: node
|
|
1278
|
-
? (readQuestionNav(node) ??
|
|
1279
|
-
(onJumpToLatestQuestion && !node.querySelector("[data-og-prompt]")
|
|
1280
|
-
? { key: null }
|
|
1281
|
-
: null))
|
|
1282
|
-
: null;
|
|
1330
|
+
const next = node ? readQuestionNav(node) : null;
|
|
1283
1331
|
setQuestionNav((current) => (sameQuestionNav(current, next) ? current : next));
|
|
1284
1332
|
});
|
|
1285
|
-
}, [readableTurns
|
|
1333
|
+
}, [readableTurns]);
|
|
1286
1334
|
useEffect(
|
|
1287
1335
|
() => () => {
|
|
1288
1336
|
if (questionNavFrameRef.current != null) {
|
|
@@ -1312,30 +1360,6 @@ export function MessageTimeline({
|
|
|
1312
1360
|
syncScrollBaseline(node);
|
|
1313
1361
|
scheduleQuestionNav();
|
|
1314
1362
|
};
|
|
1315
|
-
const jumpToLatestQuestion = async () => {
|
|
1316
|
-
if (!onJumpToLatestQuestion) {
|
|
1317
|
-
if (!hasNewer && questionNav?.key) jumpToQuestion(questionNav.key);
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
const request = ++questionRequestRef.current;
|
|
1321
|
-
releasePinFromReader();
|
|
1322
|
-
wantPinRef.current = false;
|
|
1323
|
-
setQuestionPending(true);
|
|
1324
|
-
setQuestionError(null);
|
|
1325
|
-
try {
|
|
1326
|
-
const sequence = await onJumpToLatestQuestion();
|
|
1327
|
-
if (request === questionRequestRef.current) setQuestionTarget(sequence);
|
|
1328
|
-
} catch (reason) {
|
|
1329
|
-
if (request === questionRequestRef.current)
|
|
1330
|
-
setQuestionError(
|
|
1331
|
-
reason instanceof Error && reason.name === "LatestQuestionQueuedError"
|
|
1332
|
-
? "The latest question is in the prompt queue."
|
|
1333
|
-
: "Could not load the latest question. Try again.",
|
|
1334
|
-
);
|
|
1335
|
-
} finally {
|
|
1336
|
-
if (request === questionRequestRef.current) setQuestionPending(false);
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1339
1363
|
|
|
1340
1364
|
const driveFollowRef = useRef<(node: HTMLElement, now?: number) => void>(
|
|
1341
1365
|
requestOlderIfUnderfilled as (node: HTMLElement, now?: number) => void,
|
|
@@ -1554,21 +1578,7 @@ export function MessageTimeline({
|
|
|
1554
1578
|
}
|
|
1555
1579
|
}
|
|
1556
1580
|
};
|
|
1557
|
-
|
|
1558
|
-
questionTarget === null
|
|
1559
|
-
? undefined
|
|
1560
|
-
: groups.find(
|
|
1561
|
-
({ group }) =>
|
|
1562
|
-
group.kind === "item" &&
|
|
1563
|
-
group.item.kind === "user-message" &&
|
|
1564
|
-
(group.item.sourceEvents?.some((source) => source.sequence === questionTarget) ||
|
|
1565
|
-
group.item.annotationSource?.sequence === questionTarget),
|
|
1566
|
-
);
|
|
1567
|
-
if (questionGroup) {
|
|
1568
|
-
// Exact navigation wins over prepend correction on the same commit.
|
|
1569
|
-
jumpToQuestion(questionGroup.key);
|
|
1570
|
-
setQuestionTarget(null);
|
|
1571
|
-
} else if (pendingJumpToStartRef.current && firstItemChanged) {
|
|
1581
|
+
if (pendingJumpToStartRef.current && firstItemChanged) {
|
|
1572
1582
|
// The oldest window landed — jump against the NEW DOM, and skip the
|
|
1573
1583
|
// prepend correction (it would shift the reader away from the top).
|
|
1574
1584
|
pendingJumpToStartRef.current = false;
|
|
@@ -2187,460 +2197,477 @@ export function MessageTimeline({
|
|
|
2187
2197
|
};
|
|
2188
2198
|
|
|
2189
2199
|
const timeline = (
|
|
2190
|
-
<
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
// header pins flush to the scrollport. Pinning it lower left a
|
|
2202
|
-
// band of scrolling rows visible above the header, which then
|
|
2203
|
-
// looked like it floated over the middle of the timeline. The
|
|
2204
|
-
// floating question action sits below this strip instead.
|
|
2205
|
-
style={{ "--og-work-header-top": "-4rem" } as CSSProperties}
|
|
2206
|
-
>
|
|
2207
|
-
{onAnnotate ? (
|
|
2208
|
-
<Suspense fallback={null}>
|
|
2209
|
-
<TimelineAnnotationSelection
|
|
2210
|
-
rootRef={scrollRef}
|
|
2211
|
-
sources={annotationSources}
|
|
2212
|
-
onAnnotate={onAnnotate}
|
|
2213
|
-
/>
|
|
2214
|
-
</Suspense>
|
|
2215
|
-
) : null}
|
|
2216
|
-
{/* Pinned: anchoring off so the tip-follow camera owns the motion.
|
|
2217
|
-
Unpinned: native scroll anchoring holds the reader's place. */}
|
|
2200
|
+
<AllowancePresentationProvider
|
|
2201
|
+
render={renderAllowanceExhausted}
|
|
2202
|
+
labels={allowanceExhaustedLabels}
|
|
2203
|
+
>
|
|
2204
|
+
<LightboxProvider>
|
|
2205
|
+
<FoldMemoryProvider value={foldMemoryRef.current}>
|
|
2206
|
+
<SeenActivityIdsProvider value={seenActivityIdsRef.current}>
|
|
2207
|
+
<TimelineComputeLabelProvider value={computeLabel ?? null}>
|
|
2208
|
+
<EntranceAnimationProvider value={false}>
|
|
2209
|
+
<TooltipProvider delayDuration={400}>
|
|
2210
|
+
<TimelineAnnotationSourceRootContext.Provider value={scrollRef}>
|
|
2218
2211
|
<div
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
touchPositionRef.current = null;
|
|
2253
|
-
}}
|
|
2254
|
-
onTouchCancel={() => {
|
|
2255
|
-
touchPositionRef.current = null;
|
|
2256
|
-
}}
|
|
2257
|
-
onClickCapture={(event) => {
|
|
2258
|
-
const target =
|
|
2259
|
-
event.target instanceof Element
|
|
2260
|
-
? event.target.closest("button[aria-expanded]")
|
|
2212
|
+
className={cn("og-root relative flex min-h-0 flex-col", className)}
|
|
2213
|
+
// Sticky insets start at the scroller's padded content edge
|
|
2214
|
+
// (pt-16). Subtract exactly that padding so an expanded work
|
|
2215
|
+
// header pins flush to the scrollport. Pinning it lower left a
|
|
2216
|
+
// band of scrolling rows visible above the header, which then
|
|
2217
|
+
// looked like it floated over the middle of the timeline. The
|
|
2218
|
+
// floating question action sits below this strip instead.
|
|
2219
|
+
style={{ "--og-work-header-top": "-4rem" } as CSSProperties}
|
|
2220
|
+
>
|
|
2221
|
+
{onAnnotate ? (
|
|
2222
|
+
<Suspense fallback={null}>
|
|
2223
|
+
<TimelineAnnotationSelection
|
|
2224
|
+
rootRef={scrollRef}
|
|
2225
|
+
sources={annotationSources}
|
|
2226
|
+
onAnnotate={onAnnotate}
|
|
2227
|
+
/>
|
|
2228
|
+
</Suspense>
|
|
2229
|
+
) : null}
|
|
2230
|
+
{/* Pinned: anchoring off so the tip-follow camera owns the motion.
|
|
2231
|
+
Unpinned: native scroll anchoring holds the reader's place. */}
|
|
2232
|
+
<div
|
|
2233
|
+
ref={scrollRef}
|
|
2234
|
+
data-og-timeline-scroller=""
|
|
2235
|
+
data-og-bottom-follow={autoFollow && pinned && !hasNewer ? "true" : "false"}
|
|
2236
|
+
tabIndex={-1}
|
|
2237
|
+
onScroll={onScroll}
|
|
2238
|
+
onScrollEnd={onScrollEnd}
|
|
2239
|
+
onWheel={onWheel}
|
|
2240
|
+
onTouchStart={(event) => {
|
|
2241
|
+
stopSettlement();
|
|
2242
|
+
const touch = event.touches.length === 1 ? event.touches[0] : undefined;
|
|
2243
|
+
touchPositionRef.current = touch
|
|
2244
|
+
? { x: touch.clientX, y: touch.clientY }
|
|
2261
2245
|
: null;
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2246
|
+
}}
|
|
2247
|
+
onTouchMove={(event) => {
|
|
2248
|
+
const touch = event.touches[0];
|
|
2249
|
+
const previous = touchPositionRef.current;
|
|
2250
|
+
if (!touch || !previous || event.touches.length !== 1) {
|
|
2251
|
+
touchPositionRef.current = null;
|
|
2252
|
+
return;
|
|
2269
2253
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2254
|
+
const deltaX = previous.x - touch.clientX;
|
|
2255
|
+
const deltaY = previous.y - touch.clientY;
|
|
2256
|
+
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
2257
|
+
touchPositionRef.current = { x: touch.clientX, y: touch.clientY };
|
|
2258
|
+
onWheel({
|
|
2259
|
+
deltaX,
|
|
2260
|
+
deltaY,
|
|
2261
|
+
target: event.target,
|
|
2262
|
+
currentTarget: event.currentTarget,
|
|
2263
|
+
});
|
|
2264
|
+
}}
|
|
2265
|
+
onTouchEnd={() => {
|
|
2266
|
+
touchPositionRef.current = null;
|
|
2267
|
+
}}
|
|
2268
|
+
onTouchCancel={() => {
|
|
2269
|
+
touchPositionRef.current = null;
|
|
2270
|
+
}}
|
|
2271
|
+
onClickCapture={(event) => {
|
|
2272
|
+
const target =
|
|
2273
|
+
event.target instanceof Element
|
|
2274
|
+
? event.target.closest("button[aria-expanded]")
|
|
2275
|
+
: null;
|
|
2276
|
+
if (target) {
|
|
2277
|
+
if (target.hasAttribute("data-og-work-header")) {
|
|
2278
|
+
releaseProgress(
|
|
2279
|
+
target
|
|
2280
|
+
.closest("[data-og-group-key]")
|
|
2281
|
+
?.getAttribute("data-og-group-key") ?? null,
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2296
2284
|
releasePinFromReader();
|
|
2285
|
+
disclosureKeepsUnpinnedRef.current = true;
|
|
2297
2286
|
}
|
|
2298
|
-
readingAnchorRef.current =
|
|
2299
|
-
!pinnedRef.current && scrollRef.current
|
|
2300
|
-
? captureTimelineAnchor(scrollRef.current)
|
|
2301
|
-
: null;
|
|
2302
|
-
settlementRef.current =
|
|
2303
|
-
readableTurns &&
|
|
2304
|
-
autoFollow &&
|
|
2305
|
-
pinnedRef.current &&
|
|
2306
|
-
revealedRef.current &&
|
|
2307
|
-
!hasNewer &&
|
|
2308
|
-
!pendingReaderLeaveRef.current &&
|
|
2309
|
-
!disclosureKeepsUnpinnedRef.current &&
|
|
2310
|
-
!prefersReducedMotion() &&
|
|
2311
|
-
scrollRef.current
|
|
2312
|
-
? captureTimelineSettlement(scrollRef.current, groups)
|
|
2313
|
-
: null;
|
|
2314
2287
|
}}
|
|
2288
|
+
onFocusCapture={(event) => {
|
|
2289
|
+
if (readableTurns && event.target !== event.currentTarget) {
|
|
2290
|
+
stopSettlement();
|
|
2291
|
+
releasePinFromReader();
|
|
2292
|
+
disclosureKeepsUnpinnedRef.current = true;
|
|
2293
|
+
}
|
|
2294
|
+
}}
|
|
2295
|
+
onPointerDown={onPointerDown}
|
|
2296
|
+
onKeyDown={onKeyDown}
|
|
2297
|
+
style={
|
|
2298
|
+
groups.length > 0 && !revealed ? { visibility: "hidden" } : undefined
|
|
2299
|
+
}
|
|
2300
|
+
className={cn(
|
|
2301
|
+
// tabIndex=-1 is programmatic only — never paint a focus ring on
|
|
2302
|
+
// the whole scroller (click + Shift used to flash a blue outline).
|
|
2303
|
+
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pt-16 pb-6 sm:px-6 outline-hidden",
|
|
2304
|
+
autoFollow && pinned && !hasNewer
|
|
2305
|
+
? "[overflow-anchor:none]"
|
|
2306
|
+
: "[overflow-anchor:auto]",
|
|
2307
|
+
)}
|
|
2315
2308
|
>
|
|
2316
|
-
<
|
|
2317
|
-
|
|
2318
|
-
{
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2309
|
+
<TimelineBeforeLayout
|
|
2310
|
+
capture={() => {
|
|
2311
|
+
if (readableTurns && timelineHasReader(scrollRef.current)) {
|
|
2312
|
+
releasePinFromReader();
|
|
2313
|
+
}
|
|
2314
|
+
readingAnchorRef.current =
|
|
2315
|
+
!pinnedRef.current && scrollRef.current
|
|
2316
|
+
? captureTimelineAnchor(scrollRef.current)
|
|
2317
|
+
: null;
|
|
2318
|
+
settlementRef.current =
|
|
2319
|
+
readableTurns &&
|
|
2320
|
+
autoFollow &&
|
|
2321
|
+
pinnedRef.current &&
|
|
2322
|
+
revealedRef.current &&
|
|
2323
|
+
!hasNewer &&
|
|
2324
|
+
!pendingReaderLeaveRef.current &&
|
|
2325
|
+
!disclosureKeepsUnpinnedRef.current &&
|
|
2326
|
+
!prefersReducedMotion() &&
|
|
2327
|
+
scrollRef.current
|
|
2328
|
+
? captureTimelineSettlement(scrollRef.current, groups)
|
|
2329
|
+
: null;
|
|
2330
|
+
}}
|
|
2331
|
+
>
|
|
2332
|
+
<div className="relative mx-auto flex w-full max-w-3xl flex-col gap-5">
|
|
2333
|
+
<AnimatePresence>
|
|
2334
|
+
{loadingOlder ||
|
|
2335
|
+
loadingOldest ||
|
|
2336
|
+
(hasOlder && onJumpToStart && olderPrefetchArmed) ||
|
|
2337
|
+
underfillRetryReady ? (
|
|
2338
|
+
// Reserved top gutter: controls scroll with history and cannot
|
|
2339
|
+
// cover a disclosure. Visibility never changes content height.
|
|
2340
|
+
<motion.div
|
|
2341
|
+
initial={{ opacity: 0, y: -6 }}
|
|
2342
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2343
|
+
exit={{ opacity: 0, y: -6 }}
|
|
2344
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2345
|
+
data-og-loading-older=""
|
|
2346
|
+
aria-live="polite"
|
|
2347
|
+
className={cn(
|
|
2348
|
+
"pointer-events-none absolute inset-x-0 -top-11 z-10 flex",
|
|
2349
|
+
// The scrolling history control and fixed question
|
|
2350
|
+
// action must never share the same pointer region.
|
|
2351
|
+
questionNav
|
|
2352
|
+
? "max-w-[calc(50%-0.5rem)] justify-start"
|
|
2353
|
+
: "justify-center",
|
|
2354
|
+
)}
|
|
2355
|
+
>
|
|
2356
|
+
{loadingOlder || loadingOldest ? (
|
|
2357
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
2358
|
+
<span className="og-shimmer-text">
|
|
2359
|
+
{loadingOldest
|
|
2360
|
+
? "Jumping to start…"
|
|
2361
|
+
: "Loading earlier activity…"}
|
|
2362
|
+
</span>
|
|
2346
2363
|
</span>
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
}
|
|
2368
|
-
applyPinned(false);
|
|
2369
|
-
pendingJumpToStartRef.current = true;
|
|
2370
|
-
const seq = ++jumpToStartSeqRef.current;
|
|
2371
|
-
void Promise.resolve(onJumpToStart!()).then(
|
|
2372
|
-
() => {
|
|
2373
|
-
// The commit that swaps in the oldest window consumes
|
|
2374
|
-
// the flag against the new DOM; this write covers the
|
|
2375
|
-
// already-committed order and the no-window-change
|
|
2376
|
-
// case (jumping within the current window).
|
|
2377
|
-
const scroller = scrollRef.current ?? node;
|
|
2378
|
-
if (scroller) {
|
|
2379
|
-
scroller.scrollTop = 0;
|
|
2364
|
+
) : null}
|
|
2365
|
+
{hasOlder &&
|
|
2366
|
+
!loadingOlder &&
|
|
2367
|
+
!loadingOldest &&
|
|
2368
|
+
(underfillRetryReady || onJumpToStart) ? (
|
|
2369
|
+
<button
|
|
2370
|
+
type="button"
|
|
2371
|
+
data-og-retry={underfillRetryReady || undefined}
|
|
2372
|
+
data-og-jump-to-start={!underfillRetryReady || undefined}
|
|
2373
|
+
onClick={() => {
|
|
2374
|
+
const node = scrollRef.current;
|
|
2375
|
+
if (underfillRetryReady) {
|
|
2376
|
+
if (node && underfillRetryReadyRef.current) {
|
|
2377
|
+
// AnimatePresence retains this handler during
|
|
2378
|
+
// exit. Current authorization plus the exact
|
|
2379
|
+
// attempt check prevent stale dispatch.
|
|
2380
|
+
requestOlderIfUnderfilled(
|
|
2381
|
+
node,
|
|
2382
|
+
underfillSettledAttempt!,
|
|
2383
|
+
);
|
|
2380
2384
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
applyPinned(false);
|
|
2388
|
+
pendingJumpToStartRef.current = true;
|
|
2389
|
+
const seq = ++jumpToStartSeqRef.current;
|
|
2390
|
+
void Promise.resolve(onJumpToStart!()).then(
|
|
2391
|
+
() => {
|
|
2392
|
+
// The commit that swaps in the oldest window consumes
|
|
2393
|
+
// the flag against the new DOM; this write covers the
|
|
2394
|
+
// already-committed order and the no-window-change
|
|
2395
|
+
// case (jumping within the current window).
|
|
2396
|
+
const scroller = scrollRef.current ?? node;
|
|
2397
|
+
if (scroller) {
|
|
2398
|
+
scroller.scrollTop = 0;
|
|
2399
|
+
}
|
|
2400
|
+
// A host may resolve without ever changing the
|
|
2401
|
+
// window (already on the oldest page). Any swap
|
|
2402
|
+
// commit runs its layout effect before the next
|
|
2403
|
+
// frame, so a flag still armed by then is the
|
|
2404
|
+
// no-change case — clear it, or a LATER prepend
|
|
2405
|
+
// would spuriously jump the reader to the top.
|
|
2406
|
+
requestFrame(() => {
|
|
2407
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
2408
|
+
pendingJumpToStartRef.current = false;
|
|
2409
|
+
}
|
|
2410
|
+
});
|
|
2411
|
+
},
|
|
2412
|
+
() => {
|
|
2388
2413
|
if (jumpToStartSeqRef.current === seq) {
|
|
2389
2414
|
pendingJumpToStartRef.current = false;
|
|
2390
2415
|
}
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2416
|
+
},
|
|
2417
|
+
);
|
|
2418
|
+
}}
|
|
2419
|
+
className="pointer-events-auto rounded-full border border-og-border px-3 py-1.5 text-og-control"
|
|
2420
|
+
>
|
|
2421
|
+
{underfillRetryReady
|
|
2422
|
+
? underfillSettledAttempt?.[2]?.tailPreserved
|
|
2423
|
+
? "Load earlier activity"
|
|
2424
|
+
: "Retry earlier activity"
|
|
2425
|
+
: "Jump to start"}
|
|
2426
|
+
</button>
|
|
2427
|
+
) : null}
|
|
2428
|
+
</motion.div>
|
|
2429
|
+
) : null}
|
|
2430
|
+
</AnimatePresence>
|
|
2431
|
+
{!groups.length
|
|
2432
|
+
? (emptyState ?? (
|
|
2433
|
+
<p className="py-10 text-center text-og-menu text-og-fg-subtle">
|
|
2434
|
+
No activity yet.
|
|
2435
|
+
</p>
|
|
2436
|
+
))
|
|
2437
|
+
: null}
|
|
2438
|
+
{hasOlder && olderPrefetchArmed ? (
|
|
2439
|
+
// Overlaid, not a layout row: mounting/unmounting the sentinel
|
|
2440
|
+
// must never shift content (that shift was itself a wobble).
|
|
2441
|
+
// End at the scroll origin above the pt-16 gutter so the
|
|
2442
|
+
// observer and scrollTop cooldown share the same 400px band.
|
|
2443
|
+
<div
|
|
2444
|
+
ref={topSentinelRef}
|
|
2445
|
+
data-og-top-sentinel=""
|
|
2446
|
+
data-og-timeline-chrome=""
|
|
2447
|
+
aria-hidden="true"
|
|
2448
|
+
className="pointer-events-none absolute inset-x-0 -top-16 h-px -translate-y-full"
|
|
2449
|
+
/>
|
|
2410
2450
|
) : null}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
// Overlaid, not a layout row: mounting/unmounting the sentinel
|
|
2421
|
-
// must never shift content (that shift was itself a wobble).
|
|
2422
|
-
// End at the scroll origin above the pt-16 gutter so the
|
|
2423
|
-
// observer and scrollTop cooldown share the same 400px band.
|
|
2424
|
-
<div
|
|
2425
|
-
ref={topSentinelRef}
|
|
2426
|
-
data-og-top-sentinel=""
|
|
2427
|
-
data-og-timeline-chrome=""
|
|
2428
|
-
aria-hidden="true"
|
|
2429
|
-
className="pointer-events-none absolute inset-x-0 -top-16 h-px -translate-y-full"
|
|
2430
|
-
/>
|
|
2431
|
-
) : null}
|
|
2432
|
-
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
2433
|
-
return (
|
|
2434
|
-
<TimelineSearchRevealContext.Provider
|
|
2435
|
-
key={key}
|
|
2436
|
-
value={
|
|
2437
|
-
searchItem && timelineGroupItemIds(group).includes(searchItem.id)
|
|
2438
|
-
? searchRevealKey
|
|
2439
|
-
: null
|
|
2440
|
-
}
|
|
2441
|
-
>
|
|
2442
|
-
<TimelineGroupEntry
|
|
2443
|
-
groupKey={key}
|
|
2444
|
-
group={group}
|
|
2445
|
-
nextGroup={groups[index + 1]?.group}
|
|
2446
|
-
startupDismissed={
|
|
2447
|
-
group.kind === "activity" &&
|
|
2448
|
-
group.items.some(
|
|
2449
|
-
(item) => item.turnId && turnsWithOutput.has(item.turnId),
|
|
2450
|
-
)
|
|
2451
|
-
}
|
|
2452
|
-
entranceEnabled={entranceEnabled}
|
|
2453
|
-
liveEntranceEnabled={
|
|
2454
|
-
group.kind === "activity" ? !bulkRender : undefined
|
|
2451
|
+
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
2452
|
+
return (
|
|
2453
|
+
<TimelineSearchRevealContext.Provider
|
|
2454
|
+
key={key}
|
|
2455
|
+
value={
|
|
2456
|
+
searchItem &&
|
|
2457
|
+
timelineGroupItemIds(group).includes(searchItem.id)
|
|
2458
|
+
? searchRevealKey
|
|
2459
|
+
: null
|
|
2455
2460
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2461
|
+
>
|
|
2462
|
+
<TimelineGroupEntry
|
|
2463
|
+
groupKey={key}
|
|
2464
|
+
group={group}
|
|
2465
|
+
nextGroup={groups[index + 1]?.group}
|
|
2466
|
+
startupDismissed={
|
|
2467
|
+
group.kind === "activity" &&
|
|
2468
|
+
group.items.some(
|
|
2469
|
+
(item) => item.turnId && turnsWithOutput.has(item.turnId),
|
|
2470
|
+
)
|
|
2471
|
+
}
|
|
2472
|
+
entranceEnabled={entranceEnabled}
|
|
2473
|
+
liveEntranceEnabled={
|
|
2474
|
+
group.kind === "activity" ? !bulkRender : undefined
|
|
2475
|
+
}
|
|
2476
|
+
context={timelineGroupEntryContext}
|
|
2477
|
+
/>
|
|
2478
|
+
</TimelineSearchRevealContext.Provider>
|
|
2479
|
+
);
|
|
2480
|
+
})}
|
|
2481
|
+
{groups.length > 0 && trailingState ? (
|
|
2482
|
+
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
2483
|
+
) : null}
|
|
2484
|
+
{hasNewer && newerFailure?.key === newerBoundaryKey ? (
|
|
2485
|
+
<div
|
|
2486
|
+
data-og-newer-error=""
|
|
2487
|
+
className="flex flex-col items-center gap-2 px-4 py-3 text-center text-og-menu text-og-fg-muted"
|
|
2488
|
+
>
|
|
2489
|
+
<p role="status" className="max-w-prose [overflow-wrap:anywhere]">
|
|
2490
|
+
Couldn’t load later activity. {newerFailure.message}
|
|
2491
|
+
</p>
|
|
2492
|
+
<button
|
|
2493
|
+
ref={newerRetryButtonRef}
|
|
2494
|
+
type="button"
|
|
2495
|
+
data-og-retry-newer=""
|
|
2496
|
+
className="min-h-11 rounded-og-md border border-og-border px-3 py-2 text-og-fg hover:bg-og-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-og-accent"
|
|
2497
|
+
aria-disabled={loadingNewer || newerRetryPending}
|
|
2498
|
+
aria-busy={newerRetryPending}
|
|
2499
|
+
onClick={() => requestNewer(true)}
|
|
2500
|
+
>
|
|
2501
|
+
{newerRetryPending
|
|
2502
|
+
? "Retrying later activity…"
|
|
2503
|
+
: "Retry later activity"}
|
|
2504
|
+
</button>
|
|
2505
|
+
</div>
|
|
2506
|
+
) : null}
|
|
2507
|
+
{hasNewer ? (
|
|
2508
|
+
<div
|
|
2509
|
+
ref={bottomSentinelRef}
|
|
2510
|
+
data-og-bottom-sentinel=""
|
|
2511
|
+
data-og-timeline-chrome=""
|
|
2512
|
+
aria-hidden="true"
|
|
2513
|
+
className="h-px w-full"
|
|
2514
|
+
/>
|
|
2515
|
+
) : null}
|
|
2516
|
+
</div>
|
|
2517
|
+
</TimelineBeforeLayout>
|
|
2518
|
+
</div>
|
|
2519
|
+
{draftAnnotations && draftAnnotations.length > 0 ? (
|
|
2520
|
+
<Suspense fallback={null}>
|
|
2521
|
+
<TimelineAnnotationMarkers
|
|
2522
|
+
rootRef={scrollRef}
|
|
2523
|
+
annotations={draftAnnotations}
|
|
2524
|
+
onSelect={onDraftAnnotationSelect}
|
|
2525
|
+
/>
|
|
2526
|
+
</Suspense>
|
|
2527
|
+
) : null}
|
|
2528
|
+
|
|
2529
|
+
<AnimatePresence>
|
|
2530
|
+
{questionNav ? (
|
|
2531
|
+
<motion.div
|
|
2532
|
+
key="question-nav"
|
|
2533
|
+
initial={{ opacity: 0, y: -6 }}
|
|
2534
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2535
|
+
exit={{ opacity: 0, y: -6 }}
|
|
2536
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2537
|
+
data-og-question-nav=""
|
|
2538
|
+
// Below the pinned work-header strip (py-1.5 row, 44px on coarse
|
|
2539
|
+
// pointers), never over it: the header stays a full-width target.
|
|
2540
|
+
data-og-pill-placement={questionPillPlacement.placement}
|
|
2541
|
+
style={{ marginTop: questionPillPlacement.offsetY }}
|
|
2542
|
+
className={cn(
|
|
2543
|
+
"pointer-events-none absolute inset-x-0 top-11 z-10 flex px-4 sm:px-6 pointer-coarse:top-14",
|
|
2544
|
+
QUESTION_PILL_PLACEMENT_CLASS[questionPillPlacement.placement],
|
|
2545
|
+
)}
|
|
2546
|
+
>
|
|
2547
|
+
<motion.div
|
|
2548
|
+
ref={questionPillRef}
|
|
2549
|
+
layout="position"
|
|
2550
|
+
transition={{ duration: 0.18, ease: "easeOut" }}
|
|
2551
|
+
className="pointer-events-auto inline-flex max-w-full items-center rounded-full border border-og-border bg-og-surface-3/90 text-og-control font-medium text-og-fg shadow-og-md backdrop-blur"
|
|
2468
2552
|
>
|
|
2469
|
-
<p role="status" className="max-w-prose [overflow-wrap:anywhere]">
|
|
2470
|
-
Couldn’t load later activity. {newerFailure.message}
|
|
2471
|
-
</p>
|
|
2472
2553
|
<button
|
|
2473
|
-
ref={newerRetryButtonRef}
|
|
2474
2554
|
type="button"
|
|
2475
|
-
data-og-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
aria-busy={newerRetryPending}
|
|
2479
|
-
onClick={() => requestNewer(true)}
|
|
2555
|
+
data-og-jump-to-question=""
|
|
2556
|
+
onClick={() => jumpToQuestion(questionNav.key)}
|
|
2557
|
+
className="inline-flex min-w-0 items-center gap-1.5 whitespace-nowrap px-3 py-1.5 hover:text-og-fg pointer-coarse:min-h-11"
|
|
2480
2558
|
>
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
: "Retry later activity"}
|
|
2559
|
+
<ArrowUpIcon aria-hidden className="size-3.5" />
|
|
2560
|
+
Back to your message
|
|
2484
2561
|
</button>
|
|
2485
|
-
</div>
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
<span className={LOADING_CHIP_CLASS}>
|
|
2556
|
-
<span className="og-shimmer-text">Loading later activity…</span>
|
|
2557
|
-
</span>
|
|
2558
|
-
</motion.div>
|
|
2559
|
-
) : null}
|
|
2560
|
-
</AnimatePresence>
|
|
2561
|
-
<AnimatePresence>
|
|
2562
|
-
{((!pinned && autoFollow) || hasNewer || canSkipTipCatchup) && autoFollow ? (
|
|
2563
|
-
<motion.button
|
|
2564
|
-
type="button"
|
|
2565
|
-
data-og-jump-to-latest=""
|
|
2566
|
-
initial={{ opacity: 0, y: 8 }}
|
|
2567
|
-
animate={{ opacity: 1, y: 0 }}
|
|
2568
|
-
exit={{ opacity: 0, y: 8 }}
|
|
2569
|
-
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2570
|
-
onClick={() => {
|
|
2571
|
-
// Returning to the tip explicitly releases reader-owned
|
|
2572
|
-
// prose. Clear only this timeline's selection before the
|
|
2573
|
-
// synchronous ownership check on the next commit.
|
|
2574
|
-
const viewport = scrollRef.current;
|
|
2575
|
-
const selection = viewport?.ownerDocument.getSelection();
|
|
2576
|
-
if (
|
|
2577
|
-
selection &&
|
|
2578
|
-
!selection.isCollapsed &&
|
|
2579
|
-
(viewport?.contains(selection.anchorNode) ||
|
|
2580
|
-
viewport?.contains(selection.focusNode))
|
|
2581
|
-
)
|
|
2582
|
-
selection.removeAllRanges();
|
|
2583
|
-
disclosureKeepsUnpinnedRef.current = false;
|
|
2584
|
-
if (hasNewer) {
|
|
2585
|
-
// Do not pin against the current history page — its bottom
|
|
2586
|
-
// is not the tip. The pin + snap run when the tip window
|
|
2587
|
-
// actually lands (`hasNewer` flips false).
|
|
2588
|
-
wantPinRef.current = true;
|
|
2589
|
-
const node = scrollRef.current;
|
|
2590
|
-
if (onJumpToLatest) {
|
|
2591
|
-
void Promise.resolve(onJumpToLatest()).then(
|
|
2592
|
-
() => {
|
|
2593
|
-
// Covers a host that flipped hasNewer before
|
|
2594
|
-
// resolving; otherwise the tip-window commit
|
|
2595
|
-
// consumes the flag.
|
|
2596
|
-
const current = scrollRef.current;
|
|
2597
|
-
if (current && wantPinRef.current && !hasNewerRef.current) {
|
|
2562
|
+
</motion.div>
|
|
2563
|
+
</motion.div>
|
|
2564
|
+
) : null}
|
|
2565
|
+
</AnimatePresence>
|
|
2566
|
+
<AnimatePresence>
|
|
2567
|
+
{loadingNewer ? (
|
|
2568
|
+
<motion.div
|
|
2569
|
+
initial={{ opacity: 0, y: 6 }}
|
|
2570
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2571
|
+
exit={{ opacity: 0, y: 6 }}
|
|
2572
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2573
|
+
data-og-loading-newer=""
|
|
2574
|
+
aria-live="polite"
|
|
2575
|
+
className="pointer-events-none absolute inset-x-0 bottom-14 z-10 flex justify-center"
|
|
2576
|
+
>
|
|
2577
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
2578
|
+
<span className="og-shimmer-text">Loading later activity…</span>
|
|
2579
|
+
</span>
|
|
2580
|
+
</motion.div>
|
|
2581
|
+
) : null}
|
|
2582
|
+
</AnimatePresence>
|
|
2583
|
+
<AnimatePresence>
|
|
2584
|
+
{jumpPillShown ? (
|
|
2585
|
+
<motion.button
|
|
2586
|
+
ref={jumpPillRef}
|
|
2587
|
+
type="button"
|
|
2588
|
+
data-og-jump-to-latest=""
|
|
2589
|
+
data-og-pill-placement={jumpPillPlacement.placement}
|
|
2590
|
+
layout="position"
|
|
2591
|
+
initial={{ opacity: 0, y: 8 }}
|
|
2592
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2593
|
+
exit={{ opacity: 0, y: 8 }}
|
|
2594
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2595
|
+
onClick={() => {
|
|
2596
|
+
// Returning to the tip explicitly releases reader-owned
|
|
2597
|
+
// prose. Clear only this timeline's selection before the
|
|
2598
|
+
// synchronous ownership check on the next commit.
|
|
2599
|
+
const viewport = scrollRef.current;
|
|
2600
|
+
const selection = viewport?.ownerDocument.getSelection();
|
|
2601
|
+
if (
|
|
2602
|
+
selection &&
|
|
2603
|
+
!selection.isCollapsed &&
|
|
2604
|
+
(viewport?.contains(selection.anchorNode) ||
|
|
2605
|
+
viewport?.contains(selection.focusNode))
|
|
2606
|
+
)
|
|
2607
|
+
selection.removeAllRanges();
|
|
2608
|
+
disclosureKeepsUnpinnedRef.current = false;
|
|
2609
|
+
if (hasNewer) {
|
|
2610
|
+
// Do not pin against the current history page — its bottom
|
|
2611
|
+
// is not the tip. The pin + snap run when the tip window
|
|
2612
|
+
// actually lands (`hasNewer` flips false).
|
|
2613
|
+
wantPinRef.current = true;
|
|
2614
|
+
const node = scrollRef.current;
|
|
2615
|
+
if (onJumpToLatest) {
|
|
2616
|
+
void Promise.resolve(onJumpToLatest()).then(
|
|
2617
|
+
() => {
|
|
2618
|
+
// Covers a host that flipped hasNewer before
|
|
2619
|
+
// resolving; otherwise the tip-window commit
|
|
2620
|
+
// consumes the flag.
|
|
2621
|
+
const current = scrollRef.current;
|
|
2622
|
+
if (current && wantPinRef.current && !hasNewerRef.current) {
|
|
2623
|
+
wantPinRef.current = false;
|
|
2624
|
+
applyPinned(true);
|
|
2625
|
+
snapToBottom(current);
|
|
2626
|
+
}
|
|
2627
|
+
},
|
|
2628
|
+
() => {
|
|
2629
|
+
// The tip reload failed (ordinary network error):
|
|
2630
|
+
// an armed latch would fire a surprise snap when
|
|
2631
|
+
// the reader later pages to the tip themselves.
|
|
2598
2632
|
wantPinRef.current = false;
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
// window so the newer sentinel can page forward; the
|
|
2613
|
-
// latch pins if the tip window eventually lands.
|
|
2633
|
+
},
|
|
2634
|
+
);
|
|
2635
|
+
} else if (node) {
|
|
2636
|
+
// No tip reload available: jump within the in-memory
|
|
2637
|
+
// window so the newer sentinel can page forward; the
|
|
2638
|
+
// latch pins if the tip window eventually lands.
|
|
2639
|
+
snapToBottom(node);
|
|
2640
|
+
}
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
const node = scrollRef.current;
|
|
2644
|
+
if (node) {
|
|
2645
|
+
applyPinned(true);
|
|
2614
2646
|
snapToBottom(node);
|
|
2615
2647
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
"
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
</EntranceAnimationProvider>
|
|
2640
|
-
</TimelineComputeLabelProvider>
|
|
2641
|
-
</SeenActivityIdsProvider>
|
|
2642
|
-
</FoldMemoryProvider>
|
|
2643
|
-
</LightboxProvider>
|
|
2648
|
+
}}
|
|
2649
|
+
className={cn(
|
|
2650
|
+
"absolute bottom-4 w-fit",
|
|
2651
|
+
JUMP_PILL_PLACEMENT_CLASS[jumpPillPlacement.placement],
|
|
2652
|
+
"inline-flex items-center gap-1.5 rounded-full border border-og-border bg-og-surface-3/90 px-3 py-1.5",
|
|
2653
|
+
"text-og-control font-medium text-og-fg shadow-og-md backdrop-blur",
|
|
2654
|
+
"hover:border-og-border-strong",
|
|
2655
|
+
)}
|
|
2656
|
+
>
|
|
2657
|
+
<ArrowDownIcon className="size-3.5" />
|
|
2658
|
+
Jump to latest
|
|
2659
|
+
</motion.button>
|
|
2660
|
+
) : null}
|
|
2661
|
+
</AnimatePresence>
|
|
2662
|
+
</div>
|
|
2663
|
+
</TimelineAnnotationSourceRootContext.Provider>
|
|
2664
|
+
</TooltipProvider>
|
|
2665
|
+
</EntranceAnimationProvider>
|
|
2666
|
+
</TimelineComputeLabelProvider>
|
|
2667
|
+
</SeenActivityIdsProvider>
|
|
2668
|
+
</FoldMemoryProvider>
|
|
2669
|
+
</LightboxProvider>
|
|
2670
|
+
</AllowancePresentationProvider>
|
|
2644
2671
|
);
|
|
2645
2672
|
// Agent-authored object links resolve through the host, never the console.
|
|
2646
2673
|
return <OpenGeniLinkProvider resolveLink={resolveLink}>{timeline}</OpenGeniLinkProvider>;
|
|
@@ -3899,30 +3926,43 @@ function UserMessageRow({
|
|
|
3899
3926
|
{deliveryFailed ? (
|
|
3900
3927
|
<div
|
|
3901
3928
|
role="status"
|
|
3902
|
-
className="flex max-w-full items-
|
|
3929
|
+
className="flex max-w-full flex-col items-start gap-1 px-1 text-og-xs text-og-status-failed"
|
|
3903
3930
|
>
|
|
3904
|
-
<span className="
|
|
3931
|
+
<span className="flex min-w-0 items-start gap-1">
|
|
3905
3932
|
<TriangleAlertIcon className="size-3.5 shrink-0" aria-hidden="true" />
|
|
3906
|
-
<span
|
|
3933
|
+
<span className="min-w-0 break-words">
|
|
3934
|
+
{item.delivery?.error || "Message not sent"}
|
|
3935
|
+
</span>
|
|
3907
3936
|
</span>
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3937
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
3938
|
+
{item.delivery?.onEdit ? (
|
|
3939
|
+
<button
|
|
3940
|
+
type="button"
|
|
3941
|
+
className="font-medium text-og-status-failed underline decoration-og-status-failed/50 underline-offset-2 hover:text-og-fg"
|
|
3942
|
+
onClick={item.delivery.onEdit}
|
|
3943
|
+
>
|
|
3944
|
+
Edit message
|
|
3945
|
+
</button>
|
|
3946
|
+
) : null}
|
|
3947
|
+
{item.delivery?.onRetry ? (
|
|
3948
|
+
<button
|
|
3949
|
+
type="button"
|
|
3950
|
+
className="font-medium text-og-status-failed underline decoration-og-status-failed/50 underline-offset-2 hover:text-og-fg"
|
|
3951
|
+
onClick={item.delivery.onRetry}
|
|
3952
|
+
>
|
|
3953
|
+
Retry
|
|
3954
|
+
</button>
|
|
3955
|
+
) : null}
|
|
3956
|
+
{item.delivery?.onRemove ? (
|
|
3957
|
+
<button
|
|
3958
|
+
type="button"
|
|
3959
|
+
className="font-medium text-og-fg-subtle underline decoration-og-border underline-offset-2 hover:text-og-fg"
|
|
3960
|
+
onClick={item.delivery.onRemove}
|
|
3961
|
+
>
|
|
3962
|
+
Remove
|
|
3963
|
+
</button>
|
|
3964
|
+
) : null}
|
|
3965
|
+
</div>
|
|
3926
3966
|
</div>
|
|
3927
3967
|
) : null}
|
|
3928
3968
|
</div>
|
|
@@ -4548,6 +4588,7 @@ function formatVideoDuration(seconds: number): string {
|
|
|
4548
4588
|
|
|
4549
4589
|
function NoticeRow({ item }: { item: NoticeItem }) {
|
|
4550
4590
|
const enter = useEntranceAnimation();
|
|
4591
|
+
if (item.allowance) return <AllowanceExhaustedRow refusal={item.allowance} />;
|
|
4551
4592
|
if (item.recordedOutcome) {
|
|
4552
4593
|
return (
|
|
4553
4594
|
<details
|