@opengeni/react-native 4.2.1-canary.39642758903001 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/android/build.gradle +15 -0
  2. package/android/src/main/AndroidManifest.xml +1 -0
  3. package/android/src/main/java/dev/opengeni/textselection/OpenGeniTextSelectionModule.kt +274 -0
  4. package/dist/{chunk-D3MFBDVR.js → chunk-2RNMGRTB.js} +3 -3
  5. package/dist/{chunk-D3MFBDVR.js.map → chunk-2RNMGRTB.js.map} +1 -1
  6. package/dist/{chunk-2W74OY6I.js → chunk-FHPMRXIG.js} +16 -2
  7. package/dist/chunk-FHPMRXIG.js.map +1 -0
  8. package/dist/{chunk-YTT4WSJZ.js → chunk-QFV5DISV.js} +3 -3
  9. package/dist/{chunk-YTT4WSJZ.js.map → chunk-QFV5DISV.js.map} +1 -1
  10. package/dist/{chunk-U5L2CUMJ.js → chunk-ZZNZBZWV.js} +3 -3
  11. package/dist/{chunk-U5L2CUMJ.js.map → chunk-ZZNZBZWV.js.map} +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/timeline/annotations.d.ts +81 -0
  14. package/dist/timeline/icon-nodes.generated.d.ts +5 -0
  15. package/dist/timeline/index.d.ts +2 -0
  16. package/dist/timeline/index.js +1186 -197
  17. package/dist/timeline/index.js.map +1 -1
  18. package/dist/timeline/markdown.js +5 -2
  19. package/dist/timeline/markdown.js.map +1 -1
  20. package/dist/timeline/message-timeline.d.ts +20 -2
  21. package/dist/timeline/previews.js +2 -2
  22. package/dist/timeline/queue-dock.d.ts +4 -1
  23. package/dist/timeline/text-selection.d.ts +44 -0
  24. package/expo-module.config.json +5 -2
  25. package/ios/OpenGeniTextSelectionModule.swift +560 -0
  26. package/package.json +6 -4
  27. package/src/timeline/activity.tsx +24 -15
  28. package/src/timeline/annotations.tsx +877 -0
  29. package/src/timeline/icon-nodes.generated.ts +14 -0
  30. package/src/timeline/index.ts +2 -0
  31. package/src/timeline/markdown.tsx +7 -0
  32. package/src/timeline/message-timeline.tsx +340 -174
  33. package/src/timeline/queue-dock.tsx +36 -0
  34. package/src/timeline/session-screen.tsx +122 -4
  35. package/src/timeline/sheet.tsx +3 -2
  36. package/src/timeline/text-selection.ts +115 -0
  37. package/dist/chunk-2W74OY6I.js.map +0 -1
@@ -1,19 +1,20 @@
1
- import { defaultSecretFieldMessages, ThinkingOrb, humanInputAnswerLines, isSecretQuestion, initialSecretDraft, secretSubmission, secretInputTraits, secretUsernameTraits, SECRET_SAVE_TO_OPTIONS, secretPatternMismatch } from '../chunk-D3MFBDVR.js';
2
- import { ActivityDisclosure, useNativeTimelineMessages, useReleaseFollow, ShimmerText, ReleaseFollowProvider, withAlpha, blendOver, NativeTimelineMessagesProvider, ROW_MIN_HEIGHT, PulseDot, BodyNote, PayloadBlock, TermBlock, Chip, RunningPreview } from '../chunk-YTT4WSJZ.js';
3
- export { ActivityDisclosure, BodyNote, Chip, NativeTimelineMessagesProvider, PayloadBlock, PulseDot, ShimmerText, TermBlock, defaultNativeTimelineMessages, useNativeTimelineMessages, withAlpha } from '../chunk-YTT4WSJZ.js';
4
- import { NativeImageViewer } from '../chunk-U5L2CUMJ.js';
5
- export { NativeImageViewer } from '../chunk-U5L2CUMJ.js';
1
+ import { defaultSecretFieldMessages, ThinkingOrb, humanInputAnswerLines, isSecretQuestion, initialSecretDraft, secretSubmission, secretInputTraits, secretUsernameTraits, SECRET_SAVE_TO_OPTIONS, secretPatternMismatch } from '../chunk-2RNMGRTB.js';
2
+ import { ActivityDisclosure, useNativeTimelineMessages, useReleaseFollow, ShimmerText, withAlpha, ReleaseFollowProvider, blendOver, NativeTimelineMessagesProvider, ROW_MIN_HEIGHT, PulseDot, BodyNote, PayloadBlock, TermBlock, Chip, RunningPreview } from '../chunk-QFV5DISV.js';
3
+ export { ActivityDisclosure, BodyNote, Chip, NativeTimelineMessagesProvider, PayloadBlock, PulseDot, ShimmerText, TermBlock, defaultNativeTimelineMessages, useNativeTimelineMessages, withAlpha } from '../chunk-QFV5DISV.js';
4
+ import { NativeImageViewer } from '../chunk-ZZNZBZWV.js';
5
+ export { NativeImageViewer } from '../chunk-ZZNZBZWV.js';
6
6
  import { useOpenNativeLink } from '../chunk-2WJTBHO6.js';
7
7
  import '../chunk-7ZDF5VGA.js';
8
- import { useNativeTimelineTheme, fontStyle, Icon } from '../chunk-2W74OY6I.js';
9
- export { Icon, NativeTimelineThemeProvider, createNativeTimelineTheme, fontStyle, useNativeTimelineTheme, webColorsDark, webColorsLight } from '../chunk-2W74OY6I.js';
8
+ import { useNativeTimelineTheme, fontStyle, Icon } from '../chunk-FHPMRXIG.js';
9
+ export { Icon, NativeTimelineThemeProvider, createNativeTimelineTheme, fontStyle, useNativeTimelineTheme, webColorsDark, webColorsLight } from '../chunk-FHPMRXIG.js';
10
10
  import { buildTimeline, groupTimeline, conversationTimeline } from '@opengeni/react/session';
11
- import { defaultHumanInputFormMessages, createTurnSummaryContext, resolveTurnSummaryFacets, selectTurnSummaryFacets, formatElapsed, turnSummaryFallbackLabel, rollingActivityItem, toolDisplayName, questionNavTarget, timelineGroupIndexAtSequence, QUESTION_NAV_MARGIN_PX, flattenActivityItems, timelineGroupContainsPresentedImage, durationBetween, isPreparingWork, readableWorkStatus, readableWorkShowsPreview, readableWorkDefaultOpen, compactedLandmarkCount, SESSION_STATUS_PRESENTATION, formatRelativeTime, recordedWaitSummaryText, noticeTone, noticeDisplayText, isActionableHumanInputRequest, humanInputHeading, initialDrafts, emptyDraft, SESSION_STATUS_BADGE, workerRowTitle, startupDuration, STARTUP_WAIT_TITLES, startupPhaseTitle, sandboxRowTitle, toolRowPresentation, presentsToolOutputPreview, GENIE_WAITING_PHRASES, GENIE_PREPARING_PHRASES, answersFromDrafts, notificationPlainText, pathDirname, pathBasename, formatClockTime, parseNotificationText } from '@opengeni/react/timeline-model';
11
+ import { defaultHumanInputFormMessages, createTurnSummaryContext, resolveTurnSummaryFacets, selectTurnSummaryFacets, formatElapsed, turnSummaryFallbackLabel, rollingActivityItem, toolDisplayName, annotationHasNote, annotationDisplayOrdinal, ANNOTATION_MAX_NOTE_LENGTH, questionNavTarget, timelineGroupIndexAtSequence, QUESTION_NAV_MARGIN_PX, flattenActivityItems, timelineGroupContainsPresentedImage, durationBetween, isPreparingWork, readableWorkStatus, readableWorkShowsPreview, readableWorkDefaultOpen, compactedLandmarkCount, SESSION_STATUS_PRESENTATION, formatRelativeTime, recordedWaitSummaryText, noticeTone, noticeDisplayText, isActionableHumanInputRequest, humanInputHeading, initialDrafts, emptyDraft, SESSION_STATUS_BADGE, workerRowTitle, startupDuration, STARTUP_WAIT_TITLES, startupPhaseTitle, sandboxRowTitle, toolRowPresentation, presentsToolOutputPreview, GENIE_WAITING_PHRASES, GENIE_PREPARING_PHRASES, truncateAnnotationQuote, annotationNoteNeedsDisclosure, annotationFromSelection, answersFromDrafts, notificationPlainText, pathDirname, pathBasename, formatClockTime, parseNotificationText } from '@opengeni/react/timeline-model';
12
12
  import { createContext, useContext, useRef, useState, useEffect, useMemo, useCallback, useId } from 'react';
13
- import { Platform, Text, View, Pressable, ScrollView, ActivityIndicator, Modal, KeyboardAvoidingView, TextInput, Image, useWindowDimensions, Animated as Animated$1, AccessibilityInfo, Easing as Easing$1, PanResponder, AppState, Alert, ActionSheetIOS, Linking } from 'react-native';
14
- import Animated, { FadeIn, FadeOut, FadeOutUp, FadeInDown, useAnimatedKeyboard, useAnimatedStyle, useReducedMotion, useSharedValue, cancelAnimation, withRepeat, withTiming, Easing } from 'react-native-reanimated';
13
+ import { Platform, Text, View, Pressable, useWindowDimensions, Animated as Animated$1, AccessibilityInfo, Easing, PanResponder, Modal, KeyboardAvoidingView, ScrollView, TextInput, Keyboard, ActivityIndicator, Image, Alert, AppState, ActionSheetIOS, Linking } from 'react-native';
14
+ import Animated, { FadeIn, FadeOut, FadeOutUp, FadeInDown, useAnimatedKeyboard, useAnimatedStyle, useReducedMotion, useSharedValue, cancelAnimation, withRepeat, withTiming, Easing as Easing$1 } from 'react-native-reanimated';
15
15
  import { createOpenGeniText, useOpenGeniText, SingleSendWording } from '@opengeni/react/messages';
16
16
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
17
18
  import { defaultChatComposerMessages } from '@opengeni/react/composer-messages';
18
19
  import { isLiquidGlassAvailable, GlassView } from 'expo-glass-effect';
19
20
  import { loadSessionFeedback, turnRatingsFromFeedback, feedbackSentimentLabel, feedbackDialogCopy, feedbackSubmission } from '@opengeni/react/session-feedback-model';
@@ -23,9 +24,60 @@ import { modelMarkVendor, MODEL_MARK_PATHS } from '@opengeni/react/model-mark-pa
23
24
  import { projectClientModelRows, labelReasoningEffort } from '@opengeni/react/model-policy';
24
25
  import { sessionDisplayTitle, recentSessionStatus, recentSessionModelPresentation, sessionRepoLabel, relativeTimeLabel, sessionRenameSeed, SESSION_TITLE_MAX_LENGTH, resolveRenameSubmission } from '@opengeni/react/session-list-model';
25
26
  import Svg, { Path } from 'react-native-svg';
26
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
27
27
  import { modelPickerMenuView, groupPresentationFor, compactEffortLabel, modelPickerChoice } from '@opengeni/react/model-picker-model';
28
28
 
29
+ var ANNOTATION_SOURCE_TEST_ID_PREFIX = "og-annotation-source:";
30
+ function annotationSourceTestId(sourceKey) {
31
+ return `${ANNOTATION_SOURCE_TEST_ID_PREFIX}${sourceKey ?? ""}`;
32
+ }
33
+ function textSelectionModule() {
34
+ if (Platform.OS !== "ios" && Platform.OS !== "android") return null;
35
+ const registry = globalThis.expo?.modules;
36
+ const module = registry?.OpenGeniTextSelection;
37
+ return module && typeof module.addListener === "function" ? module : null;
38
+ }
39
+ async function measureQuoteAnchors(sourceKey, queries) {
40
+ const module = textSelectionModule();
41
+ if (!module?.quoteAnchors || queries.length === 0) return queries.map(() => null);
42
+ try {
43
+ const anchors = await module.quoteAnchors(sourceKey, queries);
44
+ return queries.map((_, index) => {
45
+ const anchor = anchors[index];
46
+ if (typeof anchor?.x !== "number" || typeof anchor.top !== "number") return null;
47
+ const flat = anchor.rects ?? [];
48
+ const boxes = [];
49
+ for (let at = 0; at + 3 < flat.length; at += 4) {
50
+ boxes.push({ x: flat[at], y: flat[at + 1], width: flat[at + 2], height: flat[at + 3] });
51
+ }
52
+ return { x: anchor.x, top: anchor.top, boxes };
53
+ });
54
+ } catch {
55
+ return queries.map(() => null);
56
+ }
57
+ }
58
+ function useNativeTextAnnotation(sources, onAnnotate) {
59
+ const t = useOpenGeniText();
60
+ const latest = useRef({ sources, onAnnotate });
61
+ latest.current = { sources, onAnnotate };
62
+ const enabled = Boolean(onAnnotate);
63
+ const title = t("timelineAnnotationSelection.addNote");
64
+ useEffect(() => {
65
+ const module = textSelectionModule();
66
+ if (!module || !enabled) return;
67
+ module.setAnnotateTitle(title);
68
+ const subscription = module.addListener("onAnnotate", (event) => {
69
+ const source = latest.current.sources.get(event.sourceKey);
70
+ if (!source) return;
71
+ const annotation = annotationFromSelection(source, {
72
+ text: event.quote,
73
+ textBefore: event.before,
74
+ textAfter: event.after
75
+ });
76
+ if (annotation) latest.current.onAnnotate?.(annotation);
77
+ });
78
+ return () => subscription.remove();
79
+ }, [enabled, title]);
80
+ }
29
81
  var ActivityOptionsContext = createContext({});
30
82
  var NativeActivityOptionsProvider = ActivityOptionsContext.Provider;
31
83
  function useNativeActivityOptions() {
@@ -400,9 +452,10 @@ function ReasoningRow({ item, compact }) {
400
452
  ),
401
453
  accessibilityLabel: item.streaming ? m.thinking : m.thought,
402
454
  preview: plain || void 0,
403
- children: options.renderMarkdown ? options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming }) : /* @__PURE__ */ jsx(
455
+ children: /* @__PURE__ */ jsx(View, { testID: annotationSourceTestId(void 0), collapsable: false, children: options.renderMarkdown ? options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming }) : /* @__PURE__ */ jsx(
404
456
  Text,
405
457
  {
458
+ selectable: true,
406
459
  style: {
407
460
  ...fontStyle(theme),
408
461
  fontSize: theme.size.base,
@@ -411,7 +464,7 @@ function ReasoningRow({ item, compact }) {
411
464
  },
412
465
  children: item.text
413
466
  }
414
- )
467
+ ) })
415
468
  }
416
469
  );
417
470
  }
@@ -578,18 +631,27 @@ function ActivityNoteRow({ item }) {
578
631
  children: [
579
632
  /* @__PURE__ */ jsx(View, { style: { width: 14 } }),
580
633
  /* @__PURE__ */ jsx(View, { style: { marginTop: 4 }, children: /* @__PURE__ */ jsx(Icon, { name: "message-square-text", size: 14, color: theme.colors["fg-subtle"] }) }),
581
- /* @__PURE__ */ jsx(View, { style: { flex: 1, minWidth: 0 }, children: options.renderMarkdown ? options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming }) : /* @__PURE__ */ jsx(
582
- Text,
634
+ /* @__PURE__ */ jsx(
635
+ View,
583
636
  {
584
- style: {
585
- ...fontStyle(theme),
586
- fontSize: theme.size.sm,
587
- lineHeight: 20,
588
- color: theme.colors["fg-muted"]
589
- },
590
- children: item.text
637
+ testID: annotationSourceTestId(item.annotationSource?.eventId),
638
+ collapsable: false,
639
+ style: { flex: 1, minWidth: 0 },
640
+ children: options.renderMarkdown ? options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming }) : /* @__PURE__ */ jsx(
641
+ Text,
642
+ {
643
+ selectable: true,
644
+ style: {
645
+ ...fontStyle(theme),
646
+ fontSize: theme.size.sm,
647
+ lineHeight: 20,
648
+ color: theme.colors["fg-muted"]
649
+ },
650
+ children: item.text
651
+ }
652
+ )
591
653
  }
592
- ) })
654
+ )
593
655
  ]
594
656
  }
595
657
  );
@@ -1170,9 +1232,828 @@ function liveFoldShift(liveNotes, workId, open, heights, rowGap) {
1170
1232
  }, 0);
1171
1233
  return height === 0 ? 0 : open ? -height : height;
1172
1234
  }
1235
+ function BottomSheet(props) {
1236
+ const t = useOpenGeniText();
1237
+ const theme = useNativeTimelineTheme();
1238
+ const c = theme.colors;
1239
+ const insets = useSafeAreaInsets();
1240
+ const { height } = useWindowDimensions();
1241
+ const [mounted, setMounted] = useState(props.open);
1242
+ const progress = useRef(new Animated$1.Value(0)).current;
1243
+ const drag = useRef(new Animated$1.Value(0)).current;
1244
+ const onClose = useRef(props.onClose);
1245
+ onClose.current = props.onClose;
1246
+ useEffect(() => {
1247
+ let cancelled = false;
1248
+ if (props.open) {
1249
+ setMounted(true);
1250
+ drag.setValue(0);
1251
+ void AccessibilityInfo.isReduceMotionEnabled().then((reduce) => {
1252
+ if (cancelled) return;
1253
+ Animated$1.timing(progress, {
1254
+ toValue: 1,
1255
+ duration: reduce ? 0 : 260,
1256
+ easing: Easing.bezier(0.22, 1, 0.36, 1),
1257
+ useNativeDriver: true
1258
+ }).start();
1259
+ });
1260
+ } else if (mounted) {
1261
+ Animated$1.timing(progress, {
1262
+ toValue: 0,
1263
+ duration: 200,
1264
+ easing: Easing.in(Easing.quad),
1265
+ useNativeDriver: true
1266
+ }).start(({ finished }) => {
1267
+ if (finished && !cancelled) setMounted(false);
1268
+ });
1269
+ }
1270
+ return () => {
1271
+ cancelled = true;
1272
+ };
1273
+ }, [drag, mounted, progress, props.open]);
1274
+ const pan = useRef(
1275
+ PanResponder.create({
1276
+ onMoveShouldSetPanResponder: (_event, gesture) => gesture.dy > 6 && Math.abs(gesture.dy) > Math.abs(gesture.dx),
1277
+ onPanResponderMove: (_event, gesture) => drag.setValue(Math.max(0, gesture.dy)),
1278
+ onPanResponderRelease: (_event, gesture) => {
1279
+ if (gesture.dy > 90 || gesture.vy > 0.9) onClose.current();
1280
+ else Animated$1.spring(drag, { toValue: 0, useNativeDriver: true, bounciness: 0 }).start();
1281
+ },
1282
+ onPanResponderTerminate: () => Animated$1.spring(drag, { toValue: 0, useNativeDriver: true, bounciness: 0 }).start()
1283
+ })
1284
+ ).current;
1285
+ if (!mounted) return null;
1286
+ const translateY = Animated$1.add(
1287
+ progress.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }),
1288
+ drag
1289
+ );
1290
+ return /* @__PURE__ */ jsx(
1291
+ Modal,
1292
+ {
1293
+ visible: true,
1294
+ transparent: true,
1295
+ animationType: "none",
1296
+ statusBarTranslucent: true,
1297
+ onRequestClose: props.onClose,
1298
+ children: /* @__PURE__ */ jsxs(
1299
+ KeyboardAvoidingView,
1300
+ {
1301
+ behavior: "padding",
1302
+ style: { flex: 1, justifyContent: "flex-end" },
1303
+ children: [
1304
+ /* @__PURE__ */ jsx(
1305
+ Animated$1.View,
1306
+ {
1307
+ style: {
1308
+ position: "absolute",
1309
+ top: 0,
1310
+ right: 0,
1311
+ bottom: 0,
1312
+ left: 0,
1313
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
1314
+ opacity: progress
1315
+ },
1316
+ children: /* @__PURE__ */ jsx(
1317
+ Pressable,
1318
+ {
1319
+ style: { flex: 1 },
1320
+ onPress: props.onClose,
1321
+ accessibilityRole: "button",
1322
+ accessibilityLabel: t("native.timeline.sheet.close")
1323
+ }
1324
+ )
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsxs(
1328
+ Animated$1.View,
1329
+ {
1330
+ accessibilityViewIsModal: true,
1331
+ accessibilityLabel: props.accessibilityLabel,
1332
+ style: {
1333
+ maxHeight: height * (props.maxHeightRatio ?? 0.85),
1334
+ backgroundColor: c.bg,
1335
+ borderTopLeftRadius: 16,
1336
+ borderTopRightRadius: 16,
1337
+ paddingBottom: Math.max(insets.bottom, 12),
1338
+ transform: [{ translateY }],
1339
+ shadowColor: "#000",
1340
+ shadowOpacity: 0.12,
1341
+ shadowRadius: 16,
1342
+ shadowOffset: { width: 0, height: -2 },
1343
+ elevation: 16
1344
+ },
1345
+ children: [
1346
+ /* @__PURE__ */ jsx(
1347
+ View,
1348
+ {
1349
+ ...pan.panHandlers,
1350
+ style: { alignItems: "center", paddingTop: 8, paddingBottom: 4 },
1351
+ children: /* @__PURE__ */ jsx(
1352
+ View,
1353
+ {
1354
+ style: {
1355
+ width: 36,
1356
+ height: 5,
1357
+ borderRadius: 3,
1358
+ backgroundColor: c["border-strong"] ?? c.border
1359
+ }
1360
+ }
1361
+ )
1362
+ }
1363
+ ),
1364
+ props.children
1365
+ ]
1366
+ }
1367
+ )
1368
+ ]
1369
+ }
1370
+ )
1371
+ }
1372
+ );
1373
+ }
1374
+ function annotationQuoteQuery(annotation) {
1375
+ return {
1376
+ quote: annotation.quote,
1377
+ before: annotation.source.contextBefore,
1378
+ after: annotation.source.contextAfter
1379
+ };
1380
+ }
1381
+ var AnnotationSourcesContext = createContext(null);
1382
+ function draftMarkersBySource(annotations) {
1383
+ const markers = /* @__PURE__ */ new Map();
1384
+ (annotations ?? []).forEach((annotation, index) => {
1385
+ const list = markers.get(annotation.source.eventId) ?? [];
1386
+ list.push({
1387
+ id: annotation.id,
1388
+ ordinal: index + 1,
1389
+ incomplete: !annotationHasNote(annotation.note),
1390
+ quote: annotation.quote,
1391
+ before: annotation.source.contextBefore,
1392
+ after: annotation.source.contextAfter
1393
+ });
1394
+ markers.set(annotation.source.eventId, list);
1395
+ });
1396
+ return markers;
1397
+ }
1398
+ function quotePreview(quote) {
1399
+ const trimmed = quote.trim();
1400
+ return trimmed.includes("\n") ? trimmed : trimmed.replace(/\s+/g, " ");
1401
+ }
1402
+ function AnnotationSourceView({
1403
+ sourceKey,
1404
+ children,
1405
+ style
1406
+ }) {
1407
+ const theme = useNativeTimelineTheme();
1408
+ const sources = useContext(AnnotationSourcesContext);
1409
+ const glow = useRef(new Animated$1.Value(0)).current;
1410
+ const flash = sourceKey && sources?.flash?.sourceKey === sourceKey ? sources.flash : null;
1411
+ const [flashBoxes, setFlashBoxes] = useState(null);
1412
+ useEffect(() => {
1413
+ if (!flash || !sourceKey) return;
1414
+ let cancelled = false;
1415
+ const start = (boxes) => {
1416
+ if (cancelled) return;
1417
+ setFlashBoxes(boxes);
1418
+ glow.setValue(1);
1419
+ Animated$1.timing(glow, {
1420
+ toValue: 0,
1421
+ duration: boxes ? 1800 : 1400,
1422
+ delay: 300,
1423
+ easing: Easing.out(Easing.quad),
1424
+ useNativeDriver: false
1425
+ }).start();
1426
+ };
1427
+ if (!flash.quote) start(null);
1428
+ else
1429
+ void measureQuoteAnchors(sourceKey, [flash.quote]).then(
1430
+ ([anchor]) => start(anchor?.boxes.length ? anchor.boxes : null)
1431
+ );
1432
+ return () => {
1433
+ cancelled = true;
1434
+ };
1435
+ }, [flash, glow, sourceKey]);
1436
+ const register = sources?.register;
1437
+ const markers = sourceKey ? sources?.drafts.get(sourceKey) : void 0;
1438
+ const [anchors, setAnchors] = useState(null);
1439
+ const [size, setSize] = useState({ width: 0, height: 0 });
1440
+ const measure = useCallback(() => {
1441
+ if (!sourceKey || !markers?.length) return;
1442
+ const queries = markers.map(({ quote, before, after }) => ({ quote, before, after }));
1443
+ void measureQuoteAnchors(sourceKey, queries).then((at) => setAnchors({ markers, at }));
1444
+ }, [markers, sourceKey]);
1445
+ useEffect(() => {
1446
+ if (!markers?.length) {
1447
+ setAnchors(null);
1448
+ return;
1449
+ }
1450
+ const frame = requestAnimationFrame(measure);
1451
+ return () => cancelAnimationFrame(frame);
1452
+ }, [markers, measure, size.width, size.height]);
1453
+ return /* @__PURE__ */ jsxs(
1454
+ Animated$1.View,
1455
+ {
1456
+ ref: (node) => {
1457
+ if (sourceKey && register) register(sourceKey, node);
1458
+ },
1459
+ testID: annotationSourceTestId(sourceKey),
1460
+ collapsable: false,
1461
+ onLayout: (event) => {
1462
+ const { width, height } = event.nativeEvent.layout;
1463
+ setSize(
1464
+ (current) => current.width === width && current.height === height ? current : { width, height }
1465
+ );
1466
+ },
1467
+ style: [
1468
+ style,
1469
+ {
1470
+ borderRadius: theme.radius.md,
1471
+ backgroundColor: flashBoxes ? "transparent" : glow.interpolate({
1472
+ inputRange: [0, 1],
1473
+ outputRange: [withAlpha(theme.colors.fg, 0), withAlpha(theme.colors.fg, 0.08)]
1474
+ })
1475
+ }
1476
+ ],
1477
+ children: [
1478
+ flashBoxes ? /* @__PURE__ */ jsx(QuoteFlash, { boxes: flashBoxes, glow }) : null,
1479
+ markers?.length && anchors?.markers === markers ? /* @__PURE__ */ jsx(QuoteHighlights, { anchors: anchors.at }) : null,
1480
+ children,
1481
+ markers?.length && anchors?.markers === markers ? /* @__PURE__ */ jsx(
1482
+ QuoteBadges,
1483
+ {
1484
+ markers,
1485
+ anchors: anchors.at,
1486
+ size,
1487
+ onSelect: sources?.onSelectDraft
1488
+ }
1489
+ ) : null
1490
+ ]
1491
+ }
1492
+ );
1493
+ }
1494
+ var BADGE_SIZE = 15;
1495
+ function boxKey(box) {
1496
+ return `${Math.round(box.x)}:${Math.round(box.y)}:${Math.round(box.width)}`;
1497
+ }
1498
+ function QuoteFlash({ boxes, glow }) {
1499
+ const theme = useNativeTimelineTheme();
1500
+ const tint = withAlpha(theme.colors.accent, theme.scheme === "dark" ? 0.45 : 0.28);
1501
+ return /* @__PURE__ */ jsx(Fragment, { children: boxes.map((box) => /* @__PURE__ */ jsx(
1502
+ Animated$1.View,
1503
+ {
1504
+ pointerEvents: "none",
1505
+ style: {
1506
+ position: "absolute",
1507
+ left: box.x - 3,
1508
+ top: box.y - 2,
1509
+ width: box.width + 6,
1510
+ height: box.height + 4,
1511
+ borderRadius: 5,
1512
+ backgroundColor: tint,
1513
+ opacity: glow
1514
+ }
1515
+ },
1516
+ boxKey(box)
1517
+ )) });
1518
+ }
1519
+ function QuoteHighlights({ anchors }) {
1520
+ const theme = useNativeTimelineTheme();
1521
+ const dark = theme.scheme === "dark";
1522
+ const tint = withAlpha(theme.colors.accent, dark ? 0.16 : 0.08);
1523
+ const rule = withAlpha(theme.colors.accent, dark ? 0.7 : 0.55);
1524
+ return /* @__PURE__ */ jsx(Fragment, { children: anchors.flatMap(
1525
+ (anchor) => (anchor?.boxes ?? []).map((box) => /* @__PURE__ */ jsx(
1526
+ View,
1527
+ {
1528
+ pointerEvents: "none",
1529
+ style: {
1530
+ position: "absolute",
1531
+ left: box.x - 2,
1532
+ top: box.y - 1,
1533
+ width: box.width + 4,
1534
+ height: box.height + 2,
1535
+ borderTopLeftRadius: 3,
1536
+ borderTopRightRadius: 3,
1537
+ borderBottomWidth: 1.5,
1538
+ borderBottomColor: rule,
1539
+ backgroundColor: tint
1540
+ }
1541
+ },
1542
+ boxKey(box)
1543
+ ))
1544
+ ) });
1545
+ }
1546
+ function QuoteBadges({
1547
+ markers,
1548
+ anchors,
1549
+ size,
1550
+ onSelect
1551
+ }) {
1552
+ const t = useOpenGeniText();
1553
+ const theme = useNativeTimelineTheme();
1554
+ const c = theme.colors;
1555
+ let unplaced = 0;
1556
+ return /* @__PURE__ */ jsx(Fragment, { children: markers.map((marker, index) => {
1557
+ const anchor = anchors[index];
1558
+ const center = anchor ? { x: anchor.x + 2, y: anchor.top - 1 } : {
1559
+ x: size.width - BADGE_SIZE / 2 - unplaced++ * (BADGE_SIZE + 4),
1560
+ y: size.height - BADGE_SIZE / 2
1561
+ };
1562
+ const left = Math.min(Math.max(center.x - BADGE_SIZE / 2, 0), size.width - BADGE_SIZE);
1563
+ return /* @__PURE__ */ jsx(
1564
+ Pressable,
1565
+ {
1566
+ accessibilityRole: "button",
1567
+ accessibilityLabel: t("timelineAnnotationMarkers.annotationOrdinal", {
1568
+ ordinal: marker.ordinal
1569
+ }),
1570
+ hitSlop: 14,
1571
+ disabled: !onSelect,
1572
+ onPress: () => onSelect?.(marker.id),
1573
+ style: ({ pressed }) => ({
1574
+ position: "absolute",
1575
+ left,
1576
+ top: center.y - BADGE_SIZE / 2,
1577
+ zIndex: marker.ordinal,
1578
+ minWidth: BADGE_SIZE,
1579
+ height: BADGE_SIZE,
1580
+ paddingHorizontal: 2,
1581
+ borderRadius: BADGE_SIZE / 2,
1582
+ alignItems: "center",
1583
+ justifyContent: "center",
1584
+ backgroundColor: c.accent,
1585
+ borderWidth: marker.incomplete ? 1 : 0,
1586
+ borderColor: withAlpha(c["accent-fg"], 0.35),
1587
+ opacity: pressed ? 0.7 : 1,
1588
+ shadowColor: "#000",
1589
+ shadowOpacity: 0.12,
1590
+ shadowRadius: 2,
1591
+ shadowOffset: { width: 0, height: 1 }
1592
+ }),
1593
+ children: /* @__PURE__ */ jsx(
1594
+ Text,
1595
+ {
1596
+ style: {
1597
+ ...fontStyle(theme, 600),
1598
+ fontSize: 10,
1599
+ lineHeight: 12,
1600
+ color: c["accent-fg"],
1601
+ fontVariant: ["tabular-nums"]
1602
+ },
1603
+ children: marker.ordinal
1604
+ }
1605
+ )
1606
+ },
1607
+ marker.id
1608
+ );
1609
+ }) });
1610
+ }
1611
+ function AccentRow({ children, shrink = false }) {
1612
+ const theme = useNativeTimelineTheme();
1613
+ return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 10 }, children: [
1614
+ /* @__PURE__ */ jsx(
1615
+ View,
1616
+ {
1617
+ style: {
1618
+ width: 2,
1619
+ borderRadius: 1,
1620
+ backgroundColor: withAlpha(theme.colors.accent, 0.55)
1621
+ }
1622
+ }
1623
+ ),
1624
+ /* @__PURE__ */ jsx(View, { style: shrink ? { flexShrink: 1, minWidth: 0 } : { flex: 1, minWidth: 0 }, children })
1625
+ ] });
1626
+ }
1627
+ function OrdinalLabel({ ordinal }) {
1628
+ const t = useOpenGeniText();
1629
+ const theme = useNativeTimelineTheme();
1630
+ return /* @__PURE__ */ jsx(
1631
+ Text,
1632
+ {
1633
+ style: {
1634
+ ...fontStyle(theme, 500),
1635
+ fontSize: theme.size.xs,
1636
+ lineHeight: 16,
1637
+ color: theme.colors["fg-subtle"],
1638
+ fontVariant: ["tabular-nums"]
1639
+ },
1640
+ children: t("timelineAnnotations.annotationOrdinal", { ordinal })
1641
+ }
1642
+ );
1643
+ }
1644
+ function QuoteButton({
1645
+ annotation,
1646
+ onReveal
1647
+ }) {
1648
+ const t = useOpenGeniText();
1649
+ const theme = useNativeTimelineTheme();
1650
+ const [unavailable, setUnavailable] = useState(false);
1651
+ const text = /* @__PURE__ */ jsx(
1652
+ Text,
1653
+ {
1654
+ numberOfLines: 2,
1655
+ style: {
1656
+ ...fontStyle(theme),
1657
+ fontSize: theme.size.sm,
1658
+ lineHeight: 20,
1659
+ color: theme.colors["fg-muted"]
1660
+ },
1661
+ children: quotePreview(annotation.quote)
1662
+ }
1663
+ );
1664
+ if (!onReveal) return text;
1665
+ return /* @__PURE__ */ jsxs(View, { children: [
1666
+ /* @__PURE__ */ jsx(
1667
+ Pressable,
1668
+ {
1669
+ accessibilityRole: "button",
1670
+ accessibilityLabel: t(
1671
+ "timelineAnnotationChrome.viewSourcelabelSourceTruncateannotationquote",
1672
+ {
1673
+ sourceLabel: annotation.source.label?.trim() || (annotation.source.kind === "user_message" ? t("timelineAnnotationShared.yourMessage") : annotation.source.kind === "assistant_message" ? t("timelineAnnotationShared.assistant") : t("timelineAnnotationShared.toolOutput")),
1674
+ truncateAnnotationQuote: truncateAnnotationQuote(annotation.quote, 88)
1675
+ }
1676
+ ),
1677
+ onPress: () => setUnavailable(!onReveal(annotation.source, annotationQuoteQuery(annotation))),
1678
+ style: ({ pressed }) => ({ opacity: pressed ? 0.55 : 1, paddingVertical: 2 }),
1679
+ children: text
1680
+ }
1681
+ ),
1682
+ unavailable ? /* @__PURE__ */ jsx(
1683
+ Text,
1684
+ {
1685
+ accessibilityLiveRegion: "polite",
1686
+ style: {
1687
+ ...fontStyle(theme),
1688
+ fontSize: theme.size.xs,
1689
+ lineHeight: 16,
1690
+ color: theme.colors["status-waiting"]
1691
+ },
1692
+ children: t("timelineAnnotations.sourceIsOutsideTheLoaded")
1693
+ }
1694
+ ) : null
1695
+ ] });
1696
+ }
1697
+ function NotePreview({ note, ordinal }) {
1698
+ const t = useOpenGeniText();
1699
+ const theme = useNativeTimelineTheme();
1700
+ const [expanded, setExpanded] = useState(false);
1701
+ if (!note) return null;
1702
+ const collapsible = annotationNoteNeedsDisclosure(note);
1703
+ return /* @__PURE__ */ jsxs(View, { style: { marginTop: 2 }, children: [
1704
+ /* @__PURE__ */ jsx(
1705
+ Text,
1706
+ {
1707
+ selectable: true,
1708
+ numberOfLines: expanded ? void 0 : 4,
1709
+ style: {
1710
+ ...fontStyle(theme),
1711
+ fontSize: theme.size.sm,
1712
+ lineHeight: 20,
1713
+ color: theme.colors.fg
1714
+ },
1715
+ children: note
1716
+ }
1717
+ ),
1718
+ collapsible ? /* @__PURE__ */ jsx(
1719
+ Pressable,
1720
+ {
1721
+ accessibilityRole: "button",
1722
+ accessibilityLabel: expanded ? t("timelineAnnotationChrome.showLessAnnotationOrdinal", { ordinal }) : t("timelineAnnotationChrome.showMoreAnnotationOrdinal", { ordinal }),
1723
+ hitSlop: 8,
1724
+ onPress: () => setExpanded((current) => !current),
1725
+ style: { alignSelf: "flex-start", paddingVertical: 4 },
1726
+ children: /* @__PURE__ */ jsx(
1727
+ Text,
1728
+ {
1729
+ style: {
1730
+ ...fontStyle(theme, 500),
1731
+ fontSize: theme.size.xs,
1732
+ color: theme.colors["fg-muted"]
1733
+ },
1734
+ children: expanded ? t("timelineAnnotationChrome.showLess") : t("timelineAnnotationChrome.showMore")
1735
+ }
1736
+ )
1737
+ }
1738
+ ) : null
1739
+ ] });
1740
+ }
1741
+ function SentAnnotationCards({
1742
+ annotations,
1743
+ onReveal
1744
+ }) {
1745
+ if (annotations.length === 0) return null;
1746
+ return /* @__PURE__ */ jsx(View, { style: { gap: 10 }, children: annotations.map((annotation, index) => {
1747
+ const ordinal = annotationDisplayOrdinal(annotation, index);
1748
+ return /* @__PURE__ */ jsxs(AccentRow, { shrink: true, children: [
1749
+ /* @__PURE__ */ jsx(OrdinalLabel, { ordinal }),
1750
+ /* @__PURE__ */ jsx(QuoteButton, { annotation, onReveal }),
1751
+ /* @__PURE__ */ jsx(NotePreview, { note: annotation.note, ordinal })
1752
+ ] }, annotation.id);
1753
+ }) });
1754
+ }
1755
+ function AnnotationsChip({
1756
+ annotations,
1757
+ onPress,
1758
+ onClear,
1759
+ compact = false
1760
+ }) {
1761
+ const t = useOpenGeniText();
1762
+ const theme = useNativeTimelineTheme();
1763
+ const c = theme.colors;
1764
+ if (annotations.length === 0) return null;
1765
+ const countLabel = t("counts.annotations", { count: annotations.length });
1766
+ const incomplete = onClear !== void 0 && annotations.some((item) => !annotationHasNote(item.note));
1767
+ const height = compact ? 26 : 32;
1768
+ return /* @__PURE__ */ jsxs(
1769
+ View,
1770
+ {
1771
+ style: {
1772
+ flexDirection: "row",
1773
+ alignItems: "center",
1774
+ alignSelf: "flex-start",
1775
+ height,
1776
+ borderRadius: height / 2,
1777
+ borderWidth: 1,
1778
+ borderColor: incomplete ? withAlpha(c.accent, 0.45) : c.border,
1779
+ backgroundColor: incomplete ? c["accent-soft"] : c["surface-1"]
1780
+ },
1781
+ children: [
1782
+ /* @__PURE__ */ jsxs(
1783
+ Pressable,
1784
+ {
1785
+ accessibilityRole: "button",
1786
+ accessibilityLabel: t("timelineAnnotations.reviewCountlabel", { countLabel }),
1787
+ hitSlop: { top: (44 - height) / 2, bottom: (44 - height) / 2 },
1788
+ onPress,
1789
+ style: ({ pressed }) => ({
1790
+ flexDirection: "row",
1791
+ alignItems: "center",
1792
+ gap: 6,
1793
+ height,
1794
+ paddingLeft: compact ? 8 : 10,
1795
+ paddingRight: onClear ? 2 : compact ? 8 : 12,
1796
+ opacity: pressed ? 0.6 : 1
1797
+ }),
1798
+ children: [
1799
+ /* @__PURE__ */ jsx(Icon, { name: "quote", size: compact ? 11 : 13, color: c.accent }),
1800
+ /* @__PURE__ */ jsx(
1801
+ Text,
1802
+ {
1803
+ numberOfLines: 1,
1804
+ style: {
1805
+ ...fontStyle(theme, 500),
1806
+ fontSize: compact ? theme.size.xs : theme.size.sm,
1807
+ color: c["fg-muted"]
1808
+ },
1809
+ children: countLabel
1810
+ }
1811
+ )
1812
+ ]
1813
+ }
1814
+ ),
1815
+ onClear ? /* @__PURE__ */ jsx(
1816
+ Pressable,
1817
+ {
1818
+ accessibilityRole: "button",
1819
+ accessibilityLabel: t("timelineAnnotations.removeAllAnnotations"),
1820
+ hitSlop: { top: 6, bottom: 6, right: 6 },
1821
+ onPress: onClear,
1822
+ style: ({ pressed }) => ({
1823
+ width: height,
1824
+ height,
1825
+ alignItems: "center",
1826
+ justifyContent: "center",
1827
+ opacity: pressed ? 0.5 : 1
1828
+ }),
1829
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: 13, color: c["fg-subtle"] })
1830
+ }
1831
+ ) : null
1832
+ ]
1833
+ }
1834
+ );
1835
+ }
1836
+ function AnnotationsSheet({
1837
+ open,
1838
+ onClose,
1839
+ annotations,
1840
+ editable,
1841
+ focusId,
1842
+ onFocusConsumed,
1843
+ onUpdate,
1844
+ onRemove,
1845
+ onReveal,
1846
+ onCommit
1847
+ }) {
1848
+ const t = useOpenGeniText();
1849
+ const theme = useNativeTimelineTheme();
1850
+ const c = theme.colors;
1851
+ const inputs = useRef(/* @__PURE__ */ new Map());
1852
+ const scroll = useRef(null);
1853
+ const rowTops = useRef(/* @__PURE__ */ new Map());
1854
+ const empty = annotations.length === 0;
1855
+ useEffect(() => {
1856
+ if (open && empty) onClose();
1857
+ }, [empty, onClose, open]);
1858
+ const focusNote = (id) => {
1859
+ const top = rowTops.current.get(id);
1860
+ if (top !== void 0) scroll.current?.scrollTo({ y: Math.max(0, top - 8), animated: true });
1861
+ inputs.current.get(id)?.focus();
1862
+ };
1863
+ useEffect(() => {
1864
+ if (!open || !focusId || !editable) return;
1865
+ const timer = setTimeout(() => {
1866
+ focusNote(focusId);
1867
+ onFocusConsumed?.();
1868
+ }, 320);
1869
+ return () => clearTimeout(timer);
1870
+ }, [editable, focusId, open]);
1871
+ const commit = (fromId) => {
1872
+ const incomplete = annotations.filter((item) => !annotationHasNote(item.note));
1873
+ if (incomplete.length === 0) {
1874
+ onCommit?.();
1875
+ return;
1876
+ }
1877
+ const fromIndex = annotations.findIndex((item) => item.id === fromId);
1878
+ const next = incomplete.find((item) => annotations.indexOf(item) > fromIndex) ?? incomplete[0];
1879
+ focusNote(next.id);
1880
+ };
1881
+ const incompleteCount = annotations.filter((item) => !annotationHasNote(item.note)).length;
1882
+ const countLabel = t("counts.annotations", { count: annotations.length });
1883
+ return /* @__PURE__ */ jsxs(
1884
+ BottomSheet,
1885
+ {
1886
+ open: open && !empty,
1887
+ onClose,
1888
+ maxHeightRatio: 0.8,
1889
+ accessibilityLabel: editable ? t("timelineAnnotationsDialog.editQuotedNotes") : t("timelineAnnotationsDialog.quotedNotes"),
1890
+ children: [
1891
+ /* @__PURE__ */ jsxs(
1892
+ View,
1893
+ {
1894
+ style: {
1895
+ flexDirection: "row",
1896
+ alignItems: "center",
1897
+ justifyContent: "space-between",
1898
+ paddingLeft: 20,
1899
+ paddingRight: 8,
1900
+ paddingBottom: 4
1901
+ },
1902
+ children: [
1903
+ /* @__PURE__ */ jsx(
1904
+ Text,
1905
+ {
1906
+ accessibilityRole: "header",
1907
+ style: { ...fontStyle(theme, 600), fontSize: theme.size.md, color: c.fg },
1908
+ children: countLabel
1909
+ }
1910
+ ),
1911
+ /* @__PURE__ */ jsx(
1912
+ Pressable,
1913
+ {
1914
+ accessibilityRole: "button",
1915
+ onPress: () => {
1916
+ if (editable && incompleteCount === 0 && onCommit) onCommit();
1917
+ else onClose();
1918
+ },
1919
+ style: ({ pressed }) => ({
1920
+ height: 44,
1921
+ paddingHorizontal: 12,
1922
+ justifyContent: "center",
1923
+ opacity: pressed ? 0.5 : 1
1924
+ }),
1925
+ children: /* @__PURE__ */ jsx(Text, { style: { ...fontStyle(theme, 600), fontSize: theme.size.md, color: c.fg }, children: t("native.timeline.annotations.done") })
1926
+ }
1927
+ )
1928
+ ]
1929
+ }
1930
+ ),
1931
+ /* @__PURE__ */ jsxs(
1932
+ ScrollView,
1933
+ {
1934
+ ref: scroll,
1935
+ keyboardShouldPersistTaps: "handled",
1936
+ contentContainerStyle: { paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12, gap: 18 },
1937
+ style: { flexGrow: 0 },
1938
+ children: [
1939
+ annotations.map((annotation, index) => {
1940
+ const ordinal = annotationDisplayOrdinal(annotation, index);
1941
+ const last = index === annotations.length - 1;
1942
+ const othersIncomplete = annotations.some(
1943
+ (item) => item.id !== annotation.id && !annotationHasNote(item.note)
1944
+ );
1945
+ return /* @__PURE__ */ jsx(
1946
+ View,
1947
+ {
1948
+ onLayout: (event) => rowTops.current.set(annotation.id, event.nativeEvent.layout.y),
1949
+ children: /* @__PURE__ */ jsxs(AccentRow, { children: [
1950
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", alignItems: "flex-start", gap: 4 }, children: [
1951
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, minWidth: 0 }, children: [
1952
+ /* @__PURE__ */ jsx(OrdinalLabel, { ordinal }),
1953
+ /* @__PURE__ */ jsx(
1954
+ QuoteButton,
1955
+ {
1956
+ annotation,
1957
+ onReveal: onReveal ? (source, quote) => {
1958
+ const revealed = onReveal(source, quote);
1959
+ if (revealed) onClose();
1960
+ return revealed;
1961
+ } : void 0
1962
+ }
1963
+ )
1964
+ ] }),
1965
+ editable && onRemove ? /* @__PURE__ */ jsx(
1966
+ Pressable,
1967
+ {
1968
+ accessibilityRole: "button",
1969
+ accessibilityLabel: t("timelineAnnotationsDialog.removeAnnotationOrdinal", {
1970
+ ordinal
1971
+ }),
1972
+ onPress: () => onRemove(annotation.id),
1973
+ style: ({ pressed }) => ({
1974
+ width: 36,
1975
+ height: 36,
1976
+ marginTop: -6,
1977
+ marginRight: -10,
1978
+ alignItems: "center",
1979
+ justifyContent: "center",
1980
+ opacity: pressed ? 0.5 : 1
1981
+ }),
1982
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: 15, color: c["fg-subtle"] })
1983
+ }
1984
+ ) : null
1985
+ ] }),
1986
+ editable && onUpdate ? /* @__PURE__ */ jsx(
1987
+ TextInput,
1988
+ {
1989
+ ref: (node) => {
1990
+ if (node) inputs.current.set(annotation.id, node);
1991
+ else inputs.current.delete(annotation.id);
1992
+ },
1993
+ value: annotation.note,
1994
+ onChangeText: (note) => onUpdate(annotation.id, note),
1995
+ placeholder: t("timelineAnnotationChrome.addANote"),
1996
+ placeholderTextColor: c["fg-subtle"],
1997
+ accessibilityLabel: t("timelineAnnotationChrome.note"),
1998
+ maxLength: ANNOTATION_MAX_NOTE_LENGTH,
1999
+ multiline: true,
2000
+ submitBehavior: "submit",
2001
+ returnKeyType: othersIncomplete || last && !onCommit ? "next" : "done",
2002
+ onSubmitEditing: () => commit(annotation.id),
2003
+ style: {
2004
+ ...fontStyle(theme),
2005
+ fontSize: theme.size.md,
2006
+ lineHeight: 22,
2007
+ color: c.fg,
2008
+ marginTop: 6,
2009
+ minHeight: 40,
2010
+ maxHeight: 132,
2011
+ paddingHorizontal: 12,
2012
+ paddingTop: 9,
2013
+ paddingBottom: 9,
2014
+ borderRadius: theme.radius.md,
2015
+ backgroundColor: c["surface-2"],
2016
+ borderWidth: 1,
2017
+ borderColor: c.border
2018
+ }
2019
+ }
2020
+ ) : /* @__PURE__ */ jsx(NotePreview, { note: annotation.note, ordinal })
2021
+ ] })
2022
+ },
2023
+ annotation.id
2024
+ );
2025
+ }),
2026
+ editable && incompleteCount > 0 ? /* @__PURE__ */ jsx(
2027
+ Text,
2028
+ {
2029
+ style: {
2030
+ ...fontStyle(theme),
2031
+ fontSize: theme.size.xs,
2032
+ lineHeight: 16,
2033
+ color: c["fg-subtle"]
2034
+ },
2035
+ children: t("composerMessages.addANoteToEach")
2036
+ }
2037
+ ) : null
2038
+ ]
2039
+ }
2040
+ )
2041
+ ]
2042
+ }
2043
+ );
2044
+ }
1173
2045
  var OLDER_HISTORY_PREFETCH_PX = 600;
1174
2046
  var USER_MESSAGE_COLLAPSED_PX = 224;
1175
2047
  var ROW_GAP_PX = 20;
2048
+ function annotationSources(items) {
2049
+ const sources = /* @__PURE__ */ new Map();
2050
+ for (const item of items) {
2051
+ if (item.kind !== "user-message" && item.kind !== "agent-message") continue;
2052
+ const source = item.annotationSource;
2053
+ if (source) sources.set(source.eventId, source);
2054
+ }
2055
+ return sources;
2056
+ }
1176
2057
  function MessageTimeline(props) {
1177
2058
  const theme = useNativeTimelineTheme();
1178
2059
  const m = useNativeTimelineMessages();
@@ -1182,6 +2063,8 @@ function MessageTimeline(props) {
1182
2063
  [t, props.items, props.events]
1183
2064
  );
1184
2065
  const groups = useMemo(() => groupTimeline(t, items, { readableTurns: true }), [t, items]);
2066
+ const sources = useMemo(() => annotationSources(items), [items]);
2067
+ useNativeTextAnnotation(sources, props.onAnnotate);
1185
2068
  const foldMemory = useRef(/* @__PURE__ */ new Map()).current;
1186
2069
  const scrollRef = useRef(null);
1187
2070
  const following = useRef(!props.focusSequence);
@@ -1191,6 +2074,72 @@ function MessageTimeline(props) {
1191
2074
  const [questionNav, setQuestionNav] = useState(null);
1192
2075
  const stickyHeaders = useRef(/* @__PURE__ */ new Map()).current;
1193
2076
  const scrollY = useRef(0);
2077
+ const sourceNodes = useRef(/* @__PURE__ */ new Map()).current;
2078
+ const [flash, setFlash] = useState(null);
2079
+ const overlayInsetBottom = useRef(0);
2080
+ overlayInsetBottom.current = props.overlayInsetBottom ?? 0;
2081
+ const registerSource = useCallback(
2082
+ (sourceKey, node) => {
2083
+ if (node) sourceNodes.set(sourceKey, node);
2084
+ else sourceNodes.delete(sourceKey);
2085
+ },
2086
+ [sourceNodes]
2087
+ );
2088
+ const revealSource = useCallback(
2089
+ (source, quote) => {
2090
+ const node = sourceNodes.get(source.eventId);
2091
+ const scroller = scrollRef.current?.getNativeScrollRef?.();
2092
+ if (!node || !scroller) return false;
2093
+ following.current = false;
2094
+ const keyboardGone = Keyboard.isVisible() ? new Promise((resolve) => {
2095
+ const done = () => {
2096
+ subscription.remove();
2097
+ clearTimeout(timer);
2098
+ setTimeout(resolve, 60);
2099
+ };
2100
+ const subscription = Keyboard.addListener("keyboardDidHide", done);
2101
+ const timer = setTimeout(done, 600);
2102
+ Keyboard.dismiss();
2103
+ }) : Promise.resolve();
2104
+ const located = quote ? measureQuoteAnchors(source.eventId, [quote]).then(([anchor]) => anchor ?? null) : Promise.resolve(null);
2105
+ void Promise.all([located, keyboardGone]).then(([anchor]) => {
2106
+ scroller.measureInWindow((_scrollX, scrollTop) => {
2107
+ node.measureInWindow((_x, top, _width, height) => {
2108
+ const y = scrollY.current + top - scrollTop;
2109
+ const visible = (viewportHeight.current || 600) - overlayInsetBottom.current;
2110
+ const first = anchor?.boxes[0];
2111
+ const last = anchor?.boxes[anchor.boxes.length - 1];
2112
+ const span = height < visible - 32 || !first || !last ? { top: y, height } : { top: y + first.y, height: last.y + last.height - first.y };
2113
+ const target = span.height < visible - 32 ? span.top - (visible - span.height) / 2 : span.top - 16;
2114
+ const end = Math.max(0, contentHeight.current - (viewportHeight.current || 0));
2115
+ scrollY.current = Math.min(Math.max(0, target), end || Number.POSITIVE_INFINITY);
2116
+ scrollRef.current?.scrollTo({ y: scrollY.current, animated: true });
2117
+ setFlash((current) => ({
2118
+ sourceKey: source.eventId,
2119
+ token: (current?.token ?? 0) + 1,
2120
+ ...quote ? { quote } : {}
2121
+ }));
2122
+ });
2123
+ });
2124
+ });
2125
+ return true;
2126
+ },
2127
+ [sourceNodes]
2128
+ );
2129
+ if (props.annotationRevealRef) props.annotationRevealRef.current = revealSource;
2130
+ const draftMarkers = useMemo(
2131
+ () => draftMarkersBySource(props.draftAnnotations),
2132
+ [props.draftAnnotations]
2133
+ );
2134
+ const annotationSourcesValue = useMemo(
2135
+ () => ({
2136
+ register: registerSource,
2137
+ flash,
2138
+ drafts: draftMarkers,
2139
+ onSelectDraft: props.onDraftAnnotationSelect
2140
+ }),
2141
+ [draftMarkers, flash, props.onDraftAnnotationSelect, registerSource]
2142
+ );
1194
2143
  const [pinned, setPinned] = useState(
1195
2144
  null
1196
2145
  );
@@ -1338,6 +2287,13 @@ function MessageTimeline(props) {
1338
2287
  },
1339
2288
  [requestOlder]
1340
2289
  );
2290
+ const followLatestSignal = props.followLatestSignal ?? 0;
2291
+ useEffect(() => {
2292
+ if (!followLatestSignal) return;
2293
+ following.current = true;
2294
+ setShowJump(false);
2295
+ scrollRef.current?.scrollToEnd({ animated: true });
2296
+ }, [followLatestSignal]);
1341
2297
  const groupTops = useRef(/* @__PURE__ */ new Map()).current;
1342
2298
  const landed = useRef(null);
1343
2299
  const [layoutPass, setLayoutPass] = useState(0);
@@ -1387,9 +2343,10 @@ function MessageTimeline(props) {
1387
2343
  renderMessageActions: props.renderMessageActions,
1388
2344
  renderUserAttachments: props.renderUserAttachments,
1389
2345
  onCopy: props.onCopy,
1390
- reportLiveFold
2346
+ reportLiveFold,
2347
+ onRevealAnnotation: revealSource
1391
2348
  };
1392
- return /* @__PURE__ */ jsx(NativeActivityOptionsProvider, { value: activityOptions, children: /* @__PURE__ */ jsx(FoldMemoryProvider, { value: foldMemory, children: /* @__PURE__ */ jsx(StickyWorkHeaderProvider, { value: registerSticky, children: /* @__PURE__ */ jsx(ReleaseFollowProvider, { value: releaseFollow, children: /* @__PURE__ */ jsxs(View, { style: [{ flex: 1, backgroundColor: theme.colors.bg }, props.style], children: [
2349
+ return /* @__PURE__ */ jsx(AnnotationSourcesContext.Provider, { value: annotationSourcesValue, children: /* @__PURE__ */ jsx(NativeActivityOptionsProvider, { value: activityOptions, children: /* @__PURE__ */ jsx(FoldMemoryProvider, { value: foldMemory, children: /* @__PURE__ */ jsx(StickyWorkHeaderProvider, { value: registerSticky, children: /* @__PURE__ */ jsx(ReleaseFollowProvider, { value: releaseFollow, children: /* @__PURE__ */ jsxs(View, { style: [{ flex: 1, backgroundColor: theme.colors.bg }, props.style], children: [
1393
2350
  /* @__PURE__ */ jsxs(
1394
2351
  ScrollView,
1395
2352
  {
@@ -1418,7 +2375,13 @@ function MessageTimeline(props) {
1418
2375
  },
1419
2376
  children: [
1420
2377
  /* @__PURE__ */ jsxs(View, { children: [
1421
- props.hasOlder ? /* @__PURE__ */ jsx(OlderHistoryRow, { loading: props.loadingOlder === true, onPress: requestOlder }) : null,
2378
+ props.hasOlder ? /* @__PURE__ */ jsx(
2379
+ OlderHistoryRow,
2380
+ {
2381
+ loading: props.loadingOlder === true,
2382
+ onPress: requestOlder
2383
+ }
2384
+ ) : null,
1422
2385
  props.header
1423
2386
  ] }),
1424
2387
  groups.length === 0 && props.emptyState ? props.emptyState : null,
@@ -1522,7 +2485,7 @@ function MessageTimeline(props) {
1522
2485
  )
1523
2486
  }
1524
2487
  ) : null
1525
- ] }) }) }) }) });
2488
+ ] }) }) }) }) }) });
1526
2489
  }
1527
2490
  function NavButton(props) {
1528
2491
  const theme = useNativeTimelineTheme();
@@ -2013,9 +2976,11 @@ function UserMessageRow({ item, context }) {
2013
2976
  const theme = useNativeTimelineTheme();
2014
2977
  const m = useNativeTimelineMessages();
2015
2978
  const failed = item.delivery?.state === "failed";
2979
+ const annotations = item.annotations ?? [];
2980
+ const sourceKey = item.annotationSource?.eventId;
2016
2981
  return /* @__PURE__ */ jsx(Animated.View, { entering: FadeIn.duration(180), style: { alignItems: "flex-end" }, children: /* @__PURE__ */ jsxs(View, { style: { maxWidth: "85%", alignItems: "flex-end", gap: 6 }, children: [
2017
2982
  item.resources.some((resource) => resource.kind === "file") ? context.renderUserAttachments?.(item) : null,
2018
- item.text ? /* @__PURE__ */ jsx(
2983
+ item.text || annotations.length > 0 ? /* @__PURE__ */ jsxs(
2019
2984
  View,
2020
2985
  {
2021
2986
  style: {
@@ -2029,19 +2994,35 @@ function UserMessageRow({ item, context }) {
2029
2994
  paddingHorizontal: 16,
2030
2995
  paddingVertical: 10
2031
2996
  },
2032
- children: /* @__PURE__ */ jsx(CollapsibleUserText, { children: context.renderMarkdown ? context.renderMarkdown(item.text, { tone: "body" }) : /* @__PURE__ */ jsx(
2033
- Text,
2034
- {
2035
- selectable: true,
2036
- style: {
2037
- ...fontStyle(theme),
2038
- fontSize: theme.size.md,
2039
- lineHeight: 28,
2040
- color: theme.colors.fg
2041
- },
2042
- children: item.text
2043
- }
2044
- ) })
2997
+ children: [
2998
+ item.text ? /* @__PURE__ */ jsx(CollapsibleUserText, { children: /* @__PURE__ */ jsx(
2999
+ AnnotationSourceView,
3000
+ {
3001
+ sourceKey,
3002
+ style: { marginHorizontal: -6, paddingHorizontal: 6 },
3003
+ children: context.renderMarkdown ? context.renderMarkdown(item.text, { tone: "body" }) : /* @__PURE__ */ jsx(
3004
+ Text,
3005
+ {
3006
+ selectable: true,
3007
+ style: {
3008
+ ...fontStyle(theme),
3009
+ fontSize: theme.size.md,
3010
+ lineHeight: 28,
3011
+ color: theme.colors.fg
3012
+ },
3013
+ children: item.text
3014
+ }
3015
+ )
3016
+ }
3017
+ ) }) : null,
3018
+ annotations.length > 0 ? /* @__PURE__ */ jsx(View, { style: item.text ? { marginTop: 10, marginBottom: 2 } : { paddingVertical: 2 }, children: /* @__PURE__ */ jsx(
3019
+ SentAnnotationCards,
3020
+ {
3021
+ annotations,
3022
+ onReveal: context.onRevealAnnotation
3023
+ }
3024
+ ) }) : null
3025
+ ]
2045
3026
  }
2046
3027
  ) : null,
2047
3028
  /* @__PURE__ */ jsx(
@@ -2091,17 +3072,24 @@ function UserMessageRow({ item, context }) {
2091
3072
  function AgentMessageRow({ item, context }) {
2092
3073
  const theme = useNativeTimelineTheme();
2093
3074
  return /* @__PURE__ */ jsxs(Animated.View, { entering: FadeIn.duration(180), style: { minWidth: 0 }, children: [
2094
- context.renderMarkdown ? context.renderMarkdown(item.text, { tone: "body", streaming: item.streaming }) : /* @__PURE__ */ jsx(
2095
- Text,
3075
+ /* @__PURE__ */ jsx(
3076
+ AnnotationSourceView,
2096
3077
  {
2097
- selectable: true,
2098
- style: {
2099
- ...fontStyle(theme),
2100
- fontSize: theme.size.md,
2101
- lineHeight: 28,
2102
- color: theme.colors.fg
2103
- },
2104
- children: item.text
3078
+ sourceKey: item.annotationSource?.eventId,
3079
+ style: { marginHorizontal: -8, paddingHorizontal: 8 },
3080
+ children: context.renderMarkdown ? context.renderMarkdown(item.text, { tone: "body", streaming: item.streaming }) : /* @__PURE__ */ jsx(
3081
+ Text,
3082
+ {
3083
+ selectable: true,
3084
+ style: {
3085
+ ...fontStyle(theme),
3086
+ fontSize: theme.size.md,
3087
+ lineHeight: 28,
3088
+ color: theme.colors.fg
3089
+ },
3090
+ children: item.text
3091
+ }
3092
+ )
2105
3093
  }
2106
3094
  ),
2107
3095
  item.streaming ? null : /* @__PURE__ */ jsx(
@@ -4333,7 +5321,7 @@ function StatusDot({
4333
5321
  return;
4334
5322
  }
4335
5323
  opacity.value = withRepeat(
4336
- withTiming(0.5, { duration: 1e3, easing: Easing.bezier(0.4, 0, 0.6, 1) }),
5324
+ withTiming(0.5, { duration: 1e3, easing: Easing$1.bezier(0.4, 0, 0.6, 1) }),
4337
5325
  -1,
4338
5326
  true
4339
5327
  );
@@ -4603,145 +5591,6 @@ function SessionHeaderTitle({
4603
5591
  }
4604
5592
  );
4605
5593
  }
4606
- function BottomSheet(props) {
4607
- const t = useOpenGeniText();
4608
- const theme = useNativeTimelineTheme();
4609
- const c = theme.colors;
4610
- const insets = useSafeAreaInsets();
4611
- const { height } = useWindowDimensions();
4612
- const [mounted, setMounted] = useState(props.open);
4613
- const progress = useRef(new Animated$1.Value(0)).current;
4614
- const drag = useRef(new Animated$1.Value(0)).current;
4615
- const onClose = useRef(props.onClose);
4616
- onClose.current = props.onClose;
4617
- useEffect(() => {
4618
- let cancelled = false;
4619
- if (props.open) {
4620
- setMounted(true);
4621
- drag.setValue(0);
4622
- void AccessibilityInfo.isReduceMotionEnabled().then((reduce) => {
4623
- if (cancelled) return;
4624
- Animated$1.timing(progress, {
4625
- toValue: 1,
4626
- duration: reduce ? 0 : 260,
4627
- easing: Easing$1.bezier(0.22, 1, 0.36, 1),
4628
- useNativeDriver: true
4629
- }).start();
4630
- });
4631
- } else if (mounted) {
4632
- Animated$1.timing(progress, {
4633
- toValue: 0,
4634
- duration: 200,
4635
- easing: Easing$1.in(Easing$1.quad),
4636
- useNativeDriver: true
4637
- }).start(({ finished }) => {
4638
- if (finished && !cancelled) setMounted(false);
4639
- });
4640
- }
4641
- return () => {
4642
- cancelled = true;
4643
- };
4644
- }, [drag, mounted, progress, props.open]);
4645
- const pan = useRef(
4646
- PanResponder.create({
4647
- onMoveShouldSetPanResponder: (_event, gesture) => gesture.dy > 6 && Math.abs(gesture.dy) > Math.abs(gesture.dx),
4648
- onPanResponderMove: (_event, gesture) => drag.setValue(Math.max(0, gesture.dy)),
4649
- onPanResponderRelease: (_event, gesture) => {
4650
- if (gesture.dy > 90 || gesture.vy > 0.9) onClose.current();
4651
- else Animated$1.spring(drag, { toValue: 0, useNativeDriver: true, bounciness: 0 }).start();
4652
- },
4653
- onPanResponderTerminate: () => Animated$1.spring(drag, { toValue: 0, useNativeDriver: true, bounciness: 0 }).start()
4654
- })
4655
- ).current;
4656
- if (!mounted) return null;
4657
- const translateY = Animated$1.add(
4658
- progress.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }),
4659
- drag
4660
- );
4661
- return /* @__PURE__ */ jsx(
4662
- Modal,
4663
- {
4664
- visible: true,
4665
- transparent: true,
4666
- animationType: "none",
4667
- statusBarTranslucent: true,
4668
- onRequestClose: props.onClose,
4669
- children: /* @__PURE__ */ jsxs(
4670
- KeyboardAvoidingView,
4671
- {
4672
- behavior: Platform.OS === "ios" ? "padding" : void 0,
4673
- style: { flex: 1, justifyContent: "flex-end" },
4674
- children: [
4675
- /* @__PURE__ */ jsx(
4676
- Animated$1.View,
4677
- {
4678
- style: {
4679
- position: "absolute",
4680
- top: 0,
4681
- right: 0,
4682
- bottom: 0,
4683
- left: 0,
4684
- backgroundColor: "rgba(0, 0, 0, 0.4)",
4685
- opacity: progress
4686
- },
4687
- children: /* @__PURE__ */ jsx(
4688
- Pressable,
4689
- {
4690
- style: { flex: 1 },
4691
- onPress: props.onClose,
4692
- accessibilityRole: "button",
4693
- accessibilityLabel: t("native.timeline.sheet.close")
4694
- }
4695
- )
4696
- }
4697
- ),
4698
- /* @__PURE__ */ jsxs(
4699
- Animated$1.View,
4700
- {
4701
- accessibilityViewIsModal: true,
4702
- accessibilityLabel: props.accessibilityLabel,
4703
- style: {
4704
- maxHeight: height * (props.maxHeightRatio ?? 0.85),
4705
- backgroundColor: c.bg,
4706
- borderTopLeftRadius: 16,
4707
- borderTopRightRadius: 16,
4708
- paddingBottom: Math.max(insets.bottom, 12),
4709
- transform: [{ translateY }],
4710
- shadowColor: "#000",
4711
- shadowOpacity: 0.12,
4712
- shadowRadius: 16,
4713
- shadowOffset: { width: 0, height: -2 },
4714
- elevation: 16
4715
- },
4716
- children: [
4717
- /* @__PURE__ */ jsx(
4718
- View,
4719
- {
4720
- ...pan.panHandlers,
4721
- style: { alignItems: "center", paddingTop: 8, paddingBottom: 4 },
4722
- children: /* @__PURE__ */ jsx(
4723
- View,
4724
- {
4725
- style: {
4726
- width: 36,
4727
- height: 5,
4728
- borderRadius: 3,
4729
- backgroundColor: c["border-strong"] ?? c.border
4730
- }
4731
- }
4732
- )
4733
- }
4734
- ),
4735
- props.children
4736
- ]
4737
- }
4738
- )
4739
- ]
4740
- }
4741
- )
4742
- }
4743
- );
4744
- }
4745
5594
  var CompactSignalsContext = createContext(false);
4746
5595
  function SessionSignals(props) {
4747
5596
  const m = useNativeTimelineMessages();
@@ -5004,6 +5853,7 @@ function QueueDock({
5004
5853
  queue,
5005
5854
  composer,
5006
5855
  onComposerFocus,
5856
+ onReviewAnnotations,
5007
5857
  leading,
5008
5858
  singleSend = false
5009
5859
  }) {
@@ -5163,6 +6013,42 @@ function QueueDock({
5163
6013
  children: label
5164
6014
  }
5165
6015
  ),
6016
+ annotationCount > 0 && onReviewAnnotations ? /* @__PURE__ */ jsxs(
6017
+ Pressable,
6018
+ {
6019
+ accessibilityRole: "button",
6020
+ accessibilityLabel: t("timelineAnnotations.reviewCountlabel", {
6021
+ countLabel: t("counts.annotations", { count: annotationCount })
6022
+ }),
6023
+ hitSlop: 8,
6024
+ onPress: () => onReviewAnnotations(turn.annotations ?? []),
6025
+ style: ({ pressed }) => ({
6026
+ flexDirection: "row",
6027
+ alignItems: "center",
6028
+ gap: 3,
6029
+ height: 22,
6030
+ paddingHorizontal: 7,
6031
+ borderRadius: 11,
6032
+ backgroundColor: c["accent-soft"],
6033
+ opacity: pressed ? 0.6 : 1
6034
+ }),
6035
+ children: [
6036
+ /* @__PURE__ */ jsx(Icon, { name: "quote", size: 10, color: c["fg-muted"] }),
6037
+ /* @__PURE__ */ jsx(
6038
+ Text,
6039
+ {
6040
+ style: {
6041
+ ...fontStyle(theme, 500),
6042
+ fontSize: 11,
6043
+ color: c["fg-muted"],
6044
+ fontVariant: ["tabular-nums"]
6045
+ },
6046
+ children: annotationCount
6047
+ }
6048
+ )
6049
+ ]
6050
+ }
6051
+ ) : null,
5166
6052
  turns.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
5167
6053
  /* @__PURE__ */ jsx(
5168
6054
  RowAction,
@@ -5364,9 +6250,27 @@ function SessionScreenBody({
5364
6250
  ...timelineProps
5365
6251
  }) {
5366
6252
  const theme = useNativeTimelineTheme();
6253
+ const t = useOpenGeniText();
5367
6254
  const m = useNativeTimelineMessages();
5368
6255
  const { composer, queue, humanInput, approvals, control, attachments } = controller;
5369
6256
  const composerInput = useRef(null);
6257
+ const annotationReveal = useRef(null);
6258
+ const draftAnnotations = composer.annotations ?? [];
6259
+ const annotationsIncomplete = draftAnnotations.some(
6260
+ (annotation) => annotation.note.trim().length === 0
6261
+ );
6262
+ const reviewTargetId = composer.annotationReviewTargetId ?? null;
6263
+ const [reviewOpen, setReviewOpen] = useState(false);
6264
+ useEffect(() => {
6265
+ if (reviewTargetId) setReviewOpen(true);
6266
+ }, [reviewTargetId]);
6267
+ const closeReview = useCallback(() => {
6268
+ setReviewOpen(false);
6269
+ composer.clearAnnotationReviewTarget?.();
6270
+ }, [composer]);
6271
+ const [queuedReview, setQueuedReview] = useState(null);
6272
+ const [followLatestSignal, setFollowLatestSignal] = useState(0);
6273
+ const followLatest = useCallback(() => setFollowLatestSignal((value) => value + 1), []);
5370
6274
  const [composerHeight, setComposerHeight] = useState(120);
5371
6275
  const items = useMemo(
5372
6276
  () => conversationTimeline(controller.timeline, queue, composer),
@@ -5409,6 +6313,11 @@ function SessionScreenBody({
5409
6313
  /* @__PURE__ */ jsx(
5410
6314
  MessageTimeline,
5411
6315
  {
6316
+ onAnnotate: composer.addAnnotation,
6317
+ draftAnnotations: composer.annotations,
6318
+ onDraftAnnotationSelect: composer.requestAnnotationReview,
6319
+ annotationRevealRef: annotationReveal,
6320
+ followLatestSignal,
5412
6321
  ...timelineProps,
5413
6322
  hasOlder: controller.hasOlder,
5414
6323
  loadingOlder: controller.loadingOlder,
@@ -5467,12 +6376,29 @@ function SessionScreenBody({
5467
6376
  onActionFeedback: composerSlots?.onActionFeedback,
5468
6377
  value: composer.value,
5469
6378
  onChangeText: composer.setValue,
5470
- onSend: () => void composer.send(),
6379
+ // A quote without its note cannot be sent: Send opens that note instead
6380
+ // of staying disabled with no reason given.
6381
+ onSend: () => {
6382
+ if (annotationsIncomplete) composer.requestAnnotationReview?.();
6383
+ else {
6384
+ followLatest();
6385
+ void composer.send();
6386
+ }
6387
+ },
5471
6388
  // The other choice beside Send while the agent works: Queue when
5472
6389
  // Send steers, Steer when Send queues.
5473
6390
  alternateSend: composer.alternateSend ?? null,
5474
- ...composer.sendAlternate ? { onAlternateSend: () => void composer.sendAlternate?.() } : {},
5475
- canSend: composer.canSend && !busy,
6391
+ ...composer.sendAlternate ? {
6392
+ onAlternateSend: () => {
6393
+ if (annotationsIncomplete) {
6394
+ composer.requestAnnotationReview?.();
6395
+ return;
6396
+ }
6397
+ followLatest();
6398
+ void composer.sendAlternate?.();
6399
+ }
6400
+ } : {},
6401
+ canSend: (composer.canSend || annotationsIncomplete && !composer.sending) && !busy,
5476
6402
  sending: composer.sending,
5477
6403
  running: controller.runActive,
5478
6404
  paused,
@@ -5499,7 +6425,41 @@ function SessionScreenBody({
5499
6425
  ) : null,
5500
6426
  header: /* @__PURE__ */ jsxs(Fragment, { children: [
5501
6427
  composerSlots?.header,
5502
- /* @__PURE__ */ jsx(AttachmentChips, { attachments })
6428
+ /* @__PURE__ */ jsx(AttachmentChips, { attachments }),
6429
+ draftAnnotations.length > 0 && composer.removeAnnotation ? /* @__PURE__ */ jsx(View, { style: { paddingHorizontal: 12, paddingTop: 10 }, children: /* @__PURE__ */ jsx(
6430
+ AnnotationsChip,
6431
+ {
6432
+ annotations: draftAnnotations,
6433
+ onPress: () => composer.requestAnnotationReview?.(),
6434
+ onClear: () => {
6435
+ const removeAll = () => {
6436
+ for (const annotation of draftAnnotations) {
6437
+ composer.removeAnnotation?.(annotation.id);
6438
+ }
6439
+ };
6440
+ if (!draftAnnotations.some((item) => annotationHasNote(item.note))) {
6441
+ removeAll();
6442
+ return;
6443
+ }
6444
+ Alert.alert(
6445
+ t("native.timeline.annotations.removeAllTitle", {
6446
+ count: draftAnnotations.length
6447
+ }),
6448
+ t("native.timeline.annotations.removeAllMessage", {
6449
+ count: draftAnnotations.filter((item) => annotationHasNote(item.note)).length
6450
+ }),
6451
+ [
6452
+ { text: t("native.timeline.annotations.cancel"), style: "cancel" },
6453
+ {
6454
+ text: t("native.timeline.annotations.removeAllConfirm"),
6455
+ style: "destructive",
6456
+ onPress: removeAll
6457
+ }
6458
+ ]
6459
+ );
6460
+ }
6461
+ }
6462
+ ) }) : null
5503
6463
  ] }),
5504
6464
  above: /* @__PURE__ */ jsxs(Fragment, { children: [
5505
6465
  /* @__PURE__ */ jsx(
@@ -5509,6 +6469,7 @@ function SessionScreenBody({
5509
6469
  composer,
5510
6470
  singleSend: composer.singleSend === true,
5511
6471
  onComposerFocus: () => composerInput.current?.focus(),
6472
+ onReviewAnnotations: setQueuedReview,
5512
6473
  leading: /* @__PURE__ */ jsx(
5513
6474
  SessionSignals,
5514
6475
  {
@@ -5541,7 +6502,35 @@ function SessionScreenBody({
5541
6502
  }
5542
6503
  ) : null
5543
6504
  ] })
5544
- })
6505
+ }),
6506
+ /* @__PURE__ */ jsx(
6507
+ AnnotationsSheet,
6508
+ {
6509
+ open: reviewOpen,
6510
+ onClose: closeReview,
6511
+ annotations: draftAnnotations,
6512
+ editable: Boolean(composer.updateAnnotation),
6513
+ focusId: reviewTargetId,
6514
+ onFocusConsumed: composer.clearAnnotationReviewTarget,
6515
+ onUpdate: composer.updateAnnotation,
6516
+ onRemove: composer.removeAnnotation,
6517
+ onReveal: (source, quote) => annotationReveal.current?.(source, quote) ?? false,
6518
+ onCommit: () => {
6519
+ closeReview();
6520
+ setTimeout(() => composerInput.current?.focus(), 250);
6521
+ }
6522
+ }
6523
+ ),
6524
+ /* @__PURE__ */ jsx(
6525
+ AnnotationsSheet,
6526
+ {
6527
+ open: queuedReview !== null,
6528
+ onClose: () => setQueuedReview(null),
6529
+ annotations: queuedReview ?? [],
6530
+ editable: false,
6531
+ onReveal: (source, quote) => annotationReveal.current?.(source, quote) ?? false
6532
+ }
6533
+ )
5545
6534
  ] });
5546
6535
  }
5547
6536
  function useAutoRecover(failed, live, refresh) {
@@ -7527,6 +8516,6 @@ function NativeInboxList({
7527
8516
  ] });
7528
8517
  }
7529
8518
 
7530
- export { ActivityRail, ActivityRow, ApprovalStrip, AttachmentChips, BottomSheet, Button, ComposerPill, FeedbackSheet, HumanInputCard, IconButton, MessageTimeline, ModelMark, ModelPickerSheet, NativeInboxList, NativeMessageAttachments, NativeSessionScreen, PreparingState, PresentedToolRow, QueueDock, RollingActivity, SectionLabel, SessionActionsButton, SessionCommandsList, SessionComposer, SessionHeaderTitle, SessionRow, SessionRowList, SessionSignals, SessionStatusBadge, StatusDot, TurnFeedbackButtons, TurnRailFrame, TurnSummary, defaultApprovalStripMessages, isNeedsYouKind, nativeInboxAttentionCount, useNativeInbox, useTurnRatings };
8519
+ export { ActivityRail, ActivityRow, AnnotationsChip, AnnotationsSheet, ApprovalStrip, AttachmentChips, BottomSheet, Button, ComposerPill, FeedbackSheet, HumanInputCard, IconButton, MessageTimeline, ModelMark, ModelPickerSheet, NativeInboxList, NativeMessageAttachments, NativeSessionScreen, PreparingState, PresentedToolRow, QueueDock, RollingActivity, SectionLabel, SessionActionsButton, SessionCommandsList, SessionComposer, SessionHeaderTitle, SessionRow, SessionRowList, SessionSignals, SessionStatusBadge, StatusDot, TurnFeedbackButtons, TurnRailFrame, TurnSummary, defaultApprovalStripMessages, isNeedsYouKind, nativeInboxAttentionCount, useNativeInbox, useTurnRatings };
7531
8520
  //# sourceMappingURL=index.js.map
7532
8521
  //# sourceMappingURL=index.js.map