@tangle-network/agent-app 0.60.178 → 0.60.180

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 (60) hide show
  1. package/dist/agent-profiles/d1.js +3 -3
  2. package/dist/agent-profiles/index.js +3 -3
  3. package/dist/assistant/index.js +7 -5
  4. package/dist/assistant/index.js.map +1 -1
  5. package/dist/channels/index.js +1 -1
  6. package/dist/chat-routes/index.js +4 -4
  7. package/dist/{chunk-GIJY3YCH.js → chunk-3RHAF2P5.js} +11 -13
  8. package/dist/chunk-3RHAF2P5.js.map +1 -0
  9. package/dist/{chunk-IAFPBYQQ.js → chunk-6ZUVYAKB.js} +506 -315
  10. package/dist/chunk-6ZUVYAKB.js.map +1 -0
  11. package/dist/{chunk-ER3KSBA4.js → chunk-75ZIWV4U.js} +4 -4
  12. package/dist/{chunk-7PSYT35Z.js → chunk-7BYY7QKC.js} +5 -5
  13. package/dist/{chunk-7PSYT35Z.js.map → chunk-7BYY7QKC.js.map} +1 -1
  14. package/dist/chunk-A2GG4D4A.js +43 -0
  15. package/dist/chunk-A2GG4D4A.js.map +1 -0
  16. package/dist/{chunk-6CKNC32J.js → chunk-AJZ3EJB5.js} +2 -2
  17. package/dist/chunk-ATQQCG4I.js +194 -0
  18. package/dist/chunk-ATQQCG4I.js.map +1 -0
  19. package/dist/{chunk-4WEUWCA4.js → chunk-EQRVOMMS.js} +2 -2
  20. package/dist/{chunk-ARBBJWZA.js → chunk-KOTNMTHV.js} +3 -3
  21. package/dist/{chunk-LSUELMHY.js → chunk-LN5RBVGP.js} +6 -33
  22. package/dist/{chunk-LSUELMHY.js.map → chunk-LN5RBVGP.js.map} +1 -1
  23. package/dist/{chunk-CPNJRLPN.js → chunk-WSEXFTN4.js} +46 -7
  24. package/dist/chunk-WSEXFTN4.js.map +1 -0
  25. package/dist/{chunk-NO2YNV5C.js → chunk-YXLN4PXK.js} +2 -2
  26. package/dist/hub-approvals/index.js +1 -1
  27. package/dist/hub-approvals/types.d.ts +16 -1
  28. package/dist/hub-approvals-react/feedback.d.ts +25 -5
  29. package/dist/hub-approvals-react/index.d.ts +1 -0
  30. package/dist/hub-approvals-react/index.js +413 -257
  31. package/dist/hub-approvals-react/index.js.map +1 -1
  32. package/dist/hub-approvals-react/media.d.ts +12 -0
  33. package/dist/integrations-react/index.js +1 -1
  34. package/dist/preset-cloudflare/index.js +2 -2
  35. package/dist/public-consultation/index.d.ts +1 -1
  36. package/dist/sandbox/index.js +4 -4
  37. package/dist/stories/app-shell/interaction-evidence.d.ts +31 -0
  38. package/dist/studio-react/index.js +2 -2
  39. package/dist/theme/tokens.css +158 -0
  40. package/dist/tools/index.js +4 -4
  41. package/dist/web-react/action-dialog.d.ts +2 -2
  42. package/dist/web-react/agent-profile-editor.js +1 -1
  43. package/dist/web-react/agent-profile-form-kit.d.ts +1 -1
  44. package/dist/web-react/async/index.js +1 -1
  45. package/dist/web-react/index.d.ts +2 -0
  46. package/dist/web-react/index.js +12 -5
  47. package/dist/web-react/review-inbox.d.ts +33 -0
  48. package/dist/work-product/index.js +2 -2
  49. package/dist/workspace-react/index.js +1 -1
  50. package/package.json +2 -2
  51. package/dist/chunk-CPNJRLPN.js.map +0 -1
  52. package/dist/chunk-GIJY3YCH.js.map +0 -1
  53. package/dist/chunk-IAFPBYQQ.js.map +0 -1
  54. package/dist/chunk-QJYJOMMO.js +0 -78
  55. package/dist/chunk-QJYJOMMO.js.map +0 -1
  56. /package/dist/{chunk-ER3KSBA4.js.map → chunk-75ZIWV4U.js.map} +0 -0
  57. /package/dist/{chunk-6CKNC32J.js.map → chunk-AJZ3EJB5.js.map} +0 -0
  58. /package/dist/{chunk-4WEUWCA4.js.map → chunk-EQRVOMMS.js.map} +0 -0
  59. /package/dist/{chunk-ARBBJWZA.js.map → chunk-KOTNMTHV.js.map} +0 -0
  60. /package/dist/{chunk-NO2YNV5C.js.map → chunk-YXLN4PXK.js.map} +0 -0
@@ -0,0 +1,43 @@
1
+ // src/web-react/action-dialog.tsx
2
+ import { useRef } from "react";
3
+ import { Button, Dialog, DialogContent, DialogFooter, DialogTitle } from "@tangle-network/ui/primitives";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ function ActionDialogButton({ children, onClick, disabled, variant = "primary" }) {
6
+ return /* @__PURE__ */ jsx(Button, { type: "button", onClick, disabled, variant: variant === "primary" ? "default" : variant, children });
7
+ }
8
+ function ActionDialog({ title, children, footer, onClose, busy, error }) {
9
+ const opener = useRef(typeof document !== "undefined" && document.activeElement instanceof HTMLElement ? document.activeElement : null);
10
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => {
11
+ if (!open && !busy) onClose();
12
+ }, children: /* @__PURE__ */ jsxs(
13
+ DialogContent,
14
+ {
15
+ hideCloseButton: true,
16
+ "aria-busy": busy,
17
+ "aria-describedby": void 0,
18
+ className: "max-w-sm gap-0 p-5",
19
+ onEscapeKeyDown: (event) => {
20
+ if (busy) event.preventDefault();
21
+ },
22
+ onInteractOutside: (event) => {
23
+ if (busy) event.preventDefault();
24
+ },
25
+ onCloseAutoFocus: (event) => {
26
+ event.preventDefault();
27
+ opener.current?.focus();
28
+ },
29
+ children: [
30
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-sm", children: title }),
31
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children }),
32
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-3 rounded-md bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
33
+ /* @__PURE__ */ jsx(DialogFooter, { className: "mt-5", children: footer })
34
+ ]
35
+ }
36
+ ) });
37
+ }
38
+
39
+ export {
40
+ ActionDialogButton,
41
+ ActionDialog
42
+ };
43
+ //# sourceMappingURL=chunk-A2GG4D4A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-react/action-dialog.tsx"],"sourcesContent":["import { useRef, type ReactNode } from 'react'\nimport { Button, Dialog, DialogContent, DialogFooter, DialogTitle } from '@tangle-network/ui/primitives'\n\n// The shell owns mutation state; the shared primitives own controls and focus.\nexport function ActionDialogButton({ children, onClick, disabled, variant = 'primary' }: {\n children: ReactNode\n onClick: () => void\n disabled?: boolean\n variant?: 'primary' | 'ghost' | 'destructive'\n}) {\n return <Button type=\"button\" onClick={onClick} disabled={disabled} variant={variant === 'primary' ? 'default' : variant}>{children}</Button>\n}\n\nexport function ActionDialog({ title, children, footer, onClose, busy, error }: {\n title: string\n children: ReactNode\n footer: ReactNode\n onClose: () => void\n busy: boolean\n error: string | null\n}) {\n // These dialogs mount without a DialogTrigger: retain the shell's opener.\n const opener = useRef(typeof document !== 'undefined' && document.activeElement instanceof HTMLElement ? document.activeElement : null)\n return <Dialog open onOpenChange={open => { if (!open && !busy) onClose() }}>\n <DialogContent hideCloseButton aria-busy={busy} aria-describedby={undefined} className=\"max-w-sm gap-0 p-5\"\n onEscapeKeyDown={event => { if (busy) event.preventDefault() }}\n onInteractOutside={event => { if (busy) event.preventDefault() }}\n onCloseAutoFocus={event => { event.preventDefault(); opener.current?.focus() }}>\n <DialogTitle className=\"text-sm\">{title}</DialogTitle>\n <div className=\"mt-3\">{children}</div>\n {error && <p role=\"alert\" className=\"mt-3 rounded-md bg-destructive/10 px-3 py-2 text-xs text-destructive\">{error}</p>}\n <DialogFooter className=\"mt-5\">{footer}</DialogFooter>\n </DialogContent>\n </Dialog>\n}\n"],"mappings":";AAAA,SAAS,cAA8B;AACvC,SAAS,QAAQ,QAAQ,eAAe,cAAc,mBAAmB;AAShE,cAcL,YAdK;AANF,SAAS,mBAAmB,EAAE,UAAU,SAAS,UAAU,UAAU,UAAU,GAKnF;AACD,SAAO,oBAAC,UAAO,MAAK,UAAS,SAAkB,UAAoB,SAAS,YAAY,YAAY,YAAY,SAAU,UAAS;AACrI;AAEO,SAAS,aAAa,EAAE,OAAO,UAAU,QAAQ,SAAS,MAAM,MAAM,GAO1E;AAED,QAAM,SAAS,OAAO,OAAO,aAAa,eAAe,SAAS,yBAAyB,cAAc,SAAS,gBAAgB,IAAI;AACtI,SAAO,oBAAC,UAAO,MAAI,MAAC,cAAc,UAAQ;AAAE,QAAI,CAAC,QAAQ,CAAC,KAAM,SAAQ;AAAA,EAAE,GACxE;AAAA,IAAC;AAAA;AAAA,MAAc,iBAAe;AAAA,MAAC,aAAW;AAAA,MAAM,oBAAkB;AAAA,MAAW,WAAU;AAAA,MACrF,iBAAiB,WAAS;AAAE,YAAI,KAAM,OAAM,eAAe;AAAA,MAAE;AAAA,MAC7D,mBAAmB,WAAS;AAAE,YAAI,KAAM,OAAM,eAAe;AAAA,MAAE;AAAA,MAC/D,kBAAkB,WAAS;AAAE,cAAM,eAAe;AAAG,eAAO,SAAS,MAAM;AAAA,MAAE;AAAA,MAC7E;AAAA,4BAAC,eAAY,WAAU,WAAW,iBAAM;AAAA,QACxC,oBAAC,SAAI,WAAU,QAAQ,UAAS;AAAA,QAC/B,SAAS,oBAAC,OAAE,MAAK,SAAQ,WAAU,wEAAwE,iBAAM;AAAA,QAClH,oBAAC,gBAAa,WAAU,QAAQ,kBAAO;AAAA;AAAA;AAAA,EACzC,GACF;AACF;","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  OPENUI_NODE_JSON_SCHEMA,
6
6
  OPENUI_NODE_TYPES
7
- } from "./chunk-7PSYT35Z.js";
7
+ } from "./chunk-7BYY7QKC.js";
8
8
 
9
9
  // src/tools/openai.ts
10
10
  var APP_TOOL_NAMES = ["submit_proposal", "schedule_followup", "render_ui", "add_citation"];
@@ -112,4 +112,4 @@ export {
112
112
  customToolToOpenAI,
113
113
  findCustomTool
114
114
  };
115
- //# sourceMappingURL=chunk-6CKNC32J.js.map
115
+ //# sourceMappingURL=chunk-AJZ3EJB5.js.map
@@ -0,0 +1,194 @@
1
+ import {
2
+ presentHubAction
3
+ } from "./chunk-WSEXFTN4.js";
4
+
5
+ // src/hub-approvals-react/feedback.tsx
6
+ import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from "react";
7
+ import { Button, Input, Label, Textarea } from "@tangle-network/ui/primitives";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ var ReviewFeedbackForm = forwardRef(function ReviewFeedbackForm2({
10
+ input,
11
+ preview,
12
+ feedback: saved,
13
+ draftKey,
14
+ drafts,
15
+ onFeedback,
16
+ onVerdict,
17
+ allowedVerdicts = ["approve", "reject", "request_changes"],
18
+ approveLabel = "Approve",
19
+ disabled = false,
20
+ editNote = "Your edit is saved as a teaching example."
21
+ }, ref) {
22
+ const id = useId();
23
+ const scoreInput = useRef(null);
24
+ const commentInput = useRef(null);
25
+ const original = input && typeof input === "object" && !Array.isArray(input) ? input : {};
26
+ const priorInput = saved?.editedInput !== void 0 ? saved.editedInput : input;
27
+ const prior = priorInput && typeof priorInput === "object" && !Array.isArray(priorInput) ? priorInput : {};
28
+ const kind = preview?.kind;
29
+ const container = kind === "post" && original.container && typeof original.container === "object" && !Array.isArray(original.container) ? original.container : null;
30
+ const nestedCaption = container && typeof container.caption === "string";
31
+ const copyField = ["text", "commentary", "caption", "message", "title"].find((key) => typeof original[key] === "string");
32
+ const editablePost = kind === "post" && (nestedCaption || copyField);
33
+ const field = kind === "email" ? typeof original.html === "string" ? "html" : typeof original.body === "string" ? "body" : "text" : nestedCaption ? "caption" : copyField ?? "text";
34
+ const priorText = nestedCaption && prior.container && typeof prior.container === "object" ? prior.container[field] : prior[field];
35
+ const draft = drafts?.get(draftKey);
36
+ const [score, setScore] = useState(draft?.score ?? (saved ? String(saved.score) : ""));
37
+ const [comment, setComment] = useState(draft?.comment ?? saved?.comment ?? "");
38
+ const [editing, setEditing] = useState(draft?.editing ?? false);
39
+ const [edited, setEdited] = useState(draft?.edited ?? saved?.editedInput !== void 0);
40
+ const [subject, setSubject] = useState(draft?.subject ?? String(prior.subject ?? ""));
41
+ const [body, setBody] = useState(draft?.body ?? String(priorText ?? ""));
42
+ const [payload, setPayload] = useState(draft?.payload ?? JSON.stringify(priorInput ?? null, null, 2));
43
+ const [busy, setBusy] = useState(false);
44
+ const [status, setStatus] = useState((draft ? draft.saved : saved) ? "Feedback saved" : "");
45
+ const [error, setError] = useState("");
46
+ useEffect(() => {
47
+ drafts?.set(draftKey, { score, comment, editing, edited, subject, body, payload, saved: status === "Feedback saved" });
48
+ }, [drafts, draftKey, score, comment, editing, edited, subject, body, payload, status]);
49
+ async function save(verdict) {
50
+ if (busy || disabled || (verdict ? !onVerdict || !allowedVerdicts.includes(verdict) : !onFeedback)) return;
51
+ if (verdict && verdict !== "approve" && !comment.trim()) {
52
+ setError("Add a note before rejecting or requesting changes.");
53
+ commentInput.current?.focus();
54
+ return;
55
+ }
56
+ if (score.trim() === "" || !Number.isInteger(Number(score)) || Number(score) < 0 || Number(score) > 100) {
57
+ setError("Enter a score from 0 to 100.");
58
+ scoreInput.current?.focus();
59
+ return;
60
+ }
61
+ setBusy(true);
62
+ setError("");
63
+ setStatus("");
64
+ try {
65
+ const editedInput = edited ? kind === "email" ? { ...original, subject, [field]: body } : editablePost ? nestedCaption ? { ...original, container: { ...container, [field]: body } } : { ...original, [field]: body } : JSON.parse(payload) : saved?.editedInput;
66
+ const value = {
67
+ score: Number(score),
68
+ comment,
69
+ ...editedInput !== void 0 && JSON.stringify(editedInput) !== JSON.stringify(input) ? { editedInput } : {}
70
+ };
71
+ if (verdict) await onVerdict(verdict, value);
72
+ else await onFeedback(value);
73
+ setStatus("Feedback saved");
74
+ } catch (cause) {
75
+ setError(cause instanceof Error ? cause.message : "Feedback could not be saved. Your edits are kept here.");
76
+ } finally {
77
+ setBusy(false);
78
+ }
79
+ }
80
+ useImperativeHandle(ref, () => ({
81
+ focusScore: () => {
82
+ scoreInput.current?.focus();
83
+ scoreInput.current?.select();
84
+ },
85
+ focusComment: () => commentInput.current?.focus(),
86
+ submitVerdict: (verdict) => save(verdict)
87
+ }));
88
+ return /* @__PURE__ */ jsx("form", { "aria-label": "Preview feedback", className: "space-y-3 border-t border-border pt-3", onSubmit: (event) => {
89
+ event.preventDefault();
90
+ void save();
91
+ }, children: /* @__PURE__ */ jsxs("fieldset", { disabled: busy || disabled, className: "min-w-0 space-y-3", children: [
92
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [
93
+ /* @__PURE__ */ jsxs("div", { className: "w-36 space-y-1", children: [
94
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-score`, children: "Score (0\u2013100)" }),
95
+ /* @__PURE__ */ jsx(
96
+ Input,
97
+ {
98
+ ref: scoreInput,
99
+ id: `${id}-score`,
100
+ type: "number",
101
+ inputMode: "numeric",
102
+ min: 0,
103
+ max: 100,
104
+ step: 1,
105
+ required: true,
106
+ value: score,
107
+ placeholder: "Unscored",
108
+ disabled: busy,
109
+ onChange: (event) => {
110
+ setScore(event.target.value);
111
+ setStatus("");
112
+ }
113
+ }
114
+ )
115
+ ] }),
116
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", "aria-expanded": editing, disabled: busy, onClick: () => {
117
+ setEditing((value) => !value);
118
+ setStatus("");
119
+ }, children: "Edit to teach" })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
122
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-comment`, children: "Comment" }),
123
+ /* @__PURE__ */ jsx(Textarea, { ref: commentInput, id: `${id}-comment`, value: comment, rows: 2, disabled: busy, onChange: (event) => {
124
+ setComment(event.target.value);
125
+ setStatus("");
126
+ } })
127
+ ] }),
128
+ editing && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
129
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: editNote }),
130
+ kind === "email" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
131
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-subject`, children: "Subject" }),
132
+ /* @__PURE__ */ jsx(Input, { id: `${id}-subject`, value: subject, disabled: busy, onChange: (event) => {
133
+ setSubject(event.target.value);
134
+ setEdited(true);
135
+ setStatus("");
136
+ } })
137
+ ] }),
138
+ kind === "email" || editablePost ? /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
139
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-body`, children: kind === "email" ? "Body" : "Post text" }),
140
+ /* @__PURE__ */ jsx(Textarea, { id: `${id}-body`, value: body, rows: 6, disabled: busy, onChange: (event) => {
141
+ setBody(event.target.value);
142
+ setEdited(true);
143
+ setStatus("");
144
+ } })
145
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
146
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-payload`, children: "Payload" }),
147
+ /* @__PURE__ */ jsx(Textarea, { id: `${id}-payload`, value: payload, rows: 6, disabled: busy, onChange: (event) => {
148
+ setPayload(event.target.value);
149
+ setEdited(true);
150
+ setStatus("");
151
+ } })
152
+ ] })
153
+ ] }),
154
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: error }),
155
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
156
+ onVerdict && allowedVerdicts.map((verdict) => /* @__PURE__ */ jsx(
157
+ Button,
158
+ {
159
+ type: "button",
160
+ variant: verdict === "approve" ? "default" : "outline",
161
+ disabled: busy,
162
+ "aria-keyshortcuts": verdict === "approve" ? "A" : verdict === "request_changes" ? "R" : void 0,
163
+ onClick: () => void save(verdict),
164
+ children: verdict === "approve" ? approveLabel : verdict === "reject" ? "Reject" : "Request changes"
165
+ },
166
+ verdict
167
+ )),
168
+ onFeedback && /* @__PURE__ */ jsx(Button, { type: "submit", variant: "outline", disabled: busy, children: busy ? "Saving feedback\u2026" : "Save feedback" }),
169
+ /* @__PURE__ */ jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: busy && !onFeedback ? "Saving review\u2026" : status })
170
+ ] })
171
+ ] }) });
172
+ });
173
+ function HubPreviewFeedback({ item, onFeedback, drafts }) {
174
+ const draftKey = JSON.stringify([item.id, item.actionPath, item.input]);
175
+ return /* @__PURE__ */ jsx(
176
+ ReviewFeedbackForm,
177
+ {
178
+ input: item.input,
179
+ preview: presentHubAction(item).preview,
180
+ feedback: item.feedback,
181
+ draftKey,
182
+ drafts,
183
+ onFeedback: (feedback) => onFeedback(item, feedback),
184
+ editNote: "Your edit is saved as a teaching example. Live approval applies to the original held payload."
185
+ },
186
+ draftKey
187
+ );
188
+ }
189
+
190
+ export {
191
+ ReviewFeedbackForm,
192
+ HubPreviewFeedback
193
+ };
194
+ //# sourceMappingURL=chunk-ATQQCG4I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hub-approvals-react/feedback.tsx"],"sourcesContent":["import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react'\nimport { Button, Input, Label, Textarea } from '@tangle-network/ui/primitives'\nimport { presentHubAction, type HubActionPreview, type HubApprovalFeedback, type HubApprovalItem } from '../hub-approvals'\n\nexport interface HubPreviewFeedbackProps {\n item: HubApprovalItem\n onFeedback: (item: HubApprovalItem, feedback: HubApprovalFeedback) => Promise<void>\n /** Owner-local drafts survive collapsing/switching previews, never another viewer. */\n drafts?: Map<string, ReviewFeedbackDraft>\n}\n\nexport interface ReviewFeedbackDraft { score: string; comment: string; editing: boolean; edited: boolean; subject: string; body: string; payload: string; saved: boolean }\nexport type ReviewVerdict = 'approve' | 'reject' | 'request_changes'\nexport interface ReviewFeedbackFormHandle {\n focusScore: () => void\n focusComment: () => void\n submitVerdict: (verdict: ReviewVerdict) => Promise<void>\n}\nexport interface ReviewFeedbackFormProps {\n input: unknown\n preview?: HubActionPreview\n feedback?: HubApprovalFeedback\n draftKey: string\n drafts?: Map<string, ReviewFeedbackDraft>\n onFeedback?: (feedback: HubApprovalFeedback) => Promise<void>\n onVerdict?: (verdict: ReviewVerdict, feedback: HubApprovalFeedback) => Promise<void>\n allowedVerdicts?: readonly ReviewVerdict[]\n approveLabel?: string\n disabled?: boolean\n editNote?: string\n}\n\n/** One form for Hub previews, assets and work products. Hosts own storage and authority. */\nexport const ReviewFeedbackForm = forwardRef<ReviewFeedbackFormHandle, ReviewFeedbackFormProps>(function ReviewFeedbackForm({\n input, preview, feedback: saved, draftKey, drafts, onFeedback, onVerdict,\n allowedVerdicts = ['approve', 'reject', 'request_changes'], approveLabel = 'Approve', disabled = false,\n editNote = 'Your edit is saved as a teaching example.',\n}, ref) {\n const id = useId()\n const scoreInput = useRef<HTMLInputElement>(null)\n const commentInput = useRef<HTMLTextAreaElement>(null)\n const original = input && typeof input === 'object' && !Array.isArray(input) ? input as Record<string, unknown> : {}\n const priorInput = saved?.editedInput !== undefined ? saved.editedInput : input\n const prior = priorInput && typeof priorInput === 'object' && !Array.isArray(priorInput) ? priorInput as Record<string, unknown> : {}\n const kind = preview?.kind\n const container = kind === 'post' && original.container && typeof original.container === 'object' && !Array.isArray(original.container)\n ? original.container as Record<string, unknown> : null\n const nestedCaption = container && typeof container.caption === 'string'\n const copyField = ['text', 'commentary', 'caption', 'message', 'title'].find((key) => typeof original[key] === 'string')\n const editablePost = kind === 'post' && (nestedCaption || copyField)\n const field = kind === 'email'\n ? typeof original.html === 'string' ? 'html' : typeof original.body === 'string' ? 'body' : 'text'\n : nestedCaption ? 'caption' : copyField ?? 'text'\n const priorText = nestedCaption && prior.container && typeof prior.container === 'object'\n ? (prior.container as Record<string, unknown>)[field] : prior[field]\n const draft = drafts?.get(draftKey)\n const [score, setScore] = useState(draft?.score ?? (saved ? String(saved.score) : ''))\n const [comment, setComment] = useState(draft?.comment ?? saved?.comment ?? '')\n const [editing, setEditing] = useState(draft?.editing ?? false)\n const [edited, setEdited] = useState(draft?.edited ?? saved?.editedInput !== undefined)\n const [subject, setSubject] = useState(draft?.subject ?? String(prior.subject ?? ''))\n const [body, setBody] = useState(draft?.body ?? String(priorText ?? ''))\n const [payload, setPayload] = useState(draft?.payload ?? JSON.stringify(priorInput ?? null, null, 2))\n const [busy, setBusy] = useState(false)\n const [status, setStatus] = useState((draft ? draft.saved : saved) ? 'Feedback saved' : '')\n const [error, setError] = useState('')\n\n useEffect(() => {\n drafts?.set(draftKey, { score, comment, editing, edited, subject, body, payload, saved: status === 'Feedback saved' })\n }, [drafts, draftKey, score, comment, editing, edited, subject, body, payload, status])\n\n async function save(verdict?: ReviewVerdict) {\n if (busy || disabled || (verdict ? !onVerdict || !allowedVerdicts.includes(verdict) : !onFeedback)) return\n if (verdict && verdict !== 'approve' && !comment.trim()) {\n setError('Add a note before rejecting or requesting changes.'); commentInput.current?.focus(); return\n }\n if (score.trim() === '' || !Number.isInteger(Number(score)) || Number(score) < 0 || Number(score) > 100) {\n setError('Enter a score from 0 to 100.'); scoreInput.current?.focus(); return\n }\n setBusy(true); setError(''); setStatus('')\n try {\n const editedInput: unknown = edited\n ? kind === 'email' ? { ...original, subject, [field]: body }\n : editablePost ? nestedCaption ? { ...original, container: { ...container, [field]: body } } : { ...original, [field]: body } : JSON.parse(payload)\n : saved?.editedInput\n const value: HubApprovalFeedback = { score: Number(score), comment,\n ...(editedInput !== undefined && JSON.stringify(editedInput) !== JSON.stringify(input) ? { editedInput } : {}) }\n if (verdict) await onVerdict!(verdict, value)\n else await onFeedback!(value)\n setStatus('Feedback saved')\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : 'Feedback could not be saved. Your edits are kept here.')\n } finally { setBusy(false) }\n }\n useImperativeHandle(ref, () => ({ focusScore: () => { scoreInput.current?.focus(); scoreInput.current?.select() },\n focusComment: () => commentInput.current?.focus(), submitVerdict: (verdict) => save(verdict) }))\n\n return (\n <form aria-label=\"Preview feedback\" className=\"space-y-3 border-t border-border pt-3\" onSubmit={(event) => { event.preventDefault(); void save() }}>\n <fieldset disabled={busy || disabled} className=\"min-w-0 space-y-3\">\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"w-36 space-y-1\">\n <Label htmlFor={`${id}-score`}>Score (0–100)</Label>\n <Input ref={scoreInput} id={`${id}-score`} type=\"number\" inputMode=\"numeric\" min={0} max={100} step={1} required value={score}\n placeholder=\"Unscored\" disabled={busy} onChange={(event) => { setScore(event.target.value); setStatus('') }} />\n </div>\n <Button type=\"button\" variant=\"outline\" aria-expanded={editing} disabled={busy} onClick={() => { setEditing((value) => !value); setStatus('') }}>Edit to teach</Button>\n </div>\n <div className=\"space-y-1\">\n <Label htmlFor={`${id}-comment`}>Comment</Label>\n <Textarea ref={commentInput} id={`${id}-comment`} value={comment} rows={2} disabled={busy} onChange={(event) => { setComment(event.target.value); setStatus('') }} />\n </div>\n {editing && <div className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">{editNote}</p>\n {kind === 'email' && <div className=\"space-y-1\"><Label htmlFor={`${id}-subject`}>Subject</Label><Input id={`${id}-subject`} value={subject} disabled={busy} onChange={(event) => { setSubject(event.target.value); setEdited(true); setStatus('') }} /></div>}\n {kind === 'email' || editablePost\n ? <div className=\"space-y-1\"><Label htmlFor={`${id}-body`}>{kind === 'email' ? 'Body' : 'Post text'}</Label><Textarea id={`${id}-body`} value={body} rows={6} disabled={busy} onChange={(event) => { setBody(event.target.value); setEdited(true); setStatus('') }} /></div>\n : <div className=\"space-y-1\"><Label htmlFor={`${id}-payload`}>Payload</Label><Textarea id={`${id}-payload`} value={payload} rows={6} disabled={busy} onChange={(event) => { setPayload(event.target.value); setEdited(true); setStatus('') }} /></div>}\n </div>}\n {error && <p role=\"alert\" className=\"text-sm text-[var(--surface-danger-text)]\">{error}</p>}\n <div className=\"flex flex-wrap items-center gap-3\">\n {onVerdict && allowedVerdicts.map(verdict => <Button key={verdict} type=\"button\" variant={verdict === 'approve' ? 'default' : 'outline'} disabled={busy}\n aria-keyshortcuts={verdict === 'approve' ? 'A' : verdict === 'request_changes' ? 'R' : undefined}\n onClick={() => void save(verdict)}>{verdict === 'approve' ? approveLabel : verdict === 'reject' ? 'Reject' : 'Request changes'}</Button>)}\n {onFeedback && <Button type=\"submit\" variant=\"outline\" disabled={busy}>{busy ? 'Saving feedback…' : 'Save feedback'}</Button>}\n <p role=\"status\" className=\"text-sm text-muted-foreground\">{busy && !onFeedback ? 'Saving review…' : status}</p>\n </div>\n </fieldset>\n </form>\n )\n})\n\n/** Existing Hub adapter: teaching never calls the effect transport. */\nexport function HubPreviewFeedback({ item, onFeedback, drafts }: HubPreviewFeedbackProps) {\n const draftKey = JSON.stringify([item.id, item.actionPath, item.input])\n return <ReviewFeedbackForm key={draftKey} input={item.input} preview={presentHubAction(item).preview} feedback={item.feedback} draftKey={draftKey} drafts={drafts}\n onFeedback={(feedback) => onFeedback(item, feedback)} editNote=\"Your edit is saved as a teaching example. Live approval applies to the original held payload.\" />\n}\n"],"mappings":"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","names":["ReviewFeedbackForm"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ActionDialog,
3
3
  ActionDialogButton
4
- } from "./chunk-QJYJOMMO.js";
4
+ } from "./chunk-A2GG4D4A.js";
5
5
  import {
6
6
  UNTITLED_SESSION_LABEL,
7
7
  mergeSessionPages,
@@ -746,4 +746,4 @@ export {
746
746
  formatSessionTimestamp,
747
747
  SessionHistoryPanel
748
748
  };
749
- //# sourceMappingURL=chunk-4WEUWCA4.js.map
749
+ //# sourceMappingURL=chunk-EQRVOMMS.js.map
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-I6BZVIRH.js";
5
5
  import {
6
6
  defineAppTool
7
- } from "./chunk-6CKNC32J.js";
7
+ } from "./chunk-AJZ3EJB5.js";
8
8
  import {
9
9
  ToolInputError
10
- } from "./chunk-7PSYT35Z.js";
10
+ } from "./chunk-7BYY7QKC.js";
11
11
 
12
12
  // src/agent-profiles/index.ts
13
13
  import {
@@ -772,4 +772,4 @@ export {
772
772
  PROFILE_MEMORY_PREFIX,
773
773
  profileTools
774
774
  };
775
- //# sourceMappingURL=chunk-ARBBJWZA.js.map
775
+ //# sourceMappingURL=chunk-KOTNMTHV.js.map
@@ -198,6 +198,7 @@ function useConfirmedMutation({
198
198
 
199
199
  // src/web-react/async/async-view.tsx
200
200
  import { isValidElement } from "react";
201
+ import { Button, EmptyState } from "@tangle-network/ui/primitives";
201
202
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
202
203
  var BLOCK_CLASS = "flex flex-col items-center justify-center gap-2 px-4 py-10 text-center";
203
204
  function AsyncView({
@@ -240,16 +241,7 @@ function AsyncView({
240
241
  );
241
242
  }
242
243
  function LoadingBlock({ label }) {
243
- return /* @__PURE__ */ jsxs("div", { className: BLOCK_CLASS, children: [
244
- /* @__PURE__ */ jsx(
245
- "span",
246
- {
247
- className: "h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent",
248
- "aria-hidden": "true"
249
- }
250
- ),
251
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: label })
252
- ] });
244
+ return /* @__PURE__ */ jsx("div", { className: BLOCK_CLASS, children: /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: label }) });
253
245
  }
254
246
  function ErrorBlock({
255
247
  message,
@@ -258,31 +250,12 @@ function ErrorBlock({
258
250
  }) {
259
251
  return /* @__PURE__ */ jsxs("div", { role: "alert", className: BLOCK_CLASS, children: [
260
252
  /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: message }),
261
- /* @__PURE__ */ jsx(
262
- "button",
263
- {
264
- type: "button",
265
- onClick: retry,
266
- className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent",
267
- children: retryLabel
268
- }
269
- )
253
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: retry, children: retryLabel })
270
254
  ] });
271
255
  }
272
256
  function EmptyBlock({ spec }) {
273
- return /* @__PURE__ */ jsxs("div", { className: BLOCK_CLASS, children: [
274
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: spec.title }),
275
- spec.description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: spec.description }) : null,
276
- spec.action ? isValidElement(spec.action) ? spec.action : /* @__PURE__ */ jsx(
277
- "button",
278
- {
279
- type: "button",
280
- onClick: spec.action.onClick,
281
- className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent",
282
- children: spec.action.label
283
- }
284
- ) : null
285
- ] });
257
+ const action = spec.action;
258
+ return /* @__PURE__ */ jsx(EmptyState, { title: spec.title, description: spec.description, action: isValidElement(action) ? action : action ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: action.onClick, children: action.label }) : void 0 });
286
259
  }
287
260
  function MutationStatus({ state, labels, className }) {
288
261
  if (state.status === "idle") return null;
@@ -314,4 +287,4 @@ export {
314
287
  AsyncView,
315
288
  MutationStatus
316
289
  };
317
- //# sourceMappingURL=chunk-LSUELMHY.js.map
290
+ //# sourceMappingURL=chunk-LN5RBVGP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/web-react/async/state.ts","../src/web-react/async/use-async-resource.ts","../src/web-react/async/use-confirmed-mutation.ts","../src/web-react/async/async-view.tsx"],"sourcesContent":["/**\n * The fetch-state contract: a failed load cannot render as empty data.\n *\n * Three shapes produce that defect, and two audited verticals shipped dozens of\n * each: a `catch` handler that only clears a loading flag, an early return on a\n * non-ok response, and a bare `null` returned while loading. All three end with\n * the same pixels a successful-but-empty load produces — \"No templates\n * available\", an empty member list, a blank conversation — so the reader cannot\n * tell \"we asked and there is nothing\" from \"we could not ask\", and the retry\n * that would fix it is never offered.\n *\n * `AsyncResourceState` makes the two outcomes separate variants: `error` is the\n * only one carrying a message and it is never reachable with a value, `empty` is\n * the only one carrying a resolved-but-empty value and it never carries a\n * message. A component rendering one is structurally not rendering the other.\n *\n * This file is React-free so the state model, the emptiness rule and the\n * Response readers can be unit-tested and reused outside a component.\n */\n\n/**\n * - `idle` — no load has been attempted (the hook is disabled, or its inputs\n * are not resolved yet). Distinct from `loading`: nothing is in flight.\n * - `loading` — a load is in flight and no value is held.\n * - `error` — the load failed. Carries the message and never a value.\n * - `empty` — the load succeeded and the value is empty. Never carries a message.\n * - `ready` — the load succeeded and the value is non-empty.\n */\nexport type AsyncResourceStatus = 'idle' | 'loading' | 'error' | 'empty' | 'ready'\n\n/** Re-runs the load. Present on every variant so the error branch can never be\n * rendered without the action that recovers from it. */\nexport interface AsyncRetryable {\n readonly retry: () => void\n}\n\nexport type AsyncResourceState<T> =\n | ({ readonly status: 'idle' } & AsyncRetryable)\n | ({ readonly status: 'loading' } & AsyncRetryable)\n | ({ readonly status: 'error'; readonly message: string; readonly error: unknown } & AsyncRetryable)\n | ({ readonly status: 'empty'; readonly value: T } & AsyncRetryable)\n | ({ readonly status: 'ready'; readonly value: T } & AsyncRetryable)\n\n/** The state model without the retry action — what a reducer produces. */\nexport type AsyncResolution<T> =\n | { readonly status: 'idle' }\n | { readonly status: 'loading' }\n | { readonly status: 'error'; readonly message: string; readonly error: unknown }\n | { readonly status: 'empty'; readonly value: T }\n | { readonly status: 'ready'; readonly value: T }\n\nexport const DEFAULT_ASYNC_ERROR_MESSAGE = 'This did not load. Try again.'\n\n/**\n * Default emptiness rule: `null`/`undefined`, an empty array, an empty `Map` or\n * `Set`. A string, a number and a plain object are `ready` — `''` and `{}` are\n * legitimate values for the resources that produce them, and guessing otherwise\n * would route a real answer into the empty branch. Pass `isEmpty` for a shape\n * this cannot see into (`{ items: [] }`, a paged envelope, a count).\n */\nexport function defaultIsEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (Array.isArray(value)) return value.length === 0\n if (value instanceof Map || value instanceof Set) return value.size === 0\n return false\n}\n\nfunction hasMessage(value: object): value is { message: unknown } {\n return 'message' in value\n}\n\n/** Best available human-readable message for a thrown value. Never returns an\n * empty string — a blank error block reads as a rendering bug. */\nexport function asyncErrorMessage(error: unknown, fallback: string = DEFAULT_ASYNC_ERROR_MESSAGE): string {\n if (typeof error === 'string' && error.trim() !== '') return error\n if (typeof error === 'object' && error !== null && hasMessage(error)) {\n const message = error.message\n if (typeof message === 'string' && message.trim() !== '') return message\n }\n return fallback\n}\n\n/** Classifies a successfully loaded value into `ready` or `empty`. */\nexport function resolveAsyncValue<T>(value: T, isEmpty: (value: T) => boolean = defaultIsEmpty): AsyncResolution<T> {\n return isEmpty(value) ? { status: 'empty', value } : { status: 'ready', value }\n}\n\n/**\n * A non-ok HTTP response, as a throwable. `requireOk`/`readOkJson` raise this so\n * a non-ok response reaches the `error` branch instead of an early `return` that\n * leaves the caller rendering the empty branch.\n */\nexport class AsyncRequestError extends Error {\n readonly status: number\n readonly statusText: string\n readonly url: string\n /** First 200 characters of the response body when it could be read, else `''`.\n * Kept off `message` so a server's HTML error page never becomes UI copy. */\n readonly body: string\n\n constructor(response: { status: number; statusText?: string; url?: string }, body = '') {\n const statusText = response.statusText ?? ''\n super(`Request failed (${response.status}${statusText ? ` ${statusText}` : ''})`)\n this.name = 'AsyncRequestError'\n this.status = response.status\n this.statusText = statusText\n this.url = response.url ?? ''\n this.body = body\n }\n}\n\nasync function readBodySnippet(response: Response): Promise<string> {\n try {\n return (await response.text()).slice(0, 200)\n } catch {\n // An already-consumed or unreadable body is a missing diagnostic, not a\n // second failure — the status is what the error reports.\n return ''\n }\n}\n\n/** Returns the response when ok; throws `AsyncRequestError` otherwise. Reads the\n * body only on the failure path, so the caller keeps an unconsumed stream. */\nexport async function requireOk(response: Response): Promise<Response> {\n if (response.ok) return response\n throw new AsyncRequestError(response, await readBodySnippet(response))\n}\n\n/**\n * `requireOk` + `response.json()`. Without `parse` the result is `unknown`, so a\n * caller narrows at the JSON boundary rather than inheriting an unchecked shape;\n * with `parse` the validator runs inside the load and a rejection lands in the\n * `error` branch.\n */\nexport async function readOkJson(response: Response): Promise<unknown>\nexport async function readOkJson<T>(response: Response, parse: (data: unknown) => T): Promise<T>\nexport async function readOkJson<T>(response: Response, parse?: (data: unknown) => T): Promise<T | unknown> {\n await requireOk(response)\n let data: unknown\n try {\n data = await response.json()\n } catch (error) {\n throw new Error('The response was not valid JSON.', { cause: error })\n }\n return parse ? parse(data) : data\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport {\n asyncErrorMessage,\n defaultIsEmpty,\n resolveAsyncValue,\n type AsyncResolution,\n type AsyncResourceState,\n} from './state'\n\nexport interface AsyncLoadContext {\n /** Aborted when the inputs change, a retry supersedes this load, or the\n * component unmounts. Forward it to `fetch` so a superseded request stops. */\n readonly signal: AbortSignal\n}\n\nexport interface UseAsyncResourceOptions<T> {\n /**\n * The one load. Reject (or throw) to reach the `error` branch — a non-ok\n * response must reject too, which is what `requireOk`/`readOkJson` are for.\n * Read from a ref internally, so an inline arrow does not re-trigger; `deps`\n * is what declares when the load must run again.\n */\n load: (context: AsyncLoadContext) => Promise<T>\n /** Re-runs the load when any entry changes by `Object.is`, like `useEffect`. */\n deps?: readonly unknown[]\n /** `false` holds the resource at `idle` and runs nothing — for inputs that are\n * not resolved yet. Flipping it to `true` starts the load. */\n enabled?: boolean\n /** First-render seed (an SSR/loader page). The hook starts resolved and skips\n * the first load. Read once — later identity changes are ignored, so a\n * revalidating loader belongs in `deps`, not here. */\n initialValue?: T\n /** Splits a successful load into `empty` vs `ready`. Default: `defaultIsEmpty`. */\n isEmpty?: (value: T) => boolean\n /** Maps a thrown value to the message the `error` branch renders. */\n errorMessage?: (error: unknown) => string\n}\n\n/** Bumps a token when any dependency changes identity, so the effect's own\n * dependency list stays a fixed length whatever the caller passes. */\nfunction useChangeToken(deps: readonly unknown[]): number {\n const ref = useRef<{ deps: readonly unknown[]; token: number }>({ deps, token: 0 })\n const changed =\n ref.current.deps.length !== deps.length || deps.some((dep, index) => !Object.is(dep, ref.current.deps[index]))\n if (changed) ref.current = { deps, token: ref.current.token + 1 }\n return ref.current.token\n}\n\nconst NO_DEPS: readonly unknown[] = []\n\n/**\n * The five-state fetch machine: `idle | loading | error | empty | ready`.\n *\n * What it guarantees, and what the hand-rolled versions it replaces did not:\n *\n * - a rejected load lands on `error` with a message and a `retry`, never on an\n * empty list;\n * - `empty` is only reachable from a load that actually succeeded;\n * - a superseded load (inputs changed, retry pressed, component unmounted) is\n * aborted and its late result is discarded; resolutions are bound to their\n * input identity, including the render before the next effect runs.\n */\nexport function useAsyncResource<T>({\n load,\n deps = NO_DEPS,\n enabled = true,\n initialValue,\n isEmpty,\n errorMessage,\n}: UseAsyncResourceOptions<T>): AsyncResourceState<T> {\n // Include enablement and retries in the identity: stale data must not be\n // returned during the render before an effect clears the previous request.\n const [reloadKey, setReloadKey] = useState(0)\n const token = useChangeToken([enabled, reloadKey, ...deps])\n const options = useRef({ load, isEmpty, errorMessage })\n options.current = { load, isEmpty, errorMessage }\n const [settled, setSettled] = useState<{ token: number; resolution: AsyncResolution<T> }>(() => ({\n token,\n resolution: initialValue === undefined\n ? { status: 'loading' }\n : resolveAsyncValue(initialValue, isEmpty ?? defaultIsEmpty),\n }))\n // A seed belongs only to its first identity. Effect replay does not consume\n // it, but a dependency change, re-enable, or explicit retry does.\n const seededToken = useRef(initialValue === undefined ? null : token)\n\n useEffect(() => {\n if (!enabled || seededToken.current === token) return\n const controller = new AbortController()\n const commit = (resolution: AsyncResolution<T>) => {\n if (!controller.signal.aborted) setSettled({ token, resolution })\n }\n commit({ status: 'loading' })\n void (async () => {\n try {\n const value = await options.current.load({ signal: controller.signal })\n if (!controller.signal.aborted) commit(resolveAsyncValue(value, options.current.isEmpty ?? defaultIsEmpty))\n } catch (error) {\n if (!controller.signal.aborted) commit({\n status: 'error',\n message: options.current.errorMessage?.(error) ?? asyncErrorMessage(error),\n error,\n })\n }\n })()\n return () => controller.abort()\n }, [token, enabled])\n\n const retry = useCallback(() => setReloadKey(key => key + 1), [])\n return useMemo<AsyncResourceState<T>>(() => ({\n ...(!enabled ? { status: 'idle' as const }\n : settled.token === token ? settled.resolution : { status: 'loading' as const }),\n retry,\n }), [enabled, settled, token, retry])\n}\n","import { useCallback, useMemo, useRef, useState } from 'react'\n\nimport { asyncErrorMessage, readOkJson, requireOk } from './state'\nimport type { AsyncLoadContext } from './use-async-resource'\n\n/**\n * The brand that makes \"Saved\" unreachable without a confirmed write.\n *\n * A resolved promise is not a success: `fetch` resolves on a 404, and the\n * shipped defect this closes is a save button that rendered \"Saved\" because the\n * only thing awaited was that the request came back at all. The success variant\n * therefore carries a symbol no object literal can spell, so the only way into\n * `succeeded` is `confirmWrite` / `confirmResponse` / `confirmJson` — each of\n * which has already checked that the write landed.\n */\nexport const CONFIRMED_WRITE: unique symbol = Symbol('agent-app.confirmed-write')\n\nexport interface MutationConfirmed<T> {\n readonly succeeded: true\n readonly value: T\n readonly [CONFIRMED_WRITE]: true\n}\n\nexport interface MutationRejected {\n readonly succeeded: false\n /** Safe to render: what the user is told the write did not do. */\n readonly message: string\n readonly error?: unknown\n}\n\nexport type MutationOutcome<T> = MutationConfirmed<T> | MutationRejected\n\n/** Confirms a write whose success is already established (an SDK call that\n * throws on failure, a store returning its own typed outcome). The deliberate\n * act is the point — this call is what an audit greps for. */\nexport function confirmWrite<T>(value: T): MutationConfirmed<T> {\n return { succeeded: true, value, [CONFIRMED_WRITE]: true }\n}\n\n/** A write that did not land. Constructible by hand: failing loud is never the\n * direction that needs guarding. */\nexport function rejectWrite(message: string, error?: unknown): MutationRejected {\n return error === undefined ? { succeeded: false, message } : { succeeded: false, message, error }\n}\n\n/** Confirms only a 2xx response. A 404/500 becomes a rejection carrying the\n * status — never a success, whatever the promise did. */\nexport async function confirmResponse(response: Response): Promise<MutationOutcome<Response>> {\n try {\n return confirmWrite(await requireOk(response))\n } catch (error) {\n return rejectWrite(asyncErrorMessage(error), error)\n }\n}\n\n/** `confirmResponse` + a JSON body. A non-ok status, an unreadable body and a\n * throwing `parse` are all rejections. */\nexport async function confirmJson<T>(response: Response, parse: (data: unknown) => T): Promise<MutationOutcome<T>> {\n try {\n return confirmWrite(await readOkJson(response, parse))\n } catch (error) {\n return rejectWrite(asyncErrorMessage(error), error)\n }\n}\n\n/** True only for a value built by one of the confirm helpers. Takes `unknown`\n * because an untyped consumer can return anything from `mutate`. */\nexport function isConfirmedWrite<T>(outcome: unknown): outcome is MutationConfirmed<T> {\n if (typeof outcome !== 'object' || outcome === null) return false\n return (outcome as { readonly [CONFIRMED_WRITE]?: unknown })[CONFIRMED_WRITE] === true\n}\n\nfunction asRejection(outcome: unknown): MutationRejected | null {\n if (typeof outcome !== 'object' || outcome === null) return null\n const candidate = outcome as { succeeded?: unknown; message?: unknown; error?: unknown }\n if (candidate.succeeded !== false) return null\n const message = typeof candidate.message === 'string' && candidate.message.trim() !== ''\n ? candidate.message\n : UNCONFIRMED_MESSAGE\n return rejectWrite(message, candidate.error)\n}\n\nexport type MutationState<T> =\n | { readonly status: 'idle' }\n | { readonly status: 'pending' }\n | { readonly status: 'succeeded'; readonly value: T }\n | { readonly status: 'failed'; readonly message: string; readonly error: unknown }\n\nexport interface UseConfirmedMutationOptions<TInput, TValue> {\n /** Default replaces the observed run. `reject` keeps the current request\n * and refuses another invocation before any second write can start. */\n concurrency?: 'replace' | 'reject'\n /**\n * Performs the write and returns a confirmation. Anything else — including a\n * hand-written `{ succeeded: true }` — is treated as a failed write, because\n * an unbranded object is exactly the shape produced by code that never\n * checked the response.\n *\n * The context signal aborts only when a later `run` supersedes this one. It is\n * NOT aborted on unmount: a write the user asked for must not be cancelled by\n * navigating away.\n */\n mutate: (input: TInput, context: AsyncLoadContext) => Promise<MutationOutcome<TValue>>\n /** Fires after the state reaches `succeeded` (latest run only). */\n onSucceeded?: (value: TValue) => void\n /** Fires after the state reaches `failed` (latest run only). */\n onFailed?: (message: string, error: unknown) => void\n /** Maps a thrown value to the message the `failed` state renders. */\n errorMessage?: (error: unknown) => string\n}\n\nexport interface ConfirmedMutation<TInput, TValue> {\n readonly state: MutationState<TValue>\n /** Runs the write. Never rejects — the outcome is returned and mirrored into\n * `state`. Concurrent runs are last-write-wins unless `concurrency: 'reject'`\n * is configured. Server-side idempotency is still required. */\n readonly run: (input: TInput) => Promise<MutationOutcome<TValue>>\n /** Back to `idle` (dismisses a \"Saved\" or error affordance). */\n readonly reset: () => void\n}\n\nconst UNCONFIRMED_MESSAGE = 'The write could not be confirmed.'\n\nconst UNCONFIRMED_CONTRACT =\n 'mutate() resolved without a confirmation. Return confirmWrite(value), confirmResponse(response) or confirmJson(response, parse) so the success state proves the write landed.'\n\n/**\n * `idle | pending | succeeded | failed` over a write that must confirm itself.\n *\n * `succeeded` is reachable only through a branded confirmation, so the audited\n * \"Saved on a 404\" bug cannot be written with this hook: the 404 path produces\n * `failed` carrying the status, and a `mutate` that forgot to check produces\n * `failed` carrying the contract violation rather than a success it never\n * earned.\n */\nexport function useConfirmedMutation<TInput, TValue>({\n mutate,\n onSucceeded,\n onFailed,\n errorMessage,\n concurrency = 'replace',\n}: UseConfirmedMutationOptions<TInput, TValue>): ConfirmedMutation<TInput, TValue> {\n const options = useRef({ mutate, onSucceeded, onFailed, errorMessage, concurrency })\n options.current = { mutate, onSucceeded, onFailed, errorMessage, concurrency }\n\n const [state, setState] = useState<MutationState<TValue>>({ status: 'idle' })\n const seqRef = useRef(0)\n const inFlightRef = useRef<{ controller: AbortController; pending: boolean } | null>(null)\n\n const run = useCallback(async (input: TInput): Promise<MutationOutcome<TValue>> => {\n if (options.current.concurrency === 'reject' && inFlightRef.current?.pending)\n return rejectWrite('Another operation is still in progress.')\n inFlightRef.current?.controller.abort()\n const controller = new AbortController()\n inFlightRef.current = { controller, pending: true }\n const seq = ++seqRef.current\n\n setState({ status: 'pending' })\n\n let outcome: MutationOutcome<TValue>\n try {\n const returned: unknown = await options.current.mutate(input, { signal: controller.signal })\n outcome = isConfirmedWrite<TValue>(returned)\n ? returned\n : asRejection(returned) ?? rejectWrite(UNCONFIRMED_MESSAGE, new Error(UNCONFIRMED_CONTRACT))\n } catch (error) {\n outcome = rejectWrite(options.current.errorMessage?.(error) ?? asyncErrorMessage(error), error)\n }\n\n // A superseded run reports its own outcome to its own caller and never\n // repaints the state a newer run owns.\n if (seq !== seqRef.current) return outcome\n if (inFlightRef.current) inFlightRef.current.pending = false\n\n if (outcome.succeeded) {\n setState({ status: 'succeeded', value: outcome.value })\n options.current.onSucceeded?.(outcome.value)\n } else {\n setState({ status: 'failed', message: outcome.message, error: outcome.error })\n options.current.onFailed?.(outcome.message, outcome.error)\n }\n return outcome\n }, [])\n\n const reset = useCallback(() => {\n seqRef.current += 1\n inFlightRef.current?.controller.abort()\n inFlightRef.current = null\n setState({ status: 'idle' })\n }, [])\n\n return useMemo(() => ({ state, run, reset }), [state, run, reset])\n}\n","import { isValidElement, type ReactElement, type ReactNode } from 'react'\n\nimport type { AsyncResourceState } from './state'\nimport type { MutationState } from './use-confirmed-mutation'\n\nexport interface AsyncEmptyAction {\n label: string\n onClick: () => void\n}\n\nexport interface AsyncEmptySpec {\n /** What is not there, in the reader's words (\"No templates yet\"). Required:\n * an empty state with nothing to say is the state this module exists to\n * stop being mistaken for a failure. */\n title: string\n description?: string\n /** The next action. An element is rendered as supplied (a link, a dialog\n * trigger); the object form renders the standard button. */\n action?: AsyncEmptyAction | ReactElement\n}\n\nexport interface AsyncErrorRenderProps {\n message: string\n retry: () => void\n}\n\nexport interface AsyncViewProps<T> {\n state: AsyncResourceState<T>\n /** Rendered only for `ready`, with the loaded value — the branch cannot be\n * entered without one. */\n children: (value: T) => ReactNode\n /** Required: `empty` must name what is missing and what to do about it. */\n empty: AsyncEmptySpec | ReactElement\n /** Must return an element. Returning nothing is what produced the blank\n * screens this component replaces, so a nullish return falls back to the\n * built-in block rather than rendering nothing. */\n renderLoading?: () => ReactElement\n renderError?: (props: AsyncErrorRenderProps) => ReactElement\n /** `idle` renders the loading block by default — from the reader's side,\n * \"not started\" and \"in flight\" are the same wait. */\n renderIdle?: () => ReactElement\n loadingLabel?: string\n retryLabel?: string\n /** Applied to the wrapper around the non-`ready` branches. `ready` renders the\n * children with no wrapper element, so grids and lists keep their layout. */\n className?: string\n}\n\nconst BLOCK_CLASS = 'flex flex-col items-center justify-center gap-2 px-4 py-10 text-center'\n\n/**\n * Renders the branch the state is actually in.\n *\n * The three anti-patterns this replaces (`catch` that only clears a loading\n * flag, early return on a non-ok response, bare `null` while loading) all end at\n * the same rendered output as a successful-but-empty load. Here every branch is\n * reached from a different variant and each renders visibly: `loading` and\n * `idle` render a labelled busy block, `error` renders the message plus the\n * retry, `empty` renders the caller's copy and next action, and `ready` is the\n * only branch with a value to hand to `children`.\n */\nexport function AsyncView<T>({\n state,\n children,\n empty,\n renderLoading,\n renderError,\n renderIdle,\n loadingLabel = 'Loading…',\n retryLabel = 'Retry',\n className,\n}: AsyncViewProps<T>): ReactElement {\n if (state.status === 'ready') return <>{children(state.value)}</>\n\n // Every `??` here is the guarantee: a custom renderer that returns nothing\n // leaves the reader with the built-in block, never a blank region.\n const branch = (): ReactNode => {\n switch (state.status) {\n case 'idle':\n return renderIdle?.() ?? <LoadingBlock label={loadingLabel} />\n case 'loading':\n return renderLoading?.() ?? <LoadingBlock label={loadingLabel} />\n case 'error':\n return (\n renderError?.({ message: state.message, retry: state.retry }) ?? (\n <ErrorBlock message={state.message} retry={state.retry} retryLabel={retryLabel} />\n )\n )\n case 'empty':\n if (isValidElement(empty)) return empty\n return typeof empty === 'object' && empty !== null && typeof empty.title === 'string' ? (\n <EmptyBlock spec={empty} />\n ) : (\n <EmptyBlock spec={{ title: 'Nothing here yet.' }} />\n )\n }\n }\n\n // The busy signal lives on the WRAPPER, not inside the loading block, for the\n // same reason every `??` above exists: `renderLoading` is an escape hatch, and\n // a caller filling it with their own spinner would otherwise take the\n // announcement with them — a screen reader would hear nothing for exactly the\n // products that styled their wait state. The wrapper wraps every non-`ready`\n // branch, so it cannot be opted out of.\n //\n // `aria-busy` is emitted in BOTH states rather than only while loading: a\n // region that gains the attribute at the moment it goes busy, and drops it on\n // arrival, gives assistive tech no transition to report. The `error` branch\n // keeps its own `role=\"alert\"` and takes no live region here, because nesting\n // one inside another is what produces a doubled announcement.\n const busy = state.status === 'loading' || state.status === 'idle'\n const failed = state.status === 'error'\n return (\n <div\n data-async-state={state.status}\n className={className}\n role={failed ? undefined : 'status'}\n aria-live={failed ? undefined : 'polite'}\n aria-busy={busy}\n >\n {branch()}\n </div>\n )\n}\n\nfunction LoadingBlock({ label }: { label: string }): ReactElement {\n return (\n <div className={BLOCK_CLASS}>\n {/* The ring is the brand colour, not `border-border`. A divider token is\n tuned to be barely there, which is the opposite of what a busy\n indicator needs: measured on this block it painted 1.10:1 in light and\n 1.20:1 in dark against its own panel — below the 3:1 a meaningful\n graphic needs, and this spinner is the only signal that a fetch is in\n flight. `border-primary` is also what the composer's send spinner\n already uses, so this stops being the one that reads differently. */}\n <span\n className=\"h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent\"\n aria-hidden=\"true\"\n />\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n </div>\n )\n}\n\nfunction ErrorBlock({\n message,\n retry,\n retryLabel,\n}: {\n message: string\n retry: () => void\n retryLabel: string\n}): ReactElement {\n return (\n <div role=\"alert\" className={BLOCK_CLASS}>\n <p className=\"max-w-md text-sm text-muted-foreground\">{message}</p>\n <button\n type=\"button\"\n onClick={retry}\n className=\"h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent\"\n >\n {retryLabel}\n </button>\n </div>\n )\n}\n\nfunction EmptyBlock({ spec }: { spec: AsyncEmptySpec }): ReactElement {\n return (\n <div className={BLOCK_CLASS}>\n <p className=\"text-sm font-medium text-foreground\">{spec.title}</p>\n {spec.description ? <p className=\"max-w-md text-sm text-muted-foreground\">{spec.description}</p> : null}\n {spec.action ? (\n isValidElement(spec.action) ? (\n spec.action\n ) : (\n <button\n type=\"button\"\n onClick={(spec.action as AsyncEmptyAction).onClick}\n className=\"h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent\"\n >\n {(spec.action as AsyncEmptyAction).label}\n </button>\n )\n ) : null}\n </div>\n )\n}\n\nexport interface MutationStatusLabels {\n pending?: string\n succeeded?: string\n}\n\nexport interface MutationStatusProps<T> {\n state: MutationState<T>\n labels?: MutationStatusLabels\n className?: string\n}\n\n/**\n * The write's own status line. \"Saved\" renders only from `succeeded`, which\n * `useConfirmedMutation` can only reach through a confirmed write, so the label\n * cannot appear over a failed request.\n */\nexport function MutationStatus<T>({ state, labels, className }: MutationStatusProps<T>): ReactElement | null {\n if (state.status === 'idle') return null\n if (state.status === 'pending') {\n return (\n <span role=\"status\" aria-live=\"polite\" aria-busy={true} className={className ?? 'text-xs text-muted-foreground'}>\n {labels?.pending ?? 'Saving…'}\n </span>\n )\n }\n if (state.status === 'succeeded') {\n return (\n <span role=\"status\" aria-live=\"polite\" aria-busy={false} className={className ?? 'text-xs text-muted-foreground'}>\n {labels?.succeeded ?? 'Saved'}\n </span>\n )\n }\n return (\n <span role=\"alert\" className={className ?? 'text-xs text-destructive'}>\n {state.message}\n </span>\n )\n}\n"],"mappings":"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","names":["useCallback","useMemo","useRef","useState","useRef","useState","useCallback","useMemo"]}
1
+ {"version":3,"sources":["../src/web-react/async/state.ts","../src/web-react/async/use-async-resource.ts","../src/web-react/async/use-confirmed-mutation.ts","../src/web-react/async/async-view.tsx"],"sourcesContent":["/**\n * The fetch-state contract: a failed load cannot render as empty data.\n *\n * Three shapes produce that defect, and two audited verticals shipped dozens of\n * each: a `catch` handler that only clears a loading flag, an early return on a\n * non-ok response, and a bare `null` returned while loading. All three end with\n * the same pixels a successful-but-empty load produces — \"No templates\n * available\", an empty member list, a blank conversation — so the reader cannot\n * tell \"we asked and there is nothing\" from \"we could not ask\", and the retry\n * that would fix it is never offered.\n *\n * `AsyncResourceState` makes the two outcomes separate variants: `error` is the\n * only one carrying a message and it is never reachable with a value, `empty` is\n * the only one carrying a resolved-but-empty value and it never carries a\n * message. A component rendering one is structurally not rendering the other.\n *\n * This file is React-free so the state model, the emptiness rule and the\n * Response readers can be unit-tested and reused outside a component.\n */\n\n/**\n * - `idle` — no load has been attempted (the hook is disabled, or its inputs\n * are not resolved yet). Distinct from `loading`: nothing is in flight.\n * - `loading` — a load is in flight and no value is held.\n * - `error` — the load failed. Carries the message and never a value.\n * - `empty` — the load succeeded and the value is empty. Never carries a message.\n * - `ready` — the load succeeded and the value is non-empty.\n */\nexport type AsyncResourceStatus = 'idle' | 'loading' | 'error' | 'empty' | 'ready'\n\n/** Re-runs the load. Present on every variant so the error branch can never be\n * rendered without the action that recovers from it. */\nexport interface AsyncRetryable {\n readonly retry: () => void\n}\n\nexport type AsyncResourceState<T> =\n | ({ readonly status: 'idle' } & AsyncRetryable)\n | ({ readonly status: 'loading' } & AsyncRetryable)\n | ({ readonly status: 'error'; readonly message: string; readonly error: unknown } & AsyncRetryable)\n | ({ readonly status: 'empty'; readonly value: T } & AsyncRetryable)\n | ({ readonly status: 'ready'; readonly value: T } & AsyncRetryable)\n\n/** The state model without the retry action — what a reducer produces. */\nexport type AsyncResolution<T> =\n | { readonly status: 'idle' }\n | { readonly status: 'loading' }\n | { readonly status: 'error'; readonly message: string; readonly error: unknown }\n | { readonly status: 'empty'; readonly value: T }\n | { readonly status: 'ready'; readonly value: T }\n\nexport const DEFAULT_ASYNC_ERROR_MESSAGE = 'This did not load. Try again.'\n\n/**\n * Default emptiness rule: `null`/`undefined`, an empty array, an empty `Map` or\n * `Set`. A string, a number and a plain object are `ready` — `''` and `{}` are\n * legitimate values for the resources that produce them, and guessing otherwise\n * would route a real answer into the empty branch. Pass `isEmpty` for a shape\n * this cannot see into (`{ items: [] }`, a paged envelope, a count).\n */\nexport function defaultIsEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (Array.isArray(value)) return value.length === 0\n if (value instanceof Map || value instanceof Set) return value.size === 0\n return false\n}\n\nfunction hasMessage(value: object): value is { message: unknown } {\n return 'message' in value\n}\n\n/** Best available human-readable message for a thrown value. Never returns an\n * empty string — a blank error block reads as a rendering bug. */\nexport function asyncErrorMessage(error: unknown, fallback: string = DEFAULT_ASYNC_ERROR_MESSAGE): string {\n if (typeof error === 'string' && error.trim() !== '') return error\n if (typeof error === 'object' && error !== null && hasMessage(error)) {\n const message = error.message\n if (typeof message === 'string' && message.trim() !== '') return message\n }\n return fallback\n}\n\n/** Classifies a successfully loaded value into `ready` or `empty`. */\nexport function resolveAsyncValue<T>(value: T, isEmpty: (value: T) => boolean = defaultIsEmpty): AsyncResolution<T> {\n return isEmpty(value) ? { status: 'empty', value } : { status: 'ready', value }\n}\n\n/**\n * A non-ok HTTP response, as a throwable. `requireOk`/`readOkJson` raise this so\n * a non-ok response reaches the `error` branch instead of an early `return` that\n * leaves the caller rendering the empty branch.\n */\nexport class AsyncRequestError extends Error {\n readonly status: number\n readonly statusText: string\n readonly url: string\n /** First 200 characters of the response body when it could be read, else `''`.\n * Kept off `message` so a server's HTML error page never becomes UI copy. */\n readonly body: string\n\n constructor(response: { status: number; statusText?: string; url?: string }, body = '') {\n const statusText = response.statusText ?? ''\n super(`Request failed (${response.status}${statusText ? ` ${statusText}` : ''})`)\n this.name = 'AsyncRequestError'\n this.status = response.status\n this.statusText = statusText\n this.url = response.url ?? ''\n this.body = body\n }\n}\n\nasync function readBodySnippet(response: Response): Promise<string> {\n try {\n return (await response.text()).slice(0, 200)\n } catch {\n // An already-consumed or unreadable body is a missing diagnostic, not a\n // second failure — the status is what the error reports.\n return ''\n }\n}\n\n/** Returns the response when ok; throws `AsyncRequestError` otherwise. Reads the\n * body only on the failure path, so the caller keeps an unconsumed stream. */\nexport async function requireOk(response: Response): Promise<Response> {\n if (response.ok) return response\n throw new AsyncRequestError(response, await readBodySnippet(response))\n}\n\n/**\n * `requireOk` + `response.json()`. Without `parse` the result is `unknown`, so a\n * caller narrows at the JSON boundary rather than inheriting an unchecked shape;\n * with `parse` the validator runs inside the load and a rejection lands in the\n * `error` branch.\n */\nexport async function readOkJson(response: Response): Promise<unknown>\nexport async function readOkJson<T>(response: Response, parse: (data: unknown) => T): Promise<T>\nexport async function readOkJson<T>(response: Response, parse?: (data: unknown) => T): Promise<T | unknown> {\n await requireOk(response)\n let data: unknown\n try {\n data = await response.json()\n } catch (error) {\n throw new Error('The response was not valid JSON.', { cause: error })\n }\n return parse ? parse(data) : data\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport {\n asyncErrorMessage,\n defaultIsEmpty,\n resolveAsyncValue,\n type AsyncResolution,\n type AsyncResourceState,\n} from './state'\n\nexport interface AsyncLoadContext {\n /** Aborted when the inputs change, a retry supersedes this load, or the\n * component unmounts. Forward it to `fetch` so a superseded request stops. */\n readonly signal: AbortSignal\n}\n\nexport interface UseAsyncResourceOptions<T> {\n /**\n * The one load. Reject (or throw) to reach the `error` branch — a non-ok\n * response must reject too, which is what `requireOk`/`readOkJson` are for.\n * Read from a ref internally, so an inline arrow does not re-trigger; `deps`\n * is what declares when the load must run again.\n */\n load: (context: AsyncLoadContext) => Promise<T>\n /** Re-runs the load when any entry changes by `Object.is`, like `useEffect`. */\n deps?: readonly unknown[]\n /** `false` holds the resource at `idle` and runs nothing — for inputs that are\n * not resolved yet. Flipping it to `true` starts the load. */\n enabled?: boolean\n /** First-render seed (an SSR/loader page). The hook starts resolved and skips\n * the first load. Read once — later identity changes are ignored, so a\n * revalidating loader belongs in `deps`, not here. */\n initialValue?: T\n /** Splits a successful load into `empty` vs `ready`. Default: `defaultIsEmpty`. */\n isEmpty?: (value: T) => boolean\n /** Maps a thrown value to the message the `error` branch renders. */\n errorMessage?: (error: unknown) => string\n}\n\n/** Bumps a token when any dependency changes identity, so the effect's own\n * dependency list stays a fixed length whatever the caller passes. */\nfunction useChangeToken(deps: readonly unknown[]): number {\n const ref = useRef<{ deps: readonly unknown[]; token: number }>({ deps, token: 0 })\n const changed =\n ref.current.deps.length !== deps.length || deps.some((dep, index) => !Object.is(dep, ref.current.deps[index]))\n if (changed) ref.current = { deps, token: ref.current.token + 1 }\n return ref.current.token\n}\n\nconst NO_DEPS: readonly unknown[] = []\n\n/**\n * The five-state fetch machine: `idle | loading | error | empty | ready`.\n *\n * What it guarantees, and what the hand-rolled versions it replaces did not:\n *\n * - a rejected load lands on `error` with a message and a `retry`, never on an\n * empty list;\n * - `empty` is only reachable from a load that actually succeeded;\n * - a superseded load (inputs changed, retry pressed, component unmounted) is\n * aborted and its late result is discarded; resolutions are bound to their\n * input identity, including the render before the next effect runs.\n */\nexport function useAsyncResource<T>({\n load,\n deps = NO_DEPS,\n enabled = true,\n initialValue,\n isEmpty,\n errorMessage,\n}: UseAsyncResourceOptions<T>): AsyncResourceState<T> {\n // Include enablement and retries in the identity: stale data must not be\n // returned during the render before an effect clears the previous request.\n const [reloadKey, setReloadKey] = useState(0)\n const token = useChangeToken([enabled, reloadKey, ...deps])\n const options = useRef({ load, isEmpty, errorMessage })\n options.current = { load, isEmpty, errorMessage }\n const [settled, setSettled] = useState<{ token: number; resolution: AsyncResolution<T> }>(() => ({\n token,\n resolution: initialValue === undefined\n ? { status: 'loading' }\n : resolveAsyncValue(initialValue, isEmpty ?? defaultIsEmpty),\n }))\n // A seed belongs only to its first identity. Effect replay does not consume\n // it, but a dependency change, re-enable, or explicit retry does.\n const seededToken = useRef(initialValue === undefined ? null : token)\n\n useEffect(() => {\n if (!enabled || seededToken.current === token) return\n const controller = new AbortController()\n const commit = (resolution: AsyncResolution<T>) => {\n if (!controller.signal.aborted) setSettled({ token, resolution })\n }\n commit({ status: 'loading' })\n void (async () => {\n try {\n const value = await options.current.load({ signal: controller.signal })\n if (!controller.signal.aborted) commit(resolveAsyncValue(value, options.current.isEmpty ?? defaultIsEmpty))\n } catch (error) {\n if (!controller.signal.aborted) commit({\n status: 'error',\n message: options.current.errorMessage?.(error) ?? asyncErrorMessage(error),\n error,\n })\n }\n })()\n return () => controller.abort()\n }, [token, enabled])\n\n const retry = useCallback(() => setReloadKey(key => key + 1), [])\n return useMemo<AsyncResourceState<T>>(() => ({\n ...(!enabled ? { status: 'idle' as const }\n : settled.token === token ? settled.resolution : { status: 'loading' as const }),\n retry,\n }), [enabled, settled, token, retry])\n}\n","import { useCallback, useMemo, useRef, useState } from 'react'\n\nimport { asyncErrorMessage, readOkJson, requireOk } from './state'\nimport type { AsyncLoadContext } from './use-async-resource'\n\n/**\n * The brand that makes \"Saved\" unreachable without a confirmed write.\n *\n * A resolved promise is not a success: `fetch` resolves on a 404, and the\n * shipped defect this closes is a save button that rendered \"Saved\" because the\n * only thing awaited was that the request came back at all. The success variant\n * therefore carries a symbol no object literal can spell, so the only way into\n * `succeeded` is `confirmWrite` / `confirmResponse` / `confirmJson` — each of\n * which has already checked that the write landed.\n */\nexport const CONFIRMED_WRITE: unique symbol = Symbol('agent-app.confirmed-write')\n\nexport interface MutationConfirmed<T> {\n readonly succeeded: true\n readonly value: T\n readonly [CONFIRMED_WRITE]: true\n}\n\nexport interface MutationRejected {\n readonly succeeded: false\n /** Safe to render: what the user is told the write did not do. */\n readonly message: string\n readonly error?: unknown\n}\n\nexport type MutationOutcome<T> = MutationConfirmed<T> | MutationRejected\n\n/** Confirms a write whose success is already established (an SDK call that\n * throws on failure, a store returning its own typed outcome). The deliberate\n * act is the point — this call is what an audit greps for. */\nexport function confirmWrite<T>(value: T): MutationConfirmed<T> {\n return { succeeded: true, value, [CONFIRMED_WRITE]: true }\n}\n\n/** A write that did not land. Constructible by hand: failing loud is never the\n * direction that needs guarding. */\nexport function rejectWrite(message: string, error?: unknown): MutationRejected {\n return error === undefined ? { succeeded: false, message } : { succeeded: false, message, error }\n}\n\n/** Confirms only a 2xx response. A 404/500 becomes a rejection carrying the\n * status — never a success, whatever the promise did. */\nexport async function confirmResponse(response: Response): Promise<MutationOutcome<Response>> {\n try {\n return confirmWrite(await requireOk(response))\n } catch (error) {\n return rejectWrite(asyncErrorMessage(error), error)\n }\n}\n\n/** `confirmResponse` + a JSON body. A non-ok status, an unreadable body and a\n * throwing `parse` are all rejections. */\nexport async function confirmJson<T>(response: Response, parse: (data: unknown) => T): Promise<MutationOutcome<T>> {\n try {\n return confirmWrite(await readOkJson(response, parse))\n } catch (error) {\n return rejectWrite(asyncErrorMessage(error), error)\n }\n}\n\n/** True only for a value built by one of the confirm helpers. Takes `unknown`\n * because an untyped consumer can return anything from `mutate`. */\nexport function isConfirmedWrite<T>(outcome: unknown): outcome is MutationConfirmed<T> {\n if (typeof outcome !== 'object' || outcome === null) return false\n return (outcome as { readonly [CONFIRMED_WRITE]?: unknown })[CONFIRMED_WRITE] === true\n}\n\nfunction asRejection(outcome: unknown): MutationRejected | null {\n if (typeof outcome !== 'object' || outcome === null) return null\n const candidate = outcome as { succeeded?: unknown; message?: unknown; error?: unknown }\n if (candidate.succeeded !== false) return null\n const message = typeof candidate.message === 'string' && candidate.message.trim() !== ''\n ? candidate.message\n : UNCONFIRMED_MESSAGE\n return rejectWrite(message, candidate.error)\n}\n\nexport type MutationState<T> =\n | { readonly status: 'idle' }\n | { readonly status: 'pending' }\n | { readonly status: 'succeeded'; readonly value: T }\n | { readonly status: 'failed'; readonly message: string; readonly error: unknown }\n\nexport interface UseConfirmedMutationOptions<TInput, TValue> {\n /** Default replaces the observed run. `reject` keeps the current request\n * and refuses another invocation before any second write can start. */\n concurrency?: 'replace' | 'reject'\n /**\n * Performs the write and returns a confirmation. Anything else — including a\n * hand-written `{ succeeded: true }` — is treated as a failed write, because\n * an unbranded object is exactly the shape produced by code that never\n * checked the response.\n *\n * The context signal aborts only when a later `run` supersedes this one. It is\n * NOT aborted on unmount: a write the user asked for must not be cancelled by\n * navigating away.\n */\n mutate: (input: TInput, context: AsyncLoadContext) => Promise<MutationOutcome<TValue>>\n /** Fires after the state reaches `succeeded` (latest run only). */\n onSucceeded?: (value: TValue) => void\n /** Fires after the state reaches `failed` (latest run only). */\n onFailed?: (message: string, error: unknown) => void\n /** Maps a thrown value to the message the `failed` state renders. */\n errorMessage?: (error: unknown) => string\n}\n\nexport interface ConfirmedMutation<TInput, TValue> {\n readonly state: MutationState<TValue>\n /** Runs the write. Never rejects — the outcome is returned and mirrored into\n * `state`. Concurrent runs are last-write-wins unless `concurrency: 'reject'`\n * is configured. Server-side idempotency is still required. */\n readonly run: (input: TInput) => Promise<MutationOutcome<TValue>>\n /** Back to `idle` (dismisses a \"Saved\" or error affordance). */\n readonly reset: () => void\n}\n\nconst UNCONFIRMED_MESSAGE = 'The write could not be confirmed.'\n\nconst UNCONFIRMED_CONTRACT =\n 'mutate() resolved without a confirmation. Return confirmWrite(value), confirmResponse(response) or confirmJson(response, parse) so the success state proves the write landed.'\n\n/**\n * `idle | pending | succeeded | failed` over a write that must confirm itself.\n *\n * `succeeded` is reachable only through a branded confirmation, so the audited\n * \"Saved on a 404\" bug cannot be written with this hook: the 404 path produces\n * `failed` carrying the status, and a `mutate` that forgot to check produces\n * `failed` carrying the contract violation rather than a success it never\n * earned.\n */\nexport function useConfirmedMutation<TInput, TValue>({\n mutate,\n onSucceeded,\n onFailed,\n errorMessage,\n concurrency = 'replace',\n}: UseConfirmedMutationOptions<TInput, TValue>): ConfirmedMutation<TInput, TValue> {\n const options = useRef({ mutate, onSucceeded, onFailed, errorMessage, concurrency })\n options.current = { mutate, onSucceeded, onFailed, errorMessage, concurrency }\n\n const [state, setState] = useState<MutationState<TValue>>({ status: 'idle' })\n const seqRef = useRef(0)\n const inFlightRef = useRef<{ controller: AbortController; pending: boolean } | null>(null)\n\n const run = useCallback(async (input: TInput): Promise<MutationOutcome<TValue>> => {\n if (options.current.concurrency === 'reject' && inFlightRef.current?.pending)\n return rejectWrite('Another operation is still in progress.')\n inFlightRef.current?.controller.abort()\n const controller = new AbortController()\n inFlightRef.current = { controller, pending: true }\n const seq = ++seqRef.current\n\n setState({ status: 'pending' })\n\n let outcome: MutationOutcome<TValue>\n try {\n const returned: unknown = await options.current.mutate(input, { signal: controller.signal })\n outcome = isConfirmedWrite<TValue>(returned)\n ? returned\n : asRejection(returned) ?? rejectWrite(UNCONFIRMED_MESSAGE, new Error(UNCONFIRMED_CONTRACT))\n } catch (error) {\n outcome = rejectWrite(options.current.errorMessage?.(error) ?? asyncErrorMessage(error), error)\n }\n\n // A superseded run reports its own outcome to its own caller and never\n // repaints the state a newer run owns.\n if (seq !== seqRef.current) return outcome\n if (inFlightRef.current) inFlightRef.current.pending = false\n\n if (outcome.succeeded) {\n setState({ status: 'succeeded', value: outcome.value })\n options.current.onSucceeded?.(outcome.value)\n } else {\n setState({ status: 'failed', message: outcome.message, error: outcome.error })\n options.current.onFailed?.(outcome.message, outcome.error)\n }\n return outcome\n }, [])\n\n const reset = useCallback(() => {\n seqRef.current += 1\n inFlightRef.current?.controller.abort()\n inFlightRef.current = null\n setState({ status: 'idle' })\n }, [])\n\n return useMemo(() => ({ state, run, reset }), [state, run, reset])\n}\n","import { isValidElement, type ReactElement, type ReactNode } from 'react'\n\nimport { Button, EmptyState } from '@tangle-network/ui/primitives'\n\nimport type { AsyncResourceState } from './state'\nimport type { MutationState } from './use-confirmed-mutation'\n\nexport interface AsyncEmptyAction {\n label: string\n onClick: () => void\n}\n\nexport interface AsyncEmptySpec {\n /** What is not there, in the reader's words (\"No templates yet\"). Required:\n * an empty state with nothing to say is the state this module exists to\n * stop being mistaken for a failure. */\n title: string\n description?: string\n /** The next action. An element is rendered as supplied (a link, a dialog\n * trigger); the object form renders the standard button. */\n action?: AsyncEmptyAction | ReactElement\n}\n\nexport interface AsyncErrorRenderProps {\n message: string\n retry: () => void\n}\n\nexport interface AsyncViewProps<T> {\n state: AsyncResourceState<T>\n /** Rendered only for `ready`, with the loaded value — the branch cannot be\n * entered without one. */\n children: (value: T) => ReactNode\n /** Required: `empty` must name what is missing and what to do about it. */\n empty: AsyncEmptySpec | ReactElement\n /** Must return an element. Returning nothing is what produced the blank\n * screens this component replaces, so a nullish return falls back to the\n * built-in block rather than rendering nothing. */\n renderLoading?: () => ReactElement\n renderError?: (props: AsyncErrorRenderProps) => ReactElement\n /** `idle` renders the loading block by default — from the reader's side,\n * \"not started\" and \"in flight\" are the same wait. */\n renderIdle?: () => ReactElement\n loadingLabel?: string\n retryLabel?: string\n /** Applied to the wrapper around the non-`ready` branches. `ready` renders the\n * children with no wrapper element, so grids and lists keep their layout. */\n className?: string\n}\n\nconst BLOCK_CLASS = 'flex flex-col items-center justify-center gap-2 px-4 py-10 text-center'\n\n/**\n * Renders the branch the state is actually in.\n *\n * The three anti-patterns this replaces (`catch` that only clears a loading\n * flag, early return on a non-ok response, bare `null` while loading) all end at\n * the same rendered output as a successful-but-empty load. Here every branch is\n * reached from a different variant and each renders visibly: `loading` and\n * `idle` render a labelled busy block, `error` renders the message plus the\n * retry, `empty` renders the caller's copy and next action, and `ready` is the\n * only branch with a value to hand to `children`.\n */\nexport function AsyncView<T>({\n state,\n children,\n empty,\n renderLoading,\n renderError,\n renderIdle,\n loadingLabel = 'Loading…',\n retryLabel = 'Retry',\n className,\n}: AsyncViewProps<T>): ReactElement {\n if (state.status === 'ready') return <>{children(state.value)}</>\n\n // Every `??` here is the guarantee: a custom renderer that returns nothing\n // leaves the reader with the built-in block, never a blank region.\n const branch = (): ReactNode => {\n switch (state.status) {\n case 'idle':\n return renderIdle?.() ?? <LoadingBlock label={loadingLabel} />\n case 'loading':\n return renderLoading?.() ?? <LoadingBlock label={loadingLabel} />\n case 'error':\n return (\n renderError?.({ message: state.message, retry: state.retry }) ?? (\n <ErrorBlock message={state.message} retry={state.retry} retryLabel={retryLabel} />\n )\n )\n case 'empty':\n if (isValidElement(empty)) return empty\n return typeof empty === 'object' && empty !== null && typeof empty.title === 'string' ? (\n <EmptyBlock spec={empty} />\n ) : (\n <EmptyBlock spec={{ title: 'Nothing here yet.' }} />\n )\n }\n }\n\n // The busy signal lives on the WRAPPER, not inside the loading block, for the\n // same reason every `??` above exists: `renderLoading` is an escape hatch, and\n // a caller filling it with their own spinner would otherwise take the\n // announcement with them — a screen reader would hear nothing for exactly the\n // products that styled their wait state. The wrapper wraps every non-`ready`\n // branch, so it cannot be opted out of.\n //\n // `aria-busy` is emitted in BOTH states rather than only while loading: a\n // region that gains the attribute at the moment it goes busy, and drops it on\n // arrival, gives assistive tech no transition to report. The `error` branch\n // keeps its own `role=\"alert\"` and takes no live region here, because nesting\n // one inside another is what produces a doubled announcement.\n const busy = state.status === 'loading' || state.status === 'idle'\n const failed = state.status === 'error'\n return (\n <div\n data-async-state={state.status}\n className={className}\n role={failed ? undefined : 'status'}\n aria-live={failed ? undefined : 'polite'}\n aria-busy={busy}\n >\n {branch()}\n </div>\n )\n}\n\nfunction LoadingBlock({ label }: { label: string }): ReactElement {\n return (\n <div className={BLOCK_CLASS}>\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n </div>\n )\n}\n\nfunction ErrorBlock({\n message,\n retry,\n retryLabel,\n}: {\n message: string\n retry: () => void\n retryLabel: string\n}): ReactElement {\n return (\n <div role=\"alert\" className={BLOCK_CLASS}>\n <p className=\"max-w-md text-sm text-muted-foreground\">{message}</p>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={retry}>{retryLabel}</Button>\n </div>\n )\n}\n\nfunction EmptyBlock({ spec }: { spec: AsyncEmptySpec }): ReactElement {\n const action = spec.action\n return <EmptyState title={spec.title} description={spec.description} action={\n isValidElement(action) ? action : action ? <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={(action as AsyncEmptyAction).onClick}>{(action as AsyncEmptyAction).label}</Button> : undefined\n } />\n}\n\nexport interface MutationStatusLabels {\n pending?: string\n succeeded?: string\n}\n\nexport interface MutationStatusProps<T> {\n state: MutationState<T>\n labels?: MutationStatusLabels\n className?: string\n}\n\n/**\n * The write's own status line. \"Saved\" renders only from `succeeded`, which\n * `useConfirmedMutation` can only reach through a confirmed write, so the label\n * cannot appear over a failed request.\n */\nexport function MutationStatus<T>({ state, labels, className }: MutationStatusProps<T>): ReactElement | null {\n if (state.status === 'idle') return null\n if (state.status === 'pending') {\n return (\n <span role=\"status\" aria-live=\"polite\" aria-busy={true} className={className ?? 'text-xs text-muted-foreground'}>\n {labels?.pending ?? 'Saving…'}\n </span>\n )\n }\n if (state.status === 'succeeded') {\n return (\n <span role=\"status\" aria-live=\"polite\" aria-busy={false} className={className ?? 'text-xs text-muted-foreground'}>\n {labels?.succeeded ?? 'Saved'}\n </span>\n )\n }\n return (\n <span role=\"alert\" className={className ?? 'text-xs text-destructive'}>\n {state.message}\n </span>\n )\n}\n"],"mappings":"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","names":["useCallback","useMemo","useRef","useState","useRef","useState","useCallback","useMemo"]}