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