@liiift-studio/deploy-vercel-from-sanity 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -38,18 +38,39 @@ var import_sanity5 = require("sanity");
38
38
 
39
39
  // src/icons.tsx
40
40
  var import_react = require("react");
41
+
42
+ // src/compat/resolve.ts
43
+ var sanityUi = __toESM(require("@sanity/ui"));
41
44
  var sanityIcons = __toESM(require("@sanity/icons"));
45
+ var UI = sanityUi;
46
+ var ICONS = sanityIcons;
47
+ function resolveComponent(ns, name) {
48
+ const value = ns[name];
49
+ return typeof value === "function" || typeof value === "object" && value !== null ? value : void 0;
50
+ }
51
+ function resolveFunction(ns, name) {
52
+ const value = ns[name];
53
+ return typeof value === "function" ? value : void 0;
54
+ }
55
+ function resolveRecord(ns, name) {
56
+ const value = ns[name];
57
+ return typeof value === "object" && value !== null ? value : void 0;
58
+ }
59
+ var STACK_USES_GAP = typeof UI.Stack === "function";
60
+
61
+ // src/icons.tsx
42
62
  var import_jsx_runtime = require("react/jsx-runtime");
43
- var INSTALLED = sanityIcons;
44
63
  var GLYPH = { width: "1em", height: "1em", viewBox: "0 0 25 25", fill: "none" };
45
64
  var MissingIcon = (0, import_react.forwardRef)(function MissingIcon2(props, ref) {
46
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...GLYPH, xmlns: "http://www.w3.org/2000/svg", ...props, ref });
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...GLYPH, xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, ref });
47
66
  });
67
+ var SYMBOL_MAP = resolveRecord(ICONS, "icons");
48
68
  function resolveIcon(name, symbol) {
49
- const named = INSTALLED[name];
69
+ const named = resolveComponent(ICONS, name);
50
70
  if (named) return named;
51
- const Icon = INSTALLED.Icon;
71
+ const Icon = resolveComponent(ICONS, "Icon");
52
72
  if (!Icon) return MissingIcon;
73
+ if (SYMBOL_MAP && !(symbol in SYMBOL_MAP)) return MissingIcon;
53
74
  const Resolved = (0, import_react.forwardRef)(function SanityIcon(props, ref) {
54
75
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { symbol, ...props, ref });
55
76
  });
@@ -68,51 +89,131 @@ var EditIcon = resolveIcon("EditIcon", "edit");
68
89
  var EllipsisVerticalIcon = resolveIcon("EllipsisVerticalIcon", "ellipsis-vertical");
69
90
  var LaunchIcon = resolveIcon("LaunchIcon", "launch");
70
91
  var RocketIcon = resolveIcon("RocketIcon", "rocket");
71
- var SchemaIcon = resolveIcon("SchemaIcon", "schema");
72
92
  var TokenIcon = resolveIcon("TokenIcon", "token");
73
93
  var TrashIcon = resolveIcon("TrashIcon", "trash");
74
94
  var WarningOutlineIcon = resolveIcon("WarningOutlineIcon", "warning-outline");
75
95
 
76
96
  // src/components/DeployTool.tsx
77
- var import_react7 = require("react");
97
+ var import_react10 = require("react");
78
98
  var import_sanity3 = require("sanity");
79
- var import_ui11 = require("@sanity/ui");
80
99
 
81
- // src/ui.tsx
100
+ // src/components/DeployItem.tsx
101
+ var import_react7 = require("react");
102
+ var import_react_dom = require("react-dom");
103
+
104
+ // src/compat/primitives.tsx
82
105
  var import_react2 = require("react");
83
- var sanityUi = __toESM(require("@sanity/ui"));
84
- var import_ui = require("@sanity/ui");
85
106
  var import_jsx_runtime2 = require("react/jsx-runtime");
86
- var INSTALLED2 = sanityUi;
87
- var IS_UI_V4_PLUS = !("useToast" in INSTALLED2);
107
+ function domFallback(tag) {
108
+ const Fallback = (0, import_react2.forwardRef)(function SanityUiFallback(props, ref) {
109
+ const { children, style, id, className, onClick, href, title, as, ...rest } = props;
110
+ const element = typeof as === "string" ? as : tag;
111
+ const passthrough = { style, id, className, onClick, href, title, ref };
112
+ for (const key of [
113
+ "role",
114
+ "type",
115
+ "value",
116
+ "checked",
117
+ "placeholder",
118
+ "disabled",
119
+ "tabIndex",
120
+ "onChange",
121
+ "onKeyDown",
122
+ "onFocus",
123
+ "onBlur",
124
+ "onMouseEnter",
125
+ "onMouseLeave",
126
+ "htmlFor",
127
+ "target",
128
+ "rel",
129
+ "name",
130
+ "autoComplete",
131
+ "required",
132
+ "readOnly"
133
+ ]) {
134
+ if (key in rest) passthrough[key] = rest[key];
135
+ }
136
+ for (const key of Object.keys(rest)) {
137
+ if (key.startsWith("aria-") || key.startsWith("data-")) passthrough[key] = rest[key];
138
+ }
139
+ return (0, import_react2.createElement)(element, passthrough, children);
140
+ });
141
+ Fallback.displayName = `SanityUiFallback(${tag})`;
142
+ return Fallback;
143
+ }
144
+ function primitive(name, tag) {
145
+ return resolveComponent(UI, name) ?? domFallback(tag);
146
+ }
147
+ var Box = primitive("Box", "div");
148
+ var Card = primitive("Card", "div");
149
+ var Flex = primitive("Flex", "div");
150
+ var Grid = primitive("Grid", "div");
151
+ var Text = primitive("Text", "span");
152
+ var Heading = primitive("Heading", "h2");
153
+ var Label = primitive("Label", "label");
154
+ var Badge = primitive("Badge", "span");
155
+ var Spinner = primitive("Spinner", "span");
156
+ var Button = primitive("Button", "button");
157
+ var TextInput = primitive("TextInput", "input");
158
+ var Select = primitive("Select", "select");
159
+ var Switch = primitive("Switch", "input");
160
+ var Dialog = primitive("Dialog", "div");
161
+ var SanityStack = primitive("Stack", "div");
88
162
  function Stack({ space, children, ...rest }) {
89
- const spacing = space === void 0 ? {} : IS_UI_V4_PLUS ? { gap: space } : { space };
90
- const Component = import_ui.Stack;
91
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, { ...rest, ...spacing, children });
163
+ const spacing = space === void 0 ? {} : STACK_USES_GAP ? { gap: space } : { space };
164
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SanityStack, { ...rest, ...spacing, children });
92
165
  }
93
- var LOCAL_TOAST_MS = 5e3;
166
+
167
+ // src/compat/toast.tsx
168
+ var import_react3 = require("react");
169
+ var import_jsx_runtime3 = require("react/jsx-runtime");
170
+ var LOCAL_TOAST_MS = 6e3;
171
+ var MAX_VISIBLE_TOASTS = 4;
172
+ var TOAST_Z_INDEX = 1e6;
94
173
  var LOCAL_TOAST_TONE = {
95
174
  success: "positive",
96
175
  error: "critical",
97
176
  warning: "caution",
98
177
  info: "primary"
99
178
  };
179
+ var PERSISTENT_STATUSES = /* @__PURE__ */ new Set(["error", "warning"]);
100
180
  var nextToastId = 0;
101
181
  var localToasts = [];
102
182
  var toastListeners = /* @__PURE__ */ new Set();
183
+ var dismissTimers = /* @__PURE__ */ new Map();
103
184
  function emitToasts() {
104
185
  for (const listener of toastListeners) listener(localToasts);
105
186
  }
187
+ function removeLocalToast(id) {
188
+ const timer = dismissTimers.get(id);
189
+ if (timer) {
190
+ clearTimeout(timer);
191
+ dismissTimers.delete(id);
192
+ }
193
+ localToasts = localToasts.filter((t) => t.id !== id);
194
+ emitToasts();
195
+ }
196
+ function scheduleDismiss(id, status) {
197
+ if (PERSISTENT_STATUSES.has(status ?? "info")) return;
198
+ const existing = dismissTimers.get(id);
199
+ if (existing) clearTimeout(existing);
200
+ dismissTimers.set(id, setTimeout(() => removeLocalToast(id), LOCAL_TOAST_MS));
201
+ }
106
202
  function pushLocalToast(params) {
107
203
  const toast = { ...params, id: nextToastId++ };
108
- localToasts = [...localToasts, toast];
204
+ const next = [...localToasts, toast];
205
+ for (const dropped of next.slice(0, Math.max(0, next.length - MAX_VISIBLE_TOASTS))) {
206
+ const timer = dismissTimers.get(dropped.id);
207
+ if (timer) {
208
+ clearTimeout(timer);
209
+ dismissTimers.delete(dropped.id);
210
+ }
211
+ }
212
+ localToasts = next.slice(-MAX_VISIBLE_TOASTS);
109
213
  emitToasts();
110
- setTimeout(() => {
111
- localToasts = localToasts.filter((t) => t.id !== toast.id);
112
- emitToasts();
113
- }, LOCAL_TOAST_MS);
214
+ scheduleDismiss(toast.id, toast.status);
114
215
  }
115
- var installedUseToast = INSTALLED2.useToast;
216
+ var installedUseToast = resolveFunction(UI, "useToast");
116
217
  var localToaster = { push: pushLocalToast };
117
218
  function useToast() {
118
219
  const real = installedUseToast;
@@ -120,160 +221,350 @@ function useToast() {
120
221
  return localToaster;
121
222
  }
122
223
  function ToastViewport() {
123
- const [toasts, setToasts] = (0, import_react2.useState)(localToasts);
124
- (0, import_react2.useEffect)(() => {
224
+ const [toasts, setToasts] = (0, import_react3.useState)(localToasts);
225
+ (0, import_react3.useEffect)(() => {
125
226
  if (installedUseToast) return;
126
227
  toastListeners.add(setToasts);
228
+ setToasts(localToasts);
127
229
  return () => {
128
230
  toastListeners.delete(setToasts);
129
231
  };
130
232
  }, []);
131
- if (installedUseToast || toasts.length === 0) return null;
132
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Box, { style: { position: "fixed", bottom: 16, right: 16, zIndex: 1e6, maxWidth: 360 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Stack, { space: 2, children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
133
- import_ui.Card,
233
+ const resume = (0, import_react3.useCallback)((toast, el) => {
234
+ if (el && el.contains(document.activeElement)) return;
235
+ scheduleDismiss(toast.id, toast.status);
236
+ }, []);
237
+ const hold = (0, import_react3.useCallback)((id) => {
238
+ const timer = dismissTimers.get(id);
239
+ if (timer) {
240
+ clearTimeout(timer);
241
+ dismissTimers.delete(id);
242
+ }
243
+ }, []);
244
+ if (installedUseToast) return null;
245
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
246
+ Box,
134
247
  {
135
- padding: 3,
136
- radius: 2,
137
- shadow: 3,
138
- tone: LOCAL_TOAST_TONE[toast.status ?? "info"] ?? "primary",
139
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Stack, { space: 2, children: [
140
- toast.title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Text, { size: 1, weight: "semibold", children: toast.title }),
141
- toast.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Text, { size: 1, muted: true, children: toast.description })
142
- ] })
143
- },
144
- toast.id
145
- )) }) });
248
+ role: "log",
249
+ "aria-label": "Deployment notifications",
250
+ style: {
251
+ position: "fixed",
252
+ bottom: 16,
253
+ right: 16,
254
+ zIndex: TOAST_Z_INDEX,
255
+ width: "min(360px, calc(100vw - 32px))",
256
+ pointerEvents: "none"
257
+ },
258
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Stack, { space: 2, children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
259
+ Card,
260
+ {
261
+ padding: 3,
262
+ radius: 2,
263
+ shadow: 3,
264
+ tone: LOCAL_TOAST_TONE[toast.status ?? "info"],
265
+ style: { pointerEvents: "auto" },
266
+ onMouseEnter: () => hold(toast.id),
267
+ onFocusCapture: () => hold(toast.id),
268
+ onMouseLeave: (e) => resume(toast, e.currentTarget),
269
+ onBlurCapture: (e) => resume(toast, e.currentTarget),
270
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Flex, { align: "flex-start", gap: 3, children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Stack, { space: 2, flex: 1, children: [
272
+ toast.title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { size: 1, weight: "semibold", children: toast.title }),
273
+ toast.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { size: 1, muted: true, children: toast.description })
274
+ ] }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
276
+ Button,
277
+ {
278
+ mode: "bleed",
279
+ padding: 2,
280
+ icon: CloseIcon,
281
+ text: "",
282
+ "aria-label": "Dismiss notification",
283
+ onClick: () => removeLocalToast(toast.id)
284
+ }
285
+ )
286
+ ] })
287
+ },
288
+ toast.id
289
+ )) })
290
+ }
291
+ );
146
292
  }
147
- var InstalledTooltip = INSTALLED2.Tooltip;
293
+
294
+ // src/compat/tooltip.tsx
295
+ var import_react4 = require("react");
296
+ var import_jsx_runtime4 = require("react/jsx-runtime");
297
+ var InstalledTooltip = resolveComponent(UI, "Tooltip");
148
298
  function Tooltip({ text, children }) {
299
+ const id = (0, import_react4.useId)();
300
+ const [visible, setVisible] = (0, import_react4.useState)(false);
149
301
  if (InstalledTooltip) {
150
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InstalledTooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Text, { size: 1, children: text }) }), portal: true, children });
302
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InstalledTooltip, { content: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { size: 1, children: text }) }), portal: true, children });
151
303
  }
152
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { title: text, style: { display: "inline-flex" }, children });
304
+ const described = visible && (0, import_react4.isValidElement)(children) ? (0, import_react4.cloneElement)(children, { "aria-describedby": id }) : children;
305
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
306
+ "span",
307
+ {
308
+ style: { position: "relative", display: "inline-flex" },
309
+ onMouseEnter: () => setVisible(true),
310
+ onMouseLeave: () => setVisible(false),
311
+ onFocusCapture: () => setVisible(true),
312
+ onBlurCapture: () => setVisible(false),
313
+ onKeyDown: (e) => {
314
+ if (e.key === "Escape" && visible) {
315
+ e.stopPropagation();
316
+ setVisible(false);
317
+ }
318
+ },
319
+ children: [
320
+ described,
321
+ visible && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
322
+ "span",
323
+ {
324
+ id,
325
+ role: "tooltip",
326
+ style: {
327
+ position: "absolute",
328
+ bottom: "100%",
329
+ left: "50%",
330
+ transform: "translateX(-50%)",
331
+ // No gap between trigger and tooltip, so the pointer can travel onto it
332
+ // without leaving the hover target (WCAG 1.4.13 Hoverable).
333
+ paddingBottom: 4,
334
+ whiteSpace: "nowrap",
335
+ zIndex: 1e3
336
+ },
337
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Card, { radius: 2, shadow: 2, padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { size: 1, children: text }) })
338
+ }
339
+ )
340
+ ]
341
+ }
342
+ );
153
343
  }
154
- var InstalledMenuButton = INSTALLED2.MenuButton;
155
- var InstalledMenu = INSTALLED2.Menu;
156
- var InstalledMenuItem = INSTALLED2.MenuItem;
157
- var HAS_INSTALLED_MENU = Boolean(InstalledMenuButton && InstalledMenu && InstalledMenuItem);
158
- function ActionMenu({ id, items, buttonIcon }) {
159
- const [open, setOpen] = (0, import_react2.useState)(false);
160
- const wrapRef = (0, import_react2.useRef)(null);
161
- (0, import_react2.useEffect)(() => {
162
- if (HAS_INSTALLED_MENU || !open) return;
344
+
345
+ // src/compat/menu.tsx
346
+ var import_react5 = require("react");
347
+ var import_jsx_runtime5 = require("react/jsx-runtime");
348
+ var InstalledMenuButton = resolveComponent(UI, "MenuButton");
349
+ var InstalledMenu = resolveComponent(UI, "Menu");
350
+ var InstalledMenuItem = resolveComponent(UI, "MenuItem");
351
+ var INSTALLED_MENU = InstalledMenuButton && InstalledMenu && InstalledMenuItem ? { MenuButton: InstalledMenuButton, Menu: InstalledMenu, MenuItem: InstalledMenuItem } : null;
352
+ var itemKey = (item, index) => item.key ?? `${index}-${item.text}`;
353
+ function ActionMenu({ id, label, items, buttonIcon }) {
354
+ const menuId = (0, import_react5.useId)();
355
+ const [open, setOpen] = (0, import_react5.useState)(false);
356
+ const [activeIndex, setActiveIndex] = (0, import_react5.useState)(0);
357
+ const wrapRef = (0, import_react5.useRef)(null);
358
+ const triggerRef = (0, import_react5.useRef)(null);
359
+ const itemRefs = (0, import_react5.useRef)([]);
360
+ const [frozenItems, setFrozenItems] = (0, import_react5.useState)(items);
361
+ const shownItems = open ? frozenItems : items;
362
+ const close = (0, import_react5.useCallback)((returnFocus = true) => {
363
+ setOpen(false);
364
+ if (returnFocus) triggerRef.current?.focus();
365
+ }, []);
366
+ const openMenu = (0, import_react5.useCallback)((index) => {
367
+ setFrozenItems(items);
368
+ setActiveIndex(index);
369
+ setOpen(true);
370
+ }, [items]);
371
+ (0, import_react5.useEffect)(() => {
372
+ if (!open || INSTALLED_MENU) return;
373
+ itemRefs.current[activeIndex]?.focus();
374
+ }, [open, activeIndex]);
375
+ (0, import_react5.useEffect)(() => {
376
+ if (INSTALLED_MENU || !open) return;
163
377
  const onPointerDown = (e) => {
164
- if (!wrapRef.current?.contains(e.target)) setOpen(false);
165
- };
166
- const onKeyDown = (e) => {
167
- if (e.key === "Escape") setOpen(false);
378
+ if (wrapRef.current?.contains(e.target)) return;
379
+ const hadFocus = wrapRef.current?.contains(document.activeElement);
380
+ setOpen(false);
381
+ if (hadFocus) triggerRef.current?.focus();
168
382
  };
169
383
  document.addEventListener("mousedown", onPointerDown);
170
- document.addEventListener("keydown", onKeyDown);
171
- return () => {
172
- document.removeEventListener("mousedown", onPointerDown);
173
- document.removeEventListener("keydown", onKeyDown);
174
- };
384
+ return () => document.removeEventListener("mousedown", onPointerDown);
175
385
  }, [open]);
176
- const runAction = (0, import_react2.useCallback)((item) => {
177
- setOpen(false);
386
+ const runAction = (0, import_react5.useCallback)((item) => {
387
+ close();
178
388
  item.onClick?.();
179
- }, []);
180
- if (HAS_INSTALLED_MENU && InstalledMenuButton && InstalledMenu && InstalledMenuItem) {
181
- const MenuButton = InstalledMenuButton;
182
- const Menu = InstalledMenu;
183
- const MenuItem = InstalledMenuItem;
184
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
389
+ }, [close]);
390
+ if (INSTALLED_MENU) {
391
+ const { MenuButton, Menu, MenuItem } = INSTALLED_MENU;
392
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
185
393
  MenuButton,
186
394
  {
187
395
  id,
188
- button: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Button, { mode: "ghost", icon: buttonIcon, padding: 2 }),
396
+ button: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { mode: "ghost", icon: buttonIcon, padding: 2, "aria-label": label }),
189
397
  popover: { placement: "bottom-end" },
190
- menu: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Menu, { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
398
+ menu: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu, { children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
191
399
  MenuItem,
192
400
  {
193
401
  text: item.text,
194
402
  icon: item.icon,
195
403
  tone: item.tone,
196
- ...item.href ? { as: "a", href: item.href, target: "_blank", rel: "noreferrer" } : { onClick: () => item.onClick?.() }
404
+ ...item.href ? { as: "a", href: item.href, target: "_blank", rel: "noreferrer" } : { onClick: item.onClick }
197
405
  },
198
- item.text
406
+ itemKey(item, i)
199
407
  )) })
200
408
  }
201
409
  );
202
410
  }
203
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: wrapRef, style: { position: "relative" }, children: [
204
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
205
- import_ui.Button,
411
+ const onMenuKeyDown = (e) => {
412
+ const last = shownItems.length - 1;
413
+ if (e.key === "Escape") {
414
+ e.stopPropagation();
415
+ close();
416
+ return;
417
+ }
418
+ if (e.key === "Tab") {
419
+ triggerRef.current?.focus();
420
+ setOpen(false);
421
+ return;
422
+ }
423
+ if (e.key === "Enter" || e.key === " ") {
424
+ e.preventDefault();
425
+ const item = shownItems[activeIndex];
426
+ if (item) {
427
+ if (item.href) {
428
+ itemRefs.current[activeIndex]?.click();
429
+ } else {
430
+ runAction(item);
431
+ }
432
+ }
433
+ return;
434
+ }
435
+ if (e.key === "ArrowDown") {
436
+ e.preventDefault();
437
+ setActiveIndex((i) => i >= last ? 0 : i + 1);
438
+ return;
439
+ }
440
+ if (e.key === "ArrowUp") {
441
+ e.preventDefault();
442
+ setActiveIndex((i) => i <= 0 ? last : i - 1);
443
+ return;
444
+ }
445
+ if (e.key === "Home") {
446
+ e.preventDefault();
447
+ setActiveIndex(0);
448
+ return;
449
+ }
450
+ if (e.key === "End") {
451
+ e.preventDefault();
452
+ setActiveIndex(last);
453
+ return;
454
+ }
455
+ };
456
+ const onTriggerKeyDown = (e) => {
457
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
458
+ e.preventDefault();
459
+ openMenu(0);
460
+ } else if (e.key === "ArrowUp") {
461
+ e.preventDefault();
462
+ openMenu(items.length - 1);
463
+ }
464
+ };
465
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: wrapRef, style: { position: "relative" }, children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
467
+ Button,
206
468
  {
469
+ ref: triggerRef,
207
470
  mode: "ghost",
208
471
  icon: buttonIcon,
209
472
  padding: 2,
210
473
  id,
474
+ "aria-label": label,
211
475
  "aria-haspopup": "menu",
212
476
  "aria-expanded": open,
213
- onClick: () => setOpen((o) => !o)
477
+ "aria-controls": open ? menuId : void 0,
478
+ onClick: () => open ? close() : openMenu(0),
479
+ onKeyDown: onTriggerKeyDown
214
480
  }
215
481
  ),
216
- open && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
217
- import_ui.Card,
482
+ open && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
483
+ Card,
218
484
  {
485
+ id: menuId,
219
486
  radius: 2,
220
487
  shadow: 3,
221
488
  padding: 1,
222
489
  role: "menu",
223
- style: { position: "absolute", top: "100%", right: 0, marginTop: 4, zIndex: 1e3, minWidth: 180 },
224
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Stack, { space: 1, children: items.map((item) => {
490
+ "aria-labelledby": id,
491
+ onKeyDown: onMenuKeyDown,
492
+ style: { position: "absolute", top: "100%", right: 0, marginTop: 4, zIndex: 1e3, minWidth: 200 },
493
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Stack, { space: 1, role: "none", children: shownItems.map((item, i) => {
225
494
  const Icon = item.icon;
226
- const label = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ui.Flex, { align: "center", gap: 2, paddingX: 2, paddingY: 2, children: [
227
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { width: "1em", height: "1em" }),
228
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ui.Text, { size: 1, children: item.text })
495
+ const row = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Flex, { align: "center", gap: 2, paddingX: 2, paddingY: 2, children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { width: "1em", height: "1em", "aria-hidden": "true" }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { size: 1, children: item.text })
229
498
  ] });
230
- return item.href ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
499
+ const content = item.tone === "critical" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Card, { tone: "critical", radius: 1, children: row }) : row;
500
+ const shared = {
501
+ role: "menuitem",
502
+ // Roving tabindex — the menu is one tab stop, arrows move within it.
503
+ tabIndex: i === activeIndex ? 0 : -1,
504
+ ref: (el) => {
505
+ itemRefs.current[i] = el;
506
+ },
507
+ style: {
508
+ display: "block",
509
+ width: "100%",
510
+ textAlign: "left",
511
+ cursor: "pointer",
512
+ borderRadius: 3,
513
+ background: "none",
514
+ border: 0,
515
+ padding: 0,
516
+ font: "inherit",
517
+ color: "inherit",
518
+ textDecoration: "none",
519
+ // Card suppresses its own focus ring, so the active item draws one explicitly.
520
+ outline: i === activeIndex ? "2px solid var(--card-focus-ring-color, currentColor)" : "none",
521
+ outlineOffset: -2
522
+ }
523
+ };
524
+ return item.href ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
231
525
  "a",
232
526
  {
233
- role: "menuitem",
527
+ ...shared,
234
528
  href: item.href,
235
529
  target: "_blank",
236
530
  rel: "noreferrer",
237
- onClick: () => setOpen(false),
238
- style: { color: "inherit", textDecoration: "none", display: "block" },
239
- children: label
531
+ onClick: () => {
532
+ setOpen(false);
533
+ triggerRef.current?.focus();
534
+ },
535
+ children: content
240
536
  },
241
- item.text
242
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
243
- import_ui.Card,
537
+ itemKey(item, i)
538
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
539
+ "div",
244
540
  {
245
- as: "button",
246
- role: "menuitem",
247
- tone: item.tone === "critical" ? "critical" : "default",
248
- radius: 1,
541
+ ...shared,
249
542
  onClick: () => runAction(item),
250
- style: { display: "block", width: "100%", textAlign: "left", cursor: "pointer", border: 0, background: "none" },
251
- children: label
543
+ children: content
252
544
  },
253
- item.text
545
+ itemKey(item, i)
254
546
  );
255
547
  }) })
256
548
  }
257
549
  )
258
550
  ] });
259
551
  }
260
- var InstalledCode = INSTALLED2.Code;
552
+
553
+ // src/compat/code.tsx
554
+ var import_jsx_runtime6 = require("react/jsx-runtime");
555
+ var InstalledCode = resolveComponent(UI, "Code");
261
556
  var FALLBACK_CODE_STYLE = {
557
+ display: "block",
262
558
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
263
559
  fontSize: "0.8125rem",
264
560
  lineHeight: 1.4,
265
561
  margin: 0
266
562
  };
267
563
  function Code({ style, children }) {
268
- if (InstalledCode) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InstalledCode, { size: 1, style, children });
269
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { style: { ...FALLBACK_CODE_STYLE, ...style }, children });
564
+ if (InstalledCode) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(InstalledCode, { size: 1, style, children });
565
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { style: { ...FALLBACK_CODE_STYLE, ...style }, children });
270
566
  }
271
567
 
272
- // src/components/DeployItem.tsx
273
- var import_react4 = require("react");
274
- var import_react_dom = require("react-dom");
275
- var import_ui5 = require("@sanity/ui");
276
-
277
568
  // src/lib/api.ts
278
569
  var BASE = "https://api.vercel.com";
279
570
  var VERCEL_HOOK_RE = /^https:\/\/api\.vercel\.com\/v1\/integrations\/deploy\//;
@@ -306,10 +597,14 @@ async function listDeployments(opts) {
306
597
  return data.deployments ?? [];
307
598
  }
308
599
  async function cancelDeployment(opts) {
309
- const params = opts.teamId ? `?teamId=${opts.teamId}` : "";
310
- await vercelFetch(`/v12/deployments/${opts.deploymentId}/cancel${params}`, opts.token, {
311
- method: "PATCH"
312
- });
600
+ const params = new URLSearchParams();
601
+ if (opts.teamId) params.set("teamId", opts.teamId);
602
+ const query = params.toString() ? `?${params}` : "";
603
+ await vercelFetch(
604
+ `/v12/deployments/${encodeURIComponent(opts.deploymentId)}/cancel${query}`,
605
+ opts.token,
606
+ { method: "PATCH" }
607
+ );
313
608
  }
314
609
  async function triggerDeploy(hookUrl) {
315
610
  if (!VERCEL_HOOK_RE.test(hookUrl)) {
@@ -373,6 +668,11 @@ function safeHref(url) {
373
668
  return void 0;
374
669
  }
375
670
  }
671
+ function deploymentHref(host) {
672
+ if (!host) return void 0;
673
+ if (!/^[a-z0-9.-]+$/i.test(host)) return void 0;
674
+ return safeHref(`https://${host}`);
675
+ }
376
676
  function shortSha(sha) {
377
677
  return sha ? sha.slice(0, 7) : "";
378
678
  }
@@ -415,27 +715,26 @@ function projectHref(inspectorUrl) {
415
715
  }
416
716
 
417
717
  // src/components/StatusBadge.tsx
418
- var import_ui2 = require("@sanity/ui");
419
- var import_jsx_runtime3 = require("react/jsx-runtime");
718
+ var import_jsx_runtime7 = require("react/jsx-runtime");
420
719
  function StatusBadge({ state, showSpinner }) {
421
720
  const { label, tone } = stateLabel(state);
422
721
  const spinning = showSpinner && (state === "QUEUED" || state === "INITIALIZING" || state === "BUILDING");
423
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ui2.Flex, { align: "center", gap: 2, children: [
424
- spinning && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui2.Spinner, { muted: true }),
425
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ui2.Badge, { tone, padding: 2, children: label })
722
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Flex, { align: "center", gap: 2, children: [
723
+ spinning && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Spinner, { muted: true }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Badge, { tone, padding: 2, children: label })
426
725
  ] });
427
726
  }
428
727
 
429
728
  // src/components/DeployHistory.tsx
430
- var import_react3 = require("react");
431
- var import_ui3 = require("@sanity/ui");
432
- var import_jsx_runtime4 = require("react/jsx-runtime");
729
+ var import_react6 = require("react");
730
+ var import_jsx_runtime8 = require("react/jsx-runtime");
433
731
  function DeployHistory({ target, token, onClose }) {
434
- const [deployments, setDeployments] = (0, import_react3.useState)([]);
435
- const [loading, setLoading] = (0, import_react3.useState)(true);
436
- const [error, setError] = (0, import_react3.useState)(null);
732
+ const dialogId = (0, import_react6.useId)();
733
+ const [deployments, setDeployments] = (0, import_react6.useState)([]);
734
+ const [loading, setLoading] = (0, import_react6.useState)(true);
735
+ const [error, setError] = (0, import_react6.useState)(null);
437
736
  const { projectId, hookId } = parseHookUrl(target.url);
438
- const load = (0, import_react3.useCallback)(async () => {
737
+ const load = (0, import_react6.useCallback)(async () => {
439
738
  setLoading(true);
440
739
  setError(null);
441
740
  try {
@@ -447,64 +746,68 @@ function DeployHistory({ target, token, onClose }) {
447
746
  setLoading(false);
448
747
  }
449
748
  }, [projectId, hookId, token, target.teamId]);
450
- (0, import_react3.useEffect)(() => {
749
+ (0, import_react6.useEffect)(() => {
451
750
  load();
452
751
  }, [load]);
453
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
454
- import_ui3.Dialog,
752
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
753
+ Dialog,
455
754
  {
456
755
  header: `${target.name} \u2014 Deployment History`,
457
- id: "deploy-history",
756
+ id: dialogId,
458
757
  onClose,
459
758
  width: 2,
460
- footer: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { padding: 3, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Button, { text: "Close", icon: CloseIcon, mode: "ghost", onClick: onClose }) }),
461
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ui3.Box, { padding: 4, children: [
462
- loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Flex, { justify: "center", padding: 6, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Spinner, { muted: true }) }),
463
- error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Card, { tone: "critical", padding: 4, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, children: error }) }),
464
- !loading && !error && deployments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Card, { tone: "transparent", padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, muted: true, align: "center", children: "No deployments found for this hook." }) }),
465
- !loading && deployments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Stack, { space: 2, children: [
466
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ui3.Flex, { gap: 3, children: [
467
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 0, weight: "semibold", muted: true, children: "Preview URL" }) }),
468
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 0, weight: "semibold", muted: true, children: "Status" }) }),
469
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 0, weight: "semibold", muted: true, children: "Branch \xB7 Commit" }) }),
470
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 0, weight: "semibold", muted: true, children: "Deployed" }) }),
471
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { style: { width: 64 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 0, weight: "semibold", muted: true, children: "Logs" }) })
759
+ footer: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { padding: 3, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { text: "Close", icon: CloseIcon, mode: "ghost", onClick: onClose }) }),
760
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box, { padding: 4, children: [
761
+ loading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Flex, { justify: "center", padding: 6, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Spinner, { muted: true }) }),
762
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { tone: "critical", padding: 4, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, children: error }) }),
763
+ !loading && !error && deployments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { tone: "transparent", padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, muted: true, align: "center", children: "No deployments found for this hook." }) }),
764
+ !loading && deployments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 2, children: [
765
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Flex, { gap: 3, children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 0, weight: "semibold", muted: true, children: "Preview URL" }) }),
767
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 0, weight: "semibold", muted: true, children: "Status" }) }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 0, weight: "semibold", muted: true, children: "Branch \xB7 Commit" }) }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 0, weight: "semibold", muted: true, children: "Deployed" }) }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { style: { width: 64 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 0, weight: "semibold", muted: true, children: "Logs" }) })
472
771
  ] }) }),
473
772
  deployments.map((d) => {
474
773
  const { label, tone } = stateLabel(d.state);
475
774
  const branch = d.meta?.githubCommitRef ?? "\u2014";
476
775
  const sha = shortSha(d.meta?.githubCommitSha);
477
776
  const message = d.meta?.githubCommitMessage?.split("\n")[0] ?? "";
478
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Card, { padding: 3, radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ui3.Flex, { gap: 3, align: "center", children: [
479
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 2, style: { overflow: "hidden" }, children: d.url ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
777
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Card, { padding: 3, radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Flex, { gap: 3, align: "center", children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 2, style: { overflow: "hidden" }, children: deploymentHref(d.url) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
480
779
  "a",
481
780
  {
482
- href: `https://${d.url}`,
781
+ href: deploymentHref(d.url),
483
782
  target: "_blank",
484
783
  rel: "noreferrer",
485
784
  style: { color: "inherit" },
486
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, style: { textDecoration: "underline" }, children: d.url.length > 36 ? `${d.url.slice(0, 36)}\u2026` : d.url })
785
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, style: { textDecoration: "underline" }, children: d.url.length > 36 ? `${d.url.slice(0, 36)}\u2026` : d.url })
487
786
  }
488
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, muted: true, children: "\u2014" }) }),
489
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Badge, { tone, children: label }) }),
490
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 2, style: { overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Stack, { space: 1, children: [
491
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, children: branch }),
492
- sha && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ui3.Text, { size: 0, muted: true, children: [
787
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, muted: true, children: "\u2014" }) }),
788
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Badge, { tone, children: label }) }),
789
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 2, style: { overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 1, children: [
790
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, children: branch }),
791
+ sha && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { size: 0, muted: true, children: [
493
792
  sha,
494
793
  message ? ` \xB7 ${message.slice(0, 40)}${message.length > 40 ? "\u2026" : ""}` : ""
495
794
  ] })
496
795
  ] }) }),
497
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, muted: true, children: timeAgo(d.created) }) }),
498
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Box, { style: { width: 64 }, children: safeHref(d.inspectorUrl) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: safeHref(d.inspectorUrl), target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
499
- import_ui3.Button,
796
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { flex: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, muted: true, children: timeAgo(d.created) }) }),
797
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box, { style: { width: 64 }, children: safeHref(d.inspectorUrl) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
798
+ Button,
500
799
  {
800
+ as: "a",
801
+ href: safeHref(d.inspectorUrl),
802
+ target: "_blank",
803
+ rel: "noreferrer",
501
804
  text: "Logs",
502
805
  mode: "ghost",
503
806
  tone: "default",
504
807
  icon: LaunchIcon,
505
808
  style: { fontSize: "12px" }
506
809
  }
507
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ui3.Text, { size: 1, muted: true, children: "\u2014" }) })
810
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { size: 1, muted: true, children: "\u2014" }) })
508
811
  ] }) }, d.uid);
509
812
  })
510
813
  ] })
@@ -514,58 +817,72 @@ function DeployHistory({ target, token, onClose }) {
514
817
  }
515
818
 
516
819
  // src/components/DeployItem.tsx
517
- var import_jsx_runtime5 = require("react/jsx-runtime");
820
+ var import_jsx_runtime9 = require("react/jsx-runtime");
518
821
  var POLL_INTERVAL_MS = 5e3;
519
822
  var LABEL_WIDTH = 64;
520
823
  var PENDING_TIMEOUT_MS = 6e4;
521
824
  function DeployItem({ target, token, onDelete, onEdit }) {
522
825
  const { projectId, hookId } = parseHookUrl(target.url);
523
826
  const toast = useToast();
524
- const [deployments, setDeployments] = (0, import_react4.useState)([]);
525
- const [loadingInitial, setLoadingInitial] = (0, import_react4.useState)(true);
526
- const [pendingSince, setPendingSince] = (0, import_react4.useState)(null);
527
- const [canceling, setCanceling] = (0, import_react4.useState)(false);
528
- const [deployError, setDeployError] = (0, import_react4.useState)(null);
529
- const [showHistory, setShowHistory] = (0, import_react4.useState)(false);
530
- const [elapsed, setElapsed] = (0, import_react4.useState)(0);
531
- const [copied, setCopied] = (0, import_react4.useState)(false);
532
- const [showDetails, setShowDetails] = (0, import_react4.useState)(false);
533
- const [showErrorLogs, setShowErrorLogs] = (0, import_react4.useState)(false);
534
- const [errorLines, setErrorLines] = (0, import_react4.useState)([]);
535
- const [loadingLogs, setLoadingLogs] = (0, import_react4.useState)(false);
536
- const [logError, setLogError] = (0, import_react4.useState)(null);
537
- const triggeredFromUidRef = (0, import_react4.useRef)(void 0);
827
+ const [deployments, setDeployments] = (0, import_react7.useState)([]);
828
+ const [loadingInitial, setLoadingInitial] = (0, import_react7.useState)(true);
829
+ const [pendingSince, setPendingSince] = (0, import_react7.useState)(null);
830
+ const [canceling, setCanceling] = (0, import_react7.useState)(false);
831
+ const [deployError, setDeployError] = (0, import_react7.useState)(null);
832
+ const [showHistory, setShowHistory] = (0, import_react7.useState)(false);
833
+ const [elapsed, setElapsed] = (0, import_react7.useState)(0);
834
+ const [copied, setCopied] = (0, import_react7.useState)(false);
835
+ const [showDetails, setShowDetails] = (0, import_react7.useState)(false);
836
+ const [showErrorLogs, setShowErrorLogs] = (0, import_react7.useState)(false);
837
+ const [errorLines, setErrorLines] = (0, import_react7.useState)([]);
838
+ const [loadingLogs, setLoadingLogs] = (0, import_react7.useState)(false);
839
+ const [logError, setLogError] = (0, import_react7.useState)(null);
840
+ const [pollError, setPollError] = (0, import_react7.useState)(null);
841
+ const triggeredFromUidRef = (0, import_react7.useRef)(void 0);
842
+ const requestSeqRef = (0, import_react7.useRef)(0);
843
+ const mountedRef = (0, import_react7.useRef)(true);
844
+ (0, import_react7.useEffect)(() => {
845
+ mountedRef.current = true;
846
+ return () => {
847
+ mountedRef.current = false;
848
+ };
849
+ }, []);
538
850
  const latest = deployments[0];
539
851
  const isPending = pendingSince !== null;
540
852
  const isActive = isPending || isActiveState(latest?.state);
541
- const fetchDeployments = (0, import_react4.useCallback)(async () => {
853
+ const fetchDeployments = (0, import_react7.useCallback)(async () => {
542
854
  if (!projectId || !hookId || !token) return;
855
+ const seq = ++requestSeqRef.current;
543
856
  try {
544
857
  const data = await listDeployments({ projectId, hookId, token, teamId: target.teamId });
858
+ if (seq !== requestSeqRef.current || !mountedRef.current) return;
545
859
  setDeployments(data);
860
+ setPollError(null);
546
861
  } catch (err) {
547
- console.error("deploy-vercel-from-sanity: fetch error", err);
862
+ if (seq !== requestSeqRef.current || !mountedRef.current) return;
863
+ setPollError(err instanceof Error ? err.message : "Could not reach the Vercel API");
864
+ console.error("Deploy-vercel-from-sanity: fetch error", err);
548
865
  }
549
866
  }, [projectId, hookId, token, target.teamId]);
550
- (0, import_react4.useEffect)(() => {
867
+ (0, import_react7.useEffect)(() => {
551
868
  fetchDeployments().finally(() => setLoadingInitial(false));
552
869
  }, [fetchDeployments]);
553
- (0, import_react4.useEffect)(() => {
870
+ (0, import_react7.useEffect)(() => {
554
871
  if (!isActive) return;
555
872
  const id = setInterval(fetchDeployments, POLL_INTERVAL_MS);
556
873
  return () => clearInterval(id);
557
874
  }, [isActive, fetchDeployments]);
558
- (0, import_react4.useEffect)(() => {
875
+ (0, import_react7.useEffect)(() => {
559
876
  if (!isPending) return;
560
877
  if (latest?.uid && latest.uid !== triggeredFromUidRef.current) setPendingSince(null);
561
878
  }, [isPending, latest?.uid]);
562
- (0, import_react4.useEffect)(() => {
879
+ (0, import_react7.useEffect)(() => {
563
880
  if (!isPending) return;
564
881
  const id = setTimeout(() => setPendingSince(null), PENDING_TIMEOUT_MS);
565
882
  return () => clearTimeout(id);
566
883
  }, [isPending]);
567
- const prevStateRef = (0, import_react4.useRef)(void 0);
568
- (0, import_react4.useEffect)(() => {
884
+ const prevStateRef = (0, import_react7.useRef)(void 0);
885
+ (0, import_react7.useEffect)(() => {
569
886
  const current = latest?.state;
570
887
  const prev = prevStateRef.current;
571
888
  if (prev && isActiveState(prev) && current && !isActiveState(current)) {
@@ -579,13 +896,13 @@ function DeployItem({ target, token, onDelete, onEdit }) {
579
896
  }
580
897
  prevStateRef.current = current;
581
898
  }, [latest?.state, target.name, toast]);
582
- (0, import_react4.useEffect)(() => {
899
+ (0, import_react7.useEffect)(() => {
583
900
  setShowErrorLogs(false);
584
901
  setErrorLines([]);
585
902
  setLogError(null);
586
903
  }, [latest?.uid]);
587
- const timerRef = (0, import_react4.useRef)(null);
588
- (0, import_react4.useEffect)(() => {
904
+ const timerRef = (0, import_react7.useRef)(null);
905
+ (0, import_react7.useEffect)(() => {
589
906
  if (isActive) {
590
907
  const start = pendingSince ?? latest?.created ?? Date.now();
591
908
  setElapsed(Math.floor((Date.now() - start) / 1e3));
@@ -600,7 +917,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
600
917
  if (timerRef.current) clearInterval(timerRef.current);
601
918
  };
602
919
  }, [isActive, pendingSince, latest?.created]);
603
- const deploy = (0, import_react4.useCallback)(() => {
920
+ const deploy = (0, import_react7.useCallback)(() => {
604
921
  (0, import_react_dom.flushSync)(() => {
605
922
  setDeployError(null);
606
923
  triggeredFromUidRef.current = latest?.uid;
@@ -616,7 +933,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
616
933
  }
617
934
  })();
618
935
  }, [target.url, fetchDeployments, latest?.uid]);
619
- const cancel = (0, import_react4.useCallback)(async () => {
936
+ const cancel = (0, import_react7.useCallback)(async () => {
620
937
  if (!latest?.uid) return;
621
938
  setCanceling(true);
622
939
  try {
@@ -628,9 +945,9 @@ function DeployItem({ target, token, onDelete, onEdit }) {
628
945
  setCanceling(false);
629
946
  }
630
947
  }, [latest?.uid, token, target.teamId, fetchDeployments]);
631
- const copyUrl = (0, import_react4.useCallback)(() => {
948
+ const copyUrl = (0, import_react7.useCallback)(() => {
632
949
  if (!latest?.url) return;
633
- const fullUrl = `https://${latest.url}`;
950
+ const fullUrl = deploymentHref(latest.url) ?? "";
634
951
  navigator.clipboard.writeText(fullUrl).then(() => {
635
952
  setCopied(true);
636
953
  setTimeout(() => setCopied(false), 2e3);
@@ -638,7 +955,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
638
955
  window.prompt("Copy deployment URL:", fullUrl);
639
956
  });
640
957
  }, [latest?.url]);
641
- const fetchErrorLogs = (0, import_react4.useCallback)(async () => {
958
+ const fetchErrorLogs = (0, import_react7.useCallback)(async () => {
642
959
  if (!latest?.uid) return;
643
960
  setLoadingLogs(true);
644
961
  setLogError(null);
@@ -656,7 +973,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
656
973
  setLoadingLogs(false);
657
974
  }
658
975
  }, [latest?.uid, token, target.teamId]);
659
- const toggleErrorLogs = (0, import_react4.useCallback)(() => {
976
+ const toggleErrorLogs = (0, import_react7.useCallback)(() => {
660
977
  if (!showErrorLogs && errorLines.length === 0 && !logError) fetchErrorLogs();
661
978
  setShowErrorLogs((v) => !v);
662
979
  }, [showErrorLogs, errorLines.length, logError, fetchErrorLogs]);
@@ -664,26 +981,26 @@ function DeployItem({ target, token, onDelete, onEdit }) {
664
981
  const commitMsg = latest?.meta?.githubCommitMessage?.split("\n")[0];
665
982
  const sha = shortSha(latest?.meta?.githubCommitSha);
666
983
  const fullSha = latest?.meta?.githubCommitSha;
667
- const commitHref = githubCommitHref(latest?.meta);
984
+ const commitHref = safeHref(githubCommitHref(latest?.meta) ?? void 0);
668
985
  const creator = latest?.creator?.username;
669
986
  const deployedAt = latest?.created ? timeAgo(latest.created) : null;
670
987
  const vercelProjectUrl = projectHref(latest?.inspectorUrl);
671
988
  const isError = latest?.state === "ERROR";
672
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
673
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Card, { radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "stretch", className: "dvfs-card-flex", children: [
674
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { direction: "column", flex: 1, style: { minWidth: 0 }, children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 3, padding: 3, style: { flex: 1 }, children: [
676
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", justify: "space-between", gap: 2, children: [
677
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 2, style: { minWidth: 0, flexWrap: "wrap" }, children: [
678
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 2, weight: "semibold", style: { flexShrink: 0 }, children: target.name }),
679
- branch && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Badge, { tone: "default", padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 1, children: [
989
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { radius: 2, shadow: 1, tone: "default", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "stretch", className: "dvfs-card-flex", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { direction: "column", flex: 1, style: { minWidth: 0 }, children: [
992
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 3, padding: 3, style: { flex: 1 }, children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", justify: "space-between", gap: 2, children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, style: { minWidth: 0, flexWrap: "wrap" }, children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 2, weight: "semibold", style: { flexShrink: 0 }, children: target.name }),
996
+ branch && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Badge, { tone: "default", padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 1, children: [
680
997
  branch,
681
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", style: { marginLeft: "-0.1em", opacity: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm0 2.122a2.25 2.25 0 1 0-1.5 0v.878A2.25 2.25 0 0 0 5.75 8.5h1.5v2.128a2.251 2.251 0 1 0 1.5 0V8.5h1.5a2.25 2.25 0 0 0 2.25-2.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75h-4.5A.75.75 0 0 1 5 6.25v-.878zm3.75 7.378a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm3-8.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) })
998
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", style: { marginLeft: "-0.1em", opacity: 0.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm0 2.122a2.25 2.25 0 1 0-1.5 0v.878A2.25 2.25 0 0 0 5.75 8.5h1.5v2.128a2.251 2.251 0 1 0 1.5 0V8.5h1.5a2.25 2.25 0 0 0 2.25-2.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75h-4.5A.75.75 0 0 1 5 6.25v-.878zm3.75 7.378a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0zm3-8.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0z" }) })
682
999
  ] }) }),
683
- token && !loadingInitial && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
684
- isPending ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatusBadge, { state: "QUEUED" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatusBadge, { state: latest?.state }),
685
- isActiveState(latest?.state) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
686
- import_ui5.Button,
1000
+ token && !loadingInitial && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1001
+ isPending ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatusBadge, { state: "QUEUED" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StatusBadge, { state: latest?.state }),
1002
+ isActiveState(latest?.state) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1003
+ Button,
687
1004
  {
688
1005
  text: "Cancel",
689
1006
  mode: "ghost",
@@ -696,17 +1013,18 @@ function DeployItem({ target, token, onDelete, onEdit }) {
696
1013
  ),
697
1014
  isPending ? (
698
1015
  /* Optimistic — dimmed spinner only; no elapsed time until a real build exists */
699
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Spinner, { muted: true, style: { marginLeft: 4, opacity: 0.5 } })
700
- ) : isActive ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 1, children: [
701
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Spinner, { muted: true, style: { marginLeft: 4 } }),
702
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, children: formatDuration(elapsed) })
703
- ] }) : deployedAt ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, children: deployedAt }) : null
1016
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Spinner, { muted: true, style: { marginLeft: 4, opacity: 0.5 } })
1017
+ ) : isActive ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 1, children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Spinner, { muted: true, style: { marginLeft: 4 } }),
1019
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, children: formatDuration(elapsed) })
1020
+ ] }) : deployedAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, children: deployedAt }) : null
704
1021
  ] })
705
1022
  ] }),
706
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1023
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
707
1024
  ActionMenu,
708
1025
  {
709
1026
  id: `menu-${target._id}`,
1027
+ label: `Actions for ${target.name}`,
710
1028
  buttonIcon: EllipsisVerticalIcon,
711
1029
  items: [
712
1030
  { text: "Edit target", icon: EditIcon, onClick: () => onEdit(target) },
@@ -718,56 +1036,75 @@ function DeployItem({ target, token, onDelete, onEdit }) {
718
1036
  }
719
1037
  )
720
1038
  ] }),
721
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("hr", { style: { border: "none", borderTop: "1px solid currentColor", opacity: 0.1, margin: 0 } }),
722
- !token ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, children: "Connect a Vercel API token to see deployment status." }) : loadingInitial ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 2, children: [
723
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Spinner, { muted: true }),
724
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, children: "Loading\u2026" })
725
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 2, children: [
726
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 2, wrap: "wrap", children: [
727
- latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1039
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("hr", { style: { border: "none", borderTop: "1px solid currentColor", opacity: 0.1, margin: 0 } }),
1040
+ !token ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, children: "Connect a Vercel API token to see deployment status." }) : loadingInitial ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, children: [
1041
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Spinner, { muted: true }),
1042
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, children: "Loading\u2026" })
1043
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 2, children: [
1044
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, wrap: "wrap", children: [
1045
+ latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
728
1046
  "a",
729
1047
  {
730
- href: `https://${latest.url}`,
1048
+ href: deploymentHref(latest.url),
731
1049
  target: "_blank",
732
1050
  rel: "noreferrer",
733
1051
  style: { color: "inherit" },
734
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Flex, { align: "center", gap: 1, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, children: latest.url }) })
1052
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Flex, { align: "center", gap: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: latest.url }) })
735
1053
  }
736
1054
  ) }),
737
- sha && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tooltip, { text: commitMsg ?? sha, children: commitHref ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1055
+ sha && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Tooltip, { text: commitMsg ?? sha, children: commitHref ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
738
1056
  "a",
739
1057
  {
740
1058
  href: commitHref,
741
1059
  target: "_blank",
742
1060
  rel: "noreferrer",
743
1061
  style: { color: "inherit", textDecoration: "none" },
744
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, style: { cursor: "pointer", fontFamily: "monospace" }, children: sha })
1062
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, style: { cursor: "pointer", fontFamily: "monospace" }, children: sha })
745
1063
  }
746
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, style: { cursor: "default", fontFamily: "monospace" }, children: sha }) }),
747
- creator && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Text, { size: 1, muted: true, children: [
1064
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, style: { cursor: "default", fontFamily: "monospace" }, children: sha }) }),
1065
+ creator && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { size: 1, muted: true, children: [
748
1066
  "by ",
749
1067
  creator
750
1068
  ] }),
751
- latest?.state === "READY" && latest.ready && latest.created && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Text, { size: 1, muted: true, children: [
1069
+ latest?.state === "READY" && latest.ready && latest.created && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { size: 1, muted: true, children: [
752
1070
  "Took ",
753
1071
  formatDuration(Math.floor((latest.ready - latest.created) / 1e3)),
754
1072
  " to build"
755
1073
  ] }),
756
- latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tooltip, { text: copied ? "Copied!" : "Copy URL", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
757
- import_ui5.Button,
758
- {
759
- mode: "ghost",
760
- icon: copied ? CheckmarkIcon : CopyIcon,
761
- padding: 1,
762
- tone: copied ? "positive" : "default",
763
- onClick: copyUrl,
764
- style: { cursor: "pointer" }
765
- }
766
- ) }) })
1074
+ latest?.url && latest.state === "READY" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1075
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Tooltip, { text: copied ? "Copied" : "Copy deployment URL", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1076
+ Button,
1077
+ {
1078
+ mode: "ghost",
1079
+ icon: copied ? CheckmarkIcon : CopyIcon,
1080
+ padding: 1,
1081
+ tone: copied ? "positive" : "default",
1082
+ onClick: copyUrl,
1083
+ "aria-label": copied ? "Deployment URL copied" : "Copy deployment URL",
1084
+ style: { cursor: "pointer" }
1085
+ }
1086
+ ) }),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1088
+ "span",
1089
+ {
1090
+ role: "status",
1091
+ "aria-live": "polite",
1092
+ style: {
1093
+ position: "absolute",
1094
+ width: 1,
1095
+ height: 1,
1096
+ overflow: "hidden",
1097
+ clip: "rect(0 0 0 0)",
1098
+ whiteSpace: "nowrap"
1099
+ },
1100
+ children: copied ? "Deployment URL copied to clipboard" : ""
1101
+ }
1102
+ )
1103
+ ] })
767
1104
  ] }),
768
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 2, wrap: "wrap", children: [
769
- commitMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
770
- import_ui5.Text,
1105
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, wrap: "wrap", children: [
1106
+ commitMsg && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1107
+ Text,
771
1108
  {
772
1109
  size: 0,
773
1110
  muted: true,
@@ -775,10 +1112,11 @@ function DeployItem({ target, token, onDelete, onEdit }) {
775
1112
  children: commitMsg
776
1113
  }
777
1114
  ),
778
- isError && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 2, children: [
779
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
780
- import_ui5.Button,
1115
+ isError && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 2, children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1117
+ Button,
781
1118
  {
1119
+ "aria-expanded": showErrorLogs,
782
1120
  text: showErrorLogs ? "Hide error details" : "Show error details",
783
1121
  mode: "ghost",
784
1122
  tone: "critical",
@@ -789,37 +1127,45 @@ function DeployItem({ target, token, onDelete, onEdit }) {
789
1127
  style: { alignSelf: "flex-start", cursor: "pointer" }
790
1128
  }
791
1129
  ),
792
- showErrorLogs && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Card, { tone: "critical", radius: 2, padding: 3, children: [
793
- loadingLogs && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 2, children: [
794
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Spinner, { muted: true }),
795
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, muted: true, children: "Loading logs\u2026" })
1130
+ showErrorLogs && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Card, { tone: "critical", radius: 2, padding: 3, children: [
1131
+ loadingLogs && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Spinner, { muted: true }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, muted: true, children: "Loading logs\u2026" })
796
1134
  ] }),
797
- logError && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 2, children: [
798
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, children: logError }),
799
- safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 1, children: [
800
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LaunchIcon, {}),
801
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, children: "View full logs in Vercel" })
1135
+ logError && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 2, children: [
1136
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: logError }),
1137
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 1, children: [
1138
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LaunchIcon, {}),
1139
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: "View full logs in Vercel" })
802
1140
  ] }) })
803
1141
  ] }),
804
- !loadingLogs && !logError && errorLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 2, children: [
805
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Box, { style: { maxHeight: 240, overflowY: "auto", fontFamily: "monospace", fontSize: 13, lineHeight: 1.6 }, children: errorLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Code, { style: { display: "block", whiteSpace: "pre-wrap", wordBreak: "break-all" }, children: line }, i)) }),
806
- safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 1, children: [
807
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LaunchIcon, {}),
808
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, children: "View full logs in Vercel" })
1142
+ !loadingLogs && !logError && errorLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 2, children: [
1143
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box, { style: { maxHeight: 240, overflowY: "auto", fontFamily: "monospace", fontSize: 13, lineHeight: 1.6 }, children: errorLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Code, { style: { display: "block", whiteSpace: "pre-wrap", wordBreak: "break-all" }, children: line }, i)) }),
1144
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 1, children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LaunchIcon, {}),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: "View full logs in Vercel" })
809
1147
  ] }) })
810
1148
  ] })
811
1149
  ] })
812
1150
  ] }),
813
- deployError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 1, children: deployError }) })
1151
+ deployError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 1, children: deployError }) })
814
1152
  ] })
815
- ] })
1153
+ ] }),
1154
+ pollError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "caution", padding: 3, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 2, children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(WarningOutlineIcon, { "aria-hidden": "true" }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { size: 1, children: [
1157
+ "Status updates paused \u2014 ",
1158
+ pollError
1159
+ ] })
1160
+ ] }) })
816
1161
  ] }),
817
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Box, { children: [
818
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
819
- import_ui5.Button,
1162
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box, { children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1164
+ Button,
820
1165
  {
821
1166
  mode: "ghost",
822
1167
  iconRight: showDetails ? ChevronUpIcon : ChevronDownIcon,
1168
+ "aria-expanded": showDetails,
823
1169
  text: "Details",
824
1170
  fontSize: 0,
825
1171
  padding: 3,
@@ -827,62 +1173,62 @@ function DeployItem({ target, token, onDelete, onEdit }) {
827
1173
  style: { width: "100%", justifyContent: "flex-start", borderRadius: 0, cursor: "pointer" }
828
1174
  }
829
1175
  ),
830
- showDetails && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Card, { tone: "primary", padding: 3, className: "dvfs-accordion-content", style: { borderRadius: 0, borderTop: "1px solid rgba(128,128,128,0.15)" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Stack, { space: 2, children: [
831
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
832
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Project" }),
833
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: projectId || "\u2014" })
1176
+ showDetails && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Card, { tone: "primary", padding: 3, className: "dvfs-accordion-content", style: { borderRadius: 0, borderTop: "1px solid rgba(128,128,128,0.15)" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Stack, { space: 2, children: [
1177
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1178
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Project" }),
1179
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: projectId || "\u2014" })
834
1180
  ] }),
835
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
836
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Hook" }),
837
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: hookId || "\u2014" })
1181
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Hook" }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: hookId || "\u2014" })
838
1184
  ] }),
839
- target.teamId && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
840
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Team" }),
841
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: target.teamId })
1185
+ target.teamId && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Team" }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: target.teamId })
842
1188
  ] }),
843
- fullSha && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "flex-start", children: [
844
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Commit" }),
845
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace", wordBreak: "break-all" }, children: fullSha })
1189
+ fullSha && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "flex-start", children: [
1190
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Commit" }),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace", wordBreak: "break-all" }, children: fullSha })
846
1192
  ] }),
847
- latest?.meta?.githubCommitAuthorName && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
848
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Author" }),
849
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, children: latest.meta.githubCommitAuthorName })
1193
+ latest?.meta?.githubCommitAuthorName && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1194
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Author" }),
1195
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, children: latest.meta.githubCommitAuthorName })
850
1196
  ] }),
851
- branch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
852
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Branch" }),
853
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: branch })
1197
+ branch && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1198
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Branch" }),
1199
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: branch })
854
1200
  ] }),
855
- latest?.uid && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
856
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Deploy ID" }),
857
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: latest.uid })
1201
+ latest?.uid && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Deploy ID" }),
1203
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: latest.uid })
858
1204
  ] }),
859
- latest?.url && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "flex-start", children: [
860
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "URL" }),
861
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace", wordBreak: "break-all" }, children: latest.url })
1205
+ latest?.url && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "flex-start", children: [
1206
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "URL" }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace", wordBreak: "break-all" }, children: latest.url })
862
1208
  ] }),
863
- safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
864
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Inspector" }),
865
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { align: "center", gap: 1, children: [
866
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: "Open in Vercel" }),
867
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LaunchIcon, { style: { width: 10, height: 10 } })
1209
+ safeHref(latest?.inspectorUrl) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1210
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Inspector" }),
1211
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("a", { href: safeHref(latest?.inspectorUrl), target: "_blank", rel: "noreferrer", style: { color: "inherit" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { align: "center", gap: 1, children: [
1212
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, style: { fontFamily: "monospace" }, children: "Open in Vercel" }),
1213
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LaunchIcon, { style: { width: 10, height: 10 } })
868
1214
  ] }) })
869
1215
  ] }),
870
- latest?.created && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ui5.Flex, { gap: 2, align: "center", children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Created" }),
872
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ui5.Text, { size: 0, muted: true, children: new Date(latest.created).toLocaleString() })
1216
+ latest?.created && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Flex, { gap: 2, align: "center", children: [
1217
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, weight: "semibold", style: { minWidth: LABEL_WIDTH }, children: "Created" }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { size: 0, muted: true, children: new Date(latest.created).toLocaleString() })
873
1219
  ] })
874
1220
  ] }) })
875
1221
  ] })
876
1222
  ] }),
877
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
878
- import_ui5.Flex,
1223
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1224
+ Flex,
879
1225
  {
880
1226
  direction: "column",
881
1227
  gap: 2,
882
1228
  className: "dvfs-deploy-col",
883
1229
  style: { flexShrink: 0, alignSelf: "stretch" },
884
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
885
- import_ui5.Button,
1230
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1231
+ Button,
886
1232
  {
887
1233
  text: "Deploy",
888
1234
  tone: "primary",
@@ -901,7 +1247,7 @@ function DeployItem({ target, token, onDelete, onEdit }) {
901
1247
  }
902
1248
  )
903
1249
  ] }) }),
904
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1250
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
905
1251
  DeployHistory,
906
1252
  {
907
1253
  target,
@@ -913,17 +1259,19 @@ function DeployItem({ target, token, onDelete, onEdit }) {
913
1259
  }
914
1260
 
915
1261
  // src/components/TokenSetup.tsx
916
- var import_react5 = require("react");
1262
+ var import_react8 = require("react");
917
1263
  var import_sanity = require("sanity");
918
- var import_ui7 = require("@sanity/ui");
919
- var import_jsx_runtime6 = require("react/jsx-runtime");
1264
+ var import_jsx_runtime10 = require("react/jsx-runtime");
920
1265
  var TOKEN_DOC_ID = "config.vercelDeploy";
921
- function TokenSetup({ onSaved, onCancel }) {
1266
+ function TokenSetup({ onSaved, onCancel, hasToken = false }) {
1267
+ const dialogId = (0, import_react8.useId)();
1268
+ const tokenId = (0, import_react8.useId)();
922
1269
  const client = (0, import_sanity.useClient)({ apiVersion: "2025-01-01" });
923
- const [token, setToken] = (0, import_react5.useState)("");
924
- const [saving, setSaving] = (0, import_react5.useState)(false);
925
- const [error, setError] = (0, import_react5.useState)(null);
926
- const save = (0, import_react5.useCallback)(async () => {
1270
+ const [token, setToken] = (0, import_react8.useState)("");
1271
+ const [saving, setSaving] = (0, import_react8.useState)(false);
1272
+ const [error, setError] = (0, import_react8.useState)(null);
1273
+ const [revoking, setRevoking] = (0, import_react8.useState)(false);
1274
+ const save = (0, import_react8.useCallback)(async () => {
927
1275
  if (!token.trim()) return;
928
1276
  setSaving(true);
929
1277
  setError(null);
@@ -940,45 +1288,73 @@ function TokenSetup({ onSaved, onCancel }) {
940
1288
  setSaving(false);
941
1289
  }
942
1290
  }, [client, token, onSaved]);
943
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
944
- import_ui7.Dialog,
1291
+ const revoke = (0, import_react8.useCallback)(async () => {
1292
+ setRevoking(true);
1293
+ setError(null);
1294
+ try {
1295
+ await client.delete(TOKEN_DOC_ID);
1296
+ onSaved();
1297
+ } catch (err) {
1298
+ setError(err instanceof Error ? err.message : "Failed to remove token");
1299
+ } finally {
1300
+ setRevoking(false);
1301
+ }
1302
+ }, [client, onSaved]);
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1304
+ Dialog,
945
1305
  {
946
1306
  header: "Connect Vercel API token",
947
- id: "token-setup",
1307
+ id: dialogId,
948
1308
  onClose: onCancel,
949
1309
  width: 1,
950
- footer: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui7.Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
951
- onCancel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui7.Button, { text: "Cancel", mode: "ghost", onClick: onCancel, style: { cursor: "pointer" } }),
952
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
953
- import_ui7.Button,
1310
+ footer: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Flex, { padding: 3, gap: 2, justify: hasToken ? "space-between" : "flex-end", children: [
1311
+ hasToken && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1312
+ Button,
954
1313
  {
955
- text: "Save and connect",
956
- tone: "primary",
957
- icon: CheckmarkCircleIcon,
958
- loading: saving,
959
- disabled: !token.trim() || saving,
960
- onClick: save,
1314
+ text: "Remove token",
1315
+ mode: "ghost",
1316
+ tone: "critical",
1317
+ icon: TrashIcon,
1318
+ loading: revoking,
1319
+ disabled: revoking || saving,
1320
+ onClick: revoke,
961
1321
  style: { cursor: "pointer" }
962
1322
  }
963
- )
1323
+ ),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Flex, { gap: 2, children: [
1325
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { text: "Cancel", mode: "ghost", onClick: onCancel, style: { cursor: "pointer" } }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1327
+ Button,
1328
+ {
1329
+ text: "Save and connect",
1330
+ tone: "primary",
1331
+ icon: CheckmarkCircleIcon,
1332
+ loading: saving,
1333
+ disabled: !token.trim() || saving,
1334
+ onClick: save,
1335
+ style: { cursor: "pointer" }
1336
+ }
1337
+ )
1338
+ ] })
964
1339
  ] }),
965
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Stack, { space: 4, padding: 4, children: [
966
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Stack, { space: 3, children: [
967
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui7.Text, { size: 1, muted: true, children: "A Vercel API token lets this tool read deployment status, history, build logs, and branch metadata. Without it you can still trigger deploys \u2014 you just won't see any feedback." }),
968
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui7.Text, { size: 1, muted: true, children: [
1340
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Stack, { space: 4, padding: 4, children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Stack, { space: 3, children: [
1342
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { size: 1, muted: true, children: "A Vercel API token lets this tool read deployment status, history, build logs, and branch metadata. Without it you can still trigger deploys \u2014 you just won't see any feedback." }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { size: 1, muted: true, children: [
969
1344
  "Create one at ",
970
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "vercel.com \u2192 Settings \u2192 Tokens" }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "vercel.com \u2192 Settings \u2192 Tokens" }),
971
1346
  " with",
972
1347
  " ",
973
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Full Account" }),
1348
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "Full Account" }),
974
1349
  " scope. The token is stored in your Sanity dataset and shared across all authenticated studio users."
975
1350
  ] })
976
1351
  ] }),
977
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Stack, { space: 2, children: [
978
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui7.Text, { size: 1, weight: "semibold", children: "Vercel API Token" }),
979
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
980
- import_ui7.TextInput,
1352
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Stack, { space: 2, children: [
1353
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { as: "label", size: 1, htmlFor: tokenId, children: "Vercel API Token" }),
1354
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1355
+ TextInput,
981
1356
  {
1357
+ id: tokenId,
982
1358
  value: token,
983
1359
  onChange: (e) => setToken(e.target.value),
984
1360
  placeholder: "xxxxxxxxxxxxxxxxxxxxxxxx",
@@ -986,30 +1362,37 @@ function TokenSetup({ onSaved, onCancel }) {
986
1362
  }
987
1363
  )
988
1364
  ] }),
989
- error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui7.Card, { tone: "critical", padding: 3, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui7.Text, { size: 1, children: error }) })
1365
+ error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Card, { tone: "critical", padding: 3, radius: 2, role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { size: 1, children: error }) })
990
1366
  ] })
991
1367
  }
992
1368
  );
993
1369
  }
994
1370
 
995
1371
  // src/components/DeployTargetForm.tsx
996
- var import_react6 = require("react");
1372
+ var import_react9 = require("react");
997
1373
  var import_sanity2 = require("sanity");
998
- var import_ui9 = require("@sanity/ui");
999
- var import_jsx_runtime7 = require("react/jsx-runtime");
1374
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1000
1375
  var VERCEL_HOOK_RE2 = /^https:\/\/api\.vercel\.com\/v1\/integrations\/deploy\//;
1001
1376
  function DeployTargetForm({ initial, onSaved, onClose }) {
1002
1377
  const client = (0, import_sanity2.useClient)({ apiVersion: "2025-01-01" });
1003
1378
  const isEdit = Boolean(initial);
1004
- const [name, setName] = (0, import_react6.useState)(initial?.name ?? "");
1005
- const [url, setUrl] = (0, import_react6.useState)(initial?.url ?? "");
1006
- const [teamId, setTeamId] = (0, import_react6.useState)(initial?.teamId ?? "");
1007
- const [disableDelete, setDisableDelete] = (0, import_react6.useState)(initial?.disableDeleteAction ?? false);
1008
- const [saving, setSaving] = (0, import_react6.useState)(false);
1009
- const [error, setError] = (0, import_react6.useState)(null);
1379
+ const dialogId = (0, import_react9.useId)();
1380
+ const nameId = (0, import_react9.useId)();
1381
+ const urlId = (0, import_react9.useId)();
1382
+ const urlErrorId = (0, import_react9.useId)();
1383
+ const urlHelpId = (0, import_react9.useId)();
1384
+ const teamId_ = (0, import_react9.useId)();
1385
+ const teamHelpId = (0, import_react9.useId)();
1386
+ const disableId = (0, import_react9.useId)();
1387
+ const [name, setName] = (0, import_react9.useState)(initial?.name ?? "");
1388
+ const [url, setUrl] = (0, import_react9.useState)(initial?.url ?? "");
1389
+ const [teamId, setTeamId] = (0, import_react9.useState)(initial?.teamId ?? "");
1390
+ const [disableDelete, setDisableDelete] = (0, import_react9.useState)(initial?.disableDeleteAction ?? false);
1391
+ const [saving, setSaving] = (0, import_react9.useState)(false);
1392
+ const [error, setError] = (0, import_react9.useState)(null);
1010
1393
  const urlValid = !url || VERCEL_HOOK_RE2.test(url.trim());
1011
1394
  const canSave = name.trim() && url.trim() && urlValid;
1012
- const save = (0, import_react6.useCallback)(async () => {
1395
+ const save = (0, import_react9.useCallback)(async () => {
1013
1396
  if (!canSave) return;
1014
1397
  setSaving(true);
1015
1398
  setError(null);
@@ -1032,17 +1415,17 @@ function DeployTargetForm({ initial, onSaved, onClose }) {
1032
1415
  setSaving(false);
1033
1416
  }
1034
1417
  }, [canSave, isEdit, initial, client, name, url, teamId, disableDelete, onSaved]);
1035
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1036
- import_ui9.Dialog,
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1419
+ Dialog,
1037
1420
  {
1038
1421
  header: isEdit ? `Edit "${initial?.name}"` : "Add deploy target",
1039
- id: "deploy-target-form",
1422
+ id: dialogId,
1040
1423
  onClose,
1041
1424
  width: 1,
1042
- footer: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
1043
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Button, { text: "Cancel", mode: "ghost", onClick: onClose, style: { cursor: "pointer" } }),
1044
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1045
- import_ui9.Button,
1425
+ footer: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
1426
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { text: "Cancel", mode: "ghost", onClick: onClose, style: { cursor: "pointer" } }),
1427
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1428
+ Button,
1046
1429
  {
1047
1430
  text: isEdit ? "Save changes" : "Add target",
1048
1431
  tone: "primary",
@@ -1054,95 +1437,105 @@ function DeployTargetForm({ initial, onSaved, onClose }) {
1054
1437
  }
1055
1438
  )
1056
1439
  ] }),
1057
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Box, { padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Stack, { space: 4, children: [
1058
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Stack, { space: 2, children: [
1059
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Label, { size: 1, children: [
1440
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box, { padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Stack, { space: 4, children: [
1441
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Stack, { space: 2, children: [
1442
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Label, { as: "label", size: 1, htmlFor: nameId, children: [
1060
1443
  "Name ",
1061
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { color: "var(--card-fg-color)" }, children: "*" })
1444
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: "*" })
1062
1445
  ] }),
1063
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1064
- import_ui9.TextInput,
1446
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1447
+ TextInput,
1065
1448
  {
1449
+ id: nameId,
1450
+ required: true,
1451
+ "aria-required": "true",
1066
1452
  value: name,
1067
1453
  onChange: (e) => setName(e.target.value),
1068
1454
  placeholder: "Production"
1069
1455
  }
1070
1456
  )
1071
1457
  ] }),
1072
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Stack, { space: 2, children: [
1073
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Label, { size: 1, children: [
1458
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Stack, { space: 2, children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Label, { as: "label", size: 1, htmlFor: urlId, children: [
1074
1460
  "Deploy hook URL ",
1075
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { color: "var(--card-fg-color)" }, children: "*" })
1461
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: "*" })
1076
1462
  ] }),
1077
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1078
- import_ui9.TextInput,
1463
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1464
+ TextInput,
1079
1465
  {
1466
+ id: urlId,
1467
+ required: true,
1468
+ "aria-required": "true",
1469
+ "aria-invalid": Boolean(url) && !urlValid,
1470
+ "aria-describedby": `${urlHelpId}${url && !urlValid ? ` ${urlErrorId}` : ""}`,
1080
1471
  value: url,
1081
1472
  onChange: (e) => setUrl(e.target.value),
1082
1473
  placeholder: "https://api.vercel.com/v1/integrations/deploy/\u2026"
1083
1474
  }
1084
1475
  ),
1085
- url && !urlValid && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Text, { size: 1, style: { color: "var(--card-critical-fg-color, red)" }, children: "Must be a Vercel deploy hook URL (api.vercel.com/v1/integrations/deploy/\u2026)" }),
1086
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Text, { size: 0, muted: true, children: "Vercel dashboard \u2192 Project \u2192 Settings \u2192 Git \u2192 Deploy Hooks" })
1476
+ url && !urlValid && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Card, { tone: "critical", padding: 2, radius: 2, role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { id: urlErrorId, size: 1, children: "Must be a Vercel deploy hook URL (api.vercel.com/v1/integrations/deploy/\u2026)" }) }),
1477
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { id: urlHelpId, size: 0, muted: true, children: "Vercel dashboard \u2192 Project \u2192 Settings \u2192 Git \u2192 Deploy Hooks" })
1087
1478
  ] }),
1088
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Stack, { space: 2, children: [
1089
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Label, { size: 1, children: [
1479
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Stack, { space: 2, children: [
1480
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Label, { as: "label", size: 1, htmlFor: teamId_, children: [
1090
1481
  "Team ID ",
1091
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { opacity: 0.5, fontWeight: "normal" }, children: "\u2014 optional" })
1482
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { opacity: 0.5, fontWeight: "normal" }, children: "\u2014 optional" })
1092
1483
  ] }),
1093
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1094
- import_ui9.TextInput,
1484
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1485
+ TextInput,
1095
1486
  {
1487
+ id: teamId_,
1488
+ "aria-describedby": teamHelpId,
1096
1489
  value: teamId,
1097
1490
  onChange: (e) => setTeamId(e.target.value),
1098
1491
  placeholder: "team_xxxxxxxx"
1099
1492
  }
1100
1493
  ),
1101
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Text, { size: 0, muted: true, children: [
1494
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { id: teamHelpId, size: 0, muted: true, children: [
1102
1495
  "Required for team-owned Vercel projects. Find it at Vercel \u2192 Settings \u2192 General \u2192 Team ID (starts with ",
1103
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { children: "team_" }),
1496
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("code", { children: "team_" }),
1104
1497
  ")."
1105
1498
  ] })
1106
1499
  ] }),
1107
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui9.Flex, { align: "center", gap: 3, children: [
1108
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1109
- import_ui9.Switch,
1500
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Flex, { align: "center", gap: 3, children: [
1501
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1502
+ Switch,
1110
1503
  {
1111
1504
  checked: disableDelete,
1112
1505
  onChange: (e) => setDisableDelete(e.target.checked),
1113
- id: "disable-delete"
1506
+ id: disableId
1114
1507
  }
1115
1508
  ),
1116
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Stack, { space: 1, children: [
1117
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Label, { as: "label", size: 1, htmlFor: "disable-delete", children: "Disable delete action" }),
1118
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Text, { size: 0, muted: true, children: "Hides the delete button for this target in the studio." })
1509
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Stack, { space: 1, children: [
1510
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { as: "label", size: 1, htmlFor: disableId, children: "Disable delete action" }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { size: 0, muted: true, children: "Hides the delete button for this target in the studio." })
1119
1512
  ] })
1120
1513
  ] }),
1121
- error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Card, { tone: "critical", padding: 3, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui9.Text, { size: 1, children: error }) })
1514
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Card, { tone: "critical", padding: 3, radius: 2, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { size: 1, children: error }) })
1122
1515
  ] }) })
1123
1516
  }
1124
1517
  );
1125
1518
  }
1126
1519
 
1127
1520
  // src/version.ts
1128
- var VERSION = "1.1.1";
1521
+ var VERSION = "1.2.1";
1129
1522
 
1130
1523
  // src/components/DeployTool.tsx
1131
- var import_jsx_runtime8 = require("react/jsx-runtime");
1524
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1132
1525
  var TOKEN_QUERY = `*[_id == "config.vercelDeploy"][0].accessToken`;
1133
- var TARGETS_QUERY = `*[_type == "vercel_deploy"] | order(_createdAt asc)`;
1526
+ var TARGETS_QUERY = `*[_type == "vercel_deploy" && !(_id in path("drafts.**"))] | order(_createdAt asc)`;
1134
1527
  function DeployTool() {
1135
1528
  const client = (0, import_sanity3.useClient)({ apiVersion: "2025-01-01" });
1136
1529
  const toast = useToast();
1137
- const [token, setToken] = (0, import_react7.useState)(null);
1138
- const [targets, setTargets] = (0, import_react7.useState)([]);
1139
- const [loading, setLoading] = (0, import_react7.useState)(true);
1140
- const [showTokenSetup, setShowTokenSetup] = (0, import_react7.useState)(false);
1141
- const [showCreateForm, setShowCreateForm] = (0, import_react7.useState)(false);
1142
- const [pendingEdit, setPendingEdit] = (0, import_react7.useState)(null);
1143
- const [pendingDelete, setPendingDelete] = (0, import_react7.useState)(null);
1144
- const [deleting, setDeleting] = (0, import_react7.useState)(false);
1145
- const load = (0, import_react7.useCallback)(async () => {
1530
+ const [token, setToken] = (0, import_react10.useState)(null);
1531
+ const [targets, setTargets] = (0, import_react10.useState)([]);
1532
+ const [loading, setLoading] = (0, import_react10.useState)(true);
1533
+ const [showTokenSetup, setShowTokenSetup] = (0, import_react10.useState)(false);
1534
+ const [showCreateForm, setShowCreateForm] = (0, import_react10.useState)(false);
1535
+ const [pendingEdit, setPendingEdit] = (0, import_react10.useState)(null);
1536
+ const [pendingDelete, setPendingDelete] = (0, import_react10.useState)(null);
1537
+ const [deleting, setDeleting] = (0, import_react10.useState)(false);
1538
+ const load = (0, import_react10.useCallback)(async () => {
1146
1539
  setLoading(true);
1147
1540
  try {
1148
1541
  const [fetchedToken, fetchedTargets] = await Promise.all([
@@ -1157,11 +1550,20 @@ function DeployTool() {
1157
1550
  setLoading(false);
1158
1551
  }
1159
1552
  }, [client]);
1160
- (0, import_react7.useEffect)(() => {
1553
+ (0, import_react10.useEffect)(() => {
1161
1554
  load();
1162
1555
  }, [load]);
1163
- (0, import_react7.useEffect)(() => {
1164
- if (document.getElementById("dvfs-styles")) return;
1556
+ (0, import_react10.useEffect)(() => {
1557
+ const existing = document.getElementById("dvfs-styles");
1558
+ if (existing) {
1559
+ const n = Number(existing.dataset.refs ?? "1") + 1;
1560
+ existing.dataset.refs = String(n);
1561
+ return () => {
1562
+ const left = Number(existing.dataset.refs ?? "1") - 1;
1563
+ existing.dataset.refs = String(left);
1564
+ if (left <= 0) existing.remove();
1565
+ };
1566
+ }
1165
1567
  const style = document.createElement("style");
1166
1568
  style.id = "dvfs-styles";
1167
1569
  style.textContent = `
@@ -1182,11 +1584,14 @@ function DeployTool() {
1182
1584
  }
1183
1585
  `;
1184
1586
  document.head.appendChild(style);
1587
+ style.dataset.refs = "1";
1185
1588
  return () => {
1186
- document.getElementById("dvfs-styles")?.remove();
1589
+ const left = Number(style.dataset.refs ?? "1") - 1;
1590
+ style.dataset.refs = String(left);
1591
+ if (left <= 0) style.remove();
1187
1592
  };
1188
1593
  }, []);
1189
- (0, import_react7.useEffect)(() => {
1594
+ (0, import_react10.useEffect)(() => {
1190
1595
  const sub = client.listen(TARGETS_QUERY).subscribe(() => {
1191
1596
  client.fetch(TARGETS_QUERY).then(setTargets).catch((err) => {
1192
1597
  console.error("deploy-vercel-from-sanity: subscription refresh error", err);
@@ -1194,11 +1599,13 @@ function DeployTool() {
1194
1599
  });
1195
1600
  return () => sub.unsubscribe();
1196
1601
  }, [client]);
1197
- const confirmDelete = (0, import_react7.useCallback)(async () => {
1602
+ const confirmDelete = (0, import_react10.useCallback)(async () => {
1198
1603
  if (!pendingDelete) return;
1199
1604
  setDeleting(true);
1200
1605
  try {
1201
1606
  await client.delete(pendingDelete._id);
1607
+ await client.delete(`drafts.${pendingDelete._id}`).catch(() => {
1608
+ });
1202
1609
  setTargets((prev) => prev.filter((t) => t._id !== pendingDelete._id));
1203
1610
  toast.push({ status: "success", title: `Deleted "${pendingDelete.name}"` });
1204
1611
  } catch (err) {
@@ -1209,15 +1616,15 @@ function DeployTool() {
1209
1616
  }
1210
1617
  }, [client, pendingDelete, toast]);
1211
1618
  if (loading) {
1212
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Card, { height: "fill", tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Flex, { align: "center", justify: "center", height: "fill", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Spinner, { muted: true }) }) });
1619
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { height: "fill", tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Flex, { align: "center", justify: "center", height: "fill", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner, { muted: true }) }) });
1213
1620
  }
1214
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Card, { height: "fill", tone: "transparent", children: [
1215
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Box, { padding: 5, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 5, children: [
1216
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Flex, { align: "center", justify: "space-between", className: "dvfs-header", children: [
1217
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Heading, { size: 2, children: "Deploy with Vercel" }),
1218
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Flex, { align: "center", gap: 3, className: "dvfs-header-actions", children: [
1219
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1220
- import_ui11.Button,
1621
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Card, { height: "fill", tone: "transparent", children: [
1622
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box, { padding: 5, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Stack, { space: 5, children: [
1623
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Flex, { align: "center", justify: "space-between", className: "dvfs-header", children: [
1624
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Heading, { size: 2, children: "Deploy with Vercel" }),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Flex, { align: "center", gap: 3, className: "dvfs-header-actions", children: [
1626
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1627
+ Button,
1221
1628
  {
1222
1629
  text: token ? "Token connected" : "Connect API token",
1223
1630
  mode: "ghost",
@@ -1228,8 +1635,8 @@ function DeployTool() {
1228
1635
  style: { cursor: "pointer" }
1229
1636
  }
1230
1637
  ),
1231
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1232
- import_ui11.Button,
1638
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1639
+ Button,
1233
1640
  {
1234
1641
  text: "Add target",
1235
1642
  mode: "ghost",
@@ -1241,13 +1648,13 @@ function DeployTool() {
1241
1648
  )
1242
1649
  ] })
1243
1650
  ] }),
1244
- !token && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Card, { padding: 4, radius: 2, tone: "caution", shadow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Flex, { align: "center", justify: "space-between", gap: 4, children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 2, children: [
1246
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Text, { size: 1, weight: "semibold", children: "Deploy status is not connected" }),
1247
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Text, { size: 1, muted: true, children: "You can trigger deploys now. Connect a Vercel API token to also see deployment status, build logs, history, and commit metadata." })
1651
+ !token && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { padding: 4, radius: 2, tone: "caution", shadow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Flex, { align: "center", justify: "space-between", gap: 4, children: [
1652
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Stack, { space: 2, children: [
1653
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { size: 1, weight: "semibold", children: "Deploy status is not connected" }),
1654
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { size: 1, muted: true, children: "You can trigger deploys now. Connect a Vercel API token to also see deployment status, build logs, history, and commit metadata." })
1248
1655
  ] }),
1249
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1250
- import_ui11.Button,
1656
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1657
+ Button,
1251
1658
  {
1252
1659
  text: "Connect",
1253
1660
  tone: "caution",
@@ -1257,16 +1664,16 @@ function DeployTool() {
1257
1664
  }
1258
1665
  )
1259
1666
  ] }) }),
1260
- targets.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Card, { padding: 5, radius: 2, tone: "transparent", shadow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 4, style: { textAlign: "center" }, children: [
1261
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Text, { size: 2, weight: "semibold", children: "No deploy targets configured" }),
1262
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Text, { size: 1, muted: true, children: [
1667
+ targets.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Card, { padding: 5, radius: 2, tone: "transparent", shadow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Stack, { space: 4, style: { textAlign: "center" }, children: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { size: 2, weight: "semibold", children: "No deploy targets configured" }),
1669
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { size: 1, muted: true, children: [
1263
1670
  "Add a deploy target using the button above, or create a",
1264
1671
  " ",
1265
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: "vercel_deploy" }),
1672
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { children: "vercel_deploy" }),
1266
1673
  " document directly in the dataset."
1267
1674
  ] }),
1268
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Flex, { justify: "center", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1269
- import_ui11.Button,
1675
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Flex, { justify: "center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1676
+ Button,
1270
1677
  {
1271
1678
  text: "Add deploy target",
1272
1679
  tone: "primary",
@@ -1276,12 +1683,12 @@ function DeployTool() {
1276
1683
  }
1277
1684
  ) })
1278
1685
  ] }) }),
1279
- targets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "dvfs-grid", style: {
1686
+ targets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "dvfs-grid", style: {
1280
1687
  display: "grid",
1281
1688
  gridTemplateColumns: "repeat(auto-fill, minmax(min(540px, 100%), 1fr))",
1282
1689
  gap: "16px",
1283
1690
  alignItems: "start"
1284
- }, children: targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1691
+ }, children: targets.map((target) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1285
1692
  DeployItem,
1286
1693
  {
1287
1694
  target,
@@ -1292,8 +1699,8 @@ function DeployTool() {
1292
1699
  target._id
1293
1700
  )) })
1294
1701
  ] }) }),
1295
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1296
- import_ui11.Box,
1702
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1703
+ Box,
1297
1704
  {
1298
1705
  style: {
1299
1706
  position: "fixed",
@@ -1303,23 +1710,24 @@ function DeployTool() {
1303
1710
  pointerEvents: "none",
1304
1711
  userSelect: "none"
1305
1712
  },
1306
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Text, { size: 0, muted: true, children: [
1713
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { size: 0, muted: true, children: [
1307
1714
  "v",
1308
1715
  VERSION
1309
1716
  ] })
1310
1717
  }
1311
1718
  ),
1312
- showTokenSetup && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1719
+ showTokenSetup && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1313
1720
  TokenSetup,
1314
1721
  {
1315
1722
  onSaved: () => {
1316
1723
  setShowTokenSetup(false);
1317
1724
  load();
1318
1725
  },
1319
- onCancel: token ? () => setShowTokenSetup(false) : void 0
1726
+ onCancel: () => setShowTokenSetup(false),
1727
+ hasToken: Boolean(token)
1320
1728
  }
1321
1729
  ),
1322
- showCreateForm && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1730
+ showCreateForm && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1323
1731
  DeployTargetForm,
1324
1732
  {
1325
1733
  onSaved: () => {
@@ -1329,7 +1737,7 @@ function DeployTool() {
1329
1737
  onClose: () => setShowCreateForm(false)
1330
1738
  }
1331
1739
  ),
1332
- pendingEdit && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1740
+ pendingEdit && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1333
1741
  DeployTargetForm,
1334
1742
  {
1335
1743
  initial: pendingEdit,
@@ -1340,16 +1748,16 @@ function DeployTool() {
1340
1748
  onClose: () => setPendingEdit(null)
1341
1749
  }
1342
1750
  ),
1343
- pendingDelete && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1344
- import_ui11.Dialog,
1751
+ pendingDelete && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1752
+ Dialog,
1345
1753
  {
1346
1754
  header: "Delete deploy target?",
1347
1755
  id: "confirm-delete",
1348
1756
  onClose: () => setPendingDelete(null),
1349
1757
  width: 1,
1350
- footer: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
1351
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1352
- import_ui11.Button,
1758
+ footer: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Flex, { padding: 3, gap: 2, justify: "flex-end", children: [
1759
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1760
+ Button,
1353
1761
  {
1354
1762
  text: "Cancel",
1355
1763
  mode: "ghost",
@@ -1357,8 +1765,8 @@ function DeployTool() {
1357
1765
  style: { cursor: "pointer" }
1358
1766
  }
1359
1767
  ),
1360
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1361
- import_ui11.Button,
1768
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1769
+ Button,
1362
1770
  {
1363
1771
  text: "Delete",
1364
1772
  tone: "critical",
@@ -1370,26 +1778,59 @@ function DeployTool() {
1370
1778
  }
1371
1779
  )
1372
1780
  ] }),
1373
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Box, { padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Stack, { space: 3, children: [
1374
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui11.Flex, { align: "center", gap: 2, children: [
1375
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(WarningOutlineIcon, {}),
1376
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Text, { size: 2, weight: "semibold", children: pendingDelete.name })
1781
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box, { padding: 4, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Stack, { space: 3, children: [
1782
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Flex, { align: "center", gap: 2, children: [
1783
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WarningOutlineIcon, {}),
1784
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { size: 2, weight: "semibold", children: pendingDelete.name })
1377
1785
  ] }),
1378
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui11.Text, { size: 1, muted: true, children: "This removes the deploy target from the dataset. The Vercel deploy hook itself is not affected." })
1786
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { size: 1, muted: true, children: "This removes the deploy target from the dataset. The Vercel deploy hook itself is not affected." })
1379
1787
  ] }) })
1380
1788
  }
1381
1789
  ),
1382
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToastViewport, {})
1790
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToastViewport, {})
1383
1791
  ] });
1384
1792
  }
1385
1793
 
1386
1794
  // src/schema/vercelDeploy.ts
1387
1795
  var import_sanity4 = require("sanity");
1796
+
1797
+ // src/schema/schemaIcon.tsx
1798
+ var import_react11 = require("react");
1799
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1800
+ var SchemaRocketIcon = (0, import_react11.forwardRef)(
1801
+ function SchemaRocketIcon2(props, ref) {
1802
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1803
+ "svg",
1804
+ {
1805
+ width: "1em",
1806
+ height: "1em",
1807
+ viewBox: "0 0 25 25",
1808
+ fill: "none",
1809
+ xmlns: "http://www.w3.org/2000/svg",
1810
+ "aria-hidden": "true",
1811
+ focusable: "false",
1812
+ ...props,
1813
+ ref,
1814
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1815
+ "path",
1816
+ {
1817
+ d: "M12.5 20.5L15.5 14M11 9.49999L4.5 12.5M9 14C9 14 7.54688 14.9531 6.5 16C5.5 17 4.5 20.5 4.5 20.5C4.5 20.5 8 19.5 9 18.5C10 17.5 11 16 11 16M9 14C9 14 10.1 9.9 12.5 7.5C15.5 4.5 20.5 4.5 20.5 4.5C20.5 4.5 20.5 9.5 17.5 12.5C15.7492 14.2508 11 16 11 16L9 14ZM16.5 9.99999C16.5 10.8284 15.8284 11.5 15 11.5C14.1716 11.5 13.5 10.8284 13.5 9.99999C13.5 9.17157 14.1716 8.49999 15 8.49999C15.8284 8.49999 16.5 9.17157 16.5 9.99999Z",
1818
+ stroke: "currentColor",
1819
+ strokeWidth: 1.2,
1820
+ strokeLinejoin: "round"
1821
+ }
1822
+ )
1823
+ }
1824
+ );
1825
+ }
1826
+ );
1827
+
1828
+ // src/schema/vercelDeploy.ts
1388
1829
  var vercelDeploySchema = (0, import_sanity4.defineType)({
1389
1830
  name: "vercel_deploy",
1390
1831
  title: "Deploy Target",
1391
1832
  type: "document",
1392
- icon: RocketIcon,
1833
+ icon: SchemaRocketIcon,
1393
1834
  fields: [
1394
1835
  (0, import_sanity4.defineField)({
1395
1836
  name: "name",