@cychien/ezdesign 0.9.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +209 -550
  2. package/dist/cli.mjs +2640 -655
  3. package/dist/constants.d.ts +8 -4
  4. package/dist/extension/g/0.11.0-75c3fbebc3/dialog.js +1 -0
  5. package/dist/extension/g/0.11.0-75c3fbebc3/media.js +1 -0
  6. package/dist/extension/g/0.11.0-75c3fbebc3/page.js +44 -0
  7. package/dist/extension/g/0.11.0-75c3fbebc3/worker.js +57 -0
  8. package/dist/extension/manifest.json +15 -12
  9. package/dist/home.css +4706 -0
  10. package/dist/home.js +440 -0
  11. package/dist/native-host.mjs +33804 -115
  12. package/dist/overlay.css +5 -1
  13. package/dist/overlay.js +73 -22
  14. package/dist/shell.css +297 -1
  15. package/dist/shell.js +1230 -358
  16. package/dist/video-digest.js +16388 -0
  17. package/dist/vite.mjs +5 -1
  18. package/package.json +4 -3
  19. package/skills/ezdesign/SKILL.md +7 -3
  20. package/skills/ezdesign/reference/start.md +2 -1
  21. package/dist/extension/g/0.9.2-d9aee83dae/dialog.js +0 -1
  22. package/dist/extension/g/0.9.2-d9aee83dae/media.js +0 -1
  23. package/dist/extension/g/0.9.2-d9aee83dae/page.js +0 -39
  24. package/dist/extension/g/0.9.2-d9aee83dae/worker.js +0 -57
  25. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/dialog.css +0 -0
  26. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/dialog.html +0 -0
  27. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/icon-128.png +0 -0
  28. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/icon-16.png +0 -0
  29. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/icon-32.png +0 -0
  30. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/icon-48.png +0 -0
  31. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/media.html +0 -0
  32. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/setup.css +0 -0
  33. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/setup.html +0 -0
  34. /package/dist/extension/g/{0.9.2-d9aee83dae → 0.11.0-75c3fbebc3}/setup.js +0 -0
package/dist/shell.js CHANGED
@@ -9,6 +9,13 @@
9
9
  ["path", { d: "M18 6L6.00081 17.9992M17.9992 18L6 6.00085", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }]
10
10
  ];
11
11
 
12
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/SidebarLeftIcon.js
13
+ var SidebarLeftIcon = [
14
+ ["path", { d: "M2 12C2 8.31087 2 6.4663 2.81382 5.15877C3.1149 4.67502 3.48891 4.25427 3.91891 3.91554C5.08116 3 6.72077 3 10 3H14C17.2792 3 18.9188 3 20.0811 3.91554C20.5111 4.25427 20.8851 4.67502 21.1862 5.15877C22 6.4663 22 8.31087 22 12C22 15.6891 22 17.5337 21.1862 18.8412C20.8851 19.325 20.5111 19.7457 20.0811 20.0845C18.9188 21 17.2792 21 14 21H10C6.72077 21 5.08116 21 3.91891 20.0845C3.48891 19.7457 3.1149 19.325 2.81382 18.8412C2 17.5337 2 15.6891 2 12Z", stroke: "currentColor", strokeWidth: "1.5", key: "0" }],
15
+ ["path", { d: "M9.5 3L9.5 21", stroke: "currentColor", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }],
16
+ ["path", { d: "M5 7H6M5 10H6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }]
17
+ ];
18
+
12
19
  // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/AlignSelectionIcon.js
13
20
  var AlignSelectionIcon = [
14
21
  ["path", { d: "M20 6V18M18 4H6M18 20H6M4 18V6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
@@ -116,6 +123,14 @@
116
123
  ["path", { d: "M15.5 12H8.5M12 8.5V15.5", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "1" }]
117
124
  ];
118
125
 
126
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/Plug01Icon.js
127
+ var Plug01Icon = [
128
+ ["path", { d: "M15.5 2V6M8.5 6V2", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
129
+ ["path", { d: "M6.00446 7.61331C5.93719 6.74273 6.63957 6 7.53014 6H16.4699C17.3604 6 18.0628 6.74273 17.9955 7.61331L17.8117 9.99197C17.6796 11.7019 17.1011 13.3498 16.132 14.7773L15.5312 15.6622C14.9638 16.4979 14.0077 17 12.9838 17H11.0162C9.99228 17 9.03617 16.4979 8.46881 15.6622L7.86803 14.7773C6.89885 13.3498 6.32041 11.7019 6.18827 9.99197L6.00446 7.61331Z", stroke: "currentColor", strokeWidth: "1.5", key: "1" }],
130
+ ["path", { d: "M12 17V22", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "2" }],
131
+ ["path", { d: "M11 9H13", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "3" }]
132
+ ];
133
+
119
134
  // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/BubbleChatIcon.js
120
135
  var BubbleChatIcon = [
121
136
  ["path", { d: "M21.5 12C21.5 17.2467 17.2467 21.5 12 21.5C10.3719 21.5 8.8394 21.0904 7.5 20.3687C5.63177 19.362 4.37462 20.2979 3.26592 20.4658C3.09774 20.4913 2.93024 20.4302 2.80997 20.31C2.62741 20.1274 2.59266 19.8451 2.6935 19.6074C3.12865 18.5818 3.5282 16.6382 2.98341 15C2.6698 14.057 2.5 13.0483 2.5 12C2.5 6.75329 6.75329 2.5 12 2.5C17.2467 2.5 21.5 6.75329 21.5 12Z", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "0" }],
@@ -143,6 +158,12 @@
143
158
  ["path", { d: "M9.5 13.9995L12.8693 10.6301C13.2731 10.2263 13.8208 9.99945 14.3919 9.99945C15.0873 9.99945 15.7399 10.3353 16.1441 10.9011L20.5 16.9995", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "1.5", key: "3" }]
144
159
  ];
145
160
 
161
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/DollarSquareIcon.js
162
+ var DollarSquareIcon = [
163
+ ["path", { d: "M2.5 12C2.5 7.77027 2.5 5.6554 3.69797 4.25276C3.86808 4.05358 4.05358 3.86808 4.25276 3.69797C5.6554 2.5 7.77027 2.5 12 2.5C16.2297 2.5 18.3446 2.5 19.7472 3.69797C19.9464 3.86808 20.1319 4.05358 20.302 4.25276C21.5 5.6554 21.5 7.77027 21.5 12C21.5 16.2297 21.5 18.3446 20.302 19.7472C20.1319 19.9464 19.9464 20.1319 19.7472 20.302C18.3446 21.5 16.2297 21.5 12 21.5C7.77027 21.5 5.6554 21.5 4.25276 20.302C4.05358 20.1319 3.86808 19.9464 3.69797 19.7472C2.5 18.3446 2.5 16.2297 2.5 12Z", stroke: "currentColor", strokeWidth: "1.5", key: "0" }],
164
+ ["path", { d: "M14.7102 10.0611C14.6111 9.29844 13.7354 8.06622 12.1608 8.06619C10.3312 8.06616 9.56136 9.07946 9.40515 9.58611C9.16145 10.2638 9.21019 11.6571 11.3547 11.809C14.0354 11.999 15.1093 12.3154 14.9727 13.956C14.836 15.5965 13.3417 15.951 12.1608 15.9129C10.9798 15.875 9.04764 15.3325 8.97266 13.8733M11.9734 6.99805V8.06982M11.9734 15.9031V16.998", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "1.5", key: "1" }]
165
+ ];
166
+
146
167
  // src/acp-ask.ts
147
168
  function answeredField(field, answered) {
148
169
  return answered.has(field.key) || (field.kind === "select" || field.kind === "multiselect") && !!field.custom && answered.has(field.custom.key);
@@ -410,23 +431,53 @@
410
431
  // src/client/slash.ts
411
432
  var MAX_QUERY = 24;
412
433
  var BREAK = /[\s ]/;
434
+ var PATH_CHAR = /[./\\:@#%&=?~+*_-]/;
435
+ function opensTrigger(prev) {
436
+ return BREAK.test(prev) || /[\p{P}\p{S}]/u.test(prev) && !PATH_CHAR.test(prev);
437
+ }
413
438
  var menuCount = 0;
439
+ function menuPosition(at2, box, view, scale2) {
440
+ const margin = 8 * scale2;
441
+ const gap = 6 * scale2;
442
+ const left = Math.max(margin, Math.min(at2.left, view.width - box.width - margin));
443
+ const below = at2.bottom + gap;
444
+ const above = at2.top - box.height - gap;
445
+ const crowded = below + box.height > view.height - margin || at2.top > view.height * 0.6;
446
+ const top = crowded && above >= margin ? above : Math.min(below, view.height - box.height - margin);
447
+ return { left, top };
448
+ }
414
449
  function detectSlash(before, trigger = "/") {
415
450
  const start = before.lastIndexOf(trigger);
416
451
  if (start === -1) return null;
417
452
  const prev = start === 0 ? "" : before[start - 1];
418
- if (prev && !BREAK.test(prev)) return null;
453
+ if (prev && !opensTrigger(prev)) return null;
419
454
  const query = before.slice(start + 1);
420
455
  if (query.length > MAX_QUERY || BREAK.test(query)) return null;
421
456
  return { start, query };
422
457
  }
458
+ var WORD_BREAK = /[^\p{L}\p{N}]/u;
459
+ function rank(command, q) {
460
+ const names = [command.id, command.label].map((term) => term.toLowerCase());
461
+ if (names.some((name) => name.startsWith(q))) return 0;
462
+ if (names.some((name) => startsWord(name, q))) return 1;
463
+ if ([...names, ...command.keywords.map((k) => k.toLowerCase())].some((term) => term.includes(q))) return 2;
464
+ return null;
465
+ }
466
+ function startsWord(text, q) {
467
+ let at2 = text.indexOf(q);
468
+ while (at2 !== -1) {
469
+ if (at2 === 0 || WORD_BREAK.test(text.charAt(at2 - 1))) return true;
470
+ at2 = text.indexOf(q, at2 + 1);
471
+ }
472
+ return false;
473
+ }
423
474
  function filterCommands(commands, query) {
424
475
  const available = commands.filter((c) => c.enabled?.() ?? true);
425
476
  const q = query.trim().toLowerCase();
426
477
  if (!q) return available;
427
- return available.filter(
428
- (c) => [c.id, c.label, ...c.keywords].some((term) => term.toLowerCase().includes(q))
429
- );
478
+ const ranked = available.map((command) => ({ command, rank: rank(command, q) })).filter((r) => r.rank !== null).sort((a, b) => a.rank - b.rank).map((r) => r.command);
479
+ if (ranked.length > 0) return ranked;
480
+ return available.filter((c) => c.description && startsWord(c.description.toLowerCase(), q));
430
481
  }
431
482
  function attachSlashMenu(editable, {
432
483
  mk,
@@ -466,11 +517,8 @@
466
517
  const at2 = slashRect();
467
518
  if (!at2) return;
468
519
  const box = menu.getBoundingClientRect();
469
- const left = Math.max(8, Math.min(at2.left, view.innerWidth - box.width - 8));
470
- const below = at2.bottom + 6;
471
- const above = at2.top - box.height - 6;
472
- const crowded = below + box.height > view.innerHeight - 8 || at2.top > view.innerHeight * 0.6;
473
- const top = crowded && above >= 8 ? above : Math.min(below, view.innerHeight - box.height - 8);
520
+ const scale2 = box.width / menu.offsetWidth || 1;
521
+ const { left, top } = menuPosition(at2, box, { width: view.innerWidth, height: view.innerHeight }, scale2);
474
522
  menu.style.left = `${left}px`;
475
523
  menu.style.top = `${top}px`;
476
524
  }
@@ -626,8 +674,38 @@
626
674
  return { close };
627
675
  }
628
676
 
677
+ // src/library/policy.ts
678
+ var VIDEO_CAP_BYTES = 2e8;
679
+ var THUMBNAIL_LONG_SIDE = 400;
680
+ var TAG_MAX_LENGTH = 64;
681
+ function normalizeTag(raw) {
682
+ const name = raw.normalize("NFC").trim().replace(/\s+/g, " ");
683
+ if (name === "" || name.length > TAG_MAX_LENGTH || /[,,]/.test(name)) return null;
684
+ return lowercaseInitial(name);
685
+ }
686
+ function lowercaseInitial(name) {
687
+ const [first = "", second = ""] = name;
688
+ if (second !== second.toLowerCase()) return name;
689
+ return first.toLowerCase() + name.slice(first.length);
690
+ }
691
+ function tagKey(name) {
692
+ return name.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
693
+ }
694
+ function thumbnailSize(width, height) {
695
+ const scale2 = Math.min(1, THUMBNAIL_LONG_SIDE / Math.max(width, height));
696
+ return { width: Math.max(1, Math.round(width * scale2)), height: Math.max(1, Math.round(height * scale2)) };
697
+ }
698
+
699
+ // src/attachment-policy.ts
700
+ var MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
701
+ function isVideoMime(mime) {
702
+ return mime.startsWith("video/");
703
+ }
704
+ function attachmentByteLimit(mime) {
705
+ return isVideoMime(mime) ? VIDEO_CAP_BYTES : MAX_ATTACHMENT_BYTES;
706
+ }
707
+
629
708
  // src/client/attach.ts
630
- var MAX_BYTES = 8 * 1024 * 1024;
631
709
  function directAttachments(api2) {
632
710
  return {
633
711
  async upload(file) {
@@ -799,8 +877,9 @@
799
877
  placeCaretAfter(after);
800
878
  }
801
879
  function add(file) {
802
- if (file.size > MAX_BYTES) {
803
- showError(`${file.name || "\u9019\u500B\u6A94\u6848"} \u8D85\u904E\u55AE\u6A94 8 MB \u4E0A\u9650`);
880
+ const limit = attachmentByteLimit(file.type);
881
+ if (file.size > limit) {
882
+ showError(`${file.name || "\u9019\u500B\u6A94\u6848"} \u8D85\u904E\u55AE\u6A94 ${Math.round(limit / 1e6)} MB \u4E0A\u9650`);
804
883
  return;
805
884
  }
806
885
  const chip = fileChip(file.name || "\u8CBC\u4E0A\u7684\u5716\u7247");
@@ -986,6 +1065,16 @@
986
1065
  refs: () => draftRefs(snapshot2()),
987
1066
  pending: () => [...uploading].filter((c) => editable.contains(c)).length,
988
1067
  pendingNames: () => draftPendingNames(snapshot2()),
1068
+ paintNotes(noteFor) {
1069
+ editable.querySelectorAll(`[${CHIP_ATTR}], [${PICK_ATTR}]`).forEach((chip) => {
1070
+ const ids = chip.hasAttribute(PICK_ATTR) ? parsePick(chip.getAttribute(PICK_ATTR)).map((f) => f.id) : [chip.getAttribute(CHIP_ATTR) ?? ""].filter(Boolean);
1071
+ const text = ids.length ? noteFor(ids) : null;
1072
+ let note2 = chip.querySelector(".ez-chip-note");
1073
+ if (!text) return note2?.remove();
1074
+ note2 ?? (note2 = chip.querySelector(".ez-chip-name").insertAdjacentElement("afterend", mk("span", "ez-chip-note")));
1075
+ note2.textContent = text;
1076
+ });
1077
+ },
989
1078
  // Either menu, whichever is up - at most one can be, since a trigger only
990
1079
  // counts at the start of the word being typed.
991
1080
  closeSlash: () => slash.close() || skillMenu.close(),
@@ -1192,8 +1281,8 @@
1192
1281
  guest: sender.kind
1193
1282
  };
1194
1283
  }),
1195
- /** Mod+wheel, and from a canvas guest ctrl+wheel (a pinch): pan or zoom the
1196
- * stage or the board. `at` is in the guest's viewport. */
1284
+ /** Mod+wheel, and from a canvas guest any wheel while annotating or picking:
1285
+ * pan or zoom the stage or the board. `at` is in the guest's viewport. */
1197
1286
  "ez:wheel": up(BOTH, (r) => {
1198
1287
  const at2 = obj(r.at);
1199
1288
  const x = at2 ? num(at2.x) : null;
@@ -1238,8 +1327,8 @@
1238
1327
  case "upload": {
1239
1328
  const name = str(op.name);
1240
1329
  const mime = str(op.mime);
1241
- if (name === null || mime === null || !(op.bytes instanceof ArrayBuffer)) return null;
1242
- return { op: "upload", name, mime, bytes: op.bytes };
1330
+ if (name === null || mime === null || !(op.file instanceof Blob)) return null;
1331
+ return { op: "upload", name, mime, file: op.file };
1243
1332
  }
1244
1333
  case "discard-upload": {
1245
1334
  const id = str(op.id);
@@ -1342,6 +1431,19 @@
1342
1431
  var ZOOM_MAX = 4;
1343
1432
  var MAX_FRAME_HTML = 512 * 1024;
1344
1433
  var MAX_CANVAS_NAME = 60;
1434
+ var MAX_FRAME_TITLE = 80;
1435
+ var MIN_FRAME_SIZE = { width: 240, height: 240 };
1436
+ var MAX_FRAME_SIZE = { width: 3840, height: 12e3 };
1437
+ var MAX_IMAGE_BYTES = 20 * 1024 * 1024;
1438
+ var IMAGE_TYPES = {
1439
+ "image/png": "png",
1440
+ "image/jpeg": "jpg",
1441
+ "image/gif": "gif",
1442
+ "image/webp": "webp",
1443
+ "image/avif": "avif",
1444
+ "image/svg+xml": "svg"
1445
+ };
1446
+ var ASSET_SHAPE = new RegExp(`^[0-9a-f]{12}\\.(${Object.values(IMAGE_TYPES).join("|")})$`);
1345
1447
 
1346
1448
  // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/AiSketchIcon.js
1347
1449
  var AiSketchIcon = [
@@ -1373,6 +1475,13 @@
1373
1475
  const bottom = Math.max(...rects.map((r) => r.y + r.height));
1374
1476
  return { x, y, width: right - x, height: bottom - y };
1375
1477
  }
1478
+ function imageFrameSize(natural) {
1479
+ const scale2 = Math.min(1, MAX_FRAME_SIZE.width / natural.width, MAX_FRAME_SIZE.height / natural.height);
1480
+ return {
1481
+ width: Math.max(MIN_FRAME_SIZE.width, Math.round(natural.width * scale2)),
1482
+ height: Math.max(MIN_FRAME_SIZE.height, Math.round(natural.height * scale2))
1483
+ };
1484
+ }
1376
1485
  function freeAlongRow(from, size, existing, limit) {
1377
1486
  let x = from.x;
1378
1487
  for (; ; ) {
@@ -1425,12 +1534,26 @@
1425
1534
  height: (stage2.height + 2 * margin) / camera.zoom
1426
1535
  };
1427
1536
  }
1537
+ function boxBetween(a, b) {
1538
+ return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(a.x - b.x), height: Math.abs(a.y - b.y) };
1539
+ }
1428
1540
  function intersects(a, b) {
1429
1541
  return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
1430
1542
  }
1431
1543
  function worldTransform(camera) {
1432
1544
  return `translate(${-camera.x * camera.zoom}px, ${-camera.y * camera.zoom}px) scale(${camera.zoom})`;
1433
1545
  }
1546
+ var EDGE_ZONE = 24;
1547
+ var EDGE_SPEED = 1200;
1548
+ function edgePan(at2, from, stage2) {
1549
+ const axis = (p, start, length) => {
1550
+ const zone = Math.min(EDGE_ZONE, length / 4);
1551
+ if (p < zone && p < start) return -EDGE_SPEED * Math.min(1, (zone - p) / zone);
1552
+ if (p > length - zone && p > start) return EDGE_SPEED * Math.min(1, (p - (length - zone)) / zone);
1553
+ return 0;
1554
+ };
1555
+ return { x: axis(at2.x, from.x, stage2.width), y: axis(at2.y, from.y, stage2.height) };
1556
+ }
1434
1557
 
1435
1558
  // src/client/canvas/guides.ts
1436
1559
  var SNAP_PX = 6;
@@ -1497,6 +1620,23 @@
1497
1620
  copies
1498
1621
  };
1499
1622
  }
1623
+ function planImages(images, center, mint) {
1624
+ const width = images.reduce((sum, image) => sum + image.size.width, 0) + GAP * (images.length - 1);
1625
+ let x = Math.round(center.x - width / 2);
1626
+ const inputs = images.map((image) => {
1627
+ const rect = { x, y: Math.round(center.y - image.size.height / 2), ...image.size };
1628
+ x += image.size.width + GAP;
1629
+ return { id: mint(), asset: image.asset, title: image.title, rect };
1630
+ });
1631
+ const added = inputs.map((input) => input.id);
1632
+ return {
1633
+ label: added.length > 1 ? `\u8CBC\u4E0A ${added.length} \u5F35\u5716\u7247` : "\u8CBC\u4E0A\u5716\u7247",
1634
+ apply: inputs.map((input) => ({ t: "image", input })),
1635
+ undo: [{ t: "remove", ids: added }],
1636
+ redo: [{ t: "restore", ids: added }],
1637
+ added
1638
+ };
1639
+ }
1500
1640
  function planRemove(ids) {
1501
1641
  const list = [...ids];
1502
1642
  return {
@@ -1515,6 +1655,7 @@
1515
1655
  frames2.push({ ...source, id: edit.input.id, rect: { ...source.rect, ...edit.input.at } });
1516
1656
  continue;
1517
1657
  }
1658
+ if (edit.t === "image") continue;
1518
1659
  for (const op of edit.ops) {
1519
1660
  if (op.t === "move") {
1520
1661
  const at2 = new Map(op.frames.map((f) => [f.id, f]));
@@ -1590,7 +1731,7 @@
1590
1731
  var TEST_IDS = {
1591
1732
  /** role=tablist */
1592
1733
  tabs: "ez-tabs",
1593
- /** role=tab, only while a dev server is attached */
1734
+ /** role=tab, while a dev server is attached or there is one to start */
1594
1735
  appTab: "ez-tab-app",
1595
1736
  /** role=tab, `data-canvas-id` */
1596
1737
  canvasTab: "ez-tab-canvas",
@@ -1649,6 +1790,7 @@
1649
1790
  var ZOOM_STEP = 1.25;
1650
1791
  var WHEEL_ZOOM = 25e-4;
1651
1792
  var DRAG_PX = 3;
1793
+ var MAX_PAN_STEP_S = 1 / 30;
1652
1794
  function el(tag, className, text) {
1653
1795
  const node = document.createElement(tag);
1654
1796
  node.className = className;
@@ -1656,12 +1798,13 @@
1656
1798
  return node;
1657
1799
  }
1658
1800
  var BoardView = class {
1659
- constructor(canvas, host) {
1801
+ constructor(canvas, host2) {
1660
1802
  this.canvas = canvas;
1661
- this.host = host;
1803
+ this.host = host2;
1662
1804
  __publicField(this, "el");
1663
1805
  __publicField(this, "world");
1664
1806
  __publicField(this, "heads");
1807
+ __publicField(this, "marquee");
1665
1808
  __publicField(this, "guides");
1666
1809
  __publicField(this, "empty");
1667
1810
  __publicField(this, "readout");
@@ -1685,6 +1828,8 @@
1685
1828
  __publicField(this, "stage", { width: 0, height: 0 });
1686
1829
  __publicField(this, "shown", false);
1687
1830
  __publicField(this, "frameRequested", 0);
1831
+ __publicField(this, "edgePanRequested", 0);
1832
+ __publicField(this, "edgePanAt", 0);
1688
1833
  __publicField(this, "settleTimer", 0);
1689
1834
  __publicField(this, "saveTimer", 0);
1690
1835
  /** The zoom the frames were last told, so a pan sends them nothing. */
@@ -1708,11 +1853,13 @@
1708
1853
  this.guides = el("div", "ez-guides");
1709
1854
  this.world.append(this.guides);
1710
1855
  this.heads = el("div", "ez-board-heads");
1856
+ this.marquee = el("div", "ez-board-marquee");
1857
+ this.marquee.hidden = true;
1711
1858
  this.empty = el("div", "ez-board-empty");
1712
1859
  this.empty.append(icon(AiSketchIcon, 64));
1713
1860
  const tools = el("div", "ez-board-tools");
1714
1861
  tools.dataset.testid = TEST_IDS.tools;
1715
- const button = (label, glyph, run) => {
1862
+ const button2 = (label, glyph, run) => {
1716
1863
  const b = el("button", "ez-board-tool");
1717
1864
  b.append(icon(glyph, 14));
1718
1865
  b.setAttribute("aria-label", label);
@@ -1725,11 +1872,11 @@
1725
1872
  this.readout.setAttribute("role", "status");
1726
1873
  this.readout.setAttribute("aria-label", LABELS.zoomLevel);
1727
1874
  tools.append(
1728
- button(LABELS.zoomOut, MinusIcon, () => this.zoomBy(1 / ZOOM_STEP)),
1875
+ button2(LABELS.zoomOut, MinusIcon, () => this.zoomBy(1 / ZOOM_STEP)),
1729
1876
  this.readout,
1730
- button(LABELS.zoomIn, PlusIcon, () => this.zoomBy(ZOOM_STEP))
1877
+ button2(LABELS.zoomIn, PlusIcon, () => this.zoomBy(ZOOM_STEP))
1731
1878
  );
1732
- this.el.append(this.world, this.heads, this.empty, tools);
1879
+ this.el.append(this.world, this.heads, this.marquee, this.empty, tools);
1733
1880
  this.el.addEventListener("wheel", (e) => this.onBoardWheel(e), { passive: false });
1734
1881
  this.el.addEventListener("pointerdown", (e) => this.onPointerDown(e));
1735
1882
  this.el.addEventListener("pointermove", (e) => this.onPointerMove(e));
@@ -1818,6 +1965,7 @@
1818
1965
  window.clearTimeout(this.settleTimer);
1819
1966
  window.clearTimeout(this.saveTimer);
1820
1967
  cancelAnimationFrame(this.frameRequested);
1968
+ cancelAnimationFrame(this.edgePanRequested);
1821
1969
  this.el.remove();
1822
1970
  }
1823
1971
  /** E/R on: frames take the pointer so the overlay annotates; off: the board
@@ -1969,6 +2117,23 @@
1969
2117
  this.commit(step2);
1970
2118
  this.select(new Set(step2.copies));
1971
2119
  }
2120
+ /** Pasted images, each at its natural size, in a row centred on the view and
2121
+ * selected, as a paste lands in Figma. */
2122
+ async pasteImages(files) {
2123
+ const stored = await Promise.all(
2124
+ files.map(async (file) => {
2125
+ const natural = await naturalSize(file);
2126
+ const asset = natural && await this.host.storeImage(file);
2127
+ return natural && asset ? { asset, title: imageTitle(file), size: imageFrameSize(natural) } : null;
2128
+ })
2129
+ );
2130
+ const images = stored.filter((image) => image !== null);
2131
+ if (!images.length || !this.camera) return;
2132
+ const center = screenToWorld(this.camera, { x: this.stage.width / 2, y: this.stage.height / 2 });
2133
+ const step2 = planImages(images, center, () => mintFrameId((b) => crypto.getRandomValues(b)));
2134
+ this.commit(step2);
2135
+ this.select(new Set(step2.added));
2136
+ }
1972
2137
  select(ids) {
1973
2138
  this.selection = ids;
1974
2139
  this.paintSelection();
@@ -2156,11 +2321,13 @@
2156
2321
  } else {
2157
2322
  this.setCamera(panned(this.camera, -e.deltaX * unit, -e.deltaY * unit));
2158
2323
  }
2324
+ if (this.gesture?.t === "move" && this.gesture.started) this.dragTo(this.gesture);
2325
+ if (this.gesture?.t === "marquee" && this.gesture.started) this.paintMarquee(this.gesture);
2159
2326
  }
2160
- /** The background, or anywhere with Space held, pans; a frame's head or body
2161
- * selects and drags it. */
2327
+ /** Space held or the middle button pans; the background draws a selection
2328
+ * box; a frame's head or body selects and drags it. */
2162
2329
  onPointerDown(e) {
2163
- if (e.button !== 0 || !this.camera || this.gesture) return;
2330
+ if (e.button !== 0 && e.button !== 1 || !this.camera || this.gesture) return;
2164
2331
  const target2 = e.target;
2165
2332
  if (target2.closest(".ez-board-tools")) return;
2166
2333
  this.following = false;
@@ -2170,7 +2337,7 @@
2170
2337
  this.el.setPointerCapture(e.pointerId);
2171
2338
  this.gesture = gesture;
2172
2339
  };
2173
- if (this.spaceHeld) {
2340
+ if (this.spaceHeld || e.button === 1) {
2174
2341
  start({ t: "pan", pointer: e.pointerId, x: e.clientX, y: e.clientY, camera: this.camera });
2175
2342
  this.el.classList.add("ez-panning");
2176
2343
  return;
@@ -2192,36 +2359,76 @@
2192
2359
  } else if (!this.selection.has(id)) {
2193
2360
  this.select(/* @__PURE__ */ new Set([id]));
2194
2361
  }
2195
- start({ t: "move", pointer: e.pointerId, x: e.clientX, y: e.clientY, ids: new Set(this.selection), started: false, dx: 0, dy: 0, toggleOnClick });
2362
+ const from = this.boardPoint(e);
2363
+ start({
2364
+ t: "move",
2365
+ pointer: e.pointerId,
2366
+ x: e.clientX,
2367
+ y: e.clientY,
2368
+ from,
2369
+ at: from,
2370
+ camera: this.camera,
2371
+ ids: new Set(this.selection),
2372
+ started: false,
2373
+ dx: 0,
2374
+ dy: 0,
2375
+ toggleOnClick
2376
+ });
2196
2377
  return;
2197
2378
  }
2198
2379
  if (!id) {
2199
2380
  this.setLive(null);
2200
- this.select(/* @__PURE__ */ new Set());
2381
+ if (!e.shiftKey) this.select(/* @__PURE__ */ new Set());
2201
2382
  this.host.escape();
2202
- start({ t: "pan", pointer: e.pointerId, x: e.clientX, y: e.clientY, camera: this.camera });
2203
- this.el.classList.add("ez-panning");
2383
+ const from = this.boardPoint(e);
2384
+ start({
2385
+ t: "marquee",
2386
+ pointer: e.pointerId,
2387
+ origin: screenToWorld(this.camera, from),
2388
+ from,
2389
+ at: from,
2390
+ base: new Set(this.selection),
2391
+ started: false
2392
+ });
2204
2393
  }
2205
2394
  }
2206
2395
  onPointerMove(e) {
2207
2396
  const g = this.gesture;
2208
2397
  if (!g || e.pointerId !== g.pointer || !this.camera) return;
2209
- const zoom = this.camera.zoom;
2210
2398
  if (g.t === "pan") {
2211
2399
  this.setCamera(panned(g.camera, e.clientX - g.x, e.clientY - g.y));
2212
2400
  return;
2213
2401
  }
2402
+ if (g.t === "marquee") {
2403
+ g.at = this.boardPoint(e);
2404
+ if (!g.started && Math.hypot(g.at.x - g.from.x, g.at.y - g.from.y) < DRAG_PX) return;
2405
+ g.started = true;
2406
+ this.paintMarquee(g);
2407
+ this.startEdgePan();
2408
+ return;
2409
+ }
2214
2410
  if (!g.started) {
2215
2411
  if (Math.hypot(e.clientX - g.x, e.clientY - g.y) < DRAG_PX) return;
2216
2412
  g.started = true;
2217
2413
  this.el.classList.add("ez-arranging");
2218
2414
  }
2415
+ g.at = { x: g.from.x + e.clientX - g.x, y: g.from.y + e.clientY - g.y };
2416
+ this.dragTo(g);
2417
+ this.startEdgePan();
2418
+ }
2419
+ /** The dragged frames under the pointer, wherever the camera has gone since
2420
+ * the press. */
2421
+ dragTo(g) {
2422
+ const camera = this.camera;
2423
+ if (!camera) return;
2219
2424
  const moving = this.frames.filter((f) => g.ids.has(f.id));
2220
2425
  const box = boundsOf(moving.map((f) => f.rect));
2221
2426
  if (!box) return;
2222
- const raw = { ...box, x: box.x + (e.clientX - g.x) / zoom, y: box.y + (e.clientY - g.y) / zoom };
2427
+ const grabbed = screenToWorld(g.camera, g.from);
2428
+ const pointer = screenToWorld(camera, g.at);
2429
+ const raw = { ...box, x: box.x + pointer.x - grabbed.x, y: box.y + pointer.y - grabbed.y };
2223
2430
  const others = this.frames.filter((f) => !g.ids.has(f.id)).map((f) => f.rect);
2224
- const snapped = snapMove(raw, others, zoom);
2431
+ const snapped = snapMove(raw, others, camera.zoom);
2225
2432
  g.dx = Math.round(snapped.rect.x - box.x);
2226
2433
  g.dy = Math.round(snapped.rect.y - box.y);
2227
2434
  for (const f of moving) {
@@ -2230,12 +2437,48 @@
2230
2437
  this.paintHeads();
2231
2438
  this.paintGuides(snapped.guides);
2232
2439
  }
2440
+ /** Selects every frame the box touches, on top of what a shift-press kept. */
2441
+ paintMarquee(g) {
2442
+ const camera = this.camera;
2443
+ if (!camera) return;
2444
+ const box = boxBetween(g.origin, screenToWorld(camera, g.at));
2445
+ const hits = this.frames.filter((f) => intersects(box, f.rect)).map((f) => f.id);
2446
+ this.select(/* @__PURE__ */ new Set([...g.base, ...hits]));
2447
+ const screen = boxBetween(worldToScreen(camera, g.origin), g.at);
2448
+ this.marquee.style.transform = `translate(${screen.x}px, ${screen.y}px)`;
2449
+ this.marquee.style.width = `${screen.width}px`;
2450
+ this.marquee.style.height = `${screen.height}px`;
2451
+ this.marquee.hidden = false;
2452
+ }
2453
+ startEdgePan() {
2454
+ if (this.edgePanRequested) return;
2455
+ this.edgePanAt = performance.now();
2456
+ this.edgePanRequested = requestAnimationFrame((now) => this.edgePanStep(now));
2457
+ }
2458
+ /** One frame of a drag held at the edge: pan, then carry the frames along.
2459
+ * Stops by itself once the pointer leaves the edge or the drag ends. */
2460
+ edgePanStep(now) {
2461
+ this.edgePanRequested = 0;
2462
+ const g = this.gesture;
2463
+ if (!g || g.t === "pan" || !g.started || !this.camera) return;
2464
+ const speed = edgePan(g.at, g.from, this.stage);
2465
+ if (!speed.x && !speed.y) return;
2466
+ const dt = Math.min(MAX_PAN_STEP_S, Math.max(0, now - this.edgePanAt) / 1e3);
2467
+ this.edgePanAt = now;
2468
+ this.setCamera(panned(this.camera, -speed.x * dt, -speed.y * dt));
2469
+ if (g.t === "move") this.dragTo(g);
2470
+ else this.paintMarquee(g);
2471
+ this.edgePanRequested = requestAnimationFrame((next) => this.edgePanStep(next));
2472
+ }
2233
2473
  onPointerUp(e, cancelled = false) {
2234
2474
  const g = this.gesture;
2235
2475
  if (!g || e.pointerId !== g.pointer) return;
2236
2476
  this.gesture = null;
2477
+ cancelAnimationFrame(this.edgePanRequested);
2478
+ this.edgePanRequested = 0;
2237
2479
  this.el.classList.remove("ez-panning", "ez-arranging");
2238
2480
  this.paintGuides([]);
2481
+ this.marquee.hidden = true;
2239
2482
  if (g.t !== "move") return;
2240
2483
  if (!g.started) {
2241
2484
  if (g.toggleOnClick) this.select(new Set([...this.selection].filter((id) => id !== g.toggleOnClick)));
@@ -2255,6 +2498,29 @@
2255
2498
  if (id && this.cards.get(id)?.iframe) this.setLive(id);
2256
2499
  }
2257
2500
  };
2501
+ async function naturalSize(image) {
2502
+ const bitmap = await createImageBitmap(image).catch(() => null);
2503
+ if (bitmap) {
2504
+ const size = { width: bitmap.width, height: bitmap.height };
2505
+ bitmap.close();
2506
+ return size;
2507
+ }
2508
+ const url = URL.createObjectURL(image);
2509
+ try {
2510
+ const img = new Image();
2511
+ img.src = url;
2512
+ await img.decode();
2513
+ return img.naturalWidth && img.naturalHeight ? { width: img.naturalWidth, height: img.naturalHeight } : null;
2514
+ } catch {
2515
+ return null;
2516
+ } finally {
2517
+ URL.revokeObjectURL(url);
2518
+ }
2519
+ }
2520
+ function imageTitle(file) {
2521
+ const stem = file.name.replace(/\.[^.]*$/, "").trim();
2522
+ return [...stem || "\u5716\u7247"].slice(0, MAX_FRAME_TITLE).join("");
2523
+ }
2258
2524
 
2259
2525
  // src/client/confirm-dialog.ts
2260
2526
  function confirmDialog(offer) {
@@ -2324,10 +2590,10 @@
2324
2590
  ];
2325
2591
 
2326
2592
  // src/client/canvas/tabs.ts
2327
- function tabsModel(app, canvas) {
2593
+ function tabsModel(appTab, canvas) {
2328
2594
  const open = canvas.canvases.filter((c) => !c.archived).sort((a, b) => a.createdAt - b.createdAt);
2329
2595
  const tabs = [
2330
- ...app ? [{ id: "app", label: LABELS.appTab, closable: false }] : [],
2596
+ ...appTab ? [{ id: "app", label: LABELS.appTab, closable: false }] : [],
2331
2597
  ...open.map((c) => ({ id: c.id, label: LABELS.canvasTab(c.name), closable: true }))
2332
2598
  ];
2333
2599
  const active = tabs.some((t) => t.id === canvas.activeTab) ? canvas.activeTab : tabs[0]?.id ?? null;
@@ -2549,6 +2815,9 @@
2549
2815
  };
2550
2816
 
2551
2817
  // src/client/canvas/index.ts
2818
+ function returnKeysToShell(board, frame) {
2819
+ if (document.activeElement === frame) board.el.focus({ preventScroll: true });
2820
+ }
2552
2821
  async function canvasRequest(api2, request) {
2553
2822
  const [method, path, body] = (() => {
2554
2823
  switch (request.do) {
@@ -2568,11 +2837,18 @@
2568
2837
  return ["POST", `/canvases/${request.canvasId}/ops`, request.step];
2569
2838
  case "duplicate":
2570
2839
  return ["POST", `/canvases/${request.canvasId}/frames`, request.input];
2840
+ case "asset":
2841
+ return ["POST", `/canvases/${request.canvasId}/assets`, request.image];
2842
+ case "image-frame":
2843
+ return ["POST", `/canvases/${request.canvasId}/image-frames`, request.input];
2571
2844
  case "view":
2572
2845
  return ["PUT", "/view", request.view];
2573
2846
  }
2574
2847
  })();
2575
- const res = await api2(path, { method, ...body !== void 0 ? { body: JSON.stringify(body) } : {} });
2848
+ const res = await api2(
2849
+ path,
2850
+ body instanceof Blob ? { method, headers: { "content-type": body.type }, body } : { method, ...body !== void 0 ? { body: JSON.stringify(body) } : {} }
2851
+ );
2576
2852
  const answer2 = await res.json().catch(() => null);
2577
2853
  if (!res.ok) throw new Error(answer2?.error ?? `HTTP ${res.status}`);
2578
2854
  return answer2;
@@ -2585,7 +2861,7 @@
2585
2861
  return api2("/attachments", {
2586
2862
  method: "POST",
2587
2863
  headers: { "content-type": op.mime, "x-ez-name": encodeURIComponent(op.name) },
2588
- body: op.bytes
2864
+ body: op.file
2589
2865
  });
2590
2866
  case "discard-upload":
2591
2867
  return api2(`/attachments/${encodeURIComponent(op.id)}`, { method: "DELETE" });
@@ -2616,6 +2892,7 @@
2616
2892
  if (sure) await request({ do: "delete", canvasId: id });
2617
2893
  }
2618
2894
  });
2895
+ if (ports.tabsLead) strip2.el.prepend(ports.tabsLead);
2619
2896
  ports.stageWrap.prepend(strip2.el);
2620
2897
  async function request(r) {
2621
2898
  try {
@@ -2633,8 +2910,9 @@
2633
2910
  const made = new BoardView(doc, {
2634
2911
  guests: ports.guests,
2635
2912
  send: async (edit) => await request(
2636
- edit.t === "ops" ? { do: "ops", canvasId: id, step: { opId: newOpId(), ops: edit.ops } } : { do: "duplicate", canvasId: id, input: edit.input }
2913
+ edit.t === "ops" ? { do: "ops", canvasId: id, step: { opId: newOpId(), ops: edit.ops } } : edit.t === "duplicate" ? { do: "duplicate", canvasId: id, input: edit.input } : { do: "image-frame", canvasId: id, input: edit.input }
2637
2914
  ),
2915
+ storeImage: async (image) => (await request({ do: "asset", canvasId: id, image }))?.asset ?? null,
2638
2916
  saveCamera: (camera) => void request({ do: "view", view: { camera: { canvasId: id, camera } } }),
2639
2917
  escape: ports.escape,
2640
2918
  chooseFrame: (frame) => ports.chooseFrame({
@@ -2663,6 +2941,7 @@
2663
2941
  shown = tab;
2664
2942
  for (const [id, b] of boards) if (id !== tab) b.hide();
2665
2943
  ports.stage.hidden = tab !== "app";
2944
+ document.documentElement.classList.toggle("ez-on-board", next !== null);
2666
2945
  ports.onTabShown(tab);
2667
2946
  if (next) {
2668
2947
  next.show();
@@ -2677,11 +2956,24 @@
2677
2956
  }
2678
2957
  function paintStrip2() {
2679
2958
  if (!snapshot2?.canvas) return;
2680
- strip2.paint(tabsModel(snapshot2.app, snapshot2.canvas));
2959
+ strip2.paint(tabsModel(snapshot2.appTab, snapshot2.canvas));
2681
2960
  }
2682
2961
  function activeBoard() {
2683
2962
  return shown && shown !== "app" ? boards.get(shown) ?? null : null;
2684
2963
  }
2964
+ function focusedBoard() {
2965
+ const board2 = activeBoard();
2966
+ const focus = document.activeElement;
2967
+ return board2 && (!focus || focus === document.body || board2.el.contains(focus)) ? board2 : null;
2968
+ }
2969
+ document.addEventListener("paste", (e) => {
2970
+ const board2 = focusedBoard();
2971
+ if (!board2 || e.defaultPrevented || document.querySelector("dialog[open]")) return;
2972
+ const images = [...e.clipboardData?.files ?? []].filter((f) => f.type in IMAGE_TYPES);
2973
+ if (!images.length) return;
2974
+ e.preventDefault();
2975
+ void board2.pasteImages(images);
2976
+ });
2685
2977
  function pushAnnotations(s) {
2686
2978
  const byGuest = /* @__PURE__ */ new Map();
2687
2979
  s.annotations.forEach((a, i) => {
@@ -2704,7 +2996,7 @@
2704
2996
  render(s) {
2705
2997
  snapshot2 = s;
2706
2998
  if (!s.canvas) return;
2707
- const model = tabsModel(s.app, s.canvas);
2999
+ const model = tabsModel(s.appTab, s.canvas);
2708
3000
  strip2.paint(model);
2709
3001
  for (const [id, b] of boards) {
2710
3002
  const summary = s.canvas.canvases.find((c) => c.id === id);
@@ -2749,15 +3041,15 @@
2749
3041
  if (b) ports.guests.post(guest.id, { type: "ez:viewport", ...b.viewportFor(), scoped: false });
2750
3042
  if (snapshot2) pushAnnotations(snapshot2);
2751
3043
  return false;
3044
+ case "ez:popup":
3045
+ if (!message.open && b) returnKeysToShell(b, guest.iframe);
3046
+ return false;
2752
3047
  default:
2753
3048
  return false;
2754
3049
  }
2755
3050
  },
2756
3051
  onKeyDown(e) {
2757
- const board2 = activeBoard();
2758
- const focus = document.activeElement;
2759
- if (!board2 || focus && focus !== document.body && !board2.el.contains(focus)) return false;
2760
- return board2.onKeyDown(e);
3052
+ return focusedBoard()?.onKeyDown(e) ?? false;
2761
3053
  },
2762
3054
  onKeyUp(e) {
2763
3055
  activeBoard()?.onKeyUp(e);
@@ -2791,27 +3083,6 @@
2791
3083
  };
2792
3084
  }
2793
3085
 
2794
- // src/library/policy.ts
2795
- var THUMBNAIL_LONG_SIDE = 400;
2796
- var TAG_MAX_LENGTH = 64;
2797
- function normalizeTag(raw) {
2798
- const name = raw.normalize("NFC").trim().replace(/\s+/g, " ");
2799
- if (name === "" || name.length > TAG_MAX_LENGTH || /[,,]/.test(name)) return null;
2800
- return lowercaseInitial(name);
2801
- }
2802
- function lowercaseInitial(name) {
2803
- const [first = "", second = ""] = name;
2804
- if (second !== second.toLowerCase()) return name;
2805
- return first.toLowerCase() + name.slice(first.length);
2806
- }
2807
- function tagKey(name) {
2808
- return name.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
2809
- }
2810
- function thumbnailSize(width, height) {
2811
- const scale2 = Math.min(1, THUMBNAIL_LONG_SIDE / Math.max(width, height));
2812
- return { width: Math.max(1, Math.round(width * scale2)), height: Math.max(1, Math.round(height * scale2)) };
2813
- }
2814
-
2815
3086
  // src/client/thumbnail.ts
2816
3087
  var THUMBNAIL_TYPE = "image/webp";
2817
3088
  var THUMBNAIL_QUALITY = 0.8;
@@ -2973,11 +3244,11 @@
2973
3244
  }
2974
3245
  function card(item, index) {
2975
3246
  const li = h2("li");
2976
- const button = h2("button", "ez-library-card");
2977
- button.type = "button";
2978
- button.dataset.testid = LIBRARY_TEST_IDS.card;
2979
- button.dataset.itemId = item.id;
2980
- button.setAttribute("aria-label", cardLabel(item));
3247
+ const button2 = h2("button", "ez-library-card");
3248
+ button2.type = "button";
3249
+ button2.dataset.testid = LIBRARY_TEST_IDS.card;
3250
+ button2.dataset.itemId = item.id;
3251
+ button2.setAttribute("aria-label", cardLabel(item));
2981
3252
  const media = h2("span", "ez-library-card-media");
2982
3253
  if (item.thumbnail) {
2983
3254
  const img = h2("img");
@@ -2995,10 +3266,10 @@
2995
3266
  badge.append(h2("span", void 0, item.link ? "\u9023\u7D50" : "\u5F71\u7247"));
2996
3267
  media.append(badge);
2997
3268
  }
2998
- button.append(media);
2999
- if (captions) button.append(h2("span", "ez-library-card-caption", sourceName(item.source)));
3000
- decorate(button, item, index);
3001
- li.append(button);
3269
+ button2.append(media);
3270
+ if (captions) button2.append(h2("span", "ez-library-card-caption", sourceName(item.source)));
3271
+ decorate(button2, item, index);
3272
+ li.append(button2);
3002
3273
  return li;
3003
3274
  }
3004
3275
  function paintGrid() {
@@ -3181,12 +3452,12 @@
3181
3452
  return el2;
3182
3453
  }
3183
3454
  function iconButton(className, label, glyph, size = 16) {
3184
- const button = h3("button", className);
3185
- button.type = "button";
3186
- button.setAttribute("aria-label", label);
3187
- button.title = label;
3188
- button.append(icon(glyph, size));
3189
- return button;
3455
+ const button2 = h3("button", className);
3456
+ button2.type = "button";
3457
+ button2.setAttribute("aria-label", label);
3458
+ button2.title = label;
3459
+ button2.append(icon(glyph, size));
3460
+ return button2;
3190
3461
  }
3191
3462
  var dateFormat = new Intl.DateTimeFormat("zh-TW", { year: "numeric", month: "long", day: "numeric" });
3192
3463
  function openDetail(options) {
@@ -3302,6 +3573,11 @@
3302
3573
  video.playsInline = true;
3303
3574
  video.preload = "metadata";
3304
3575
  if (item.thumbnail) video.poster = item.thumbnail.url;
3576
+ if (item.dimensions) {
3577
+ const { width, height } = item.dimensions;
3578
+ video.style.aspectRatio = `${width} / ${height}`;
3579
+ video.style.width = `min(100cqw, ${width}px, calc(100cqh * ${width / height}))`;
3580
+ }
3305
3581
  media = video;
3306
3582
  } else if (original || item.thumbnail) {
3307
3583
  const img = h3("img", "ez-library-detail-media");
@@ -3545,20 +3821,20 @@
3545
3821
  function copyField(value, label) {
3546
3822
  const row = h4("div", "ez-install-copy");
3547
3823
  const code = h4("code", void 0, value);
3548
- const button = h4("button", "ez-install-copy-button");
3549
- button.type = "button";
3550
- button.setAttribute("aria-label", label);
3551
- button.title = label;
3552
- button.append(icon(Copy01Icon, 14));
3824
+ const button2 = h4("button", "ez-install-copy-button");
3825
+ button2.type = "button";
3826
+ button2.setAttribute("aria-label", label);
3827
+ button2.title = label;
3828
+ button2.append(icon(Copy01Icon, 14));
3553
3829
  let timer;
3554
- button.onclick = () => {
3830
+ button2.onclick = () => {
3555
3831
  void navigator.clipboard.writeText(value).then(() => {
3556
3832
  clearTimeout(timer);
3557
- button.replaceChildren(icon(Tick02Icon, 14));
3558
- timer = setTimeout(() => button.replaceChildren(icon(Copy01Icon, 14)), COPIED_MS);
3833
+ button2.replaceChildren(icon(Tick02Icon, 14));
3834
+ timer = setTimeout(() => button2.replaceChildren(icon(Copy01Icon, 14)), COPIED_MS);
3559
3835
  });
3560
3836
  };
3561
- row.append(code, button);
3837
+ row.append(code, button2);
3562
3838
  return row;
3563
3839
  }
3564
3840
  var PROBLEMS = {
@@ -3728,8 +4004,8 @@
3728
4004
  const title = h5("h1", "ez-library-title", "\u7D20\u6750\u5EAB");
3729
4005
  const browser = libraryBrowser(api2, {
3730
4006
  scroll,
3731
- decorate: (button, _item, index) => {
3732
- button.onclick = () => showDetail(index);
4007
+ decorate: (button2, _item, index) => {
4008
+ button2.onclick = () => showDetail(index);
3733
4009
  },
3734
4010
  blank: () => {
3735
4011
  const install = h5("button", "ez-library-empty-link", "\u5B89\u88DD ezdesign \u63D2\u4EF6");
@@ -3852,8 +4128,8 @@
3852
4128
  let painted = false;
3853
4129
  let open = false;
3854
4130
  const el2 = h6("div", "ez-account");
3855
- const button = h6("button", "ez-activity-btn ez-account-btn");
3856
- button.type = "button";
4131
+ const button2 = h6("button", "ez-activity-btn ez-account-btn");
4132
+ button2.type = "button";
3857
4133
  const menu = h6("div", "ez-menu ez-account-menu");
3858
4134
  menu.setAttribute("role", "menu");
3859
4135
  const who = h6("div", "ez-account-who");
@@ -3862,13 +4138,13 @@
3862
4138
  signOut.setAttribute("role", "menuitem");
3863
4139
  signOut.append(icon(Logout01Icon, 14), h6("span", "ez-menu-name", "\u767B\u51FA"));
3864
4140
  menu.append(who, signOut);
3865
- el2.append(button, menu);
4141
+ el2.append(button2, menu);
3866
4142
  const signIn = () => window.open(`${apiBase}/account/login`, "_blank");
3867
4143
  function setOpen(next) {
3868
4144
  open = next && !!email;
3869
4145
  el2.toggleAttribute("data-open", open);
3870
- if (email) button.setAttribute("aria-expanded", String(open));
3871
- else button.removeAttribute("aria-expanded");
4146
+ if (email) button2.setAttribute("aria-expanded", String(open));
4147
+ else button2.removeAttribute("aria-expanded");
3872
4148
  }
3873
4149
  function render2(account2) {
3874
4150
  const nextEmail = account2?.email ?? null;
@@ -3877,22 +4153,22 @@
3877
4153
  offered = !!account2;
3878
4154
  email = nextEmail;
3879
4155
  el2.hidden = !offered;
3880
- button.textContent = "";
4156
+ button2.textContent = "";
3881
4157
  if (email) {
3882
- button.setAttribute("aria-label", `\u5E33\u865F\uFF1A${email}`);
3883
- button.title = email;
3884
- button.setAttribute("aria-haspopup", "menu");
3885
- button.append(h6("span", "ez-avatar", email.charAt(0).toUpperCase()));
4158
+ button2.setAttribute("aria-label", `\u5E33\u865F\uFF1A${email}`);
4159
+ button2.title = email;
4160
+ button2.setAttribute("aria-haspopup", "menu");
4161
+ button2.append(h6("span", "ez-avatar", email.charAt(0).toUpperCase()));
3886
4162
  who.replaceChildren(h6("span", "ez-account-label", "\u5DF2\u767B\u5165"), h6("span", "ez-account-email", email));
3887
4163
  } else {
3888
- button.setAttribute("aria-label", "\u767B\u5165 ezdesign");
3889
- button.title = "\u767B\u5165 ezdesign";
3890
- button.removeAttribute("aria-haspopup");
3891
- button.append(icon(UserCircleIcon, 18));
4164
+ button2.setAttribute("aria-label", "\u767B\u5165 ezdesign");
4165
+ button2.title = "\u767B\u5165 ezdesign";
4166
+ button2.removeAttribute("aria-haspopup");
4167
+ button2.append(icon(UserCircleIcon, 18));
3892
4168
  }
3893
4169
  setOpen(false);
3894
4170
  }
3895
- button.onclick = () => {
4171
+ button2.onclick = () => {
3896
4172
  if (email) setOpen(!open);
3897
4173
  else signIn();
3898
4174
  };
@@ -3906,7 +4182,7 @@
3906
4182
  el2.addEventListener("keydown", (e) => {
3907
4183
  if (e.key === "Escape" && open) {
3908
4184
  setOpen(false);
3909
- button.focus();
4185
+ button2.focus();
3910
4186
  }
3911
4187
  });
3912
4188
  render2(void 0);
@@ -4045,16 +4321,16 @@
4045
4321
  const browser = libraryBrowser(api2, {
4046
4322
  scroll,
4047
4323
  captions: false,
4048
- decorate: (button, item) => {
4324
+ decorate: (button2, item) => {
4049
4325
  const check = h8("span", "ez-library-card-check");
4050
4326
  check.append(icon(Tick02Icon, 12));
4051
- button.querySelector(".ez-library-card-media").append(check);
4052
- button.setAttribute("aria-pressed", String(chosen.includes(item.id)));
4053
- button.onclick = () => {
4327
+ button2.querySelector(".ez-library-card-media").append(check);
4328
+ button2.setAttribute("aria-pressed", String(chosen.includes(item.id)));
4329
+ button2.onclick = () => {
4054
4330
  const at2 = chosen.indexOf(item.id);
4055
4331
  if (at2 < 0) chosen.push(item.id);
4056
4332
  else chosen.splice(at2, 1);
4057
- button.setAttribute("aria-pressed", String(at2 < 0));
4333
+ button2.setAttribute("aria-pressed", String(at2 < 0));
4058
4334
  paintCount();
4059
4335
  };
4060
4336
  },
@@ -4113,6 +4389,485 @@
4113
4389
  });
4114
4390
  }
4115
4391
 
4392
+ // src/client/video-digest/runner.ts
4393
+ var IDLE_MS = 3e4;
4394
+ var STALL_MS = 2e4;
4395
+ var RETRY_MS = 1e4;
4396
+ function videoDigests(api2, workerUrl, onChange) {
4397
+ const fractions = /* @__PURE__ */ new Map();
4398
+ const queue = [];
4399
+ const resting = /* @__PURE__ */ new Set();
4400
+ let current = null;
4401
+ let worker = null;
4402
+ let stallTimer = 0;
4403
+ let idleTimer = 0;
4404
+ const spawn = () => {
4405
+ const w = new Worker(workerUrl);
4406
+ w.onmessage = (e) => handle(e.data);
4407
+ w.onerror = (e) => {
4408
+ e.preventDefault();
4409
+ crashed("the digest worker crashed");
4410
+ };
4411
+ return w;
4412
+ };
4413
+ const next = () => {
4414
+ if (current) return;
4415
+ const id = queue.shift();
4416
+ if (!id) {
4417
+ idleTimer = window.setTimeout(() => {
4418
+ worker?.terminate();
4419
+ worker = null;
4420
+ }, IDLE_MS);
4421
+ return;
4422
+ }
4423
+ clearTimeout(idleTimer);
4424
+ current = id;
4425
+ worker ?? (worker = spawn());
4426
+ worker.postMessage({ id, api: api2 });
4427
+ armStall();
4428
+ };
4429
+ const armStall = () => {
4430
+ clearTimeout(stallTimer);
4431
+ stallTimer = window.setTimeout(() => crashed("the digest stopped answering"), STALL_MS);
4432
+ };
4433
+ const finish = (id) => {
4434
+ clearTimeout(stallTimer);
4435
+ fractions.delete(id);
4436
+ current = null;
4437
+ next();
4438
+ onChange();
4439
+ };
4440
+ const handle = (event) => {
4441
+ if (event.id !== current) return;
4442
+ if (event.type === "progress") {
4443
+ fractions.set(event.id, event.fraction);
4444
+ armStall();
4445
+ onChange();
4446
+ return;
4447
+ }
4448
+ if (event.type === "error") {
4449
+ resting.add(event.id);
4450
+ window.setTimeout(() => resting.delete(event.id), RETRY_MS);
4451
+ }
4452
+ finish(event.id);
4453
+ };
4454
+ const crashed = (detail) => {
4455
+ worker?.terminate();
4456
+ worker = null;
4457
+ const id = current;
4458
+ if (!id) return;
4459
+ const body = { state: "failed", reason: "crashed", detail };
4460
+ void fetch(`${api2}/attachments/${id}/video`, {
4461
+ method: "PUT",
4462
+ headers: { "content-type": "application/json" },
4463
+ body: JSON.stringify(body)
4464
+ }).catch(() => {
4465
+ });
4466
+ finish(id);
4467
+ };
4468
+ return {
4469
+ observe(pending) {
4470
+ const listed = new Set(pending);
4471
+ for (const id of pending) {
4472
+ if (fractions.has(id) || resting.has(id)) continue;
4473
+ fractions.set(id, 0);
4474
+ queue.push(id);
4475
+ }
4476
+ for (const id of [...fractions.keys()]) {
4477
+ if (listed.has(id) || id === current) continue;
4478
+ fractions.delete(id);
4479
+ const at2 = queue.indexOf(id);
4480
+ if (at2 >= 0) queue.splice(at2, 1);
4481
+ }
4482
+ next();
4483
+ },
4484
+ progress: (id) => fractions.get(id) ?? 0
4485
+ };
4486
+ }
4487
+
4488
+ // node_modules/.pnpm/@hugeicons+core-free-icons@4.3.5/node_modules/@hugeicons/core-free-icons/dist/esm/StopIcon.js
4489
+ var StopIcon = [
4490
+ ["path", { d: "M4 12C4 8.72077 4 7.08116 4.81382 5.91891C5.1149 5.48891 5.48891 5.1149 5.91891 4.81382C7.08116 4 8.72077 4 12 4C15.2792 4 16.9188 4 18.0811 4.81382C18.5111 5.1149 18.8851 5.48891 19.1862 5.91891C20 7.08116 20 8.72077 20 12C20 15.2792 20 16.9188 19.1862 18.0811C18.8851 18.5111 18.5111 18.8851 18.0811 19.1862C16.9188 20 15.2792 20 12 20C8.72077 20 7.08116 20 5.91891 19.1862C5.48891 18.8851 5.1149 18.5111 4.81382 18.0811C4 16.9188 4 15.2792 4 12Z", stroke: "currentColor", strokeWidth: "1.5", key: "0" }]
4491
+ ];
4492
+
4493
+ // src/desktop-wire.ts
4494
+ var DESKTOP_BRIDGE = "ezdesignDesktop";
4495
+
4496
+ // src/client/host.ts
4497
+ var bridge = window[DESKTOP_BRIDGE];
4498
+ var host = bridge ? {
4499
+ kind: "desktop",
4500
+ showProject: (opened) => bridge.showProject(opened),
4501
+ pickFolder: () => bridge.pickFolder(),
4502
+ pathForFile: (file) => bridge.pathForFile(file),
4503
+ toggleSidebar: () => bridge.toggleSidebar(),
4504
+ onLayout: (listener) => bridge.onLayout(listener),
4505
+ onUpdate: (listener) => bridge.onUpdate?.(listener),
4506
+ takeUpdate: () => bridge.takeUpdate?.()
4507
+ } : { kind: "browser", showProject: (opened) => location.assign(opened.shellUrl) };
4508
+ if (bridge) {
4509
+ const root2 = document.documentElement;
4510
+ root2.dataset.host = "desktop";
4511
+ root2.style.setProperty("--sidebar-width", `${bridge.sidebarWidth}px`);
4512
+ root2.style.setProperty("--pane-inset", `${bridge.paneInset}px`);
4513
+ root2.style.setProperty("--pane-radius", `${bridge.paneRadius}px`);
4514
+ bridge.onLayout(({ sidebar: sidebar2 }) => {
4515
+ root2.dataset.sidebar = sidebar2 ? "open" : "closed";
4516
+ root2.style.setProperty("--traffic-light-room", sidebar2 ? "0px" : `${bridge.trafficLightRoom}px`);
4517
+ });
4518
+ }
4519
+
4520
+ // src/client/dev-server-panel.ts
4521
+ var DEV_SERVER_TEST_IDS = {
4522
+ card: "ez-devserver-card",
4523
+ log: "ez-devserver-log"
4524
+ };
4525
+ var LOG_POLL_MS = 700;
4526
+ var CARD_LOG_LINES = 200;
4527
+ var SOURCE = {
4528
+ detected: "\u5F9E package.json \u5075\u6E2C",
4529
+ agent: "agent \u5EFA\u8B70\u3001\u4F60\u78BA\u8A8D\u904E\u7684\u6307\u4EE4",
4530
+ user: "\u4F60\u8A2D\u5B9A\u7684\u6307\u4EE4"
4531
+ };
4532
+ function h9(tag, className, text) {
4533
+ const node = document.createElement(tag);
4534
+ if (className) node.className = className;
4535
+ if (text !== void 0) node.textContent = text;
4536
+ return node;
4537
+ }
4538
+ function button(className, label, glyph) {
4539
+ const b = h9("button", className);
4540
+ b.type = "button";
4541
+ if (glyph) b.append(icon(glyph, 14));
4542
+ b.append(h9("span", void 0, label));
4543
+ return b;
4544
+ }
4545
+ function installLine(parts) {
4546
+ const span = h9("span");
4547
+ for (const part of parts) span.append("command" in part ? h9("code", "ez-devserver-inline", part.command) : part.text);
4548
+ return span;
4549
+ }
4550
+ var agentName = (command) => agentProfileFor(command)?.name ?? "agent";
4551
+ function crashLine(state) {
4552
+ if (state.cause === "no-url") return "60 \u79D2\u5167\u6C92\u6709\u5370\u51FA\u7DB2\u5740\uFF0C\u4E5F\u6C92\u6709\u958B\u555F\u4EFB\u4F55 port\uFF0C\u5DF2\u505C\u6B62\u5B83\u3002";
4553
+ if (state.cause === "spawn") return "\u6307\u4EE4\u6C92\u8FA6\u6CD5\u57F7\u884C\u3002";
4554
+ if (state.signal) return `Dev server \u88AB ${state.signal} \u7D50\u675F\u4E86\u3002`;
4555
+ return state.exitCode !== void 0 ? `Dev server \u7D50\u675F\u4E86\uFF08exit code ${state.exitCode}\uFF09\u3002` : "Dev server \u7D50\u675F\u4E86\u3002";
4556
+ }
4557
+ function installDevServerPanel(api2) {
4558
+ const card = h9("section", "ez-devserver-card");
4559
+ card.dataset.testid = DEV_SERVER_TEST_IDS.card;
4560
+ card.setAttribute("aria-live", "polite");
4561
+ card.hidden = true;
4562
+ let state = null;
4563
+ let drawnFor = "";
4564
+ let editing2 = false;
4565
+ let error = null;
4566
+ let busy = false;
4567
+ const log = new LogFeed(api2);
4568
+ async function post(path, body = {}, method = "POST") {
4569
+ busy = true;
4570
+ error = null;
4571
+ repaint();
4572
+ try {
4573
+ const res = await fetch(`${api2}/dev-server/${path}`, {
4574
+ method,
4575
+ headers: { "content-type": "application/json" },
4576
+ body: JSON.stringify({ ...body, host: host.kind })
4577
+ });
4578
+ if (!res.ok) error = (await res.json().catch(() => null))?.error ?? `${res.status}`;
4579
+ return res.ok;
4580
+ } catch {
4581
+ error = "\u9023\u4E0D\u4E0A ezdesign daemon";
4582
+ return false;
4583
+ } finally {
4584
+ busy = false;
4585
+ repaint();
4586
+ }
4587
+ }
4588
+ function repaint() {
4589
+ drawnFor = "";
4590
+ if (state) paint(state, lastPage);
4591
+ }
4592
+ function commandBlock(command) {
4593
+ const block = h9("div", "ez-devserver-command");
4594
+ const line = h9("code", "ez-devserver-line", command.line);
4595
+ block.append(line);
4596
+ const where = command.cwd ? `${SOURCE[command.source]} \xB7 \u5728 ${command.cwd}/ \u57F7\u884C` : SOURCE[command.source];
4597
+ block.append(h9("span", "ez-devserver-source", where));
4598
+ return block;
4599
+ }
4600
+ function commandForm(initial, cancellable, proposalId) {
4601
+ const form = h9("form", "ez-devserver-form");
4602
+ const lineLabel = h9("label", "ez-devserver-label", "\u555F\u52D5\u6307\u4EE4");
4603
+ const lineInput = h9("input", "ez-devserver-input");
4604
+ lineInput.name = "line";
4605
+ lineInput.placeholder = "pnpm --filter web dev";
4606
+ lineInput.value = initial?.line ?? "";
4607
+ lineInput.spellcheck = false;
4608
+ lineInput.autocomplete = "off";
4609
+ lineLabel.append(lineInput);
4610
+ const cwdLabel = h9("label", "ez-devserver-label", "\u5728\u54EA\u500B\u8CC7\u6599\u593E\u57F7\u884C\uFF08\u76F8\u5C0D\u65BC\u5C08\u6848\uFF0C\u9078\u586B\uFF09");
4611
+ const cwdInput = h9("input", "ez-devserver-input");
4612
+ cwdInput.name = "cwd";
4613
+ cwdInput.placeholder = ".";
4614
+ cwdInput.value = initial?.cwd ?? "";
4615
+ cwdInput.spellcheck = false;
4616
+ cwdInput.autocomplete = "off";
4617
+ cwdLabel.append(cwdInput);
4618
+ const actions = h9("div", "ez-devserver-actions");
4619
+ const go = button("ez-devserver-go", "\u5132\u5B58\u4E26\u555F\u52D5", PlayIcon);
4620
+ go.type = "submit";
4621
+ go.disabled = busy;
4622
+ actions.append(go);
4623
+ if (cancellable) {
4624
+ const cancel = button("ez-devserver-secondary", "\u53D6\u6D88");
4625
+ cancel.onclick = () => {
4626
+ editing2 = false;
4627
+ error = null;
4628
+ repaint();
4629
+ };
4630
+ actions.append(cancel);
4631
+ }
4632
+ if (!proposalId && (initial?.source === "user" || initial?.source === "agent")) {
4633
+ const reset = button("ez-devserver-secondary", "\u6539\u56DE\u81EA\u52D5\u5075\u6E2C");
4634
+ reset.onclick = () => {
4635
+ editing2 = false;
4636
+ void post("command", {}, "DELETE");
4637
+ };
4638
+ actions.append(reset);
4639
+ }
4640
+ form.append(lineLabel, cwdLabel, actions);
4641
+ form.onsubmit = (e) => {
4642
+ e.preventDefault();
4643
+ void post("command", { line: lineInput.value, cwd: cwdInput.value, ...proposalId ? { proposalId } : {} }).then((ok) => {
4644
+ if (ok) editing2 = false;
4645
+ repaint();
4646
+ });
4647
+ };
4648
+ queueMicrotask(() => lineInput.focus({ preventScroll: true }));
4649
+ return form;
4650
+ }
4651
+ function proposalBlock(offer) {
4652
+ const block = commandBlock({ line: offer.line, cwd: offer.cwd, source: "agent" });
4653
+ block.querySelector(".ez-devserver-source").textContent = `${agentName(offer.agent)} \u7684\u5EFA\u8B70${offer.cwd ? ` \xB7 \u5728 ${offer.cwd}/ \u57F7\u884C` : ""}`;
4654
+ if (offer.reason) block.append(h9("span", "ez-devserver-reason", offer.reason));
4655
+ return block;
4656
+ }
4657
+ function proposalNote(offer) {
4658
+ const name = agentName(offer.agent);
4659
+ switch (offer.state) {
4660
+ case "asking":
4661
+ return h9("p", "ez-devserver-asking", "agent \u5075\u6E2C\u4E2D\u2026");
4662
+ case "none-found":
4663
+ return h9("p", "ez-devserver-hint", `${name} \u770B\u904E\u4E86\uFF0C\u6C92\u627E\u5230\u53EF\u4EE5\u555F\u52D5\u7684\u7DB2\u9801${offer.reason ? `\uFF1A${offer.reason}` : "\u3002"}`);
4664
+ case "failed":
4665
+ return h9("p", "ez-devserver-error", `\u6C92\u8FA6\u6CD5\u53D6\u5F97 ${name} \u7684\u5EFA\u8B70\uFF1A${offer.error}`);
4666
+ case "proposed":
4667
+ return h9("p", "ez-devserver-hint", `${name} \u5EFA\u8B70\u7684\u6307\u4EE4\uFF0C\u53EF\u4EE5\u5148\u4FEE\u6539\u518D\u57F7\u884C\u3002`);
4668
+ }
4669
+ }
4670
+ function logBlock(lines) {
4671
+ const pre = h9("pre", "ez-devserver-log");
4672
+ pre.dataset.testid = DEV_SERVER_TEST_IDS.log;
4673
+ pre.tabIndex = 0;
4674
+ log.into(pre, lines);
4675
+ return pre;
4676
+ }
4677
+ function paintCard(s) {
4678
+ const title = h9("h2", "ez-devserver-title");
4679
+ const body = [];
4680
+ const actions = h9("div", "ez-devserver-actions");
4681
+ const start = (label) => {
4682
+ const b = button("ez-devserver-go", label, PlayIcon);
4683
+ b.disabled = busy;
4684
+ b.onclick = () => void post(s.phase === "crashed" ? "restart" : "start");
4685
+ actions.append(b);
4686
+ };
4687
+ const edit = (label = "\u6539\u7528\u5176\u4ED6\u6307\u4EE4") => {
4688
+ const b = button("ez-devserver-secondary", label);
4689
+ b.onclick = () => {
4690
+ editing2 = true;
4691
+ repaint();
4692
+ };
4693
+ actions.append(b);
4694
+ };
4695
+ const command = "command" in s ? s.command : void 0;
4696
+ if (editing2 && s.phase !== "starting" && s.phase !== "needs-command") {
4697
+ title.textContent = "dev server \u7684\u555F\u52D5\u6307\u4EE4";
4698
+ body.push(commandForm(command, true));
4699
+ } else {
4700
+ switch (s.phase) {
4701
+ case "idle":
4702
+ case "stopped":
4703
+ title.textContent = s.phase === "idle" ? "\u555F\u52D5 dev server" : "Dev server \u5DF2\u505C\u6B62";
4704
+ if (command) {
4705
+ body.push(commandBlock(command));
4706
+ start("\u555F\u52D5");
4707
+ edit();
4708
+ } else {
4709
+ title.textContent = "\u6B63\u5728\u5075\u6E2C\u555F\u52D5\u6307\u4EE4";
4710
+ body.push(h9("p", "ez-devserver-asking", "agent \u5075\u6E2C\u4E2D\u2026"));
4711
+ }
4712
+ break;
4713
+ case "starting": {
4714
+ title.textContent = "\u6B63\u5728\u555F\u52D5 dev server";
4715
+ if (command) body.push(commandBlock(command));
4716
+ body.push(logBlock(CARD_LOG_LINES));
4717
+ const stop = button("ez-devserver-secondary", "\u505C\u6B62", StopIcon);
4718
+ stop.disabled = busy;
4719
+ stop.onclick = () => void post("stop");
4720
+ actions.append(stop);
4721
+ break;
4722
+ }
4723
+ case "needs-command": {
4724
+ const offer = s.proposal;
4725
+ if (offer?.state === "asking" && !editing2) {
4726
+ title.textContent = "\u6B63\u5728\u5075\u6E2C\u555F\u52D5\u6307\u4EE4";
4727
+ body.push(proposalNote(offer));
4728
+ break;
4729
+ }
4730
+ title.textContent = "\u9700\u8981\u4E00\u500B\u555F\u52D5\u6307\u4EE4";
4731
+ if (offer?.state === "proposed" && !editing2) {
4732
+ title.textContent = "\u555F\u52D5 dev server";
4733
+ body.push(proposalBlock(offer));
4734
+ const run = button("ez-devserver-go", "\u57F7\u884C", PlayIcon);
4735
+ run.disabled = busy;
4736
+ run.onclick = () => void post("command", { line: offer.line, cwd: offer.cwd, proposalId: offer.id });
4737
+ const change = button("ez-devserver-secondary", "\u4FEE\u6539");
4738
+ change.onclick = () => {
4739
+ editing2 = true;
4740
+ repaint();
4741
+ };
4742
+ actions.append(run, change);
4743
+ break;
4744
+ }
4745
+ if (offer) body.push(proposalNote(offer));
4746
+ if (s.serverScripts.length) body.push(h9("p", "ez-devserver-hint", `\u770B\u8D77\u4F86\u50CF dev server \u7684 script\uFF1A${s.serverScripts.join("\u3001")}`));
4747
+ body.push(
4748
+ offer?.state === "proposed" ? commandForm({ line: offer.line, cwd: offer.cwd, source: "agent" }, true, offer.id) : commandForm(void 0, false)
4749
+ );
4750
+ if (offer?.state === "failed" || offer?.state === "none-found") {
4751
+ const again = button("ez-devserver-secondary", "\u518D\u554F\u4E00\u6B21");
4752
+ again.onclick = () => void post("propose");
4753
+ actions.append(again);
4754
+ }
4755
+ break;
4756
+ }
4757
+ case "blocked": {
4758
+ title.textContent = "\u7F3A\u5C11\u555F\u52D5\u9700\u8981\u7684\u5DE5\u5177";
4759
+ body.push(commandBlock(s.command));
4760
+ const list = h9("ul", "ez-devserver-missing");
4761
+ for (const m of s.missing) {
4762
+ const item = h9("li");
4763
+ item.append(h9("code", "ez-devserver-tool", m.tool), installLine(m.install));
4764
+ list.append(item);
4765
+ }
4766
+ body.push(list, h9("p", "ez-devserver-hint", "\u88DD\u597D\u4E4B\u5F8C\u6309\u300C\u518D\u8A66\u4E00\u6B21\u300D\u3002"));
4767
+ start("\u518D\u8A66\u4E00\u6B21");
4768
+ edit();
4769
+ break;
4770
+ }
4771
+ case "crashed":
4772
+ title.textContent = "Dev server \u505C\u6B62\u904B\u4F5C\u4E86";
4773
+ body.push(h9("p", "ez-devserver-text", crashLine(s)), commandBlock(s.command));
4774
+ if (s.tail.length) {
4775
+ const pre = h9("pre", "ez-devserver-log", s.tail.join("\n"));
4776
+ pre.dataset.testid = DEV_SERVER_TEST_IDS.log;
4777
+ pre.tabIndex = 0;
4778
+ body.push(pre);
4779
+ queueMicrotask(() => pre.scrollTop = pre.scrollHeight);
4780
+ }
4781
+ start("\u91CD\u65B0\u555F\u52D5");
4782
+ edit();
4783
+ break;
4784
+ case "running":
4785
+ case "external":
4786
+ title.textContent = "\u6B63\u5728\u9023\u4E0A dev server";
4787
+ break;
4788
+ }
4789
+ }
4790
+ card.replaceChildren(title, ...body);
4791
+ if (actions.childElementCount) card.append(actions);
4792
+ if (error) card.append(h9("p", "ez-devserver-error", error));
4793
+ }
4794
+ let lastPage = { shown: false, onAppTab: false };
4795
+ let detecting = false;
4796
+ function paint(s, page) {
4797
+ const showCard = page.onAppTab && !page.shown;
4798
+ if (showCard && s.phase === "idle" && !s.command && !detecting) {
4799
+ detecting = true;
4800
+ void post("start");
4801
+ }
4802
+ card.hidden = !showCard;
4803
+ log.feedCard(showCard && s.phase === "starting");
4804
+ const key = JSON.stringify([s, showCard, editing2, busy, error]);
4805
+ if (key === drawnFor) return;
4806
+ drawnFor = key;
4807
+ if (showCard) paintCard(s);
4808
+ }
4809
+ return {
4810
+ card,
4811
+ update(next, page) {
4812
+ if (next.phase === "starting" && state?.phase !== "starting") log.reset();
4813
+ state = next;
4814
+ lastPage = page;
4815
+ paint(next, page);
4816
+ }
4817
+ };
4818
+ }
4819
+ var LogFeed = class {
4820
+ constructor(api2) {
4821
+ this.api = api2;
4822
+ __publicField(this, "timer", null);
4823
+ __publicField(this, "views", /* @__PURE__ */ new Set());
4824
+ __publicField(this, "last", { lines: [], dropped: 0 });
4825
+ __publicField(this, "wanted", false);
4826
+ }
4827
+ into(pre, lines) {
4828
+ const view = { pre, lines };
4829
+ this.views.add(view);
4830
+ this.draw(view);
4831
+ this.schedule();
4832
+ void this.fetch();
4833
+ }
4834
+ reset() {
4835
+ this.last = { lines: [], dropped: 0 };
4836
+ }
4837
+ feedCard(on) {
4838
+ this.wanted = on;
4839
+ for (const view of [...this.views]) if (!view.pre.isConnected) this.views.delete(view);
4840
+ this.schedule();
4841
+ }
4842
+ schedule() {
4843
+ const needed = this.wanted;
4844
+ if (needed && this.timer === null) {
4845
+ this.timer = window.setInterval(() => void this.fetch(), LOG_POLL_MS);
4846
+ } else if (!needed && this.timer !== null) {
4847
+ window.clearInterval(this.timer);
4848
+ this.timer = null;
4849
+ }
4850
+ }
4851
+ async fetch() {
4852
+ try {
4853
+ const res = await fetch(`${this.api}/dev-server/log`);
4854
+ if (!res.ok) return;
4855
+ this.last = await res.json();
4856
+ } catch {
4857
+ return;
4858
+ }
4859
+ for (const view of this.views) this.draw(view);
4860
+ }
4861
+ draw(view) {
4862
+ const { pre } = view;
4863
+ const pinned = pre.scrollHeight - pre.scrollTop - pre.clientHeight < 24;
4864
+ const text = this.last.lines.slice(-view.lines).join("\n");
4865
+ if (pre.textContent === text) return;
4866
+ pre.textContent = text;
4867
+ if (pinned) pre.scrollTop = pre.scrollHeight;
4868
+ }
4869
+ };
4870
+
4116
4871
  // src/canvas/wire.ts
4117
4872
  var SSE_CANVAS = "canvas";
4118
4873
  var SSE_CANVAS_REMOVED = "canvas-removed";
@@ -4139,7 +4894,7 @@
4139
4894
  // src/client/page-storage.ts
4140
4895
  var SHELL_OWNED_PREFIXES = ["ezdesign:", "ez-update-dismissed", "ez-confirm-skip:"];
4141
4896
  var MAX_RECORDS_PER_STORE = 1e3;
4142
- var MAX_BYTES2 = 15e5;
4897
+ var MAX_BYTES = 15e5;
4143
4898
  var COLLECT_TIMEOUT_MS = 1500;
4144
4899
  function storageEntries(store) {
4145
4900
  const out = [];
@@ -4233,7 +4988,7 @@
4233
4988
  const timedOut = new Promise((ok) => setTimeout(() => ok([]), COLLECT_TIMEOUT_MS));
4234
4989
  const indexedDB = await Promise.race([readIndexedDb(factory).catch(() => []), timedOut]);
4235
4990
  const storage = { localStorage: local, sessionStorage: session, indexedDB };
4236
- return JSON.stringify(storage).length > MAX_BYTES2 ? { ...storage, indexedDB: [] } : storage;
4991
+ return JSON.stringify(storage).length > MAX_BYTES ? { ...storage, indexedDB: [] } : storage;
4237
4992
  }
4238
4993
 
4239
4994
  // src/client/usage-note.ts
@@ -4381,9 +5136,9 @@
4381
5136
  const heights = rows.map((row) => m.head + Math.max(...row.map((d) => d.height)));
4382
5137
  const tops = [];
4383
5138
  let y = 0;
4384
- for (const h10 of heights) {
5139
+ for (const h11 of heights) {
4385
5140
  tops.push(y);
4386
- y += h10 + m.rowGap;
5141
+ y += h11 + m.rowGap;
4387
5142
  }
4388
5143
  return {
4389
5144
  tops,
@@ -5093,6 +5848,7 @@
5093
5848
  var API = `${PREFIX}/api`;
5094
5849
  var BUILD = document.currentScript?.dataset.version ?? "";
5095
5850
  var shellAttachments = directAttachments(API);
5851
+ var digests = videoDigests(API, `${PREFIX}/video-digest.js?v=${BUILD}`, () => paintVideos());
5096
5852
  var MOD_LABEL = modLabel(navigator.userAgent);
5097
5853
  var PAGE_PATH = new URLSearchParams(location.search).get("path") || "/";
5098
5854
  var ANNOTATE_MODES = [
@@ -5117,7 +5873,7 @@
5117
5873
  var TICK_ICON = 14;
5118
5874
  var snapshot = null;
5119
5875
  var canvasUi = null;
5120
- var stagedApp = void 0;
5876
+ var stagedApp = document.currentScript?.dataset.app || null;
5121
5877
  var annotateMode = "off";
5122
5878
  var daemonGone = false;
5123
5879
  var goneTimer;
@@ -5158,26 +5914,26 @@
5158
5914
  }
5159
5915
  loadView();
5160
5916
  var root = document.getElementById("ez-shell");
5161
- function h9(tag, className, text) {
5917
+ function h10(tag, className, text) {
5162
5918
  const node = document.createElement(tag);
5163
5919
  if (className) node.className = className;
5164
5920
  if (text !== void 0) node.textContent = text;
5165
5921
  return node;
5166
5922
  }
5167
- var brand = h9("div", "ez-logo");
5923
+ var brand = h10("div", "ez-logo");
5168
5924
  brand.setAttribute("role", "img");
5169
5925
  brand.setAttribute("aria-label", "ezdesign");
5170
5926
  brand.innerHTML = brandLockupSvg();
5171
- var version = h9("span", "ez-version");
5172
- var updatePill = h9("button", "ez-update-flag");
5927
+ var version = h10("span", "ez-version");
5928
+ var updatePill = h10("button", "ez-update-flag");
5173
5929
  updatePill.setAttribute("aria-haspopup", "menu");
5174
5930
  updatePill.setAttribute("aria-expanded", "false");
5175
- var updateMenu = h9("div", "ez-menu");
5931
+ var updateMenu = h10("div", "ez-menu");
5176
5932
  updateMenu.setAttribute("role", "menu");
5177
- var updateNow = h9("button", "ez-menu-item");
5933
+ var updateNow = h10("button", "ez-menu-item");
5178
5934
  updateNow.setAttribute("role", "menuitem");
5179
5935
  updateMenu.append(updateNow);
5180
- var updateHint = h9("div", "ez-update-hint");
5936
+ var updateHint = h10("div", "ez-update-hint");
5181
5937
  updateHint.hidden = true;
5182
5938
  updateHint.append(updatePill, updateMenu);
5183
5939
  var updateMenuOpen = false;
@@ -5200,15 +5956,15 @@
5200
5956
  paintUpdateMenu();
5201
5957
  }
5202
5958
  });
5203
- var target = h9("span", "ez-target");
5204
- var agentStatus = h9("div", "ez-badge");
5205
- var agentPill = h9("button", "ez-agent-pill");
5959
+ var target = h10("span", "ez-target");
5960
+ var agentStatus = h10("div", "ez-badge");
5961
+ var agentPill = h10("button", "ez-agent-pill");
5206
5962
  agentPill.setAttribute("aria-haspopup", "menu");
5207
5963
  agentPill.setAttribute("aria-expanded", "false");
5208
- var agentMenu = h9("div", "ez-menu ez-menu-up");
5964
+ var agentMenu = h10("div", "ez-menu ez-menu-up");
5209
5965
  agentMenu.setAttribute("role", "menu");
5210
5966
  agentMenu.setAttribute("aria-label", "Agent");
5211
- var agentWrap = h9("div", "ez-agent");
5967
+ var agentWrap = h10("div", "ez-agent");
5212
5968
  agentWrap.hidden = true;
5213
5969
  agentWrap.append(agentPill, agentMenu);
5214
5970
  var plain = (key) => (e) => !e.metaKey && !e.ctrlKey && e.key.toLowerCase() === key.toLowerCase();
@@ -5307,7 +6063,7 @@
5307
6063
  }
5308
6064
  return false;
5309
6065
  }
5310
- var keysBtn = h9("button", "ez-keys");
6066
+ var keysBtn = h10("button", "ez-keys");
5311
6067
  keysBtn.append(icon(KeyboardIcon, 14));
5312
6068
  keysBtn.title = "\u5FEB\u6377\u9375";
5313
6069
  keysBtn.setAttribute("aria-label", "\u5FEB\u6377\u9375");
@@ -5326,13 +6082,13 @@
5326
6082
  paintKeys();
5327
6083
  }
5328
6084
  keysBtn.onclick = () => pinKeys(!keysPinned);
5329
- var keysPanel = h9("div", "ez-keys-panel");
6085
+ var keysPanel = h10("div", "ez-keys-panel");
5330
6086
  for (const s of SHORTCUTS) {
5331
- const row = h9("div", "ez-keys-row");
5332
- row.append(h9("kbd", "ez-kbd", s.keys), h9("span", "ez-keys-label", s.label));
6087
+ const row = h10("div", "ez-keys-row");
6088
+ row.append(h10("kbd", "ez-kbd", s.keys), h10("span", "ez-keys-label", s.label));
5333
6089
  keysPanel.appendChild(row);
5334
6090
  }
5335
- var keysWrap = h9("div", "ez-keys-wrap");
6091
+ var keysWrap = h10("div", "ez-keys-wrap");
5336
6092
  keysWrap.append(keysBtn, keysPanel);
5337
6093
  keysWrap.addEventListener("pointerenter", () => {
5338
6094
  window.clearTimeout(keysCloseTimer);
@@ -5349,23 +6105,23 @@
5349
6105
  document.addEventListener("click", (e) => {
5350
6106
  if (keysPinned && e.target instanceof Node && !keysWrap.contains(e.target)) pinKeys(false);
5351
6107
  });
5352
- var headRow = h9("div", "ez-head-row");
5353
- headRow.append(brand, version, updateHint, h9("div", "ez-spacer"), keysWrap, agentStatus);
5354
- var deviceName = h9("button", "ez-seg-label");
6108
+ var headRow = h10("div", "ez-head-row");
6109
+ headRow.append(brand, version, updateHint, h10("div", "ez-spacer"), keysWrap, agentStatus);
6110
+ var deviceName = h10("button", "ez-seg-label");
5355
6111
  deviceName.onclick = () => setDevice(deviceId);
5356
- var deviceCaret = h9("button", "ez-seg-caret");
6112
+ var deviceCaret = h10("button", "ez-seg-caret");
5357
6113
  deviceCaret.append(icon(ChevronDownIcon, 12));
5358
6114
  deviceCaret.setAttribute("aria-haspopup", "menu");
5359
6115
  deviceCaret.setAttribute("aria-expanded", "false");
5360
- var deviceMenu = h9("div", "ez-menu");
6116
+ var deviceMenu = h10("div", "ez-menu");
5361
6117
  deviceMenu.setAttribute("role", "menu");
5362
6118
  deviceMenu.setAttribute("aria-label", "\u9810\u89BD\u5C3A\u5BF8");
5363
6119
  var deviceItems = DEVICES.map((d) => {
5364
- const item = h9("button", "ez-menu-item");
6120
+ const item = h10("button", "ez-menu-item");
5365
6121
  item.setAttribute("role", "menuitemradio");
5366
6122
  item.dataset.device = d.id;
5367
6123
  item.title = deviceLabel(d);
5368
- item.append(h9("span", "ez-menu-name", d.name), menuCheck());
6124
+ item.append(h10("span", "ez-menu-name", d.name), menuCheck());
5369
6125
  item.onclick = () => {
5370
6126
  closeDeviceMenu();
5371
6127
  setDevice(d.id);
@@ -5373,7 +6129,7 @@
5373
6129
  deviceMenu.appendChild(item);
5374
6130
  return item;
5375
6131
  });
5376
- var deviceGroup = h9("div", "ez-seg-item ez-seg-device");
6132
+ var deviceGroup = h10("div", "ez-seg-item ez-seg-device");
5377
6133
  deviceGroup.append(deviceName, deviceCaret, deviceMenu);
5378
6134
  var deviceMenuOpen = false;
5379
6135
  function paintDeviceMenu() {
@@ -5417,11 +6173,12 @@
5417
6173
  if (to instanceof Node && deviceGroup.contains(to)) return;
5418
6174
  closeDeviceMenu();
5419
6175
  });
5420
- var multiBtn = h9("button", "ez-seg-item ez-seg-multi", "\u5168\u90E8\u5C3A\u5BF8");
6176
+ var multiBtn = h10("button", "ez-seg-item ez-seg-multi", "\u5168\u90E8\u5C3A\u5BF8");
5421
6177
  multiBtn.title = "\u6240\u6709\u5C3A\u5BF8\u6392\u6210\u4E00\u5F35\u756B\u5E03\uFF0C\u62D6\u66F3\u80CC\u666F\u79FB\u52D5\uFF084\uFF09";
5422
6178
  multiBtn.onclick = () => setMulti(!multi);
5423
- var deviceRow = h9("div", "ez-seg");
6179
+ var deviceRow = h10("div", "ez-seg");
5424
6180
  deviceRow.append(deviceGroup, multiBtn);
6181
+ deviceRow.hidden = stagedApp === null;
5425
6182
  function paintControls() {
5426
6183
  const device = deviceById(deviceId);
5427
6184
  deviceName.textContent = device.name;
@@ -5437,10 +6194,10 @@
5437
6194
  multiBtn.setAttribute("aria-pressed", String(multi));
5438
6195
  paintShown();
5439
6196
  }
5440
- var annotateGroup = h9("div", "ez-annotate-group");
6197
+ var annotateGroup = h10("div", "ez-annotate-group");
5441
6198
  var annotateBtns = ANNOTATE_MODES.map((m) => {
5442
- const btn = h9("button", `ez-tool-btn ez-mode-${m.id}`);
5443
- btn.append(icon(m.svg, 15), h9("span", void 0, m.label));
6199
+ const btn = h10("button", `ez-tool-btn ez-mode-${m.id}`);
6200
+ btn.append(icon(m.svg, 15), h10("span", void 0, m.label));
5444
6201
  btn.title = `${m.hint}\uFF08${m.key}\uFF09\u30FBEsc \u96E2\u958B`;
5445
6202
  btn.onclick = () => sendMode(annotateMode === m.id ? "off" : m.id);
5446
6203
  annotateGroup.appendChild(btn);
@@ -5454,12 +6211,13 @@
5454
6211
  }
5455
6212
  canvasUi?.setAnnotating(annotateMode);
5456
6213
  }
5457
- var sideHead = h9("div", "ez-side-head");
6214
+ var devServerPanel = installDevServerPanel(API);
6215
+ var sideHead = h10("div", "ez-side-head");
5458
6216
  sideHead.append(headRow, target, deviceRow, annotateGroup);
5459
- var statusPill = h9("div", "ez-status");
6217
+ var statusPill = h10("div", "ez-status");
5460
6218
  statusPill.setAttribute("role", "status");
5461
6219
  statusPill.hidden = true;
5462
- var updateCard = h9("section", "ez-update");
6220
+ var updateCard = h10("section", "ez-update");
5463
6221
  updateCard.hidden = true;
5464
6222
  var UPDATE_DISMISSED_KEY = "ez-update-dismissed";
5465
6223
  function updateOfferKey(u) {
@@ -5525,10 +6283,10 @@
5525
6283
  updateCard.hidden = !u || !!dismissed;
5526
6284
  updateCard.textContent = "";
5527
6285
  if (!u || dismissed) return;
5528
- updateCard.append(h9("div", "ez-update-title", updateTitle(u)));
6286
+ updateCard.append(h10("div", "ez-update-title", updateTitle(u)));
5529
6287
  const busy = u.phase !== "available" && u.phase !== "failed";
5530
- const lines = h9("ul", "ez-update-lines");
5531
- const line = (text, cls = "") => lines.append(h9("li", cls || void 0, text));
6288
+ const lines = h10("ul", "ez-update-lines");
6289
+ const line = (text, cls = "") => lines.append(h10("li", cls || void 0, text));
5532
6290
  const working = s.acp?.state === "working";
5533
6291
  if (busy) {
5534
6292
  if (u.phase === "installing") line("\u4E0B\u8F09\u4E26\u5B89\u88DD\u65B0\u7248\u672C\u2026");
@@ -5539,10 +6297,10 @@
5539
6297
  }
5540
6298
  if (lines.childElementCount) updateCard.append(lines);
5541
6299
  if (!busy) {
5542
- const actions = h9("div", "ez-update-actions");
5543
- const go = h9("button", "ez-update-go");
6300
+ const actions = h10("div", "ez-update-actions");
6301
+ const go = h10("button", "ez-update-go");
5544
6302
  go.append(
5545
- h9(
6303
+ h10(
5546
6304
  "span",
5547
6305
  "ez-update-label",
5548
6306
  u.phase === "failed" ? "\u91CD\u8A66" : working ? "\u4ECD\u8981\u66F4\u65B0" : "\u7ACB\u5373\u66F4\u65B0"
@@ -5552,7 +6310,7 @@
5552
6310
  go.onclick = () => void startUpdate();
5553
6311
  actions.append(go);
5554
6312
  updateCard.append(actions);
5555
- const dismiss = h9("button", "ez-update-x");
6313
+ const dismiss = h10("button", "ez-update-x");
5556
6314
  dismiss.append(icon(Cancel01Icon, 13));
5557
6315
  dismiss.title = "\u7A0D\u5F8C\u518D\u8AAA\uFF08\u7248\u865F\u65C1\u7684\u63D0\u793A\u6703\u7559\u8457\uFF09";
5558
6316
  dismiss.setAttribute("aria-label", dismiss.title);
@@ -5560,16 +6318,16 @@
5560
6318
  updateCard.append(dismiss);
5561
6319
  }
5562
6320
  }
5563
- var stageWrap = h9("div", "ez-stage-wrap");
5564
- var stage = h9("div", "ez-stage");
5565
- var grid = h9("div", "ez-grid");
5566
- stage.append(grid);
5567
- var strip = h9("div", "ez-strip");
6321
+ var stageWrap = h10("div", "ez-stage-wrap");
6322
+ var stage = h10("div", "ez-stage");
6323
+ var grid = h10("div", "ez-grid");
6324
+ stage.append(devServerPanel.card, grid);
6325
+ var strip = h10("div", "ez-strip");
5568
6326
  strip.hidden = true;
5569
6327
  function stripNotice(text) {
5570
6328
  strip.hidden = false;
5571
- const pill = h9("div", "ez-strip-pill");
5572
- pill.append(icon(MagicWand01Icon, 14), h9("div", "ez-strip-pending", text));
6329
+ const pill = h10("div", "ez-strip-pill");
6330
+ pill.append(icon(MagicWand01Icon, 14), h10("div", "ez-strip-pending", text));
5573
6331
  strip.replaceChildren(pill);
5574
6332
  setTimeout(() => {
5575
6333
  if (!liveExplores().length) strip.hidden = true;
@@ -5701,18 +6459,18 @@
5701
6459
  return;
5702
6460
  }
5703
6461
  closeStripAsk();
5704
- const card = h9("div", "ez-strip-ask");
6462
+ const card = h10("div", "ez-strip-ask");
5705
6463
  card.setAttribute("role", "dialog");
5706
6464
  card.setAttribute("aria-label", offer.go);
5707
- const skip = h9("input");
6465
+ const skip = h10("input");
5708
6466
  skip.type = "checkbox";
5709
- const skipLabel = h9("label", "ez-strip-ask-skip");
5710
- skipLabel.append(skip, h9("span", void 0, "\u4E0D\u8981\u518D\u63D0\u793A"));
5711
- const go = h9("button", "ez-strip-ask-go", offer.go);
5712
- const no = h9("button", "ez-strip-ask-no", "\u53D6\u6D88");
5713
- const row = h9("div", "ez-strip-ask-actions");
6467
+ const skipLabel = h10("label", "ez-strip-ask-skip");
6468
+ skipLabel.append(skip, h10("span", void 0, "\u4E0D\u8981\u518D\u63D0\u793A"));
6469
+ const go = h10("button", "ez-strip-ask-go", offer.go);
6470
+ const no = h10("button", "ez-strip-ask-no", "\u53D6\u6D88");
6471
+ const row = h10("div", "ez-strip-ask-actions");
5714
6472
  row.append(go, no);
5715
- card.append(h9("div", void 0, offer.body), row, skipLabel);
6473
+ card.append(h10("div", void 0, offer.body), row, skipLabel);
5716
6474
  const outside = (e) => {
5717
6475
  if (e.target instanceof Node && !card.contains(e.target)) done3(false);
5718
6476
  };
@@ -5760,17 +6518,17 @@
5760
6518
  });
5761
6519
  }
5762
6520
  function roundPill(round2) {
5763
- const pill = h9("div", "ez-strip-pill");
6521
+ const pill = h10("div", "ez-strip-pill");
5764
6522
  pill.dataset.round = round2.id;
5765
6523
  pill.title = round2.direction ? `${round2.label}\uFF1A${round2.direction}` : round2.label;
5766
6524
  pill.setAttribute("role", "group");
5767
6525
  pill.setAttribute("aria-label", `\u63A2\u7D22 ${round2.label}`);
5768
6526
  pill.append(icon(MagicWand01Icon, 14));
5769
- const chips = h9("div", "ez-strip-chips");
6527
+ const chips = h10("div", "ez-strip-chips");
5770
6528
  chips.setAttribute("role", "radiogroup");
5771
6529
  chips.setAttribute("aria-label", "\u8B8A\u9AD4");
5772
6530
  const chip = (choice, name, title) => {
5773
- const btn = h9("button", "ez-strip-chip", name);
6531
+ const btn = h10("button", "ez-strip-chip", name);
5774
6532
  btn.setAttribute("role", "radio");
5775
6533
  btn.dataset.choice = choice ?? "";
5776
6534
  btn.title = title;
@@ -5788,12 +6546,12 @@
5788
6546
  radioKeys(chips, radios, (btn) => select(round2, btn.dataset.choice || null));
5789
6547
  chips.append(...radios);
5790
6548
  if (round2.status === "generating") {
5791
- chips.append(h9("div", "ez-strip-pending", round2.variants.length ? "\u9084\u5728\u60F3\u2026" : "\u6B63\u5728\u7522\u751F\u2026"));
6549
+ chips.append(h10("div", "ez-strip-pending", round2.variants.length ? "\u9084\u5728\u60F3\u2026" : "\u6B63\u5728\u7522\u751F\u2026"));
5792
6550
  }
5793
6551
  pill.append(chips);
5794
- const actions = h9("div", "ez-strip-actions");
6552
+ const actions = h10("div", "ez-strip-actions");
5795
6553
  const chosen = round2.variants.find((v) => v.id === round2.selected);
5796
- const adopt = h9("button", "ez-strip-action", "\u9078\u64C7");
6554
+ const adopt = h10("button", "ez-strip-action", "\u9078\u64C7");
5797
6555
  adopt.disabled = !chosen;
5798
6556
  adopt.title = chosen ? `\u5E36\u300C${chosen.name}\u300D\u56DE\u4E3B\u5C0D\u8A71\uFF0C\u4EA4\u7D66 agent \u5BE6\u4F5C\u9032\u5C08\u6848` : "\u5148\u6311\u4E00\u500B\u8B8A\u9AD4\uFF0C\u624D\u6709\u6771\u897F\u53EF\u4EE5\u5E36\u56DE\u53BB";
5799
6557
  adopt.onclick = () => askOnStrip(
@@ -5806,7 +6564,7 @@
5806
6564
  () => void adoptVariant(round2, chosen)
5807
6565
  );
5808
6566
  actions.append(adopt);
5809
- const quit = h9("button", "ez-strip-action", "\u9000\u51FA\u63A2\u7D22");
6567
+ const quit = h10("button", "ez-strip-action", "\u9000\u51FA\u63A2\u7D22");
5810
6568
  quit.title = "\u7D50\u675F\u9019\u4E00\u8F2A\u63A2\u7D22\uFF0C\u56DE\u5230\u4E3B\u5C0D\u8A71";
5811
6569
  quit.onclick = () => askOnStrip(
5812
6570
  actions,
@@ -5821,36 +6579,36 @@
5821
6579
  pill.append(actions);
5822
6580
  return pill;
5823
6581
  }
5824
- var shownBtn = h9("button", "ez-shown-btn");
6582
+ var shownBtn = h10("button", "ez-shown-btn");
5825
6583
  shownBtn.append(icon(Grid02Icon, 14));
5826
6584
  shownBtn.title = "\u9078\u64C7\u756B\u5E03\u4E0A\u8981\u986F\u793A\u7684\u5C3A\u5BF8";
5827
6585
  shownBtn.setAttribute("aria-label", "\u9078\u64C7\u756B\u5E03\u4E0A\u8981\u986F\u793A\u7684\u5C3A\u5BF8");
5828
6586
  shownBtn.setAttribute("aria-haspopup", "menu");
5829
6587
  shownBtn.setAttribute("aria-expanded", "false");
5830
- var shownMenu = h9("div", "ez-menu ez-menu-right");
6588
+ var shownMenu = h10("div", "ez-menu ez-menu-right");
5831
6589
  shownMenu.setAttribute("role", "menu");
5832
6590
  shownMenu.setAttribute("aria-label", "\u756B\u5E03\u4E0A\u7684\u5C3A\u5BF8");
5833
6591
  var shownItems = GRID_DEVICES.map((d) => {
5834
- const item = h9("button", "ez-menu-item");
6592
+ const item = h10("button", "ez-menu-item");
5835
6593
  item.setAttribute("role", "menuitemcheckbox");
5836
6594
  item.dataset.device = d.id;
5837
6595
  item.title = deviceLabel(d);
5838
- const tick = h9("span", "ez-menu-tick");
6596
+ const tick = h10("span", "ez-menu-tick");
5839
6597
  tick.append(icon(CheckIcon, 14));
5840
- item.append(tick, h9("span", "ez-menu-name", d.name));
6598
+ item.append(tick, h10("span", "ez-menu-name", d.name));
5841
6599
  item.onclick = () => toggleShown(d.id);
5842
6600
  shownMenu.appendChild(item);
5843
6601
  return item;
5844
6602
  });
5845
- var shownWrap = h9("div", "ez-shown");
6603
+ var shownWrap = h10("div", "ez-shown");
5846
6604
  shownWrap.append(shownBtn, shownMenu);
5847
- var pickPill = h9("div", "ez-pick-pill");
6605
+ var pickPill = h10("div", "ez-pick-pill");
5848
6606
  pickPill.hidden = true;
5849
- var pickPillText = h9("span", "ez-pick-pill-text");
5850
- var pickPillCancel = h9("button", "ez-pick-pill-cancel", "\u53D6\u6D88");
6607
+ var pickPillText = h10("span", "ez-pick-pill-text");
6608
+ var pickPillCancel = h10("button", "ez-pick-pill-cancel", "\u53D6\u6D88");
5851
6609
  pickPillCancel.onclick = () => abortPick("escape");
5852
6610
  pickPill.append(icon(AlignSelectionIcon, 14), pickPillText, pickPillCancel);
5853
- var stageDock = h9("div", "ez-stage-dock");
6611
+ var stageDock = h10("div", "ez-stage-dock");
5854
6612
  stageDock.append(strip, pickPill);
5855
6613
  stageWrap.append(stage, shownWrap, stageDock, installAnnouncement(API, ANNOUNCEMENTS));
5856
6614
  var shownMenuOpen = false;
@@ -5965,7 +6723,7 @@
5965
6723
  return;
5966
6724
  }
5967
6725
  if (stagedApp === null) {
5968
- target.textContent = "\u6C92\u6709 app";
6726
+ target.textContent = "";
5969
6727
  document.title = "ezdesign";
5970
6728
  return;
5971
6729
  }
@@ -5974,12 +6732,12 @@
5974
6732
  document.title = `ezdesign \xB7 ${nav.url}`;
5975
6733
  }
5976
6734
  function mountFrame(id, device, into) {
5977
- const card = h9("div", "ez-card");
5978
- const head = h9("div", "ez-card-head");
6735
+ const card = h10("div", "ez-card");
6736
+ const head = h10("div", "ez-card-head");
5979
6737
  head.title = "\u62D6\u66F3\u8ABF\u6574\u6392\u5217";
5980
- const box = h9("div", "ez-screen");
5981
- const wrap = h9("div", "ez-frame-wrap");
5982
- const iframe = h9("iframe", "ez-frame");
6738
+ const box = h10("div", "ez-screen");
6739
+ const wrap = h10("div", "ez-frame-wrap");
6740
+ const iframe = h10("iframe", "ez-frame");
5983
6741
  iframe.src = nav.url;
5984
6742
  wrap.appendChild(iframe);
5985
6743
  box.appendChild(wrap);
@@ -6095,10 +6853,8 @@
6095
6853
  }
6096
6854
  function stageApp(app) {
6097
6855
  if (app === stagedApp) return;
6098
- const presumed = stagedApp === void 0;
6099
6856
  stagedApp = app;
6100
6857
  deviceRow.hidden = app === null || !!canvasUi && canvasUi.activeTab !== "app";
6101
- if (presumed && app !== null) return;
6102
6858
  rebuildStage();
6103
6859
  }
6104
6860
  function rebuildStage() {
@@ -6127,33 +6883,24 @@
6127
6883
  paintAnnotateMode();
6128
6884
  paintStageClasses();
6129
6885
  rebuildStage();
6130
- var sidebar = h9("aside", "ez-sidebar");
6131
- var queueSection = h9("section", "ez-section ez-queue-section");
6132
- var queueList = h9("ul", "ez-queue");
6133
- var queueScroll = h9("div", "ez-fade ez-queue-scroll");
6886
+ var sidebar = h10("aside", "ez-sidebar");
6887
+ var queueSection = h10("section", "ez-section ez-queue-section");
6888
+ var queueList = h10("ul", "ez-queue");
6889
+ var queueScroll = h10("div", "ez-fade ez-queue-scroll");
6134
6890
  queueScroll.appendChild(queueList);
6135
- var sendBtn = h9("button", "ez-send");
6136
- var sendLabel = h9("span", void 0, "\u9001\u51FA\u7D66 agent");
6891
+ var sendBtn = h10("button", "ez-send");
6892
+ var sendLabel = h10("span", void 0, "\u9001\u51FA\u7D66 agent");
6137
6893
  sendBtn.title = "\u9001\u51FA\u7D66 agent\uFF08\u2318/Ctrl + Enter\uFF09";
6138
6894
  sendBtn.append(icon(Navigation03Icon, 15), sendLabel);
6139
6895
  sendBtn.onclick = () => void sendBatch();
6140
6896
  var noteAttach = attachify({
6141
6897
  attachments: shellAttachments,
6142
- mk: h9,
6898
+ mk: h10,
6143
6899
  className: "ez-note",
6144
6900
  placeholder: "\u6574\u9AD4\u60F3\u6CD5\u6216\u88DC\u5145\u8AAA\u660E\uFF0C\u8F38\u5165 / \u7528\u6307\u4EE4\uFF08\u9078\u586B\uFF09",
6145
6901
  onChange: () => paintSendState(),
6146
6902
  commands: [
6147
- {
6148
- id: "element",
6149
- label: "Element",
6150
- hint: "\u9078\u53D6\u9801\u9762\u5143\u7D20\u6216\u7BC4\u570D",
6151
- keywords: ["element", "pick", "ref", "reference", "\u5143\u7D20", "\u6307\u5B9A", "\u53C3\u8003", "\u6846\u9078"],
6152
- icon: AlignSelectionIcon,
6153
- // The pointer is in the other document, so this can only ask. The answer
6154
- // comes back as a message and lands here via the pick reducer.
6155
- run: () => startPick("note")
6156
- },
6903
+ elementCommand(() => noteAttach),
6157
6904
  {
6158
6905
  id: "new",
6159
6906
  label: "New chat",
@@ -6177,6 +6924,17 @@
6177
6924
  // while this review has only ever had the one conversation.
6178
6925
  enabled: () => !!snapshot?.acp && (snapshot.chats?.length ?? 0) > 1,
6179
6926
  run: () => void openResume()
6927
+ },
6928
+ {
6929
+ id: "skills",
6930
+ label: "Skills",
6931
+ hint: "\u8F38\u5165 $ \u4F7F\u7528 skill",
6932
+ keywords: ["skill", "skills", "$", "\u6280\u80FD"],
6933
+ icon: DollarSquareIcon,
6934
+ // Skills have their own menu, behind `$`: this one row is how a user who
6935
+ // only knows `/` finds it. Typed rather than inserted, so it opens there.
6936
+ enabled: () => !!snapshot?.acp,
6937
+ run: () => document.execCommand("insertText", false, "$")
6180
6938
  }
6181
6939
  ],
6182
6940
  skills: () => fetchSkills(),
@@ -6184,50 +6942,61 @@
6184
6942
  });
6185
6943
  var note = noteAttach.editable;
6186
6944
  note.title = "\u5BEB\u88DC\u5145\u8AAA\u660E\uFF08N\uFF09";
6187
- var configPill = h9("button", "ez-config-pill");
6945
+ var pickComposer = noteAttach;
6946
+ function elementCommand(into) {
6947
+ return {
6948
+ id: "element",
6949
+ label: "Element",
6950
+ hint: "\u9078\u53D6\u9801\u9762\u5143\u7D20\u6216\u7BC4\u570D",
6951
+ keywords: ["element", "pick", "ref", "reference", "\u5143\u7D20", "\u6307\u5B9A", "\u53C3\u8003", "\u6846\u9078"],
6952
+ icon: AlignSelectionIcon,
6953
+ run: () => startPick(into())
6954
+ };
6955
+ }
6956
+ var configPill = h10("button", "ez-config-pill");
6188
6957
  configPill.setAttribute("aria-haspopup", "menu");
6189
6958
  configPill.setAttribute("aria-expanded", "false");
6190
- var configMenu = h9("div", "ez-menu ez-menu-up");
6959
+ var configMenu = h10("div", "ez-menu ez-menu-up");
6191
6960
  configMenu.setAttribute("role", "menu");
6192
- var configWrap = h9("div", "ez-config");
6961
+ var configWrap = h10("div", "ez-config");
6193
6962
  configWrap.hidden = true;
6194
6963
  configWrap.append(configPill, configMenu);
6195
- var limitPill = h9("button", "ez-limit");
6964
+ var limitPill = h10("button", "ez-limit");
6196
6965
  limitPill.type = "button";
6197
6966
  limitPill.setAttribute("aria-describedby", "ez-usage-card");
6198
- var limitCard = h9("div", "ez-usage-card");
6967
+ var limitCard = h10("div", "ez-usage-card");
6199
6968
  limitCard.id = "ez-usage-card";
6200
6969
  limitCard.setAttribute("role", "tooltip");
6201
- var limitWrap = h9("div", "ez-usage");
6970
+ var limitWrap = h10("div", "ez-usage");
6202
6971
  limitWrap.hidden = true;
6203
6972
  limitWrap.append(limitPill, limitCard);
6204
- var confirmTitle = h9("div", "ez-notice-title");
6205
- var confirmBody = h9("div", "ez-notice-body");
6206
- var confirmSkip = h9("input");
6973
+ var confirmTitle = h10("div", "ez-notice-title");
6974
+ var confirmBody = h10("div", "ez-notice-body");
6975
+ var confirmSkip = h10("input");
6207
6976
  confirmSkip.type = "checkbox";
6208
6977
  confirmSkip.id = "ez-confirm-skip";
6209
- var confirmSkipLabel = h9("label", "ez-notice-skip");
6978
+ var confirmSkipLabel = h10("label", "ez-notice-skip");
6210
6979
  confirmSkipLabel.htmlFor = confirmSkip.id;
6211
- confirmSkipLabel.append(confirmSkip, h9("span", "", "\u4EE5\u5F8C\u4E0D\u518D\u63D0\u9192"));
6212
- var confirmGo = h9("button", "ez-notice-go");
6213
- var confirmNo = h9("button", "ez-notice-no", "\u53D6\u6D88");
6214
- var confirmActions = h9("div", "ez-notice-actions");
6980
+ confirmSkipLabel.append(confirmSkip, h10("span", "", "\u4EE5\u5F8C\u4E0D\u518D\u63D0\u9192"));
6981
+ var confirmGo = h10("button", "ez-notice-go");
6982
+ var confirmNo = h10("button", "ez-notice-no", "\u53D6\u6D88");
6983
+ var confirmActions = h10("div", "ez-notice-actions");
6215
6984
  confirmActions.append(confirmGo, confirmNo);
6216
- var confirmCard = h9("section", "ez-notice ez-confirm");
6985
+ var confirmCard = h10("section", "ez-notice ez-confirm");
6217
6986
  confirmCard.hidden = true;
6218
6987
  confirmCard.dataset.testid = "ez-confirm";
6219
6988
  confirmCard.setAttribute("role", "dialog");
6220
6989
  confirmTitle.id = "ez-confirm-title";
6221
6990
  confirmCard.setAttribute("aria-labelledby", confirmTitle.id);
6222
6991
  confirmCard.append(confirmTitle, confirmBody, confirmActions, confirmSkipLabel);
6223
- var resumeTitle = h9("div", "ez-notice-title", "\u56DE\u5230\u4E4B\u524D\u7684\u5C0D\u8A71");
6224
- var resumeList = h9("div", "ez-notice-list");
6992
+ var resumeTitle = h10("div", "ez-notice-title", "\u56DE\u5230\u4E4B\u524D\u7684\u5C0D\u8A71");
6993
+ var resumeList = h10("div", "ez-notice-list");
6225
6994
  resumeList.setAttribute("role", "menu");
6226
- var resumeClose = h9("button", "ez-notice-x");
6995
+ var resumeClose = h10("button", "ez-notice-x");
6227
6996
  resumeClose.title = "\u95DC\u9589";
6228
6997
  resumeClose.setAttribute("aria-label", "\u95DC\u9589");
6229
6998
  resumeClose.append(icon(Cancel01Icon, 13));
6230
- var resumeCard = h9("section", "ez-notice ez-resume");
6999
+ var resumeCard = h10("section", "ez-notice ez-resume");
6231
7000
  resumeCard.hidden = true;
6232
7001
  resumeCard.append(resumeClose, resumeTitle, resumeList);
6233
7002
  var resumeRows = [];
@@ -6248,14 +7017,14 @@
6248
7017
  const { chats } = await res.json();
6249
7018
  resumeList.textContent = "";
6250
7019
  resumeRows = chats.map((chat) => {
6251
- const row = h9("button", "ez-notice-row");
7020
+ const row = h10("button", "ez-notice-row");
6252
7021
  row.setAttribute("role", "menuitemradio");
6253
7022
  row.setAttribute("aria-checked", String(chat.current));
6254
7023
  if (chat.current) row.dataset.current = "";
6255
7024
  row.append(
6256
7025
  icon(ArrowRight02Icon, 12),
6257
- h9("span", "ez-notice-row-name", chat.title || chatTime(chat.startedAt)),
6258
- h9("span", "ez-notice-row-when", chat.title ? chatTime(chat.startedAt) : "")
7026
+ h10("span", "ez-notice-row-name", chat.title || chatTime(chat.startedAt)),
7027
+ h10("span", "ez-notice-row-when", chat.title ? chatTime(chat.startedAt) : "")
6259
7028
  );
6260
7029
  row.addEventListener("pointerenter", () => pointAtResumeRow(row));
6261
7030
  row.addEventListener("focus", () => pointAtResumeRow(row));
@@ -6278,7 +7047,7 @@
6278
7047
  }
6279
7048
  };
6280
7049
  resumeClose.onclick = () => closeResume();
6281
- var notices = h9("div", "ez-notices");
7050
+ var notices = h10("div", "ez-notices");
6282
7051
  notices.append(updateCard, resumeCard, confirmCard);
6283
7052
  var answerConfirm = null;
6284
7053
  function closeConfirm(ok, offer) {
@@ -6314,20 +7083,20 @@
6314
7083
  answerConfirm = resolve;
6315
7084
  });
6316
7085
  }
6317
- var harnessBtn = h9("button", "ez-harness");
7086
+ var harnessBtn = h10("button", "ez-harness");
6318
7087
  harnessBtn.type = "button";
6319
7088
  harnessBtn.setAttribute("role", "switch");
6320
7089
  harnessBtn.setAttribute("aria-checked", "false");
6321
7090
  var HARNESS_HINT = "\u5BE6\u9A57\u6027\u529F\u80FD\uFF1A\u958B\u555F\u5F8C\uFF0Cagent \u6703\u5148\u4E86\u89E3\u4F60\u7684\u7522\u54C1\u3001\u6574\u7406\u51FA\u8A2D\u8A08\u898F\u7BC4\uFF0C\u4E4B\u5F8C\u90FD\u7167\u898F\u7BC4\u8A2D\u8A08";
6322
7091
  var RUNNING_HINT = "agent \u6B63\u5728\u57F7\u884C\uFF0C\u9019\u4E00\u8F2A\u7D50\u675F\u5F8C\u624D\u80FD\u8ABF\u6574";
6323
7092
  harnessBtn.title = HARNESS_HINT;
6324
- var harnessMark = h9("span", "ez-harness-mark");
7093
+ var harnessMark = h10("span", "ez-harness-mark");
6325
7094
  harnessMark.append(icon(SparklesIcon, 12));
6326
7095
  harnessBtn.append(
6327
7096
  harnessMark,
6328
- h9("span", "ez-harness-label", "\u589E\u5F37\u8A2D\u8A08"),
6329
- h9("span", "ez-switch"),
6330
- h9("span", "ez-harness-tag", "\u5BE6\u9A57")
7097
+ h10("span", "ez-harness-label", "\u589E\u5F37\u8A2D\u8A08"),
7098
+ h10("span", "ez-switch"),
7099
+ h10("span", "ez-harness-tag", "\u5BE6\u9A57")
6331
7100
  );
6332
7101
  harnessBtn.onclick = () => void toggleHarness();
6333
7102
  var EFFORTS = [
@@ -6335,21 +7104,21 @@
6335
7104
  { level: "medium", label: "\u4E2D", hint: "\u4E2D\uFF1A\u5B8C\u6574\u8A2D\u8A08\u6D41\u7A0B\uFF0C\u4F46\u4E0D\u53E6\u5916\u8ACB\u4EBA\u6AA2\u67E5" },
6336
7105
  { level: "high", label: "\u9AD8", hint: "\u9AD8\uFF1A\u5B8C\u6574\u8A2D\u8A08\u6D41\u7A0B\uFF0C\u6700\u5F8C\u518D\u7368\u7ACB\u6AA2\u67E5\u4E00\u6B21" }
6337
7106
  ];
6338
- var effortGroup = h9("label", "ez-effort");
7107
+ var effortGroup = h10("label", "ez-effort");
6339
7108
  effortGroup.hidden = true;
6340
- var effortTrack = h9("span", "ez-effort-track");
7109
+ var effortTrack = h10("span", "ez-effort-track");
6341
7110
  effortTrack.setAttribute("aria-hidden", "true");
6342
- effortTrack.append(h9("span", "ez-effort-fill"), h9("span", "ez-effort-thumb"));
6343
- var effortSlider = h9("input", "ez-effort-input");
7111
+ effortTrack.append(h10("span", "ez-effort-fill"), h10("span", "ez-effort-thumb"));
7112
+ var effortSlider = h10("input", "ez-effort-input");
6344
7113
  effortSlider.type = "range";
6345
7114
  effortSlider.min = "0";
6346
7115
  effortSlider.max = String(EFFORTS.length - 1);
6347
7116
  effortSlider.step = "1";
6348
7117
  effortSlider.setAttribute("aria-label", "\u8A2D\u8A08\u529B\u5EA6");
6349
- var effortValue = h9("span", "ez-effort-value");
6350
- var effortControl = h9("span", "ez-effort-slider");
7118
+ var effortValue = h10("span", "ez-effort-value");
7119
+ var effortControl = h10("span", "ez-effort-slider");
6351
7120
  effortControl.append(effortTrack, effortSlider);
6352
- effortGroup.append(h9("span", "ez-effort-label", "\u529B\u5EA6"), effortControl, effortValue);
7121
+ effortGroup.append(h10("span", "ez-effort-label", "\u529B\u5EA6"), effortControl, effortValue);
6353
7122
  function showEffort(at2) {
6354
7123
  const effort = EFFORTS[at2];
6355
7124
  effortSlider.value = String(at2);
@@ -6418,16 +7187,16 @@
6418
7187
  var effortHeld = false;
6419
7188
  effortSlider.onpointerdown = () => effortHeld = true;
6420
7189
  window.addEventListener("pointerup", () => effortHeld = false);
6421
- var controlRow = h9("div", "ez-control-row");
7190
+ var controlRow = h10("div", "ez-control-row");
6422
7191
  controlRow.hidden = true;
6423
7192
  controlRow.append(agentWrap, configWrap, limitWrap);
6424
- var harnessRow = h9("div", "ez-harness-row");
7193
+ var harnessRow = h10("div", "ez-harness-row");
6425
7194
  harnessRow.hidden = true;
6426
7195
  harnessRow.append(harnessBtn, effortGroup);
6427
7196
  queueSection.append(queueScroll, harnessRow, controlRow, noteAttach.wrap, sendBtn);
6428
7197
  var editAttach = attachify({
6429
7198
  attachments: shellAttachments,
6430
- mk: h9,
7199
+ mk: h10,
6431
7200
  className: "ez-qi-input",
6432
7201
  // Worded as the page popup's, which asks the same question of the same kind of
6433
7202
  // text, and puts the keys in the field rather than on a line of their own.
@@ -6437,7 +7206,9 @@
6437
7206
  onChange: () => {
6438
7207
  paintEditState();
6439
7208
  capQueue();
6440
- }
7209
+ },
7210
+ commands: [elementCommand(() => editAttach)],
7211
+ chooseLibrary: () => chooseLibraryAttachments(API)
6441
7212
  });
6442
7213
  editAttach.wrap.addEventListener(
6443
7214
  "keydown",
@@ -6456,12 +7227,12 @@
6456
7227
  },
6457
7228
  true
6458
7229
  );
6459
- var forkBar = h9("nav", "ez-fork-bar");
7230
+ var forkBar = h10("nav", "ez-fork-bar");
6460
7231
  forkBar.hidden = true;
6461
7232
  forkBar.setAttribute("aria-label", "\u76EE\u524D\u7684\u5C0D\u8A71\u4F4D\u7F6E");
6462
- var forkRoot = h9("button", "ez-fork-root");
6463
- var forkChip = h9("button", "ez-fork-here");
6464
- var forkName = h9("span", "ez-fork-name");
7233
+ var forkRoot = h10("button", "ez-fork-root");
7234
+ var forkChip = h10("button", "ez-fork-here");
7235
+ var forkName = h10("span", "ez-fork-name");
6465
7236
  forkChip.append(icon(MagicWand01Icon, 13), forkName);
6466
7237
  function rootOf(chat, chats) {
6467
7238
  let at2 = chat;
@@ -6486,7 +7257,7 @@
6486
7257
  return all.filter((c) => c.parentChatId && (here ? c.parentChatId === here.id : false));
6487
7258
  }
6488
7259
  function branchPicker(trigger, menuClass) {
6489
- const menu = h9("div", `ez-menu ${menuClass}`);
7260
+ const menu = h10("div", `ez-menu ${menuClass}`);
6490
7261
  menu.setAttribute("role", "menu");
6491
7262
  menu.setAttribute("aria-label", "\u5207\u63DB\u5230\u67D0\u500B\u5C0D\u8A71");
6492
7263
  menu.hidden = true;
@@ -6504,14 +7275,14 @@
6504
7275
  function show() {
6505
7276
  menu.textContent = "";
6506
7277
  rows = branchesOf(snapshot).map((chat) => {
6507
- const row = h9("button", "ez-notice-row ez-fork-row");
7278
+ const row = h10("button", "ez-notice-row ez-fork-row");
6508
7279
  row.setAttribute("role", "menuitemradio");
6509
7280
  row.setAttribute("aria-checked", String(chat.current));
6510
7281
  if (chat.current) row.dataset.current = "";
6511
7282
  row.append(
6512
7283
  icon(ArrowRight02Icon, 12),
6513
- h9("span", "ez-notice-row-name", chatName(chat, false)),
6514
- h9("span", "ez-notice-row-when", chatDetail(chat))
7284
+ h10("span", "ez-notice-row-name", chatName(chat, false)),
7285
+ h10("span", "ez-notice-row-when", chatDetail(chat))
6515
7286
  );
6516
7287
  row.addEventListener("pointerenter", () => pointAt(row));
6517
7288
  row.addEventListener("focus", () => pointAt(row));
@@ -6555,8 +7326,8 @@
6555
7326
  forkChip.disabled = true;
6556
7327
  forkChip.setAttribute("aria-current", "page");
6557
7328
  }
6558
- var forkMore = h9("button", "ez-fork-more");
6559
- forkMore.append(h9("span", void 0, "\u5206\u652F\u5C0D\u8A71"), icon(ChevronDownIcon, 12));
7329
+ var forkMore = h10("button", "ez-fork-more");
7330
+ forkMore.append(h10("span", void 0, "\u5206\u652F\u5C0D\u8A71"), icon(ChevronDownIcon, 12));
6560
7331
  forkMore.title = "\u9019\u689D\u4E3B\u7DDA\u5E95\u4E0B\u7684\u5206\u652F\u5C0D\u8A71";
6561
7332
  var morePicker = BRANCHES_REACHABLE ? branchPicker(forkMore, "ez-fork-more-menu") : null;
6562
7333
  async function leaveBranch(toChatId) {
@@ -6626,17 +7397,17 @@
6626
7397
  void convScroll.offsetWidth;
6627
7398
  convScroll.dataset.enter = deeper ? "deeper" : back ? "back" : "swap";
6628
7399
  }
6629
- var convSection = h9("section", "ez-section ez-conv-section");
6630
- var convList = h9("div", "ez-conv");
6631
- var convScroll = h9("div", "ez-fade ez-conv-scroll");
7400
+ var convSection = h10("section", "ez-section ez-conv-section");
7401
+ var convList = h10("div", "ez-conv");
7402
+ var convScroll = h10("div", "ez-fade ez-conv-scroll");
6632
7403
  convScroll.appendChild(convList);
6633
- var forkSep = h9("span", "ez-fork-sep");
7404
+ var forkSep = h10("span", "ez-fork-sep");
6634
7405
  forkSep.append(icon(ChevronRightIcon, 10));
6635
7406
  forkBar.append(forkRoot, forkSep, forkChip);
6636
7407
  if (forkPicker) forkBar.append(forkPicker.menu);
6637
7408
  if (morePicker) forkBar.append(forkMore, morePicker.menu);
6638
7409
  convSection.append(forkBar, convScroll, notices);
6639
- var resizer = h9("div", "ez-resizer");
7410
+ var resizer = h10("div", "ez-resizer");
6640
7411
  resizer.tabIndex = 0;
6641
7412
  resizer.setAttribute("role", "separator");
6642
7413
  resizer.setAttribute("aria-orientation", "vertical");
@@ -6644,17 +7415,17 @@
6644
7415
  resizer.setAttribute("aria-label", "\u8ABF\u6574\u5074\u908A\u6B04\u5BEC\u5EA6");
6645
7416
  resizer.title = "\u62D6\u66F3\u8ABF\u6574\u5BEC\u5EA6\uFF0C\u62D6\u5230\u5E95\u6536\u8D77\uFF0C\u96D9\u64CA\u9084\u539F";
6646
7417
  sidebar.append(sideHead, convSection, queueSection);
6647
- var sidebarSlot = h9("div", "ez-sidebar-slot");
7418
+ var sidebarSlot = h10("div", "ez-sidebar-slot");
6648
7419
  sidebarSlot.append(sidebar);
6649
- var activityBar = h9("nav", "ez-activity");
7420
+ var activityBar = h10("nav", "ez-activity");
6650
7421
  activityBar.setAttribute("aria-label", "\u5074\u908A\u6B04");
6651
- var reviewPanelBtn = h9("button", "ez-activity-btn");
7422
+ var reviewPanelBtn = h10("button", "ez-activity-btn");
6652
7423
  reviewPanelBtn.append(icon(BubbleChatIcon, 18));
6653
7424
  reviewPanelBtn.onclick = () => toggleSidebar();
6654
7425
  var VIEW_PARAM = "view";
6655
7426
  var LIBRARY_VIEW = "library";
6656
7427
  var libraryInUrl = () => new URLSearchParams(location.search).get(VIEW_PARAM) === LIBRARY_VIEW;
6657
- var libraryBtn = h9("button", "ez-activity-btn");
7428
+ var libraryBtn = h10("button", "ez-activity-btn");
6658
7429
  libraryBtn.setAttribute("aria-pressed", String(libraryInUrl()));
6659
7430
  libraryBtn.dataset.testid = LIBRARY_TEST_IDS.open;
6660
7431
  libraryBtn.setAttribute("aria-label", "\u7D20\u6750\u5EAB");
@@ -6722,7 +7493,7 @@
6722
7493
  function gridMetrics() {
6723
7494
  return { gap: GRID_GAP, rowGap: GRID_ROW_GAP, head: cardHead() };
6724
7495
  }
6725
- var dropHint = h9("div", "ez-drop-hint");
7496
+ var dropHint = h10("div", "ez-drop-hint");
6726
7497
  var DRAG_SLOP = 4;
6727
7498
  var ROW_SNAP = 30;
6728
7499
  var PAN_EDGE = 44;
@@ -6985,7 +7756,7 @@
6985
7756
  paintVeil();
6986
7757
  paintPickPill();
6987
7758
  if (was && !pickState && !out.effects.some((x) => x.do === "insert-note")) {
6988
- noteAttach.cancelRef();
7759
+ pickComposer.cancelRef();
6989
7760
  paintSendState();
6990
7761
  }
6991
7762
  }
@@ -6998,7 +7769,7 @@
6998
7769
  switch (effect.do) {
6999
7770
  case "arm-overlay":
7000
7771
  if (effect.host === "note") {
7001
- noteAttach.beginRef(PICKING_LABEL);
7772
+ pickComposer.beginRef(PICKING_LABEL);
7002
7773
  paintSendState();
7003
7774
  }
7004
7775
  for (const f of pickFrames(effect.frame)) {
@@ -7031,8 +7802,9 @@
7031
7802
  for (const f of pickFrames(effect.frame)) if (f.kind === "app") navigateFrame(f, effect.page);
7032
7803
  return;
7033
7804
  case "insert-note":
7034
- noteAttach.resolveRef(effect.ref);
7035
- focusNote();
7805
+ pickComposer.resolveRef(effect.ref);
7806
+ if (pickComposer === noteAttach) focusNote();
7807
+ else pickComposer.editable.focus();
7036
7808
  paintSendState();
7037
7809
  return;
7038
7810
  case "banner":
@@ -7041,11 +7813,12 @@
7041
7813
  return;
7042
7814
  }
7043
7815
  }
7044
- function startPick(host) {
7816
+ function startPick(into) {
7817
+ pickComposer = into;
7045
7818
  dispatchPick({
7046
7819
  t: "arm",
7047
7820
  id: `s${++pickSeq}-${Date.now().toString(36)}`,
7048
- host,
7821
+ host: "note",
7049
7822
  now: Date.now()
7050
7823
  });
7051
7824
  }
@@ -7094,14 +7867,25 @@
7094
7867
  ...init
7095
7868
  });
7096
7869
  }
7870
+ function sidebarToggle(toggle) {
7871
+ const button2 = h10("button", "ez-sidebar-toggle");
7872
+ button2.type = "button";
7873
+ button2.title = "\u5C55\u958B\u5074\u908A\u6B04\uFF08\u2303\u2318S\uFF09";
7874
+ button2.setAttribute("aria-label", button2.title);
7875
+ button2.append(icon(SidebarLeftIcon, 16));
7876
+ button2.onclick = () => toggle();
7877
+ return button2;
7878
+ }
7097
7879
  function canvasPorts() {
7098
7880
  return {
7099
7881
  guests: frames,
7100
7882
  stageWrap,
7883
+ ...host.kind === "desktop" ? { tabsLead: sidebarToggle(host.toggleSidebar) } : {},
7101
7884
  stage,
7102
7885
  api,
7103
7886
  onTabShown: (tab) => {
7104
7887
  deviceRow.hidden = stagedApp === null || tab !== "app";
7888
+ paintDevServer();
7105
7889
  if (tab === "app") paintFrames();
7106
7890
  paintPage();
7107
7891
  paintPickPill();
@@ -7120,6 +7904,46 @@
7120
7904
  function paintSendState() {
7121
7905
  sendBtn.disabled = !canSend();
7122
7906
  }
7907
+ var sendWhenReady = false;
7908
+ function setSendWhenReady(on) {
7909
+ sendWhenReady = on;
7910
+ sendBtn.classList.toggle("ez-send-waiting", on);
7911
+ sendBtn.setAttribute("aria-busy", String(on));
7912
+ sendBtn.title = on ? "\u5F71\u7247\u8655\u7406\u5B8C\u5C31\u6703\u9001\u51FA\uFF08\u518D\u6309\u4E00\u6B21\u53D6\u6D88\uFF09" : "\u9001\u51FA\u7D66 agent\uFF08\u2318/Ctrl + Enter\uFF09";
7913
+ }
7914
+ function videosHoldSend() {
7915
+ const pending = new Set(snapshot?.pendingVideos ?? []);
7916
+ if (!pending.size) return false;
7917
+ const queued = snapshot?.annotations.flatMap((a) => a.attachments?.map((f) => f.id) ?? []) ?? [];
7918
+ return [...noteAttach.ids(), ...editAttach.ids(), ...queued].some((id) => pending.has(id));
7919
+ }
7920
+ var VIDEO_UNREADABLE = {
7921
+ "unsupported-browser": "\u9019\u500B\u700F\u89BD\u5668\u7121\u6CD5\u89E3\u78BC\u5F71\u7247",
7922
+ "unsupported-codec": "\u700F\u89BD\u5668\u4E0D\u652F\u63F4\u9019\u6BB5\u5F71\u7247\u7684\u7DE8\u78BC",
7923
+ unreadable: "\u8B80\u4E0D\u51FA\u5F71\u7247\u5167\u5BB9",
7924
+ crashed: "\u64F7\u53D6\u756B\u9762\u6642\u4E2D\u65B7"
7925
+ };
7926
+ var videoLines = /* @__PURE__ */ new Map();
7927
+ var progressOf = (ids) => `${Math.round(ids.reduce((sum, id) => sum + digests.progress(id), 0) / ids.length * 100)}%`;
7928
+ function paintVideos() {
7929
+ const pending = new Set(snapshot?.pendingVideos ?? []);
7930
+ const noteFor = (ids) => {
7931
+ const waiting = ids.filter((id) => pending.has(id));
7932
+ return waiting.length ? `\u8655\u7406\u4E2D ${progressOf(waiting)}` : null;
7933
+ };
7934
+ noteAttach.paintNotes(noteFor);
7935
+ editAttach.paintNotes(noteFor);
7936
+ for (const [line, videos] of videoLines) {
7937
+ const failed = videos.find((f) => f.video?.state === "failed");
7938
+ const waiting = videos.filter((f) => pending.has(f.id)).map((f) => f.id);
7939
+ line.classList.toggle("ez-qi-video-failed", !waiting.length && !!failed);
7940
+ if (waiting.length) line.textContent = `\u5F71\u7247\u8655\u7406\u4E2D ${progressOf(waiting)}`;
7941
+ else if (failed?.video?.state === "failed") {
7942
+ line.textContent = `${failed.name}\uFF1A${VIDEO_UNREADABLE[failed.video.reason]}\uFF0Cagent \u770B\u4E0D\u5230\u756B\u9762`;
7943
+ } else line.textContent = "";
7944
+ }
7945
+ if (sendWhenReady && !videosHoldSend()) void sendBatch();
7946
+ }
7123
7947
  var LIMIT_BEAT_MS = 12e4;
7124
7948
  function refreshLimit() {
7125
7949
  if (document.visibilityState !== "visible" || !snapshot?.acp) return;
@@ -7131,6 +7955,11 @@
7131
7955
  setInterval(refreshLimit, LIMIT_BEAT_MS);
7132
7956
  async function sendBatch() {
7133
7957
  if (!canSend()) return;
7958
+ if (videosHoldSend()) {
7959
+ setSendWhenReady(!sendWhenReady);
7960
+ return;
7961
+ }
7962
+ setSendWhenReady(false);
7134
7963
  const count = snapshot?.annotations.length ?? 0;
7135
7964
  const attachments = noteAttach.ids();
7136
7965
  const references = noteAttach.refs();
@@ -7183,6 +8012,7 @@
7183
8012
  function closeEdit(reason) {
7184
8013
  const at2 = editing;
7185
8014
  if (!at2) return;
8015
+ if (pickState?.host === "note" && pickComposer === editAttach) abortPick("escape");
7186
8016
  editing = null;
7187
8017
  editSaving = false;
7188
8018
  if (reason === "cancelled" || reason === "switched") {
@@ -7226,12 +8056,12 @@
7226
8056
  }
7227
8057
  function brandMark(brand2, size) {
7228
8058
  if (!brand2) return [];
7229
- const mark = h9("span", "ez-brand-mark");
8059
+ const mark = h10("span", "ez-brand-mark");
7230
8060
  mark.append(icon(brand2 === "claude" ? ClaudeIcon : ChatGptIcon, size));
7231
8061
  return [mark];
7232
8062
  }
7233
8063
  function menuCheck() {
7234
- const check = h9("span", "ez-menu-check");
8064
+ const check = h10("span", "ez-menu-check");
7235
8065
  check.append(icon(CheckIcon, 14));
7236
8066
  return check;
7237
8067
  }
@@ -7292,6 +8122,7 @@
7292
8122
  }
7293
8123
  async function switchAgent(agent) {
7294
8124
  closeAgentMenu();
8125
+ if (!snapshot?.acp) return void connectAgent(agent);
7295
8126
  if (agent.id === runningAgentId()) return;
7296
8127
  const ok = await askConfirm({
7297
8128
  // Keyed on the act, not on which agent: the reason is the same whichever
@@ -7302,37 +8133,63 @@
7302
8133
  go: `\u6539\u7528 ${agent.name}`
7303
8134
  });
7304
8135
  if (!ok) return;
8136
+ await connectAgent(agent);
8137
+ }
8138
+ async function connectAgent(agent) {
7305
8139
  await api("/acp/agent", {
7306
8140
  method: "POST",
7307
- body: JSON.stringify({ id: agent.id })
8141
+ body: JSON.stringify({ id: agent.id, host: host.kind })
7308
8142
  });
7309
8143
  }
8144
+ async function disconnectAgent() {
8145
+ closeAgentMenu();
8146
+ await api("/acp/disconnect", { method: "POST" });
8147
+ }
7310
8148
  function paintAgents() {
7311
8149
  const command = snapshot?.acp?.agent ?? "";
7312
8150
  const runningId = runningAgentId();
7313
8151
  const shown = agents.filter((a) => a.installed || a.id === runningId);
7314
- const signature = JSON.stringify([shown, command]);
8152
+ const connected = !!snapshot?.acp;
8153
+ const signature = JSON.stringify([shown, command, connected]);
7315
8154
  if (signature === agentsDrawn) return;
7316
8155
  agentsDrawn = signature;
7317
8156
  agentPill.textContent = "";
8157
+ agentPill.toggleAttribute("data-offline", !connected);
7318
8158
  agentPill.append(
7319
- ...brandMark(agentBrandFor(command), 14),
7320
- h9("span", "ez-agent-name", agentProfileFor(command)?.name ?? "Agent"),
8159
+ ...connected ? brandMark(agentBrandFor(command), 14) : [icon(Plug01Icon, 14)],
8160
+ h10("span", "ez-agent-name", connected ? agentProfileFor(command)?.name ?? "Agent" : "\u9023\u7DDA Agent"),
7321
8161
  icon(ChevronDownIcon, 12)
7322
8162
  );
7323
8163
  agentMenu.textContent = "";
8164
+ const choices = h10("div", "ez-menu-group");
8165
+ agentMenu.append(choices);
8166
+ if (!shown.length) {
8167
+ const none = h10("button", "ez-menu-item");
8168
+ none.disabled = true;
8169
+ none.append(h10("span", "ez-menu-name", "\u9019\u53F0\u96FB\u8166\u4E0A\u6C92\u6709\u53EF\u7528\u7684 agent"));
8170
+ choices.append(none);
8171
+ }
7324
8172
  agentRows = shown.map((agent) => {
7325
8173
  const on = agent.id === runningId;
7326
- const row = h9("button", "ez-menu-item");
8174
+ const row = h10("button", "ez-menu-item");
7327
8175
  row.setAttribute("role", "menuitemradio");
7328
8176
  row.setAttribute("aria-checked", String(on));
7329
8177
  if (on) row.dataset.current = "";
7330
8178
  const brand2 = AGENT_PROFILES.find((p) => p.id === agent.id)?.brand;
7331
- row.append(...brandMark(brand2, 15), h9("span", "ez-menu-name", agent.name), menuCheck());
8179
+ row.append(...brandMark(brand2, 15), h10("span", "ez-menu-name", agent.name), menuCheck());
7332
8180
  row.onclick = () => void switchAgent(agent);
7333
- agentMenu.append(row);
8181
+ choices.append(row);
7334
8182
  return row;
7335
8183
  });
8184
+ if (!connected) return;
8185
+ const off = h10("button", "ez-menu-item");
8186
+ off.setAttribute("role", "menuitem");
8187
+ off.append(icon(Plug01Icon, 15), h10("span", "ez-menu-name", "\u4E2D\u65B7\u9023\u7DDA"));
8188
+ off.onclick = () => void disconnectAgent();
8189
+ const offGroup = h10("div", "ez-menu-group");
8190
+ offGroup.append(off);
8191
+ agentMenu.append(offGroup);
8192
+ agentRows.push(off);
7336
8193
  }
7337
8194
  async function fetchSkills() {
7338
8195
  if (!snapshot?.acp) return [];
@@ -7342,7 +8199,8 @@
7342
8199
  return skills.map((skill) => ({
7343
8200
  id: skill.name,
7344
8201
  label: skill.name,
7345
- keywords: [skill.description],
8202
+ keywords: [],
8203
+ description: skill.description,
7346
8204
  run: () => noteAttach.insertSkill(skill.name)
7347
8205
  }));
7348
8206
  }
@@ -7404,24 +8262,24 @@
7404
8262
  if (!line) return;
7405
8263
  limitPill.textContent = line;
7406
8264
  const plan = planName(limit?.plan);
7407
- const head = h9("div", "ez-usage-head");
7408
- head.append(h9("span", "ez-usage-title", "\u7528\u91CF"));
7409
- if (plan) head.append(h9("span", "ez-usage-plan", plan));
8265
+ const head = h10("div", "ez-usage-head");
8266
+ head.append(h10("span", "ez-usage-title", "\u7528\u91CF"));
8267
+ if (plan) head.append(h10("span", "ez-usage-plan", plan));
7410
8268
  limitCard.replaceChildren(head);
7411
8269
  for (const row of usageRows(limit)) {
7412
- const top = h9("div", "ez-usage-line");
8270
+ const top = h10("div", "ez-usage-line");
7413
8271
  top.append(
7414
- h9("span", "ez-usage-label", row.label),
7415
- h9("span", "ez-usage-left", `\u5269 ${row.percent}%`)
8272
+ h10("span", "ez-usage-label", row.label),
8273
+ h10("span", "ez-usage-left", `\u5269 ${row.percent}%`)
7416
8274
  );
7417
- const fill = h9("i");
8275
+ const fill = h10("i");
7418
8276
  fill.style.width = `${row.used}%`;
7419
- const bar = h9("div", "ez-usage-bar");
8277
+ const bar = h10("div", "ez-usage-bar");
7420
8278
  bar.append(fill);
7421
- const item = h9("div", "ez-usage-row");
8279
+ const item = h10("div", "ez-usage-row");
7422
8280
  if (row.low) item.dataset.low = "";
7423
8281
  item.append(top, bar);
7424
- if (row.when) item.append(h9("div", "ez-usage-when", row.when));
8282
+ if (row.when) item.append(h10("div", "ez-usage-when", row.when));
7425
8283
  limitCard.append(item);
7426
8284
  }
7427
8285
  }
@@ -7442,13 +8300,13 @@
7442
8300
  const named = pillOption(options);
7443
8301
  const label = named && named.type === "select" ? shortConfigValueName(configValueName(named, named.currentValue)) : named && configLabel(named) || "";
7444
8302
  configPill.textContent = "";
7445
- configPill.append(h9("span", "ez-config-name", label), icon(ChevronDownIcon, 12));
8303
+ configPill.append(h10("span", "ez-config-name", label), icon(ChevronDownIcon, 12));
7446
8304
  const full = named && named.type === "select" ? configValueName(named, named.currentValue) : "";
7447
8305
  const detail = named && named.type === "select" ? configValues(named).find((o) => o.value === named.currentValue)?.description : void 0;
7448
8306
  configPill.title = [named ? configLabel(named) : "", full, detail].filter(Boolean).join(" \xB7 ");
7449
8307
  configMenu.textContent = "";
7450
8308
  for (const option of [...options].sort(byCategory)) {
7451
- const group = h9("div", "ez-menu-group");
8309
+ const group = h10("div", "ez-menu-group");
7452
8310
  group.setAttribute("role", "group");
7453
8311
  group.setAttribute("aria-label", configLabel(option));
7454
8312
  if (option.type === "boolean") {
@@ -7456,7 +8314,7 @@
7456
8314
  configRow(option, !option.currentValue, configLabel(option), option, option.currentValue)
7457
8315
  );
7458
8316
  } else {
7459
- group.append(h9("div", "ez-menu-label", configLabel(option)));
8317
+ group.append(h10("div", "ez-menu-label", configLabel(option)));
7460
8318
  for (const value of configValues(option)) {
7461
8319
  group.append(
7462
8320
  configRow(option, value.value, value.name, value, value.value === option.currentValue)
@@ -7470,11 +8328,11 @@
7470
8328
  var HIDDEN_CATEGORIES = /* @__PURE__ */ new Set(["mode", "model_config"]);
7471
8329
  var CATEGORY_ORDER = ["model", "thought_level"];
7472
8330
  function byCategory(a, b) {
7473
- const rank = (o) => {
8331
+ const rank2 = (o) => {
7474
8332
  const at2 = CATEGORY_ORDER.indexOf(o.category ?? "");
7475
8333
  return at2 === -1 ? CATEGORY_ORDER.length : at2;
7476
8334
  };
7477
- return rank(a) - rank(b);
8335
+ return rank2(a) - rank2(b);
7478
8336
  }
7479
8337
  function resolvedName(option, value, name, describes) {
7480
8338
  const resolved = describes.description;
@@ -7484,11 +8342,11 @@
7484
8342
  return `${name.replace(/\s*\([^)]*\)\s*$/, "")} (${resolved.replace(/\s*\(([^)]*)\)/g, " \xB7 $1")})`;
7485
8343
  }
7486
8344
  function configRow(option, value, name, describes, on) {
7487
- const row = h9("button", "ez-menu-item");
8345
+ const row = h10("button", "ez-menu-item");
7488
8346
  row.setAttribute("role", option.type === "boolean" ? "menuitemcheckbox" : "menuitemradio");
7489
8347
  row.setAttribute("aria-checked", String(on));
7490
8348
  if (on) row.dataset.current = "";
7491
- row.append(h9("span", "ez-menu-name", resolvedName(option, value, name, describes)), menuCheck());
8349
+ row.append(h10("span", "ez-menu-name", resolvedName(option, value, name, describes)), menuCheck());
7492
8350
  if (describes.description) row.title = describes.description;
7493
8351
  row.onclick = () => void setConfig(option.id, value);
7494
8352
  return row;
@@ -7500,23 +8358,24 @@
7500
8358
  if (signature === queueDrawn) return;
7501
8359
  queueDrawn = signature;
7502
8360
  queueList.textContent = "";
8361
+ videoLines.clear();
7503
8362
  annotations.forEach((a, i) => {
7504
- const li = h9("li", "ez-queue-item");
7505
- const head = h9("div", "ez-qi-head");
7506
- head.append(h9("span", "ez-qi-num", String(i + 1)), h9("span", "ez-qi-label", annotationLabel(a)));
8363
+ const li = h10("li", "ez-queue-item");
8364
+ const head = h10("div", "ez-qi-head");
8365
+ head.append(h10("span", "ez-qi-num", String(i + 1)), h10("span", "ez-qi-label", annotationLabel(a)));
7507
8366
  const vp = a.anchor.viewport;
7508
8367
  if (vp?.width && vp.preset !== "desktop") {
7509
- head.append(h9("span", "ez-qi-vp", `${vp.width}px`));
8368
+ head.append(h10("span", "ez-qi-vp", `${vp.width}px`));
7510
8369
  }
7511
- const acts = h9("div", "ez-qi-acts");
8370
+ const acts = h10("div", "ez-qi-acts");
7512
8371
  const open = editing?.id === a.id;
7513
- const primary = h9("button", `ez-qi-act ${open ? "ez-qi-tick" : "ez-qi-edit"}`);
8372
+ const primary = h10("button", `ez-qi-act ${open ? "ez-qi-tick" : "ez-qi-edit"}`);
7514
8373
  primary.append(
7515
8374
  open ? icon(CheckIcon, TICK_ICON) : icon(Edit02Icon, EDIT_ICON)
7516
8375
  );
7517
8376
  primary.title = open ? `\u5132\u5B58\uFF08${MOD_LABEL}+Enter\uFF09` : "\u6539\u9019\u5247\u6A19\u8A3B\uFF08\u9001\u51FA\u524D\u90FD\u9084\u80FD\u6539\uFF09";
7518
8377
  primary.onclick = open ? () => void saveEdit() : () => openEdit(a);
7519
- const dismiss = h9("button", "ez-qi-act ez-qi-del");
8378
+ const dismiss = h10("button", "ez-qi-act ez-qi-del");
7520
8379
  dismiss.append(icon(Cancel01Icon, 13));
7521
8380
  dismiss.title = open ? "\u53D6\u6D88\uFF08Esc\uFF09" : "\u79FB\u9664\u9019\u5247\u6A19\u8A3B";
7522
8381
  dismiss.onclick = open ? () => closeEdit("cancelled") : () => void api(`/annotations/${a.id}`, { method: "DELETE" });
@@ -7540,6 +8399,12 @@
7540
8399
  if (a.comment || a.references?.length || a.attachments?.length) li.append(row.box);
7541
8400
  const files = fileChips(row.unplacedFiles);
7542
8401
  if (files) li.append(files);
8402
+ const videos = a.attachments?.filter((f) => f.video && f.video.state !== "ready") ?? [];
8403
+ if (videos.length) {
8404
+ const line = h10("div", "ez-qi-video");
8405
+ videoLines.set(line, videos);
8406
+ li.append(line);
8407
+ }
7543
8408
  queueList.appendChild(li);
7544
8409
  });
7545
8410
  capQueue();
@@ -7555,17 +8420,17 @@
7555
8420
  return parts.join(" \xB7 ") || a.anchor.page || "";
7556
8421
  }
7557
8422
  function buildSaid(entry, isUser) {
7558
- const said = h9("div", isUser ? "ez-bubble" : "ez-said");
8423
+ const said = h10("div", isUser ? "ez-bubble" : "ez-said");
7559
8424
  const items = entry.items ?? [];
7560
8425
  if (items.length) {
7561
8426
  const key = entry.batchId ?? String(entry.ts);
7562
8427
  const expanded = expandedBatches.has(key);
7563
8428
  const collapsed = items.length > ITEM_LIMIT + 1 && !expanded;
7564
8429
  const shown = collapsed ? items.slice(0, ITEM_LIMIT) : items;
7565
- const list = h9("ol", "ez-bubble-list");
8430
+ const list = h10("ol", "ez-bubble-list");
7566
8431
  shown.forEach((item, i) => {
7567
- const li = h9("li", "ez-bubble-item");
7568
- const body = h9("div", "ez-bi-body");
8432
+ const li = h10("li", "ez-bubble-item");
8433
+ const body = h10("div", "ez-bi-body");
7569
8434
  const rendered = commentEl(
7570
8435
  item.comment,
7571
8436
  item.references,
@@ -7577,12 +8442,12 @@
7577
8442
  if (rendered.box.childNodes.length) body.append(rendered.box);
7578
8443
  const files = fileChips(rendered.unplacedFiles);
7579
8444
  if (files) body.append(files);
7580
- li.append(h9("span", "ez-bi-num", `${i + 1}.`), body);
8445
+ li.append(h10("span", "ez-bi-num", `${i + 1}.`), body);
7581
8446
  list.appendChild(li);
7582
8447
  });
7583
8448
  said.appendChild(list);
7584
8449
  if (collapsed) {
7585
- const more = h9("button", "ez-bubble-more", `\u9084\u6709 ${items.length - ITEM_LIMIT} \u5247`);
8450
+ const more = h10("button", "ez-bubble-more", `\u9084\u6709 ${items.length - ITEM_LIMIT} \u5247`);
7586
8451
  more.onclick = () => {
7587
8452
  expandedBatches.add(key);
7588
8453
  render();
@@ -7609,9 +8474,9 @@
7609
8474
  return said;
7610
8475
  }
7611
8476
  function chipEl(name, glyph, kind, title) {
7612
- const chip = h9("span", kind ? `ez-chip ez-chip-${kind}` : "ez-chip");
8477
+ const chip = h10("span", kind ? `ez-chip ez-chip-${kind}` : "ez-chip");
7613
8478
  if (glyph) chip.append(icon(glyph, 11));
7614
- chip.append(h9("span", "ez-chip-name", name));
8479
+ chip.append(h10("span", "ez-chip-name", name));
7615
8480
  if (title) chip.title = title;
7616
8481
  return chip;
7617
8482
  }
@@ -7619,12 +8484,12 @@
7619
8484
  var skillChipEl = (name) => chipEl(`$${name}`, null, "skill");
7620
8485
  function fileChips(names) {
7621
8486
  if (!names.length) return null;
7622
- const row = h9("div", "ez-file-chips");
8487
+ const row = h10("div", "ez-file-chips");
7623
8488
  for (const name of names) row.appendChild(chipEl(name, File02Icon));
7624
8489
  return row;
7625
8490
  }
7626
8491
  function commentEl(text, refs, files, className, skills) {
7627
- const box = h9("div", className);
8492
+ const box = h10("div", className);
7628
8493
  const numbered = new Map(
7629
8494
  (refs ?? []).flatMap((r) => typeof r === "string" ? [] : [[r.n, r.label]])
7630
8495
  );
@@ -7724,9 +8589,9 @@
7724
8589
  function paintStatus() {
7725
8590
  statusPill.toggleAttribute("data-alert", daemonGone);
7726
8591
  if (daemonGone) {
7727
- const how = h9("span");
7728
- how.append("\u5728\u5C08\u6848\u76EE\u9304\u57F7\u884C ", h9("code", "ez-status-code", "npx -y @cychien/ezdesign@latest"), " \u91CD\u65B0\u555F\u52D5");
7729
- statusPill.replaceChildren(h9("span", "ez-status-title", "ezdesign \u5DF2\u505C\u6B62\u904B\u4F5C"), how);
8592
+ const how = h10("span");
8593
+ how.append("\u5728\u5C08\u6848\u76EE\u9304\u57F7\u884C ", h10("code", "ez-status-code", "npx -y @cychien/ezdesign@latest"), " \u91CD\u65B0\u555F\u52D5");
8594
+ statusPill.replaceChildren(h10("span", "ez-status-title", "ezdesign \u5DF2\u505C\u6B62\u904B\u4F5C"), how);
7730
8595
  statusPill.hidden = false;
7731
8596
  return;
7732
8597
  }
@@ -7738,12 +8603,17 @@
7738
8603
  const off = snapshot?.state === "ended" || daemonGone || stagedApp === null;
7739
8604
  for (const { btn } of annotateBtns) btn.disabled = off;
7740
8605
  }
8606
+ function paintDevServer() {
8607
+ if (!snapshot) return;
8608
+ devServerPanel.update(snapshot.devServer, { shown: stagedApp !== null, onAppTab: canvasUi?.activeTab === "app" });
8609
+ }
7741
8610
  function render() {
7742
8611
  if (!snapshot) return;
7743
8612
  const s = snapshot;
7744
8613
  stageApp(s.app ?? null);
7745
8614
  canvasUi ?? (canvasUi = installCanvas(canvasPorts()));
7746
8615
  canvasUi.render(s);
8616
+ paintDevServer();
7747
8617
  paintPage();
7748
8618
  const ended = s.state === "ended";
7749
8619
  if (ended) abortPick("ended");
@@ -7763,10 +8633,10 @@
7763
8633
  onBranch = !!s.chats?.find((c) => c.current)?.parentChatId;
7764
8634
  paintVeil();
7765
8635
  frames.postAll({ type: "ez:in-explore", on: onBranch });
7766
- agentWrap.hidden = !s.acp;
8636
+ agentWrap.hidden = !s.acp && s.agentOnline;
7767
8637
  paintHarness();
7768
8638
  controlRow.hidden = agentWrap.hidden && configWrap.hidden;
7769
- if (s.acp && !agents.length) void loadAgents();
8639
+ if (!agentWrap.hidden && !agents.length) void loadAgents();
7770
8640
  paintAgents();
7771
8641
  if (s.acp?.cancelling) {
7772
8642
  agentStatus.className = "ez-badge ez-working";
@@ -7790,17 +8660,19 @@
7790
8660
  agentStatus.title = s.acp?.state === "exited" && s.acp.error || "";
7791
8661
  reviewPanelBtn.toggleAttribute("data-working", agentStatus.classList.contains("ez-working"));
7792
8662
  paintQueue(s.annotations);
8663
+ digests.observe(s.pendingVideos ?? []);
8664
+ paintVideos();
7793
8665
  convList.textContent = "";
7794
8666
  let prevRole = null;
7795
8667
  let livePlaced = false;
7796
8668
  for (const entry of threadOrder(s.conversation)) {
7797
8669
  if (entry.role === "system") {
7798
- convList.appendChild(h9("div", "ez-msg-system", entry.text));
8670
+ convList.appendChild(h10("div", "ez-msg-system", entry.text));
7799
8671
  prevRole = null;
7800
8672
  continue;
7801
8673
  }
7802
8674
  const grouped = entry.role === prevRole;
7803
- const item = h9("div", `ez-msg ez-msg-${entry.role}${grouped ? " ez-msg-cont" : ""}`);
8675
+ const item = h10("div", `ez-msg ez-msg-${entry.role}${grouped ? " ez-msg-cont" : ""}`);
7804
8676
  const isUser = entry.role === "user";
7805
8677
  item.append(buildSaid(entry, isUser));
7806
8678
  convList.appendChild(item);
@@ -7821,28 +8693,28 @@
7821
8693
  }
7822
8694
  if (s.acp?.state === "exited" && s.acp.error) {
7823
8695
  convList.appendChild(
7824
- h9("div", "ez-msg-system ez-msg-system-plain", `agent \u5DF2\u96E2\u7DDA\uFF1A${s.acp.error}`)
8696
+ h10("div", "ez-msg-system ez-msg-system-plain", `agent \u5DF2\u96E2\u7DDA\uFF1A${s.acp.error}`)
7825
8697
  );
7826
8698
  }
7827
8699
  convList.scrollTop = convList.scrollHeight;
7828
8700
  paintConvFades();
7829
8701
  }
7830
8702
  function liveTurnEl(s, prevRole) {
7831
- const row = h9("div", `ez-msg ez-msg-agent${prevRole === "agent" ? " ez-msg-cont" : ""}`);
7832
- const dots = h9("div", "ez-thinking");
8703
+ const row = h10("div", `ez-msg ez-msg-agent${prevRole === "agent" ? " ez-msg-cont" : ""}`);
8704
+ const dots = h10("div", "ez-thinking");
7833
8705
  dots.setAttribute("role", "status");
7834
8706
  const opening = s.acp?.state === "starting";
7835
8707
  dots.setAttribute("aria-label", s.agentProgress ?? (opening ? "Agent \u6E96\u5099\u4E2D" : "Agent \u4FEE\u6539\u4E2D"));
7836
- for (let i = 0; i < 3; i++) dots.appendChild(h9("span", "ez-dot"));
7837
- if (s.agentProgress) dots.appendChild(h9("span", "ez-progress", s.agentProgress));
7838
- const turn = h9("div", "ez-acp-turn");
8708
+ for (let i = 0; i < 3; i++) dots.appendChild(h10("span", "ez-dot"));
8709
+ if (s.agentProgress) dots.appendChild(h10("span", "ez-progress", s.agentProgress));
8710
+ const turn = h10("div", "ez-acp-turn");
7839
8711
  if (s.acp && s.acp.state === "working") turn.append(acpFeedEl(s.acp));
7840
8712
  turn.append(dots);
7841
8713
  row.append(turn);
7842
8714
  return row;
7843
8715
  }
7844
8716
  function acpFeedEl(acp) {
7845
- const box = h9("div", "ez-acp-feed");
8717
+ const box = h10("div", "ez-acp-feed");
7846
8718
  const latest = acp.feed.at(-1);
7847
8719
  for (const item of acp.feed) {
7848
8720
  switch (item.kind) {
@@ -7850,18 +8722,18 @@
7850
8722
  box.appendChild(markdownEl(item.text, "ez-md ez-acp-say"));
7851
8723
  break;
7852
8724
  case "thought":
7853
- if (item === latest) box.appendChild(h9("div", "ez-acp-thought", item.text.slice(-160)));
8725
+ if (item === latest) box.appendChild(h10("div", "ez-acp-thought", item.text.slice(-160)));
7854
8726
  break;
7855
8727
  case "tool": {
7856
- const line = h9("div", `ez-acp-tool ez-acp-tool-${item.status}`);
7857
- line.append(h9("span", "ez-acp-dot"), h9("span", "ez-acp-text", item.title));
8728
+ const line = h10("div", `ez-acp-tool ez-acp-tool-${item.status}`);
8729
+ line.append(h10("span", "ez-acp-dot"), h10("span", "ez-acp-text", item.title));
7858
8730
  box.appendChild(line);
7859
8731
  break;
7860
8732
  }
7861
8733
  case "plan":
7862
8734
  for (const entry of item.entries) {
7863
- const line = h9("div", `ez-acp-plan ez-acp-plan-${entry.status}`);
7864
- line.append(h9("span", "ez-acp-tick"), h9("span", "ez-acp-text", entry.content));
8735
+ const line = h10("div", `ez-acp-plan ez-acp-plan-${entry.status}`);
8736
+ line.append(h10("span", "ez-acp-tick"), h10("span", "ez-acp-text", entry.content));
7865
8737
  box.appendChild(line);
7866
8738
  }
7867
8739
  break;
@@ -7870,8 +8742,8 @@
7870
8742
  return box;
7871
8743
  }
7872
8744
  function acpAskEl(ask) {
7873
- const card = h9("div", "ez-acp-ask");
7874
- if (ask.title) card.append(h9("div", "ez-acp-ask-title", ask.title));
8745
+ const card = h10("div", "ez-acp-ask");
8746
+ if (ask.title) card.append(h10("div", "ez-acp-ask-title", ask.title));
7875
8747
  const values = /* @__PURE__ */ new Map();
7876
8748
  const choicesOnly = ask.fields.every((f) => f.kind === "select");
7877
8749
  const customKeys = new Set(
@@ -7894,7 +8766,7 @@
7894
8766
  const submit = () => {
7895
8767
  if (complete()) send({ answers: Object.fromEntries(values) });
7896
8768
  };
7897
- const submitBtn = h9("button", "ez-acp-opt ez-acp-opt-primary", "\u9001\u51FA");
8769
+ const submitBtn = h10("button", "ez-acp-opt ez-acp-opt-primary", "\u9001\u51FA");
7898
8770
  submitBtn.onclick = submit;
7899
8771
  const refresh = () => {
7900
8772
  submitBtn.hidden = sendsOnPick();
@@ -7908,7 +8780,7 @@
7908
8780
  ask.fields.forEach((field, i) => {
7909
8781
  let labelId;
7910
8782
  if (field.text || mixed && field.optional) {
7911
- const label = h9(
8783
+ const label = h10(
7912
8784
  "div",
7913
8785
  "ez-acp-ask-q",
7914
8786
  `${field.text ?? ""}${mixed && field.optional ? "\uFF08\u9078\u586B\uFF09" : ""}`
@@ -7920,9 +8792,9 @@
7920
8792
  card.append(acpFieldEl(field, values, changed, submit, labelId));
7921
8793
  });
7922
8794
  if (ask.kind === "question" || !choicesOnly || !askSendsOnPick(ask.fields, false)) {
7923
- const actions = h9("div", "ez-acp-ask-actions");
8795
+ const actions = h10("div", "ez-acp-ask-actions");
7924
8796
  if (ask.kind === "question") {
7925
- const skip = h9("button", "ez-acp-opt", "\u7565\u904E");
8797
+ const skip = h10("button", "ez-acp-opt", "\u7565\u904E");
7926
8798
  skip.onclick = () => send({ decline: true });
7927
8799
  actions.append(skip);
7928
8800
  }
@@ -7939,9 +8811,9 @@
7939
8811
  return el2;
7940
8812
  };
7941
8813
  const optionButton = (option) => {
7942
- const btn = h9("button", `ez-acp-opt${option.hint ? ` ez-acp-opt-${option.hint}` : ""}`);
7943
- btn.append(h9("span", void 0, option.name));
7944
- if (option.description) btn.append(h9("span", "ez-acp-opt-desc", option.description));
8814
+ const btn = h10("button", `ez-acp-opt${option.hint ? ` ez-acp-opt-${option.hint}` : ""}`);
8815
+ btn.append(h10("span", void 0, option.name));
8816
+ if (option.description) btn.append(h10("span", "ez-acp-opt-desc", option.description));
7945
8817
  btn.title = option.description ?? "";
7946
8818
  return btn;
7947
8819
  };
@@ -7962,7 +8834,7 @@
7962
8834
  }
7963
8835
  };
7964
8836
  const textField = (key, opts) => {
7965
- const area = h9("textarea", "ez-acp-field");
8837
+ const area = h10("textarea", "ez-acp-field");
7966
8838
  area.rows = 1;
7967
8839
  if (opts.max !== void 0) area.maxLength = opts.max;
7968
8840
  if (opts.placeholder) area.placeholder = opts.placeholder;
@@ -7989,7 +8861,7 @@
7989
8861
  if (field.kind !== "select" && field.kind !== "multiselect") return row;
7990
8862
  const custom = field.custom;
7991
8863
  if (!custom) return row;
7992
- const group = h9("div", "ez-acp-group");
8864
+ const group = h10("div", "ez-acp-group");
7993
8865
  const box = textField(custom.key, {
7994
8866
  placeholder: field.kind === "multiselect" ? "\u5176\u4ED6\u7B54\u6848\uFF08\u9078\u586B\uFF09" : "\u81EA\u5DF1\u56DE\u7B54\uFF0C\u6216\u88DC\u5145\u8AAA\u660E\uFF08\u9078\u586B\uFF09",
7995
8867
  onInput: () => {
@@ -8001,7 +8873,7 @@
8001
8873
  };
8002
8874
  switch (field.kind) {
8003
8875
  case "select": {
8004
- const row = labelled(h9("div", "ez-acp-ask-options"), "radiogroup");
8876
+ const row = labelled(h10("div", "ez-acp-ask-options"), "radiogroup");
8005
8877
  const radios = field.options.map(radioButton);
8006
8878
  const clearPick = () => {
8007
8879
  values.delete(field.key);
@@ -8026,7 +8898,7 @@
8026
8898
  return group;
8027
8899
  }
8028
8900
  case "multiselect": {
8029
- const row = labelled(h9("div", "ez-acp-ask-options"), "group");
8901
+ const row = labelled(h10("div", "ez-acp-ask-options"), "group");
8030
8902
  const picked = new Set(field.default ?? []);
8031
8903
  const sync = () => {
8032
8904
  if (picked.size) values.set(field.key, [...picked]);
@@ -8055,7 +8927,7 @@
8055
8927
  });
8056
8928
  }
8057
8929
  case "boolean": {
8058
- const row = labelled(h9("div", "ez-acp-ask-options"), "radiogroup");
8930
+ const row = labelled(h10("div", "ez-acp-ask-options"), "radiogroup");
8059
8931
  const radios = [
8060
8932
  radioButton({ id: "true", name: "\u662F" }),
8061
8933
  radioButton({ id: "false", name: "\u5426" })
@@ -8073,7 +8945,7 @@
8073
8945
  return row;
8074
8946
  }
8075
8947
  case "number": {
8076
- const input = labelled(h9("input", "ez-acp-field ez-acp-field-number"));
8948
+ const input = labelled(h10("input", "ez-acp-field ez-acp-field-number"));
8077
8949
  input.type = "number";
8078
8950
  if (field.min !== void 0) input.min = String(field.min);
8079
8951
  if (field.max !== void 0) input.max = String(field.max);