@mulmoclaude/collection-plugin 0.5.3 → 0.5.6

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,6 +1,6 @@
1
1
  import { A as buildUpdatedRecord, C as dateSortValue, D as numericSortValue, E as nextSortDirection, F as rowFromItem, G as TOOL_DEFINITION, I as actionVisible, L as fieldVisible, M as draftToRecord, N as emptyRow, O as sortItems, P as firstMissingRequiredField, T as isSortableField, W as resolveEnumColor, _ as ymdKey, a as assignLanes, b as labelFieldFor, i as MINUTES_PER_DAY, j as coerceInlineValue, k as stringSortValue, l as dateOf, n as shortHexId, o as bucketRecords, q as executePresentCollection, r as errorMessage, s as buildMonthGrid, t as defangForPrompt, u as daySlice, v as itemIdOf, w as enumSortValue, x as boolSortValue, y as itemLabelOf } from "./promptSafety-DIQ2yDca.js";
2
2
  import { t as deriveAll } from "./deriveAll-BHcs1erT.js";
3
- import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, effectScope, mergeModels, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useModel, vModelCheckbox, vModelDynamic, vModelSelect, vModelText, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
3
+ import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, effectScope, mergeModels, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useModel, vModelCheckbox, vModelDynamic, vModelSelect, vModelText, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
4
4
  import { createI18n } from "vue-i18n";
5
5
  import draggable from "vuedraggable";
6
6
  //#region src/vue/uiContext.ts
@@ -2376,12 +2376,15 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2376
2376
  slug: {},
2377
2377
  view: {}
2378
2378
  },
2379
- setup(__props) {
2379
+ emits: ["openItem", "startChat"],
2380
+ setup(__props, { emit: __emit }) {
2380
2381
  const { t } = useCollectionI18n();
2381
2382
  const props = __props;
2383
+ const emit = __emit;
2382
2384
  const loading = ref(true);
2383
2385
  const error = ref(null);
2384
2386
  const srcdoc = ref(null);
2387
+ const iframeEl = ref(null);
2385
2388
  let refreshTimer;
2386
2389
  function clearRefresh() {
2387
2390
  if (refreshTimer !== void 0) {
@@ -2430,9 +2433,53 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2430
2433
  }
2431
2434
  }
2432
2435
  watch([() => props.slug, () => props.view.id], () => void load(), { immediate: true });
2433
- onBeforeUnmount(clearRefresh);
2436
+ let changeUnsub = null;
2437
+ function relayChange() {
2438
+ iframeEl.value?.contentWindow?.postMessage({
2439
+ type: "mc-collection-changed",
2440
+ slug: props.slug
2441
+ }, "*");
2442
+ }
2443
+ watch(() => props.slug, (slug) => {
2444
+ changeUnsub?.();
2445
+ changeUnsub = null;
2446
+ const subscribe = collectionUi().subscribeChanges;
2447
+ if (slug && subscribe) changeUnsub = subscribe(slug, relayChange);
2448
+ }, { immediate: true });
2449
+ function handleOpenItem(body) {
2450
+ const itemId = typeof body.id === "string" ? body.id : String(body.id ?? "");
2451
+ if (!itemId) return;
2452
+ emit("openItem", {
2453
+ id: itemId,
2454
+ mode: body.mode === "edit" ? "edit" : "view"
2455
+ });
2456
+ }
2457
+ function handleStartChat(body) {
2458
+ const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
2459
+ if (!prompt) return;
2460
+ emit("startChat", {
2461
+ prompt,
2462
+ role: typeof body.role === "string" ? body.role : void 0
2463
+ });
2464
+ }
2465
+ function onWindowMessage(event) {
2466
+ if (event.source !== iframeEl.value?.contentWindow) return;
2467
+ const msg = event.data;
2468
+ if (!msg || msg.slug !== props.slug) return;
2469
+ if (msg.type === "mc-open-item") handleOpenItem(msg);
2470
+ else if (msg.type === "mc-start-chat") handleStartChat(msg);
2471
+ }
2472
+ onMounted(() => window.addEventListener("message", onWindowMessage));
2473
+ onBeforeUnmount(() => {
2474
+ clearRefresh();
2475
+ changeUnsub?.();
2476
+ changeUnsub = null;
2477
+ window.removeEventListener("message", onWindowMessage);
2478
+ });
2434
2479
  return (_ctx, _cache) => {
2435
2480
  return openBlock(), createElementBlock("div", _hoisted_1$5, [error.value ? (openBlock(), createElementBlock("div", _hoisted_2$4, toDisplayString(unref(t)("collectionsView.customViewError", { error: error.value })), 1)) : loading.value ? (openBlock(), createElementBlock("div", _hoisted_3$4, toDisplayString(unref(t)("collectionsView.customViewLoading")), 1)) : srcdoc.value ? (openBlock(), createElementBlock("iframe", {
2481
+ ref_key: "iframeEl",
2482
+ ref: iframeEl,
2436
2483
  key: __props.view.id,
2437
2484
  "data-testid": "collection-custom-view-iframe",
2438
2485
  title: __props.view.label,
@@ -2452,7 +2499,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
2452
2499
  };
2453
2500
  //#endregion
2454
2501
  //#region src/vue/components/CollectionCustomView.vue
2455
- var CollectionCustomView_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CollectionCustomView_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-3ab71997"]]);
2502
+ var CollectionCustomView_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CollectionCustomView_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-d5fb7ca6"]]);
2456
2503
  //#endregion
2457
2504
  //#region src/vue/useCollectionRendering.ts
2458
2505
  function useCollectionRendering(collection, locale) {
@@ -2982,6 +3029,17 @@ var _hoisted_83 = { class: "px-6 py-5" };
2982
3029
  var _hoisted_84 = ["placeholder", "onKeydown"];
2983
3030
  var _hoisted_85 = { class: "px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50" };
2984
3031
  var _hoisted_86 = ["disabled"];
3032
+ /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3033
+ * `presentCollection` chat card mounts this component and drives both
3034
+ * from the tool result). In standalone route mode (the
3035
+ * `/collections/:slug` page) both are undefined and the component reads
3036
+ * `route.params.slug` / `route.query.selected` as before.
3037
+ *
3038
+ * `sendTextMessage` is forwarded ONLY by the chat card — its presence
3039
+ * is our "rendered inside a chat" signal. When set, chat-triggering
3040
+ * actions send into the current session instead of spawning a new
3041
+ * chat (see `runAction` / `submitChat`). */
3042
+ var LIVE_REFRESH_DEBOUNCE_MS = 150;
2985
3043
  //#endregion
2986
3044
  //#region src/vue/components/CollectionView.vue
2987
3045
  var CollectionView_default = /* @__PURE__ */ defineComponent({
@@ -3355,6 +3413,26 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3355
3413
  }
3356
3414
  maybeAutoRefreshFeed(slug);
3357
3415
  }
3416
+ /** Refresh records + schema IN PLACE for a live (pub/sub-driven) update,
3417
+ * preserving the user's browsing state — unlike `loadCollection`, which is the
3418
+ * route-change path and resets it. Specifically: does NOT null `collection`
3419
+ * (so the layout and an active custom-view iframe don't remount), keeps
3420
+ * `searchQuery` / `openDay` / `sortState`, and shows no loading spinner; the
3421
+ * open detail (`viewing`) is re-resolved against the fresh records by id, so it
3422
+ * follows an edited record and closes only if the record was deleted. A failed
3423
+ * fetch is a no-op (keep the current data) — a transient blip shouldn't blank a
3424
+ * view the user is reading. */
3425
+ async function refreshItemsInPlace(slug) {
3426
+ const result = await cui.fetchCollectionDetail(slug);
3427
+ if (!result.ok || activeSlug.value !== slug) return;
3428
+ collection.value = result.data.collection;
3429
+ items.value = result.data.items;
3430
+ dataIssues.value = result.data.issues ?? [];
3431
+ enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
3432
+ await render.loadLinkedCollections(result.data.collection.schema, slug);
3433
+ if (activeSlug.value !== slug) return;
3434
+ if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
3435
+ }
3358
3436
  function maybeAutoRefreshFeed(slug) {
3359
3437
  if (embedded.value) return;
3360
3438
  const current = collection.value;
@@ -3870,6 +3948,30 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3870
3948
  showDetail(item);
3871
3949
  writeSelectedToUrl(itemId);
3872
3950
  }
3951
+ /** A custom (sandboxed) view asked to open a record in the shared modal.
3952
+ * `view` → read-only detail, `edit` → straight into the editor. Ungated: the
3953
+ * capability token governs the view's *code*, not user actions through the
3954
+ * host's own trusted modal (no write happens without an explicit Save). */
3955
+ function onCustomViewOpenItem(payload) {
3956
+ const item = findItemById(payload.id);
3957
+ if (!item) return;
3958
+ if (editing.value) closeEditor();
3959
+ if (payload.mode === "edit") {
3960
+ openEdit(item);
3961
+ return;
3962
+ }
3963
+ showDetail(item);
3964
+ writeSelectedToUrl(payload.id);
3965
+ }
3966
+ /** The custom view called `__MC_VIEW.startChat(prompt, role)` — open a new chat
3967
+ * with the prompt prefilled as an editable draft. The host validates `role`
3968
+ * (falls back to General). The view's code only proposes text; the user
3969
+ * approves / edits / sends, so no capability is required. */
3970
+ function onCustomViewStartChat(payload) {
3971
+ const prompt = payload.prompt.trim();
3972
+ if (!prompt) return;
3973
+ cui.startNewChatDraft(prompt, payload.role);
3974
+ }
3873
3975
  /** A calendar day cell was activated → open its popup on a clean slate
3874
3976
  * (clear any prior selection so the popup opens timeline-only). */
3875
3977
  function onOpenDay(day) {
@@ -3925,6 +4027,42 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3925
4027
  loading.value = false;
3926
4028
  }
3927
4029
  }, { immediate: true });
4030
+ let changeUnsub = null;
4031
+ let liveRefreshTimer;
4032
+ let pendingRemoteRefresh = false;
4033
+ function clearLiveRefreshTimer() {
4034
+ if (liveRefreshTimer !== void 0) {
4035
+ clearTimeout(liveRefreshTimer);
4036
+ liveRefreshTimer = void 0;
4037
+ }
4038
+ }
4039
+ function onRemoteChange(slug) {
4040
+ clearLiveRefreshTimer();
4041
+ liveRefreshTimer = setTimeout(() => {
4042
+ liveRefreshTimer = void 0;
4043
+ if (editing.value) {
4044
+ pendingRemoteRefresh = true;
4045
+ return;
4046
+ }
4047
+ if (activeSlug.value === slug) refreshItemsInPlace(slug);
4048
+ }, LIVE_REFRESH_DEBOUNCE_MS);
4049
+ }
4050
+ watch(editing, (current) => {
4051
+ if (current || !pendingRemoteRefresh) return;
4052
+ pendingRemoteRefresh = false;
4053
+ if (activeSlug.value) refreshItemsInPlace(activeSlug.value);
4054
+ });
4055
+ watch(activeSlug, (slug) => {
4056
+ changeUnsub?.();
4057
+ changeUnsub = null;
4058
+ clearLiveRefreshTimer();
4059
+ if (slug && cui.subscribeChanges) changeUnsub = cui.subscribeChanges(slug, () => onRemoteChange(slug));
4060
+ }, { immediate: true });
4061
+ onUnmounted(() => {
4062
+ changeUnsub?.();
4063
+ changeUnsub = null;
4064
+ clearLiveRefreshTimer();
4065
+ });
3928
4066
  watch([
3929
4067
  activeView,
3930
4068
  calendarAnchorField,
@@ -4249,7 +4387,9 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4249
4387
  "notified"
4250
4388
  ])])])) : activeCustomView.value ? (openBlock(), createElementBlock("div", _hoisted_45, [createVNode(CollectionCustomView_default, {
4251
4389
  slug: collection.value.slug,
4252
- view: activeCustomView.value
4390
+ view: activeCustomView.value,
4391
+ onOpenItem: onCustomViewOpenItem,
4392
+ onStartChat: onCustomViewStartChat
4253
4393
  }, null, 8, ["slug", "view"])])) : items.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_46, [_cache[41] || (_cache[41] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), createElementVNode("p", _hoisted_47, toDisplayString(unref(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_48, [
4254
4394
  _cache[42] || (_cache[42] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
4255
4395
  createElementVNode("p", _hoisted_49, toDisplayString(unref(t)("collectionsView.noMatchingItems")), 1),