@mulmoclaude/mulmoscript-plugin 3.1.0 → 4.1.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
@@ -1,7 +1,8 @@
1
1
  import { a as pluginCore, d as errorMessage, f as TOOL_DEFINITION, p as TOOL_NAME } from "./plugin-CXhOP_xU.js";
2
- import { t as GENERATION_EVENT } from "./contract-vY0niHkh.js";
2
+ import { n as SCRIPT_CHANGED_EVENT, r as shouldReloadForScriptChange, t as GENERATION_EVENT } from "./contract-CIt1ZAtt.js";
3
3
  import { mulmoBeatSchema, mulmoScriptSchema } from "@mulmocast/types";
4
4
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineAsyncComponent, defineComponent, inject, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, reactive, ref, renderList, toDisplayString, unref, vModelText, vShow, watch, withDirectives, withModifiers } from "vue";
5
+ import { beatsOf, withBeats } from "@mulmocast/beat-editor";
5
6
  import { createUseT, useRuntime } from "gui-chat-protocol/vue";
6
7
  //#region src/vue/support.ts
7
8
  function isRecord(value) {
@@ -115,8 +116,8 @@ function isBeatImageReference(beat) {
115
116
  return beat.image?.type === "beat";
116
117
  }
117
118
  /** Pure check: is every beat in the script a `slide`-typed beat?
118
- * When true, the View mounts `@mulmocast/deck-web`'s
119
- * `MulmoScriptDeckEditor` instead of the per-beat list UI (#1575).
119
+ * When true, the View mounts `@mulmocast/beat-editor`'s
120
+ * `BeatListEditor` instead of the per-beat list UI (#1575).
120
121
  * Empty / missing `beats[]` returns false — there's nothing to edit
121
122
  * as a deck, fall through to the existing UI which renders an empty
122
123
  * state. Mixed scripts (any non-`slide` beat) also return false; that
@@ -208,6 +209,15 @@ var GENERATION_EVENT_KINDS = /* @__PURE__ */ new Set([
208
209
  "movie",
209
210
  "pdf"
210
211
  ]);
212
+ function parseScriptChangedEvent(payload) {
213
+ if (!isRecord(payload)) return null;
214
+ const { filePath, origin } = payload;
215
+ if (typeof filePath !== "string") return null;
216
+ return {
217
+ filePath,
218
+ ...typeof origin === "string" ? { origin } : {}
219
+ };
220
+ }
211
221
  function parseGenerationEvent(payload) {
212
222
  if (!isRecord(payload)) return null;
213
223
  const { kind, filePath, key, done, error } = payload;
@@ -254,9 +264,25 @@ function useMulmoScriptTransport() {
254
264
  handler(event);
255
265
  });
256
266
  }
267
+ /**
268
+ * A write to this script landed — reload from disk.
269
+ *
270
+ * `ownOrigin` is this View's id. Its own writes echo back on the same channel, and acting
271
+ * on them would rebuild the element the caret is in on every keystroke, so they are dropped
272
+ * here. A write from the agent carries no origin and always reaches the handler.
273
+ */
274
+ function onScriptChanged(filePath, ownOrigin, handler) {
275
+ return runtime.pubsub.subscribe(SCRIPT_CHANGED_EVENT, (payload) => {
276
+ const event = parseScriptChangedEvent(payload);
277
+ if (!event) return;
278
+ if (!shouldReloadForScriptChange(event, filePath(), ownOrigin)) return;
279
+ handler();
280
+ });
281
+ }
257
282
  return {
258
283
  call,
259
- onGenerationEvent
284
+ onGenerationEvent,
285
+ onScriptChanged
260
286
  };
261
287
  }
262
288
  //#endregion
@@ -550,6 +576,17 @@ function useCharacterImages({ api, filePath, chatSessionId, getImages }) {
550
576
  //#endregion
551
577
  //#region src/vue/composables/useDeckEditor.ts
552
578
  var DECK_SAVE_DEBOUNCE_MS = 300;
579
+ /**
580
+ * Who this editor is, on the wire.
581
+ *
582
+ * Every write carries it so the server's "this script changed" broadcast can be told apart
583
+ * from someone else's. Without it a save would echo back and reload the editor mid-keystroke,
584
+ * rebuilding the element the caret sits in.
585
+ *
586
+ * Per module instance rather than per component: one View is mounted at a time, and a value
587
+ * that survives a remount keeps a save in flight from being mistaken for a foreign write.
588
+ */
589
+ var EDITOR_ORIGIN = `deck-editor-${Math.random().toString(36).slice(2)}`;
553
590
  function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
554
591
  const isDeck = computed(() => isAllSlideDeck(effectiveScript.value));
555
592
  const deckScriptInput = computed(() => effectiveScript.value);
@@ -569,7 +606,8 @@ function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
569
606
  if (!next || !filePath.value) return;
570
607
  const response = await api.call("updateScript", {
571
608
  filePath: filePath.value,
572
- script: next
609
+ script: next,
610
+ origin: EDITOR_ORIGIN
573
611
  });
574
612
  if (!response.ok) {
575
613
  console.error("[presentMulmoScript] deck save failed:", response.error);
@@ -586,11 +624,25 @@ function useDeckEditor({ api, filePath, effectiveScript, commitScript }) {
586
624
  flushDeckSave();
587
625
  }
588
626
  }
627
+ /**
628
+ * Reload when someone else writes this script — the agent, or another window.
629
+ *
630
+ * A pending local edit is flushed first rather than dropped: the user's keystrokes are the
631
+ * thing they would notice losing, and the write that triggered this has already landed, so
632
+ * flushing cannot clobber it out of order.
633
+ */
634
+ function watchForeignWrites(reload) {
635
+ return api.onScriptChanged(() => filePath.value, EDITOR_ORIGIN, () => {
636
+ flushPendingDeckSave();
637
+ reload();
638
+ });
639
+ }
589
640
  return {
590
641
  isDeck,
591
642
  deckScriptInput,
592
643
  onDeckUpdate,
593
- flushPendingDeckSave
644
+ flushPendingDeckSave,
645
+ watchForeignWrites
594
646
  };
595
647
  }
596
648
  /** Reactive message bundle for the active host locale. The plugin carries its
@@ -1372,7 +1424,7 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
1372
1424
  props: { selectedResult: {} },
1373
1425
  emits: ["updateResult"],
1374
1426
  setup(__props, { emit: __emit }) {
1375
- const MulmoScriptDeckEditor = defineAsyncComponent(() => import("@mulmocast/deck-web").then((mod) => mod.MulmoScriptDeckEditor));
1427
+ const BeatListEditor = defineAsyncComponent(() => import("@mulmocast/beat-editor").then((mod) => mod.BeatListEditor));
1376
1428
  const api = useMulmoScriptTransport();
1377
1429
  const adapter = useHostAdapter();
1378
1430
  const canFetchMedia = computed(() => Boolean(adapter.fetchMediaBlob));
@@ -1533,16 +1585,24 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
1533
1585
  }
1534
1586
  });
1535
1587
  }
1536
- const { isDeck, deckScriptInput, onDeckUpdate, flushPendingDeckSave } = useDeckEditor({
1588
+ const { isDeck, deckScriptInput, onDeckUpdate, flushPendingDeckSave, watchForeignWrites } = useDeckEditor({
1537
1589
  api,
1538
1590
  filePath,
1539
1591
  effectiveScript,
1540
1592
  commitScript
1541
1593
  });
1594
+ const unsubscribeForeignWrites = watchForeignWrites(() => {
1595
+ refreshScriptFromDisk();
1596
+ });
1597
+ const deckBeats = computed(() => beatsOf(deckScriptInput.value));
1598
+ function onDeckBeatsUpdate(beats) {
1599
+ onDeckUpdate(withBeats(deckScriptInput.value, beats));
1600
+ }
1542
1601
  onBeforeUnmount(() => {
1543
1602
  flushPendingDeckSave();
1544
1603
  resetBeatMovies();
1545
1604
  unsubscribeGenerationEvents();
1605
+ unsubscribeForeignWrites();
1546
1606
  });
1547
1607
  const loadedSource = ref("");
1548
1608
  const sourceChanged = computed(() => editableSource.value !== loadedSource.value);
@@ -2015,11 +2075,10 @@ var View_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
2015
2075
  "onCharDrop",
2016
2076
  "onRenderCharacter"
2017
2077
  ])) : createCommentVNode("", true),
2018
- unref(isDeck) ? (openBlock(), createElementBlock("div", _hoisted_14, [createVNode(unref(MulmoScriptDeckEditor), {
2019
- script: unref(deckScriptInput),
2020
- layout: "compact",
2021
- "onUpdate:script": unref(onDeckUpdate)
2022
- }, null, 8, ["script", "onUpdate:script"])])) : (openBlock(), createElementBlock("div", {
2078
+ unref(isDeck) ? (openBlock(), createElementBlock("div", _hoisted_14, [createVNode(unref(BeatListEditor), {
2079
+ beats: deckBeats.value,
2080
+ "onUpdate:beats": onDeckBeatsUpdate
2081
+ }, null, 8, ["beats"])])) : (openBlock(), createElementBlock("div", {
2023
2082
  key: 3,
2024
2083
  ref_key: "beatListEl",
2025
2084
  ref: beatListEl,
@@ -2221,7 +2280,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
2221
2280
  };
2222
2281
  //#endregion
2223
2282
  //#region src/vue/View.vue
2224
- var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-f8c0d52b"]]);
2283
+ var View_default = /*#__PURE__*/ _plugin_vue_export_helper_default(View_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-87bf6e4c"]]);
2225
2284
  //#endregion
2226
2285
  //#region src/vue/Preview.vue?vue&type=script&setup=true&lang.ts
2227
2286
  var _hoisted_1 = {