@opengeni/react 3.7.0-canary.0 → 3.7.0-canary.2
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/dist/artifacts.js +24 -5
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-R55HFTT3.js → chunk-3IY6UZI6.js} +4306 -4020
- package/dist/chunk-3IY6UZI6.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-FJBOU2TH.js → chunk-MOWYEBCQ.js} +1728 -12
- package/dist/chunk-MOWYEBCQ.js.map +1 -0
- package/dist/chunk-PAJU5KKL.js +331 -0
- package/dist/chunk-PAJU5KKL.js.map +1 -0
- package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
- package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
- package/dist/{chunk-DOK4KZF2.js → chunk-WZIMM3GX.js} +1536 -539
- package/dist/chunk-WZIMM3GX.js.map +1 -0
- package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
- package/dist/chunk-ZFOFY5ST.js.map +1 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +3 -2
- package/dist/components/child-session-link.d.ts +5 -0
- package/dist/components/session-chrome.d.ts +43 -6
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-conversation.d.ts +14 -0
- package/dist/components/timeline-anchor.d.ts +21 -0
- package/dist/composer.js +2 -3
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +716 -914
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/lib/format.d.ts +5 -0
- package/dist/machines.js +3 -2
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +14 -4
- package/dist/session.js +19 -19
- package/dist/timeline/types.d.ts +3 -1
- package/package.json +2 -2
- package/src/components/artifacts/published-html-artifact-frame.tsx +27 -4
- package/src/components/child-session-link.tsx +27 -0
- package/src/components/composer.tsx +1 -1
- package/src/components/machine-input-display.ts +8 -2
- package/src/components/message-timeline.tsx +336 -198
- package/src/components/session-chrome.tsx +572 -161
- package/src/components/session-commands-panel.tsx +113 -0
- package/src/components/session-conversation.tsx +141 -0
- package/src/components/timeline-anchor.tsx +91 -0
- package/src/conversation-timeline.ts +83 -0
- package/src/hooks/use-session-background-commands.ts +21 -10
- package/src/hooks/use-session-events.ts +36 -14
- package/src/hooks/use-session.ts +23 -2
- package/src/index.ts +4 -0
- package/src/lib/format.ts +32 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/projection.ts +6 -12
- package/src/timeline/types.ts +4 -0
- package/styles/compiled.css +513 -410
- package/styles/index.css +23 -0
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-C6WDLHBA.js +0 -2590
- package/dist/chunk-C6WDLHBA.js.map +0 -1
- package/dist/chunk-DOK4KZF2.js.map +0 -1
- package/dist/chunk-FJBOU2TH.js.map +0 -1
- package/dist/chunk-R55HFTT3.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChildSessionLink } from "./child-session-link";
|
|
1
2
|
import type {
|
|
2
3
|
DraftTimelineAnnotation,
|
|
3
4
|
MediaGenerationResult,
|
|
@@ -49,6 +50,12 @@ import {
|
|
|
49
50
|
type OlderHistoryLoader,
|
|
50
51
|
} from "../older-history";
|
|
51
52
|
import { Markdown } from "./markdown";
|
|
53
|
+
import {
|
|
54
|
+
TimelineBeforeLayout,
|
|
55
|
+
captureTimelineAnchor,
|
|
56
|
+
timelineAnchorCorrection,
|
|
57
|
+
type TimelineAnchor,
|
|
58
|
+
} from "./timeline-anchor";
|
|
52
59
|
import {
|
|
53
60
|
UserMessageBody,
|
|
54
61
|
UserMessageDisclosureProvider,
|
|
@@ -418,8 +425,15 @@ export function MessageTimeline({
|
|
|
418
425
|
(item) => item.kind !== "auth-needed" || shouldRenderAuthNeeded(item),
|
|
419
426
|
);
|
|
420
427
|
}, [items, events, shouldRenderAuthNeeded]);
|
|
421
|
-
|
|
428
|
+
// Event-window identity is independent of projected rows (partial messages
|
|
429
|
+
// can acquire a different first-delta id when older text arrives).
|
|
430
|
+
const sourceItems = events ?? items;
|
|
422
431
|
const olderBoundaryKey = sourceItems?.[0]?.id;
|
|
432
|
+
const previousSourceIdsRef = useRef(new Set<string>());
|
|
433
|
+
const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
|
|
434
|
+
const readingAnchorRef = useRef<TimelineAnchor | null>(null);
|
|
435
|
+
const olderPageBudgetRef = useRef(0);
|
|
436
|
+
const [olderDemand, setOlderDemand] = useState(0);
|
|
423
437
|
const allGroups = useMemo(() => groupTimeline(resolvedItems), [resolvedItems]);
|
|
424
438
|
const annotationSources = useMemo(() => {
|
|
425
439
|
const sources = new Map<string, TimelineAnnotationSourceDescriptor>();
|
|
@@ -648,29 +662,22 @@ export function MessageTimeline({
|
|
|
648
662
|
}, []);
|
|
649
663
|
|
|
650
664
|
/** Reader left the tip — wheel, keyboard, pointer-armed scroll-up, or scrollend. */
|
|
651
|
-
const releasePinFromReader = useCallback(
|
|
652
|
-
(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if (!olderPrefetchArmedRef.current) {
|
|
668
|
-
olderPrefetchArmedRef.current = true;
|
|
669
|
-
setOlderPrefetchArmed(true);
|
|
670
|
-
}
|
|
671
|
-
},
|
|
672
|
-
[autoFollow, applyPinned, clearPendingReaderLeave, clearReaderIntent, stopFollow],
|
|
673
|
-
);
|
|
665
|
+
const releasePinFromReader = useCallback(() => {
|
|
666
|
+
if (!autoFollow || !pinnedRef.current || hasNewerRef.current) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
clearReaderIntent();
|
|
670
|
+
clearPendingReaderLeave();
|
|
671
|
+
stopFollow();
|
|
672
|
+
applyPinned(false);
|
|
673
|
+
if (wantPinRef.current) {
|
|
674
|
+
wantPinRef.current = false;
|
|
675
|
+
}
|
|
676
|
+
if (!olderPrefetchArmedRef.current) {
|
|
677
|
+
olderPrefetchArmedRef.current = true;
|
|
678
|
+
setOlderPrefetchArmed(true);
|
|
679
|
+
}
|
|
680
|
+
}, [autoFollow, applyPinned, clearPendingReaderLeave, clearReaderIntent, stopFollow]);
|
|
674
681
|
|
|
675
682
|
/**
|
|
676
683
|
* Settled away from the tip while the camera is idle — Vimium / unfocused
|
|
@@ -691,7 +698,7 @@ export function MessageTimeline({
|
|
|
691
698
|
clearPendingReaderLeave();
|
|
692
699
|
return;
|
|
693
700
|
}
|
|
694
|
-
releasePinFromReader(
|
|
701
|
+
releasePinFromReader();
|
|
695
702
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
696
703
|
},
|
|
697
704
|
[autoFollow, clearPendingReaderLeave, rearmOlderPrefetchAfterLeavingTop, releasePinFromReader],
|
|
@@ -712,6 +719,19 @@ export function MessageTimeline({
|
|
|
712
719
|
});
|
|
713
720
|
}, [cancelLeaveFallback, releasePinAfterScrollSettled]);
|
|
714
721
|
|
|
722
|
+
const requestEarlierFromReader = () => {
|
|
723
|
+
releasePinFromReader();
|
|
724
|
+
wantPinRef.current = false;
|
|
725
|
+
// A stationary upward gesture is still demand. Successful short/folded
|
|
726
|
+
// pages may never create enough range to leave the prefetch band.
|
|
727
|
+
olderPageBudgetRef.current = 8;
|
|
728
|
+
if (olderLoadAttemptRef.current?.[1] === 2) {
|
|
729
|
+
olderLoadAttemptRef.current = null;
|
|
730
|
+
setOlderDemand((value) => value + 1);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
const touchPositionRef = useRef<{ x: number; y: number } | null>(null);
|
|
734
|
+
|
|
715
735
|
const onWheel = (event: {
|
|
716
736
|
deltaY: number;
|
|
717
737
|
deltaX: number;
|
|
@@ -729,12 +749,11 @@ export function MessageTimeline({
|
|
|
729
749
|
return;
|
|
730
750
|
}
|
|
731
751
|
disclosureKeepsUnpinnedRef.current = false;
|
|
752
|
+
programmaticScrollRef.current = 0;
|
|
732
753
|
if (event.deltaY >= 0) {
|
|
733
754
|
return;
|
|
734
755
|
}
|
|
735
|
-
|
|
736
|
-
event.currentTarget instanceof HTMLElement ? event.currentTarget : scrollRef.current;
|
|
737
|
-
releasePinFromReader(node);
|
|
756
|
+
requestEarlierFromReader();
|
|
738
757
|
};
|
|
739
758
|
|
|
740
759
|
/** Touch / stylus / mouse drag on the scroller — explicit leave (not layout). */
|
|
@@ -779,9 +798,8 @@ export function MessageTimeline({
|
|
|
779
798
|
if (event.key !== "ArrowUp" && event.key !== "PageUp" && event.key !== "Home") {
|
|
780
799
|
return;
|
|
781
800
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
releasePinFromReader(node);
|
|
801
|
+
programmaticScrollRef.current = 0;
|
|
802
|
+
requestEarlierFromReader();
|
|
785
803
|
};
|
|
786
804
|
|
|
787
805
|
const snapToBottom = useCallback(
|
|
@@ -1103,15 +1121,26 @@ export function MessageTimeline({
|
|
|
1103
1121
|
previousMaxScroll <= 1 ||
|
|
1104
1122
|
previousMaxScroll - previousScrollTop < Math.min(PIN_THRESHOLD_PX, previousMaxScroll);
|
|
1105
1123
|
const firstItemChanged = !!previousFirstItemId && firstItemId !== previousFirstItemId;
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1124
|
+
const sourceChanged = olderBoundaryKey !== previousSourceBoundaryRef.current;
|
|
1125
|
+
const retainedSource =
|
|
1126
|
+
sourceItems?.some((item) => previousSourceIdsRef.current.has(item.id)) ?? false;
|
|
1127
|
+
const prepended = sourceChanged && retainedSource;
|
|
1128
|
+
previousSourceBoundaryRef.current = olderBoundaryKey;
|
|
1129
|
+
previousSourceIdsRef.current = new Set(sourceItems?.map((item) => item.id));
|
|
1130
|
+
const readingAnchor = readingAnchorRef.current;
|
|
1131
|
+
readingAnchorRef.current = null;
|
|
1108
1132
|
const attempt = olderLoadAttemptRef.current;
|
|
1109
1133
|
const committedZeroOverlapOlderReplacement = !!(
|
|
1110
1134
|
attempt?.[2]?.committed &&
|
|
1111
|
-
|
|
1112
|
-
!
|
|
1135
|
+
sourceChanged &&
|
|
1136
|
+
!retainedSource
|
|
1113
1137
|
);
|
|
1114
1138
|
const restorePrependAnchor = () => {
|
|
1139
|
+
const correction = readingAnchor && timelineAnchorCorrection(node, readingAnchor);
|
|
1140
|
+
if (correction != null) {
|
|
1141
|
+
if (Math.abs(correction) > 1) writeScrollTop(node, node.scrollTop + correction);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1115
1144
|
// Keep the reader on the same retained rows. Prefer the exact first-item
|
|
1116
1145
|
// content coordinate (needed when a prepend merges inside one group),
|
|
1117
1146
|
// then the retained group offset, then scrollHeight as a final fallback.
|
|
@@ -1289,6 +1318,19 @@ export function MessageTimeline({
|
|
|
1289
1318
|
// Boundary progress retires the request owner. A late settlement from
|
|
1290
1319
|
// that completed prefetch cannot mutate the new window's cooldown owner.
|
|
1291
1320
|
olderLoadAttemptRef.current = [olderBoundaryKey, 2];
|
|
1321
|
+
// Bound automatic work, but let continued upward input replenish demand.
|
|
1322
|
+
// Only a committed first-party receipt proves that another page is safe.
|
|
1323
|
+
if (
|
|
1324
|
+
attempt[2]?.committed &&
|
|
1325
|
+
!pinnedRef.current &&
|
|
1326
|
+
hasOlder &&
|
|
1327
|
+
node.scrollTop <= OLDER_PREFETCH_MARGIN_PX &&
|
|
1328
|
+
olderPageBudgetRef.current > 0
|
|
1329
|
+
) {
|
|
1330
|
+
olderPageBudgetRef.current -= 1;
|
|
1331
|
+
olderLoadAttemptRef.current = null;
|
|
1332
|
+
setOlderDemand((value) => value + 1);
|
|
1333
|
+
}
|
|
1292
1334
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
1293
1335
|
requestOlderIfUnderfilled(node);
|
|
1294
1336
|
});
|
|
@@ -1459,6 +1501,7 @@ export function MessageTimeline({
|
|
|
1459
1501
|
loadingOlder,
|
|
1460
1502
|
olderBoundaryKey,
|
|
1461
1503
|
olderPrefetchArmed,
|
|
1504
|
+
olderDemand,
|
|
1462
1505
|
onLoadOlder,
|
|
1463
1506
|
requestOlderIfUnderfilled,
|
|
1464
1507
|
]);
|
|
@@ -1651,7 +1694,7 @@ export function MessageTimeline({
|
|
|
1651
1694
|
// Pointer-dragged scroll-up away from tip. Layout churn never arms this.
|
|
1652
1695
|
if (readerArmed && cumulativeReaderUp > TIP_FOLLOW_READER_UP_EPS_PX && !nearBottomPinned) {
|
|
1653
1696
|
clearReaderIntent();
|
|
1654
|
-
|
|
1697
|
+
requestEarlierFromReader();
|
|
1655
1698
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
1656
1699
|
return;
|
|
1657
1700
|
}
|
|
@@ -1676,6 +1719,10 @@ export function MessageTimeline({
|
|
|
1676
1719
|
}
|
|
1677
1720
|
|
|
1678
1721
|
syncScrollBaseline(node);
|
|
1722
|
+
if (programmatic) {
|
|
1723
|
+
// Anchor restoration is never permission to resume tip-follow.
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1679
1726
|
const nearBottom = isNearBottom(node);
|
|
1680
1727
|
|
|
1681
1728
|
// Re-pin only when the reader moved toward/at the tip without a content
|
|
@@ -1685,8 +1732,7 @@ export function MessageTimeline({
|
|
|
1685
1732
|
// inside PIN_THRESHOLD once the window is tall) and snapped them back.
|
|
1686
1733
|
const inserted = Math.max(0, nextMaxScroll - previousMaxScroll);
|
|
1687
1734
|
const towardTip = nextTop - previousTop - inserted;
|
|
1688
|
-
const nextPinned =
|
|
1689
|
-
!hasNewer && nearBottom && towardTip >= -TIP_FOLLOW_READER_UP_EPS_PX && inserted <= 1;
|
|
1735
|
+
const nextPinned = !hasNewer && nearBottom && towardTip > 0.5 && inserted <= 1;
|
|
1690
1736
|
if (!nextPinned) {
|
|
1691
1737
|
stopFollow();
|
|
1692
1738
|
}
|
|
@@ -1759,148 +1805,202 @@ export function MessageTimeline({
|
|
|
1759
1805
|
onScroll={onScroll}
|
|
1760
1806
|
onScrollEnd={onScrollEnd}
|
|
1761
1807
|
onWheel={onWheel}
|
|
1808
|
+
onTouchStart={(event) => {
|
|
1809
|
+
const touch = event.touches.length === 1 ? event.touches[0] : undefined;
|
|
1810
|
+
touchPositionRef.current = touch
|
|
1811
|
+
? { x: touch.clientX, y: touch.clientY }
|
|
1812
|
+
: null;
|
|
1813
|
+
}}
|
|
1814
|
+
onTouchMove={(event) => {
|
|
1815
|
+
const touch = event.touches[0];
|
|
1816
|
+
const previous = touchPositionRef.current;
|
|
1817
|
+
if (!touch || !previous || event.touches.length !== 1) {
|
|
1818
|
+
touchPositionRef.current = null;
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const deltaX = previous.x - touch.clientX;
|
|
1822
|
+
const deltaY = previous.y - touch.clientY;
|
|
1823
|
+
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
1824
|
+
touchPositionRef.current = { x: touch.clientX, y: touch.clientY };
|
|
1825
|
+
onWheel({
|
|
1826
|
+
deltaX,
|
|
1827
|
+
deltaY,
|
|
1828
|
+
target: event.target,
|
|
1829
|
+
currentTarget: event.currentTarget,
|
|
1830
|
+
});
|
|
1831
|
+
}}
|
|
1832
|
+
onTouchEnd={() => {
|
|
1833
|
+
touchPositionRef.current = null;
|
|
1834
|
+
}}
|
|
1835
|
+
onTouchCancel={() => {
|
|
1836
|
+
touchPositionRef.current = null;
|
|
1837
|
+
}}
|
|
1838
|
+
onClickCapture={(event) => {
|
|
1839
|
+
const target =
|
|
1840
|
+
event.target instanceof Element
|
|
1841
|
+
? event.target.closest("button[aria-expanded]")
|
|
1842
|
+
: null;
|
|
1843
|
+
if (target) {
|
|
1844
|
+
releasePinFromReader();
|
|
1845
|
+
disclosureKeepsUnpinnedRef.current = true;
|
|
1846
|
+
}
|
|
1847
|
+
}}
|
|
1762
1848
|
onPointerDown={onPointerDown}
|
|
1763
1849
|
onKeyDown={onKeyDown}
|
|
1764
1850
|
style={groups.length > 0 && !revealed ? { visibility: "hidden" } : undefined}
|
|
1765
1851
|
className={cn(
|
|
1766
1852
|
// tabIndex=-1 is programmatic only — never paint a focus ring on
|
|
1767
1853
|
// the whole scroller (click + Shift used to flash a blue outline).
|
|
1768
|
-
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4
|
|
1854
|
+
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pt-16 pb-6 sm:px-6 outline-hidden",
|
|
1769
1855
|
autoFollow && pinned && !hasNewer
|
|
1770
1856
|
? "[overflow-anchor:none]"
|
|
1771
1857
|
: "[overflow-anchor:auto]",
|
|
1772
1858
|
)}
|
|
1773
1859
|
>
|
|
1774
|
-
<
|
|
1775
|
-
{
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1860
|
+
<TimelineBeforeLayout
|
|
1861
|
+
capture={() => {
|
|
1862
|
+
readingAnchorRef.current =
|
|
1863
|
+
!pinnedRef.current && scrollRef.current
|
|
1864
|
+
? captureTimelineAnchor(scrollRef.current)
|
|
1865
|
+
: null;
|
|
1866
|
+
}}
|
|
1867
|
+
>
|
|
1868
|
+
<div className="relative mx-auto flex w-full max-w-3xl flex-col gap-5">
|
|
1869
|
+
<AnimatePresence>
|
|
1870
|
+
{loadingOlder ||
|
|
1871
|
+
loadingOldest ||
|
|
1872
|
+
(hasOlder && onJumpToStart && olderPrefetchArmed) ||
|
|
1873
|
+
underfillRetryReady ? (
|
|
1874
|
+
// Reserved top gutter: controls scroll with history and cannot
|
|
1875
|
+
// cover a disclosure. Visibility never changes content height.
|
|
1876
|
+
<motion.div
|
|
1877
|
+
initial={{ opacity: 0, y: -6 }}
|
|
1878
|
+
animate={{ opacity: 1, y: 0 }}
|
|
1879
|
+
exit={{ opacity: 0, y: -6 }}
|
|
1880
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
1881
|
+
data-og-loading-older=""
|
|
1882
|
+
aria-live="polite"
|
|
1883
|
+
className="pointer-events-none absolute inset-x-0 -top-11 z-10 flex justify-center"
|
|
1884
|
+
>
|
|
1885
|
+
{loadingOlder || loadingOldest ? (
|
|
1886
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
1887
|
+
<span className="og-shimmer-text">
|
|
1888
|
+
{loadingOldest
|
|
1889
|
+
? "Jumping to start…"
|
|
1890
|
+
: "Loading earlier activity…"}
|
|
1891
|
+
</span>
|
|
1892
|
+
</span>
|
|
1893
|
+
) : null}
|
|
1894
|
+
{hasOlder &&
|
|
1895
|
+
!loadingOlder &&
|
|
1896
|
+
!loadingOldest &&
|
|
1897
|
+
(underfillRetryReady || onJumpToStart) ? (
|
|
1898
|
+
<button
|
|
1899
|
+
type="button"
|
|
1900
|
+
data-og-retry={underfillRetryReady || undefined}
|
|
1901
|
+
data-og-jump-to-start={!underfillRetryReady || undefined}
|
|
1902
|
+
onClick={() => {
|
|
1903
|
+
const node = scrollRef.current;
|
|
1904
|
+
if (underfillRetryReady) {
|
|
1905
|
+
if (node && underfillRetryReadyRef.current) {
|
|
1906
|
+
// AnimatePresence retains this handler during
|
|
1907
|
+
// exit. Current authorization plus the exact
|
|
1908
|
+
// attempt check prevent stale dispatch.
|
|
1909
|
+
requestOlderIfUnderfilled(node, underfillSettledAttempt!);
|
|
1910
|
+
}
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
applyPinned(false);
|
|
1914
|
+
pendingJumpToStartRef.current = true;
|
|
1915
|
+
const seq = ++jumpToStartSeqRef.current;
|
|
1916
|
+
void Promise.resolve(onJumpToStart!()).then(
|
|
1917
|
+
() => {
|
|
1918
|
+
// The commit that swaps in the oldest window consumes
|
|
1919
|
+
// the flag against the new DOM; this write covers the
|
|
1920
|
+
// already-committed order and the no-window-change
|
|
1921
|
+
// case (jumping within the current window).
|
|
1922
|
+
const scroller = scrollRef.current ?? node;
|
|
1923
|
+
if (scroller) {
|
|
1924
|
+
scroller.scrollTop = 0;
|
|
1925
|
+
}
|
|
1926
|
+
// A host may resolve without ever changing the
|
|
1927
|
+
// window (already on the oldest page). Any swap
|
|
1928
|
+
// commit runs its layout effect before the next
|
|
1929
|
+
// frame, so a flag still armed by then is the
|
|
1930
|
+
// no-change case — clear it, or a LATER prepend
|
|
1931
|
+
// would spuriously jump the reader to the top.
|
|
1932
|
+
requestFrame(() => {
|
|
1933
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
1934
|
+
pendingJumpToStartRef.current = false;
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
},
|
|
1938
|
+
() => {
|
|
1939
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
1940
|
+
pendingJumpToStartRef.current = false;
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
);
|
|
1944
|
+
}}
|
|
1945
|
+
className="pointer-events-auto rounded-full border border-og-border px-3 py-1.5 text-og-control"
|
|
1946
|
+
>
|
|
1947
|
+
{underfillRetryReady ? "Retry earlier activity" : "Jump to start"}
|
|
1948
|
+
</button>
|
|
1949
|
+
) : null}
|
|
1950
|
+
</motion.div>
|
|
1951
|
+
) : null}
|
|
1952
|
+
</AnimatePresence>
|
|
1953
|
+
{!groups.length
|
|
1954
|
+
? (emptyState ?? (
|
|
1955
|
+
<p className="py-10 text-center text-og-menu text-og-fg-subtle">
|
|
1956
|
+
No activity yet.
|
|
1957
|
+
</p>
|
|
1958
|
+
))
|
|
1959
|
+
: null}
|
|
1960
|
+
{hasOlder && olderPrefetchArmed ? (
|
|
1961
|
+
// Overlaid, not a layout row: mounting/unmounting the sentinel
|
|
1962
|
+
// must never shift content (that shift was itself a wobble).
|
|
1963
|
+
// End at the scroll origin above the pt-16 gutter so the
|
|
1964
|
+
// observer and scrollTop cooldown share the same 400px band.
|
|
1965
|
+
<div
|
|
1966
|
+
ref={topSentinelRef}
|
|
1967
|
+
data-og-top-sentinel=""
|
|
1968
|
+
data-og-timeline-chrome=""
|
|
1969
|
+
aria-hidden="true"
|
|
1970
|
+
className="pointer-events-none absolute inset-x-0 -top-16 h-px -translate-y-full"
|
|
1805
1971
|
/>
|
|
1806
|
-
);
|
|
1807
|
-
})}
|
|
1808
|
-
{groups.length > 0 && trailingState ? (
|
|
1809
|
-
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
1810
|
-
) : null}
|
|
1811
|
-
{hasNewer ? (
|
|
1812
|
-
<div
|
|
1813
|
-
ref={bottomSentinelRef}
|
|
1814
|
-
data-og-bottom-sentinel=""
|
|
1815
|
-
data-og-timeline-chrome=""
|
|
1816
|
-
aria-hidden="true"
|
|
1817
|
-
className="h-px w-full"
|
|
1818
|
-
/>
|
|
1819
|
-
) : null}
|
|
1820
|
-
</div>
|
|
1821
|
-
</div>
|
|
1822
|
-
<AnimatePresence>
|
|
1823
|
-
{loadingOlder ||
|
|
1824
|
-
loadingOldest ||
|
|
1825
|
-
(hasOlder && onJumpToStart && olderPrefetchArmed) ||
|
|
1826
|
-
underfillRetryReady ? (
|
|
1827
|
-
// Floating over the scroller (not a timeline row) so showing and
|
|
1828
|
-
// hiding it never reflows history under the reader.
|
|
1829
|
-
<motion.div
|
|
1830
|
-
initial={{ opacity: 0, y: -6 }}
|
|
1831
|
-
animate={{ opacity: 1, y: 0 }}
|
|
1832
|
-
exit={{ opacity: 0, y: -6 }}
|
|
1833
|
-
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
1834
|
-
data-og-loading-older=""
|
|
1835
|
-
aria-live="polite"
|
|
1836
|
-
className="absolute inset-x-0 top-3 z-10 flex justify-center"
|
|
1837
|
-
>
|
|
1838
|
-
{loadingOlder || loadingOldest ? (
|
|
1839
|
-
<span className={LOADING_CHIP_CLASS}>
|
|
1840
|
-
<span className="og-shimmer-text">
|
|
1841
|
-
{loadingOldest ? "Jumping to start…" : "Loading earlier activity…"}
|
|
1842
|
-
</span>
|
|
1843
|
-
</span>
|
|
1844
1972
|
) : null}
|
|
1845
|
-
{
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
if (underfillRetryReady) {
|
|
1856
|
-
if (node && underfillRetryReadyRef.current) {
|
|
1857
|
-
// AnimatePresence retains this handler during
|
|
1858
|
-
// exit. Current authorization plus the exact
|
|
1859
|
-
// attempt check prevent stale dispatch.
|
|
1860
|
-
requestOlderIfUnderfilled(node, underfillSettledAttempt!);
|
|
1861
|
-
}
|
|
1862
|
-
return;
|
|
1973
|
+
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
1974
|
+
return (
|
|
1975
|
+
<TimelineGroupEntry
|
|
1976
|
+
key={key}
|
|
1977
|
+
groupKey={key}
|
|
1978
|
+
group={group}
|
|
1979
|
+
nextGroup={groups[index + 1]?.group}
|
|
1980
|
+
entranceEnabled={entranceEnabled}
|
|
1981
|
+
liveEntranceEnabled={
|
|
1982
|
+
group.kind === "activity" ? !bulkRender : undefined
|
|
1863
1983
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
// the flag against the new DOM; this write covers the
|
|
1871
|
-
// already-committed order and the no-window-change
|
|
1872
|
-
// case (jumping within the current window).
|
|
1873
|
-
const scroller = scrollRef.current ?? node;
|
|
1874
|
-
if (scroller) {
|
|
1875
|
-
scroller.scrollTop = 0;
|
|
1876
|
-
}
|
|
1877
|
-
// A host may resolve without ever changing the
|
|
1878
|
-
// window (already on the oldest page). Any swap
|
|
1879
|
-
// commit runs its layout effect before the next
|
|
1880
|
-
// frame, so a flag still armed by then is the
|
|
1881
|
-
// no-change case — clear it, or a LATER prepend
|
|
1882
|
-
// would spuriously jump the reader to the top.
|
|
1883
|
-
requestFrame(() => {
|
|
1884
|
-
if (jumpToStartSeqRef.current === seq) {
|
|
1885
|
-
pendingJumpToStartRef.current = false;
|
|
1886
|
-
}
|
|
1887
|
-
});
|
|
1888
|
-
},
|
|
1889
|
-
() => {
|
|
1890
|
-
if (jumpToStartSeqRef.current === seq) {
|
|
1891
|
-
pendingJumpToStartRef.current = false;
|
|
1892
|
-
}
|
|
1893
|
-
},
|
|
1894
|
-
);
|
|
1895
|
-
}}
|
|
1896
|
-
className="rounded-full border border-og-border px-3 py-1.5 text-og-control"
|
|
1897
|
-
>
|
|
1898
|
-
{underfillRetryReady ? "Retry earlier activity" : "Jump to start"}
|
|
1899
|
-
</button>
|
|
1984
|
+
context={timelineGroupEntryContext}
|
|
1985
|
+
/>
|
|
1986
|
+
);
|
|
1987
|
+
})}
|
|
1988
|
+
{groups.length > 0 && trailingState ? (
|
|
1989
|
+
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
1900
1990
|
) : null}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1991
|
+
{hasNewer ? (
|
|
1992
|
+
<div
|
|
1993
|
+
ref={bottomSentinelRef}
|
|
1994
|
+
data-og-bottom-sentinel=""
|
|
1995
|
+
data-og-timeline-chrome=""
|
|
1996
|
+
aria-hidden="true"
|
|
1997
|
+
className="h-px w-full"
|
|
1998
|
+
/>
|
|
1999
|
+
) : null}
|
|
2000
|
+
</div>
|
|
2001
|
+
</TimelineBeforeLayout>
|
|
2002
|
+
</div>
|
|
2003
|
+
|
|
1904
2004
|
<AnimatePresence>
|
|
1905
2005
|
{loadingNewer ? (
|
|
1906
2006
|
<motion.div
|
|
@@ -2748,7 +2848,11 @@ export function TimelineRow({
|
|
|
2748
2848
|
return <GoalRow item={item} />;
|
|
2749
2849
|
case "machine-input-batch":
|
|
2750
2850
|
return (
|
|
2751
|
-
<MachineInputBatchRow
|
|
2851
|
+
<MachineInputBatchRow
|
|
2852
|
+
item={item}
|
|
2853
|
+
onOpenSession={onOpenSession}
|
|
2854
|
+
loadVideoArtifactPlayback={loadVideoArtifactPlayback}
|
|
2855
|
+
/>
|
|
2752
2856
|
);
|
|
2753
2857
|
case "notice":
|
|
2754
2858
|
return <NoticeRow item={item} />;
|
|
@@ -2805,7 +2909,7 @@ function CompactionRow({ item }: { item: ContextCompactionItem }) {
|
|
|
2805
2909
|
? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
|
|
2806
2910
|
: item.phase === "started"
|
|
2807
2911
|
? WAITING_PILL_CLASS
|
|
2808
|
-
:
|
|
2912
|
+
: NEUTRAL_PILL;
|
|
2809
2913
|
return (
|
|
2810
2914
|
<div className={cn(enter && "animate-og-enter", "flex justify-center")}>
|
|
2811
2915
|
<div
|
|
@@ -3301,41 +3405,46 @@ type GoalMeta = { label: string; pill: string; icon: ComponentType<{ className?:
|
|
|
3301
3405
|
|
|
3302
3406
|
const NEUTRAL_PILL = "border-og-border bg-og-surface-1 text-og-fg-muted";
|
|
3303
3407
|
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
label: "Goal
|
|
3325
|
-
pill:
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3408
|
+
// Shared production chunks can be cyclic. Resolve icon bindings during render,
|
|
3409
|
+
// after their modules initialize, rather than permanently capturing undefined.
|
|
3410
|
+
function goalMeta(action: GoalItem["action"]): GoalMeta {
|
|
3411
|
+
const metadata: Record<GoalItem["action"], GoalMeta> = {
|
|
3412
|
+
set: {
|
|
3413
|
+
label: "Goal set",
|
|
3414
|
+
pill: "border-og-accent/30 bg-og-accent/10 text-og-accent",
|
|
3415
|
+
icon: TargetIcon,
|
|
3416
|
+
},
|
|
3417
|
+
updated: { label: "Goal updated", pill: NEUTRAL_PILL, icon: PencilLineIcon },
|
|
3418
|
+
completed: {
|
|
3419
|
+
label: "Goal completed",
|
|
3420
|
+
pill: "border-og-status-idle/30 bg-og-status-idle/10 text-og-status-idle",
|
|
3421
|
+
icon: CheckIcon,
|
|
3422
|
+
},
|
|
3423
|
+
paused: {
|
|
3424
|
+
label: "Goal paused",
|
|
3425
|
+
pill: WAITING_PILL_CLASS,
|
|
3426
|
+
icon: PauseIcon,
|
|
3427
|
+
},
|
|
3428
|
+
resumed: { label: "Goal resumed", pill: NEUTRAL_PILL, icon: PlayIcon },
|
|
3429
|
+
cleared: { label: "Goal cleared", pill: NEUTRAL_PILL, icon: Trash2Icon },
|
|
3430
|
+
held: {
|
|
3431
|
+
label: "Goal held",
|
|
3432
|
+
pill: WAITING_PILL_CLASS,
|
|
3433
|
+
icon: PauseCircleIcon,
|
|
3434
|
+
},
|
|
3435
|
+
continuation: { label: "Continuing toward the goal", pill: NEUTRAL_PILL, icon: ArrowRightIcon },
|
|
3436
|
+
};
|
|
3437
|
+
return metadata[action];
|
|
3438
|
+
}
|
|
3330
3439
|
|
|
3331
3440
|
/**
|
|
3332
3441
|
* A goal landmark pill. Resolves its label, accent/tone, and glyph from
|
|
3333
|
-
* {@link
|
|
3442
|
+
* {@link goalMeta} so all six actions are visually distinguishable while the
|
|
3334
3443
|
* palette stays restrained — see that table for the per-action rationale.
|
|
3335
3444
|
*/
|
|
3336
3445
|
function GoalRow({ item }: { item: GoalItem }) {
|
|
3337
3446
|
const enter = useEntranceAnimation();
|
|
3338
|
-
const { label, pill, icon: Icon } =
|
|
3447
|
+
const { label, pill, icon: Icon } = goalMeta(item.action);
|
|
3339
3448
|
return (
|
|
3340
3449
|
<div className={cn(enter && "animate-og-enter", "flex justify-center")}>
|
|
3341
3450
|
<span
|
|
@@ -3356,9 +3465,11 @@ function GoalRow({ item }: { item: GoalItem }) {
|
|
|
3356
3465
|
|
|
3357
3466
|
function MachineInputBatchRow({
|
|
3358
3467
|
item,
|
|
3468
|
+
onOpenSession,
|
|
3359
3469
|
loadVideoArtifactPlayback,
|
|
3360
3470
|
}: {
|
|
3361
3471
|
item: MachineInputBatchItem;
|
|
3472
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
3362
3473
|
loadVideoArtifactPlayback?: VideoArtifactPlaybackLoader | undefined;
|
|
3363
3474
|
}) {
|
|
3364
3475
|
const enter = useEntranceAnimation();
|
|
@@ -3378,7 +3489,11 @@ function MachineInputBatchRow({
|
|
|
3378
3489
|
|
|
3379
3490
|
return (
|
|
3380
3491
|
<div className={cn(enter && "animate-og-enter", "flex flex-col items-center gap-1.5")}>
|
|
3381
|
-
<details
|
|
3492
|
+
<details
|
|
3493
|
+
className="group w-full max-w-full"
|
|
3494
|
+
data-og-machine-input-batch=""
|
|
3495
|
+
title={`Received ${formatClockTime(item.occurredAt)}`}
|
|
3496
|
+
>
|
|
3382
3497
|
<summary className="flex cursor-pointer list-none justify-center [&::-webkit-details-marker]:hidden">
|
|
3383
3498
|
<span
|
|
3384
3499
|
className={cn(
|
|
@@ -3394,10 +3509,14 @@ function MachineInputBatchRow({
|
|
|
3394
3509
|
</span>
|
|
3395
3510
|
</summary>
|
|
3396
3511
|
<div className="mx-auto mt-2 w-full max-w-lg space-y-2 border-t border-og-border/50 pt-2">
|
|
3512
|
+
<p className="text-og-xs text-og-fg-subtle">
|
|
3513
|
+
Received <time dateTime={item.occurredAt}>{formatClockTime(item.occurredAt)}</time>
|
|
3514
|
+
</p>
|
|
3397
3515
|
{item.members.map((member) => (
|
|
3398
3516
|
<MachineInputRow
|
|
3399
3517
|
key={member.id}
|
|
3400
3518
|
member={member}
|
|
3519
|
+
onOpenSession={onOpenSession}
|
|
3401
3520
|
loadVideoArtifactPlayback={loadVideoArtifactPlayback}
|
|
3402
3521
|
/>
|
|
3403
3522
|
))}
|
|
@@ -3414,9 +3533,11 @@ function MachineInputBatchRow({
|
|
|
3414
3533
|
|
|
3415
3534
|
function MachineInputRow({
|
|
3416
3535
|
member,
|
|
3536
|
+
onOpenSession,
|
|
3417
3537
|
loadVideoArtifactPlayback,
|
|
3418
3538
|
}: {
|
|
3419
3539
|
member: MachineInputBatchItem["members"][number];
|
|
3540
|
+
onOpenSession?: ((sessionId: string) => void) | undefined;
|
|
3420
3541
|
loadVideoArtifactPlayback?: VideoArtifactPlaybackLoader | undefined;
|
|
3421
3542
|
}) {
|
|
3422
3543
|
if (member.kind === "media_generation_result" && member.result) {
|
|
@@ -3443,6 +3564,11 @@ function MachineInputRow({
|
|
|
3443
3564
|
{truncate(summary, 320)}
|
|
3444
3565
|
</p>
|
|
3445
3566
|
) : null}
|
|
3567
|
+
<ChildSessionLink
|
|
3568
|
+
kind={member.kind}
|
|
3569
|
+
sourceId={member.sourceId}
|
|
3570
|
+
onOpenSession={onOpenSession}
|
|
3571
|
+
/>
|
|
3446
3572
|
</div>
|
|
3447
3573
|
</div>
|
|
3448
3574
|
);
|
|
@@ -3521,7 +3647,7 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
3521
3647
|
? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
|
|
3522
3648
|
: item.tone === "waiting"
|
|
3523
3649
|
? WAITING_PILL_CLASS
|
|
3524
|
-
:
|
|
3650
|
+
: NEUTRAL_PILL;
|
|
3525
3651
|
return (
|
|
3526
3652
|
<div
|
|
3527
3653
|
className={cn(
|
|
@@ -3529,12 +3655,24 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
3529
3655
|
"flex items-start gap-2.5 rounded-og-md border px-3.5 py-2.5 text-og-menu",
|
|
3530
3656
|
tone,
|
|
3531
3657
|
)}
|
|
3532
|
-
role="status"
|
|
3658
|
+
role={item.recordedOutcome ? "note" : "status"}
|
|
3659
|
+
data-og-recorded-outcome={item.recordedOutcome ? "wait" : undefined}
|
|
3533
3660
|
>
|
|
3534
3661
|
<TriangleAlertIcon
|
|
3535
3662
|
className={cn("mt-0.5 size-4 shrink-0", item.tone === "cancelled" && "opacity-60")}
|
|
3536
3663
|
/>
|
|
3537
3664
|
<div className="min-w-0 flex-1">
|
|
3665
|
+
{item.recordedOutcome ? (
|
|
3666
|
+
<p className="mb-1 text-og-control font-medium">
|
|
3667
|
+
Wait recorded{" "}
|
|
3668
|
+
<time dateTime={item.occurredAt}>
|
|
3669
|
+
{new Date(item.occurredAt).toLocaleString(undefined, {
|
|
3670
|
+
dateStyle: "medium",
|
|
3671
|
+
timeStyle: "short",
|
|
3672
|
+
})}
|
|
3673
|
+
</time>
|
|
3674
|
+
</p>
|
|
3675
|
+
) : null}
|
|
3538
3676
|
<span className="whitespace-pre-wrap break-words">{item.text}</span>
|
|
3539
3677
|
{item.details ? (
|
|
3540
3678
|
<details className="mt-2 text-og-control">
|