@mulmoclaude/collection-plugin 0.5.3 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/server/host.d.ts +25 -0
  2. package/dist/server/host.d.ts.map +1 -1
  3. package/dist/server/index.d.ts +1 -1
  4. package/dist/server/index.d.ts.map +1 -1
  5. package/dist/server/io.d.ts +6 -0
  6. package/dist/server/io.d.ts.map +1 -1
  7. package/dist/server.cjs +32 -8
  8. package/dist/server.cjs.map +1 -1
  9. package/dist/server.js +31 -9
  10. package/dist/server.js.map +1 -1
  11. package/dist/style.css +19 -3
  12. package/dist/vue/components/CollectionCustomView.vue.d.ts +19 -1
  13. package/dist/vue/components/CollectionCustomView.vue.d.ts.map +1 -1
  14. package/dist/vue/components/CollectionRecordPanel.vue.d.ts +4 -2
  15. package/dist/vue/components/CollectionRecordPanel.vue.d.ts.map +1 -1
  16. package/dist/vue/components/CollectionView.vue.d.ts.map +1 -1
  17. package/dist/vue/lang/de.d.ts.map +1 -1
  18. package/dist/vue/lang/en.d.ts +2 -0
  19. package/dist/vue/lang/en.d.ts.map +1 -1
  20. package/dist/vue/lang/es.d.ts.map +1 -1
  21. package/dist/vue/lang/fr.d.ts.map +1 -1
  22. package/dist/vue/lang/index.d.ts +2 -0
  23. package/dist/vue/lang/index.d.ts.map +1 -1
  24. package/dist/vue/lang/ja.d.ts.map +1 -1
  25. package/dist/vue/lang/ko.d.ts.map +1 -1
  26. package/dist/vue/lang/ptBR.d.ts.map +1 -1
  27. package/dist/vue/lang/zh.d.ts.map +1 -1
  28. package/dist/vue/uiContext.d.ts +12 -0
  29. package/dist/vue/uiContext.d.ts.map +1 -1
  30. package/dist/vue/useCollectionRendering.d.ts +2 -0
  31. package/dist/vue/useCollectionRendering.d.ts.map +1 -1
  32. package/dist/vue.cjs +240 -19
  33. package/dist/vue.cjs.map +1 -1
  34. package/dist/vue.js +241 -20
  35. package/dist/vue.js.map +1 -1
  36. package/package.json +2 -2
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
@@ -67,6 +67,8 @@ var i18n = createI18n({
67
67
  chatTitle: "Start a chat",
68
68
  chatPlaceholder: "Describe what you want to do with this collection…",
69
69
  chatStart: "Start chat",
70
+ itemChatLabel: "Chat about this record",
71
+ itemChatPlaceholder: "Ask or instruct about this record…",
70
72
  viewToggle: "View",
71
73
  viewTable: "Table",
72
74
  viewCalendar: "Calendar",
@@ -168,6 +170,8 @@ var i18n = createI18n({
168
170
  chatTitle: "チャットを開始",
169
171
  chatPlaceholder: "このコレクションで行いたいことを入力してください…",
170
172
  chatStart: "チャットを開始",
173
+ itemChatLabel: "このレコードについてチャット",
174
+ itemChatPlaceholder: "このレコードについて質問・指示する…",
171
175
  viewToggle: "表示",
172
176
  viewTable: "テーブル",
173
177
  viewCalendar: "カレンダー",
@@ -269,6 +273,8 @@ var i18n = createI18n({
269
273
  chatTitle: "开始对话",
270
274
  chatPlaceholder: "描述你想对这个集合做什么…",
271
275
  chatStart: "开始对话",
276
+ itemChatLabel: "就此记录对话",
277
+ itemChatPlaceholder: "询问或指示有关此记录的操作…",
272
278
  viewToggle: "视图",
273
279
  viewTable: "表格",
274
280
  viewCalendar: "日历",
@@ -370,6 +376,8 @@ var i18n = createI18n({
370
376
  chatTitle: "채팅 시작",
371
377
  chatPlaceholder: "이 컬렉션으로 하고 싶은 작업을 설명하세요…",
372
378
  chatStart: "채팅 시작",
379
+ itemChatLabel: "이 레코드에 대해 채팅",
380
+ itemChatPlaceholder: "이 레코드에 대해 질문하거나 지시하세요…",
373
381
  viewToggle: "보기",
374
382
  viewTable: "표",
375
383
  viewCalendar: "캘린더",
@@ -471,6 +479,8 @@ var i18n = createI18n({
471
479
  chatTitle: "Iniciar un chat",
472
480
  chatPlaceholder: "Describe qué quieres hacer con esta colección…",
473
481
  chatStart: "Iniciar chat",
482
+ itemChatLabel: "Chatear sobre este registro",
483
+ itemChatPlaceholder: "Pregunta o indica algo sobre este registro…",
474
484
  viewToggle: "Vista",
475
485
  viewTable: "Tabla",
476
486
  viewCalendar: "Calendario",
@@ -572,6 +582,8 @@ var i18n = createI18n({
572
582
  chatTitle: "Iniciar um chat",
573
583
  chatPlaceholder: "Descreva o que você quer fazer com esta coleção…",
574
584
  chatStart: "Iniciar chat",
585
+ itemChatLabel: "Conversar sobre este registro",
586
+ itemChatPlaceholder: "Pergunte ou instrua sobre este registro…",
575
587
  viewToggle: "Visualização",
576
588
  viewTable: "Tabela",
577
589
  viewCalendar: "Calendário",
@@ -673,6 +685,8 @@ var i18n = createI18n({
673
685
  chatTitle: "Démarrer une discussion",
674
686
  chatPlaceholder: "Décrivez ce que vous voulez faire avec cette collection…",
675
687
  chatStart: "Démarrer la discussion",
688
+ itemChatLabel: "Discuter de cet enregistrement",
689
+ itemChatPlaceholder: "Posez une question ou donnez une instruction sur cet enregistrement…",
676
690
  viewToggle: "Affichage",
677
691
  viewTable: "Tableau",
678
692
  viewCalendar: "Calendrier",
@@ -774,6 +788,8 @@ var i18n = createI18n({
774
788
  chatTitle: "Chat starten",
775
789
  chatPlaceholder: "Beschreibe, was du mit dieser Sammlung tun möchtest…",
776
790
  chatStart: "Chat starten",
791
+ itemChatLabel: "Über diesen Eintrag chatten",
792
+ itemChatPlaceholder: "Stelle eine Frage oder gib eine Anweisung zu diesem Eintrag…",
777
793
  viewToggle: "Ansicht",
778
794
  viewTable: "Tabelle",
779
795
  viewCalendar: "Kalender",
@@ -1755,6 +1771,7 @@ var _hoisted_36$1 = ["onUpdate:modelValue", "required"];
1755
1771
  var _hoisted_37$1 = [
1756
1772
  "onUpdate:modelValue",
1757
1773
  "type",
1774
+ "step",
1758
1775
  "required"
1759
1776
  ];
1760
1777
  var _hoisted_38$1 = { class: "text-center px-1" };
@@ -1783,6 +1800,7 @@ var _hoisted_45$1 = [
1783
1800
  "id",
1784
1801
  "onUpdate:modelValue",
1785
1802
  "type",
1803
+ "step",
1786
1804
  "required",
1787
1805
  "disabled",
1788
1806
  "data-testid"
@@ -1873,6 +1891,18 @@ var _hoisted_69$1 = {
1873
1891
  key: 0,
1874
1892
  class: "col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl"
1875
1893
  };
1894
+ var _hoisted_70$1 = {
1895
+ key: 1,
1896
+ class: "mt-5 pt-4 border-t border-slate-200/60",
1897
+ "data-testid": "collections-detail-chat"
1898
+ };
1899
+ var _hoisted_71$1 = {
1900
+ class: "block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1.5",
1901
+ for: "collections-detail-chat-input"
1902
+ };
1903
+ var _hoisted_72$1 = { class: "flex items-end gap-2" };
1904
+ var _hoisted_73$1 = ["placeholder", "onKeydown"];
1905
+ var _hoisted_74$1 = ["disabled"];
1876
1906
  //#endregion
1877
1907
  //#region src/vue/components/CollectionRecordPanel.vue
1878
1908
  var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
@@ -1901,7 +1931,8 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
1901
1931
  "edit",
1902
1932
  "close",
1903
1933
  "delete",
1904
- "runAction"
1934
+ "runAction",
1935
+ "itemChat"
1905
1936
  ], ["update:editing"]),
1906
1937
  setup(__props, { emit: __emit }) {
1907
1938
  const cui = collectionUi();
@@ -1910,6 +1941,16 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
1910
1941
  const editing = useModel(__props, "editing");
1911
1942
  const emit = __emit;
1912
1943
  const { t } = useCollectionI18n();
1944
+ const chatMessage = ref("");
1945
+ watch(() => props.viewing, () => {
1946
+ chatMessage.value = "";
1947
+ });
1948
+ function submitItemChat() {
1949
+ const message = chatMessage.value.trim();
1950
+ if (!message) return;
1951
+ emit("itemChat", message);
1952
+ chatMessage.value = "";
1953
+ }
1913
1954
  const embedViews = computed(() => props.render.embedViews.value);
1914
1955
  /** The record the read-only displays render from: the live draft while
1915
1956
  * editing (so non-editable cells like derived/embed preview the in-flight
@@ -1988,7 +2029,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
1988
2029
  return openBlock(), createBlock(resolveDynamicComponent(editing.value ? "form" : "div"), {
1989
2030
  class: "px-6 py-5 max-h-[60vh] overflow-y-auto",
1990
2031
  "data-testid": rootTestid.value,
1991
- onSubmit: _cache[4] || (_cache[4] = withModifiers(($event) => emit("submit"), ["prevent"]))
2032
+ onSubmit: _cache[5] || (_cache[5] = withModifiers(($event) => emit("submit"), ["prevent"]))
1992
2033
  }, {
1993
2034
  default: withCtx(() => [
1994
2035
  createElementVNode("div", _hoisted_1$7, [createElementVNode("div", _hoisted_2$6, [createElementVNode("span", _hoisted_3$6, toDisplayString(__props.collection.title), 1), createElementVNode("h2", {
@@ -2020,20 +2061,20 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2020
2061
  class: "h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1",
2021
2062
  "data-testid": "collections-detail-edit",
2022
2063
  onClick: _cache[1] || (_cache[1] = ($event) => emit("edit"))
2023
- }, [_cache[5] || (_cache[5] = createElementVNode("span", { class: "material-icons text-sm" }, "edit", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.editItem")), 1)]),
2064
+ }, [_cache[6] || (_cache[6] = createElementVNode("span", { class: "material-icons text-sm" }, "edit", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.editItem")), 1)]),
2024
2065
  createElementVNode("button", {
2025
2066
  type: "button",
2026
2067
  class: "h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1",
2027
2068
  "data-testid": "collections-detail-remove",
2028
2069
  onClick: _cache[2] || (_cache[2] = ($event) => emit("delete"))
2029
- }, [_cache[6] || (_cache[6] = createElementVNode("span", { class: "material-icons text-sm" }, "delete", -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.remove")), 1)]),
2070
+ }, [_cache[7] || (_cache[7] = createElementVNode("span", { class: "material-icons text-sm" }, "delete", -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.remove")), 1)]),
2030
2071
  createElementVNode("button", {
2031
2072
  type: "button",
2032
2073
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors",
2033
2074
  "aria-label": unref(t)("common.close"),
2034
2075
  "data-testid": "collections-detail-close",
2035
2076
  onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
2036
- }, [..._cache[7] || (_cache[7] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9$4)
2077
+ }, [..._cache[8] || (_cache[8] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9$4)
2037
2078
  ]))]),
2038
2079
  !editing.value && __props.actionError ? (openBlock(), createElementBlock("p", _hoisted_10$4, toDisplayString(__props.actionError), 1)) : createCommentVNode("", true),
2039
2080
  createElementVNode("div", _hoisted_11$4, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.collection.schema.fields, (field, key) => {
@@ -2083,7 +2124,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2083
2124
  key: subKey,
2084
2125
  class: "text-left px-3 py-2 font-bold"
2085
2126
  }, toDisplayString(subField.label), 1);
2086
- }), 128)), _cache[8] || (_cache[8] = createElementVNode("th", { class: "w-9" }, null, -1))])]), createElementVNode("tbody", _hoisted_26$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(editing.value.table[key], (row, rowIdx) => {
2127
+ }), 128)), _cache[9] || (_cache[9] = createElementVNode("th", { class: "w-9" }, null, -1))])]), createElementVNode("tbody", _hoisted_26$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(editing.value.table[key], (row, rowIdx) => {
2087
2128
  return openBlock(), createElementBlock("tr", {
2088
2129
  key: rowIdx,
2089
2130
  class: "hover:bg-slate-50/50"
@@ -2127,6 +2168,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2127
2168
  key: 4,
2128
2169
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2129
2170
  type: __props.render.inputTypeFor(subField.type),
2171
+ step: __props.render.stepFor(subField.type),
2130
2172
  required: subField.required,
2131
2173
  class: "w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700"
2132
2174
  }, null, 8, _hoisted_37$1)), [[vModelDynamic, row.text[subKey]]])]);
@@ -2136,13 +2178,13 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2136
2178
  "aria-label": unref(t)("collectionsView.removeRow"),
2137
2179
  "data-testid": `collections-table-${key}-remove-${rowIdx}`,
2138
2180
  onClick: ($event) => removeTableRow(String(key), rowIdx)
2139
- }, [..._cache[9] || (_cache[9] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_39$1)])]);
2181
+ }, [..._cache[10] || (_cache[10] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_39$1)])]);
2140
2182
  }), 128))])])])) : (openBlock(), createElementBlock("p", _hoisted_40$1, toDisplayString(unref(t)("collectionsView.noRows")), 1)), createElementVNode("button", {
2141
2183
  type: "button",
2142
2184
  class: "inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline",
2143
2185
  "data-testid": `collections-table-${key}-add`,
2144
2186
  onClick: ($event) => addTableRow(String(key), field.of)
2145
- }, [_cache[10] || (_cache[10] = createElementVNode("span", { class: "material-icons text-xs" }, "add", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.addRow")), 1)], 8, _hoisted_41$1)], 8, _hoisted_22$2)) : field.type === "money" ? (openBlock(), createElementBlock("div", _hoisted_42$1, [createElementVNode("div", _hoisted_43$1, toDisplayString(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), withDirectives(createElementVNode("input", {
2187
+ }, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "material-icons text-xs" }, "add", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.addRow")), 1)], 8, _hoisted_41$1)], 8, _hoisted_22$2)) : field.type === "money" ? (openBlock(), createElementBlock("div", _hoisted_42$1, [createElementVNode("div", _hoisted_43$1, toDisplayString(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), withDirectives(createElementVNode("input", {
2146
2188
  id: `collections-field-${key}`,
2147
2189
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2148
2190
  type: "number",
@@ -2164,6 +2206,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2164
2206
  id: `collections-field-${key}`,
2165
2207
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2166
2208
  type: __props.render.inputTypeFor(field.type),
2209
+ step: __props.render.stepFor(field.type),
2167
2210
  required: isFieldRequiredInUi(field),
2168
2211
  disabled: field.primary === true && (editing.value.mode === "edit" || __props.isSingleton),
2169
2212
  class: "w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all",
@@ -2180,7 +2223,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2180
2223
  key: 1,
2181
2224
  class: "text-xs font-medium text-slate-700 break-words",
2182
2225
  "data-testid": `collections-detail-value-${key}`
2183
- }, [field.type === "toggle" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? (openBlock(), createElementBlock("span", _hoisted_48$1, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : (openBlock(), createElementBlock("span", _hoisted_49$1, toDisplayString(unref(t)("common.no")), 1))], 64)) : field.type === "boolean" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [detailRecord.value[key] === true ? (openBlock(), createElementBlock("span", _hoisted_50$1, [_cache[12] || (_cache[12] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? (openBlock(), createElementBlock("span", _hoisted_51$1, toDisplayString(unref(t)("common.no")), 1)) : (openBlock(), createElementBlock("span", _hoisted_52$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? (openBlock(), createElementBlock("a", {
2226
+ }, [field.type === "toggle" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? (openBlock(), createElementBlock("span", _hoisted_48$1, [_cache[12] || (_cache[12] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : (openBlock(), createElementBlock("span", _hoisted_49$1, toDisplayString(unref(t)("common.no")), 1))], 64)) : field.type === "boolean" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [detailRecord.value[key] === true ? (openBlock(), createElementBlock("span", _hoisted_50$1, [_cache[13] || (_cache[13] = createElementVNode("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), createTextVNode(" " + toDisplayString(unref(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? (openBlock(), createElementBlock("span", _hoisted_51$1, toDisplayString(unref(t)("common.no")), 1)) : (openBlock(), createElementBlock("span", _hoisted_52$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? (openBlock(), createElementBlock("a", {
2184
2227
  key: 2,
2185
2228
  href: unref(cui).recordHref?.(field.to, String(detailRecord.value[key])),
2186
2229
  tabindex: unref(cui).recordHref?.(field.to, String(detailRecord.value[key])) ? void 0 : 0,
@@ -2238,7 +2281,22 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2238
2281
  "data-testid": `collections-detail-file-${key}`,
2239
2282
  onClick: ($event) => unref(activatePathLink)($event, __props.render.fileRoutePath(detailRecord.value[key]) ?? "")
2240
2283
  }, toDisplayString(String(detailRecord.value[key])), 9, _hoisted_67$1)) : (openBlock(), createElementBlock("span", _hoisted_68$1, toDisplayString(__props.render.formatCell(detailRecord.value[key], field.type)), 1))], 8, _hoisted_47$1))], 2)) : createCommentVNode("", true)], 64);
2241
- }), 128)), editing.value && __props.saveError ? (openBlock(), createElementBlock("p", _hoisted_69$1, toDisplayString(__props.saveError), 1)) : createCommentVNode("", true)])
2284
+ }), 128)), editing.value && __props.saveError ? (openBlock(), createElementBlock("p", _hoisted_69$1, toDisplayString(__props.saveError), 1)) : createCommentVNode("", true)]),
2285
+ !editing.value ? (openBlock(), createElementBlock("div", _hoisted_70$1, [createElementVNode("label", _hoisted_71$1, toDisplayString(unref(t)("collectionsView.itemChatLabel")), 1), createElementVNode("div", _hoisted_72$1, [withDirectives(createElementVNode("textarea", {
2286
+ id: "collections-detail-chat-input",
2287
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => chatMessage.value = $event),
2288
+ rows: "2",
2289
+ placeholder: unref(t)("collectionsView.itemChatPlaceholder"),
2290
+ class: "flex-1 bg-slate-50 border border-slate-200/80 rounded-xl px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all resize-none",
2291
+ "data-testid": "collections-detail-chat-input",
2292
+ onKeydown: [withKeys(withModifiers(submitItemChat, ["meta"]), ["enter"]), withKeys(withModifiers(submitItemChat, ["ctrl"]), ["enter"])]
2293
+ }, null, 40, _hoisted_73$1), [[vModelText, chatMessage.value]]), createElementVNode("button", {
2294
+ type: "button",
2295
+ class: "h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10 flex items-center gap-1 shrink-0",
2296
+ disabled: !chatMessage.value.trim(),
2297
+ "data-testid": "collections-detail-chat-send",
2298
+ onClick: submitItemChat
2299
+ }, [_cache[14] || (_cache[14] = createElementVNode("span", { class: "material-icons text-sm" }, "forum", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.chat")), 1)], 8, _hoisted_74$1)])])) : createCommentVNode("", true)
2242
2300
  ]),
2243
2301
  _: 1
2244
2302
  }, 40, ["data-testid"]);
@@ -2376,12 +2434,15 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2376
2434
  slug: {},
2377
2435
  view: {}
2378
2436
  },
2379
- setup(__props) {
2437
+ emits: ["openItem", "startChat"],
2438
+ setup(__props, { emit: __emit }) {
2380
2439
  const { t } = useCollectionI18n();
2381
2440
  const props = __props;
2441
+ const emit = __emit;
2382
2442
  const loading = ref(true);
2383
2443
  const error = ref(null);
2384
2444
  const srcdoc = ref(null);
2445
+ const iframeEl = ref(null);
2385
2446
  let refreshTimer;
2386
2447
  function clearRefresh() {
2387
2448
  if (refreshTimer !== void 0) {
@@ -2430,9 +2491,53 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2430
2491
  }
2431
2492
  }
2432
2493
  watch([() => props.slug, () => props.view.id], () => void load(), { immediate: true });
2433
- onBeforeUnmount(clearRefresh);
2494
+ let changeUnsub = null;
2495
+ function relayChange() {
2496
+ iframeEl.value?.contentWindow?.postMessage({
2497
+ type: "mc-collection-changed",
2498
+ slug: props.slug
2499
+ }, "*");
2500
+ }
2501
+ watch(() => props.slug, (slug) => {
2502
+ changeUnsub?.();
2503
+ changeUnsub = null;
2504
+ const subscribe = collectionUi().subscribeChanges;
2505
+ if (slug && subscribe) changeUnsub = subscribe(slug, relayChange);
2506
+ }, { immediate: true });
2507
+ function handleOpenItem(body) {
2508
+ const itemId = typeof body.id === "string" ? body.id : String(body.id ?? "");
2509
+ if (!itemId) return;
2510
+ emit("openItem", {
2511
+ id: itemId,
2512
+ mode: body.mode === "edit" ? "edit" : "view"
2513
+ });
2514
+ }
2515
+ function handleStartChat(body) {
2516
+ const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
2517
+ if (!prompt) return;
2518
+ emit("startChat", {
2519
+ prompt,
2520
+ role: typeof body.role === "string" ? body.role : void 0
2521
+ });
2522
+ }
2523
+ function onWindowMessage(event) {
2524
+ if (event.source !== iframeEl.value?.contentWindow) return;
2525
+ const msg = event.data;
2526
+ if (!msg || msg.slug !== props.slug) return;
2527
+ if (msg.type === "mc-open-item") handleOpenItem(msg);
2528
+ else if (msg.type === "mc-start-chat") handleStartChat(msg);
2529
+ }
2530
+ onMounted(() => window.addEventListener("message", onWindowMessage));
2531
+ onBeforeUnmount(() => {
2532
+ clearRefresh();
2533
+ changeUnsub?.();
2534
+ changeUnsub = null;
2535
+ window.removeEventListener("message", onWindowMessage);
2536
+ });
2434
2537
  return (_ctx, _cache) => {
2435
2538
  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", {
2539
+ ref_key: "iframeEl",
2540
+ ref: iframeEl,
2436
2541
  key: __props.view.id,
2437
2542
  "data-testid": "collection-custom-view-iframe",
2438
2543
  title: __props.view.label,
@@ -2452,9 +2557,13 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
2452
2557
  };
2453
2558
  //#endregion
2454
2559
  //#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"]]);
2560
+ var CollectionCustomView_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CollectionCustomView_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-d5fb7ca6"]]);
2456
2561
  //#endregion
2457
2562
  //#region src/vue/useCollectionRendering.ts
2563
+ function stepForFieldType(type) {
2564
+ if (type === "money") return "0.01";
2565
+ if (type === "number") return "any";
2566
+ }
2458
2567
  function useCollectionRendering(collection, locale) {
2459
2568
  const refCache = ref({});
2460
2569
  const refRecordCache = ref({});
@@ -2669,6 +2778,7 @@ function useCollectionRendering(collection, locale) {
2669
2778
  if (type === "datetime") return "datetime-local";
2670
2779
  return "text";
2671
2780
  }
2781
+ const stepFor = stepForFieldType;
2672
2782
  function evaluateDerivedAgainstItem(field, fieldKey, item) {
2673
2783
  if (!field.formula || !collection.value) return null;
2674
2784
  const result = deriveAll(collection.value.schema, item, refRecordCache.value)[fieldKey];
@@ -2700,6 +2810,7 @@ function useCollectionRendering(collection, locale) {
2700
2810
  hasTableRows,
2701
2811
  formatSubCell,
2702
2812
  inputTypeFor,
2813
+ stepFor,
2703
2814
  deriveAll,
2704
2815
  evaluateDerivedAgainstItem,
2705
2816
  derivedDisplay
@@ -2982,6 +3093,17 @@ var _hoisted_83 = { class: "px-6 py-5" };
2982
3093
  var _hoisted_84 = ["placeholder", "onKeydown"];
2983
3094
  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
3095
  var _hoisted_86 = ["disabled"];
3096
+ /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3097
+ * `presentCollection` chat card mounts this component and drives both
3098
+ * from the tool result). In standalone route mode (the
3099
+ * `/collections/:slug` page) both are undefined and the component reads
3100
+ * `route.params.slug` / `route.query.selected` as before.
3101
+ *
3102
+ * `sendTextMessage` is forwarded ONLY by the chat card — its presence
3103
+ * is our "rendered inside a chat" signal. When set, chat-triggering
3104
+ * actions send into the current session instead of spawning a new
3105
+ * chat (see `runAction` / `submitChat`). */
3106
+ var LIVE_REFRESH_DEBOUNCE_MS = 150;
2985
3107
  //#endregion
2986
3108
  //#region src/vue/components/CollectionView.vue
2987
3109
  var CollectionView_default = /* @__PURE__ */ defineComponent({
@@ -3304,13 +3426,13 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3304
3426
  * nothing. Instead, point the agent at the feed's schema + records
3305
3427
  * (`feeds/<slug>/schema.json` and `<dataPath>/`) and let it act on the
3306
3428
  * request directly. */
3307
- function buildChatSeed(slug, message) {
3429
+ function buildChatSeed(slug, message, itemId) {
3308
3430
  const schema = collection.value?.schema;
3309
- if (!schema?.ingest) return `/${slug} ${message}`;
3431
+ if (!schema?.ingest) return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
3310
3432
  return t("collectionsView.feedChatSeed", {
3311
3433
  slug,
3312
3434
  dataPath: schema.dataPath ?? `data/feeds/${slug}`,
3313
- message
3435
+ message: itemId ? `(for record \`${itemId}\`) ${message}` : message
3314
3436
  });
3315
3437
  }
3316
3438
  /** Start a new general-role chat seeded from the current view. */
@@ -3326,6 +3448,21 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3326
3448
  }
3327
3449
  appApi.startNewChat(text, cui.generalRoleId);
3328
3450
  }
3451
+ /** The open record's chat box: start a chat scoped to that one record. Seeds
3452
+ * the collection's skill command with an `id=<itemId>` selector
3453
+ * (`/<slug> id=<itemId> <message>`) so the agent acts on this record. */
3454
+ function onItemChat(message) {
3455
+ if (!collection.value || !viewing.value) return;
3456
+ const text = message.trim();
3457
+ if (!text) return;
3458
+ const itemId = String(viewing.value[collection.value.schema.primaryKey] ?? "");
3459
+ const seed = buildChatSeed(collection.value.slug, text, itemId || void 0);
3460
+ if (props.sendTextMessage) {
3461
+ props.sendTextMessage(seed);
3462
+ return;
3463
+ }
3464
+ appApi.startNewChat(seed, cui.generalRoleId);
3465
+ }
3329
3466
  async function loadCollection(slug) {
3330
3467
  const requestedKind = !embedded.value && cui.isFeedRoute() ? "feed" : "collection";
3331
3468
  loading.value = true;
@@ -3355,6 +3492,26 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3355
3492
  }
3356
3493
  maybeAutoRefreshFeed(slug);
3357
3494
  }
3495
+ /** Refresh records + schema IN PLACE for a live (pub/sub-driven) update,
3496
+ * preserving the user's browsing state — unlike `loadCollection`, which is the
3497
+ * route-change path and resets it. Specifically: does NOT null `collection`
3498
+ * (so the layout and an active custom-view iframe don't remount), keeps
3499
+ * `searchQuery` / `openDay` / `sortState`, and shows no loading spinner; the
3500
+ * open detail (`viewing`) is re-resolved against the fresh records by id, so it
3501
+ * follows an edited record and closes only if the record was deleted. A failed
3502
+ * fetch is a no-op (keep the current data) — a transient blip shouldn't blank a
3503
+ * view the user is reading. */
3504
+ async function refreshItemsInPlace(slug) {
3505
+ const result = await cui.fetchCollectionDetail(slug);
3506
+ if (!result.ok || activeSlug.value !== slug) return;
3507
+ collection.value = result.data.collection;
3508
+ items.value = result.data.items;
3509
+ dataIssues.value = result.data.issues ?? [];
3510
+ enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
3511
+ await render.loadLinkedCollections(result.data.collection.schema, slug);
3512
+ if (activeSlug.value !== slug) return;
3513
+ if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
3514
+ }
3358
3515
  function maybeAutoRefreshFeed(slug) {
3359
3516
  if (embedded.value) return;
3360
3517
  const current = collection.value;
@@ -3870,6 +4027,30 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3870
4027
  showDetail(item);
3871
4028
  writeSelectedToUrl(itemId);
3872
4029
  }
4030
+ /** A custom (sandboxed) view asked to open a record in the shared modal.
4031
+ * `view` → read-only detail, `edit` → straight into the editor. Ungated: the
4032
+ * capability token governs the view's *code*, not user actions through the
4033
+ * host's own trusted modal (no write happens without an explicit Save). */
4034
+ function onCustomViewOpenItem(payload) {
4035
+ const item = findItemById(payload.id);
4036
+ if (!item) return;
4037
+ if (editing.value) closeEditor();
4038
+ if (payload.mode === "edit") {
4039
+ openEdit(item);
4040
+ return;
4041
+ }
4042
+ showDetail(item);
4043
+ writeSelectedToUrl(payload.id);
4044
+ }
4045
+ /** The custom view called `__MC_VIEW.startChat(prompt, role)` — open a new chat
4046
+ * with the prompt prefilled as an editable draft. The host validates `role`
4047
+ * (falls back to General). The view's code only proposes text; the user
4048
+ * approves / edits / sends, so no capability is required. */
4049
+ function onCustomViewStartChat(payload) {
4050
+ const prompt = payload.prompt.trim();
4051
+ if (!prompt) return;
4052
+ cui.startNewChatDraft(prompt, payload.role);
4053
+ }
3873
4054
  /** A calendar day cell was activated → open its popup on a clean slate
3874
4055
  * (clear any prior selection so the popup opens timeline-only). */
3875
4056
  function onOpenDay(day) {
@@ -3925,6 +4106,42 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3925
4106
  loading.value = false;
3926
4107
  }
3927
4108
  }, { immediate: true });
4109
+ let changeUnsub = null;
4110
+ let liveRefreshTimer;
4111
+ let pendingRemoteRefresh = false;
4112
+ function clearLiveRefreshTimer() {
4113
+ if (liveRefreshTimer !== void 0) {
4114
+ clearTimeout(liveRefreshTimer);
4115
+ liveRefreshTimer = void 0;
4116
+ }
4117
+ }
4118
+ function onRemoteChange(slug) {
4119
+ clearLiveRefreshTimer();
4120
+ liveRefreshTimer = setTimeout(() => {
4121
+ liveRefreshTimer = void 0;
4122
+ if (editing.value) {
4123
+ pendingRemoteRefresh = true;
4124
+ return;
4125
+ }
4126
+ if (activeSlug.value === slug) refreshItemsInPlace(slug);
4127
+ }, LIVE_REFRESH_DEBOUNCE_MS);
4128
+ }
4129
+ watch(editing, (current) => {
4130
+ if (current || !pendingRemoteRefresh) return;
4131
+ pendingRemoteRefresh = false;
4132
+ if (activeSlug.value) refreshItemsInPlace(activeSlug.value);
4133
+ });
4134
+ watch(activeSlug, (slug) => {
4135
+ changeUnsub?.();
4136
+ changeUnsub = null;
4137
+ clearLiveRefreshTimer();
4138
+ if (slug && cui.subscribeChanges) changeUnsub = cui.subscribeChanges(slug, () => onRemoteChange(slug));
4139
+ }, { immediate: true });
4140
+ onUnmounted(() => {
4141
+ changeUnsub?.();
4142
+ changeUnsub = null;
4143
+ clearLiveRefreshTimer();
4144
+ });
3928
4145
  watch([
3929
4146
  activeView,
3930
4147
  calendarAnchorField,
@@ -4195,7 +4412,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4195
4412
  onEdit: editFromView,
4196
4413
  onClose: onDayClose,
4197
4414
  onDelete: _cache[9] || (_cache[9] = ($event) => viewing.value && confirmDelete(viewing.value)),
4198
- onRunAction: runAction
4415
+ onRunAction: runAction,
4416
+ onItemChat
4199
4417
  }, null, 8, [
4200
4418
  "editing",
4201
4419
  "collection",
@@ -4249,7 +4467,9 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4249
4467
  "notified"
4250
4468
  ])])])) : activeCustomView.value ? (openBlock(), createElementBlock("div", _hoisted_45, [createVNode(CollectionCustomView_default, {
4251
4469
  slug: collection.value.slug,
4252
- view: activeCustomView.value
4470
+ view: activeCustomView.value,
4471
+ onOpenItem: onCustomViewOpenItem,
4472
+ onStartChat: onCustomViewStartChat
4253
4473
  }, 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
4474
  _cache[42] || (_cache[42] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
4255
4475
  createElementVNode("p", _hoisted_49, toDisplayString(unref(t)("collectionsView.noMatchingItems")), 1),
@@ -4391,7 +4611,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4391
4611
  onEdit: editFromView,
4392
4612
  onClose: closeView,
4393
4613
  onDelete: _cache[20] || (_cache[20] = ($event) => viewing.value && confirmDelete(viewing.value)),
4394
- onRunAction: runAction
4614
+ onRunAction: runAction,
4615
+ onItemChat
4395
4616
  }, null, 8, [
4396
4617
  "editing",
4397
4618
  "collection",