@tangle-network/agent-app 0.45.50 → 0.45.52

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.
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-YJMCRXQQ.js";
41
41
 
42
42
  // src/web-react/index.tsx
43
- import { useEffect as useEffect12, useMemo as useMemo7, useRef as useRef12, useState as useState15, memo } from "react";
43
+ import { useEffect as useEffect12, useId as useId4, useMemo as useMemo7, useRef as useRef12, useState as useState16, memo } from "react";
44
44
  import { InlineToolItem } from "@tangle-network/ui/run";
45
45
 
46
46
  // src/web-react/smooth-text.ts
@@ -83,6 +83,22 @@ function useSmoothText(target, enabled, opts) {
83
83
  return target.slice(0, Math.floor(shownRef.current));
84
84
  }
85
85
 
86
+ // src/web-react/inert.ts
87
+ function inertProps(inert) {
88
+ if (!inert) return {};
89
+ return { inert: "1" };
90
+ }
91
+
92
+ // src/web-react/motion.ts
93
+ import { useState as useState2 } from "react";
94
+ function staggerStyle(index, base) {
95
+ return { ...base, "--stagger-index": index };
96
+ }
97
+ function useArrivalStyle(index) {
98
+ const [frozen] = useState2(index);
99
+ return staggerStyle(frozen);
100
+ }
101
+
86
102
  // src/web-react/brand-mark.tsx
87
103
  import { lazy, Suspense } from "react";
88
104
  import { jsx } from "react/jsx-runtime";
@@ -102,10 +118,10 @@ function BrandMark({ size = 24, className }) {
102
118
  }
103
119
 
104
120
  // src/web-react/durable-plan-card.tsx
105
- import { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
121
+ import { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
106
122
 
107
123
  // src/web-react/interaction-question-card.tsx
108
- import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
124
+ import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState3 } from "react";
109
125
 
110
126
  // src/web-react/interaction-card-support.ts
111
127
  function interactionStatusLabels(labels) {
@@ -313,7 +329,8 @@ function QuestionOptionList({
313
329
  "label",
314
330
  {
315
331
  htmlFor: inputId,
316
- className: `flex gap-2 rounded-lg border p-3 transition-colors ${highlighted ? "border-primary bg-primary/5" : "border-strong"} ${disabled ? "cursor-default" : "cursor-pointer hover:bg-accent"}`,
332
+ style: staggerStyle(optionIndex),
333
+ className: `agent-arrive flex gap-2 rounded-lg border p-3 transition-colors ${highlighted ? "border-primary bg-primary/5" : "border-strong"} ${disabled ? "cursor-default" : "cursor-pointer hover:bg-accent"}`,
317
334
  children: [
318
335
  /* @__PURE__ */ jsx2(
319
336
  "input",
@@ -379,13 +396,13 @@ function InteractionQuestionCard({
379
396
  renderMarkdown,
380
397
  className
381
398
  }) {
382
- const [values, setValues] = useState2(() => fieldValuesFromAnswers(interaction.fields, interaction.answers));
383
- const [submitting, setSubmitting] = useState2(false);
384
- const [localStatus, setLocalStatus] = useState2(null);
385
- const [lateAnswerSent, setLateAnswerSent] = useState2(false);
386
- const [error, setError] = useState2(null);
399
+ const [values, setValues] = useState3(() => fieldValuesFromAnswers(interaction.fields, interaction.answers));
400
+ const [submitting, setSubmitting] = useState3(false);
401
+ const [localStatus, setLocalStatus] = useState3(null);
402
+ const [lateAnswerSent, setLateAnswerSent] = useState3(false);
403
+ const [error, setError] = useState3(null);
387
404
  const submitInFlightRef = useRef2(false);
388
- const [askId, setAskId] = useState2(interaction.id);
405
+ const [askId, setAskId] = useState3(interaction.id);
389
406
  if (askId !== interaction.id) {
390
407
  setAskId(interaction.id);
391
408
  setValues(fieldValuesFromAnswers(interaction.fields, interaction.answers));
@@ -471,10 +488,17 @@ function InteractionQuestionCard({
471
488
  submitLabel = "Submitting\u2026";
472
489
  }
473
490
  return (
491
+ // The card LANDS. This is the moment the run stopped and handed the turn
492
+ // back — a surface that blinks into place reads as chrome that was always
493
+ // there, which is exactly the wrong reading for the one thing on screen
494
+ // waiting on the reader. `.agent-arrive` runs once on mount; a status
495
+ // change (answered, expired, a failed submit) re-renders the same DOM node
496
+ // and therefore does NOT replay it.
497
+ //
474
498
  // `dark:[color-scheme:dark]` keeps the native radios/checkboxes on the
475
499
  // dark control scheme — without it they paint light-scheme white on the
476
500
  // dark card.
477
- /* @__PURE__ */ jsxs("div", { className: `rounded-xl border border-card-edge bg-card p-4 dark:[color-scheme:dark] ${className ?? ""}`, children: [
501
+ /* @__PURE__ */ jsxs("div", { className: `agent-arrive rounded-xl border border-card-edge bg-card p-4 dark:[color-scheme:dark] ${className ?? ""}`, children: [
478
502
  /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2", children: [
479
503
  /* @__PURE__ */ jsx2(InteractionBadge, { variant: "outline", children: kindLabel ?? "Question" }),
480
504
  /* @__PURE__ */ jsx2(InteractionBadge, { variant: answered ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS[status] })
@@ -610,14 +634,14 @@ function DurablePlanCard({
610
634
  renderMarkdown,
611
635
  className
612
636
  }) {
613
- const [feedback, setFeedback] = useState3("");
614
- const [expanded, setExpanded] = useState3(false);
615
- const [localError, setLocalError] = useState3(null);
637
+ const [feedback, setFeedback] = useState4("");
638
+ const [expanded, setExpanded] = useState4(false);
639
+ const [localError, setLocalError] = useState4(null);
616
640
  useEffect3(() => setLocalError(null), [plan.planId, plan.revision, plan.status]);
617
641
  const actionable = plan.status === "pending";
618
642
  const disabled = !canWrite || !actionable || deciding !== null;
619
643
  const bodyRef = useRef3(null);
620
- const [overflows, setOverflows] = useState3(false);
644
+ const [overflows, setOverflows] = useState4(false);
621
645
  useLayoutEffect(() => {
622
646
  const el = bodyRef.current;
623
647
  if (el) setOverflows(el.scrollHeight > COLLAPSED_MAX_HEIGHT);
@@ -688,7 +712,7 @@ function DurablePlanCard({
688
712
  }
689
713
 
690
714
  // src/web-react/interaction-plan-card.tsx
691
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
715
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef4, useState as useState5 } from "react";
692
716
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
693
717
  function CheckGlyph2({ className }) {
694
718
  return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" }) });
@@ -717,7 +741,7 @@ function submittedFieldText(field, values) {
717
741
  }
718
742
  function useBodyOverflows(body, renderMarkdown) {
719
743
  const bodyRef = useRef4(null);
720
- const [overflows, setOverflows] = useState4(false);
744
+ const [overflows, setOverflows] = useState5(false);
721
745
  useLayoutEffect2(() => {
722
746
  const el = bodyRef.current;
723
747
  if (el) setOverflows(el.scrollHeight > COLLAPSED_MAX_HEIGHT2);
@@ -734,12 +758,12 @@ function InteractionPlanCard({
734
758
  renderMarkdown,
735
759
  className
736
760
  }) {
737
- const [values, setValues] = useState4({});
738
- const [expanded, setExpanded] = useState4(false);
739
- const [submitting, setSubmitting] = useState4(null);
740
- const [localStatus, setLocalStatus] = useState4(null);
741
- const [reRequested, setReRequested] = useState4(false);
742
- const [error, setError] = useState4(null);
761
+ const [values, setValues] = useState5({});
762
+ const [expanded, setExpanded] = useState5(false);
763
+ const [submitting, setSubmitting] = useState5(null);
764
+ const [localStatus, setLocalStatus] = useState5(null);
765
+ const [reRequested, setReRequested] = useState5(false);
766
+ const [error, setError] = useState5(null);
743
767
  const submitInFlightRef = useRef4(false);
744
768
  const { bodyRef, overflows } = useBodyOverflows(interaction.body, renderMarkdown);
745
769
  const status = isTerminalInteractionStatus(interaction.status) ? interaction.status : localStatus ?? interaction.status;
@@ -803,85 +827,105 @@ function InteractionPlanCard({
803
827
  const terminalNote = TERMINAL_NOTES2[status];
804
828
  const approved = status === "answered";
805
829
  const submittedValues = interaction.answers ? fieldValuesFromAnswers(interaction.fields, interaction.answers) : values;
806
- return /* @__PURE__ */ jsxs3("div", { className: `rounded-xl border border-card-edge bg-card p-4 ${className ?? ""}`, children: [
807
- /* @__PURE__ */ jsxs3("div", { className: "mb-3 flex flex-wrap items-center gap-2", children: [
808
- /* @__PURE__ */ jsx4(InteractionBadge, { variant: "outline", children: "Plan" }),
809
- /* @__PURE__ */ jsx4(InteractionBadge, { variant: approved ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS2[status] })
810
- ] }),
811
- interaction.title.trim() && /* @__PURE__ */ jsx4("p", { className: "mb-3 text-[15px] font-semibold leading-snug text-foreground", children: interaction.title }),
812
- interaction.body && /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
813
- /* @__PURE__ */ jsx4(
814
- "div",
815
- {
816
- ref: bodyRef,
817
- className: "overflow-hidden text-sm text-foreground",
818
- style: expanded || !overflows ? void 0 : { maxHeight: COLLAPSED_MAX_HEIGHT2 },
819
- children: renderMarkdown ? renderMarkdown(interaction.body) : /* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap leading-5", children: interaction.body })
820
- }
821
- ),
822
- overflows && !expanded && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-card to-transparent" }),
823
- overflows && /* @__PURE__ */ jsxs3(
824
- "button",
825
- {
826
- type: "button",
827
- onClick: () => setExpanded((prev) => !prev),
828
- className: "relative z-10 mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground transition hover:text-foreground",
829
- children: [
830
- /* @__PURE__ */ jsx4(ChevronDownGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
831
- expanded ? "Collapse plan" : "Show full plan"
832
- ]
833
- }
834
- )
835
- ] }),
836
- interaction.fields.length > 0 && status === "pending" && /* @__PURE__ */ jsx4("div", { className: "mt-3 space-y-4", children: interaction.fields.map((field) => /* @__PURE__ */ jsxs3("fieldset", { className: "space-y-2", children: [
837
- /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
838
- fieldAcceptsFreeText(field) ? /* @__PURE__ */ jsx4(
839
- "textarea",
840
- {
841
- value: values[field.name]?.text ?? "",
842
- disabled,
843
- "aria-label": field.label,
844
- onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
845
- rows: 2,
846
- placeholder: field.type === "text" ? field.placeholder ?? "Optional feedback for the agent" : void 0,
847
- className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-primary disabled:opacity-50"
848
- }
849
- ) : /* @__PURE__ */ jsx4(
850
- "input",
851
- {
852
- type: "text",
853
- value: values[field.name]?.text ?? "",
854
- disabled,
855
- "aria-label": field.label,
856
- onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
857
- className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-primary disabled:opacity-50"
858
- }
859
- )
860
- ] }, field.name)) }),
861
- interaction.fields.length > 0 && status !== "pending" && /* @__PURE__ */ jsx4("div", { className: "mt-3 space-y-2", children: interaction.fields.map((field) => {
862
- const text = submittedFieldText(field, submittedValues);
863
- if (!text) return null;
864
- return /* @__PURE__ */ jsxs3("div", { children: [
865
- /* @__PURE__ */ jsx4("p", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
866
- /* @__PURE__ */ jsx4("p", { className: "mt-0.5 whitespace-pre-wrap text-sm text-foreground", children: text })
867
- ] }, field.name);
868
- }) }),
869
- error && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-destructive", children: error }),
870
- terminalNote && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
871
- canReRequest && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsx4(InteractionActionButton, { variant: "outline", onClick: () => void requestReSubmission(), disabled: submitting !== null, children: submitting === "requesting" ? "Asking\u2026" : reRequestLabel ?? DEFAULT_RE_REQUEST_LABEL }) }),
872
- reRequested && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
873
- /* @__PURE__ */ jsx4(CheckGlyph2, { className: "h-3 w-3" }),
874
- "Re-submission requested"
875
- ] }) }),
876
- status === "pending" && /* @__PURE__ */ jsxs3("div", { className: "mt-4 flex items-center justify-end gap-2", children: [
877
- /* @__PURE__ */ jsx4(InteractionActionButton, { variant: "outline", onClick: () => void submit("declined"), disabled, children: submitting === "reject" ? "Sending\u2026" : "Request changes" }),
878
- /* @__PURE__ */ jsx4(InteractionActionButton, { onClick: () => void submit("accepted"), disabled: disabled || approveData === null, children: submitting === "approve" ? "Approving\u2026" : "Approve plan" })
879
- ] }),
880
- approved && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
881
- /* @__PURE__ */ jsx4(CheckGlyph2, { className: "h-3 w-3" }),
882
- "Approved"
883
- ] }) })
884
- ] });
830
+ return (
831
+ // Same arrival as the question card: an approval is the run stopping, and
832
+ // the card that carries it should land rather than appear. Once on screen
833
+ // it never re-animates approving, rejecting or a 410 changes state on the
834
+ // same DOM node, and a CSS animation does not replay on a re-render.
835
+ /* @__PURE__ */ jsxs3("div", { className: `agent-arrive rounded-xl border border-card-edge bg-card p-4 ${className ?? ""}`, children: [
836
+ /* @__PURE__ */ jsxs3("div", { className: "mb-3 flex flex-wrap items-center gap-2", children: [
837
+ /* @__PURE__ */ jsx4(InteractionBadge, { variant: "outline", children: "Plan" }),
838
+ /* @__PURE__ */ jsx4(InteractionBadge, { variant: approved ? "default" : status === "expired" || status === "declined" ? "destructive" : "outline", children: STATUS_LABELS2[status] })
839
+ ] }),
840
+ interaction.title.trim() && /* @__PURE__ */ jsx4("p", { className: "mb-3 text-[15px] font-semibold leading-snug text-foreground", children: interaction.title }),
841
+ interaction.body && /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
842
+ /* @__PURE__ */ jsx4(
843
+ "div",
844
+ {
845
+ ref: bodyRef,
846
+ className: "overflow-hidden text-sm text-foreground",
847
+ style: expanded || !overflows ? void 0 : { maxHeight: COLLAPSED_MAX_HEIGHT2 },
848
+ children: renderMarkdown ? renderMarkdown(interaction.body) : /* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap leading-5", children: interaction.body })
849
+ }
850
+ ),
851
+ overflows && !expanded && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-card to-transparent" }),
852
+ overflows && /* @__PURE__ */ jsxs3(
853
+ "button",
854
+ {
855
+ type: "button",
856
+ onClick: () => setExpanded((prev) => !prev),
857
+ className: "relative z-10 mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground transition hover:text-foreground",
858
+ children: [
859
+ /* @__PURE__ */ jsx4(ChevronDownGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
860
+ expanded ? "Collapse plan" : "Show full plan"
861
+ ]
862
+ }
863
+ )
864
+ ] }),
865
+ interaction.fields.length > 0 && status === "pending" && // The fields do NOT carry their own `.agent-arrive`. One level per
866
+ // surface: the card is the thing that was not there a moment ago, and a
867
+ // second entrance nested inside a travelling parent composes two
868
+ // translations and two opacity ramps over the same pixels the card
869
+ // lands while its contents are still arriving into it, which reads as
870
+ // instability rather than as sequence.
871
+ //
872
+ // The card level is the one that survives, because a stagger is a claim
873
+ // that these appeared one after another and inside a landing card that
874
+ // claim is false — they all appeared with it. The question card keeps
875
+ // its option rows staggered for the opposite reason: those are the
876
+ // CHOICES being offered, and telling the eye there are three of them
877
+ // before it has read any is information about the decision. A form's
878
+ // fields carry no such count to announce.
879
+ /* @__PURE__ */ jsx4("div", { className: "mt-3 space-y-4", children: interaction.fields.map((field) => /* @__PURE__ */ jsxs3("fieldset", { className: "space-y-2", children: [
880
+ /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium leading-5 text-foreground", children: field.label }),
881
+ fieldAcceptsFreeText(field) ? /* @__PURE__ */ jsx4(
882
+ "textarea",
883
+ {
884
+ value: values[field.name]?.text ?? "",
885
+ disabled,
886
+ "aria-label": field.label,
887
+ onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
888
+ rows: 2,
889
+ placeholder: field.type === "text" ? field.placeholder ?? "Optional feedback for the agent" : void 0,
890
+ className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-primary disabled:opacity-50"
891
+ }
892
+ ) : /* @__PURE__ */ jsx4(
893
+ "input",
894
+ {
895
+ type: "text",
896
+ value: values[field.name]?.text ?? "",
897
+ disabled,
898
+ "aria-label": field.label,
899
+ onChange: (event) => setValues((prev) => ({ ...prev, [field.name]: { ...prev[field.name], text: event.target.value } })),
900
+ className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:border-primary disabled:opacity-50"
901
+ }
902
+ )
903
+ ] }, field.name)) }),
904
+ interaction.fields.length > 0 && status !== "pending" && /* @__PURE__ */ jsx4("div", { className: "mt-3 space-y-2", children: interaction.fields.map((field) => {
905
+ const text = submittedFieldText(field, submittedValues);
906
+ if (!text) return null;
907
+ return /* @__PURE__ */ jsxs3("div", { children: [
908
+ /* @__PURE__ */ jsx4("p", { className: "text-xs font-medium text-muted-foreground", children: field.label }),
909
+ /* @__PURE__ */ jsx4("p", { className: "mt-0.5 whitespace-pre-wrap text-sm text-foreground", children: text })
910
+ ] }, field.name);
911
+ }) }),
912
+ error && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-destructive", children: error }),
913
+ terminalNote && /* @__PURE__ */ jsx4("p", { className: "mt-3 text-xs text-muted-foreground", children: terminalNote }),
914
+ canReRequest && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsx4(InteractionActionButton, { variant: "outline", onClick: () => void requestReSubmission(), disabled: submitting !== null, children: submitting === "requesting" ? "Asking\u2026" : reRequestLabel ?? DEFAULT_RE_REQUEST_LABEL }) }),
915
+ reRequested && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
916
+ /* @__PURE__ */ jsx4(CheckGlyph2, { className: "h-3 w-3" }),
917
+ "Re-submission requested"
918
+ ] }) }),
919
+ status === "pending" && /* @__PURE__ */ jsxs3("div", { className: "mt-4 flex items-center justify-end gap-2", children: [
920
+ /* @__PURE__ */ jsx4(InteractionActionButton, { variant: "outline", onClick: () => void submit("declined"), disabled, children: submitting === "reject" ? "Sending\u2026" : "Request changes" }),
921
+ /* @__PURE__ */ jsx4(InteractionActionButton, { onClick: () => void submit("accepted"), disabled: disabled || approveData === null, children: submitting === "approve" ? "Approving\u2026" : "Approve plan" })
922
+ ] }),
923
+ approved && /* @__PURE__ */ jsx4("div", { className: "mt-4 flex items-center justify-end", children: /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [
924
+ /* @__PURE__ */ jsx4(CheckGlyph2, { className: "h-3 w-3" }),
925
+ "Approved"
926
+ ] }) })
927
+ ] })
928
+ );
885
929
  }
886
930
 
887
931
  // src/web-react/durable-chat-cards.tsx
@@ -971,7 +1015,7 @@ function DurableChatCards({
971
1015
  }
972
1016
 
973
1017
  // src/web-react/message-attachments.tsx
974
- import { useCallback, useEffect as useEffect4, useState as useState5 } from "react";
1018
+ import { useCallback, useEffect as useEffect4, useState as useState6 } from "react";
975
1019
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
976
1020
  function FileGlyph({ className }) {
977
1021
  return /* @__PURE__ */ jsxs4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
@@ -1046,7 +1090,7 @@ function triggerAttachmentDownload(name, blob) {
1046
1090
  }
1047
1091
  }
1048
1092
  function useAttachmentObjectUrl(blob) {
1049
- const [url, setUrl] = useState5(null);
1093
+ const [url, setUrl] = useState6(null);
1050
1094
  useEffect4(() => {
1051
1095
  if (!blob) {
1052
1096
  setUrl(null);
@@ -1093,7 +1137,7 @@ function AttachmentUnavailable({ shape }) {
1093
1137
  function AttachmentThumbnail({ part, resolveFileUrl, fetchFile }) {
1094
1138
  const url = resolveFileUrl(part);
1095
1139
  const displayName = attachmentDisplayName(part);
1096
- const [result, setResult] = useState5(null);
1140
+ const [result, setResult] = useState6(null);
1097
1141
  useEffect4(() => {
1098
1142
  if (!displayName) return;
1099
1143
  let cancelled = false;
@@ -1132,8 +1176,8 @@ function AttachmentThumbnail({ part, resolveFileUrl, fetchFile }) {
1132
1176
  }
1133
1177
  function AttachmentChip({ part, resolveFileUrl, fetchFile }) {
1134
1178
  const displayName = attachmentDisplayName(part);
1135
- const [status, setStatus] = useState5("idle");
1136
- const [errorMessage, setErrorMessage] = useState5(null);
1179
+ const [status, setStatus] = useState6("idle");
1180
+ const [errorMessage, setErrorMessage] = useState6(null);
1137
1181
  const handleClick = useCallback(() => {
1138
1182
  if (!displayName || status === "loading") return;
1139
1183
  setStatus("loading");
@@ -1372,7 +1416,7 @@ import {
1372
1416
  useCallback as useCallback2,
1373
1417
  useEffect as useEffect5,
1374
1418
  useRef as useRef5,
1375
- useState as useState6
1419
+ useState as useState7
1376
1420
  } from "react";
1377
1421
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1378
1422
  var IS_APPLE_PLATFORM = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/i.test(navigator.platform);
@@ -1444,14 +1488,14 @@ function ChatComposer({
1444
1488
  className
1445
1489
  }) {
1446
1490
  const isControlled = value !== void 0;
1447
- const [internal, setInternal] = useState6(initialValue ?? "");
1491
+ const [internal, setInternal] = useState7(initialValue ?? "");
1448
1492
  const text = isControlled ? value : internal;
1449
1493
  const textRef = useRef5(text);
1450
1494
  textRef.current = text;
1451
1495
  const textareaRef = useRef5(null);
1452
1496
  const fileInputRef = useRef5(null);
1453
1497
  const folderInputRef = useRef5(null);
1454
- const [dragOver, setDragOver] = useState6(false);
1498
+ const [dragOver, setDragOver] = useState7(false);
1455
1499
  const dragDepth = useRef5(0);
1456
1500
  const setText = useCallback2(
1457
1501
  (next) => {
@@ -1517,7 +1561,7 @@ function ChatComposer({
1517
1561
  const readyFileCount = pendingFiles.filter((f) => f.status === "ready").length;
1518
1562
  const hasSendable = text.trim().length > 0 || readyFileCount > 0;
1519
1563
  const canSend = hasSendable && !isStreaming && !disabled;
1520
- const [failedSend, setFailedSend] = useState6(null);
1564
+ const [failedSend, setFailedSend] = useState7(null);
1521
1565
  const failSend = useCallback2(
1522
1566
  (error, draft, trimmed, parts, caret) => {
1523
1567
  const message = sendFailureText(error, sendFailureMessage);
@@ -1825,7 +1869,7 @@ function ChatComposer({
1825
1869
  }
1826
1870
 
1827
1871
  // src/web-react/durable-plan-flow.ts
1828
- import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef6, useState as useState7 } from "react";
1872
+ import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef6, useState as useState8 } from "react";
1829
1873
  var DurablePlanClientError = class extends Error {
1830
1874
  constructor(message, status, code, currentPlan) {
1831
1875
  super(message);
@@ -1912,10 +1956,10 @@ function createDurablePlanDecisionClient(options) {
1912
1956
  };
1913
1957
  }
1914
1958
  function useDurablePlanFlow(options) {
1915
- const [plan, setPlan] = useState7(options.plan);
1916
- const [deciding, setDeciding] = useState7(null);
1917
- const [restoring, setRestoring] = useState7(false);
1918
- const [error, setError] = useState7(null);
1959
+ const [plan, setPlan] = useState8(options.plan);
1960
+ const [deciding, setDeciding] = useState8(null);
1961
+ const [restoring, setRestoring] = useState8(false);
1962
+ const [error, setError] = useState8(null);
1919
1963
  const attachments = useRef6(/* @__PURE__ */ new Map());
1920
1964
  const decisionInFlight = useRef6(false);
1921
1965
  useEffect6(() => setPlan(options.plan), [options.plan]);
@@ -2087,7 +2131,7 @@ function createDurableInteractionAnswerSubmitter(options) {
2087
2131
  }
2088
2132
 
2089
2133
  // src/web-react/use-chat-interactions.ts
2090
- import { useCallback as useCallback4, useMemo as useMemo3, useState as useState8 } from "react";
2134
+ import { useCallback as useCallback4, useMemo as useMemo3, useState as useState9 } from "react";
2091
2135
  function hasPendingContentDuplicate(list, interaction) {
2092
2136
  if (interaction.status !== "pending") return false;
2093
2137
  const signature = questionInteractionContentSignature(interaction);
@@ -2166,7 +2210,7 @@ function hydrateChatInteractions(list, persisted) {
2166
2210
  return persisted.reduce(upsertChatInteraction, list);
2167
2211
  }
2168
2212
  function useChatInteractions(options = {}) {
2169
- const [interactions, setInteractions] = useState8([]);
2213
+ const [interactions, setInteractions] = useState9([]);
2170
2214
  const upsert = useCallback4((interaction) => {
2171
2215
  setInteractions((prev) => upsertChatInteraction(prev, interaction));
2172
2216
  }, []);
@@ -2193,7 +2237,7 @@ function useChatInteractions(options = {}) {
2193
2237
  }
2194
2238
 
2195
2239
  // src/web-react/use-file-mentions.ts
2196
- import { useCallback as useCallback5, useMemo as useMemo4, useRef as useRef7, useState as useState9 } from "react";
2240
+ import { useCallback as useCallback5, useMemo as useMemo4, useRef as useRef7, useState as useState10 } from "react";
2197
2241
  var FILE_MENTION_KIND = "file";
2198
2242
  function toMentionItem(file) {
2199
2243
  return { id: file.path, label: file.name, detail: file.path, kind: FILE_MENTION_KIND };
@@ -2251,11 +2295,11 @@ function useFileMentions(options) {
2251
2295
  emptyText = DEFAULT_MENTION_EMPTY_TEXT
2252
2296
  } = options;
2253
2297
  const fetchImpl = options.fetchImpl ?? fetch;
2254
- const [state, setState] = useState9({ kind: "idle" });
2298
+ const [state, setState] = useState10({ kind: "idle" });
2255
2299
  const stateRef = useRef7(state);
2256
2300
  stateRef.current = state;
2257
2301
  const inFlightRef = useRef7(null);
2258
- const [mentions, setMentions] = useState9([]);
2302
+ const [mentions, setMentions] = useState10([]);
2259
2303
  const load = useCallback5(() => {
2260
2304
  if (inFlightRef.current) return inFlightRef.current;
2261
2305
  if (stateRef.current.kind === "idle") {
@@ -2364,7 +2408,7 @@ function segmentMentionContent(content, parts) {
2364
2408
  }
2365
2409
 
2366
2410
  // src/web-react/mission-activity.tsx
2367
- import { useCallback as useCallback6, useEffect as useEffect7, useState as useState10 } from "react";
2411
+ import { useCallback as useCallback6, useEffect as useEffect7, useState as useState11 } from "react";
2368
2412
  import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
2369
2413
  var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
2370
2414
  var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
@@ -2424,7 +2468,7 @@ function CopyGlyph({ className }) {
2424
2468
  ] });
2425
2469
  }
2426
2470
  function TraceIdCopy({ traceId }) {
2427
- const [copied, setCopied] = useState10(false);
2471
+ const [copied, setCopied] = useState11(false);
2428
2472
  const copy = useCallback6(() => {
2429
2473
  void navigator.clipboard?.writeText(traceId).then(
2430
2474
  () => {
@@ -2457,12 +2501,21 @@ function StatusDot({ tone }) {
2457
2501
  "span",
2458
2502
  {
2459
2503
  "aria-hidden": true,
2460
- className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "animate-pulse bg-warning" : tone === "ok" ? "bg-success" : tone === "error" ? "bg-destructive" : "bg-muted-foreground/40"}`
2504
+ className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "bg-warning" : tone === "ok" ? "bg-success" : tone === "error" ? "bg-destructive" : "bg-muted-foreground/40"}`
2461
2505
  }
2462
2506
  ),
2463
2507
  /* @__PURE__ */ jsx8("span", { className: "sr-only", children: tone })
2464
2508
  ] });
2465
2509
  }
2510
+ function RunLabel({ tool, detail, live }) {
2511
+ return /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
2512
+ /* @__PURE__ */ jsx8("span", { className: live ? "agent-shimmer font-medium" : "font-medium", "data-motion": live ? "essential" : void 0, children: tool }),
2513
+ /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
2514
+ " \u2014 ",
2515
+ detail
2516
+ ] })
2517
+ ] });
2518
+ }
2466
2519
  var BAR_CLASS = {
2467
2520
  pipeline: "bg-muted-foreground/30",
2468
2521
  model: "bg-primary/60",
@@ -2491,31 +2544,27 @@ function FlowWaterfall({ trace }) {
2491
2544
  ] })
2492
2545
  ] });
2493
2546
  }
2547
+ function LaneRow({ run, staggerIndex }) {
2548
+ const arrival = useArrivalStyle(staggerIndex);
2549
+ const tone = activityTone(run.status);
2550
+ const cost = formatActivityCost(run.costUsd);
2551
+ const duration = formatActivityDuration(run.durationMs);
2552
+ return /* @__PURE__ */ jsxs6("div", { className: "agent-arrive flex items-center gap-2 py-1 text-xs", style: arrival, children: [
2553
+ /* @__PURE__ */ jsx8(StatusDot, { tone }),
2554
+ /* @__PURE__ */ jsx8(RunLabel, { tool: run.tool, detail: run.detail, live: tone === "live" }),
2555
+ tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx8("span", { className: "shrink-0 rounded-full bg-warning/10 px-1.5 py-0.5 font-mono text-xs text-warning", children: [run.iteration !== void 0 ? `iter ${run.iteration}` : null, run.phase ?? null].filter(Boolean).join(" \xB7 ") }),
2556
+ /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-xs tabular-nums text-muted-foreground/70", children: [
2557
+ tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx8("span", { children: run.status }),
2558
+ cost && /* @__PURE__ */ jsx8("span", { children: cost }),
2559
+ duration && /* @__PURE__ */ jsx8("span", { children: duration })
2560
+ ] })
2561
+ ] });
2562
+ }
2494
2563
  function MissionActivityLane({ activity, startedAt, nowMs }) {
2495
- const [expanded, setExpanded] = useState10(false);
2564
+ const [expanded, setExpanded] = useState11(false);
2496
2565
  if (activity.length === 0) return null;
2497
2566
  return /* @__PURE__ */ jsxs6("div", { className: "mt-1 border-l border-border pl-3", children: [
2498
- activity.map((run) => {
2499
- const tone = activityTone(run.status);
2500
- const cost = formatActivityCost(run.costUsd);
2501
- const duration = formatActivityDuration(run.durationMs);
2502
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
2503
- /* @__PURE__ */ jsx8(StatusDot, { tone }),
2504
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
2505
- /* @__PURE__ */ jsx8("span", { className: "font-medium", children: run.tool }),
2506
- /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
2507
- " \u2014 ",
2508
- run.detail
2509
- ] })
2510
- ] }),
2511
- tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx8("span", { className: "shrink-0 rounded-full bg-warning/10 px-1.5 py-0.5 font-mono text-xs text-warning", children: [run.iteration !== void 0 ? `iter ${run.iteration}` : null, run.phase ?? null].filter(Boolean).join(" \xB7 ") }),
2512
- /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-xs tabular-nums text-muted-foreground/70", children: [
2513
- tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx8("span", { children: run.status }),
2514
- cost && /* @__PURE__ */ jsx8("span", { children: cost }),
2515
- duration && /* @__PURE__ */ jsx8("span", { children: duration })
2516
- ] })
2517
- ] }, run.taskId);
2518
- }),
2567
+ activity.map((run, index) => /* @__PURE__ */ jsx8(LaneRow, { run, staggerIndex: index }, run.taskId)),
2519
2568
  /* @__PURE__ */ jsxs6(
2520
2569
  "button",
2521
2570
  {
@@ -2541,22 +2590,18 @@ function MissionActivityLane({ activity, startedAt, nowMs }) {
2541
2590
  }
2542
2591
  function ActivityRow({
2543
2592
  record,
2544
- renderMissionRef
2593
+ renderMissionRef,
2594
+ staggerIndex
2545
2595
  }) {
2546
- const [open, setOpen] = useState10(false);
2596
+ const arrival = useArrivalStyle(staggerIndex);
2597
+ const [open, setOpen] = useState11(false);
2547
2598
  const tone = activityTone(record.status);
2548
2599
  const cost = formatActivityCost(record.costUsd);
2549
2600
  const duration = formatActivityDuration(record.durationMs);
2550
- return /* @__PURE__ */ jsxs6("div", { className: "rounded-lg border border-card-edge bg-card", children: [
2601
+ return /* @__PURE__ */ jsxs6("div", { className: "agent-arrive rounded-lg border border-card-edge bg-card", style: arrival, children: [
2551
2602
  /* @__PURE__ */ jsxs6("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm", children: [
2552
2603
  /* @__PURE__ */ jsx8(StatusDot, { tone }),
2553
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1 truncate", children: [
2554
- /* @__PURE__ */ jsx8("span", { className: "font-medium", children: record.tool }),
2555
- /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
2556
- " \u2014 ",
2557
- record.detail
2558
- ] })
2559
- ] }),
2604
+ /* @__PURE__ */ jsx8(RunLabel, { tool: record.tool, detail: record.detail, live: tone === "live" }),
2560
2605
  tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx8("span", { className: "shrink-0 rounded-full bg-warning/10 px-2 py-0.5 font-mono text-xs text-warning", children: [record.iteration !== void 0 ? `iter ${record.iteration}` : null, record.phase ?? null].filter(Boolean).join(" \xB7 ") }),
2561
2606
  /* @__PURE__ */ jsx8(
2562
2607
  "span",
@@ -2589,10 +2634,10 @@ function ActivityRow({
2589
2634
  ] });
2590
2635
  }
2591
2636
  function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
2592
- const [rows, setRows] = useState10([]);
2593
- const [cursor, setCursor] = useState10(void 0);
2594
- const [status, setStatus] = useState10("loading");
2595
- const [error, setError] = useState10(null);
2637
+ const [rows, setRows] = useState11([]);
2638
+ const [cursor, setCursor] = useState11(void 0);
2639
+ const [status, setStatus] = useState11("loading");
2640
+ const [error, setError] = useState11(null);
2596
2641
  const load = useCallback6(
2597
2642
  async (from) => {
2598
2643
  setStatus("loading");
@@ -2631,7 +2676,7 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
2631
2676
  status === "error" && /* @__PURE__ */ jsx8("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
2632
2677
  status === "ready" && rows.length === 0 && /* @__PURE__ */ jsx8("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
2633
2678
  /* @__PURE__ */ jsx8("span", { role: "status", "aria-live": "polite", "aria-busy": loading, className: "sr-only", children: loading ? "Loading activity\u2026" : "" }),
2634
- /* @__PURE__ */ jsx8("div", { className: "space-y-1.5", "aria-busy": loading, children: rows.map((record) => /* @__PURE__ */ jsx8(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
2679
+ /* @__PURE__ */ jsx8("div", { className: "space-y-1.5", "aria-busy": loading, children: rows.map((record, index) => /* @__PURE__ */ jsx8(ActivityRow, { record, renderMissionRef, staggerIndex: index }, record.taskId)) }),
2635
2680
  cursor && /* @__PURE__ */ jsx8(
2636
2681
  "button",
2637
2682
  {
@@ -2646,7 +2691,7 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
2646
2691
  }
2647
2692
 
2648
2693
  // src/web-react/provenance.tsx
2649
- import { useCallback as useCallback7, useEffect as useEffect8, useId, useRef as useRef8, useState as useState11 } from "react";
2694
+ import { useCallback as useCallback7, useEffect as useEffect8, useId, useRef as useRef8, useState as useState12 } from "react";
2650
2695
 
2651
2696
  // src/web-react/provenance-model.ts
2652
2697
  var PROVENANCE_BASES = ["extracted", "entered", "computed", "asserted"];
@@ -2940,7 +2985,7 @@ function ProvenanceValue({
2940
2985
  missingValueLabel = DEFAULT_MISSING_VALUE_LABEL,
2941
2986
  className
2942
2987
  }) {
2943
- const [open, setOpen] = useState11(defaultOpen);
2988
+ const [open, setOpen] = useState12(defaultOpen);
2944
2989
  const triggerRef = useRef8(null);
2945
2990
  const rootRef = useRef8(null);
2946
2991
  const panelId = useId();
@@ -3226,7 +3271,7 @@ import {
3226
3271
  useId as useId2,
3227
3272
  useMemo as useMemo5,
3228
3273
  useRef as useRef9,
3229
- useState as useState12
3274
+ useState as useState13
3230
3275
  } from "react";
3231
3276
  import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3232
3277
  function rethrowAsync(error) {
@@ -3235,7 +3280,7 @@ function rethrowAsync(error) {
3235
3280
  });
3236
3281
  }
3237
3282
  function useInfiniteScroll(onLoadMore, { enabled, root, rootMargin = "300px" }) {
3238
- const [sentinel, setSentinel] = useState12(null);
3283
+ const [sentinel, setSentinel] = useState13(null);
3239
3284
  const sentinelRef = useCallback8((node) => setSentinel(node), []);
3240
3285
  const onLoadMoreRef = useRef9(onLoadMore);
3241
3286
  useEffect9(() => {
@@ -3270,10 +3315,10 @@ function useSessionHistory({
3270
3315
  initialPage,
3271
3316
  defaultSort = "newest"
3272
3317
  }) {
3273
- const [items, setItems] = useState12(initialPage.items);
3274
- const [nextCursor, setNextCursor] = useState12(initialPage.nextCursor ?? null);
3275
- const [phase, setPhase] = useState12("idle");
3276
- const [reloadKey, setReloadKey] = useState12(0);
3318
+ const [items, setItems] = useState13(initialPage.items);
3319
+ const [nextCursor, setNextCursor] = useState13(initialPage.nextCursor ?? null);
3320
+ const [phase, setPhase] = useState13("idle");
3321
+ const [reloadKey, setReloadKey] = useState13(0);
3277
3322
  const seqRef = useRef9(0);
3278
3323
  const resetAbortRef = useRef9(null);
3279
3324
  const loadMoreAbortRef = useRef9(null);
@@ -3391,11 +3436,11 @@ function useSessionActions({
3391
3436
  labels
3392
3437
  }) {
3393
3438
  const text = { ...DEFAULT_LABELS, ...labels };
3394
- const [renameTarget, setRenameTarget] = useState12(null);
3395
- const [renameValue, setRenameValue] = useState12("");
3396
- const [deleteTarget, setDeleteTarget] = useState12(null);
3397
- const [busy, setBusy] = useState12(false);
3398
- const [error, setError] = useState12(null);
3439
+ const [renameTarget, setRenameTarget] = useState13(null);
3440
+ const [renameValue, setRenameValue] = useState13("");
3441
+ const [deleteTarget, setDeleteTarget] = useState13(null);
3442
+ const [busy, setBusy] = useState13(false);
3443
+ const [error, setError] = useState13(null);
3399
3444
  const openRename = useCallback8((session) => {
3400
3445
  setError(null);
3401
3446
  setRenameTarget(session);
@@ -3621,11 +3666,11 @@ function SessionHistoryPanel({
3621
3666
  const searchTerm = query.trim();
3622
3667
  const isSearching = searchTerm.length > 0;
3623
3668
  const column = contentWidth === "full" ? "w-full" : "mx-auto w-full max-w-4xl";
3624
- const [selectedIds, setSelectedIds] = useState12(/* @__PURE__ */ new Set());
3625
- const [ageDays, setAgeDays] = useState12("30");
3626
- const [bulkTarget, setBulkTarget] = useState12(null);
3627
- const [bulkBusy, setBulkBusy] = useState12(false);
3628
- const [bulkError, setBulkError] = useState12(null);
3669
+ const [selectedIds, setSelectedIds] = useState13(/* @__PURE__ */ new Set());
3670
+ const [ageDays, setAgeDays] = useState13("30");
3671
+ const [bulkTarget, setBulkTarget] = useState13(null);
3672
+ const [bulkBusy, setBulkBusy] = useState13(false);
3673
+ const [bulkError, setBulkError] = useState13(null);
3629
3674
  useEffect9(() => {
3630
3675
  setSelectedIds(/* @__PURE__ */ new Set());
3631
3676
  }, [searchTerm, sort]);
@@ -3905,7 +3950,7 @@ function SessionRow({
3905
3950
  selected,
3906
3951
  onSelectedChange
3907
3952
  }) {
3908
- const [menuOpen, setMenuOpen] = useState12(false);
3953
+ const [menuOpen, setMenuOpen] = useState13(false);
3909
3954
  const panelId = useId2();
3910
3955
  const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(menuOpen, setMenuOpen);
3911
3956
  const extras = extraActions?.(session) ?? [];
@@ -4014,7 +4059,7 @@ import {
4014
4059
  useId as useId3,
4015
4060
  useMemo as useMemo6,
4016
4061
  useRef as useRef10,
4017
- useState as useState13
4062
+ useState as useState14
4018
4063
  } from "react";
4019
4064
 
4020
4065
  // src/web-react/record-grid-model.ts
@@ -4399,19 +4444,19 @@ function RecordGrid({
4399
4444
  className
4400
4445
  }) {
4401
4446
  const fieldPrefix = useId3();
4402
- const [overlay, setOverlay] = useState13(EMPTY_RECORD_GRID_OVERLAY);
4403
- const [editing, setEditingState] = useState13(null);
4404
- const [cellErrors, setCellErrors] = useState13({});
4405
- const [rowErrors, setRowErrors] = useState13({});
4406
- const [pendingRows, setPendingRows] = useState13({});
4407
- const [focus, setFocus] = useState13(null);
4408
- const [openSource, setOpenSource] = useState13(null);
4409
- const [confirmDelete, setConfirmDelete] = useState13(null);
4410
- const [adding, setAdding] = useState13(false);
4411
- const [draft, setDraft] = useState13({ ...newRowDefaults ?? {} });
4412
- const [draftErrors, setDraftErrors] = useState13({});
4413
- const [draftError, setDraftError] = useState13(null);
4414
- const [creating, setCreating] = useState13(false);
4447
+ const [overlay, setOverlay] = useState14(EMPTY_RECORD_GRID_OVERLAY);
4448
+ const [editing, setEditingState] = useState14(null);
4449
+ const [cellErrors, setCellErrors] = useState14({});
4450
+ const [rowErrors, setRowErrors] = useState14({});
4451
+ const [pendingRows, setPendingRows] = useState14({});
4452
+ const [focus, setFocus] = useState14(null);
4453
+ const [openSource, setOpenSource] = useState14(null);
4454
+ const [confirmDelete, setConfirmDelete] = useState14(null);
4455
+ const [adding, setAdding] = useState14(false);
4456
+ const [draft, setDraft] = useState14({ ...newRowDefaults ?? {} });
4457
+ const [draftErrors, setDraftErrors] = useState14({});
4458
+ const [draftError, setDraftError] = useState14(null);
4459
+ const [creating, setCreating] = useState14(false);
4415
4460
  const cellRefs = useRef10(/* @__PURE__ */ new Map());
4416
4461
  const settling = useRef10(false);
4417
4462
  const draftCounter = useRef10(0);
@@ -5425,7 +5470,7 @@ import {
5425
5470
  useCallback as useCallback10,
5426
5471
  useEffect as useEffect11,
5427
5472
  useRef as useRef11,
5428
- useState as useState14
5473
+ useState as useState15
5429
5474
  } from "react";
5430
5475
  import { Fragment as Fragment7, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
5431
5476
  function insightDelta(value, previous) {
@@ -5578,9 +5623,6 @@ function insightPageSlice(items, page, pageSize = DEFAULT_INSIGHT_PAGE_SIZE) {
5578
5623
  const safe = Math.min(Math.max(requested, 0), count - 1);
5579
5624
  return items.slice(safe * size, safe * size + size);
5580
5625
  }
5581
- function staggerStyle(index, base) {
5582
- return { ...base, "--stagger-index": index };
5583
- }
5584
5626
  function InsightDeck({
5585
5627
  state,
5586
5628
  empty,
@@ -5591,8 +5633,8 @@ function InsightDeck({
5591
5633
  className,
5592
5634
  onPageChange
5593
5635
  }) {
5594
- const [page, setPage] = useState14(0);
5595
- const [held, setHeld] = useState14(null);
5636
+ const [page, setPage] = useState15(0);
5637
+ const [held, setHeld] = useState15(null);
5596
5638
  const answered = state.status === "error" || state.status === "empty";
5597
5639
  const carried = state.status === "ready" ? state.value : answered ? null : held;
5598
5640
  if (carried !== held) setHeld(carried);
@@ -6074,7 +6116,7 @@ function ProposalCard({
6074
6116
  approval,
6075
6117
  renderers
6076
6118
  }) {
6077
- const [expanded, setExpanded] = useState15(false);
6119
+ const [expanded, setExpanded] = useState16(false);
6078
6120
  const { summary, meta, typeSlug } = proposalPreview(call);
6079
6121
  const custom = renderers?.[call.name]?.(call, message);
6080
6122
  const { pending: deciding, run: decide } = usePending();
@@ -6165,48 +6207,55 @@ function ToolCallCard({
6165
6207
  message,
6166
6208
  approval,
6167
6209
  onOpenRun,
6168
- renderers
6210
+ renderers,
6211
+ staggerIndex
6169
6212
  }) {
6213
+ const arrival = useArrivalStyle(staggerIndex ?? 0);
6170
6214
  const pending = call.status === "done" ? pendingApprovalOf(call) : null;
6171
6215
  const kind = blockKindOf(call);
6216
+ const arrive = (row) => /* @__PURE__ */ jsx15("div", { className: "agent-arrive", style: arrival, children: row });
6172
6217
  if (pending) {
6173
- return /* @__PURE__ */ jsx15(
6174
- ProposalCard,
6175
- {
6176
- call,
6177
- message,
6178
- pending,
6179
- approval,
6180
- renderers
6181
- }
6218
+ return arrive(
6219
+ /* @__PURE__ */ jsx15(
6220
+ ProposalCard,
6221
+ {
6222
+ call,
6223
+ message,
6224
+ pending,
6225
+ approval,
6226
+ renderers
6227
+ }
6228
+ )
6182
6229
  );
6183
6230
  }
6184
6231
  if (kind === "followup") {
6185
- return /* @__PURE__ */ jsx15(FollowupCard, { call });
6232
+ return arrive(/* @__PURE__ */ jsx15(FollowupCard, { call }));
6186
6233
  }
6187
6234
  const custom = renderers?.[call.name]?.(call, message);
6188
- return /* @__PURE__ */ jsx15(
6189
- InlineToolItem,
6190
- {
6191
- part: chatToolCallPart(call),
6192
- title: toolRowTitle(call),
6193
- description: toolRowDescription(call),
6194
- renderToolDetail: () => custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx15(ShellDetail, { call }) : /* @__PURE__ */ jsx15(DefaultToolDetail, { call })),
6195
- actions: onOpenRun && call.name.startsWith("sandbox_") ? /* @__PURE__ */ jsx15(
6196
- "button",
6197
- {
6198
- type: "button",
6199
- onClick: () => onOpenRun(call, message),
6200
- "aria-label": "Open full transcript",
6201
- title: "Open full transcript",
6202
- className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent hover:text-foreground",
6203
- children: /* @__PURE__ */ jsxs13("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
6204
- /* @__PURE__ */ jsx15("path", { d: "M7 17 17 7" }),
6205
- /* @__PURE__ */ jsx15("path", { d: "M7 7h10v10" })
6206
- ] })
6207
- }
6208
- ) : void 0
6209
- }
6235
+ return arrive(
6236
+ /* @__PURE__ */ jsx15(
6237
+ InlineToolItem,
6238
+ {
6239
+ part: chatToolCallPart(call),
6240
+ title: toolRowTitle(call),
6241
+ description: toolRowDescription(call),
6242
+ renderToolDetail: () => custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx15(ShellDetail, { call }) : /* @__PURE__ */ jsx15(DefaultToolDetail, { call })),
6243
+ actions: onOpenRun && call.name.startsWith("sandbox_") ? /* @__PURE__ */ jsx15(
6244
+ "button",
6245
+ {
6246
+ type: "button",
6247
+ onClick: () => onOpenRun(call, message),
6248
+ "aria-label": "Open full transcript",
6249
+ title: "Open full transcript",
6250
+ className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent hover:text-foreground",
6251
+ children: /* @__PURE__ */ jsxs13("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
6252
+ /* @__PURE__ */ jsx15("path", { d: "M7 17 17 7" }),
6253
+ /* @__PURE__ */ jsx15("path", { d: "M7 7h10v10" })
6254
+ ] })
6255
+ }
6256
+ ) : void 0
6257
+ }
6258
+ )
6210
6259
  );
6211
6260
  }
6212
6261
  function StreamingCaret() {
@@ -6262,14 +6311,15 @@ function SegmentedBody({
6262
6311
  const leftoverToolCalls = (msg.toolCalls ?? []).filter(
6263
6312
  (tc) => !segmentToolIds.has(tc.id)
6264
6313
  );
6265
- const renderToolCard = (call) => /* @__PURE__ */ jsx15(
6314
+ const renderToolCard = (call, index) => /* @__PURE__ */ jsx15(
6266
6315
  ToolCallCard,
6267
6316
  {
6268
6317
  call,
6269
6318
  message: msg,
6270
6319
  approval,
6271
6320
  onOpenRun: onToolCallClick,
6272
- renderers: toolRenderers
6321
+ renderers: toolRenderers,
6322
+ staggerIndex: index
6273
6323
  },
6274
6324
  `tool-${call.id}`
6275
6325
  );
@@ -6285,34 +6335,44 @@ function SegmentedBody({
6285
6335
  else groups.push({ kind: "tools", index: i, calls: [seg.call] });
6286
6336
  }
6287
6337
  }
6288
- return /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-2", children: [
6289
- groups.map(
6290
- (g) => g.kind === "text" ? /* @__PURE__ */ jsx15(
6291
- SegmentText,
6292
- {
6293
- content: g.content,
6294
- streaming: streaming && g.index === lastIndex,
6295
- showCaret: streaming && g.index === lastIndex,
6296
- renderBody,
6297
- messageClassName
6298
- },
6299
- `text-${g.index}`
6300
- ) : !streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool) ? (
6301
- // The fold is a quiet disclosure line, not a filled box — the
6302
- // canonical rows inside it carry the row chrome.
6338
+ const children = [];
6339
+ for (const g of groups) {
6340
+ if (g.kind === "text") {
6341
+ children.push(
6342
+ /* @__PURE__ */ jsx15(
6343
+ SegmentText,
6344
+ {
6345
+ content: g.content,
6346
+ streaming: streaming && g.index === lastIndex,
6347
+ showCaret: streaming && g.index === lastIndex,
6348
+ renderBody,
6349
+ messageClassName
6350
+ },
6351
+ `text-${g.index}`
6352
+ )
6353
+ );
6354
+ continue;
6355
+ }
6356
+ if (!streaming && g.calls.length >= COLLAPSE_TOOL_RUN_AT && !g.calls.some(isImportantTool)) {
6357
+ children.push(
6303
6358
  /* @__PURE__ */ jsxs13("details", { children: [
6304
- /* @__PURE__ */ jsxs13("summary", { className: "cursor-pointer select-none rounded-md py-0.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground", children: [
6359
+ /* @__PURE__ */ jsxs13("summary", { className: "cursor-pointer select-none rounded-md py-0.5 text-xs font-medium text-muted-foreground [transition:color_var(--motion-control)] hover:text-foreground", children: [
6305
6360
  "Worked through ",
6306
6361
  g.calls.length,
6307
6362
  " steps"
6308
6363
  ] }),
6309
6364
  /* @__PURE__ */ jsx15("div", { className: "mt-1.5 flex flex-col gap-1.5", children: g.calls.map(renderToolCard) })
6310
- ] }, `tools-${g.index}`)
6311
- ) : /* @__PURE__ */ jsx15("div", { className: "flex flex-col gap-2", children: g.calls.map(renderToolCard) }, `tools-${g.index}`)
6312
- ),
6313
- leftoverToolCalls.map(renderToolCard),
6314
- streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx15(StreamingCaret, {})
6315
- ] });
6365
+ ] }, `tools-fold-${g.index}`)
6366
+ );
6367
+ continue;
6368
+ }
6369
+ g.calls.forEach((call, index) => children.push(renderToolCard(call, index)));
6370
+ }
6371
+ leftoverToolCalls.forEach((call, index) => children.push(renderToolCard(call, index)));
6372
+ if (streaming && segments[lastIndex]?.kind === "tool") {
6373
+ children.push(/* @__PURE__ */ jsx15(StreamingCaret, {}, "streaming-caret"));
6374
+ }
6375
+ return /* @__PURE__ */ jsx15("div", { className: "flex flex-col gap-2", children });
6316
6376
  }
6317
6377
  var QUIET_META_LANE_CLASS = "mt-1 flex h-[18px] items-center gap-2 text-xs tabular-nums text-muted-foreground opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100";
6318
6378
  function copyTextOf(msg) {
@@ -6321,7 +6381,7 @@ function copyTextOf(msg) {
6321
6381
  return msg.content;
6322
6382
  }
6323
6383
  function CopyMessageButton({ text }) {
6324
- const [copied, setCopied] = useState15(false);
6384
+ const [copied, setCopied] = useState16(false);
6325
6385
  const timerRef = useRef12(null);
6326
6386
  useEffect12(
6327
6387
  () => () => {
@@ -6394,6 +6454,9 @@ function AssistantMessageImpl({
6394
6454
  const thinkingSeconds = useThinkingSeconds(
6395
6455
  streaming && !!reasoning && !hasAnswerText
6396
6456
  );
6457
+ const reasoningId = useId4();
6458
+ const [reasoningToggled, setReasoningToggled] = useState16(null);
6459
+ const reasoningOpen = reasoningToggled ?? !hasAnswerText;
6397
6460
  const quiet = chrome === "quiet";
6398
6461
  return /* @__PURE__ */ jsxs13("div", { className: `mx-auto w-full max-w-3xl px-6 ${quiet ? "group pb-1 pt-3" : "py-3"}`, children: [
6399
6462
  !quiet && /* @__PURE__ */ jsxs13("div", { className: "mb-1 flex items-baseline gap-2 text-xs tabular-nums text-muted-foreground", children: [
@@ -6402,20 +6465,59 @@ function AssistantMessageImpl({
6402
6465
  formatTokensPerSecond(msg) && /* @__PURE__ */ jsx15("span", { children: formatTokensPerSecond(msg) }),
6403
6466
  formatModelCost(msg, models) && /* @__PURE__ */ jsx15("span", { children: formatModelCost(msg, models) })
6404
6467
  ] }),
6405
- reasoning && /* @__PURE__ */ jsxs13("details", { className: "mb-2 rounded-lg border-l-2 border-border bg-secondary px-3 py-2", open: !hasAnswerText, children: [
6406
- /* @__PURE__ */ jsx15("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? (
6407
- // A pulse dims the whole word on a loop, which is the same cue a
6408
- // skeleton placeholder uses it reads as "nothing here yet". A
6409
- // sweep travels THROUGH the glyphs, which reads as work in
6410
- // flight, and the elapsed seconds say how much. `essential`
6411
- // because it is the only signal separating a working agent from
6412
- // a stuck one, and reduced-motion still collapses its duration.
6413
- /* @__PURE__ */ jsxs13("span", { className: "agent-shimmer", "data-motion": "essential", children: [
6414
- "Thinking",
6415
- thinkingSeconds >= 1 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
6416
- ] })
6417
- ) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
6418
- /* @__PURE__ */ jsx15("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground", children: reasoning })
6468
+ reasoning && // A button + `.agent-disclose` rather than `<details>`: a native
6469
+ // disclosure has no transition to give, so the trace SNAPPED to full
6470
+ // height the moment the answer arrived the single largest layout jump
6471
+ // in a turn, at the exact moment the reader is trying to start reading.
6472
+ // The grid row animates the content's REAL height (0fr 1fr), which a
6473
+ // max-height guess cannot do without either clipping a long trace or
6474
+ // easing toward a number it never reaches. The keyboard contract
6475
+ // `<details>` supplied for free is restated explicitly: a real
6476
+ // `<button>` (Enter/Space, focusable, in the tab order) carrying
6477
+ // `aria-expanded` and `aria-controls` — which also announces the state
6478
+ // that a `<summary>` leaves to the browser.
6479
+ /* @__PURE__ */ jsxs13("div", { className: "mb-2 rounded-lg border-l-2 border-border bg-secondary px-3 py-2", children: [
6480
+ /* @__PURE__ */ jsxs13(
6481
+ "button",
6482
+ {
6483
+ type: "button",
6484
+ onClick: () => setReasoningToggled(!reasoningOpen),
6485
+ "aria-expanded": reasoningOpen,
6486
+ "aria-controls": reasoningId,
6487
+ className: "flex w-full select-none items-center gap-1.5 text-left text-xs font-medium text-muted-foreground [transition:color_var(--motion-control)] hover:text-foreground",
6488
+ children: [
6489
+ /* @__PURE__ */ jsx15(ChevronDown, { className: `h-3 w-3 shrink-0 [transition:transform_var(--motion-surface)] ${reasoningOpen ? "" : "-rotate-90"}` }),
6490
+ !hasAnswerText ? (
6491
+ // A pulse dims the whole word on a loop, which is the same cue a
6492
+ // skeleton placeholder uses — it reads as "nothing here yet". A
6493
+ // sweep travels THROUGH the glyphs, which reads as work in
6494
+ // flight, and the elapsed seconds say how much. `essential`
6495
+ // because it is the only signal separating a working agent from
6496
+ // a stuck one — and `essential` is an exemption from the blanket
6497
+ // floor, never a licence to sweep forever at someone who asked
6498
+ // for less motion: under `prefers-reduced-motion` tokens.css
6499
+ // stops this animation and leaves a dotted rule under the word
6500
+ // that the settled label ("Thought for 12s") does not carry. The
6501
+ // rule is the only difference — measured in Chromium, both labels
6502
+ // compute this button's own `text-muted-foreground`, so tone
6503
+ // separates neither.
6504
+ /* @__PURE__ */ jsxs13("span", { className: "agent-shimmer", "data-motion": "essential", children: [
6505
+ "Thinking",
6506
+ thinkingSeconds >= 1 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
6507
+ ] })
6508
+ ) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process"
6509
+ ]
6510
+ }
6511
+ ),
6512
+ /* @__PURE__ */ jsx15("div", { className: "agent-disclose", "data-open": reasoningOpen ? "true" : "false", children: /* @__PURE__ */ jsx15("div", { ...inertProps(!reasoningOpen), children: /* @__PURE__ */ jsx15(
6513
+ "div",
6514
+ {
6515
+ id: reasoningId,
6516
+ ref: reasoningScrollRef,
6517
+ className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground",
6518
+ children: reasoning
6519
+ }
6520
+ ) }) })
6419
6521
  ] }),
6420
6522
  segments && segments.length > 0 ? /* @__PURE__ */ jsx15(
6421
6523
  SegmentedBody,
@@ -6434,14 +6536,15 @@ function AssistantMessageImpl({
6434
6536
  body,
6435
6537
  streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx15(StreamingCaret, {})
6436
6538
  ] }),
6437
- msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx15("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx15(
6539
+ msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx15("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc, index) => /* @__PURE__ */ jsx15(
6438
6540
  ToolCallCard,
6439
6541
  {
6440
6542
  call: tc,
6441
6543
  message: msg,
6442
6544
  approval,
6443
6545
  onOpenRun: onToolCallClick,
6444
- renderers: toolRenderers
6546
+ renderers: toolRenderers,
6547
+ staggerIndex: index
6445
6548
  },
6446
6549
  tc.id
6447
6550
  )) })
@@ -6483,7 +6586,7 @@ function AssistantMessageImpl({
6483
6586
  }
6484
6587
  var AssistantMessage = memo(AssistantMessageImpl);
6485
6588
  function useThinkingSeconds(active) {
6486
- const [seconds, setSeconds] = useState15(0);
6589
+ const [seconds, setSeconds] = useState16(0);
6487
6590
  useEffect12(() => {
6488
6591
  if (!active) return;
6489
6592
  setSeconds(0);
@@ -6740,4 +6843,4 @@ export {
6740
6843
  useThinkingSeconds,
6741
6844
  ChatMessages
6742
6845
  };
6743
- //# sourceMappingURL=chunk-7HY4LX7O.js.map
6846
+ //# sourceMappingURL=chunk-JEZILUC3.js.map