@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.cjs CHANGED
@@ -70,6 +70,8 @@ var i18n = (0, vue_i18n.createI18n)({
70
70
  chatTitle: "Start a chat",
71
71
  chatPlaceholder: "Describe what you want to do with this collection…",
72
72
  chatStart: "Start chat",
73
+ itemChatLabel: "Chat about this record",
74
+ itemChatPlaceholder: "Ask or instruct about this record…",
73
75
  viewToggle: "View",
74
76
  viewTable: "Table",
75
77
  viewCalendar: "Calendar",
@@ -171,6 +173,8 @@ var i18n = (0, vue_i18n.createI18n)({
171
173
  chatTitle: "チャットを開始",
172
174
  chatPlaceholder: "このコレクションで行いたいことを入力してください…",
173
175
  chatStart: "チャットを開始",
176
+ itemChatLabel: "このレコードについてチャット",
177
+ itemChatPlaceholder: "このレコードについて質問・指示する…",
174
178
  viewToggle: "表示",
175
179
  viewTable: "テーブル",
176
180
  viewCalendar: "カレンダー",
@@ -272,6 +276,8 @@ var i18n = (0, vue_i18n.createI18n)({
272
276
  chatTitle: "开始对话",
273
277
  chatPlaceholder: "描述你想对这个集合做什么…",
274
278
  chatStart: "开始对话",
279
+ itemChatLabel: "就此记录对话",
280
+ itemChatPlaceholder: "询问或指示有关此记录的操作…",
275
281
  viewToggle: "视图",
276
282
  viewTable: "表格",
277
283
  viewCalendar: "日历",
@@ -373,6 +379,8 @@ var i18n = (0, vue_i18n.createI18n)({
373
379
  chatTitle: "채팅 시작",
374
380
  chatPlaceholder: "이 컬렉션으로 하고 싶은 작업을 설명하세요…",
375
381
  chatStart: "채팅 시작",
382
+ itemChatLabel: "이 레코드에 대해 채팅",
383
+ itemChatPlaceholder: "이 레코드에 대해 질문하거나 지시하세요…",
376
384
  viewToggle: "보기",
377
385
  viewTable: "표",
378
386
  viewCalendar: "캘린더",
@@ -474,6 +482,8 @@ var i18n = (0, vue_i18n.createI18n)({
474
482
  chatTitle: "Iniciar un chat",
475
483
  chatPlaceholder: "Describe qué quieres hacer con esta colección…",
476
484
  chatStart: "Iniciar chat",
485
+ itemChatLabel: "Chatear sobre este registro",
486
+ itemChatPlaceholder: "Pregunta o indica algo sobre este registro…",
477
487
  viewToggle: "Vista",
478
488
  viewTable: "Tabla",
479
489
  viewCalendar: "Calendario",
@@ -575,6 +585,8 @@ var i18n = (0, vue_i18n.createI18n)({
575
585
  chatTitle: "Iniciar um chat",
576
586
  chatPlaceholder: "Descreva o que você quer fazer com esta coleção…",
577
587
  chatStart: "Iniciar chat",
588
+ itemChatLabel: "Conversar sobre este registro",
589
+ itemChatPlaceholder: "Pergunte ou instrua sobre este registro…",
578
590
  viewToggle: "Visualização",
579
591
  viewTable: "Tabela",
580
592
  viewCalendar: "Calendário",
@@ -676,6 +688,8 @@ var i18n = (0, vue_i18n.createI18n)({
676
688
  chatTitle: "Démarrer une discussion",
677
689
  chatPlaceholder: "Décrivez ce que vous voulez faire avec cette collection…",
678
690
  chatStart: "Démarrer la discussion",
691
+ itemChatLabel: "Discuter de cet enregistrement",
692
+ itemChatPlaceholder: "Posez une question ou donnez une instruction sur cet enregistrement…",
679
693
  viewToggle: "Affichage",
680
694
  viewTable: "Tableau",
681
695
  viewCalendar: "Calendrier",
@@ -777,6 +791,8 @@ var i18n = (0, vue_i18n.createI18n)({
777
791
  chatTitle: "Chat starten",
778
792
  chatPlaceholder: "Beschreibe, was du mit dieser Sammlung tun möchtest…",
779
793
  chatStart: "Chat starten",
794
+ itemChatLabel: "Über diesen Eintrag chatten",
795
+ itemChatPlaceholder: "Stelle eine Frage oder gib eine Anweisung zu diesem Eintrag…",
780
796
  viewToggle: "Ansicht",
781
797
  viewTable: "Tabelle",
782
798
  viewCalendar: "Kalender",
@@ -1758,6 +1774,7 @@ var _hoisted_36$1 = ["onUpdate:modelValue", "required"];
1758
1774
  var _hoisted_37$1 = [
1759
1775
  "onUpdate:modelValue",
1760
1776
  "type",
1777
+ "step",
1761
1778
  "required"
1762
1779
  ];
1763
1780
  var _hoisted_38$1 = { class: "text-center px-1" };
@@ -1786,6 +1803,7 @@ var _hoisted_45$1 = [
1786
1803
  "id",
1787
1804
  "onUpdate:modelValue",
1788
1805
  "type",
1806
+ "step",
1789
1807
  "required",
1790
1808
  "disabled",
1791
1809
  "data-testid"
@@ -1876,6 +1894,18 @@ var _hoisted_69$1 = {
1876
1894
  key: 0,
1877
1895
  class: "col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl"
1878
1896
  };
1897
+ var _hoisted_70$1 = {
1898
+ key: 1,
1899
+ class: "mt-5 pt-4 border-t border-slate-200/60",
1900
+ "data-testid": "collections-detail-chat"
1901
+ };
1902
+ var _hoisted_71$1 = {
1903
+ class: "block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1.5",
1904
+ for: "collections-detail-chat-input"
1905
+ };
1906
+ var _hoisted_72$1 = { class: "flex items-end gap-2" };
1907
+ var _hoisted_73$1 = ["placeholder", "onKeydown"];
1908
+ var _hoisted_74$1 = ["disabled"];
1879
1909
  //#endregion
1880
1910
  //#region src/vue/components/CollectionRecordPanel.vue
1881
1911
  var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -1904,7 +1934,8 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
1904
1934
  "edit",
1905
1935
  "close",
1906
1936
  "delete",
1907
- "runAction"
1937
+ "runAction",
1938
+ "itemChat"
1908
1939
  ], ["update:editing"]),
1909
1940
  setup(__props, { emit: __emit }) {
1910
1941
  const cui = collectionUi();
@@ -1913,6 +1944,16 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
1913
1944
  const editing = (0, vue.useModel)(__props, "editing");
1914
1945
  const emit = __emit;
1915
1946
  const { t } = useCollectionI18n();
1947
+ const chatMessage = (0, vue.ref)("");
1948
+ (0, vue.watch)(() => props.viewing, () => {
1949
+ chatMessage.value = "";
1950
+ });
1951
+ function submitItemChat() {
1952
+ const message = chatMessage.value.trim();
1953
+ if (!message) return;
1954
+ emit("itemChat", message);
1955
+ chatMessage.value = "";
1956
+ }
1916
1957
  const embedViews = (0, vue.computed)(() => props.render.embedViews.value);
1917
1958
  /** The record the read-only displays render from: the live draft while
1918
1959
  * editing (so non-editable cells like derived/embed preview the in-flight
@@ -1991,7 +2032,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
1991
2032
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(editing.value ? "form" : "div"), {
1992
2033
  class: "px-6 py-5 max-h-[60vh] overflow-y-auto",
1993
2034
  "data-testid": rootTestid.value,
1994
- onSubmit: _cache[4] || (_cache[4] = (0, vue.withModifiers)(($event) => emit("submit"), ["prevent"]))
2035
+ onSubmit: _cache[5] || (_cache[5] = (0, vue.withModifiers)(($event) => emit("submit"), ["prevent"]))
1995
2036
  }, {
1996
2037
  default: (0, vue.withCtx)(() => [
1997
2038
  (0, vue.createElementVNode)("div", _hoisted_1$7, [(0, vue.createElementVNode)("div", _hoisted_2$6, [(0, vue.createElementVNode)("span", _hoisted_3$6, (0, vue.toDisplayString)(__props.collection.title), 1), (0, vue.createElementVNode)("h2", {
@@ -2023,20 +2064,20 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2023
2064
  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",
2024
2065
  "data-testid": "collections-detail-edit",
2025
2066
  onClick: _cache[1] || (_cache[1] = ($event) => emit("edit"))
2026
- }, [_cache[5] || (_cache[5] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "edit", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.editItem")), 1)]),
2067
+ }, [_cache[6] || (_cache[6] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "edit", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.editItem")), 1)]),
2027
2068
  (0, vue.createElementVNode)("button", {
2028
2069
  type: "button",
2029
2070
  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",
2030
2071
  "data-testid": "collections-detail-remove",
2031
2072
  onClick: _cache[2] || (_cache[2] = ($event) => emit("delete"))
2032
- }, [_cache[6] || (_cache[6] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.remove")), 1)]),
2073
+ }, [_cache[7] || (_cache[7] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.remove")), 1)]),
2033
2074
  (0, vue.createElementVNode)("button", {
2034
2075
  type: "button",
2035
2076
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors",
2036
2077
  "aria-label": (0, vue.unref)(t)("common.close"),
2037
2078
  "data-testid": "collections-detail-close",
2038
2079
  onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
2039
- }, [..._cache[7] || (_cache[7] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9$4)
2080
+ }, [..._cache[8] || (_cache[8] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9$4)
2040
2081
  ]))]),
2041
2082
  !editing.value && __props.actionError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_10$4, (0, vue.toDisplayString)(__props.actionError), 1)) : (0, vue.createCommentVNode)("", true),
2042
2083
  (0, vue.createElementVNode)("div", _hoisted_11$4, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.collection.schema.fields, (field, key) => {
@@ -2086,7 +2127,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2086
2127
  key: subKey,
2087
2128
  class: "text-left px-3 py-2 font-bold"
2088
2129
  }, (0, vue.toDisplayString)(subField.label), 1);
2089
- }), 128)), _cache[8] || (_cache[8] = (0, vue.createElementVNode)("th", { class: "w-9" }, null, -1))])]), (0, vue.createElementVNode)("tbody", _hoisted_26$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(editing.value.table[key], (row, rowIdx) => {
2130
+ }), 128)), _cache[9] || (_cache[9] = (0, vue.createElementVNode)("th", { class: "w-9" }, null, -1))])]), (0, vue.createElementVNode)("tbody", _hoisted_26$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(editing.value.table[key], (row, rowIdx) => {
2090
2131
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
2091
2132
  key: rowIdx,
2092
2133
  class: "hover:bg-slate-50/50"
@@ -2130,6 +2171,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2130
2171
  key: 4,
2131
2172
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2132
2173
  type: __props.render.inputTypeFor(subField.type),
2174
+ step: __props.render.stepFor(subField.type),
2133
2175
  required: subField.required,
2134
2176
  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"
2135
2177
  }, null, 8, _hoisted_37$1)), [[vue.vModelDynamic, row.text[subKey]]])]);
@@ -2139,13 +2181,13 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2139
2181
  "aria-label": (0, vue.unref)(t)("collectionsView.removeRow"),
2140
2182
  "data-testid": `collections-table-${key}-remove-${rowIdx}`,
2141
2183
  onClick: ($event) => removeTableRow(String(key), rowIdx)
2142
- }, [..._cache[9] || (_cache[9] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_39$1)])]);
2184
+ }, [..._cache[10] || (_cache[10] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_39$1)])]);
2143
2185
  }), 128))])])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_40$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 1)), (0, vue.createElementVNode)("button", {
2144
2186
  type: "button",
2145
2187
  class: "inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline",
2146
2188
  "data-testid": `collections-table-${key}-add`,
2147
2189
  onClick: ($event) => addTableRow(String(key), field.of)
2148
- }, [_cache[10] || (_cache[10] = (0, vue.createElementVNode)("span", { class: "material-icons text-xs" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addRow")), 1)], 8, _hoisted_41$1)], 8, _hoisted_22$2)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42$1, [(0, vue.createElementVNode)("div", _hoisted_43$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2190
+ }, [_cache[11] || (_cache[11] = (0, vue.createElementVNode)("span", { class: "material-icons text-xs" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addRow")), 1)], 8, _hoisted_41$1)], 8, _hoisted_22$2)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42$1, [(0, vue.createElementVNode)("div", _hoisted_43$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2149
2191
  id: `collections-field-${key}`,
2150
2192
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2151
2193
  type: "number",
@@ -2167,6 +2209,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2167
2209
  id: `collections-field-${key}`,
2168
2210
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2169
2211
  type: __props.render.inputTypeFor(field.type),
2212
+ step: __props.render.stepFor(field.type),
2170
2213
  required: isFieldRequiredInUi(field),
2171
2214
  disabled: field.primary === true && (editing.value.mode === "edit" || __props.isSingleton),
2172
2215
  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",
@@ -2183,7 +2226,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2183
2226
  key: 1,
2184
2227
  class: "text-xs font-medium text-slate-700 break-words",
2185
2228
  "data-testid": `collections-detail-value-${key}`
2186
- }, [field.type === "toggle" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_48$1, [_cache[11] || (_cache[11] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_49$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1))], 64)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [detailRecord.value[key] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_50$1, [_cache[12] || (_cache[12] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_51$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_52$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2229
+ }, [field.type === "toggle" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_48$1, [_cache[12] || (_cache[12] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_49$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1))], 64)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [detailRecord.value[key] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_50$1, [_cache[13] || (_cache[13] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_51$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_52$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2187
2230
  key: 2,
2188
2231
  href: (0, vue.unref)(cui).recordHref?.(field.to, String(detailRecord.value[key])),
2189
2232
  tabindex: (0, vue.unref)(cui).recordHref?.(field.to, String(detailRecord.value[key])) ? void 0 : 0,
@@ -2241,7 +2284,22 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2241
2284
  "data-testid": `collections-detail-file-${key}`,
2242
2285
  onClick: ($event) => (0, vue.unref)(activatePathLink)($event, __props.render.fileRoutePath(detailRecord.value[key]) ?? "")
2243
2286
  }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_67$1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_68$1, (0, vue.toDisplayString)(__props.render.formatCell(detailRecord.value[key], field.type)), 1))], 8, _hoisted_47$1))], 2)) : (0, vue.createCommentVNode)("", true)], 64);
2244
- }), 128)), editing.value && __props.saveError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_69$1, (0, vue.toDisplayString)(__props.saveError), 1)) : (0, vue.createCommentVNode)("", true)])
2287
+ }), 128)), editing.value && __props.saveError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_69$1, (0, vue.toDisplayString)(__props.saveError), 1)) : (0, vue.createCommentVNode)("", true)]),
2288
+ !editing.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_70$1, [(0, vue.createElementVNode)("label", _hoisted_71$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemChatLabel")), 1), (0, vue.createElementVNode)("div", _hoisted_72$1, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
2289
+ id: "collections-detail-chat-input",
2290
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => chatMessage.value = $event),
2291
+ rows: "2",
2292
+ placeholder: (0, vue.unref)(t)("collectionsView.itemChatPlaceholder"),
2293
+ 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",
2294
+ "data-testid": "collections-detail-chat-input",
2295
+ onKeydown: [(0, vue.withKeys)((0, vue.withModifiers)(submitItemChat, ["meta"]), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(submitItemChat, ["ctrl"]), ["enter"])]
2296
+ }, null, 40, _hoisted_73$1), [[vue.vModelText, chatMessage.value]]), (0, vue.createElementVNode)("button", {
2297
+ type: "button",
2298
+ 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",
2299
+ disabled: !chatMessage.value.trim(),
2300
+ "data-testid": "collections-detail-chat-send",
2301
+ onClick: submitItemChat
2302
+ }, [_cache[14] || (_cache[14] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "forum", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chat")), 1)], 8, _hoisted_74$1)])])) : (0, vue.createCommentVNode)("", true)
2245
2303
  ]),
2246
2304
  _: 1
2247
2305
  }, 40, ["data-testid"]);
@@ -2379,12 +2437,15 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2379
2437
  slug: {},
2380
2438
  view: {}
2381
2439
  },
2382
- setup(__props) {
2440
+ emits: ["openItem", "startChat"],
2441
+ setup(__props, { emit: __emit }) {
2383
2442
  const { t } = useCollectionI18n();
2384
2443
  const props = __props;
2444
+ const emit = __emit;
2385
2445
  const loading = (0, vue.ref)(true);
2386
2446
  const error = (0, vue.ref)(null);
2387
2447
  const srcdoc = (0, vue.ref)(null);
2448
+ const iframeEl = (0, vue.ref)(null);
2388
2449
  let refreshTimer;
2389
2450
  function clearRefresh() {
2390
2451
  if (refreshTimer !== void 0) {
@@ -2433,9 +2494,53 @@ var CollectionCustomView_vue_vue_type_script_setup_true_lang_default = /* @__PUR
2433
2494
  }
2434
2495
  }
2435
2496
  (0, vue.watch)([() => props.slug, () => props.view.id], () => void load(), { immediate: true });
2436
- (0, vue.onBeforeUnmount)(clearRefresh);
2497
+ let changeUnsub = null;
2498
+ function relayChange() {
2499
+ iframeEl.value?.contentWindow?.postMessage({
2500
+ type: "mc-collection-changed",
2501
+ slug: props.slug
2502
+ }, "*");
2503
+ }
2504
+ (0, vue.watch)(() => props.slug, (slug) => {
2505
+ changeUnsub?.();
2506
+ changeUnsub = null;
2507
+ const subscribe = collectionUi().subscribeChanges;
2508
+ if (slug && subscribe) changeUnsub = subscribe(slug, relayChange);
2509
+ }, { immediate: true });
2510
+ function handleOpenItem(body) {
2511
+ const itemId = typeof body.id === "string" ? body.id : String(body.id ?? "");
2512
+ if (!itemId) return;
2513
+ emit("openItem", {
2514
+ id: itemId,
2515
+ mode: body.mode === "edit" ? "edit" : "view"
2516
+ });
2517
+ }
2518
+ function handleStartChat(body) {
2519
+ const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
2520
+ if (!prompt) return;
2521
+ emit("startChat", {
2522
+ prompt,
2523
+ role: typeof body.role === "string" ? body.role : void 0
2524
+ });
2525
+ }
2526
+ function onWindowMessage(event) {
2527
+ if (event.source !== iframeEl.value?.contentWindow) return;
2528
+ const msg = event.data;
2529
+ if (!msg || msg.slug !== props.slug) return;
2530
+ if (msg.type === "mc-open-item") handleOpenItem(msg);
2531
+ else if (msg.type === "mc-start-chat") handleStartChat(msg);
2532
+ }
2533
+ (0, vue.onMounted)(() => window.addEventListener("message", onWindowMessage));
2534
+ (0, vue.onBeforeUnmount)(() => {
2535
+ clearRefresh();
2536
+ changeUnsub?.();
2537
+ changeUnsub = null;
2538
+ window.removeEventListener("message", onWindowMessage);
2539
+ });
2437
2540
  return (_ctx, _cache) => {
2438
2541
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$5, [error.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$4, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.customViewError", { error: error.value })), 1)) : loading.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_3$4, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.customViewLoading")), 1)) : srcdoc.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("iframe", {
2542
+ ref_key: "iframeEl",
2543
+ ref: iframeEl,
2439
2544
  key: __props.view.id,
2440
2545
  "data-testid": "collection-custom-view-iframe",
2441
2546
  title: __props.view.label,
@@ -2455,9 +2560,13 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
2455
2560
  };
2456
2561
  //#endregion
2457
2562
  //#region src/vue/components/CollectionCustomView.vue
2458
- var CollectionCustomView_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CollectionCustomView_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-3ab71997"]]);
2563
+ var CollectionCustomView_default = /* @__PURE__ */ _plugin_vue_export_helper_default(CollectionCustomView_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-d5fb7ca6"]]);
2459
2564
  //#endregion
2460
2565
  //#region src/vue/useCollectionRendering.ts
2566
+ function stepForFieldType(type) {
2567
+ if (type === "money") return "0.01";
2568
+ if (type === "number") return "any";
2569
+ }
2461
2570
  function useCollectionRendering(collection, locale) {
2462
2571
  const refCache = (0, vue.ref)({});
2463
2572
  const refRecordCache = (0, vue.ref)({});
@@ -2672,6 +2781,7 @@ function useCollectionRendering(collection, locale) {
2672
2781
  if (type === "datetime") return "datetime-local";
2673
2782
  return "text";
2674
2783
  }
2784
+ const stepFor = stepForFieldType;
2675
2785
  function evaluateDerivedAgainstItem(field, fieldKey, item) {
2676
2786
  if (!field.formula || !collection.value) return null;
2677
2787
  const result = require_deriveAll.deriveAll(collection.value.schema, item, refRecordCache.value)[fieldKey];
@@ -2703,6 +2813,7 @@ function useCollectionRendering(collection, locale) {
2703
2813
  hasTableRows,
2704
2814
  formatSubCell,
2705
2815
  inputTypeFor,
2816
+ stepFor,
2706
2817
  deriveAll: require_deriveAll.deriveAll,
2707
2818
  evaluateDerivedAgainstItem,
2708
2819
  derivedDisplay
@@ -2985,6 +3096,17 @@ var _hoisted_83 = { class: "px-6 py-5" };
2985
3096
  var _hoisted_84 = ["placeholder", "onKeydown"];
2986
3097
  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" };
2987
3098
  var _hoisted_86 = ["disabled"];
3099
+ /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3100
+ * `presentCollection` chat card mounts this component and drives both
3101
+ * from the tool result). In standalone route mode (the
3102
+ * `/collections/:slug` page) both are undefined and the component reads
3103
+ * `route.params.slug` / `route.query.selected` as before.
3104
+ *
3105
+ * `sendTextMessage` is forwarded ONLY by the chat card — its presence
3106
+ * is our "rendered inside a chat" signal. When set, chat-triggering
3107
+ * actions send into the current session instead of spawning a new
3108
+ * chat (see `runAction` / `submitChat`). */
3109
+ var LIVE_REFRESH_DEBOUNCE_MS = 150;
2988
3110
  //#endregion
2989
3111
  //#region src/vue/components/CollectionView.vue
2990
3112
  var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -3307,13 +3429,13 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3307
3429
  * nothing. Instead, point the agent at the feed's schema + records
3308
3430
  * (`feeds/<slug>/schema.json` and `<dataPath>/`) and let it act on the
3309
3431
  * request directly. */
3310
- function buildChatSeed(slug, message) {
3432
+ function buildChatSeed(slug, message, itemId) {
3311
3433
  const schema = collection.value?.schema;
3312
- if (!schema?.ingest) return `/${slug} ${message}`;
3434
+ if (!schema?.ingest) return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
3313
3435
  return t("collectionsView.feedChatSeed", {
3314
3436
  slug,
3315
3437
  dataPath: schema.dataPath ?? `data/feeds/${slug}`,
3316
- message
3438
+ message: itemId ? `(for record \`${itemId}\`) ${message}` : message
3317
3439
  });
3318
3440
  }
3319
3441
  /** Start a new general-role chat seeded from the current view. */
@@ -3329,6 +3451,21 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3329
3451
  }
3330
3452
  appApi.startNewChat(text, cui.generalRoleId);
3331
3453
  }
3454
+ /** The open record's chat box: start a chat scoped to that one record. Seeds
3455
+ * the collection's skill command with an `id=<itemId>` selector
3456
+ * (`/<slug> id=<itemId> <message>`) so the agent acts on this record. */
3457
+ function onItemChat(message) {
3458
+ if (!collection.value || !viewing.value) return;
3459
+ const text = message.trim();
3460
+ if (!text) return;
3461
+ const itemId = String(viewing.value[collection.value.schema.primaryKey] ?? "");
3462
+ const seed = buildChatSeed(collection.value.slug, text, itemId || void 0);
3463
+ if (props.sendTextMessage) {
3464
+ props.sendTextMessage(seed);
3465
+ return;
3466
+ }
3467
+ appApi.startNewChat(seed, cui.generalRoleId);
3468
+ }
3332
3469
  async function loadCollection(slug) {
3333
3470
  const requestedKind = !embedded.value && cui.isFeedRoute() ? "feed" : "collection";
3334
3471
  loading.value = true;
@@ -3358,6 +3495,26 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3358
3495
  }
3359
3496
  maybeAutoRefreshFeed(slug);
3360
3497
  }
3498
+ /** Refresh records + schema IN PLACE for a live (pub/sub-driven) update,
3499
+ * preserving the user's browsing state — unlike `loadCollection`, which is the
3500
+ * route-change path and resets it. Specifically: does NOT null `collection`
3501
+ * (so the layout and an active custom-view iframe don't remount), keeps
3502
+ * `searchQuery` / `openDay` / `sortState`, and shows no loading spinner; the
3503
+ * open detail (`viewing`) is re-resolved against the fresh records by id, so it
3504
+ * follows an edited record and closes only if the record was deleted. A failed
3505
+ * fetch is a no-op (keep the current data) — a transient blip shouldn't blank a
3506
+ * view the user is reading. */
3507
+ async function refreshItemsInPlace(slug) {
3508
+ const result = await cui.fetchCollectionDetail(slug);
3509
+ if (!result.ok || activeSlug.value !== slug) return;
3510
+ collection.value = result.data.collection;
3511
+ items.value = result.data.items;
3512
+ dataIssues.value = result.data.issues ?? [];
3513
+ enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
3514
+ await render.loadLinkedCollections(result.data.collection.schema, slug);
3515
+ if (activeSlug.value !== slug) return;
3516
+ if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
3517
+ }
3361
3518
  function maybeAutoRefreshFeed(slug) {
3362
3519
  if (embedded.value) return;
3363
3520
  const current = collection.value;
@@ -3873,6 +4030,30 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3873
4030
  showDetail(item);
3874
4031
  writeSelectedToUrl(itemId);
3875
4032
  }
4033
+ /** A custom (sandboxed) view asked to open a record in the shared modal.
4034
+ * `view` → read-only detail, `edit` → straight into the editor. Ungated: the
4035
+ * capability token governs the view's *code*, not user actions through the
4036
+ * host's own trusted modal (no write happens without an explicit Save). */
4037
+ function onCustomViewOpenItem(payload) {
4038
+ const item = findItemById(payload.id);
4039
+ if (!item) return;
4040
+ if (editing.value) closeEditor();
4041
+ if (payload.mode === "edit") {
4042
+ openEdit(item);
4043
+ return;
4044
+ }
4045
+ showDetail(item);
4046
+ writeSelectedToUrl(payload.id);
4047
+ }
4048
+ /** The custom view called `__MC_VIEW.startChat(prompt, role)` — open a new chat
4049
+ * with the prompt prefilled as an editable draft. The host validates `role`
4050
+ * (falls back to General). The view's code only proposes text; the user
4051
+ * approves / edits / sends, so no capability is required. */
4052
+ function onCustomViewStartChat(payload) {
4053
+ const prompt = payload.prompt.trim();
4054
+ if (!prompt) return;
4055
+ cui.startNewChatDraft(prompt, payload.role);
4056
+ }
3876
4057
  /** A calendar day cell was activated → open its popup on a clean slate
3877
4058
  * (clear any prior selection so the popup opens timeline-only). */
3878
4059
  function onOpenDay(day) {
@@ -3928,6 +4109,42 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3928
4109
  loading.value = false;
3929
4110
  }
3930
4111
  }, { immediate: true });
4112
+ let changeUnsub = null;
4113
+ let liveRefreshTimer;
4114
+ let pendingRemoteRefresh = false;
4115
+ function clearLiveRefreshTimer() {
4116
+ if (liveRefreshTimer !== void 0) {
4117
+ clearTimeout(liveRefreshTimer);
4118
+ liveRefreshTimer = void 0;
4119
+ }
4120
+ }
4121
+ function onRemoteChange(slug) {
4122
+ clearLiveRefreshTimer();
4123
+ liveRefreshTimer = setTimeout(() => {
4124
+ liveRefreshTimer = void 0;
4125
+ if (editing.value) {
4126
+ pendingRemoteRefresh = true;
4127
+ return;
4128
+ }
4129
+ if (activeSlug.value === slug) refreshItemsInPlace(slug);
4130
+ }, LIVE_REFRESH_DEBOUNCE_MS);
4131
+ }
4132
+ (0, vue.watch)(editing, (current) => {
4133
+ if (current || !pendingRemoteRefresh) return;
4134
+ pendingRemoteRefresh = false;
4135
+ if (activeSlug.value) refreshItemsInPlace(activeSlug.value);
4136
+ });
4137
+ (0, vue.watch)(activeSlug, (slug) => {
4138
+ changeUnsub?.();
4139
+ changeUnsub = null;
4140
+ clearLiveRefreshTimer();
4141
+ if (slug && cui.subscribeChanges) changeUnsub = cui.subscribeChanges(slug, () => onRemoteChange(slug));
4142
+ }, { immediate: true });
4143
+ (0, vue.onUnmounted)(() => {
4144
+ changeUnsub?.();
4145
+ changeUnsub = null;
4146
+ clearLiveRefreshTimer();
4147
+ });
3931
4148
  (0, vue.watch)([
3932
4149
  activeView,
3933
4150
  calendarAnchorField,
@@ -4198,7 +4415,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4198
4415
  onEdit: editFromView,
4199
4416
  onClose: onDayClose,
4200
4417
  onDelete: _cache[9] || (_cache[9] = ($event) => viewing.value && confirmDelete(viewing.value)),
4201
- onRunAction: runAction
4418
+ onRunAction: runAction,
4419
+ onItemChat
4202
4420
  }, null, 8, [
4203
4421
  "editing",
4204
4422
  "collection",
@@ -4252,7 +4470,9 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4252
4470
  "notified"
4253
4471
  ])])])) : activeCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_45, [(0, vue.createVNode)(CollectionCustomView_default, {
4254
4472
  slug: collection.value.slug,
4255
- view: activeCustomView.value
4473
+ view: activeCustomView.value,
4474
+ onOpenItem: onCustomViewOpenItem,
4475
+ onStartChat: onCustomViewStartChat
4256
4476
  }, null, 8, ["slug", "view"])])) : items.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_46, [_cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), (0, vue.createElementVNode)("p", _hoisted_47, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [
4257
4477
  _cache[42] || (_cache[42] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
4258
4478
  (0, vue.createElementVNode)("p", _hoisted_49, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noMatchingItems")), 1),
@@ -4394,7 +4614,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4394
4614
  onEdit: editFromView,
4395
4615
  onClose: closeView,
4396
4616
  onDelete: _cache[20] || (_cache[20] = ($event) => viewing.value && confirmDelete(viewing.value)),
4397
- onRunAction: runAction
4617
+ onRunAction: runAction,
4618
+ onItemChat
4398
4619
  }, null, 8, [
4399
4620
  "editing",
4400
4621
  "collection",