@tangle-network/agent-app 0.60.177 → 0.60.178

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.
@@ -7,17 +7,16 @@ import {
7
7
  formatWhen,
8
8
  hubActionReceipt,
9
9
  presentHubAction
10
- } from "../chunk-MCAIZKQ5.js";
10
+ } from "../chunk-CPNJRLPN.js";
11
11
 
12
12
  // src/hub-approvals-react/dock.tsx
13
- import { useEffect, useMemo, useRef, useState as useState2 } from "react";
13
+ import { useEffect as useEffect3, useMemo, useRef, useState as useState4 } from "react";
14
14
  import { Check, ChevronDown, ExternalLink, Loader2, X } from "lucide-react";
15
- import { Button as Button2, Label, RadioGroup, RadioGroupItem } from "@tangle-network/ui/primitives";
15
+ import { Button as Button3, Label as Label2, RadioGroup, RadioGroupItem } from "@tangle-network/ui/primitives";
16
16
 
17
17
  // src/hub-approvals-react/parts.tsx
18
18
  import { useState } from "react";
19
19
  import { ChevronRight, Clock, Zap } from "lucide-react";
20
- import { ProviderIcon } from "@tangle-network/sandbox-ui/integrations";
21
20
  import { Button, StatusPill } from "@tangle-network/ui/primitives";
22
21
  import { jsx, jsxs } from "react/jsx-runtime";
23
22
  var PHASE_TONES = {
@@ -47,13 +46,15 @@ function HubApprovalWaitNote({ wait, className = "" }) {
47
46
  ] });
48
47
  }
49
48
  function HubProviderMark({ providerId, name, size = 28 }) {
50
- const inner = Math.round(size * 0.64);
51
49
  return /* @__PURE__ */ jsx(
52
50
  "span",
53
51
  {
54
52
  className: "flex shrink-0 items-center justify-center rounded-lg border border-border bg-background",
55
53
  style: { width: size, height: size },
56
- children: /* @__PURE__ */ jsx(ProviderIcon, { id: providerId, displayName: name, size: inner, className: "rounded-sm" })
54
+ title: name,
55
+ "data-provider": providerId,
56
+ "aria-hidden": true,
57
+ children: /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-muted-foreground", children: name.charAt(0).toUpperCase() })
57
58
  }
58
59
  );
59
60
  }
@@ -115,121 +116,196 @@ function relativeTime(at, now = Date.now()) {
115
116
  // src/hub-approvals-react/preview.tsx
116
117
  import { ArrowRight, CalendarDays, GitBranch, GitCommitHorizontal, GitPullRequest, MapPin, Phone, Users } from "lucide-react";
117
118
  import { ApprovalDiffSummary } from "@tangle-network/ui/run";
119
+
120
+ // src/hub-approvals-react/email.tsx
121
+ import { useEffect, useState as useState2 } from "react";
118
122
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
119
- function plainText(body, html) {
120
- if (!html) return body;
121
- return body.replace(/<(script|style)[\s\S]*?<\/\1>/gi, "").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h[1-6]|tr)>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\n{3,}/g, "\n\n").trim();
123
+ var EMAIL_TAGS = new Set("a abbr b blockquote br caption center code col colgroup dd del div dl dt em font h1 h2 h3 h4 h5 h6 hr i img li ol p pre s small span strong sub sup table tbody td tfoot th thead tr u ul".split(" "));
124
+ var EMAIL_ATTRIBUTES = new Set("align alt bgcolor border cellpadding cellspacing class color colspan dir face height lang rowspan size style title valign width".split(" "));
125
+ function embeddedPreviewImage(value) {
126
+ return value && /^data:image\/(?:png|jpeg|jpg|gif|webp);base64,[a-z0-9+/=\s]+$/i.test(value) ? value : void 0;
127
+ }
128
+ function emailDocument(body) {
129
+ const template = document.createElement("template");
130
+ template.innerHTML = body;
131
+ const target = document.createElement("div");
132
+ function copy(source, parent) {
133
+ if (source.nodeType === Node.TEXT_NODE) {
134
+ parent.appendChild(document.createTextNode(source.textContent ?? ""));
135
+ return;
136
+ }
137
+ if (!(source instanceof HTMLElement) || !EMAIL_TAGS.has(source.localName)) return;
138
+ const element = document.createElement(source.localName);
139
+ for (const attribute of source.attributes) {
140
+ if (EMAIL_ATTRIBUTES.has(attribute.name)) element.setAttribute(attribute.name, attribute.value);
141
+ }
142
+ if (source.localName === "a") {
143
+ const href = source.getAttribute("href") ?? "";
144
+ if (/^(https?:|mailto:)/i.test(href.trim())) element.setAttribute("href", href);
145
+ element.setAttribute("target", "_blank");
146
+ element.setAttribute("rel", "noopener noreferrer");
147
+ }
148
+ if (source.localName === "img") {
149
+ const image = embeddedPreviewImage(source.getAttribute("src") ?? void 0);
150
+ if (image) element.setAttribute("src", image);
151
+ }
152
+ for (const child of source.childNodes) copy(child, element);
153
+ parent.appendChild(element);
154
+ }
155
+ for (const child of template.content.childNodes) copy(child, target);
156
+ return {
157
+ document: `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; form-action 'none'; base-uri 'none'"><meta name="referrer" content="no-referrer"><style>body{margin:0;padding:16px 20px;color:#202124;background:#fff;font:14px/1.6 Arial,sans-serif;overflow-wrap:anywhere}a{color:#1155cc}img{max-width:100%;height:auto}table{max-width:100%}pre{white-space:pre-wrap}</style></head><body>${target.innerHTML}</body></html>`,
158
+ text: target.textContent ?? ""
159
+ };
160
+ }
161
+ function EmailPreview({ preview, account }) {
162
+ const [html, setHtml] = useState2(null);
163
+ useEffect(() => {
164
+ if (preview.html && preview.body) setHtml({ source: preview.body, ...emailDocument(preview.body) });
165
+ else setHtml(null);
166
+ }, [preview.body, preview.html]);
167
+ const sender = preview.from ?? account ?? "Sender unavailable";
168
+ const mailbox = /^(.*?)\s*<([^>]+)>$/.exec(sender);
169
+ const name = preview.fromName ?? mailbox?.[1]?.replace(/^"|"$/g, "") ?? sender;
170
+ const address = mailbox?.[2] ?? sender;
171
+ const currentHtml = html?.source === preview.body ? html : null;
172
+ const snippet = preview.previewText ?? (preview.html ? currentHtml?.text : preview.body)?.replace(/\s+/g, " ").trim();
173
+ return /* @__PURE__ */ jsxs2("div", { "data-hub-email-preview": true, className: "overflow-hidden rounded-lg border border-border bg-white font-[Arial,sans-serif] text-[#202124] [color-scheme:light]", children: [
174
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 items-baseline gap-3 border-b border-[#e8eaed] bg-[#f2f6fc] px-4 py-3 text-sm", "aria-label": "Inbox preview", children: [
175
+ /* @__PURE__ */ jsx2("span", { className: "max-w-[30%] shrink-0 truncate font-semibold", children: name }),
176
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0 truncate", children: [
177
+ /* @__PURE__ */ jsx2("strong", { children: preview.subject ?? "(no subject)" }),
178
+ snippet && /* @__PURE__ */ jsxs2("span", { className: "text-[#5f6368]", children: [
179
+ " \u2014 ",
180
+ snippet
181
+ ] })
182
+ ] })
183
+ ] }),
184
+ /* @__PURE__ */ jsxs2("div", { className: "px-4 pt-5 sm:px-5", children: [
185
+ /* @__PURE__ */ jsx2("h4", { className: "break-words text-[22px] font-normal leading-7", children: preview.subject ?? (preview.reply ? "Reply" : "(no subject)") }),
186
+ /* @__PURE__ */ jsxs2("div", { className: "mb-4 mt-5 flex items-start gap-3", children: [
187
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": true, className: "flex size-9 shrink-0 items-center justify-center rounded-full bg-[#e8eaed] text-base text-[#5f6368]", children: name.charAt(0).toUpperCase() }),
188
+ /* @__PURE__ */ jsxs2("dl", { className: "min-w-0 space-y-1 text-[13px]", children: [
189
+ /* @__PURE__ */ jsxs2("div", { className: "break-words", children: [
190
+ /* @__PURE__ */ jsx2("dt", { className: "sr-only", children: "From" }),
191
+ /* @__PURE__ */ jsxs2("dd", { children: [
192
+ /* @__PURE__ */ jsx2("strong", { children: name }),
193
+ name !== address && /* @__PURE__ */ jsxs2("span", { className: "text-[#5f6368]", children: [
194
+ " <",
195
+ address,
196
+ ">"
197
+ ] })
198
+ ] })
199
+ ] }),
200
+ /* @__PURE__ */ jsxs2("div", { className: "break-words text-[#5f6368]", children: [
201
+ /* @__PURE__ */ jsx2("dt", { className: "inline", children: "To " }),
202
+ /* @__PURE__ */ jsx2("dd", { className: "inline", children: preview.to.join(", ") || "Recipient unavailable" })
203
+ ] }),
204
+ preview.cc.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "break-words text-[#5f6368]", children: [
205
+ /* @__PURE__ */ jsx2("dt", { className: "inline", children: "Cc " }),
206
+ /* @__PURE__ */ jsx2("dd", { className: "inline", children: preview.cc.join(", ") })
207
+ ] })
208
+ ] })
209
+ ] })
210
+ ] }),
211
+ preview.html ? currentHtml ? /* @__PURE__ */ jsx2("iframe", { title: "Email body", sandbox: "", referrerPolicy: "no-referrer", srcDoc: currentHtml.document, className: "h-[360px] w-full border-0 sm:h-[440px]" }) : /* @__PURE__ */ jsx2("p", { role: "status", className: "px-5 pb-5 text-sm", children: "Rendering email\u2026" }) : /* @__PURE__ */ jsx2("div", { className: "whitespace-pre-wrap break-words px-4 pb-6 text-[14px] leading-[1.6] sm:px-5", children: linkedText(preview.body ?? "") }),
212
+ preview.bcc.length > 0 && /* @__PURE__ */ jsxs2("p", { className: "border-t border-[#e8eaed] px-4 py-2 text-[13px] text-[#5f6368]", children: [
213
+ "Bcc (sender only): ",
214
+ preview.bcc.join(", ")
215
+ ] })
216
+ ] });
122
217
  }
218
+ function linkedText(value) {
219
+ return value.split(/(https?:\/\/[^\s<>"']+)/g).map((part, index) => /^https?:\/\//i.test(part) ? /* @__PURE__ */ jsx2("a", { href: part, onClick: (event) => event.preventDefault(), title: "Link shown for review", className: "text-[#1155cc] underline", children: part }, index) : part);
220
+ }
221
+
222
+ // src/hub-approvals-react/preview.tsx
223
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
123
224
  function initial(name) {
124
225
  return (name?.trim().replace(/^@/, "").charAt(0) || "?").toUpperCase();
125
226
  }
126
227
  function Panel({ children, className = "" }) {
127
- return /* @__PURE__ */ jsx2("div", { className: `rounded-lg border border-border bg-background ${className}`, children });
228
+ return /* @__PURE__ */ jsx3("div", { className: `rounded-lg border border-border bg-background ${className}`, children });
128
229
  }
129
230
  function PullRequestPreview({ preview }) {
130
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
131
- /* @__PURE__ */ jsxs2(Panel, { className: "px-3 py-3", children: [
132
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2 text-sm text-muted-foreground", children: [
133
- /* @__PURE__ */ jsx2(GitPullRequest, { "aria-hidden": true, className: "size-4 shrink-0" }),
134
- preview.repository && /* @__PURE__ */ jsx2("span", { className: "font-medium text-foreground", children: preview.repository }),
135
- preview.head && preview.base && /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [
136
- /* @__PURE__ */ jsx2(Chip, { mono: true, children: preview.head }),
137
- /* @__PURE__ */ jsx2(ArrowRight, { "aria-label": "into", className: "size-4 shrink-0" }),
138
- /* @__PURE__ */ jsx2(Chip, { mono: true, children: preview.base })
231
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
232
+ /* @__PURE__ */ jsxs3(Panel, { className: "px-3 py-3", children: [
233
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2 text-sm text-muted-foreground", children: [
234
+ /* @__PURE__ */ jsx3(GitPullRequest, { "aria-hidden": true, className: "size-4 shrink-0" }),
235
+ preview.repository && /* @__PURE__ */ jsx3("span", { className: "font-medium text-foreground", children: preview.repository }),
236
+ preview.head && preview.base && /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [
237
+ /* @__PURE__ */ jsx3(Chip, { mono: true, children: preview.head }),
238
+ /* @__PURE__ */ jsx3(ArrowRight, { "aria-label": "into", className: "size-4 shrink-0" }),
239
+ /* @__PURE__ */ jsx3(Chip, { mono: true, children: preview.base })
139
240
  ] }),
140
- preview.draft && /* @__PURE__ */ jsx2("span", { className: "rounded-md border border-border px-1.5 py-0.5 text-sm text-muted-foreground", children: "Draft" })
141
- ] }),
142
- preview.title && /* @__PURE__ */ jsx2("p", { className: "mt-2 text-base font-semibold leading-6 text-foreground", children: preview.title }),
143
- preview.body && /* @__PURE__ */ jsx2("p", { className: "mt-1 line-clamp-3 whitespace-pre-line text-sm leading-6 text-muted-foreground sm:line-clamp-4", children: preview.body })
144
- ] }),
145
- preview.files.length > 0 && /* @__PURE__ */ jsx2(ApprovalDiffSummary, { files: preview.files })
146
- ] });
147
- }
148
- function EmailPreview({ preview, account }) {
149
- const rows = [
150
- ...account ? [["From", account]] : [],
151
- ...preview.to.length ? [["To", preview.to.join(", ")]] : [],
152
- ...preview.cc.length ? [["Cc", preview.cc.join(", ")]] : [],
153
- ...preview.bcc.length ? [["Bcc", preview.bcc.join(", ")]] : []
154
- ];
155
- const body = preview.body ? plainText(preview.body, preview.html) : "";
156
- return /* @__PURE__ */ jsxs2(Panel, { className: "overflow-hidden", children: [
157
- /* @__PURE__ */ jsxs2("dl", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 border-b border-border px-3 py-2.5 text-sm", children: [
158
- rows.map(([label, value]) => /* @__PURE__ */ jsxs2("div", { className: "contents", children: [
159
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground", children: label }),
160
- /* @__PURE__ */ jsx2("dd", { className: "break-words text-foreground", children: value })
161
- ] }, label)),
162
- preview.subject && /* @__PURE__ */ jsxs2("div", { className: "contents", children: [
163
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground", children: "Subject" }),
164
- /* @__PURE__ */ jsx2("dd", { className: "break-words font-semibold text-foreground", children: preview.subject })
241
+ preview.draft && /* @__PURE__ */ jsx3("span", { className: "rounded-md border border-border px-1.5 py-0.5 text-sm text-muted-foreground", children: "Draft" })
165
242
  ] }),
166
- preview.reply && !preview.subject && /* @__PURE__ */ jsxs2("div", { className: "contents", children: [
167
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground", children: "Reply" }),
168
- /* @__PURE__ */ jsx2("dd", { className: "text-foreground", children: "In the existing conversation" })
169
- ] })
243
+ preview.title && /* @__PURE__ */ jsx3("p", { className: "mt-2 text-base font-semibold leading-6 text-foreground", children: preview.title }),
244
+ preview.body && /* @__PURE__ */ jsx3("p", { className: "mt-1 line-clamp-3 whitespace-pre-line text-sm leading-6 text-muted-foreground sm:line-clamp-4", children: preview.body })
170
245
  ] }),
171
- body && /* @__PURE__ */ jsx2("div", { className: "max-h-60 overflow-y-auto whitespace-pre-wrap break-words px-3 py-3 text-sm leading-6 text-foreground", children: body })
246
+ preview.files.length > 0 && /* @__PURE__ */ jsx3(ApprovalDiffSummary, { files: preview.files })
172
247
  ] });
173
248
  }
174
249
  function PostPreview({ preview, account }) {
175
250
  if (preview.channel === "slack") {
176
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-3 py-3", children: [
177
- /* @__PURE__ */ jsx2("p", { className: "text-sm font-semibold text-foreground", children: preview.where ?? "Slack" }),
178
- /* @__PURE__ */ jsxs2("div", { className: "mt-2 flex gap-3", children: [
179
- /* @__PURE__ */ jsx2("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-foreground", children: initial(account) }),
180
- /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
181
- /* @__PURE__ */ jsx2("p", { className: "text-sm font-semibold text-foreground", children: account ?? "Your account" }),
182
- /* @__PURE__ */ jsx2("p", { className: "whitespace-pre-wrap break-words text-sm leading-6 text-foreground", children: preview.text })
251
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-3 py-3", children: [
252
+ /* @__PURE__ */ jsx3("p", { className: "text-sm font-semibold text-foreground", children: preview.where ?? "Slack" }),
253
+ /* @__PURE__ */ jsxs3("div", { className: "mt-2 flex gap-3", children: [
254
+ /* @__PURE__ */ jsx3("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-foreground", children: initial(account) }),
255
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
256
+ /* @__PURE__ */ jsx3("p", { className: "text-sm font-semibold text-foreground", children: account ?? "Your account" }),
257
+ /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap break-words text-sm leading-6 text-foreground", children: linkedText(preview.text) })
183
258
  ] })
184
259
  ] })
185
260
  ] });
186
261
  }
187
262
  const x = preview.channel === "x";
188
263
  const length = [...preview.text].length;
189
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-4 py-3", children: [
190
- /* @__PURE__ */ jsxs2("div", { className: "flex gap-3", children: [
191
- /* @__PURE__ */ jsx2("span", { className: `flex size-10 shrink-0 items-center justify-center rounded-full text-base font-semibold ${x ? "bg-foreground text-background" : "bg-[#0a66c2] text-white"}`, children: initial(account) }),
192
- /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
193
- /* @__PURE__ */ jsxs2("p", { className: "flex flex-wrap items-baseline gap-x-1.5 text-sm", children: [
194
- /* @__PURE__ */ jsx2("span", { className: "font-semibold text-foreground", children: account ?? "Your account" }),
195
- /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: x ? "\xB7 now" : "\xB7 Post \xB7 Anyone" })
264
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-4 py-3", children: [
265
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-3", children: [
266
+ /* @__PURE__ */ jsx3("span", { className: `flex size-10 shrink-0 items-center justify-center rounded-full text-base font-semibold ${x ? "bg-foreground text-background" : "bg-[#0a66c2] text-white"}`, children: initial(account) }),
267
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
268
+ /* @__PURE__ */ jsxs3("p", { className: "flex flex-wrap items-baseline gap-x-1.5 text-sm", children: [
269
+ /* @__PURE__ */ jsx3("span", { className: "font-semibold text-foreground", children: account ?? "Your account" }),
270
+ /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground", children: x ? "\xB7 now" : "\xB7 Post \xB7 Anyone" })
196
271
  ] }),
197
- /* @__PURE__ */ jsx2("p", { className: `mt-1 whitespace-pre-wrap break-words text-foreground ${x ? "text-base leading-6" : "text-sm leading-6"}`, children: preview.text }),
198
- preview.link && /* @__PURE__ */ jsx2("div", { className: "mt-3 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxs2("div", { className: "px-3 py-2", children: [
199
- /* @__PURE__ */ jsx2("p", { className: "truncate text-sm text-muted-foreground", children: preview.link.url.replace(/^https?:\/\//, "") }),
200
- preview.link.title && /* @__PURE__ */ jsx2("p", { className: "truncate text-sm font-semibold text-foreground", children: preview.link.title }),
201
- preview.link.description && /* @__PURE__ */ jsx2("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: preview.link.description })
272
+ /* @__PURE__ */ jsx3("p", { className: `mt-1 whitespace-pre-wrap break-words text-foreground ${x ? "text-base leading-6" : "text-sm leading-6"}`, children: linkedText(preview.text) }),
273
+ preview.images && preview.images.length > 0 && /* @__PURE__ */ jsx3("div", { className: "mt-3 grid grid-cols-2 gap-0.5 overflow-hidden rounded-xl border border-border", children: preview.images.map((image, index) => /* @__PURE__ */ jsx3("div", { className: `${preview.images.length === 1 ? "col-span-2 aspect-[16/9]" : "aspect-square"} overflow-hidden bg-muted`, children: embeddedPreviewImage(image.url) ? /* @__PURE__ */ jsx3("img", { src: image.url, alt: image.alt ?? `Post image ${index + 1}`, className: "size-full object-cover" }) : /* @__PURE__ */ jsx3("p", { className: "p-3 text-sm text-muted-foreground", children: "Image not embedded in this preview" }) }, index)) }),
274
+ preview.link && /* @__PURE__ */ jsx3("div", { className: "mt-3 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxs3("div", { className: "px-3 py-2", children: [
275
+ /* @__PURE__ */ jsx3("p", { className: "truncate text-sm text-muted-foreground", children: preview.link.url.replace(/^https?:\/\//, "") }),
276
+ preview.link.title && /* @__PURE__ */ jsx3("p", { className: "truncate text-sm font-semibold text-foreground", children: preview.link.title }),
277
+ preview.link.description && /* @__PURE__ */ jsx3("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: preview.link.description })
202
278
  ] }) })
203
279
  ] })
204
280
  ] }),
205
- x && /* @__PURE__ */ jsxs2("p", { className: `mt-2 text-right text-sm tabular-nums ${length > 280 ? "text-[var(--surface-danger-text)]" : "text-muted-foreground"}`, children: [
281
+ /* @__PURE__ */ jsxs3("p", { className: "mt-2 text-right text-sm tabular-nums text-muted-foreground", children: [
206
282
  length,
207
- " / 280"
283
+ " characters"
208
284
  ] })
209
285
  ] });
210
286
  }
211
287
  function PaymentPreview({ preview }) {
212
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-4 py-3", children: [
213
- /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground", children: preview.label.charAt(0).toUpperCase() + preview.label.slice(1) }),
214
- preview.amount !== void 0 && /* @__PURE__ */ jsx2("p", { className: "mt-0.5 text-2xl font-semibold tabular-nums text-foreground", children: formatMinorAmount(preview.amount, preview.currency) }),
215
- /* @__PURE__ */ jsxs2("div", { className: "mt-2 space-y-1 text-sm", children: [
216
- preview.customer && /* @__PURE__ */ jsxs2("p", { className: "text-muted-foreground", children: [
288
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-4 py-3", children: [
289
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: preview.label.charAt(0).toUpperCase() + preview.label.slice(1) }),
290
+ preview.amount !== void 0 && /* @__PURE__ */ jsx3("p", { className: "mt-0.5 text-2xl font-semibold tabular-nums text-foreground", children: formatMinorAmount(preview.amount, preview.currency) }),
291
+ /* @__PURE__ */ jsxs3("div", { className: "mt-2 space-y-1 text-sm", children: [
292
+ preview.customer && /* @__PURE__ */ jsxs3("p", { className: "text-muted-foreground", children: [
217
293
  "Customer ",
218
- /* @__PURE__ */ jsx2("span", { className: `text-foreground ${preview.customer.startsWith("cus_") ? "font-mono" : ""}`, children: preview.customer })
294
+ /* @__PURE__ */ jsx3("span", { className: `text-foreground ${preview.customer.startsWith("cus_") ? "font-mono" : ""}`, children: preview.customer })
219
295
  ] }),
220
- preview.description && /* @__PURE__ */ jsx2("p", { className: "text-foreground", children: preview.description })
296
+ preview.description && /* @__PURE__ */ jsx3("p", { className: "text-foreground", children: preview.description })
221
297
  ] })
222
298
  ] });
223
299
  }
224
300
  function SpeechPreview({ preview }) {
225
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-4 py-3", children: [
226
- /* @__PURE__ */ jsx2("blockquote", { className: "border-l-2 border-[var(--accent-text)] pl-3 text-base leading-7 text-foreground", children: preview.text }),
227
- (preview.voice || preview.seconds) && /* @__PURE__ */ jsxs2("div", { className: "mt-3 flex flex-wrap gap-2", children: [
228
- preview.voice && /* @__PURE__ */ jsxs2(Chip, { children: [
301
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-4 py-3", children: [
302
+ /* @__PURE__ */ jsx3("blockquote", { className: "border-l-2 border-[var(--accent-text)] pl-3 text-base leading-7 text-foreground", children: preview.text }),
303
+ (preview.voice || preview.seconds) && /* @__PURE__ */ jsxs3("div", { className: "mt-3 flex flex-wrap gap-2", children: [
304
+ preview.voice && /* @__PURE__ */ jsxs3(Chip, { children: [
229
305
  "Voice: ",
230
306
  preview.voice
231
307
  ] }),
232
- preview.seconds && /* @__PURE__ */ jsxs2(Chip, { children: [
308
+ preview.seconds && /* @__PURE__ */ jsxs3(Chip, { children: [
233
309
  "About ",
234
310
  preview.seconds,
235
311
  " s"
@@ -238,97 +314,245 @@ function SpeechPreview({ preview }) {
238
314
  ] });
239
315
  }
240
316
  function CallPreview({ preview }) {
241
- return /* @__PURE__ */ jsxs2(Panel, { className: "flex gap-3 px-4 py-3", children: [
242
- /* @__PURE__ */ jsx2("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx2(Phone, { "aria-hidden": true, className: "size-5 text-foreground" }) }),
243
- /* @__PURE__ */ jsxs2("div", { className: "min-w-0 space-y-1 text-sm", children: [
244
- /* @__PURE__ */ jsx2("p", { className: "text-base font-semibold tabular-nums text-foreground", children: preview.to ?? "Unknown number" }),
245
- preview.from && /* @__PURE__ */ jsxs2("p", { className: "text-muted-foreground", children: [
317
+ return /* @__PURE__ */ jsxs3(Panel, { className: "flex gap-3 px-4 py-3", children: [
318
+ /* @__PURE__ */ jsx3("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx3(Phone, { "aria-hidden": true, className: "size-5 text-foreground" }) }),
319
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 space-y-1 text-sm", children: [
320
+ /* @__PURE__ */ jsx3("p", { className: "text-base font-semibold tabular-nums text-foreground", children: preview.to ?? "Unknown number" }),
321
+ preview.from && /* @__PURE__ */ jsxs3("p", { className: "text-muted-foreground", children: [
246
322
  "From ",
247
- /* @__PURE__ */ jsx2("span", { className: "tabular-nums text-foreground", children: preview.from })
323
+ /* @__PURE__ */ jsx3("span", { className: "tabular-nums text-foreground", children: preview.from })
248
324
  ] }),
249
- preview.purpose && /* @__PURE__ */ jsx2("p", { className: "leading-6 text-foreground", children: preview.purpose })
325
+ preview.purpose && /* @__PURE__ */ jsx3("p", { className: "leading-6 text-foreground", children: preview.purpose })
250
326
  ] })
251
327
  ] });
252
328
  }
253
329
  function EventPreview({ preview }) {
254
330
  const start = preview.start ? new Date(preview.start) : null;
255
331
  const valid = start && !Number.isNaN(start.getTime());
256
- return /* @__PURE__ */ jsxs2(Panel, { className: "flex gap-4 px-4 py-3", children: [
257
- /* @__PURE__ */ jsxs2("div", { className: "flex w-14 shrink-0 flex-col items-center overflow-hidden rounded-lg border border-border text-center", children: [
258
- /* @__PURE__ */ jsx2("span", { className: "w-full bg-[var(--surface-danger-bg)] py-0.5 text-sm font-semibold uppercase text-[var(--surface-danger-text)]", children: valid ? start.toLocaleDateString(void 0, { month: "short" }) : /* @__PURE__ */ jsx2(CalendarDays, { "aria-hidden": true, className: "mx-auto size-4" }) }),
259
- /* @__PURE__ */ jsx2("span", { className: "py-1 text-xl font-semibold tabular-nums text-foreground", children: valid ? start.getDate() : "\u2013" })
332
+ return /* @__PURE__ */ jsxs3(Panel, { className: "flex gap-4 px-4 py-3", children: [
333
+ /* @__PURE__ */ jsxs3("div", { className: "flex w-14 shrink-0 flex-col items-center overflow-hidden rounded-lg border border-border text-center", children: [
334
+ /* @__PURE__ */ jsx3("span", { className: "w-full bg-[var(--surface-danger-bg)] py-0.5 text-sm font-semibold uppercase text-[var(--surface-danger-text)]", children: valid ? start.toLocaleDateString(void 0, { month: "short" }) : /* @__PURE__ */ jsx3(CalendarDays, { "aria-hidden": true, className: "mx-auto size-4" }) }),
335
+ /* @__PURE__ */ jsx3("span", { className: "py-1 text-xl font-semibold tabular-nums text-foreground", children: valid ? start.getDate() : "\u2013" })
260
336
  ] }),
261
- /* @__PURE__ */ jsxs2("div", { className: "min-w-0 space-y-1 text-sm", children: [
262
- /* @__PURE__ */ jsx2("p", { className: "text-base font-semibold text-foreground", children: preview.title ?? "Untitled event" }),
263
- preview.start && /* @__PURE__ */ jsx2("p", { className: "text-foreground", children: formatWhen(preview.start, preview.end) }),
264
- preview.location && /* @__PURE__ */ jsxs2("p", { className: "flex items-center gap-1.5 text-muted-foreground", children: [
265
- /* @__PURE__ */ jsx2(MapPin, { "aria-hidden": true, className: "size-4 shrink-0" }),
337
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 space-y-1 text-sm", children: [
338
+ /* @__PURE__ */ jsx3("p", { className: "text-base font-semibold text-foreground", children: preview.title ?? "Untitled event" }),
339
+ preview.start && /* @__PURE__ */ jsx3("p", { className: "text-foreground", children: formatWhen(preview.start, preview.end) }),
340
+ preview.location && /* @__PURE__ */ jsxs3("p", { className: "flex items-center gap-1.5 text-muted-foreground", children: [
341
+ /* @__PURE__ */ jsx3(MapPin, { "aria-hidden": true, className: "size-4 shrink-0" }),
266
342
  preview.location
267
343
  ] }),
268
- preview.attendees.length > 0 && /* @__PURE__ */ jsxs2("p", { className: "flex items-start gap-1.5 text-muted-foreground", children: [
269
- /* @__PURE__ */ jsx2(Users, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0" }),
270
- /* @__PURE__ */ jsx2("span", { className: "break-words", children: preview.attendees.join(", ") })
344
+ preview.attendees.length > 0 && /* @__PURE__ */ jsxs3("p", { className: "flex items-start gap-1.5 text-muted-foreground", children: [
345
+ /* @__PURE__ */ jsx3(Users, { "aria-hidden": true, className: "mt-0.5 size-4 shrink-0" }),
346
+ /* @__PURE__ */ jsx3("span", { className: "break-words", children: preview.attendees.join(", ") })
271
347
  ] }),
272
- preview.description && /* @__PURE__ */ jsx2("p", { className: "line-clamp-3 leading-6 text-foreground", children: preview.description })
348
+ preview.description && /* @__PURE__ */ jsx3("p", { className: "line-clamp-3 leading-6 text-foreground", children: preview.description })
273
349
  ] })
274
350
  ] });
275
351
  }
276
352
  function HubActionPreviewView({ preview, account }) {
277
353
  switch (preview.kind) {
278
354
  case "pull-request":
279
- return /* @__PURE__ */ jsx2(PullRequestPreview, { preview });
355
+ return /* @__PURE__ */ jsx3(PullRequestPreview, { preview });
280
356
  case "files":
281
- return preview.files.length > 0 ? /* @__PURE__ */ jsx2(ApprovalDiffSummary, { files: preview.files }) : null;
357
+ return preview.files.length > 0 ? /* @__PURE__ */ jsx3(ApprovalDiffSummary, { files: preview.files }) : null;
282
358
  case "commit":
283
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-3 py-3", children: [
284
- /* @__PURE__ */ jsxs2("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
285
- /* @__PURE__ */ jsx2(GitCommitHorizontal, { "aria-hidden": true, className: "size-4 shrink-0" }),
359
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-3 py-3", children: [
360
+ /* @__PURE__ */ jsxs3("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
361
+ /* @__PURE__ */ jsx3(GitCommitHorizontal, { "aria-hidden": true, className: "size-4 shrink-0" }),
286
362
  preview.repository ?? "Commit"
287
363
  ] }),
288
- preview.message && /* @__PURE__ */ jsx2("p", { className: "mt-2 whitespace-pre-line text-sm leading-6 text-foreground", children: preview.message }),
289
- /* @__PURE__ */ jsx2("div", { className: "mt-2", children: /* @__PURE__ */ jsx2(HubFieldList, { fields: [
364
+ preview.message && /* @__PURE__ */ jsx3("p", { className: "mt-2 whitespace-pre-line text-sm leading-6 text-foreground", children: preview.message }),
365
+ /* @__PURE__ */ jsx3("div", { className: "mt-2", children: /* @__PURE__ */ jsx3(HubFieldList, { fields: [
290
366
  ...preview.tree ? [{ label: "Tree", value: preview.tree.slice(0, 7), mono: true }] : [],
291
367
  ...preview.parents.map((parent) => ({ label: "Parent", value: parent.slice(0, 7), mono: true }))
292
368
  ] }) })
293
369
  ] });
294
370
  case "branch":
295
- return /* @__PURE__ */ jsxs2(Panel, { className: "flex flex-wrap items-center gap-2 px-3 py-3 text-sm", children: [
296
- /* @__PURE__ */ jsx2(GitBranch, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
297
- preview.branch && /* @__PURE__ */ jsx2(Chip, { mono: true, children: preview.branch }),
298
- preview.sha && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
371
+ return /* @__PURE__ */ jsxs3(Panel, { className: "flex flex-wrap items-center gap-2 px-3 py-3 text-sm", children: [
372
+ /* @__PURE__ */ jsx3(GitBranch, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
373
+ preview.branch && /* @__PURE__ */ jsx3(Chip, { mono: true, children: preview.branch }),
374
+ preview.sha && /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
299
375
  "at ",
300
- /* @__PURE__ */ jsx2("span", { className: "font-mono text-foreground", children: preview.sha.slice(0, 7) })
376
+ /* @__PURE__ */ jsx3("span", { className: "font-mono text-foreground", children: preview.sha.slice(0, 7) })
301
377
  ] })
302
378
  ] });
303
379
  case "issue":
304
- return /* @__PURE__ */ jsxs2(Panel, { className: "px-3 py-3", children: [
305
- preview.verdict && /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium capitalize text-foreground", children: preview.verdict }),
306
- preview.title && /* @__PURE__ */ jsx2("p", { className: "text-base font-semibold leading-6 text-foreground", children: preview.title }),
307
- preview.body && /* @__PURE__ */ jsx2("p", { className: "mt-1 line-clamp-6 whitespace-pre-line text-sm leading-6 text-foreground", children: preview.body })
380
+ return /* @__PURE__ */ jsxs3(Panel, { className: "px-3 py-3", children: [
381
+ preview.verdict && /* @__PURE__ */ jsx3("p", { className: "text-sm font-medium capitalize text-foreground", children: preview.verdict }),
382
+ preview.title && /* @__PURE__ */ jsx3("p", { className: "text-base font-semibold leading-6 text-foreground", children: preview.title }),
383
+ preview.body && /* @__PURE__ */ jsx3("p", { className: "mt-1 line-clamp-6 whitespace-pre-line text-sm leading-6 text-foreground", children: preview.body })
308
384
  ] });
309
385
  case "email":
310
- return /* @__PURE__ */ jsx2(EmailPreview, { preview, account });
386
+ return /* @__PURE__ */ jsx3(EmailPreview, { preview, account });
311
387
  case "post":
312
- return /* @__PURE__ */ jsx2(PostPreview, { preview, account });
388
+ return /* @__PURE__ */ jsx3(PostPreview, { preview, account });
389
+ case "ad":
390
+ return /* @__PURE__ */ jsxs3(Panel, { className: "overflow-hidden", children: [
391
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-1 px-4 py-3", children: [
392
+ /* @__PURE__ */ jsx3("p", { className: "text-sm font-semibold text-foreground", children: preview.advertiser ?? account ?? "Advertiser unavailable" }),
393
+ /* @__PURE__ */ jsxs3("p", { className: "text-sm text-muted-foreground", children: [
394
+ "Sponsored \xB7 ",
395
+ preview.placement
396
+ ] }),
397
+ preview.text && /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap break-words text-sm leading-6 text-foreground", children: preview.text })
398
+ ] }),
399
+ preview.image && (embeddedPreviewImage(preview.image) ? /* @__PURE__ */ jsx3("img", { src: preview.image, alt: preview.headline ?? "Ad creative", className: "aspect-[1.91/1] w-full object-cover" }) : /* @__PURE__ */ jsx3("p", { className: "bg-muted p-4 text-sm text-muted-foreground", children: "Creative image not embedded in this preview" })),
400
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-3 bg-muted/40 px-4 py-3", children: [
401
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
402
+ preview.destination && /* @__PURE__ */ jsx3("p", { className: "truncate text-sm text-muted-foreground", children: preview.destination }),
403
+ preview.headline && /* @__PURE__ */ jsx3("p", { className: "break-words font-semibold text-foreground", children: preview.headline })
404
+ ] }),
405
+ preview.callToAction && /* @__PURE__ */ jsx3("span", { className: "rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground", children: preview.callToAction })
406
+ ] })
407
+ ] });
408
+ case "page":
409
+ return /* @__PURE__ */ jsxs3(Panel, { className: "overflow-hidden", children: [
410
+ /* @__PURE__ */ jsxs3("div", { className: "border-b border-border px-4 py-2", children: [
411
+ /* @__PURE__ */ jsx3("p", { className: "text-sm font-medium text-foreground", children: preview.title ?? "Page preview" }),
412
+ preview.url && /* @__PURE__ */ jsx3("p", { className: "truncate text-sm text-muted-foreground", children: preview.url })
413
+ ] }),
414
+ embeddedPreviewImage(preview.screenshot) ? /* @__PURE__ */ jsx3("img", { src: preview.screenshot, alt: preview.title ?? "Page screenshot", className: "h-auto w-full" }) : /* @__PURE__ */ jsx3("p", { className: "p-4 text-sm text-muted-foreground", children: "A captured screenshot is needed to preview this page." })
415
+ ] });
313
416
  case "payment":
314
- return /* @__PURE__ */ jsx2(PaymentPreview, { preview });
417
+ return /* @__PURE__ */ jsx3(PaymentPreview, { preview });
315
418
  case "speech":
316
- return /* @__PURE__ */ jsx2(SpeechPreview, { preview });
419
+ return /* @__PURE__ */ jsx3(SpeechPreview, { preview });
317
420
  case "call":
318
- return /* @__PURE__ */ jsx2(CallPreview, { preview });
421
+ return /* @__PURE__ */ jsx3(CallPreview, { preview });
319
422
  case "event":
320
- return /* @__PURE__ */ jsx2(EventPreview, { preview });
423
+ return /* @__PURE__ */ jsx3(EventPreview, { preview });
321
424
  case "fields":
322
- return preview.fields.length > 0 ? /* @__PURE__ */ jsx2(Panel, { className: "px-3 py-3", children: /* @__PURE__ */ jsx2(HubFieldList, { fields: preview.fields }) }) : null;
425
+ return preview.fields.length > 0 ? /* @__PURE__ */ jsx3(Panel, { className: "px-3 py-3", children: /* @__PURE__ */ jsx3(HubFieldList, { fields: preview.fields }) }) : null;
426
+ }
427
+ }
428
+
429
+ // src/hub-approvals-react/feedback.tsx
430
+ import { useEffect as useEffect2, useId, useState as useState3 } from "react";
431
+ import { Button as Button2, Input, Label, Textarea } from "@tangle-network/ui/primitives";
432
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
433
+ function HubPreviewFeedback({ item, onFeedback, drafts }) {
434
+ const id = useId();
435
+ const saved = item.feedback;
436
+ const original = item.input && typeof item.input === "object" && !Array.isArray(item.input) ? item.input : {};
437
+ const prior = saved?.editedInput && typeof saved.editedInput === "object" && !Array.isArray(saved.editedInput) ? saved.editedInput : original;
438
+ const kind = presentHubAction(item).preview.kind;
439
+ const field = kind === "email" ? typeof original.html === "string" ? "html" : typeof original.body === "string" ? "body" : "text" : typeof original.commentary === "string" ? "commentary" : "text";
440
+ const draft = drafts?.get(item.id);
441
+ const [score, setScore] = useState3(draft?.score ?? (saved ? String(saved.score) : ""));
442
+ const [comment, setComment] = useState3(draft?.comment ?? saved?.comment ?? "");
443
+ const [editing, setEditing] = useState3(draft?.editing ?? false);
444
+ const [edited, setEdited] = useState3(draft?.edited ?? saved?.editedInput !== void 0);
445
+ const [subject, setSubject] = useState3(draft?.subject ?? String(prior.subject ?? ""));
446
+ const [body, setBody] = useState3(draft?.body ?? String(prior[field] ?? ""));
447
+ const [payload, setPayload] = useState3(draft?.payload ?? JSON.stringify(prior, null, 2));
448
+ const [busy, setBusy] = useState3(false);
449
+ const [status, setStatus] = useState3((draft ? draft.saved : saved) ? "Feedback saved" : "");
450
+ const [error, setError] = useState3("");
451
+ useEffect2(() => {
452
+ drafts?.set(item.id, { score, comment, editing, edited, subject, body, payload, saved: status === "Feedback saved" });
453
+ }, [drafts, item.id, score, comment, editing, edited, subject, body, payload, status]);
454
+ async function save() {
455
+ if (busy) return;
456
+ if (score.trim() === "" || !Number.isInteger(Number(score)) || Number(score) < 0 || Number(score) > 100) {
457
+ setError("Enter a score from 0 to 100.");
458
+ return;
459
+ }
460
+ setBusy(true);
461
+ setError("");
462
+ setStatus("");
463
+ try {
464
+ const editedInput = edited ? kind === "email" ? { ...original, subject, [field]: body } : kind === "post" ? { ...original, [field]: body } : JSON.parse(payload) : saved?.editedInput;
465
+ await onFeedback(item, {
466
+ score: Number(score),
467
+ comment,
468
+ ...editedInput !== void 0 && JSON.stringify(editedInput) !== JSON.stringify(item.input) ? { editedInput } : {}
469
+ });
470
+ setStatus("Feedback saved");
471
+ } catch (cause) {
472
+ setError(cause instanceof Error ? cause.message : "Feedback could not be saved. Your edits are kept here.");
473
+ } finally {
474
+ setBusy(false);
475
+ }
323
476
  }
477
+ return /* @__PURE__ */ jsxs4("form", { "aria-label": "Preview feedback", className: "space-y-3 border-t border-border pt-3", onSubmit: (event) => {
478
+ event.preventDefault();
479
+ void save();
480
+ }, children: [
481
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-end gap-3", children: [
482
+ /* @__PURE__ */ jsxs4("div", { className: "w-36 space-y-1", children: [
483
+ /* @__PURE__ */ jsx4(Label, { htmlFor: `${id}-score`, children: "Score (0\u2013100)" }),
484
+ /* @__PURE__ */ jsx4(
485
+ Input,
486
+ {
487
+ id: `${id}-score`,
488
+ type: "number",
489
+ inputMode: "numeric",
490
+ min: 0,
491
+ max: 100,
492
+ step: 1,
493
+ required: true,
494
+ value: score,
495
+ placeholder: "Unscored",
496
+ disabled: busy,
497
+ onChange: (event) => {
498
+ setScore(event.target.value);
499
+ setStatus("");
500
+ }
501
+ }
502
+ )
503
+ ] }),
504
+ /* @__PURE__ */ jsx4(Button2, { type: "button", variant: "outline", "aria-expanded": editing, disabled: busy, onClick: () => {
505
+ setEditing((value) => !value);
506
+ setStatus("");
507
+ }, children: "Edit to teach" })
508
+ ] }),
509
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
510
+ /* @__PURE__ */ jsx4(Label, { htmlFor: `${id}-comment`, children: "Comment" }),
511
+ /* @__PURE__ */ jsx4(Textarea, { id: `${id}-comment`, value: comment, rows: 2, disabled: busy, onChange: (event) => {
512
+ setComment(event.target.value);
513
+ setStatus("");
514
+ } })
515
+ ] }),
516
+ editing && /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
517
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: "Your edit is saved as a teaching example. Live approval applies to the original held payload." }),
518
+ kind === "email" && /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
519
+ /* @__PURE__ */ jsx4(Label, { htmlFor: `${id}-subject`, children: "Subject" }),
520
+ /* @__PURE__ */ jsx4(Input, { id: `${id}-subject`, value: subject, disabled: busy, onChange: (event) => {
521
+ setSubject(event.target.value);
522
+ setEdited(true);
523
+ setStatus("");
524
+ } })
525
+ ] }),
526
+ kind === "email" || kind === "post" ? /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
527
+ /* @__PURE__ */ jsx4(Label, { htmlFor: `${id}-body`, children: kind === "email" ? "Body" : "Post text" }),
528
+ /* @__PURE__ */ jsx4(Textarea, { id: `${id}-body`, value: body, rows: 6, disabled: busy, onChange: (event) => {
529
+ setBody(event.target.value);
530
+ setEdited(true);
531
+ setStatus("");
532
+ } })
533
+ ] }) : /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
534
+ /* @__PURE__ */ jsx4(Label, { htmlFor: `${id}-payload`, children: "Payload" }),
535
+ /* @__PURE__ */ jsx4(Textarea, { id: `${id}-payload`, value: payload, rows: 6, disabled: busy, onChange: (event) => {
536
+ setPayload(event.target.value);
537
+ setEdited(true);
538
+ setStatus("");
539
+ } })
540
+ ] })
541
+ ] }),
542
+ error && /* @__PURE__ */ jsx4("p", { role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: error }),
543
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-3", children: [
544
+ /* @__PURE__ */ jsx4(Button2, { type: "submit", variant: "outline", disabled: busy, children: busy ? "Saving feedback\u2026" : "Save feedback" }),
545
+ /* @__PURE__ */ jsx4("p", { role: "status", className: "text-sm text-muted-foreground", children: status })
546
+ ] })
547
+ ] });
324
548
  }
325
549
 
326
550
  // src/hub-approvals-react/dock.tsx
327
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
551
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
328
552
  var DONE_VISIBLE_MS = 6e3;
329
553
  function useNow(intervalMs) {
330
- const [now, setNow] = useState2(() => Date.now());
331
- useEffect(() => {
554
+ const [now, setNow] = useState4(() => Date.now());
555
+ useEffect3(() => {
332
556
  const timer = setInterval(() => setNow(Date.now()), intervalMs);
333
557
  return () => clearInterval(timer);
334
558
  }, [intervalMs]);
@@ -337,48 +561,50 @@ function useNow(intervalMs) {
337
561
  function Progress({ item }) {
338
562
  const provider = presentHubAction(item).provider.name;
339
563
  if (item.phase === "queued" || item.phase === "running") {
340
- return /* @__PURE__ */ jsxs3("p", { role: "status", className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
341
- /* @__PURE__ */ jsx3(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-[var(--surface-info-text)]" }),
564
+ return /* @__PURE__ */ jsxs5("p", { role: "status", className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
565
+ /* @__PURE__ */ jsx5(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-[var(--surface-info-text)]" }),
342
566
  item.phase === "queued" ? "Approved. It runs when the agent\u2019s turn finishes\u2026" : `Running on ${provider}\u2026`
343
567
  ] });
344
568
  }
345
569
  if (item.phase === "done") {
346
570
  const receipt = hubActionReceipt(item);
347
- return /* @__PURE__ */ jsxs3("p", { role: "status", className: "flex min-w-0 items-center gap-2 text-sm font-medium text-foreground", children: [
348
- /* @__PURE__ */ jsx3(Check, { "aria-hidden": true, className: "size-4 shrink-0 text-[var(--surface-success-text)]" }),
349
- receipt.href ? /* @__PURE__ */ jsxs3("a", { href: receipt.href, target: "_blank", rel: "noreferrer", className: "inline-flex min-w-0 items-center gap-1 hover:underline", children: [
350
- /* @__PURE__ */ jsx3("span", { className: "truncate", children: receipt.title }),
351
- /* @__PURE__ */ jsx3(ExternalLink, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" })
352
- ] }) : /* @__PURE__ */ jsx3("span", { className: "truncate", children: receipt.title })
571
+ return /* @__PURE__ */ jsxs5("p", { role: "status", className: "flex min-w-0 items-center gap-2 text-sm font-medium text-foreground", children: [
572
+ /* @__PURE__ */ jsx5(Check, { "aria-hidden": true, className: "size-4 shrink-0 text-[var(--surface-success-text)]" }),
573
+ receipt.href ? /* @__PURE__ */ jsxs5("a", { href: receipt.href, target: "_blank", rel: "noreferrer", className: "inline-flex min-w-0 items-center gap-1 hover:underline", children: [
574
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: receipt.title }),
575
+ /* @__PURE__ */ jsx5(ExternalLink, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" })
576
+ ] }) : /* @__PURE__ */ jsx5("span", { className: "truncate", children: receipt.title })
353
577
  ] });
354
578
  }
355
579
  if (item.phase === "unknown") {
356
- return /* @__PURE__ */ jsxs3("p", { role: "alert", className: "break-words text-sm text-[var(--surface-warning-text)]", children: [
357
- /* @__PURE__ */ jsx3("span", { className: "font-medium", children: "May have run." }),
580
+ return /* @__PURE__ */ jsxs5("p", { role: "alert", className: "break-words text-sm text-[var(--surface-warning-text)]", children: [
581
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: "May have run." }),
358
582
  " ",
359
583
  HUB_APPROVAL_UNKNOWN_WARNING
360
584
  ] });
361
585
  }
362
586
  if (item.phase === "failed") {
363
- return /* @__PURE__ */ jsxs3("p", { role: "alert", className: "break-words text-sm text-[var(--surface-danger-text)]", children: [
587
+ return /* @__PURE__ */ jsxs5("p", { role: "alert", className: "break-words text-sm text-[var(--surface-danger-text)]", children: [
364
588
  hubActionReceipt(item).title,
365
589
  item.error ? `: ${item.error}` : "."
366
590
  ] });
367
591
  }
368
592
  return null;
369
593
  }
370
- function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, className = "" }) {
594
+ function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, onFeedback, className = "" }) {
371
595
  const now = useNow(3e4);
372
596
  const watched = useRef(/* @__PURE__ */ new Map());
373
- const [settled, setSettled] = useState2({});
374
- const [dismissed, setDismissed] = useState2(/* @__PURE__ */ new Set());
375
- const [submitting, setSubmitting] = useState2(null);
376
- const [error, setError] = useState2(null);
377
- const [activeId, setActiveId] = useState2(null);
378
- const [collapsed, setCollapsed] = useState2(false);
379
- const [permissionsOpen, setPermissionsOpen] = useState2(false);
380
- const [scope, setScope] = useState2(null);
381
- useEffect(() => {
597
+ const [settled, setSettled] = useState4({});
598
+ const [dismissed, setDismissed] = useState4(/* @__PURE__ */ new Set());
599
+ const [submitting, setSubmitting] = useState4(null);
600
+ const [error, setError] = useState4(null);
601
+ const [activeId, setActiveId] = useState4(null);
602
+ const [collapsed, setCollapsed] = useState4(false);
603
+ const [permissionsOpen, setPermissionsOpen] = useState4(false);
604
+ const [scope, setScope] = useState4(null);
605
+ const feedbackDrafts = useRef(/* @__PURE__ */ new Map());
606
+ const [feedbackSaving, setFeedbackSaving] = useState4(false);
607
+ useEffect3(() => {
382
608
  const finishedNow = {};
383
609
  for (const item of items) {
384
610
  const before = watched.current.get(item.id);
@@ -388,7 +614,7 @@ function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, classN
388
614
  }
389
615
  if (Object.keys(finishedNow).length > 0) setSettled((current) => ({ ...current, ...finishedNow }));
390
616
  }, [items]);
391
- useEffect(() => {
617
+ useEffect3(() => {
392
618
  const doneIds = Object.entries(settled).filter(([id]) => items.find((item) => item.id === id)?.phase === "done");
393
619
  if (doneIds.length === 0) return;
394
620
  const oldest = Math.min(...doneIds.map(([, at]) => at));
@@ -399,26 +625,26 @@ function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, classN
399
625
  return () => clearTimeout(timer);
400
626
  }, [settled, items]);
401
627
  const queue = useMemo(() => items.filter((item) => !dismissed.has(item.id) && (HUB_APPROVAL_OPEN_PHASES.has(item.phase) || HUB_APPROVAL_ACTIVE_PHASES.has(item.phase) || item.phase === "unknown" || item.id in settled || submitting?.id === item.id)), [items, dismissed, settled, submitting]);
402
- useEffect(() => {
403
- if (focusId && queue.some((item) => item.id === focusId)) {
628
+ useEffect3(() => {
629
+ if (!feedbackSaving && focusId && queue.some((item) => item.id === focusId)) {
404
630
  setActiveId(focusId);
405
631
  setCollapsed(false);
406
632
  }
407
- }, [focusId, queue]);
633
+ }, [focusId, queue, feedbackSaving]);
408
634
  const activeIndex = Math.max(0, queue.findIndex((item) => item.id === activeId));
409
635
  const active = queue[activeIndex];
410
- useEffect(() => {
636
+ useEffect3(() => {
411
637
  if (!active || activeId === active.id) return;
412
638
  setActiveId(active.id);
413
639
  }, [active, activeId]);
414
- useEffect(() => {
640
+ useEffect3(() => {
415
641
  setPermissionsOpen(false);
416
642
  setScope(null);
417
643
  }, [active?.id]);
418
644
  if (!active) return null;
419
645
  const presentation = presentHubAction(active);
420
646
  const waitingCount = queue.filter((item) => HUB_APPROVAL_OPEN_PHASES.has(item.phase)).length;
421
- const choices = permissions?.(active) ?? null;
647
+ const choices = active.executionMode === "dry-run" ? null : permissions?.(active) ?? null;
422
648
  const scopes = choices && "scopes" in choices ? choices.scopes : [];
423
649
  const chosenScope = scope ?? scopes[0]?.id ?? null;
424
650
  const busy = submitting?.id === active.id;
@@ -438,104 +664,115 @@ function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, classN
438
664
  setSubmitting(null);
439
665
  }
440
666
  }
441
- const card = /* @__PURE__ */ jsxs3(Fragment, { children: [
442
- /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-3 px-4 pt-3", children: [
443
- /* @__PURE__ */ jsx3(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 28 }),
444
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
445
- /* @__PURE__ */ jsx3("h3", { className: "line-clamp-2 break-words text-base font-semibold leading-6 text-foreground", title: presentation.title, children: presentation.title }),
446
- /* @__PURE__ */ jsx3("p", { className: "truncate text-sm text-muted-foreground", children: subtitle })
667
+ const card = /* @__PURE__ */ jsxs5(Fragment, { children: [
668
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-3 px-4 pt-3", children: [
669
+ /* @__PURE__ */ jsx5(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 28 }),
670
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
671
+ /* @__PURE__ */ jsx5("h3", { className: "line-clamp-2 break-words text-base font-semibold leading-6 text-foreground", title: presentation.title, children: presentation.title }),
672
+ /* @__PURE__ */ jsx5("p", { className: "truncate text-sm text-muted-foreground", children: subtitle })
447
673
  ] }),
448
- /* @__PURE__ */ jsx3(
449
- Button2,
674
+ /* @__PURE__ */ jsx5(
675
+ Button3,
450
676
  {
451
677
  variant: "ghost",
452
678
  size: "icon-sm",
453
679
  "aria-label": collapsed ? "Show the request" : "Hide the preview",
454
680
  "aria-expanded": !collapsed,
681
+ disabled: feedbackSaving,
455
682
  onClick: () => setCollapsed((value) => !value),
456
- children: /* @__PURE__ */ jsx3(ChevronDown, { className: `transition-transform ${collapsed ? "-rotate-90" : ""}` })
683
+ children: /* @__PURE__ */ jsx5(ChevronDown, { className: `transition-transform ${collapsed ? "-rotate-90" : ""}` })
457
684
  }
458
685
  )
459
686
  ] }),
460
- !collapsed && /* @__PURE__ */ jsxs3("div", { className: "max-h-48 space-y-3 overflow-y-auto px-4 pt-3 sm:max-h-80", children: [
461
- active.bundle?.steps && active.bundle.steps.length > 0 && /* @__PURE__ */ jsx3("ol", { className: "space-y-1 text-sm", "aria-label": "Steps this approval covers", children: active.bundle.steps.map((stepItem, index) => /* @__PURE__ */ jsxs3("li", { className: `flex items-center gap-2 ${stepItem.state === "current" ? "font-medium text-foreground" : "text-muted-foreground"}`, children: [
462
- /* @__PURE__ */ jsx3("span", { className: `flex size-5 shrink-0 items-center justify-center rounded-full border text-sm tabular-nums ${stepItem.state === "done" ? "border-transparent bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]" : "border-border"}`, children: stepItem.state === "done" ? /* @__PURE__ */ jsx3(Check, { "aria-hidden": true, className: "size-3.5" }) : index + 1 }),
687
+ !collapsed && /* @__PURE__ */ jsxs5("div", { className: "max-h-[55vh] space-y-3 overflow-y-auto px-4 pt-3 sm:max-h-[60vh]", children: [
688
+ active.bundle?.steps && active.bundle.steps.length > 0 && /* @__PURE__ */ jsx5("ol", { className: "space-y-1 text-sm", "aria-label": "Steps this approval covers", children: active.bundle.steps.map((stepItem, index) => /* @__PURE__ */ jsxs5("li", { className: `flex items-center gap-2 ${stepItem.state === "current" ? "font-medium text-foreground" : "text-muted-foreground"}`, children: [
689
+ /* @__PURE__ */ jsx5("span", { className: `flex size-5 shrink-0 items-center justify-center rounded-full border text-sm tabular-nums ${stepItem.state === "done" ? "border-transparent bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]" : "border-border"}`, children: stepItem.state === "done" ? /* @__PURE__ */ jsx5(Check, { "aria-hidden": true, className: "size-3.5" }) : index + 1 }),
463
690
  stepItem.label
464
691
  ] }, index)) }),
465
- /* @__PURE__ */ jsx3(HubActionPreviewView, { preview: presentation.preview, account: active.account }),
466
- /* @__PURE__ */ jsx3(HubRawDetails, { sections: [{ label: "Call", value: { action: active.actionPath, input: active.input } }] })
692
+ /* @__PURE__ */ jsx5(HubActionPreviewView, { preview: presentation.preview, account: active.account }),
693
+ /* @__PURE__ */ jsx5(HubRawDetails, { sections: [{ label: "Call", value: { action: active.actionPath, input: active.input } }] }),
694
+ onFeedback && /* @__PURE__ */ jsx5(HubPreviewFeedback, { item: active, drafts: feedbackDrafts.current, onFeedback: async (item, feedback) => {
695
+ setFeedbackSaving(true);
696
+ try {
697
+ await onFeedback(item, feedback);
698
+ } finally {
699
+ setFeedbackSaving(false);
700
+ }
701
+ } }, active.id)
467
702
  ] }),
468
- /* @__PURE__ */ jsxs3("div", { className: "space-y-3 px-4 pb-4 pt-3", children: [
469
- busy && submitting && !HUB_APPROVAL_ACTIVE_PHASES.has(active.phase) && active.phase === "waiting" && /* @__PURE__ */ jsxs3("p", { role: "status", className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
470
- /* @__PURE__ */ jsx3(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-[var(--surface-info-text)]" }),
703
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-3 px-4 pb-4 pt-3", children: [
704
+ busy && submitting && !HUB_APPROVAL_ACTIVE_PHASES.has(active.phase) && active.phase === "waiting" && /* @__PURE__ */ jsxs5("p", { role: "status", className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
705
+ /* @__PURE__ */ jsx5(Loader2, { "aria-hidden": true, className: "size-4 animate-spin text-[var(--surface-info-text)]" }),
471
706
  submitting.label
472
707
  ] }),
473
- /* @__PURE__ */ jsx3(Progress, { item: active }),
474
- error?.id === active.id && active.phase === "waiting" && /* @__PURE__ */ jsx3("p", { role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: error.message }),
475
- HUB_APPROVAL_OPEN_PHASES.has(active.phase) && active.wait && /* @__PURE__ */ jsx3(HubApprovalWaitNote, { wait: active.wait }),
476
- active.phase === "blocked" && !active.wait && /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: "The workspace owner approves this request." }),
477
- decidable && permissionsOpen && scopes.length > 0 && /* @__PURE__ */ jsxs3("fieldset", { className: "space-y-2 rounded-xl border border-border bg-muted/30 px-3 py-3", children: [
478
- /* @__PURE__ */ jsx3("legend", { className: "sr-only", children: "Standing permission" }),
479
- scopes.length > 1 && /* @__PURE__ */ jsx3(
708
+ /* @__PURE__ */ jsx5(Progress, { item: active }),
709
+ HUB_APPROVAL_OPEN_PHASES.has(active.phase) && active.executionMode === "dry-run" && /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: "Dry run. This exact payload stays held until you approve this action live." }),
710
+ error?.id === active.id && active.phase === "waiting" && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-sm text-[var(--surface-danger-text)]", children: error.message }),
711
+ HUB_APPROVAL_OPEN_PHASES.has(active.phase) && active.wait && /* @__PURE__ */ jsx5(HubApprovalWaitNote, { wait: active.wait }),
712
+ active.phase === "blocked" && !active.wait && /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: "The workspace owner approves this request." }),
713
+ decidable && permissionsOpen && scopes.length > 0 && /* @__PURE__ */ jsxs5("fieldset", { className: "space-y-2 rounded-xl border border-border bg-muted/30 px-3 py-3", children: [
714
+ /* @__PURE__ */ jsx5("legend", { className: "sr-only", children: "Standing permission" }),
715
+ scopes.length > 1 && /* @__PURE__ */ jsx5(
480
716
  RadioGroup,
481
717
  {
482
718
  "aria-label": "What the permission covers",
483
719
  value: chosenScope ?? void 0,
484
720
  onValueChange: setScope,
485
721
  className: "gap-1.5",
486
- children: scopes.map((option) => /* @__PURE__ */ jsxs3("div", { className: "flex min-h-8 items-center gap-2", children: [
487
- /* @__PURE__ */ jsx3(RadioGroupItem, { id: `hub-scope-${active.id}-${option.id}`, value: option.id }),
488
- /* @__PURE__ */ jsx3(Label, { htmlFor: `hub-scope-${active.id}-${option.id}`, className: "cursor-pointer text-sm font-normal text-foreground", children: option.label })
722
+ children: scopes.map((option) => /* @__PURE__ */ jsxs5("div", { className: "flex min-h-8 items-center gap-2", children: [
723
+ /* @__PURE__ */ jsx5(RadioGroupItem, { id: `hub-scope-${active.id}-${option.id}`, value: option.id }),
724
+ /* @__PURE__ */ jsx5(Label2, { htmlFor: `hub-scope-${active.id}-${option.id}`, className: "cursor-pointer text-sm font-normal text-foreground", children: option.label })
489
725
  ] }, option.id))
490
726
  }
491
727
  ),
492
- scopes.length === 1 && /* @__PURE__ */ jsx3("p", { className: "text-sm text-foreground", children: scopes[0]?.label }),
493
- /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-2", children: [
494
- /* @__PURE__ */ jsx3(Button2, { variant: "outline", onClick: () => chosenScope && void decide({ kind: "allow", duration: "24h", scope: chosenScope }, "Approving and allowing for 24 hours\u2026"), children: "Approve and allow for 24 hours" }),
495
- /* @__PURE__ */ jsx3(Button2, { variant: "outline", onClick: () => chosenScope && void decide({ kind: "allow", duration: "always", scope: chosenScope }, "Approving and allowing from now on\u2026"), children: "Approve and always allow" })
728
+ scopes.length === 1 && /* @__PURE__ */ jsx5("p", { className: "text-sm text-foreground", children: scopes[0]?.label }),
729
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap gap-2", children: [
730
+ /* @__PURE__ */ jsx5(Button3, { variant: "outline", onClick: () => chosenScope && void decide({ kind: "allow", duration: "24h", scope: chosenScope }, "Approving and allowing for 24 hours\u2026"), children: "Approve and allow for 24 hours" }),
731
+ /* @__PURE__ */ jsx5(Button3, { variant: "outline", onClick: () => chosenScope && void decide({ kind: "allow", duration: "always", scope: chosenScope }, "Approving and allowing from now on\u2026"), children: "Approve and always allow" })
496
732
  ] })
497
733
  ] }),
498
- /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-2", children: [
499
- decidable && /* @__PURE__ */ jsxs3(Fragment, { children: [
500
- /* @__PURE__ */ jsx3(Button2, { onClick: () => void decide({ kind: "approve" }, "Approving\u2026"), children: "Approve" }),
501
- /* @__PURE__ */ jsx3(Button2, { variant: "outline", onClick: () => void decide({ kind: "deny" }, "Denying\u2026"), children: "Deny" }),
502
- scopes.length > 0 && /* @__PURE__ */ jsx3(Button2, { variant: "ghost", "aria-expanded": permissionsOpen, onClick: () => setPermissionsOpen((value) => !value), children: permissionsOpen ? "Fewer options" : "Allow future calls\u2026" })
734
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
735
+ decidable && /* @__PURE__ */ jsxs5(Fragment, { children: [
736
+ /* @__PURE__ */ jsx5(Button3, { disabled: feedbackSaving, onClick: () => void decide({ kind: "approve" }, "Approving\u2026"), children: active.executionMode === "dry-run" ? "Approve live" : "Approve" }),
737
+ /* @__PURE__ */ jsx5(Button3, { disabled: feedbackSaving, variant: "outline", onClick: () => void decide({ kind: "deny" }, "Denying\u2026"), children: "Deny" }),
738
+ scopes.length > 0 && /* @__PURE__ */ jsx5(Button3, { variant: "ghost", "aria-expanded": permissionsOpen, onClick: () => setPermissionsOpen((value) => !value), children: permissionsOpen ? "Fewer options" : "Allow future calls\u2026" })
503
739
  ] }),
504
- (active.phase === "failed" || active.phase === "unknown") && /* @__PURE__ */ jsxs3(Button2, { variant: "outline", onClick: () => setDismissed((current) => /* @__PURE__ */ new Set([...current, active.id])), children: [
505
- /* @__PURE__ */ jsx3(X, { "aria-hidden": true }),
740
+ (active.phase === "failed" || active.phase === "unknown") && /* @__PURE__ */ jsxs5(Button3, { variant: "outline", onClick: () => setDismissed((current) => /* @__PURE__ */ new Set([...current, active.id])), children: [
741
+ /* @__PURE__ */ jsx5(X, { "aria-hidden": true }),
506
742
  " Dismiss"
507
743
  ] }),
508
- /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
509
- decidable && choices && "never" in choices && !active.wait && /* @__PURE__ */ jsx3("span", { className: "text-sm text-muted-foreground", children: choices.never }),
510
- onOpen && /* @__PURE__ */ jsx3(Button2, { variant: "ghost", onClick: () => onOpen(active), children: "View in chat" })
744
+ /* @__PURE__ */ jsx5("span", { className: "flex-1" }),
745
+ decidable && choices && "never" in choices && !active.wait && /* @__PURE__ */ jsx5("span", { className: "text-sm text-muted-foreground", children: choices.never }),
746
+ onOpen && /* @__PURE__ */ jsx5(Button3, { variant: "ghost", onClick: () => onOpen(active), children: "View in chat" })
511
747
  ] })
512
748
  ] })
513
749
  ] });
514
- return /* @__PURE__ */ jsx3(
750
+ return /* @__PURE__ */ jsx5(
515
751
  "section",
516
752
  {
517
753
  "aria-label": active.phase === "unknown" ? "Action may have run" : "Waiting for your approval",
518
754
  "data-hub-approval-dock": active.id,
519
755
  className: `overflow-hidden rounded-2xl border border-[var(--surface-warning-border)] bg-card shadow-md ${className}`,
520
- children: queue.length > 1 ? /* @__PURE__ */ jsxs3("div", { className: "max-h-[70vh] overflow-y-auto pt-2.5", children: [
521
- /* @__PURE__ */ jsx3("p", { className: "px-4 pb-1.5 text-sm font-medium text-muted-foreground", "aria-live": "polite", children: waitingCount === 0 ? `${queue.length} requests` : waitingCount === 1 ? "1 request waiting" : `${waitingCount} requests waiting` }),
522
- /* @__PURE__ */ jsx3("ul", { "aria-label": "Requests in this conversation", className: "divide-y divide-border border-t border-border", children: queue.map((item) => {
523
- if (item.id === active.id) return /* @__PURE__ */ jsx3("li", { "aria-current": "true", children: card }, item.id);
756
+ children: queue.length > 1 ? /* @__PURE__ */ jsxs5("div", { className: "max-h-[70vh] overflow-y-auto pt-2.5", children: [
757
+ /* @__PURE__ */ jsx5("p", { className: "px-4 pb-1.5 text-sm font-medium text-muted-foreground", "aria-live": "polite", children: waitingCount === 0 ? `${queue.length} requests` : waitingCount === 1 ? "1 request waiting" : `${waitingCount} requests waiting` }),
758
+ /* @__PURE__ */ jsx5("ul", { "aria-label": "Requests in this conversation", className: "divide-y divide-border border-t border-border", children: queue.map((item) => {
759
+ if (item.id === active.id) return /* @__PURE__ */ jsx5("li", { "aria-current": "true", children: card }, item.id);
524
760
  const shown = presentHubAction(item);
525
- return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
526
- Button2,
761
+ return /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs5(
762
+ Button3,
527
763
  {
528
764
  variant: "bare",
529
765
  "data-hub-approval-dock-row": item.id,
766
+ disabled: feedbackSaving,
530
767
  onClick: () => {
531
768
  setActiveId(item.id);
532
769
  setCollapsed(false);
533
770
  },
534
771
  className: "flex min-h-11 w-full items-center gap-2.5 px-4 text-left transition-colors hover:bg-muted/60",
535
772
  children: [
536
- /* @__PURE__ */ jsx3(HubProviderMark, { providerId: shown.provider.id, name: shown.provider.name, size: 24 }),
537
- /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: item.phase === "done" ? hubActionReceipt(item).title : shown.title }),
538
- /* @__PURE__ */ jsx3("span", { className: item.phase === "waiting" ? "hidden sm:inline-flex" : "inline-flex", children: /* @__PURE__ */ jsx3(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item) }) })
773
+ /* @__PURE__ */ jsx5(HubProviderMark, { providerId: shown.provider.id, name: shown.provider.name, size: 24 }),
774
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: item.phase === "done" ? hubActionReceipt(item).title : shown.title }),
775
+ /* @__PURE__ */ jsx5("span", { className: item.phase === "waiting" ? "hidden sm:inline-flex" : "inline-flex", children: /* @__PURE__ */ jsx5(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item) }) })
539
776
  ]
540
777
  }
541
778
  ) }, item.id);
@@ -546,10 +783,10 @@ function HubApprovalDock({ items, onDecide, permissions, onOpen, focusId, classN
546
783
  }
547
784
 
548
785
  // src/hub-approvals-react/list.tsx
549
- import { useEffect as useEffect2, useState as useState3 } from "react";
786
+ import { useEffect as useEffect4, useState as useState5 } from "react";
550
787
  import { ChevronRight as ChevronRight2, ShieldCheck } from "lucide-react";
551
- import { Button as Button3 } from "@tangle-network/ui/primitives";
552
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
788
+ import { Button as Button4 } from "@tangle-network/ui/primitives";
789
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
553
790
  var GROUPS = [
554
791
  { id: "waiting", label: "Waiting", phases: ["waiting", "blocked"] },
555
792
  { id: "running", label: "Running", phases: ["queued", "running"] },
@@ -560,55 +797,55 @@ var GROUPS = [
560
797
  { id: "closed", label: "Denied or expired", phases: ["denied", "expired"] }
561
798
  ];
562
799
  function HubApprovalsList({ items, onSelect, selectedId, className = "" }) {
563
- const [now, setNow] = useState3(() => Date.now());
564
- useEffect2(() => {
800
+ const [now, setNow] = useState5(() => Date.now());
801
+ useEffect4(() => {
565
802
  const timer = setInterval(() => setNow(Date.now()), 3e4);
566
803
  return () => clearInterval(timer);
567
804
  }, []);
568
805
  const waiting = items.filter((item) => item.phase === "waiting" || item.phase === "blocked").length;
569
806
  const groups = GROUPS.map((group) => ({ ...group, items: items.filter((item) => group.phases.includes(item.phase)).slice().reverse() })).filter((group) => group.items.length > 0);
570
- return /* @__PURE__ */ jsxs4("div", { className: `flex h-full min-h-0 flex-col ${className}`, children: [
571
- /* @__PURE__ */ jsxs4("div", { className: "shrink-0 border-b border-border px-4 py-3", children: [
572
- /* @__PURE__ */ jsx4("h2", { className: "text-base font-semibold text-foreground", children: "Approvals" }),
573
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", "aria-live": "polite", children: waiting === 0 ? "Nothing is waiting for you" : waiting === 1 ? "1 request is waiting" : `${waiting} requests are waiting` })
807
+ return /* @__PURE__ */ jsxs6("div", { className: `flex h-full min-h-0 flex-col ${className}`, children: [
808
+ /* @__PURE__ */ jsxs6("div", { className: "shrink-0 border-b border-border px-4 py-3", children: [
809
+ /* @__PURE__ */ jsx6("h2", { className: "text-base font-semibold text-foreground", children: "Approvals" }),
810
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", "aria-live": "polite", children: waiting === 0 ? "Nothing is waiting for you" : waiting === 1 ? "1 request is waiting" : `${waiting} requests are waiting` })
574
811
  ] }),
575
- groups.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 px-6 py-10 text-center", children: [
576
- /* @__PURE__ */ jsx4("span", { className: "flex size-10 items-center justify-center rounded-xl bg-muted", children: /* @__PURE__ */ jsx4(ShieldCheck, { "aria-hidden": true, className: "size-5 text-muted-foreground" }) }),
577
- /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium text-foreground", children: "No approvals in this conversation" }),
578
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: "Requests to act on your connected accounts appear here." })
579
- ] }) : /* @__PURE__ */ jsx4("div", { className: "min-h-0 flex-1 space-y-5 overflow-y-auto px-2 py-3", children: groups.map((group) => /* @__PURE__ */ jsxs4("section", { "aria-label": group.label, children: [
580
- /* @__PURE__ */ jsxs4("h3", { className: "px-2 pb-1 text-sm font-medium text-muted-foreground", children: [
812
+ groups.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 px-6 py-10 text-center", children: [
813
+ /* @__PURE__ */ jsx6("span", { className: "flex size-10 items-center justify-center rounded-xl bg-muted", children: /* @__PURE__ */ jsx6(ShieldCheck, { "aria-hidden": true, className: "size-5 text-muted-foreground" }) }),
814
+ /* @__PURE__ */ jsx6("p", { className: "text-sm font-medium text-foreground", children: "No approvals in this conversation" }),
815
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "Requests to act on your connected accounts appear here." })
816
+ ] }) : /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1 space-y-5 overflow-y-auto px-2 py-3", children: groups.map((group) => /* @__PURE__ */ jsxs6("section", { "aria-label": group.label, children: [
817
+ /* @__PURE__ */ jsxs6("h3", { className: "px-2 pb-1 text-sm font-medium text-muted-foreground", children: [
581
818
  group.label,
582
819
  " ",
583
- /* @__PURE__ */ jsxs4("span", { className: "tabular-nums", children: [
820
+ /* @__PURE__ */ jsxs6("span", { className: "tabular-nums", children: [
584
821
  "\xB7 ",
585
822
  group.items.length
586
823
  ] })
587
824
  ] }),
588
- /* @__PURE__ */ jsx4("ul", { className: "space-y-1", children: group.items.map((item) => {
825
+ /* @__PURE__ */ jsx6("ul", { className: "space-y-1", children: group.items.map((item) => {
589
826
  const presentation = presentHubAction(item);
590
827
  const title = item.phase === "done" ? hubActionReceipt(item).title : presentation.title;
591
828
  const when = item.phase === "waiting" ? closesIn(item.expiresAt, now) ?? relativeTime(item.requestedAt, now) : relativeTime(item.requestedAt, now);
592
829
  const detail = [presentation.provider.name, distinctAccount(item.account, presentation.provider.name), when].filter(Boolean).join(" \xB7 ");
593
830
  const selected = selectedId === item.id;
594
- return /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4(
595
- Button3,
831
+ return /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs6(
832
+ Button4,
596
833
  {
597
834
  variant: "bare",
598
835
  onClick: () => onSelect?.(item),
599
836
  "aria-current": selected ? "true" : void 0,
600
837
  className: `flex w-full items-start gap-3 rounded-lg px-2 py-2.5 text-left transition-colors hover:bg-muted ${selected ? "bg-muted" : ""}`,
601
838
  children: [
602
- /* @__PURE__ */ jsx4(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 28 }),
603
- /* @__PURE__ */ jsxs4("span", { className: "min-w-0 flex-1", children: [
604
- /* @__PURE__ */ jsx4("span", { className: "line-clamp-2 text-sm font-medium leading-5 text-foreground", children: title }),
605
- /* @__PURE__ */ jsx4("span", { className: "mt-0.5 block truncate text-sm text-muted-foreground", children: detail }),
606
- item.phase === "unknown" && /* @__PURE__ */ jsx4("span", { className: "mt-1 block text-sm text-[var(--surface-warning-text)]", children: HUB_APPROVAL_UNKNOWN_WARNING }),
607
- (item.phase === "waiting" || item.phase === "blocked") && item.wait && /* @__PURE__ */ jsx4("span", { className: "mt-1 line-clamp-2 block text-sm text-muted-foreground", children: item.wait.reason })
839
+ /* @__PURE__ */ jsx6(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 28 }),
840
+ /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1", children: [
841
+ /* @__PURE__ */ jsx6("span", { className: "line-clamp-2 text-sm font-medium leading-5 text-foreground", children: title }),
842
+ /* @__PURE__ */ jsx6("span", { className: "mt-0.5 block truncate text-sm text-muted-foreground", children: detail }),
843
+ item.phase === "unknown" && /* @__PURE__ */ jsx6("span", { className: "mt-1 block text-sm text-[var(--surface-warning-text)]", children: HUB_APPROVAL_UNKNOWN_WARNING }),
844
+ (item.phase === "waiting" || item.phase === "blocked") && item.wait && /* @__PURE__ */ jsx6("span", { className: "mt-1 line-clamp-2 block text-sm text-muted-foreground", children: item.wait.reason })
608
845
  ] }),
609
- /* @__PURE__ */ jsxs4("span", { className: "flex shrink-0 items-center gap-1", children: [
610
- /* @__PURE__ */ jsx4(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item, "Owner") }),
611
- /* @__PURE__ */ jsx4(ChevronRight2, { "aria-hidden": true, className: "size-4 text-muted-foreground" })
846
+ /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1", children: [
847
+ /* @__PURE__ */ jsx6(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item, "Owner") }),
848
+ /* @__PURE__ */ jsx6(ChevronRight2, { "aria-hidden": true, className: "size-4 text-muted-foreground" })
612
849
  ] })
613
850
  ]
614
851
  }
@@ -619,19 +856,19 @@ function HubApprovalsList({ items, onSelect, selectedId, className = "" }) {
619
856
  }
620
857
 
621
858
  // src/hub-approvals-react/row.tsx
622
- import { useEffect as useEffect3, useRef as useRef2, useState as useState5 } from "react";
859
+ import { useEffect as useEffect5, useRef as useRef2, useState as useState7 } from "react";
623
860
  import { ChevronDown as ChevronDown2 } from "lucide-react";
624
- import { Button as Button4 } from "@tangle-network/ui/primitives";
861
+ import { Button as Button5 } from "@tangle-network/ui/primitives";
625
862
 
626
863
  // src/hub-approvals-react/receipt.tsx
627
- import { useState as useState4 } from "react";
864
+ import { useState as useState6 } from "react";
628
865
  import { ExternalLink as ExternalLink2 } from "lucide-react";
629
866
  import { ApprovalDiffSummary as ApprovalDiffSummary2 } from "@tangle-network/ui/run";
630
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
867
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
631
868
  function AudioPlayer({ src }) {
632
- const [failed, setFailed] = useState4(false);
633
- if (failed) return /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: "The audio link has expired. Ask the agent for a new one." });
634
- return /* @__PURE__ */ jsx5("audio", { controls: true, preload: "metadata", src, onError: () => setFailed(true), className: "h-10 w-full" });
869
+ const [failed, setFailed] = useState6(false);
870
+ if (failed) return /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground", children: "The audio link has expired. Ask the agent for a new one." });
871
+ return /* @__PURE__ */ jsx7("audio", { controls: true, preload: "metadata", src, onError: () => setFailed(true), className: "h-10 w-full" });
635
872
  }
636
873
  function HubActionReceiptCard({ item, className = "" }) {
637
874
  const receipt = hubActionReceipt(item);
@@ -639,27 +876,27 @@ function HubActionReceiptCard({ item, className = "" }) {
639
876
  const account = distinctAccount(item.account, receipt.provider.name);
640
877
  const subtitle = [receipt.provider.name, account ? `as ${account}` : null, presentation.target && !receipt.title.includes(presentation.target) ? presentation.target : null].filter(Boolean).join(" \xB7 ");
641
878
  const phase = item.phase;
642
- return /* @__PURE__ */ jsxs5("article", { "aria-label": receipt.title, className: `rounded-xl border border-border bg-card px-4 py-3 shadow-sm ${className}`, children: [
643
- /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-3", children: [
644
- /* @__PURE__ */ jsx5(HubProviderMark, { providerId: receipt.provider.id, name: receipt.provider.name }),
645
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
646
- /* @__PURE__ */ jsx5("p", { className: "text-base font-semibold leading-6 text-foreground", children: receipt.href ? /* @__PURE__ */ jsxs5("a", { href: receipt.href, target: "_blank", rel: "noreferrer", className: "inline-flex max-w-full items-center gap-1.5 hover:underline", children: [
647
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: receipt.title }),
648
- /* @__PURE__ */ jsx5(ExternalLink2, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" })
649
- ] }) : /* @__PURE__ */ jsx5("span", { className: "break-words", children: receipt.title }) }),
650
- subtitle && /* @__PURE__ */ jsx5("p", { className: "truncate text-sm text-muted-foreground", children: subtitle })
879
+ return /* @__PURE__ */ jsxs7("article", { "aria-label": receipt.title, className: `rounded-xl border border-border bg-card px-4 py-3 shadow-sm ${className}`, children: [
880
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-3", children: [
881
+ /* @__PURE__ */ jsx7(HubProviderMark, { providerId: receipt.provider.id, name: receipt.provider.name }),
882
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1", children: [
883
+ /* @__PURE__ */ jsx7("p", { className: "text-base font-semibold leading-6 text-foreground", children: receipt.href ? /* @__PURE__ */ jsxs7("a", { href: receipt.href, target: "_blank", rel: "noreferrer", className: "inline-flex max-w-full items-center gap-1.5 hover:underline", children: [
884
+ /* @__PURE__ */ jsx7("span", { className: "truncate", children: receipt.title }),
885
+ /* @__PURE__ */ jsx7(ExternalLink2, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" })
886
+ ] }) : /* @__PURE__ */ jsx7("span", { className: "break-words", children: receipt.title }) }),
887
+ subtitle && /* @__PURE__ */ jsx7("p", { className: "truncate text-sm text-muted-foreground", children: subtitle })
651
888
  ] }),
652
- /* @__PURE__ */ jsx5(HubApprovalPhasePill, { phase })
889
+ /* @__PURE__ */ jsx7(HubApprovalPhasePill, { phase })
653
890
  ] }),
654
- (receipt.fields.length > 0 || receipt.media || receipt.files || receipt.error || receipt.warning) && /* @__PURE__ */ jsxs5("div", { className: "mt-3 space-y-3", children: [
655
- /* @__PURE__ */ jsx5(HubFieldList, { fields: receipt.fields }),
656
- receipt.media && /* @__PURE__ */ jsx5(AudioPlayer, { src: receipt.media.src }),
657
- receipt.files && /* @__PURE__ */ jsx5(ApprovalDiffSummary2, { files: receipt.files }),
658
- receipt.warning && /* @__PURE__ */ jsx5("p", { className: "break-words rounded-lg border border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] px-3 py-2 text-sm text-[var(--surface-warning-text)]", children: receipt.warning }),
659
- receipt.error && /* @__PURE__ */ jsx5("p", { className: "break-words rounded-lg border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-3 py-2 text-sm text-[var(--surface-danger-text)]", children: receipt.error })
891
+ (receipt.fields.length > 0 || receipt.media || receipt.files || receipt.error || receipt.warning) && /* @__PURE__ */ jsxs7("div", { className: "mt-3 space-y-3", children: [
892
+ /* @__PURE__ */ jsx7(HubFieldList, { fields: receipt.fields }),
893
+ receipt.media && /* @__PURE__ */ jsx7(AudioPlayer, { src: receipt.media.src }),
894
+ receipt.files && /* @__PURE__ */ jsx7(ApprovalDiffSummary2, { files: receipt.files }),
895
+ receipt.warning && /* @__PURE__ */ jsx7("p", { className: "break-words rounded-lg border border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] px-3 py-2 text-sm text-[var(--surface-warning-text)]", children: receipt.warning }),
896
+ receipt.error && /* @__PURE__ */ jsx7("p", { className: "break-words rounded-lg border border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] px-3 py-2 text-sm text-[var(--surface-danger-text)]", children: receipt.error })
660
897
  ] }),
661
- (item.grant || item.autoApproved) && /* @__PURE__ */ jsx5("p", { className: "mt-3 text-sm text-muted-foreground", children: item.autoApproved ?? `Allowed ${item.grant?.expiresAt ? "for 24 hours" : "from now on"}: ${item.grant?.scope}` }),
662
- /* @__PURE__ */ jsx5("div", { className: "mt-2", children: /* @__PURE__ */ jsx5(HubRawDetails, { sections: [
898
+ (item.grant || item.autoApproved) && /* @__PURE__ */ jsx7("p", { className: "mt-3 text-sm text-muted-foreground", children: item.autoApproved ?? `Allowed ${item.grant?.expiresAt ? "for 24 hours" : "from now on"}: ${item.grant?.scope}` }),
899
+ /* @__PURE__ */ jsx7("div", { className: "mt-2", children: /* @__PURE__ */ jsx7(HubRawDetails, { sections: [
663
900
  { label: "Call", value: { action: item.actionPath, input: item.input } },
664
901
  { label: "Result", value: item.result }
665
902
  ] }) })
@@ -675,63 +912,74 @@ function HubApprovalReceipts({ items, message, at }) {
675
912
  const verbs = [approved ? `approved ${actions(approved)}` : null, denied ? `denied ${actions(denied)}` : null].filter(Boolean);
676
913
  const summary = verbs.length ? `You ${verbs.join(" and ")}${expired ? `; ${actions(expired)} expired` : ""}` : expired ? `${actions(expired)} expired` : "Your decisions";
677
914
  const when = at ? new Date(at) : null;
678
- return /* @__PURE__ */ jsxs5("section", { "aria-label": "Approval results", className: "space-y-2", children: [
679
- /* @__PURE__ */ jsxs5("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
680
- /* @__PURE__ */ jsx5("span", { children: summary }),
681
- when && !Number.isNaN(when.getTime()) && /* @__PURE__ */ jsxs5("time", { dateTime: when.toISOString(), children: [
915
+ return /* @__PURE__ */ jsxs7("section", { "aria-label": "Approval results", className: "space-y-2", children: [
916
+ /* @__PURE__ */ jsxs7("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
917
+ /* @__PURE__ */ jsx7("span", { children: summary }),
918
+ when && !Number.isNaN(when.getTime()) && /* @__PURE__ */ jsxs7("time", { dateTime: when.toISOString(), children: [
682
919
  "\xB7 ",
683
920
  when.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" })
684
921
  ] })
685
922
  ] }),
686
- decided.map((item) => /* @__PURE__ */ jsx5(HubActionReceiptCard, { item }, item.id)),
687
- message && /* @__PURE__ */ jsx5(HubRawDetails, { label: "Message sent to the agent", sections: [{ label: "The agent received", value: message }] })
923
+ decided.map((item) => /* @__PURE__ */ jsx7(HubActionReceiptCard, { item }, item.id)),
924
+ message && /* @__PURE__ */ jsx7(HubRawDetails, { label: "Message sent to the agent", sections: [{ label: "The agent received", value: message }] })
688
925
  ] });
689
926
  }
690
927
 
691
928
  // src/hub-approvals-react/row.tsx
692
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
693
- function HubApprovalRow({ item, expanded: controlled, onExpandedChange, onReview, actions }) {
694
- const [own, setOwn] = useState5(false);
929
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
930
+ function HubApprovalRow({ item, expanded: controlled, onExpandedChange, onReview, actions, onFeedback }) {
931
+ const [own, setOwn] = useState7(false);
695
932
  const expanded = controlled ?? own;
696
933
  const ref = useRef2(null);
934
+ const feedbackDrafts = useRef2(/* @__PURE__ */ new Map());
935
+ const [feedbackSaving, setFeedbackSaving] = useState7(false);
697
936
  const presentation = presentHubAction(item);
698
937
  const title = item.phase === "done" ? hubActionReceipt(item).title : presentation.title;
699
- useEffect3(() => {
938
+ useEffect5(() => {
700
939
  if (controlled) ref.current?.scrollIntoView({ block: "center", behavior: "smooth" });
701
940
  }, [controlled]);
702
941
  const toggle = () => {
703
942
  setOwn(!expanded);
704
943
  onExpandedChange?.(!expanded);
705
944
  };
706
- return /* @__PURE__ */ jsxs6("div", { ref, "data-hub-approval-row": item.id, className: `rounded-xl border bg-card ${expanded ? "border-border shadow-sm" : "border-border/80"}`, children: [
707
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3 px-3 py-2", children: [
708
- /* @__PURE__ */ jsxs6(
709
- Button4,
945
+ return /* @__PURE__ */ jsxs8("div", { ref, "data-hub-approval-row": item.id, className: `rounded-xl border bg-card ${expanded ? "border-border shadow-sm" : "border-border/80"}`, children: [
946
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3 px-3 py-2", children: [
947
+ /* @__PURE__ */ jsxs8(
948
+ Button5,
710
949
  {
711
950
  variant: "bare",
712
951
  "aria-expanded": expanded,
952
+ disabled: feedbackSaving,
713
953
  onClick: toggle,
714
954
  className: "flex min-h-9 min-w-0 flex-1 items-center gap-3 rounded-md text-left",
715
955
  children: [
716
- /* @__PURE__ */ jsx6(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 24 }),
717
- /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: title }),
718
- /* @__PURE__ */ jsx6(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item) }),
719
- /* @__PURE__ */ jsx6(ChevronDown2, { "aria-hidden": true, className: `size-4 shrink-0 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
956
+ /* @__PURE__ */ jsx8(HubProviderMark, { providerId: presentation.provider.id, name: presentation.provider.name, size: 24 }),
957
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 truncate text-sm font-medium text-foreground", children: title }),
958
+ /* @__PURE__ */ jsx8(HubApprovalPhasePill, { phase: item.phase, label: openPhaseLabel(item) }),
959
+ /* @__PURE__ */ jsx8(ChevronDown2, { "aria-hidden": true, className: `size-4 shrink-0 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
720
960
  ]
721
961
  }
722
962
  ),
723
963
  item.phase === "waiting" && onReview && // On a phone the dock sits just below; the row keeps its width for the title.
724
- /* @__PURE__ */ jsx6(Button4, { variant: "outline", className: "hidden sm:inline-flex", onClick: () => onReview(item), children: "Review" })
964
+ /* @__PURE__ */ jsx8(Button5, { variant: "outline", className: "hidden sm:inline-flex", onClick: () => onReview(item), children: "Review" })
725
965
  ] }),
726
- !expanded && item.phase === "unknown" && /* @__PURE__ */ jsx6("p", { className: "px-3 pb-3 text-sm text-[var(--surface-warning-text)]", children: HUB_APPROVAL_UNKNOWN_WARNING }),
727
- expanded && /* @__PURE__ */ jsxs6("div", { className: "space-y-3 border-t border-border px-3 pb-3 pt-3", children: [
728
- item.phase === "done" || item.phase === "failed" || item.phase === "unknown" ? /* @__PURE__ */ jsx6(HubActionReceiptCard, { item, className: "border-0 px-0 py-0 shadow-none" }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
729
- /* @__PURE__ */ jsx6(HubActionPreviewView, { preview: presentation.preview, account: item.account }),
730
- /* @__PURE__ */ jsx6(HubRawDetails, { sections: [{ label: "Call", value: { action: item.actionPath, input: item.input } }] })
966
+ !expanded && item.phase === "unknown" && /* @__PURE__ */ jsx8("p", { className: "px-3 pb-3 text-sm text-[var(--surface-warning-text)]", children: HUB_APPROVAL_UNKNOWN_WARNING }),
967
+ expanded && /* @__PURE__ */ jsxs8("div", { className: "space-y-3 border-t border-border px-3 pb-3 pt-3", children: [
968
+ item.phase === "done" || item.phase === "failed" || item.phase === "unknown" ? /* @__PURE__ */ jsx8(HubActionReceiptCard, { item, className: "border-0 px-0 py-0 shadow-none" }) : /* @__PURE__ */ jsxs8(Fragment2, { children: [
969
+ /* @__PURE__ */ jsx8(HubActionPreviewView, { preview: presentation.preview, account: item.account }),
970
+ /* @__PURE__ */ jsx8(HubRawDetails, { sections: [{ label: "Call", value: { action: item.actionPath, input: item.input } }] })
731
971
  ] }),
732
- item.phase !== "unknown" && actions
972
+ item.phase !== "unknown" && actions,
973
+ onFeedback && /* @__PURE__ */ jsx8(HubPreviewFeedback, { item, drafts: feedbackDrafts.current, onFeedback: async (source, feedback) => {
974
+ setFeedbackSaving(true);
975
+ try {
976
+ await onFeedback(source, feedback);
977
+ } finally {
978
+ setFeedbackSaving(false);
979
+ }
980
+ } }, item.id)
733
981
  ] }),
734
- !expanded && actions && (item.phase === "expired" || item.phase === "failed") && /* @__PURE__ */ jsx6("div", { className: "border-t border-border px-3 py-2", children: actions })
982
+ !expanded && actions && (item.phase === "expired" || item.phase === "failed") && /* @__PURE__ */ jsx8("div", { className: "border-t border-border px-3 py-2", children: actions })
735
983
  ] });
736
984
  }
737
985
  export {
@@ -743,6 +991,7 @@ export {
743
991
  HubApprovalRow,
744
992
  HubApprovalWaitNote,
745
993
  HubApprovalsList,
994
+ HubPreviewFeedback,
746
995
  HubProviderMark,
747
996
  openPhaseLabel
748
997
  };