@opengeni/react 3.7.0-canary.0 → 3.7.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.
- 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-DOK4KZF2.js → chunk-5IXTJTK3.js} +1322 -392
- package/dist/chunk-5IXTJTK3.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-R55HFTT3.js → chunk-INGB2N5R.js} +4305 -4019
- package/dist/chunk-INGB2N5R.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-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/session-chrome.d.ts +37 -4
- 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/machine-input-display.ts +6 -0
- package/src/components/message-timeline.tsx +320 -197
- package/src/components/session-chrome.tsx +498 -125
- 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 +495 -411
- 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
|
@@ -49,6 +49,12 @@ import {
|
|
|
49
49
|
type OlderHistoryLoader,
|
|
50
50
|
} from "../older-history";
|
|
51
51
|
import { Markdown } from "./markdown";
|
|
52
|
+
import {
|
|
53
|
+
TimelineBeforeLayout,
|
|
54
|
+
captureTimelineAnchor,
|
|
55
|
+
timelineAnchorCorrection,
|
|
56
|
+
type TimelineAnchor,
|
|
57
|
+
} from "./timeline-anchor";
|
|
52
58
|
import {
|
|
53
59
|
UserMessageBody,
|
|
54
60
|
UserMessageDisclosureProvider,
|
|
@@ -418,8 +424,15 @@ export function MessageTimeline({
|
|
|
418
424
|
(item) => item.kind !== "auth-needed" || shouldRenderAuthNeeded(item),
|
|
419
425
|
);
|
|
420
426
|
}, [items, events, shouldRenderAuthNeeded]);
|
|
421
|
-
|
|
427
|
+
// Event-window identity is independent of projected rows (partial messages
|
|
428
|
+
// can acquire a different first-delta id when older text arrives).
|
|
429
|
+
const sourceItems = events ?? items;
|
|
422
430
|
const olderBoundaryKey = sourceItems?.[0]?.id;
|
|
431
|
+
const previousSourceIdsRef = useRef(new Set<string>());
|
|
432
|
+
const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
|
|
433
|
+
const readingAnchorRef = useRef<TimelineAnchor | null>(null);
|
|
434
|
+
const olderPageBudgetRef = useRef(0);
|
|
435
|
+
const [olderDemand, setOlderDemand] = useState(0);
|
|
423
436
|
const allGroups = useMemo(() => groupTimeline(resolvedItems), [resolvedItems]);
|
|
424
437
|
const annotationSources = useMemo(() => {
|
|
425
438
|
const sources = new Map<string, TimelineAnnotationSourceDescriptor>();
|
|
@@ -648,29 +661,22 @@ export function MessageTimeline({
|
|
|
648
661
|
}, []);
|
|
649
662
|
|
|
650
663
|
/** 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
|
-
);
|
|
664
|
+
const releasePinFromReader = useCallback(() => {
|
|
665
|
+
if (!autoFollow || !pinnedRef.current || hasNewerRef.current) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
clearReaderIntent();
|
|
669
|
+
clearPendingReaderLeave();
|
|
670
|
+
stopFollow();
|
|
671
|
+
applyPinned(false);
|
|
672
|
+
if (wantPinRef.current) {
|
|
673
|
+
wantPinRef.current = false;
|
|
674
|
+
}
|
|
675
|
+
if (!olderPrefetchArmedRef.current) {
|
|
676
|
+
olderPrefetchArmedRef.current = true;
|
|
677
|
+
setOlderPrefetchArmed(true);
|
|
678
|
+
}
|
|
679
|
+
}, [autoFollow, applyPinned, clearPendingReaderLeave, clearReaderIntent, stopFollow]);
|
|
674
680
|
|
|
675
681
|
/**
|
|
676
682
|
* Settled away from the tip while the camera is idle — Vimium / unfocused
|
|
@@ -691,7 +697,7 @@ export function MessageTimeline({
|
|
|
691
697
|
clearPendingReaderLeave();
|
|
692
698
|
return;
|
|
693
699
|
}
|
|
694
|
-
releasePinFromReader(
|
|
700
|
+
releasePinFromReader();
|
|
695
701
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
696
702
|
},
|
|
697
703
|
[autoFollow, clearPendingReaderLeave, rearmOlderPrefetchAfterLeavingTop, releasePinFromReader],
|
|
@@ -712,6 +718,19 @@ export function MessageTimeline({
|
|
|
712
718
|
});
|
|
713
719
|
}, [cancelLeaveFallback, releasePinAfterScrollSettled]);
|
|
714
720
|
|
|
721
|
+
const requestEarlierFromReader = () => {
|
|
722
|
+
releasePinFromReader();
|
|
723
|
+
wantPinRef.current = false;
|
|
724
|
+
// A stationary upward gesture is still demand. Successful short/folded
|
|
725
|
+
// pages may never create enough range to leave the prefetch band.
|
|
726
|
+
olderPageBudgetRef.current = 8;
|
|
727
|
+
if (olderLoadAttemptRef.current?.[1] === 2) {
|
|
728
|
+
olderLoadAttemptRef.current = null;
|
|
729
|
+
setOlderDemand((value) => value + 1);
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
const touchPositionRef = useRef<{ x: number; y: number } | null>(null);
|
|
733
|
+
|
|
715
734
|
const onWheel = (event: {
|
|
716
735
|
deltaY: number;
|
|
717
736
|
deltaX: number;
|
|
@@ -729,12 +748,11 @@ export function MessageTimeline({
|
|
|
729
748
|
return;
|
|
730
749
|
}
|
|
731
750
|
disclosureKeepsUnpinnedRef.current = false;
|
|
751
|
+
programmaticScrollRef.current = 0;
|
|
732
752
|
if (event.deltaY >= 0) {
|
|
733
753
|
return;
|
|
734
754
|
}
|
|
735
|
-
|
|
736
|
-
event.currentTarget instanceof HTMLElement ? event.currentTarget : scrollRef.current;
|
|
737
|
-
releasePinFromReader(node);
|
|
755
|
+
requestEarlierFromReader();
|
|
738
756
|
};
|
|
739
757
|
|
|
740
758
|
/** Touch / stylus / mouse drag on the scroller — explicit leave (not layout). */
|
|
@@ -779,9 +797,8 @@ export function MessageTimeline({
|
|
|
779
797
|
if (event.key !== "ArrowUp" && event.key !== "PageUp" && event.key !== "Home") {
|
|
780
798
|
return;
|
|
781
799
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
releasePinFromReader(node);
|
|
800
|
+
programmaticScrollRef.current = 0;
|
|
801
|
+
requestEarlierFromReader();
|
|
785
802
|
};
|
|
786
803
|
|
|
787
804
|
const snapToBottom = useCallback(
|
|
@@ -1103,15 +1120,26 @@ export function MessageTimeline({
|
|
|
1103
1120
|
previousMaxScroll <= 1 ||
|
|
1104
1121
|
previousMaxScroll - previousScrollTop < Math.min(PIN_THRESHOLD_PX, previousMaxScroll);
|
|
1105
1122
|
const firstItemChanged = !!previousFirstItemId && firstItemId !== previousFirstItemId;
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1123
|
+
const sourceChanged = olderBoundaryKey !== previousSourceBoundaryRef.current;
|
|
1124
|
+
const retainedSource =
|
|
1125
|
+
sourceItems?.some((item) => previousSourceIdsRef.current.has(item.id)) ?? false;
|
|
1126
|
+
const prepended = sourceChanged && retainedSource;
|
|
1127
|
+
previousSourceBoundaryRef.current = olderBoundaryKey;
|
|
1128
|
+
previousSourceIdsRef.current = new Set(sourceItems?.map((item) => item.id));
|
|
1129
|
+
const readingAnchor = readingAnchorRef.current;
|
|
1130
|
+
readingAnchorRef.current = null;
|
|
1108
1131
|
const attempt = olderLoadAttemptRef.current;
|
|
1109
1132
|
const committedZeroOverlapOlderReplacement = !!(
|
|
1110
1133
|
attempt?.[2]?.committed &&
|
|
1111
|
-
|
|
1112
|
-
!
|
|
1134
|
+
sourceChanged &&
|
|
1135
|
+
!retainedSource
|
|
1113
1136
|
);
|
|
1114
1137
|
const restorePrependAnchor = () => {
|
|
1138
|
+
const correction = readingAnchor && timelineAnchorCorrection(node, readingAnchor);
|
|
1139
|
+
if (correction != null) {
|
|
1140
|
+
if (Math.abs(correction) > 1) writeScrollTop(node, node.scrollTop + correction);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1115
1143
|
// Keep the reader on the same retained rows. Prefer the exact first-item
|
|
1116
1144
|
// content coordinate (needed when a prepend merges inside one group),
|
|
1117
1145
|
// then the retained group offset, then scrollHeight as a final fallback.
|
|
@@ -1289,6 +1317,19 @@ export function MessageTimeline({
|
|
|
1289
1317
|
// Boundary progress retires the request owner. A late settlement from
|
|
1290
1318
|
// that completed prefetch cannot mutate the new window's cooldown owner.
|
|
1291
1319
|
olderLoadAttemptRef.current = [olderBoundaryKey, 2];
|
|
1320
|
+
// Bound automatic work, but let continued upward input replenish demand.
|
|
1321
|
+
// Only a committed first-party receipt proves that another page is safe.
|
|
1322
|
+
if (
|
|
1323
|
+
attempt[2]?.committed &&
|
|
1324
|
+
!pinnedRef.current &&
|
|
1325
|
+
hasOlder &&
|
|
1326
|
+
node.scrollTop <= OLDER_PREFETCH_MARGIN_PX &&
|
|
1327
|
+
olderPageBudgetRef.current > 0
|
|
1328
|
+
) {
|
|
1329
|
+
olderPageBudgetRef.current -= 1;
|
|
1330
|
+
olderLoadAttemptRef.current = null;
|
|
1331
|
+
setOlderDemand((value) => value + 1);
|
|
1332
|
+
}
|
|
1292
1333
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
1293
1334
|
requestOlderIfUnderfilled(node);
|
|
1294
1335
|
});
|
|
@@ -1459,6 +1500,7 @@ export function MessageTimeline({
|
|
|
1459
1500
|
loadingOlder,
|
|
1460
1501
|
olderBoundaryKey,
|
|
1461
1502
|
olderPrefetchArmed,
|
|
1503
|
+
olderDemand,
|
|
1462
1504
|
onLoadOlder,
|
|
1463
1505
|
requestOlderIfUnderfilled,
|
|
1464
1506
|
]);
|
|
@@ -1651,7 +1693,7 @@ export function MessageTimeline({
|
|
|
1651
1693
|
// Pointer-dragged scroll-up away from tip. Layout churn never arms this.
|
|
1652
1694
|
if (readerArmed && cumulativeReaderUp > TIP_FOLLOW_READER_UP_EPS_PX && !nearBottomPinned) {
|
|
1653
1695
|
clearReaderIntent();
|
|
1654
|
-
|
|
1696
|
+
requestEarlierFromReader();
|
|
1655
1697
|
rearmOlderPrefetchAfterLeavingTop(node);
|
|
1656
1698
|
return;
|
|
1657
1699
|
}
|
|
@@ -1676,6 +1718,10 @@ export function MessageTimeline({
|
|
|
1676
1718
|
}
|
|
1677
1719
|
|
|
1678
1720
|
syncScrollBaseline(node);
|
|
1721
|
+
if (programmatic) {
|
|
1722
|
+
// Anchor restoration is never permission to resume tip-follow.
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1679
1725
|
const nearBottom = isNearBottom(node);
|
|
1680
1726
|
|
|
1681
1727
|
// Re-pin only when the reader moved toward/at the tip without a content
|
|
@@ -1685,8 +1731,7 @@ export function MessageTimeline({
|
|
|
1685
1731
|
// inside PIN_THRESHOLD once the window is tall) and snapped them back.
|
|
1686
1732
|
const inserted = Math.max(0, nextMaxScroll - previousMaxScroll);
|
|
1687
1733
|
const towardTip = nextTop - previousTop - inserted;
|
|
1688
|
-
const nextPinned =
|
|
1689
|
-
!hasNewer && nearBottom && towardTip >= -TIP_FOLLOW_READER_UP_EPS_PX && inserted <= 1;
|
|
1734
|
+
const nextPinned = !hasNewer && nearBottom && towardTip > 0.5 && inserted <= 1;
|
|
1690
1735
|
if (!nextPinned) {
|
|
1691
1736
|
stopFollow();
|
|
1692
1737
|
}
|
|
@@ -1759,148 +1804,202 @@ export function MessageTimeline({
|
|
|
1759
1804
|
onScroll={onScroll}
|
|
1760
1805
|
onScrollEnd={onScrollEnd}
|
|
1761
1806
|
onWheel={onWheel}
|
|
1807
|
+
onTouchStart={(event) => {
|
|
1808
|
+
const touch = event.touches.length === 1 ? event.touches[0] : undefined;
|
|
1809
|
+
touchPositionRef.current = touch
|
|
1810
|
+
? { x: touch.clientX, y: touch.clientY }
|
|
1811
|
+
: null;
|
|
1812
|
+
}}
|
|
1813
|
+
onTouchMove={(event) => {
|
|
1814
|
+
const touch = event.touches[0];
|
|
1815
|
+
const previous = touchPositionRef.current;
|
|
1816
|
+
if (!touch || !previous || event.touches.length !== 1) {
|
|
1817
|
+
touchPositionRef.current = null;
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
const deltaX = previous.x - touch.clientX;
|
|
1821
|
+
const deltaY = previous.y - touch.clientY;
|
|
1822
|
+
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
1823
|
+
touchPositionRef.current = { x: touch.clientX, y: touch.clientY };
|
|
1824
|
+
onWheel({
|
|
1825
|
+
deltaX,
|
|
1826
|
+
deltaY,
|
|
1827
|
+
target: event.target,
|
|
1828
|
+
currentTarget: event.currentTarget,
|
|
1829
|
+
});
|
|
1830
|
+
}}
|
|
1831
|
+
onTouchEnd={() => {
|
|
1832
|
+
touchPositionRef.current = null;
|
|
1833
|
+
}}
|
|
1834
|
+
onTouchCancel={() => {
|
|
1835
|
+
touchPositionRef.current = null;
|
|
1836
|
+
}}
|
|
1837
|
+
onClickCapture={(event) => {
|
|
1838
|
+
const target =
|
|
1839
|
+
event.target instanceof Element
|
|
1840
|
+
? event.target.closest("button[aria-expanded]")
|
|
1841
|
+
: null;
|
|
1842
|
+
if (target) {
|
|
1843
|
+
releasePinFromReader();
|
|
1844
|
+
disclosureKeepsUnpinnedRef.current = true;
|
|
1845
|
+
}
|
|
1846
|
+
}}
|
|
1762
1847
|
onPointerDown={onPointerDown}
|
|
1763
1848
|
onKeyDown={onKeyDown}
|
|
1764
1849
|
style={groups.length > 0 && !revealed ? { visibility: "hidden" } : undefined}
|
|
1765
1850
|
className={cn(
|
|
1766
1851
|
// tabIndex=-1 is programmatic only — never paint a focus ring on
|
|
1767
1852
|
// the whole scroller (click + Shift used to flash a blue outline).
|
|
1768
|
-
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4
|
|
1853
|
+
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pt-16 pb-6 sm:px-6 outline-hidden",
|
|
1769
1854
|
autoFollow && pinned && !hasNewer
|
|
1770
1855
|
? "[overflow-anchor:none]"
|
|
1771
1856
|
: "[overflow-anchor:auto]",
|
|
1772
1857
|
)}
|
|
1773
1858
|
>
|
|
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
|
-
|
|
1859
|
+
<TimelineBeforeLayout
|
|
1860
|
+
capture={() => {
|
|
1861
|
+
readingAnchorRef.current =
|
|
1862
|
+
!pinnedRef.current && scrollRef.current
|
|
1863
|
+
? captureTimelineAnchor(scrollRef.current)
|
|
1864
|
+
: null;
|
|
1865
|
+
}}
|
|
1866
|
+
>
|
|
1867
|
+
<div className="relative mx-auto flex w-full max-w-3xl flex-col gap-5">
|
|
1868
|
+
<AnimatePresence>
|
|
1869
|
+
{loadingOlder ||
|
|
1870
|
+
loadingOldest ||
|
|
1871
|
+
(hasOlder && onJumpToStart && olderPrefetchArmed) ||
|
|
1872
|
+
underfillRetryReady ? (
|
|
1873
|
+
// Reserved top gutter: controls scroll with history and cannot
|
|
1874
|
+
// cover a disclosure. Visibility never changes content height.
|
|
1875
|
+
<motion.div
|
|
1876
|
+
initial={{ opacity: 0, y: -6 }}
|
|
1877
|
+
animate={{ opacity: 1, y: 0 }}
|
|
1878
|
+
exit={{ opacity: 0, y: -6 }}
|
|
1879
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
1880
|
+
data-og-loading-older=""
|
|
1881
|
+
aria-live="polite"
|
|
1882
|
+
className="pointer-events-none absolute inset-x-0 -top-11 z-10 flex justify-center"
|
|
1883
|
+
>
|
|
1884
|
+
{loadingOlder || loadingOldest ? (
|
|
1885
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
1886
|
+
<span className="og-shimmer-text">
|
|
1887
|
+
{loadingOldest
|
|
1888
|
+
? "Jumping to start…"
|
|
1889
|
+
: "Loading earlier activity…"}
|
|
1890
|
+
</span>
|
|
1891
|
+
</span>
|
|
1892
|
+
) : null}
|
|
1893
|
+
{hasOlder &&
|
|
1894
|
+
!loadingOlder &&
|
|
1895
|
+
!loadingOldest &&
|
|
1896
|
+
(underfillRetryReady || onJumpToStart) ? (
|
|
1897
|
+
<button
|
|
1898
|
+
type="button"
|
|
1899
|
+
data-og-retry={underfillRetryReady || undefined}
|
|
1900
|
+
data-og-jump-to-start={!underfillRetryReady || undefined}
|
|
1901
|
+
onClick={() => {
|
|
1902
|
+
const node = scrollRef.current;
|
|
1903
|
+
if (underfillRetryReady) {
|
|
1904
|
+
if (node && underfillRetryReadyRef.current) {
|
|
1905
|
+
// AnimatePresence retains this handler during
|
|
1906
|
+
// exit. Current authorization plus the exact
|
|
1907
|
+
// attempt check prevent stale dispatch.
|
|
1908
|
+
requestOlderIfUnderfilled(node, underfillSettledAttempt!);
|
|
1909
|
+
}
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
applyPinned(false);
|
|
1913
|
+
pendingJumpToStartRef.current = true;
|
|
1914
|
+
const seq = ++jumpToStartSeqRef.current;
|
|
1915
|
+
void Promise.resolve(onJumpToStart!()).then(
|
|
1916
|
+
() => {
|
|
1917
|
+
// The commit that swaps in the oldest window consumes
|
|
1918
|
+
// the flag against the new DOM; this write covers the
|
|
1919
|
+
// already-committed order and the no-window-change
|
|
1920
|
+
// case (jumping within the current window).
|
|
1921
|
+
const scroller = scrollRef.current ?? node;
|
|
1922
|
+
if (scroller) {
|
|
1923
|
+
scroller.scrollTop = 0;
|
|
1924
|
+
}
|
|
1925
|
+
// A host may resolve without ever changing the
|
|
1926
|
+
// window (already on the oldest page). Any swap
|
|
1927
|
+
// commit runs its layout effect before the next
|
|
1928
|
+
// frame, so a flag still armed by then is the
|
|
1929
|
+
// no-change case — clear it, or a LATER prepend
|
|
1930
|
+
// would spuriously jump the reader to the top.
|
|
1931
|
+
requestFrame(() => {
|
|
1932
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
1933
|
+
pendingJumpToStartRef.current = false;
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
},
|
|
1937
|
+
() => {
|
|
1938
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
1939
|
+
pendingJumpToStartRef.current = false;
|
|
1940
|
+
}
|
|
1941
|
+
},
|
|
1942
|
+
);
|
|
1943
|
+
}}
|
|
1944
|
+
className="pointer-events-auto rounded-full border border-og-border px-3 py-1.5 text-og-control"
|
|
1945
|
+
>
|
|
1946
|
+
{underfillRetryReady ? "Retry earlier activity" : "Jump to start"}
|
|
1947
|
+
</button>
|
|
1948
|
+
) : null}
|
|
1949
|
+
</motion.div>
|
|
1950
|
+
) : null}
|
|
1951
|
+
</AnimatePresence>
|
|
1952
|
+
{!groups.length
|
|
1953
|
+
? (emptyState ?? (
|
|
1954
|
+
<p className="py-10 text-center text-og-menu text-og-fg-subtle">
|
|
1955
|
+
No activity yet.
|
|
1956
|
+
</p>
|
|
1957
|
+
))
|
|
1958
|
+
: null}
|
|
1959
|
+
{hasOlder && olderPrefetchArmed ? (
|
|
1960
|
+
// Overlaid, not a layout row: mounting/unmounting the sentinel
|
|
1961
|
+
// must never shift content (that shift was itself a wobble).
|
|
1962
|
+
// End at the scroll origin above the pt-16 gutter so the
|
|
1963
|
+
// observer and scrollTop cooldown share the same 400px band.
|
|
1964
|
+
<div
|
|
1965
|
+
ref={topSentinelRef}
|
|
1966
|
+
data-og-top-sentinel=""
|
|
1967
|
+
data-og-timeline-chrome=""
|
|
1968
|
+
aria-hidden="true"
|
|
1969
|
+
className="pointer-events-none absolute inset-x-0 -top-16 h-px -translate-y-full"
|
|
1805
1970
|
/>
|
|
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
1971
|
) : 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;
|
|
1972
|
+
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
1973
|
+
return (
|
|
1974
|
+
<TimelineGroupEntry
|
|
1975
|
+
key={key}
|
|
1976
|
+
groupKey={key}
|
|
1977
|
+
group={group}
|
|
1978
|
+
nextGroup={groups[index + 1]?.group}
|
|
1979
|
+
entranceEnabled={entranceEnabled}
|
|
1980
|
+
liveEntranceEnabled={
|
|
1981
|
+
group.kind === "activity" ? !bulkRender : undefined
|
|
1863
1982
|
}
|
|
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>
|
|
1983
|
+
context={timelineGroupEntryContext}
|
|
1984
|
+
/>
|
|
1985
|
+
);
|
|
1986
|
+
})}
|
|
1987
|
+
{groups.length > 0 && trailingState ? (
|
|
1988
|
+
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
1900
1989
|
) : null}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1990
|
+
{hasNewer ? (
|
|
1991
|
+
<div
|
|
1992
|
+
ref={bottomSentinelRef}
|
|
1993
|
+
data-og-bottom-sentinel=""
|
|
1994
|
+
data-og-timeline-chrome=""
|
|
1995
|
+
aria-hidden="true"
|
|
1996
|
+
className="h-px w-full"
|
|
1997
|
+
/>
|
|
1998
|
+
) : null}
|
|
1999
|
+
</div>
|
|
2000
|
+
</TimelineBeforeLayout>
|
|
2001
|
+
</div>
|
|
2002
|
+
|
|
1904
2003
|
<AnimatePresence>
|
|
1905
2004
|
{loadingNewer ? (
|
|
1906
2005
|
<motion.div
|
|
@@ -2805,7 +2904,7 @@ function CompactionRow({ item }: { item: ContextCompactionItem }) {
|
|
|
2805
2904
|
? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
|
|
2806
2905
|
: item.phase === "started"
|
|
2807
2906
|
? WAITING_PILL_CLASS
|
|
2808
|
-
:
|
|
2907
|
+
: NEUTRAL_PILL;
|
|
2809
2908
|
return (
|
|
2810
2909
|
<div className={cn(enter && "animate-og-enter", "flex justify-center")}>
|
|
2811
2910
|
<div
|
|
@@ -3301,41 +3400,46 @@ type GoalMeta = { label: string; pill: string; icon: ComponentType<{ className?:
|
|
|
3301
3400
|
|
|
3302
3401
|
const NEUTRAL_PILL = "border-og-border bg-og-surface-1 text-og-fg-muted";
|
|
3303
3402
|
|
|
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
|
-
|
|
3403
|
+
// Shared production chunks can be cyclic. Resolve icon bindings during render,
|
|
3404
|
+
// after their modules initialize, rather than permanently capturing undefined.
|
|
3405
|
+
function goalMeta(action: GoalItem["action"]): GoalMeta {
|
|
3406
|
+
const metadata: Record<GoalItem["action"], GoalMeta> = {
|
|
3407
|
+
set: {
|
|
3408
|
+
label: "Goal set",
|
|
3409
|
+
pill: "border-og-accent/30 bg-og-accent/10 text-og-accent",
|
|
3410
|
+
icon: TargetIcon,
|
|
3411
|
+
},
|
|
3412
|
+
updated: { label: "Goal updated", pill: NEUTRAL_PILL, icon: PencilLineIcon },
|
|
3413
|
+
completed: {
|
|
3414
|
+
label: "Goal completed",
|
|
3415
|
+
pill: "border-og-status-idle/30 bg-og-status-idle/10 text-og-status-idle",
|
|
3416
|
+
icon: CheckIcon,
|
|
3417
|
+
},
|
|
3418
|
+
paused: {
|
|
3419
|
+
label: "Goal paused",
|
|
3420
|
+
pill: WAITING_PILL_CLASS,
|
|
3421
|
+
icon: PauseIcon,
|
|
3422
|
+
},
|
|
3423
|
+
resumed: { label: "Goal resumed", pill: NEUTRAL_PILL, icon: PlayIcon },
|
|
3424
|
+
cleared: { label: "Goal cleared", pill: NEUTRAL_PILL, icon: Trash2Icon },
|
|
3425
|
+
held: {
|
|
3426
|
+
label: "Goal held",
|
|
3427
|
+
pill: WAITING_PILL_CLASS,
|
|
3428
|
+
icon: PauseCircleIcon,
|
|
3429
|
+
},
|
|
3430
|
+
continuation: { label: "Continuing toward the goal", pill: NEUTRAL_PILL, icon: ArrowRightIcon },
|
|
3431
|
+
};
|
|
3432
|
+
return metadata[action];
|
|
3433
|
+
}
|
|
3330
3434
|
|
|
3331
3435
|
/**
|
|
3332
3436
|
* A goal landmark pill. Resolves its label, accent/tone, and glyph from
|
|
3333
|
-
* {@link
|
|
3437
|
+
* {@link goalMeta} so all six actions are visually distinguishable while the
|
|
3334
3438
|
* palette stays restrained — see that table for the per-action rationale.
|
|
3335
3439
|
*/
|
|
3336
3440
|
function GoalRow({ item }: { item: GoalItem }) {
|
|
3337
3441
|
const enter = useEntranceAnimation();
|
|
3338
|
-
const { label, pill, icon: Icon } =
|
|
3442
|
+
const { label, pill, icon: Icon } = goalMeta(item.action);
|
|
3339
3443
|
return (
|
|
3340
3444
|
<div className={cn(enter && "animate-og-enter", "flex justify-center")}>
|
|
3341
3445
|
<span
|
|
@@ -3378,7 +3482,11 @@ function MachineInputBatchRow({
|
|
|
3378
3482
|
|
|
3379
3483
|
return (
|
|
3380
3484
|
<div className={cn(enter && "animate-og-enter", "flex flex-col items-center gap-1.5")}>
|
|
3381
|
-
<details
|
|
3485
|
+
<details
|
|
3486
|
+
className="group w-full max-w-full"
|
|
3487
|
+
data-og-machine-input-batch=""
|
|
3488
|
+
title={`Received ${formatClockTime(item.occurredAt)}`}
|
|
3489
|
+
>
|
|
3382
3490
|
<summary className="flex cursor-pointer list-none justify-center [&::-webkit-details-marker]:hidden">
|
|
3383
3491
|
<span
|
|
3384
3492
|
className={cn(
|
|
@@ -3394,6 +3502,9 @@ function MachineInputBatchRow({
|
|
|
3394
3502
|
</span>
|
|
3395
3503
|
</summary>
|
|
3396
3504
|
<div className="mx-auto mt-2 w-full max-w-lg space-y-2 border-t border-og-border/50 pt-2">
|
|
3505
|
+
<p className="text-og-xs text-og-fg-subtle">
|
|
3506
|
+
Received <time dateTime={item.occurredAt}>{formatClockTime(item.occurredAt)}</time>
|
|
3507
|
+
</p>
|
|
3397
3508
|
{item.members.map((member) => (
|
|
3398
3509
|
<MachineInputRow
|
|
3399
3510
|
key={member.id}
|
|
@@ -3521,7 +3632,7 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
3521
3632
|
? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
|
|
3522
3633
|
: item.tone === "waiting"
|
|
3523
3634
|
? WAITING_PILL_CLASS
|
|
3524
|
-
:
|
|
3635
|
+
: NEUTRAL_PILL;
|
|
3525
3636
|
return (
|
|
3526
3637
|
<div
|
|
3527
3638
|
className={cn(
|
|
@@ -3529,12 +3640,24 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
3529
3640
|
"flex items-start gap-2.5 rounded-og-md border px-3.5 py-2.5 text-og-menu",
|
|
3530
3641
|
tone,
|
|
3531
3642
|
)}
|
|
3532
|
-
role="status"
|
|
3643
|
+
role={item.recordedOutcome ? "note" : "status"}
|
|
3644
|
+
data-og-recorded-outcome={item.recordedOutcome ? "wait" : undefined}
|
|
3533
3645
|
>
|
|
3534
3646
|
<TriangleAlertIcon
|
|
3535
3647
|
className={cn("mt-0.5 size-4 shrink-0", item.tone === "cancelled" && "opacity-60")}
|
|
3536
3648
|
/>
|
|
3537
3649
|
<div className="min-w-0 flex-1">
|
|
3650
|
+
{item.recordedOutcome ? (
|
|
3651
|
+
<p className="mb-1 text-og-control font-medium">
|
|
3652
|
+
Wait recorded{" "}
|
|
3653
|
+
<time dateTime={item.occurredAt}>
|
|
3654
|
+
{new Date(item.occurredAt).toLocaleString(undefined, {
|
|
3655
|
+
dateStyle: "medium",
|
|
3656
|
+
timeStyle: "short",
|
|
3657
|
+
})}
|
|
3658
|
+
</time>
|
|
3659
|
+
</p>
|
|
3660
|
+
) : null}
|
|
3538
3661
|
<span className="whitespace-pre-wrap break-words">{item.text}</span>
|
|
3539
3662
|
{item.details ? (
|
|
3540
3663
|
<details className="mt-2 text-og-control">
|