@mulmoclaude/mulmoscript-plugin 4.3.0 → 4.4.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.
package/dist/vue.cjs CHANGED
@@ -119,22 +119,20 @@ function beatMayHaveMovie(beat) {
119
119
  function isBeatImageReference(beat) {
120
120
  return beat.image?.type === "beat";
121
121
  }
122
- /** Pure check: is every beat in the script a `slide`-typed beat?
123
- * When true, the View mounts `@mulmocast/beat-editor`'s
124
- * `BeatListEditor` instead of the per-beat list UI (#1575).
125
- * Empty / missing `beats[]` returns false there's nothing to edit
126
- * as a deck, fall through to the existing UI which renders an empty
127
- * state. Mixed scripts (any non-`slide` beat) also return false; that
128
- * case is deferred to a future phase. */
129
- function isAllSlideDeck(script) {
122
+ /** Whether the beat editor has anything to edit.
123
+ *
124
+ * Any beat type, not just `slide`: `@mulmocast/beat-editor` renders and edits all eight
125
+ * (`textSlide` / `markdown` / `chart` / `mermaid` / `image` / `movie` / `slide` /
126
+ * `html_tailwind`). The all-slide test this replaces was a limit of the OLD iframe deck
127
+ * editor, which only understood decks it stayed in place through the migration and kept a
128
+ * markdown script read-only for no reason.
129
+ *
130
+ * Empty / missing `beats[]` is false: there is nothing to edit, and the per-beat list already
131
+ * renders an empty state. */
132
+ function hasEditableBeats(script) {
130
133
  if (!isRecord(script)) return false;
131
134
  const { beats } = script;
132
- if (!Array.isArray(beats) || beats.length === 0) return false;
133
- return beats.every((beat) => {
134
- if (!isRecord(beat)) return false;
135
- const { image } = beat;
136
- return isRecord(image) && image.type === "slide";
137
- });
135
+ return Array.isArray(beats) && beats.length > 0;
138
136
  }
139
137
  /** Resolve the beat the View should render at `index`: the user's
140
138
  * in-place edit (`overrides`) wins over the on-disk beat, and an
@@ -608,7 +606,7 @@ var DECK_SAVE_DEBOUNCE_MS = 300;
608
606
  */
609
607
  var EDITOR_ORIGIN = `deck-editor-${Math.random().toString(36).slice(2)}`;
610
608
  function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
611
- const isDeck = (0, vue.computed)(() => isAllSlideDeck(effectiveScript.value));
609
+ const canEditBeats = (0, vue.computed)(() => hasEditableBeats(effectiveScript.value));
612
610
  const deckScriptInput = (0, vue.computed)(() => effectiveScript.value);
613
611
  let deckSaveTimer = null;
614
612
  let pendingDeckScript = null;
@@ -658,7 +656,7 @@ function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
658
656
  });
659
657
  }
660
658
  return {
661
- isDeck,
659
+ canEditBeats,
662
660
  deckScriptInput,
663
661
  onDeckUpdate,
664
662
  flushPendingDeckSave,
@@ -672,6 +670,8 @@ function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
672
670
  var useT = (0, gui_chat_protocol_vue.createUseT)({
673
671
  de: {
674
672
  beatCount: (count) => count === 1 ? `${count} Beat` : `${count} Beats`,
673
+ editTab: "Bearbeiten",
674
+ mediaTab: "Medien",
675
675
  movie: "Video",
676
676
  generating: "Wird generiert…",
677
677
  rendering: "Wird gerendert…",
@@ -704,6 +704,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
704
704
  },
705
705
  en: {
706
706
  beatCount: (count) => count === 1 ? `${count} beat` : `${count} beats`,
707
+ editTab: "Edit",
708
+ mediaTab: "Media",
707
709
  movie: "Movie",
708
710
  generating: "Generating…",
709
711
  rendering: "Rendering…",
@@ -736,6 +738,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
736
738
  },
737
739
  es: {
738
740
  beatCount: (count) => count === 1 ? `${count} beat` : `${count} beats`,
741
+ editTab: "Editar",
742
+ mediaTab: "Medios",
739
743
  movie: "Vídeo",
740
744
  generating: "Generando…",
741
745
  rendering: "Renderizando…",
@@ -768,6 +772,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
768
772
  },
769
773
  fr: {
770
774
  beatCount: (count) => count === 1 ? `${count} beat` : `${count} beats`,
775
+ editTab: "Éditer",
776
+ mediaTab: "Médias",
771
777
  movie: "Film",
772
778
  generating: "Génération…",
773
779
  rendering: "Rendu…",
@@ -800,6 +806,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
800
806
  },
801
807
  ja: {
802
808
  beatCount: (count) => `${count} ビート`,
809
+ editTab: "編集",
810
+ mediaTab: "メディア",
803
811
  movie: "動画",
804
812
  generating: "生成中…",
805
813
  rendering: "レンダリング中…",
@@ -832,6 +840,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
832
840
  },
833
841
  ko: {
834
842
  beatCount: (count) => `${count}개 비트`,
843
+ editTab: "편집",
844
+ mediaTab: "미디어",
835
845
  movie: "영상",
836
846
  generating: "생성 중…",
837
847
  rendering: "렌더링 중…",
@@ -864,6 +874,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
864
874
  },
865
875
  "pt-BR": {
866
876
  beatCount: (count) => count === 1 ? `${count} beat` : `${count} beats`,
877
+ editTab: "Editar",
878
+ mediaTab: "Mídia",
867
879
  movie: "Vídeo",
868
880
  generating: "Gerando…",
869
881
  rendering: "Renderizando…",
@@ -896,6 +908,8 @@ var useT = (0, gui_chat_protocol_vue.createUseT)({
896
908
  },
897
909
  zh: {
898
910
  beatCount: (count) => `${count} 个 beat`,
911
+ editTab: "编辑",
912
+ mediaTab: "媒体",
899
913
  movie: "视频",
900
914
  generating: "生成中…",
901
915
  rendering: "渲染中…",
@@ -1325,115 +1339,120 @@ var _hoisted_10 = { class: "flex-1 min-w-0" };
1325
1339
  var _hoisted_11 = { class: "font-medium" };
1326
1340
  var _hoisted_12 = { class: "break-words whitespace-pre-wrap mt-0.5" };
1327
1341
  var _hoisted_13 = ["disabled"];
1328
- var _hoisted_14 = { class: "flex gap-3 items-stretch" };
1329
- var _hoisted_15 = [
1342
+ var _hoisted_14 = {
1343
+ key: 2,
1344
+ class: "flex shrink-0 gap-1 px-2 pt-1 text-[11px]"
1345
+ };
1346
+ var _hoisted_15 = { class: "flex gap-3 items-stretch" };
1347
+ var _hoisted_16 = [
1330
1348
  "onDragover",
1331
1349
  "onDragleave",
1332
1350
  "onDrop"
1333
1351
  ];
1334
- var _hoisted_16 = ["data-testid"];
1335
- var _hoisted_17 = ["src", "data-testid"];
1336
- var _hoisted_18 = [
1352
+ var _hoisted_17 = ["data-testid"];
1353
+ var _hoisted_18 = ["src", "data-testid"];
1354
+ var _hoisted_19 = [
1337
1355
  "title",
1338
1356
  "aria-label",
1339
1357
  "data-testid",
1340
1358
  "onClick"
1341
1359
  ];
1342
- var _hoisted_19 = [
1360
+ var _hoisted_20 = [
1343
1361
  "src",
1344
1362
  "alt",
1345
1363
  "onClick"
1346
1364
  ];
1347
- var _hoisted_20 = [
1365
+ var _hoisted_21 = [
1348
1366
  "title",
1349
1367
  "aria-label",
1350
1368
  "data-testid",
1351
1369
  "onClick"
1352
1370
  ];
1353
- var _hoisted_21 = {
1371
+ var _hoisted_22 = {
1354
1372
  key: 0,
1355
1373
  class: "animate-spin w-5 h-5",
1356
1374
  viewBox: "0 0 24 24",
1357
1375
  fill: "none"
1358
1376
  };
1359
- var _hoisted_22 = {
1377
+ var _hoisted_23 = {
1360
1378
  key: 1,
1361
1379
  class: "material-icons text-3xl"
1362
1380
  };
1363
- var _hoisted_23 = ["disabled", "onClick"];
1364
- var _hoisted_24 = {
1381
+ var _hoisted_24 = ["disabled", "onClick"];
1382
+ var _hoisted_25 = {
1365
1383
  key: 3,
1366
1384
  class: "w-full aspect-video flex flex-col items-center justify-center gap-1 p-2"
1367
1385
  };
1368
- var _hoisted_25 = { class: "text-xs text-green-500" };
1369
- var _hoisted_26 = {
1386
+ var _hoisted_26 = { class: "text-xs text-green-500" };
1387
+ var _hoisted_27 = {
1370
1388
  key: 1,
1371
1389
  class: "text-xs text-red-400 text-center"
1372
1390
  };
1373
- var _hoisted_27 = {
1391
+ var _hoisted_28 = {
1374
1392
  key: 0,
1375
1393
  class: "text-xs text-gray-400 text-center italic leading-relaxed px-1"
1376
1394
  };
1377
- var _hoisted_28 = {
1395
+ var _hoisted_29 = {
1378
1396
  key: 1,
1379
1397
  class: "text-xs text-gray-300"
1380
1398
  };
1381
- var _hoisted_29 = {
1399
+ var _hoisted_30 = {
1382
1400
  key: 2,
1383
1401
  class: "absolute inset-0 flex items-center justify-center bg-blue-50/80 pointer-events-none"
1384
1402
  };
1385
- var _hoisted_30 = { class: "text-xs text-blue-500 font-medium" };
1386
- var _hoisted_31 = {
1403
+ var _hoisted_31 = { class: "text-xs text-blue-500 font-medium" };
1404
+ var _hoisted_32 = {
1387
1405
  key: 3,
1388
1406
  class: "absolute bottom-0 inset-x-0 text-center text-xs text-gray-400 bg-white/70 py-0.5 pointer-events-none"
1389
1407
  };
1390
- var _hoisted_32 = ["onClick"];
1391
- var _hoisted_33 = { class: "flex flex-col flex-1 min-w-0 px-2 py-1.5" };
1392
- var _hoisted_34 = { class: "text-sm text-gray-800 leading-relaxed" };
1393
- var _hoisted_35 = { class: "flex justify-between mt-auto pt-1" };
1394
- var _hoisted_36 = { class: "flex items-center gap-1" };
1395
- var _hoisted_37 = {
1408
+ var _hoisted_33 = ["onClick"];
1409
+ var _hoisted_34 = { class: "flex flex-col flex-1 min-w-0 px-2 py-1.5" };
1410
+ var _hoisted_35 = { class: "text-sm text-gray-800 leading-relaxed" };
1411
+ var _hoisted_36 = { class: "flex justify-between mt-auto pt-1" };
1412
+ var _hoisted_37 = { class: "flex items-center gap-1" };
1413
+ var _hoisted_38 = {
1396
1414
  key: 0,
1397
1415
  class: "animate-spin w-3 h-3 text-green-400",
1398
1416
  viewBox: "0 0 24 24",
1399
1417
  fill: "none"
1400
1418
  };
1401
- var _hoisted_38 = ["onClick"];
1402
- var _hoisted_39 = ["title"];
1403
- var _hoisted_40 = ["disabled", "onClick"];
1404
- var _hoisted_41 = ["onClick"];
1405
- var _hoisted_42 = [
1419
+ var _hoisted_39 = ["onClick"];
1420
+ var _hoisted_40 = ["title"];
1421
+ var _hoisted_41 = ["disabled", "onClick"];
1422
+ var _hoisted_42 = ["onClick"];
1423
+ var _hoisted_43 = [
1406
1424
  "title",
1407
1425
  "data-testid",
1408
1426
  "onClick"
1409
1427
  ];
1410
- var _hoisted_43 = {
1428
+ var _hoisted_44 = {
1411
1429
  key: 0,
1412
1430
  class: "border-t border-gray-100"
1413
1431
  };
1414
- var _hoisted_44 = ["onUpdate:modelValue", "data-testid"];
1415
- var _hoisted_45 = { class: "flex items-center justify-end gap-2 px-2 pb-2" };
1416
- var _hoisted_46 = {
1432
+ var _hoisted_45 = ["onUpdate:modelValue", "data-testid"];
1433
+ var _hoisted_46 = { class: "flex items-center justify-end gap-2 px-2 pb-2" };
1434
+ var _hoisted_47 = {
1417
1435
  key: 0,
1418
1436
  class: "text-xs text-red-600",
1419
1437
  role: "alert"
1420
1438
  };
1421
- var _hoisted_47 = [
1439
+ var _hoisted_48 = [
1422
1440
  "disabled",
1423
1441
  "data-testid",
1424
1442
  "onClick"
1425
1443
  ];
1426
- var _hoisted_48 = {
1444
+ var _hoisted_49 = {
1427
1445
  key: 0,
1428
1446
  class: "flex items-center justify-center h-32 text-gray-400 text-sm"
1429
1447
  };
1430
- var _hoisted_49 = { class: "bottom-bar-wrapper" };
1431
- var _hoisted_50 = { class: "editor-actions" };
1432
- var _hoisted_51 = ["disabled"];
1433
- var _hoisted_52 = ["title"];
1434
- var _hoisted_53 = { class: "material-icons" };
1448
+ var _hoisted_50 = { class: "bottom-bar-wrapper" };
1449
+ var _hoisted_51 = { class: "editor-actions" };
1450
+ var _hoisted_52 = ["disabled"];
1451
+ var _hoisted_53 = ["title"];
1452
+ var _hoisted_54 = { class: "material-icons" };
1435
1453
  var SILENT_BEAT_DEFAULT_SEC = 3;
1436
1454
  var MS_PER_SECOND = 1e3;
1455
+ var BEAT_TAB_BASE = "rounded px-2 py-0.5 font-sans";
1437
1456
  var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.defineComponent)({
1438
1457
  __name: "View",
1439
1458
  props: { selectedResult: {} },
@@ -1600,12 +1619,27 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
1600
1619
  }
1601
1620
  });
1602
1621
  }
1603
- const { isDeck, deckScriptInput, onDeckUpdate, flushPendingDeckSave, watchForeignWrites } = useDeckEditor({
1622
+ const { canEditBeats, deckScriptInput, onDeckUpdate, flushPendingDeckSave, watchForeignWrites } = useDeckEditor({
1604
1623
  api,
1605
1624
  filePath,
1606
1625
  effectiveScript,
1607
1626
  commitScript
1608
1627
  });
1628
+ /**
1629
+ * Which pane the beats are shown in.
1630
+ *
1631
+ * `edit` is the beat editor — every beat type, edited in place. `media` is the per-beat list,
1632
+ * which is the only place audio / image / movie generation lives. A script with nothing to edit
1633
+ * has only the list, so the switch is hidden and this is ignored.
1634
+ *
1635
+ * `media` is the default because opening the script is what triggers rendering each beat's
1636
+ * image: the auto-render on mount lives in that list, so defaulting to `edit` silently stopped
1637
+ * thumbnails from being produced at all. Someone who wants to edit clicks once; nobody has to
1638
+ * click to get the previews they always got.
1639
+ */
1640
+ const beatPane = (0, vue.ref)("media");
1641
+ const showBeatEditor = (0, vue.computed)(() => canEditBeats.value && beatPane.value === "edit");
1642
+ const beatPaneTabClass = (active) => [BEAT_TAB_BASE, active ? "bg-gray-700 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"];
1609
1643
  const unsubscribeForeignWrites = watchForeignWrites(() => {
1610
1644
  refreshScriptFromDisk();
1611
1645
  });
@@ -2062,7 +2096,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2062
2096
  "onDownloadPdf"
2063
2097
  ])]),
2064
2098
  (0, vue.unref)(movieError) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_9, [
2065
- _cache[3] || (_cache[3] = (0, vue.createElementVNode)("span", { class: "material-icons text-base shrink-0 mt-px" }, "error_outline", -1)),
2099
+ _cache[5] || (_cache[5] = (0, vue.createElementVNode)("span", { class: "material-icons text-base shrink-0 mt-px" }, "error_outline", -1)),
2066
2100
  (0, vue.createElementVNode)("div", _hoisted_10, [(0, vue.createElementVNode)("div", _hoisted_11, (0, vue.toDisplayString)((0, vue.unref)(m).movieGenerationFailed), 1), (0, vue.createElementVNode)("div", _hoisted_12, (0, vue.toDisplayString)((0, vue.unref)(movieError)), 1)]),
2067
2101
  (0, vue.createElementVNode)("button", {
2068
2102
  class: "shrink-0 h-7 px-2 text-xs rounded border border-red-300 text-red-700 hover:bg-red-100 disabled:opacity-50",
@@ -2102,8 +2136,19 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2102
2136
  "onCharDrop",
2103
2137
  "onRenderCharacter"
2104
2138
  ])) : (0, vue.createCommentVNode)("", true),
2105
- (0, vue.unref)(isDeck) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2106
- key: 2,
2139
+ (0, vue.unref)(canEditBeats) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_14, [(0, vue.createElementVNode)("button", {
2140
+ type: "button",
2141
+ class: (0, vue.normalizeClass)(beatPaneTabClass(beatPane.value === "edit")),
2142
+ "data-testid": "mulmo-script-tab-edit",
2143
+ onClick: _cache[1] || (_cache[1] = ($event) => beatPane.value = "edit")
2144
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).editTab), 3), (0, vue.createElementVNode)("button", {
2145
+ type: "button",
2146
+ class: (0, vue.normalizeClass)(beatPaneTabClass(beatPane.value === "media")),
2147
+ "data-testid": "mulmo-script-tab-media",
2148
+ onClick: _cache[2] || (_cache[2] = ($event) => beatPane.value = "media")
2149
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).mediaTab), 3)])) : (0, vue.createCommentVNode)("", true),
2150
+ showBeatEditor.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2151
+ key: 3,
2107
2152
  class: "flex-1 overflow-hidden",
2108
2153
  "data-testid": "mulmo-script-deck-editor",
2109
2154
  onFocusout: onDeckFocusOut
@@ -2111,7 +2156,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2111
2156
  beats: deckBeats.value,
2112
2157
  "onUpdate:beats": onDeckBeatsUpdate
2113
2158
  }, null, 8, ["beats"])], 32)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2114
- key: 3,
2159
+ key: 4,
2115
2160
  ref_key: "beatListEl",
2116
2161
  ref: beatListEl,
2117
2162
  class: "flex-1 overflow-y-auto p-2 space-y-1.5"
@@ -2119,7 +2164,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2119
2164
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2120
2165
  key: index,
2121
2166
  class: "rounded-lg border border-gray-200 overflow-hidden"
2122
- }, [(0, vue.createElementVNode)("div", _hoisted_14, [(0, vue.createElementVNode)("div", {
2167
+ }, [(0, vue.createElementVNode)("div", _hoisted_15, [(0, vue.createElementVNode)("div", {
2123
2168
  class: (0, vue.normalizeClass)(["relative shrink-0 w-[45%] overflow-hidden bg-gray-50 transition-colors", beatDragOver[index] ? "bg-blue-50" : ""]),
2124
2169
  onDragover: ($event) => onBeatDragOver($event, index),
2125
2170
  onDragleave: ($event) => onBeatDragLeave(index),
@@ -2128,27 +2173,27 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2128
2173
  (0, vue.createElementVNode)("div", {
2129
2174
  class: "absolute top-1.5 left-1.5 z-10 px-1.5 py-0.5 rounded bg-black/55 text-white text-xs font-medium leading-none pointer-events-none",
2130
2175
  "data-testid": `mulmo-script-beat-number-${index}`
2131
- }, (0, vue.toDisplayString)(index + 1), 9, _hoisted_16),
2176
+ }, (0, vue.toDisplayString)(index + 1), 9, _hoisted_17),
2132
2177
  (0, vue.unref)(beatMovieOpen)[index] && (0, vue.unref)(beatMovieUrls)[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createElementVNode)("video", {
2133
2178
  src: (0, vue.unref)(beatMovieUrls)[index],
2134
2179
  class: "w-full object-contain",
2135
2180
  controls: "",
2136
2181
  autoplay: "",
2137
2182
  "data-testid": `mulmo-script-beat-movie-player-${index}`
2138
- }, null, 8, _hoisted_17), (0, vue.createElementVNode)("button", {
2183
+ }, null, 8, _hoisted_18), (0, vue.createElementVNode)("button", {
2139
2184
  class: "absolute top-1.5 right-1.5 flex items-center justify-center w-6 h-6 rounded border border-gray-400 text-gray-600 bg-white hover:bg-gray-50",
2140
2185
  title: (0, vue.unref)(m).close,
2141
2186
  "aria-label": (0, vue.unref)(m).close,
2142
2187
  "data-testid": `mulmo-script-beat-movie-close-${index}`,
2143
2188
  onClick: (0, vue.withModifiers)(($event) => (0, vue.unref)(closeBeatMovie)(index), ["stop"])
2144
- }, [..._cache[4] || (_cache[4] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_18)], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
2189
+ }, [..._cache[6] || (_cache[6] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_19)], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
2145
2190
  renderedImages[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
2146
2191
  key: 0,
2147
2192
  src: renderedImages[index],
2148
2193
  class: "w-full object-contain cursor-zoom-in",
2149
2194
  alt: `Beat ${index + 1}`,
2150
2195
  onClick: ($event) => openLightbox(index)
2151
- }, null, 8, _hoisted_19)) : (0, vue.createCommentVNode)("", true),
2196
+ }, null, 8, _hoisted_20)) : (0, vue.createCommentVNode)("", true),
2152
2197
  renderedImages[index] && (0, vue.unref)(beatMovies)[index] && canFetchMedia.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2153
2198
  key: 1,
2154
2199
  class: "absolute inset-0 m-auto w-12 h-12 flex items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70",
@@ -2156,7 +2201,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2156
2201
  "aria-label": (0, vue.unref)(m).play,
2157
2202
  "data-testid": `mulmo-script-beat-movie-play-${index}`,
2158
2203
  onClick: (0, vue.withModifiers)(($event) => (0, vue.unref)(playBeatMovie)(index), ["stop"])
2159
- }, [(0, vue.unref)(beatMovieLoading)[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("svg", _hoisted_21, [..._cache[5] || (_cache[5] = [(0, vue.createElementVNode)("circle", {
2204
+ }, [(0, vue.unref)(beatMovieLoading)[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("svg", _hoisted_22, [..._cache[7] || (_cache[7] = [(0, vue.createElementVNode)("circle", {
2160
2205
  class: "opacity-25",
2161
2206
  cx: "12",
2162
2207
  cy: "12",
@@ -2167,13 +2212,13 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2167
2212
  class: "opacity-75",
2168
2213
  fill: "currentColor",
2169
2214
  d: "M4 12a8 8 0 018-8v8H4z"
2170
- }, null, -1)])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_22, "play_arrow"))], 8, _hoisted_20)) : (0, vue.createCommentVNode)("", true),
2215
+ }, null, -1)])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_23, "play_arrow"))], 8, _hoisted_21)) : (0, vue.createCommentVNode)("", true),
2171
2216
  renderedImages[index] && renderState[index] !== "rendering" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2172
2217
  key: 2,
2173
2218
  class: "absolute top-1.5 right-1.5 flex items-center gap-1 px-2 py-0.5 text-xs rounded border border-gray-400 text-gray-600 bg-white hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed",
2174
2219
  disabled: (0, vue.unref)(movieGenerating),
2175
2220
  onClick: (0, vue.withModifiers)(($event) => regenerateBeat(index), ["stop"])
2176
- }, " ↺ ", 8, _hoisted_23)) : !renderedImages[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_24, [renderState[index] === "rendering" || (0, vue.unref)(movieGenerating) && !renderedImages[index] && effectiveBeat$1(index).imagePrompt ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [_cache[6] || (_cache[6] = (0, vue.createElementVNode)("svg", {
2221
+ }, " ↺ ", 8, _hoisted_24)) : !renderedImages[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_25, [renderState[index] === "rendering" || (0, vue.unref)(movieGenerating) && !renderedImages[index] && effectiveBeat$1(index).imagePrompt ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [_cache[8] || (_cache[8] = (0, vue.createElementVNode)("svg", {
2177
2222
  class: "animate-spin w-4 h-4 text-green-400",
2178
2223
  viewBox: "0 0 24 24",
2179
2224
  fill: "none"
@@ -2188,15 +2233,15 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2188
2233
  class: "opacity-75",
2189
2234
  fill: "currentColor",
2190
2235
  d: "M4 12a8 8 0 018-8v8H4z"
2191
- })], -1)), (0, vue.createElementVNode)("span", _hoisted_25, (0, vue.toDisplayString)((0, vue.unref)(m).rendering), 1)], 64)) : renderState[index] === "error" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_26, (0, vue.toDisplayString)(renderErrors[index]), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [effectiveBeat$1(index).imagePrompt ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_27, (0, vue.toDisplayString)(effectiveBeat$1(index).imagePrompt), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_28, (0, vue.toDisplayString)(beat.image?.type ?? "—"), 1))], 64))])) : (0, vue.createCommentVNode)("", true)
2236
+ })], -1)), (0, vue.createElementVNode)("span", _hoisted_26, (0, vue.toDisplayString)((0, vue.unref)(m).rendering), 1)], 64)) : renderState[index] === "error" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_27, (0, vue.toDisplayString)(renderErrors[index]), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [effectiveBeat$1(index).imagePrompt ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_28, (0, vue.toDisplayString)(effectiveBeat$1(index).imagePrompt), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_29, (0, vue.toDisplayString)(beat.image?.type ?? "—"), 1))], 64))])) : (0, vue.createCommentVNode)("", true)
2192
2237
  ], 64)),
2193
- beatDragOver[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_29, [(0, vue.createElementVNode)("span", _hoisted_30, (0, vue.toDisplayString)((0, vue.unref)(m).drop), 1)])) : !renderedImages[index] && renderState[index] !== "rendering" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_31, (0, vue.toDisplayString)((0, vue.unref)(m).orDropImage), 1)) : (0, vue.createCommentVNode)("", true),
2238
+ beatDragOver[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_30, [(0, vue.createElementVNode)("span", _hoisted_31, (0, vue.toDisplayString)((0, vue.unref)(m).drop), 1)])) : !renderedImages[index] && renderState[index] !== "rendering" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_32, (0, vue.toDisplayString)((0, vue.unref)(m).orDropImage), 1)) : (0, vue.createCommentVNode)("", true),
2194
2239
  !renderedImages[index] && renderState[index] !== "rendering" && !(0, vue.unref)(movieGenerating) && !(0, vue.unref)(isBeatImageReference)(effectiveBeat$1(index)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2195
2240
  key: 4,
2196
2241
  class: "absolute top-1.5 right-1.5 flex items-center gap-1 px-2 py-0.5 text-xs rounded border border-blue-400 text-blue-600 bg-white hover:bg-blue-50",
2197
2242
  onClick: ($event) => renderBeat(index)
2198
- }, (0, vue.toDisplayString)((0, vue.unref)(m).generate), 9, _hoisted_32)) : (0, vue.createCommentVNode)("", true)
2199
- ], 42, _hoisted_15), (0, vue.createElementVNode)("div", _hoisted_33, [(0, vue.createElementVNode)("span", _hoisted_34, (0, vue.toDisplayString)(effectiveBeat$1(index).text), 1), (0, vue.createElementVNode)("div", _hoisted_35, [(0, vue.createElementVNode)("div", _hoisted_36, [audioState[index] === "generating" || (0, vue.unref)(movieGenerating) && !beatAudios[index] && effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("svg", _hoisted_37, [..._cache[7] || (_cache[7] = [(0, vue.createElementVNode)("circle", {
2243
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).generate), 9, _hoisted_33)) : (0, vue.createCommentVNode)("", true)
2244
+ ], 42, _hoisted_16), (0, vue.createElementVNode)("div", _hoisted_34, [(0, vue.createElementVNode)("span", _hoisted_35, (0, vue.toDisplayString)(effectiveBeat$1(index).text), 1), (0, vue.createElementVNode)("div", _hoisted_36, [(0, vue.createElementVNode)("div", _hoisted_37, [audioState[index] === "generating" || (0, vue.unref)(movieGenerating) && !beatAudios[index] && effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("svg", _hoisted_38, [..._cache[9] || (_cache[9] = [(0, vue.createElementVNode)("circle", {
2200
2245
  class: "opacity-25",
2201
2246
  cx: "12",
2202
2247
  cy: "12",
@@ -2211,24 +2256,24 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2211
2256
  key: 1,
2212
2257
  class: (0, vue.normalizeClass)(["text-xs px-2 py-0.5 rounded border", playingAudio.value?.index === index ? "border-red-400 text-red-600 hover:bg-red-50" : "border-green-400 text-green-600 hover:bg-green-50"]),
2213
2258
  onClick: ($event) => playAudio(index)
2214
- }, (0, vue.toDisplayString)(playingAudio.value?.index === index ? (0, vue.unref)(m).stop : (0, vue.unref)(m).play), 11, _hoisted_38)) : audioErrors[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [(0, vue.createElementVNode)("span", {
2259
+ }, (0, vue.toDisplayString)(playingAudio.value?.index === index ? (0, vue.unref)(m).stop : (0, vue.unref)(m).play), 11, _hoisted_39)) : audioErrors[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [(0, vue.createElementVNode)("span", {
2215
2260
  class: "text-xs text-red-400 truncate min-w-0 max-w-[20rem]",
2216
2261
  title: audioErrors[index]
2217
- }, (0, vue.toDisplayString)((0, vue.unref)(m).errPrefix) + " " + (0, vue.toDisplayString)(audioErrors[index]), 9, _hoisted_39), effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2262
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).errPrefix) + " " + (0, vue.toDisplayString)(audioErrors[index]), 9, _hoisted_40), effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2218
2263
  key: 0,
2219
2264
  class: "text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50 disabled:opacity-50",
2220
2265
  disabled: (0, vue.unref)(movieGenerating),
2221
2266
  onClick: ($event) => generateAudio(index)
2222
- }, " ↺ ", 8, _hoisted_40)) : (0, vue.createCommentVNode)("", true)], 64)) : effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2267
+ }, " ↺ ", 8, _hoisted_41)) : (0, vue.createCommentVNode)("", true)], 64)) : effectiveBeat$1(index).text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2223
2268
  key: 3,
2224
2269
  class: "text-xs px-2 py-0.5 rounded border border-gray-300 text-gray-500 hover:bg-gray-50",
2225
2270
  onClick: ($event) => generateAudio(index)
2226
- }, (0, vue.toDisplayString)((0, vue.unref)(m).generateAudio), 9, _hoisted_41)) : (0, vue.createCommentVNode)("", true)]), (0, vue.createElementVNode)("button", {
2271
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).generateAudio), 9, _hoisted_42)) : (0, vue.createCommentVNode)("", true)]), (0, vue.createElementVNode)("button", {
2227
2272
  class: "text-gray-400 hover:text-gray-600",
2228
2273
  title: sourceOpen[index] ? "Hide source" : "Show source",
2229
2274
  "data-testid": `mulmo-script-beat-source-toggle-${index}`,
2230
2275
  onClick: ($event) => toggleSource(index)
2231
- }, [..._cache[8] || (_cache[8] = [(0, vue.createElementVNode)("svg", {
2276
+ }, [..._cache[10] || (_cache[10] = [(0, vue.createElementVNode)("svg", {
2232
2277
  xmlns: "http://www.w3.org/2000/svg",
2233
2278
  class: "w-3.5 h-3.5",
2234
2279
  viewBox: "0 0 24 24",
@@ -2237,36 +2282,36 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2237
2282
  "stroke-width": "2",
2238
2283
  "stroke-linecap": "round",
2239
2284
  "stroke-linejoin": "round"
2240
- }, [(0, vue.createElementVNode)("polyline", { points: "16 18 22 12 16 6" }), (0, vue.createElementVNode)("polyline", { points: "8 6 2 12 8 18" })], -1)])], 8, _hoisted_42)])])]), sourceOpen[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_43, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
2285
+ }, [(0, vue.createElementVNode)("polyline", { points: "16 18 22 12 16 6" }), (0, vue.createElementVNode)("polyline", { points: "8 6 2 12 8 18" })], -1)])], 8, _hoisted_43)])])]), sourceOpen[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_44, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
2241
2286
  "onUpdate:modelValue": ($event) => sourceText[index] = $event,
2242
2287
  class: (0, vue.normalizeClass)(["w-full text-xs text-gray-600 bg-gray-50 p-2 font-mono resize-none", isValidBeat$1(index) ? "outline-none" : "outline outline-2 outline-red-400"]),
2243
2288
  rows: "8",
2244
2289
  spellcheck: "false",
2245
2290
  "data-testid": `mulmo-script-beat-source-textarea-${index}`
2246
- }, null, 10, _hoisted_44), [[vue.vModelText, sourceText[index]]]), (0, vue.createElementVNode)("div", _hoisted_45, [beatSaveErrors[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_46, (0, vue.toDisplayString)(beatSaveErrors[index].kind === "invalidJson" ? (0, vue.unref)(m).saveErrorInvalidJson(beatSaveErrors[index].error) : (0, vue.unref)(m).saveErrorSaveFailed(beatSaveErrors[index].error)), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("button", {
2291
+ }, null, 10, _hoisted_45), [[vue.vModelText, sourceText[index]]]), (0, vue.createElementVNode)("div", _hoisted_46, [beatSaveErrors[index] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_47, (0, vue.toDisplayString)(beatSaveErrors[index].kind === "invalidJson" ? (0, vue.unref)(m).saveErrorInvalidJson(beatSaveErrors[index].error) : (0, vue.unref)(m).saveErrorSaveFailed(beatSaveErrors[index].error)), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("button", {
2247
2292
  class: (0, vue.normalizeClass)(["px-2 py-1 text-xs rounded border", isValidBeat$1(index) && !beatSaving[index] ? "border-blue-400 text-blue-600 hover:bg-blue-50 cursor-pointer" : "border-gray-200 text-gray-300 cursor-not-allowed"]),
2248
2293
  disabled: !isValidBeat$1(index) || !!beatSaving[index],
2249
2294
  "data-testid": `mulmo-script-beat-update-button-${index}`,
2250
2295
  onClick: ($event) => updateBeat(index)
2251
- }, (0, vue.toDisplayString)(beatSaving[index] ? (0, vue.unref)(m).saving : (0, vue.unref)(m).update), 11, _hoisted_47)])])) : (0, vue.createCommentVNode)("", true)]);
2252
- }), 128)), beats.value.length === 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, (0, vue.toDisplayString)((0, vue.unref)(m).noBeats), 1)) : (0, vue.createCommentVNode)("", true)], 512)),
2253
- (0, vue.createElementVNode)("div", _hoisted_49, [(0, vue.createElementVNode)("details", {
2296
+ }, (0, vue.toDisplayString)(beatSaving[index] ? (0, vue.unref)(m).saving : (0, vue.unref)(m).update), 11, _hoisted_48)])])) : (0, vue.createCommentVNode)("", true)]);
2297
+ }), 128)), beats.value.length === 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_49, (0, vue.toDisplayString)((0, vue.unref)(m).noBeats), 1)) : (0, vue.createCommentVNode)("", true)], 512)),
2298
+ (0, vue.createElementVNode)("div", _hoisted_50, [(0, vue.createElementVNode)("details", {
2254
2299
  ref_key: "sourceDetails",
2255
2300
  ref: sourceDetails,
2256
2301
  class: "script-source",
2257
- onToggle: _cache[2] || (_cache[2] = ($event) => onSourceToggle($event.target.open))
2302
+ onToggle: _cache[4] || (_cache[4] = ($event) => onSourceToggle($event.target.open))
2258
2303
  }, [
2259
2304
  (0, vue.createElementVNode)("summary", null, (0, vue.toDisplayString)((0, vue.unref)(m).editSource), 1),
2260
2305
  (0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
2261
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => editableSource.value = $event),
2306
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => editableSource.value = $event),
2262
2307
  class: (0, vue.normalizeClass)(["script-editor", { "script-editor-invalid": sourceChanged.value && !sourceValid.value }]),
2263
2308
  spellcheck: "false"
2264
2309
  }, null, 2), [[vue.vModelText, editableSource.value]]),
2265
- (0, vue.createElementVNode)("div", _hoisted_50, [(0, vue.createElementVNode)("button", {
2310
+ (0, vue.createElementVNode)("div", _hoisted_51, [(0, vue.createElementVNode)("button", {
2266
2311
  class: "apply-btn",
2267
2312
  disabled: !sourceChanged.value || !sourceValid.value,
2268
2313
  onClick: applySource
2269
- }, (0, vue.toDisplayString)((0, vue.unref)(m).applyChanges), 9, _hoisted_51), (0, vue.createElementVNode)("button", {
2314
+ }, (0, vue.toDisplayString)((0, vue.unref)(m).applyChanges), 9, _hoisted_52), (0, vue.createElementVNode)("button", {
2270
2315
  class: "cancel-btn",
2271
2316
  onClick: cancelSourceEdit
2272
2317
  }, (0, vue.toDisplayString)((0, vue.unref)(m).cancel), 1)])
@@ -2274,9 +2319,9 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.def
2274
2319
  class: "copy-btn",
2275
2320
  title: (0, vue.unref)(copied) ? "Copied!" : "Copy",
2276
2321
  onClick: copyText
2277
- }, [(0, vue.createElementVNode)("span", _hoisted_53, (0, vue.toDisplayString)((0, vue.unref)(copied) ? "check" : "content_copy"), 1)], 8, _hoisted_52), [[vue.vShow, !editing.value]])]),
2322
+ }, [(0, vue.createElementVNode)("span", _hoisted_54, (0, vue.toDisplayString)((0, vue.unref)(copied) ? "check" : "content_copy"), 1)], 8, _hoisted_53), [[vue.vShow, !editing.value]])]),
2278
2323
  lightbox.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(BeatLightbox_default, {
2279
- key: 4,
2324
+ key: 5,
2280
2325
  lightbox: lightbox.value,
2281
2326
  "beat-count": beats.value.length,
2282
2327
  "beat-texts": beatTexts.value,
@@ -2312,7 +2357,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
2312
2357
  };
2313
2358
  //#endregion
2314
2359
  //#region src/vue/View.vue
2315
- var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-d78be72d"]]);
2360
+ var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-f9957eaf"]]);
2316
2361
  //#endregion
2317
2362
  //#region src/vue/Preview.vue?vue&type=script&setup=true&lang.ts
2318
2363
  var _hoisted_1 = {