@mulmoclaude/collection-plugin 0.7.4 → 0.7.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.cjs CHANGED
@@ -142,6 +142,8 @@ var i18n = (0, vue_i18n.createI18n)({
142
142
  kanbanUncategorized: "Uncategorized",
143
143
  kanbanOpenCard: "Open {label}",
144
144
  addView: "Add view",
145
+ addViewDesktop: "Custom view",
146
+ addViewMobile: "Phone view",
145
147
  config: {
146
148
  open: "Collection settings",
147
149
  title: "{title} · settings",
@@ -153,6 +155,7 @@ var i18n = (0, vue_i18n.createI18n)({
153
155
  customViewLoading: "Loading view…",
154
156
  customViewError: "Couldn't load this view: {error}",
155
157
  addViewPrompt: "I want to add a custom view to the \"{title}\" collection. Ask me what I want to see or edit, then author the HTML view file at {base}/views/your-view.html and register it in {base}/schema.json under `views[]` (capabilities [\"read\"] for a read-only view, [\"read\",\"write\"] if it edits records). Follow the custom-view help for the data contract.",
158
+ addMobileViewPrompt: "I want to add a phone (remote app) custom view to the \"{title}\" collection. Ask me what I want to see or edit, then author the HTML view file at {base}/views/your-view.html and register it in {base}/schema.json under `views[]` with `target: \"mobile\"` (read-only by default; declare `editableFields` / `allowDelete` only if it edits records). Follow the custom-view-remote help for the postMessage data contract — no fetch of any kind in the view.",
156
159
  repair: "Repair",
157
160
  dataIssuesDetected: "{count} record file(s) have data problems and may be missing from this view.",
158
161
  repairPrompt: "The \"{title}\" collection has {count} record file(s) with data problems that keep them from appearing. Fix each one — Read the file, correct it, then Write it back:\n{issues}\n\nWhen you're done, call presentCollection to confirm the records load.",
@@ -275,6 +278,8 @@ var i18n = (0, vue_i18n.createI18n)({
275
278
  kanbanUncategorized: "未分類",
276
279
  kanbanOpenCard: "{label} を開く",
277
280
  addView: "ビューを追加",
281
+ addViewDesktop: "カスタムビュー",
282
+ addViewMobile: "スマホビュー",
278
283
  config: {
279
284
  open: "コレクション設定",
280
285
  title: "{title} · 設定",
@@ -286,6 +291,7 @@ var i18n = (0, vue_i18n.createI18n)({
286
291
  customViewLoading: "ビューを読み込み中…",
287
292
  customViewError: "このビューを読み込めませんでした: {error}",
288
293
  addViewPrompt: "「{title}」コレクションにカスタムビューを追加したい。何を見たい/編集したいか質問してから、HTML ビューファイルを {base}/views/your-view.html に作成し、{base}/schema.json の `views[]` に登録して(読み取り専用なら capabilities [\"read\"]、レコードを編集するなら [\"read\",\"write\"])。データ契約は custom-view ヘルプに従ってください。",
294
+ addMobileViewPrompt: "「{title}」コレクションにスマホ(リモートアプリ)用カスタムビューを追加したい。何を見たい/編集したいか質問してから、HTML ビューファイルを {base}/views/your-view.html に作成し、{base}/schema.json の `views[]` に `target: \"mobile\"` 付きで登録して(デフォルトは読み取り専用。レコードを編集する場合のみ `editableFields` / `allowDelete` を宣言)。データ契約は custom-view-remote ヘルプに従ってください — ビュー内での fetch は一切禁止です。",
289
295
  repair: "修復",
290
296
  dataIssuesDetected: "{count} 件のレコードファイルにデータの問題があり、この表示に出てこない可能性があります。",
291
297
  repairPrompt: "コレクション「{title}」に、表示されない原因となるデータの問題を持つレコードファイルが {count} 件あります。それぞれを修正してください — ファイルを Read し、修正してから Write し直します:\n{issues}\n\n完了したら presentCollection を呼び出して、レコードが読み込めることを確認してください。",
@@ -408,6 +414,8 @@ var i18n = (0, vue_i18n.createI18n)({
408
414
  kanbanUncategorized: "未分类",
409
415
  kanbanOpenCard: "打开 {label}",
410
416
  addView: "添加视图",
417
+ addViewDesktop: "自定义视图",
418
+ addViewMobile: "手机视图",
411
419
  config: {
412
420
  open: "集合设置",
413
421
  title: "{title} · 设置",
@@ -419,6 +427,7 @@ var i18n = (0, vue_i18n.createI18n)({
419
427
  customViewLoading: "正在加载视图…",
420
428
  customViewError: "无法加载此视图:{error}",
421
429
  addViewPrompt: "我想为“{title}”集合添加一个自定义视图。先问我想查看或编辑什么,然后在 {base}/views/your-view.html 创建 HTML 视图文件,并在 {base}/schema.json 的 `views[]` 中注册(只读视图用 capabilities [\"read\"],需要编辑记录则用 [\"read\",\"write\"])。数据契约请遵循 custom-view 帮助文档。",
430
+ addMobileViewPrompt: "我想为“{title}”集合添加一个手机(远程应用)自定义视图。先问我想查看或编辑什么,然后在 {base}/views/your-view.html 创建 HTML 视图文件,并在 {base}/schema.json 的 `views[]` 中以 `target: \"mobile\"` 注册(默认只读;仅在需要编辑记录时声明 `editableFields` / `allowDelete`)。数据契约请遵循 custom-view-remote 帮助文档 — 视图内禁止任何 fetch。",
422
431
  repair: "修复",
423
432
  dataIssuesDetected: "有 {count} 个记录文件存在数据问题,可能未显示在此视图中。",
424
433
  repairPrompt: "集合 {title} 有 {count} 个记录文件存在导致无法显示的数据问题。请逐一修复——用 Read 读取该文件,更正后再用 Write 写回:\n{issues}\n\n完成后,调用 presentCollection 确认记录可以正常加载。",
@@ -541,6 +550,8 @@ var i18n = (0, vue_i18n.createI18n)({
541
550
  kanbanUncategorized: "미분류",
542
551
  kanbanOpenCard: "{label} 열기",
543
552
  addView: "보기 추가",
553
+ addViewDesktop: "사용자 지정 보기",
554
+ addViewMobile: "스마트폰 보기",
544
555
  config: {
545
556
  open: "컬렉션 설정",
546
557
  title: "{title} · 설정",
@@ -552,6 +563,7 @@ var i18n = (0, vue_i18n.createI18n)({
552
563
  customViewLoading: "보기를 불러오는 중…",
553
564
  customViewError: "이 보기를 불러오지 못했습니다: {error}",
554
565
  addViewPrompt: "{title} 컬렉션에 사용자 지정 보기를 추가하고 싶어요. 무엇을 보거나 편집하고 싶은지 먼저 물어본 뒤, {base}/views/your-view.html 에 HTML 보기 파일을 만들고 {base}/schema.json의 `views[]`에 등록해 주세요(읽기 전용 보기는 capabilities [\"read\"], 레코드를 편집하면 [\"read\",\"write\"]). 데이터 계약은 custom-view 도움말을 따르세요.",
566
+ addMobileViewPrompt: "{title} 컬렉션에 스마트폰(리모트 앱)용 사용자 지정 보기를 추가하고 싶어요. 무엇을 보거나 편집하고 싶은지 먼저 물어본 뒤, {base}/views/your-view.html 에 HTML 보기 파일을 만들고 {base}/schema.json의 `views[]`에 `target: \"mobile\"`과 함께 등록해 주세요(기본은 읽기 전용, 레코드를 편집할 때만 `editableFields` / `allowDelete` 선언). 데이터 계약은 custom-view-remote 도움말을 따르세요 — 보기 안에서 fetch는 일절 금지입니다.",
555
567
  repair: "복구",
556
568
  dataIssuesDetected: "{count}개의 레코드 파일에 데이터 문제가 있어 이 보기에 표시되지 않을 수 있습니다.",
557
569
  repairPrompt: "{title} 컬렉션에 표시되지 않는 원인이 되는 데이터 문제가 있는 레코드 파일이 {count}개 있습니다. 각 파일을 수정하세요 — Read로 파일을 읽고 수정한 뒤 Write로 다시 저장하세요:\n{issues}\n\n완료되면 presentCollection을 호출하여 레코드가 로드되는지 확인하세요.",
@@ -674,6 +686,8 @@ var i18n = (0, vue_i18n.createI18n)({
674
686
  kanbanUncategorized: "Sin categoría",
675
687
  kanbanOpenCard: "Abrir {label}",
676
688
  addView: "Añadir vista",
689
+ addViewDesktop: "Vista personalizada",
690
+ addViewMobile: "Vista móvil",
677
691
  config: {
678
692
  open: "Ajustes de la colección",
679
693
  title: "{title} · ajustes",
@@ -685,6 +699,7 @@ var i18n = (0, vue_i18n.createI18n)({
685
699
  customViewLoading: "Cargando vista…",
686
700
  customViewError: "No se pudo cargar esta vista: {error}",
687
701
  addViewPrompt: "Quiero añadir una vista personalizada a la colección «{title}». Pregúntame qué quiero ver o editar, luego crea el archivo HTML de la vista en {base}/views/your-view.html y regístralo en {base}/schema.json dentro de `views[]` (capabilities [\"read\"] para una vista de solo lectura, [\"read\",\"write\"] si edita registros). Sigue la ayuda custom-view para el contrato de datos.",
702
+ addMobileViewPrompt: "Quiero añadir una vista personalizada para el móvil (aplicación remota) a la colección «{title}». Pregúntame qué quiero ver o editar, luego crea el archivo HTML de la vista en {base}/views/your-view.html y regístralo en {base}/schema.json dentro de `views[]` con `target: \"mobile\"` (de solo lectura por defecto; declara `editableFields` / `allowDelete` solo si edita registros). Sigue la ayuda custom-view-remote para el contrato de datos por postMessage — nada de fetch en la vista.",
688
703
  repair: "Reparar",
689
704
  dataIssuesDetected: "{count} archivo(s) de registro tienen problemas de datos y podrían no aparecer en esta vista.",
690
705
  repairPrompt: "La colección {title} tiene {count} archivo(s) de registro con problemas de datos que impiden que aparezcan. Corrige cada uno: lee el archivo con Read, corrígelo y vuelve a escribirlo con Write:\n{issues}\n\nCuando termines, llama a presentCollection para confirmar que los registros se cargan.",
@@ -807,6 +822,8 @@ var i18n = (0, vue_i18n.createI18n)({
807
822
  kanbanUncategorized: "Sem categoria",
808
823
  kanbanOpenCard: "Abrir {label}",
809
824
  addView: "Adicionar visualização",
825
+ addViewDesktop: "Visualização personalizada",
826
+ addViewMobile: "Visualização para celular",
810
827
  config: {
811
828
  open: "Configurações da coleção",
812
829
  title: "{title} · configurações",
@@ -818,6 +835,7 @@ var i18n = (0, vue_i18n.createI18n)({
818
835
  customViewLoading: "Carregando visualização…",
819
836
  customViewError: "Não foi possível carregar esta visualização: {error}",
820
837
  addViewPrompt: "Quero adicionar uma visualização personalizada à coleção \"{title}\". Pergunte o que eu quero ver ou editar, depois crie o arquivo HTML da visualização em {base}/views/your-view.html e registre-o em {base}/schema.json em `views[]` (capabilities [\"read\"] para uma visualização somente leitura, [\"read\",\"write\"] se editar registros). Siga a ajuda custom-view para o contrato de dados.",
838
+ addMobileViewPrompt: "Quero adicionar uma visualização personalizada para o celular (app remoto) à coleção \"{title}\". Pergunte o que eu quero ver ou editar, depois crie o arquivo HTML da visualização em {base}/views/your-view.html e registre-o em {base}/schema.json em `views[]` com `target: \"mobile\"` (somente leitura por padrão; declare `editableFields` / `allowDelete` apenas se editar registros). Siga a ajuda custom-view-remote para o contrato de dados via postMessage — nenhum fetch na visualização.",
821
839
  repair: "Reparar",
822
840
  dataIssuesDetected: "{count} arquivo(s) de registro têm problemas de dados e podem não aparecer nesta visualização.",
823
841
  repairPrompt: "A coleção {title} tem {count} arquivo(s) de registro com problemas de dados que impedem que apareçam. Corrija cada um: leia o arquivo com Read, corrija-o e grave-o novamente com Write:\n{issues}\n\nQuando terminar, chame presentCollection para confirmar que os registros carregam.",
@@ -940,6 +958,8 @@ var i18n = (0, vue_i18n.createI18n)({
940
958
  kanbanUncategorized: "Non classé",
941
959
  kanbanOpenCard: "Ouvrir {label}",
942
960
  addView: "Ajouter une vue",
961
+ addViewDesktop: "Vue personnalisée",
962
+ addViewMobile: "Vue mobile",
943
963
  config: {
944
964
  open: "Paramètres de la collection",
945
965
  title: "{title} · paramètres",
@@ -951,6 +971,7 @@ var i18n = (0, vue_i18n.createI18n)({
951
971
  customViewLoading: "Chargement de la vue…",
952
972
  customViewError: "Impossible de charger cette vue : {error}",
953
973
  addViewPrompt: "Je veux ajouter une vue personnalisée à la collection « {title} ». Demande-moi ce que je veux voir ou modifier, puis crée le fichier HTML de la vue dans {base}/views/your-view.html et enregistre-le dans {base}/schema.json sous `views[]` (capabilities [\"read\"] pour une vue en lecture seule, [\"read\",\"write\"] si elle modifie des enregistrements). Suis l'aide custom-view pour le contrat de données.",
974
+ addMobileViewPrompt: "Je veux ajouter une vue personnalisée pour le mobile (application distante) à la collection « {title} ». Demande-moi ce que je veux voir ou modifier, puis crée le fichier HTML de la vue dans {base}/views/your-view.html et enregistre-le dans {base}/schema.json sous `views[]` avec `target: \"mobile\"` (en lecture seule par défaut ; déclare `editableFields` / `allowDelete` uniquement si elle modifie des enregistrements). Suis l'aide custom-view-remote pour le contrat de données par postMessage — aucun fetch dans la vue.",
954
975
  repair: "Réparer",
955
976
  dataIssuesDetected: "{count} fichier(s) d'enregistrement présentent des problèmes de données et peuvent être absents de cette vue.",
956
977
  repairPrompt: "La collection « {title} » comporte {count} fichier(s) d'enregistrement présentant des problèmes de données qui les empêchent d'apparaître. Corrigez chacun : lisez le fichier avec Read, corrigez-le, puis réécrivez-le avec Write :\n{issues}\n\nUne fois terminé, appelez presentCollection pour confirmer que les enregistrements se chargent.",
@@ -1073,6 +1094,8 @@ var i18n = (0, vue_i18n.createI18n)({
1073
1094
  kanbanUncategorized: "Nicht kategorisiert",
1074
1095
  kanbanOpenCard: "{label} öffnen",
1075
1096
  addView: "Ansicht hinzufügen",
1097
+ addViewDesktop: "Benutzerdefinierte Ansicht",
1098
+ addViewMobile: "Smartphone-Ansicht",
1076
1099
  config: {
1077
1100
  open: "Sammlungseinstellungen",
1078
1101
  title: "{title} · Einstellungen",
@@ -1084,6 +1107,7 @@ var i18n = (0, vue_i18n.createI18n)({
1084
1107
  customViewLoading: "Ansicht wird geladen…",
1085
1108
  customViewError: "Diese Ansicht konnte nicht geladen werden: {error}",
1086
1109
  addViewPrompt: "Ich möchte der Sammlung {title} eine benutzerdefinierte Ansicht hinzufügen. Frag mich, was ich sehen oder bearbeiten möchte, erstelle dann die HTML-Ansichtsdatei unter {base}/views/your-view.html und registriere sie in {base}/schema.json unter `views[]` (capabilities [\"read\"] für eine schreibgeschützte Ansicht, [\"read\",\"write\"] wenn sie Datensätze bearbeitet). Folge der custom-view-Hilfe für den Datenvertrag.",
1110
+ addMobileViewPrompt: "Ich möchte der Sammlung {title} eine benutzerdefinierte Ansicht für das Smartphone (Remote-App) hinzufügen. Frag mich, was ich sehen oder bearbeiten möchte, erstelle dann die HTML-Ansichtsdatei unter {base}/views/your-view.html und registriere sie in {base}/schema.json unter `views[]` mit `target: \"mobile\"` (standardmäßig schreibgeschützt; deklariere `editableFields` / `allowDelete` nur, wenn sie Datensätze bearbeitet). Folge der custom-view-remote-Hilfe für den postMessage-Datenvertrag — kein fetch jeglicher Art in der Ansicht.",
1087
1111
  repair: "Reparieren",
1088
1112
  dataIssuesDetected: "{count} Datensatzdatei(en) haben Datenprobleme und fehlen möglicherweise in dieser Ansicht.",
1089
1113
  repairPrompt: "Die Sammlung {title} hat {count} Datensatzdatei(en) mit Datenproblemen, die ihr Erscheinen verhindern. Korrigiere jede — die Datei mit Read lesen, korrigieren und mit Write zurückschreiben:\n{issues}\n\nRufe anschließend presentCollection auf, um zu bestätigen, dass die Datensätze geladen werden.",
@@ -1717,7 +1741,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1717
1741
  const emit = __emit;
1718
1742
  const { t } = useCollectionI18n();
1719
1743
  /** The Uncategorized column uses the empty string as its sentinel value. */
1720
- const groupSpec = (0, vue.computed)(() => props.schema.fields[props.groupField]);
1744
+ const groupSpec = (0, vue.computed)(() => {
1745
+ const spec = props.schema.fields[props.groupField];
1746
+ return spec?.type === "enum" ? spec : void 0;
1747
+ });
1721
1748
  /** Declared enum values become columns in order, with a trailing
1722
1749
  * Uncategorized column for empty/unknown values (also a drop target that
1723
1750
  * clears the field). The Uncategorized column is omitted when the group
@@ -1764,7 +1791,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1764
1791
  const value = String(raw);
1765
1792
  return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;
1766
1793
  }
1767
- const visibleItems = (0, vue.computed)(() => groupSpec.value ? props.items.filter((item) => (0, _mulmoclaude_core_collection.fieldVisible)(groupSpec.value, item)) : []);
1794
+ const visibleItems = (0, vue.computed)(() => {
1795
+ const spec = groupSpec.value;
1796
+ return spec ? props.items.filter((item) => (0, _mulmoclaude_core_collection.fieldVisible)(spec, item)) : [];
1797
+ });
1768
1798
  const itemsByColumnMap = (0, vue.computed)(() => {
1769
1799
  const map = /* @__PURE__ */ new Map();
1770
1800
  for (const column of columns.value) map.set(column.value, []);
@@ -2291,7 +2321,8 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2291
2321
  /** Required flag for an embed's per-record picker — read off the storage
2292
2322
  * field it writes (`idField`), since the embed itself stores nothing. */
2293
2323
  function embedPickerRequired(field) {
2294
- const target = field.idField ? props.collection.schema.fields[field.idField] : void 0;
2324
+ const idField = field.type === "embed" ? field.idField : void 0;
2325
+ const target = idField ? props.collection.schema.fields[idField] : void 0;
2295
2326
  return target ? isFieldRequiredInUi(target) : false;
2296
2327
  }
2297
2328
  /** Tailwind fill/text/border classes tinting an enum `<select>` by its current
@@ -3012,284 +3043,342 @@ var CollectionRemoteViewPreview_default = /*#__PURE__*/ _plugin_vue_export_helpe
3012
3043
  }
3013
3044
  }), [["__scopeId", "data-v-965ade6c"]]);
3014
3045
  //#endregion
3015
- //#region src/vue/useCollectionRendering.ts
3046
+ //#region src/vue/useCollectionRendering.helpers.ts
3047
+ var EM_DASH = "—";
3048
+ var DEFAULT_CURRENCY = "USD";
3049
+ var MARKDOWN_CELL_PREVIEW_MAX = 80;
3016
3050
  function stepForFieldType(type) {
3017
3051
  if (type === "money") return "0.01";
3018
3052
  if (type === "number") return "any";
3019
3053
  }
3020
- function useCollectionRendering(collection, locale) {
3021
- const refCache = (0, vue.ref)({});
3022
- const refRecordCache = (0, vue.ref)({});
3023
- const embedCache = (0, vue.ref)({});
3024
- function resetLinkedCaches() {
3025
- refCache.value = {};
3026
- refRecordCache.value = {};
3027
- embedCache.value = {};
3028
- }
3029
- function uniqueRefTargets(schema) {
3030
- const targets = /* @__PURE__ */ new Set();
3031
- const walk = (fields) => {
3032
- for (const field of Object.values(fields)) {
3033
- if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3034
- if (field.type === "table" && field.of) walk(field.of);
3035
- }
3036
- };
3037
- walk(schema.fields);
3038
- return [...targets];
3039
- }
3040
- function uniqueEmbedTargets(schema) {
3041
- const targets = /* @__PURE__ */ new Set();
3042
- for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3043
- return [...targets];
3054
+ function inputTypeFor(type) {
3055
+ if (type === "email") return "email";
3056
+ if (type === "number") return "number";
3057
+ if (type === "money") return "number";
3058
+ if (type === "date") return "date";
3059
+ if (type === "datetime") return "datetime-local";
3060
+ return "text";
3061
+ }
3062
+ function isExternalUrl(value) {
3063
+ return typeof value === "string" && /^https?:\/\//i.test(value);
3064
+ }
3065
+ function detailText(value) {
3066
+ if (value === void 0 || value === null || value === "") return EM_DASH;
3067
+ return String(value);
3068
+ }
3069
+ function formatCell(value, type) {
3070
+ if (value === void 0 || value === null || value === "") return EM_DASH;
3071
+ if (type === "markdown" && typeof value === "string") return value.length > MARKDOWN_CELL_PREVIEW_MAX ? `${value.slice(0, MARKDOWN_CELL_PREVIEW_MAX)}…` : value;
3072
+ if (typeof value === "string" || typeof value === "number") return String(value);
3073
+ return JSON.stringify(value);
3074
+ }
3075
+ /** Resolve the ISO 4217 code for a money field: a per-record
3076
+ * `currencyField` (when present and non-blank) wins over the field's
3077
+ * literal `currency`. Only `money` / `derived` variants carry currency
3078
+ * keys; any other field resolves to undefined (the formatter's USD
3079
+ * fallback), as before. */
3080
+ function resolveCurrency(field, record) {
3081
+ if (field.type !== "money" && field.type !== "derived") return void 0;
3082
+ if (field.currencyField && record) {
3083
+ const code = record[field.currencyField];
3084
+ if (typeof code === "string" && code.trim().length > 0) return code;
3044
3085
  }
3045
- function buildRefDisplayMap(detail) {
3046
- const { fields, primaryKey } = detail.collection.schema;
3047
- const displayField = "name" in fields ? "name" : "title" in fields ? "title" : primaryKey;
3048
- const map = {};
3049
- for (const item of detail.items) {
3050
- const slugRaw = item[primaryKey];
3051
- if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
3052
- const displayRaw = item[displayField];
3053
- map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
3054
- }
3055
- return map;
3086
+ return field.currency;
3087
+ }
3088
+ function formatMoney(value, currency, displayLocale) {
3089
+ if (value === void 0 || value === "") return EM_DASH;
3090
+ const amount = typeof value === "number" ? value : Number(value);
3091
+ if (!Number.isFinite(amount)) return String(value);
3092
+ const currencyCode = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3093
+ try {
3094
+ return new Intl.NumberFormat(displayLocale, {
3095
+ style: "currency",
3096
+ currency: currencyCode
3097
+ }).format(amount);
3098
+ } catch {
3099
+ return String(amount);
3056
3100
  }
3057
- function buildRefRecordMap(detail) {
3058
- const { schema } = detail.collection;
3059
- const map = {};
3060
- for (const item of detail.items) {
3061
- const slugRaw = item[schema.primaryKey];
3062
- if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, {});
3063
- }
3064
- return map;
3101
+ }
3102
+ function currencySymbolForLocale(currency, locale) {
3103
+ const code = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3104
+ try {
3105
+ return new Intl.NumberFormat(locale, {
3106
+ style: "currency",
3107
+ currency: code
3108
+ }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
3109
+ } catch {
3110
+ return code;
3065
3111
  }
3066
- async function loadLinkedCollections(schema, expectedSlug) {
3067
- const refTargets = new Set(uniqueRefTargets(schema));
3068
- const embedTargets = new Set(uniqueEmbedTargets(schema));
3069
- const allTargets = [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])];
3070
- if (allTargets.length === 0) return;
3071
- const binding = collectionUi();
3072
- const results = await Promise.all(allTargets.map(async (target) => {
3073
- try {
3074
- return {
3075
- target,
3076
- result: await binding.fetchCollectionDetail(target)
3077
- };
3078
- } catch {
3079
- return {
3080
- target,
3081
- result: { ok: false }
3082
- };
3083
- }
3084
- }));
3085
- if (collection.value?.slug !== expectedSlug) return;
3086
- const nextRef = {};
3087
- const nextRefRecords = {};
3088
- const nextEmbed = {};
3089
- for (const { target, result } of results) {
3090
- if (!result.ok) continue;
3091
- if (refTargets.has(target)) {
3092
- nextRef[target] = buildRefDisplayMap(result.data);
3093
- nextRefRecords[target] = buildRefRecordMap(result.data);
3094
- }
3095
- if (embedTargets.has(target)) nextEmbed[target] = {
3096
- schema: result.data.collection.schema,
3097
- items: result.data.items
3098
- };
3112
+ }
3113
+ function tableRows(value) {
3114
+ if (!Array.isArray(value)) return [];
3115
+ return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
3116
+ }
3117
+ function hasTableRows(value) {
3118
+ return tableRows(value).length > 0;
3119
+ }
3120
+ /** Pick the field used to label a referenced/embedded record: prefer a
3121
+ * `name` field, then `title`, else fall back to the primary key. */
3122
+ function displayFieldFor(fields, primaryKey) {
3123
+ if ("name" in fields) return "name";
3124
+ if ("title" in fields) return "title";
3125
+ return primaryKey;
3126
+ }
3127
+ function uniqueRefTargets(schema) {
3128
+ const targets = /* @__PURE__ */ new Set();
3129
+ const walk = (fields) => {
3130
+ for (const field of Object.values(fields)) {
3131
+ if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3132
+ if (field.type === "table" && field.of) walk(field.of);
3099
3133
  }
3100
- refCache.value = nextRef;
3101
- refRecordCache.value = nextRefRecords;
3102
- embedCache.value = nextEmbed;
3103
- }
3104
- function refDisplay(targetSlug, itemSlug) {
3105
- const map = refCache.value[targetSlug];
3106
- return map && map[itemSlug] || itemSlug;
3107
- }
3108
- function refOptions(targetSlug) {
3109
- const map = refCache.value[targetSlug];
3110
- if (!map) return [];
3111
- return Object.entries(map).map(([slug, display]) => ({
3112
- slug,
3113
- display
3114
- })).sort((left, right) => left.display.localeCompare(right.display));
3134
+ };
3135
+ walk(schema.fields);
3136
+ return [...targets];
3137
+ }
3138
+ function uniqueEmbedTargets(schema) {
3139
+ const targets = /* @__PURE__ */ new Set();
3140
+ for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3141
+ return [...targets];
3142
+ }
3143
+ function buildRefDisplayMap(detail) {
3144
+ const { fields, primaryKey } = detail.collection.schema;
3145
+ const displayField = displayFieldFor(fields, primaryKey);
3146
+ const map = {};
3147
+ for (const item of detail.items) {
3148
+ const slugRaw = item[primaryKey];
3149
+ if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
3150
+ const displayRaw = item[displayField];
3151
+ map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
3115
3152
  }
3116
- /** Dropdown options for an `embed` field's per-record picker (`idField`):
3117
- * every record in the target collection, labelled by its name/title (or
3118
- * primary key). Built from `embedCache` so it works for embed targets
3119
- * that aren't also `ref` targets (the profile collection, say). */
3120
- function embedOptions(targetSlug) {
3121
- const data = embedCache.value[targetSlug];
3122
- if (!data) return [];
3123
- const { fields, primaryKey } = data.schema;
3124
- const displayField = "name" in fields ? "name" : "title" in fields ? "title" : primaryKey;
3125
- return data.items.map((item) => {
3126
- const slug = String(item[primaryKey] ?? "");
3127
- const labelRaw = item[displayField];
3128
- return {
3129
- slug,
3130
- display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
3131
- };
3132
- }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3153
+ return map;
3154
+ }
3155
+ function buildRefRecordMap(detail) {
3156
+ const { schema } = detail.collection;
3157
+ const map = {};
3158
+ for (const item of detail.items) {
3159
+ const slugRaw = item[schema.primaryKey];
3160
+ if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, {});
3133
3161
  }
3134
- function resolveEmbed(field, record) {
3135
- if (field.type !== "embed" || !field.to) return {
3136
- schema: null,
3137
- item: null
3138
- };
3139
- const targetId = (0, _mulmoclaude_core_collection.embedTargetId)(field, record);
3140
- const data = targetId ? embedCache.value[field.to] : void 0;
3141
- if (!data) return {
3142
- schema: null,
3143
- item: null
3144
- };
3145
- const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3162
+ return map;
3163
+ }
3164
+ function sortedRefOptions(map) {
3165
+ return Object.entries(map).map(([slug, display]) => ({
3166
+ slug,
3167
+ display
3168
+ })).sort((left, right) => left.display.localeCompare(right.display));
3169
+ }
3170
+ /** Dropdown options for an `embed` field's per-record picker: every
3171
+ * record in the target collection, labelled by its name/title (or
3172
+ * primary key), skipping records without a slug and sorted by label. */
3173
+ function buildEmbedOptions(schema, items) {
3174
+ const { fields, primaryKey } = schema;
3175
+ const displayField = displayFieldFor(fields, primaryKey);
3176
+ return items.map((item) => {
3177
+ const slug = String(item[primaryKey] ?? "");
3178
+ const labelRaw = item[displayField];
3146
3179
  return {
3147
- schema: data.schema,
3148
- item
3180
+ slug,
3181
+ display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
3149
3182
  };
3150
- }
3151
- function embedValue(field, value, record) {
3152
- if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale.value);
3153
- return detailText(value);
3154
- }
3155
- /** Build the read-only embed view-models for one record. A function of
3156
- * the open record (not a bare computed) because a per-record `idField`
3157
- * embed resolves a different target per row. */
3158
- function embedViewsFor(record) {
3159
- const out = {};
3160
- if (!collection.value) return out;
3161
- for (const [key, field] of Object.entries(collection.value.schema.fields)) {
3162
- if (field.type !== "embed") continue;
3163
- const { schema, item } = resolveEmbed(field, record);
3164
- const rows = [];
3165
- if (schema && item) for (const [subKey, subField] of Object.entries(schema.fields)) {
3166
- const value = item[subKey];
3167
- if (value === void 0 || value === null || value === "") continue;
3168
- rows.push({
3169
- key: subKey,
3170
- label: subField.label,
3171
- type: subField.type,
3172
- value,
3173
- display: embedValue(subField, value, item)
3174
- });
3175
- }
3176
- out[key] = {
3177
- found: Boolean(item),
3178
- rows,
3179
- targetSlug: field.to ?? "",
3180
- recordId: (0, _mulmoclaude_core_collection.embedTargetId)(field, record)
3181
- };
3182
- }
3183
- return out;
3184
- }
3185
- function resolveCurrency(field, record) {
3186
- if (field.currencyField && record) {
3187
- const code = record[field.currencyField];
3188
- if (typeof code === "string" && code.trim().length > 0) return code;
3189
- }
3190
- return field.currency;
3191
- }
3192
- function currencySymbol(currency) {
3193
- const code = currency && currency.length > 0 ? currency : "USD";
3194
- try {
3195
- return new Intl.NumberFormat(locale.value, {
3196
- style: "currency",
3197
- currency: code
3198
- }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
3199
- } catch {
3200
- return code;
3201
- }
3202
- }
3203
- function formatMoney(value, currency, displayLocale) {
3204
- if (value === void 0 || value === "") return "—";
3205
- const amount = typeof value === "number" ? value : Number(value);
3206
- if (!Number.isFinite(amount)) return String(value);
3207
- const currencyCode = currency && currency.length > 0 ? currency : "USD";
3183
+ }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3184
+ }
3185
+ //#endregion
3186
+ //#region src/vue/useLinkedCollectionCaches.ts
3187
+ /** The de-duplicated ref + embed target slugs a schema links to. `allTargets`
3188
+ * is the union (each target fetched once even when both ref'd and embedded). */
3189
+ function linkedTargets(schema) {
3190
+ const refTargets = new Set(uniqueRefTargets(schema));
3191
+ const embedTargets = new Set(uniqueEmbedTargets(schema));
3192
+ return {
3193
+ refTargets,
3194
+ embedTargets,
3195
+ allTargets: [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])]
3196
+ };
3197
+ }
3198
+ /** Fan-out fetch that hydrates the linked-collection caches. Best-effort: a
3199
+ * target whose fetch *rejects* (vs. resolving `{ ok: false }`) is coerced to a
3200
+ * skip and must not abort the others. Returns null when a quicker subsequent
3201
+ * load has already moved on (stale-write guard) so the caller drops the write.
3202
+ * Pure + injectable (`fetchDetail`, `currentSlug`) so both paths are testable. */
3203
+ async function fetchLinkedCaches(targets, fetchDetail, currentSlug, expectedSlug) {
3204
+ const { refTargets, embedTargets, allTargets } = targets;
3205
+ const results = await Promise.all(allTargets.map(async (target) => {
3208
3206
  try {
3209
- return new Intl.NumberFormat(displayLocale, {
3210
- style: "currency",
3211
- currency: currencyCode
3212
- }).format(amount);
3207
+ return {
3208
+ target,
3209
+ result: await fetchDetail(target)
3210
+ };
3213
3211
  } catch {
3214
- return String(amount);
3212
+ return {
3213
+ target,
3214
+ result: { ok: false }
3215
+ };
3215
3216
  }
3217
+ }));
3218
+ if (currentSlug() !== expectedSlug) return null;
3219
+ const refCache = {};
3220
+ const refRecordCache = {};
3221
+ const embedCache = {};
3222
+ for (const { target, result } of results) {
3223
+ if (!result.ok) continue;
3224
+ if (refTargets.has(target)) {
3225
+ refCache[target] = buildRefDisplayMap(result.data);
3226
+ refRecordCache[target] = buildRefRecordMap(result.data);
3227
+ }
3228
+ if (embedTargets.has(target)) embedCache[target] = {
3229
+ schema: result.data.collection.schema,
3230
+ items: result.data.items
3231
+ };
3216
3232
  }
3217
- function formatCell(value, type) {
3218
- if (value === void 0 || value === null || value === "") return "—";
3219
- if (type === "markdown" && typeof value === "string") return value.length > 80 ? `${value.slice(0, 80)}…` : value;
3220
- if (typeof value === "string" || typeof value === "number") return String(value);
3221
- return JSON.stringify(value);
3222
- }
3223
- function isExternalUrl(value) {
3224
- return typeof value === "string" && /^https?:\/\//i.test(value);
3225
- }
3226
- function artifactUrl(value) {
3227
- return collectionUi().fileAssetUrl(value);
3228
- }
3229
- function fileRoutePath(value) {
3230
- return collectionUi().fileRoutePath(value);
3231
- }
3232
- function detailText(value) {
3233
- if (value === void 0 || value === null || value === "") return "—";
3234
- return String(value);
3235
- }
3236
- function tableRows(value) {
3237
- if (!Array.isArray(value)) return [];
3238
- return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
3239
- }
3240
- function hasTableRows(value) {
3241
- return tableRows(value).length > 0;
3242
- }
3243
- function formatSubCell(subField, value, record) {
3244
- if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale.value);
3245
- if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return refDisplay(subField.to, value);
3246
- return formatCell(value, subField.type);
3247
- }
3248
- function inputTypeFor(type) {
3249
- if (type === "email") return "email";
3250
- if (type === "number") return "number";
3251
- if (type === "money") return "number";
3252
- if (type === "date") return "date";
3253
- if (type === "datetime") return "datetime-local";
3254
- return "text";
3255
- }
3256
- const stepFor = stepForFieldType;
3257
- function evaluateDerivedAgainstItem(field, fieldKey, item) {
3258
- if (!field.formula || !collection.value) return null;
3259
- const result = (0, _mulmoclaude_core_collection.deriveAll)(collection.value.schema, item, refRecordCache.value)[fieldKey];
3260
- return typeof result === "number" && Number.isFinite(result) ? result : null;
3233
+ return {
3234
+ refCache,
3235
+ refRecordCache,
3236
+ embedCache
3237
+ };
3238
+ }
3239
+ function useLinkedCollectionCaches(collection) {
3240
+ const refCache = (0, vue.ref)({});
3241
+ const refRecordCache = (0, vue.ref)({});
3242
+ const embedCache = (0, vue.ref)({});
3243
+ function resetLinkedCaches() {
3244
+ refCache.value = {};
3245
+ refRecordCache.value = {};
3246
+ embedCache.value = {};
3261
3247
  }
3262
- function derivedDisplay(field, computedValue, record) {
3263
- if (computedValue === null || computedValue === void 0) return "—";
3264
- if (field.display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);
3265
- return formatCell(computedValue, field.display ?? "number");
3248
+ async function loadLinkedCollections(schema, expectedSlug) {
3249
+ const targets = linkedTargets(schema);
3250
+ if (targets.allTargets.length === 0) return;
3251
+ const binding = collectionUi();
3252
+ const snapshot = await fetchLinkedCaches(targets, (slug) => binding.fetchCollectionDetail(slug), () => collection.value?.slug, expectedSlug);
3253
+ if (!snapshot) return;
3254
+ refCache.value = snapshot.refCache;
3255
+ refRecordCache.value = snapshot.refRecordCache;
3256
+ embedCache.value = snapshot.embedCache;
3266
3257
  }
3267
3258
  return {
3268
3259
  refCache,
3269
3260
  refRecordCache,
3270
3261
  embedCache,
3271
3262
  resetLinkedCaches,
3272
- loadLinkedCollections,
3273
- refDisplay,
3274
- refOptions,
3275
- embedOptions,
3276
- embedViewsFor,
3277
- resolveCurrency,
3278
- currencySymbol,
3279
- formatMoney,
3280
- formatCell,
3281
- detailText,
3282
- isExternalUrl,
3283
- artifactUrl,
3284
- fileRoutePath,
3285
- tableRows,
3286
- hasTableRows,
3287
- formatSubCell,
3288
- inputTypeFor,
3289
- stepFor,
3290
- deriveAll: _mulmoclaude_core_collection.deriveAll,
3291
- evaluateDerivedAgainstItem,
3292
- derivedDisplay
3263
+ loadLinkedCollections
3264
+ };
3265
+ }
3266
+ //#endregion
3267
+ //#region src/vue/useCollectionRendering.renderers.ts
3268
+ function lookupRefDisplay(refCache, targetSlug, itemSlug) {
3269
+ const map = refCache[targetSlug];
3270
+ return map && map[itemSlug] || itemSlug;
3271
+ }
3272
+ function refOptionsFor(refCache, targetSlug) {
3273
+ const map = refCache[targetSlug];
3274
+ return map ? sortedRefOptions(map) : [];
3275
+ }
3276
+ /** Dropdown options for an `embed` field's per-record picker (`idField`):
3277
+ * every record in the target collection, labelled by its name/title (or
3278
+ * primary key). Built from `embedCache` so it works for embed targets
3279
+ * that aren't also `ref` targets (the profile collection, say). */
3280
+ function embedOptionsFor(embedCache, targetSlug) {
3281
+ const data = embedCache[targetSlug];
3282
+ return data ? buildEmbedOptions(data.schema, data.items) : [];
3283
+ }
3284
+ function resolveEmbed(field, record, embedCache) {
3285
+ if (field.type !== "embed" || !field.to) return {
3286
+ schema: null,
3287
+ item: null
3288
+ };
3289
+ const targetId = (0, _mulmoclaude_core_collection.embedTargetId)(field, record);
3290
+ const data = targetId ? embedCache[field.to] : void 0;
3291
+ if (!data) return {
3292
+ schema: null,
3293
+ item: null
3294
+ };
3295
+ const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3296
+ return {
3297
+ schema: data.schema,
3298
+ item
3299
+ };
3300
+ }
3301
+ function formatEmbedValue(field, value, record, locale) {
3302
+ if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale);
3303
+ return detailText(value);
3304
+ }
3305
+ /** Build the read-only embed view-models for one record. A function of the
3306
+ * open record (not a bare computed) because a per-record `idField` embed
3307
+ * resolves a different target per row. `schema` is the OPEN collection's
3308
+ * schema (null when no collection is loaded → no views). */
3309
+ function buildEmbedViews(schema, embedCache, record, locale) {
3310
+ const out = {};
3311
+ if (!schema) return out;
3312
+ for (const [key, field] of Object.entries(schema.fields)) {
3313
+ if (field.type !== "embed") continue;
3314
+ const { schema: targetSchema, item } = resolveEmbed(field, record, embedCache);
3315
+ const rows = [];
3316
+ if (targetSchema && item) for (const [subKey, subField] of Object.entries(targetSchema.fields)) {
3317
+ const value = item[subKey];
3318
+ if (value === void 0 || value === null || value === "") continue;
3319
+ rows.push({
3320
+ key: subKey,
3321
+ label: subField.label,
3322
+ type: subField.type,
3323
+ value,
3324
+ display: formatEmbedValue(subField, value, item, locale)
3325
+ });
3326
+ }
3327
+ out[key] = {
3328
+ found: Boolean(item),
3329
+ rows,
3330
+ targetSlug: field.to ?? "",
3331
+ recordId: (0, _mulmoclaude_core_collection.embedTargetId)(field, record)
3332
+ };
3333
+ }
3334
+ return out;
3335
+ }
3336
+ function renderSubCell(subField, value, record, refCache, locale) {
3337
+ if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale);
3338
+ if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return lookupRefDisplay(refCache, subField.to, value);
3339
+ return formatCell(value, subField.type);
3340
+ }
3341
+ function evaluateDerived(field, fieldKey, item, schema, refRecords) {
3342
+ if (field.type !== "derived" || !schema) return null;
3343
+ const result = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, refRecords)[fieldKey];
3344
+ return typeof result === "number" && Number.isFinite(result) ? result : null;
3345
+ }
3346
+ function renderDerived(field, computedValue, record, locale) {
3347
+ if (computedValue === null || computedValue === void 0) return "—";
3348
+ const display = field.type === "derived" ? field.display : void 0;
3349
+ if (display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale);
3350
+ return formatCell(computedValue, display ?? "number");
3351
+ }
3352
+ //#endregion
3353
+ //#region src/vue/useCollectionRendering.ts
3354
+ var STATELESS_RENDERERS = {
3355
+ resolveCurrency,
3356
+ formatMoney,
3357
+ formatCell,
3358
+ detailText,
3359
+ isExternalUrl,
3360
+ tableRows,
3361
+ hasTableRows,
3362
+ inputTypeFor,
3363
+ stepFor: stepForFieldType,
3364
+ deriveAll: _mulmoclaude_core_collection.deriveAll
3365
+ };
3366
+ function useCollectionRendering(collection, locale) {
3367
+ const caches = useLinkedCollectionCaches(collection);
3368
+ const { refCache, refRecordCache, embedCache } = caches;
3369
+ return {
3370
+ ...caches,
3371
+ ...STATELESS_RENDERERS,
3372
+ refDisplay: (targetSlug, itemSlug) => lookupRefDisplay(refCache.value, targetSlug, itemSlug),
3373
+ refOptions: (targetSlug) => refOptionsFor(refCache.value, targetSlug),
3374
+ embedOptions: (targetSlug) => embedOptionsFor(embedCache.value, targetSlug),
3375
+ embedViewsFor: (record) => buildEmbedViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
3376
+ currencySymbol: (currency) => currencySymbolForLocale(currency, locale.value),
3377
+ artifactUrl: (value) => collectionUi().fileAssetUrl(value),
3378
+ fileRoutePath: (value) => collectionUi().fileRoutePath(value),
3379
+ formatSubCell: (subField, value, record) => renderSubCell(subField, value, record, refCache.value, locale.value),
3380
+ evaluateDerivedAgainstItem: (field, fieldKey, item) => evaluateDerived(field, fieldKey, item, collection.value?.schema ?? null, refRecordCache.value),
3381
+ derivedDisplay: (field, computedValue, record) => renderDerived(field, computedValue, record, locale.value)
3293
3382
  };
3294
3383
  }
3295
3384
  //#endregion
@@ -3436,166 +3525,175 @@ var _hoisted_26$1 = [
3436
3525
  "onClick"
3437
3526
  ];
3438
3527
  var _hoisted_27$1 = { class: "material-icons text-sm" };
3439
- var _hoisted_28 = ["title", "aria-label"];
3440
- var _hoisted_29 = ["title", "aria-label"];
3441
- var _hoisted_30 = ["value", "aria-label"];
3442
- var _hoisted_31 = ["value"];
3443
- var _hoisted_32 = ["value", "aria-label"];
3444
- var _hoisted_33 = ["value"];
3445
- var _hoisted_34 = {
3528
+ var _hoisted_28 = [
3529
+ "title",
3530
+ "aria-label",
3531
+ "aria-expanded"
3532
+ ];
3533
+ var _hoisted_29 = {
3534
+ key: 0,
3535
+ class: "absolute left-0 top-full mt-1 z-20 min-w-max rounded border border-slate-200 bg-white shadow-lg py-1",
3536
+ "data-testid": "collection-view-add-menu"
3537
+ };
3538
+ var _hoisted_30 = ["title", "aria-label"];
3539
+ var _hoisted_31 = ["value", "aria-label"];
3540
+ var _hoisted_32 = ["value"];
3541
+ var _hoisted_33 = ["value", "aria-label"];
3542
+ var _hoisted_34 = ["value"];
3543
+ var _hoisted_35 = {
3446
3544
  key: 3,
3447
3545
  class: "text-[10px] text-slate-400 font-bold uppercase tracking-wider select-none"
3448
3546
  };
3449
- var _hoisted_35 = {
3547
+ var _hoisted_36 = {
3450
3548
  key: 3,
3451
3549
  class: "mx-6 mt-4 rounded-xl border border-amber-200 bg-amber-50/60 p-4 text-sm text-amber-900 shadow-sm flex items-center gap-3",
3452
3550
  "data-testid": "collections-data-issues"
3453
3551
  };
3454
- var _hoisted_36 = { class: "flex-1" };
3455
- var _hoisted_37 = { class: "flex-1 overflow-auto" };
3456
- var _hoisted_38 = {
3552
+ var _hoisted_37 = { class: "flex-1" };
3553
+ var _hoisted_38 = { class: "flex-1 overflow-auto" };
3554
+ var _hoisted_39 = {
3457
3555
  key: 0,
3458
3556
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3"
3459
3557
  };
3460
- var _hoisted_39 = {
3558
+ var _hoisted_40 = {
3461
3559
  key: 1,
3462
3560
  class: "m-6 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3"
3463
3561
  };
3464
- var _hoisted_40 = { key: 2 };
3465
- var _hoisted_41 = {
3562
+ var _hoisted_41 = { key: 2 };
3563
+ var _hoisted_42 = {
3466
3564
  key: 3,
3467
3565
  class: "p-4"
3468
3566
  };
3469
- var _hoisted_42 = {
3567
+ var _hoisted_43 = {
3470
3568
  key: 4,
3471
3569
  class: "h-full flex flex-col"
3472
3570
  };
3473
- var _hoisted_43 = {
3571
+ var _hoisted_44 = {
3474
3572
  key: 0,
3475
3573
  class: "m-3 mb-0 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3",
3476
3574
  "data-testid": "collections-inline-error"
3477
3575
  };
3478
- var _hoisted_44 = { class: "flex-1" };
3479
- var _hoisted_45 = ["aria-label"];
3480
- var _hoisted_46 = { class: "flex-1 min-h-0 px-3 py-2" };
3481
- var _hoisted_47 = {
3576
+ var _hoisted_45 = { class: "flex-1" };
3577
+ var _hoisted_46 = ["aria-label"];
3578
+ var _hoisted_47 = { class: "flex-1 min-h-0 px-3 py-2" };
3579
+ var _hoisted_48 = {
3482
3580
  key: 5,
3483
3581
  class: "h-full",
3484
3582
  "data-testid": "collection-custom-view-body"
3485
3583
  };
3486
- var _hoisted_48 = {
3584
+ var _hoisted_49 = {
3487
3585
  key: 6,
3488
3586
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3489
3587
  };
3490
- var _hoisted_49 = { class: "font-semibold text-slate-600" };
3491
- var _hoisted_50 = {
3588
+ var _hoisted_50 = { class: "font-semibold text-slate-600" };
3589
+ var _hoisted_51 = {
3492
3590
  key: 7,
3493
3591
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3494
3592
  };
3495
- var _hoisted_51 = { class: "font-semibold text-slate-600" };
3496
- var _hoisted_52 = {
3593
+ var _hoisted_52 = { class: "font-semibold text-slate-600" };
3594
+ var _hoisted_53 = {
3497
3595
  key: 8,
3498
3596
  class: "overflow-x-auto [container-type:inline-size]"
3499
3597
  };
3500
- var _hoisted_53 = {
3598
+ var _hoisted_54 = {
3501
3599
  key: 0,
3502
3600
  class: "m-4 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3",
3503
3601
  "data-testid": "collections-inline-error"
3504
3602
  };
3505
- var _hoisted_54 = { class: "flex-1" };
3506
- var _hoisted_55 = ["aria-label"];
3507
- var _hoisted_56 = { class: "min-w-full text-xs" };
3508
- var _hoisted_57 = { class: "bg-slate-50 border-b border-slate-200" };
3509
- var _hoisted_58 = ["aria-sort"];
3510
- var _hoisted_59 = { class: "flex items-center gap-1" };
3511
- var _hoisted_60 = ["title"];
3512
- var _hoisted_61 = [
3603
+ var _hoisted_55 = { class: "flex-1" };
3604
+ var _hoisted_56 = ["aria-label"];
3605
+ var _hoisted_57 = { class: "min-w-full text-xs" };
3606
+ var _hoisted_58 = { class: "bg-slate-50 border-b border-slate-200" };
3607
+ var _hoisted_59 = ["aria-sort"];
3608
+ var _hoisted_60 = { class: "flex items-center gap-1" };
3609
+ var _hoisted_61 = ["title"];
3610
+ var _hoisted_62 = [
3513
3611
  "data-testid",
3514
3612
  "aria-label",
3515
3613
  "onClick",
3516
3614
  "onPointerenter"
3517
3615
  ];
3518
- var _hoisted_62 = { class: "material-icons text-base align-middle" };
3519
- var _hoisted_63 = { class: "divide-y divide-slate-100 bg-white" };
3520
- var _hoisted_64 = [
3616
+ var _hoisted_63 = { class: "material-icons text-base align-middle" };
3617
+ var _hoisted_64 = { class: "divide-y divide-slate-100 bg-white" };
3618
+ var _hoisted_65 = [
3521
3619
  "aria-label",
3522
3620
  "data-testid",
3523
3621
  "onClick",
3524
3622
  "onKeydown"
3525
3623
  ];
3526
- var _hoisted_65 = [
3624
+ var _hoisted_66 = [
3527
3625
  "checked",
3528
3626
  "disabled",
3529
3627
  "data-testid",
3530
3628
  "aria-label",
3531
3629
  "onChange"
3532
3630
  ];
3533
- var _hoisted_66 = [
3631
+ var _hoisted_67 = [
3534
3632
  "checked",
3535
3633
  "disabled",
3536
3634
  "data-testid",
3537
3635
  "aria-label",
3538
3636
  "onChange"
3539
3637
  ];
3540
- var _hoisted_67 = {
3638
+ var _hoisted_68 = {
3541
3639
  key: 2,
3542
3640
  class: "block truncate"
3543
3641
  };
3544
- var _hoisted_68 = [
3642
+ var _hoisted_69 = [
3545
3643
  "href",
3546
3644
  "tabindex",
3547
3645
  "data-testid",
3548
3646
  "onClick",
3549
3647
  "onKeydown"
3550
3648
  ];
3551
- var _hoisted_69 = [
3649
+ var _hoisted_70 = [
3552
3650
  "value",
3553
3651
  "disabled",
3554
3652
  "data-testid",
3555
3653
  "aria-label",
3556
3654
  "onChange"
3557
3655
  ];
3558
- var _hoisted_70 = {
3656
+ var _hoisted_71 = {
3559
3657
  key: 0,
3560
3658
  value: ""
3561
3659
  };
3562
- var _hoisted_71 = ["value"];
3563
- var _hoisted_72 = {
3660
+ var _hoisted_72 = ["value"];
3661
+ var _hoisted_73 = {
3564
3662
  key: 4,
3565
3663
  class: "block truncate tabular-nums font-semibold text-slate-900"
3566
3664
  };
3567
- var _hoisted_73 = {
3665
+ var _hoisted_74 = {
3568
3666
  key: 5,
3569
3667
  class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-lg text-[10px] font-bold bg-slate-100 text-slate-600 border border-slate-200/40"
3570
3668
  };
3571
- var _hoisted_74 = {
3669
+ var _hoisted_75 = {
3572
3670
  key: 6,
3573
3671
  class: "inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-1.5 py-0.5 rounded border border-indigo-100/50"
3574
3672
  };
3575
- var _hoisted_75 = ["href", "data-testid"];
3576
3673
  var _hoisted_76 = ["href", "data-testid"];
3577
- var _hoisted_77 = [
3674
+ var _hoisted_77 = ["href", "data-testid"];
3675
+ var _hoisted_78 = [
3578
3676
  "href",
3579
3677
  "data-testid",
3580
3678
  "onClick"
3581
3679
  ];
3582
- var _hoisted_78 = {
3680
+ var _hoisted_79 = {
3583
3681
  key: 10,
3584
3682
  class: "block truncate text-slate-600"
3585
3683
  };
3586
- var _hoisted_79 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
3587
- var _hoisted_80 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
3588
- var _hoisted_81 = { class: "flex-1" };
3589
- var _hoisted_82 = {
3684
+ var _hoisted_80 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
3685
+ var _hoisted_81 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
3686
+ var _hoisted_82 = { class: "flex-1" };
3687
+ var _hoisted_83 = {
3590
3688
  id: "collections-chat-title",
3591
3689
  class: "text-sm font-bold text-slate-800 uppercase tracking-wide"
3592
3690
  };
3593
- var _hoisted_83 = { class: "text-xs text-slate-400 font-semibold" };
3594
- var _hoisted_84 = ["aria-label"];
3595
- var _hoisted_85 = { class: "px-6 py-5" };
3596
- var _hoisted_86 = ["placeholder", "onKeydown"];
3597
- var _hoisted_87 = { class: "px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50" };
3598
- var _hoisted_88 = ["disabled"];
3691
+ var _hoisted_84 = { class: "text-xs text-slate-400 font-semibold" };
3692
+ var _hoisted_85 = ["aria-label"];
3693
+ var _hoisted_86 = { class: "px-6 py-5" };
3694
+ var _hoisted_87 = ["placeholder", "onKeydown"];
3695
+ var _hoisted_88 = { class: "px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50" };
3696
+ var _hoisted_89 = ["disabled"];
3599
3697
  /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3600
3698
  * `presentCollection` chat card mounts this component and drives both
3601
3699
  * from the tool result). In standalone route mode (the
@@ -3771,12 +3869,12 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3771
3869
  return scalarSortValue(field, item[key]);
3772
3870
  }
3773
3871
  /** Derived rows sort by their display type: money/number → numeric,
3774
- * date/datetime → epoch, anything else → the enriched value as a string. */
3872
+ * date → epoch, anything else → the enriched value as a string. */
3775
3873
  function derivedSortValue(field, key, item) {
3776
- const { display } = field;
3874
+ const display = field.type === "derived" ? field.display : void 0;
3777
3875
  if (display === void 0 || display === "number" || display === "money") return (0, _mulmoclaude_core_collection.numericSortValue)(evaluateDerivedAgainstItem(field, key, item));
3778
3876
  const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;
3779
- if (display === "date" || display === "datetime") return (0, _mulmoclaude_core_collection.dateSortValue)(enriched[key]);
3877
+ if (display === "date") return (0, _mulmoclaude_core_collection.dateSortValue)(enriched[key]);
3780
3878
  return (0, _mulmoclaude_core_collection.stringSortValue)(enriched[key]);
3781
3879
  }
3782
3880
  const sortedItems = (0, vue.computed)(() => {
@@ -3954,10 +4052,12 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3954
4052
  function buildChatSeed(slug, message, itemId) {
3955
4053
  const current = collection.value;
3956
4054
  if (current?.source !== "feed") return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
4055
+ const dataPath = current.schema.dataPath ?? `data/feeds/${slug}`;
4056
+ const scoped = itemId ? `(for record \`${itemId}\`) ${message}` : message;
3957
4057
  return t("collectionsView.feedChatSeed", {
3958
4058
  slug,
3959
- dataPath: current.schema.dataPath ?? `data/feeds/${slug}`,
3960
- message: itemId ? `(for record \`${itemId}\`) ${message}` : message
4059
+ dataPath,
4060
+ message: scoped
3961
4061
  });
3962
4062
  }
3963
4063
  /** Start a new general-role chat seeded from the current view. */
@@ -4032,7 +4132,10 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4032
4132
  enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
4033
4133
  await render.loadLinkedCollections(result.data.collection.schema, slug);
4034
4134
  if (activeSlug.value !== slug) return;
4035
- if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
4135
+ if (viewing.value) {
4136
+ const openId = String(viewing.value[result.data.collection.schema.primaryKey] ?? "");
4137
+ viewing.value = findItemById(openId) ?? null;
4138
+ }
4036
4139
  }
4037
4140
  function maybeAutoRefreshFeed(slug) {
4038
4141
  if (embedded.value) return;
@@ -4121,16 +4224,41 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4121
4224
  * page only (the seed starts a chat). Feeds qualify too — their views are
4122
4225
  * authored under feeds/<slug>/ and the seed prompt points there. */
4123
4226
  const canAddCustomView = (0, vue.computed)(() => Boolean(collection.value) && !embedded.value);
4227
+ const addMenuOpen = (0, vue.ref)(false);
4228
+ const addMenuRef = (0, vue.ref)(null);
4229
+ /** Whether authoring a phone (remote app) view is worth offering — mirrors
4230
+ * the selector filter above: without the host's `fetchRemoteView` binding a
4231
+ * mobile view could be authored but never shown here. */
4232
+ const canAddMobileView = (0, vue.computed)(() => Boolean(cui.fetchRemoteView));
4233
+ /** "+" click: open the target chooser, or skip the one-item menu and seed
4234
+ * the desktop prompt directly when mobile views aren't available. */
4235
+ function onAddViewClick() {
4236
+ if (!canAddMobileView.value) {
4237
+ addCustomView("desktop");
4238
+ return;
4239
+ }
4240
+ addMenuOpen.value = !addMenuOpen.value;
4241
+ }
4242
+ function closeAddMenuOnOutsideClick(event) {
4243
+ if (!addMenuRef.value?.contains(event.target)) addMenuOpen.value = false;
4244
+ }
4245
+ (0, vue.watch)(addMenuOpen, (open) => {
4246
+ if (open) document.addEventListener("mousedown", closeAddMenuOnOutsideClick);
4247
+ else document.removeEventListener("mousedown", closeAddMenuOnOutsideClick);
4248
+ });
4124
4249
  /** Seed a chat asking Claude to author a new custom view for this collection.
4125
4250
  * Reuses the same chat-seed path as collection actions — the host injects a
4126
4251
  * templated prompt; Claude asks, authors the HTML, and registers it. The
4127
4252
  * authoring base is source-aware: a feed lives under `feeds/<slug>/`, every
4128
- * other collection under the `data/skills/<slug>/` staging dir. */
4129
- function addCustomView() {
4253
+ * other collection under the `data/skills/<slug>/` staging dir. The prompt
4254
+ * is target-aware: phone views follow the custom-view-remote contract and
4255
+ * register with `target: "mobile"`. */
4256
+ function addCustomView(target) {
4257
+ addMenuOpen.value = false;
4130
4258
  const current = collection.value;
4131
4259
  if (!current) return;
4132
4260
  const base = current.source === "feed" ? `feeds/${current.slug}` : `data/skills/${current.slug}`;
4133
- const prompt = t("collectionsView.addViewPrompt", {
4261
+ const prompt = t(target === "mobile" ? "collectionsView.addMobileViewPrompt" : "collectionsView.addViewPrompt", {
4134
4262
  title: current.title,
4135
4263
  base
4136
4264
  });
@@ -4185,7 +4313,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4185
4313
  }
4186
4314
  /** Select a custom view by id (builds the `custom:<id>` mode key). */
4187
4315
  function setCustomView(viewId) {
4188
- view.value = `custom:${viewId}`;
4316
+ const mode = `custom:${viewId}`;
4317
+ view.value = mode;
4189
4318
  }
4190
4319
  /** A short, slug-safe id not already used by a loaded record. Collisions
4191
4320
  * are astronomically unlikely (32 bits), but we still re-roll a few
@@ -4343,7 +4472,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4343
4472
  viewing.value = null;
4344
4473
  return;
4345
4474
  }
4346
- viewing.value = findItemById(selected) ?? null;
4475
+ const match = findItemById(selected) ?? null;
4476
+ viewing.value = match;
4347
4477
  }
4348
4478
  /** Title for the open-mode header: the record's primary-key value
4349
4479
  * (e.g. `INV-2026-0001`), falling back to the collection title.
@@ -4437,19 +4567,17 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4437
4567
  /** Whether a `toggle` field reads as checked: its projected enum field
4438
4568
  * currently equals `onValue`. The toggle stores nothing itself. */
4439
4569
  function toggleChecked(item, field) {
4440
- return field.field !== void 0 && String(item[field.field] ?? "") === field.onValue;
4570
+ return field.type === "toggle" && String(item[field.field] ?? "") === field.onValue;
4441
4571
  }
4442
4572
  /** Flip a `toggle`: write the projected enum field to `offValue` when
4443
4573
  * currently checked, else `onValue`. Reuses the inline-edit PUT path
4444
4574
  * (optimistic + rollback) — the toggle has no value of its own. */
4445
4575
  function commitToggle(item, field) {
4576
+ if (field.type !== "toggle" || !collection.value) return;
4446
4577
  const targetKey = field.field;
4447
- if (!targetKey || !collection.value) return;
4448
4578
  const enumField = collection.value.schema.fields[targetKey];
4449
4579
  if (!enumField) return;
4450
- const next = toggleChecked(item, field) ? field.offValue : field.onValue;
4451
- if (next === void 0) return;
4452
- commitInlineEdit(item, targetKey, enumField, next);
4580
+ commitInlineEdit(item, targetKey, enumField, toggleChecked(item, field) ? field.offValue : field.onValue);
4453
4581
  }
4454
4582
  async function confirmDelete(item) {
4455
4583
  if (!collection.value) return;
@@ -4606,6 +4734,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4606
4734
  view.value = slug && readCollectionViewMode(slug) || "table";
4607
4735
  anchorOverride.value = null;
4608
4736
  kanbanOverride.value = null;
4737
+ addMenuOpen.value = false;
4609
4738
  sortState.value = storedSortFor(slug);
4610
4739
  }
4611
4740
  if (slug) loadCollection(slug);
@@ -4654,6 +4783,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4654
4783
  changeUnsub = null;
4655
4784
  clearLiveRefreshTimer();
4656
4785
  if (refreshNoteTimer !== void 0) clearTimeout(refreshNoteTimer);
4786
+ document.removeEventListener("mousedown", closeAddMenuOnOutsideClick);
4657
4787
  });
4658
4788
  (0, vue.watch)([
4659
4789
  activeView,
@@ -4688,7 +4818,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4688
4818
  "aria-label": (0, vue.unref)(t)("collectionsView.backToIndex"),
4689
4819
  "data-testid": "collections-back",
4690
4820
  onClick: goBack
4691
- }, [..._cache[23] || (_cache[23] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : (0, vue.createCommentVNode)("", true),
4821
+ }, [..._cache[25] || (_cache[25] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : (0, vue.createCommentVNode)("", true),
4692
4822
  collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_4$5, [(0, vue.createElementVNode)("span", _hoisted_5$5, (0, vue.toDisplayString)(collection.value.icon), 1)])) : (0, vue.createCommentVNode)("", true),
4693
4823
  (0, vue.createElementVNode)("div", _hoisted_6$4, [(0, vue.createElementVNode)("h1", _hoisted_7$4, (0, vue.toDisplayString)(collection.value?.title ?? (0, vue.unref)(t)("collectionsView.title")), 1), collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8$4, (0, vue.toDisplayString)(collection.value.slug), 1)) : (0, vue.createCommentVNode)("", true)]),
4694
4824
  collection.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(pinToggle)), {
@@ -4717,7 +4847,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4717
4847
  class: "h-8 px-2.5 flex items-center gap-1 rounded border border-indigo-200 bg-white hover:bg-indigo-50 text-indigo-600 font-bold text-xs transition-colors",
4718
4848
  "data-testid": "collections-chat",
4719
4849
  onClick: openChat
4720
- }, [_cache[24] || (_cache[24] = (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)])) : (0, vue.createCommentVNode)("", true),
4850
+ }, [_cache[26] || (_cache[26] = (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)])) : (0, vue.createCommentVNode)("", true),
4721
4851
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(collectionActions.value, (action) => {
4722
4852
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4723
4853
  key: action.id,
@@ -4734,7 +4864,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4734
4864
  class: "h-8 px-2.5 flex items-center gap-1 rounded bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-xs transition-colors shadow-sm",
4735
4865
  "data-testid": "collections-add-item",
4736
4866
  onClick: openCreate
4737
- }, [_cache[25] || (_cache[25] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.add")), 1)])) : (0, vue.createCommentVNode)("", true),
4867
+ }, [_cache[27] || (_cache[27] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.add")), 1)])) : (0, vue.createCommentVNode)("", true),
4738
4868
  canDeleteCollection.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4739
4869
  key: 6,
4740
4870
  type: "button",
@@ -4743,7 +4873,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4743
4873
  "aria-label": (0, vue.unref)(t)("collectionsView.deleteCollection"),
4744
4874
  "data-testid": "collections-delete",
4745
4875
  onClick: confirmCollectionDelete
4746
- }, [..._cache[26] || (_cache[26] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_13$4)) : (0, vue.createCommentVNode)("", true),
4876
+ }, [..._cache[28] || (_cache[28] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_13$4)) : (0, vue.createCommentVNode)("", true),
4747
4877
  canDeleteFeed.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4748
4878
  key: 7,
4749
4879
  type: "button",
@@ -4752,11 +4882,11 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4752
4882
  "aria-label": (0, vue.unref)(t)("collectionsView.deleteFeed"),
4753
4883
  "data-testid": "feeds-delete",
4754
4884
  onClick: confirmFeedDelete
4755
- }, [..._cache[27] || (_cache[27] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : (0, vue.createCommentVNode)("", true)
4885
+ }, [..._cache[29] || (_cache[29] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : (0, vue.createCommentVNode)("", true)
4756
4886
  ])) : (0, vue.createCommentVNode)("", true),
4757
- refreshNote.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_15$4, [_cache[28] || (_cache[28] = (0, vue.createElementVNode)("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), (0, vue.createElementVNode)("span", _hoisted_16$4, (0, vue.toDisplayString)(refreshNote.value), 1)])) : (0, vue.createCommentVNode)("", true),
4887
+ refreshNote.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_15$4, [_cache[30] || (_cache[30] = (0, vue.createElementVNode)("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), (0, vue.createElementVNode)("span", _hoisted_16$4, (0, vue.toDisplayString)(refreshNote.value), 1)])) : (0, vue.createCommentVNode)("", true),
4758
4888
  collection.value && (!__props.hideSearch && items.value.length > 0 || !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_17$4, [!__props.hideSearch && items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_18$4, [
4759
- _cache[30] || (_cache[30] = (0, vue.createElementVNode)("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "search")], -1)),
4889
+ _cache[32] || (_cache[32] = (0, vue.createElementVNode)("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "search")], -1)),
4760
4890
  (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
4761
4891
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
4762
4892
  type: "text",
@@ -4770,7 +4900,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4770
4900
  "aria-label": (0, vue.unref)(t)("collectionsView.clearSearch"),
4771
4901
  class: "absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-400 hover:text-slate-600",
4772
4902
  onClick: _cache[1] || (_cache[1] = ($event) => searchQuery.value = "")
4773
- }, [..._cache[29] || (_cache[29] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_20$2)) : (0, vue.createCommentVNode)("", true)
4903
+ }, [..._cache[31] || (_cache[31] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_20$2)) : (0, vue.createCommentVNode)("", true)
4774
4904
  ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_21$2, [
4775
4905
  !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
4776
4906
  key: 0,
@@ -4784,7 +4914,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4784
4914
  "aria-pressed": activeView.value === "table",
4785
4915
  "data-testid": "collection-view-toggle-table",
4786
4916
  onClick: _cache[2] || (_cache[2] = ($event) => setView("table"))
4787
- }, [_cache[31] || (_cache[31] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "table_rows", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewTable")), 1)], 10, _hoisted_23$2),
4917
+ }, [_cache[33] || (_cache[33] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "table_rows", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewTable")), 1)], 10, _hoisted_23$2),
4788
4918
  hasCalendar.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4789
4919
  key: 0,
4790
4920
  type: "button",
@@ -4792,7 +4922,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4792
4922
  "aria-pressed": activeView.value === "calendar",
4793
4923
  "data-testid": "collection-view-toggle-calendar",
4794
4924
  onClick: _cache[3] || (_cache[3] = ($event) => setView("calendar"))
4795
- }, [_cache[32] || (_cache[32] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "calendar_month", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_24$1)) : (0, vue.createCommentVNode)("", true),
4925
+ }, [_cache[34] || (_cache[34] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "calendar_month", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_24$1)) : (0, vue.createCommentVNode)("", true),
4796
4926
  hasKanban.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4797
4927
  key: 1,
4798
4928
  type: "button",
@@ -4800,7 +4930,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4800
4930
  "aria-pressed": activeView.value === "kanban",
4801
4931
  "data-testid": "collection-view-toggle-kanban",
4802
4932
  onClick: _cache[4] || (_cache[4] = ($event) => setView("kanban"))
4803
- }, [_cache[33] || (_cache[33] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "view_kanban", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_25$1)) : (0, vue.createCommentVNode)("", true),
4933
+ }, [_cache[35] || (_cache[35] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "view_kanban", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_25$1)) : (0, vue.createCommentVNode)("", true),
4804
4934
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(customViews.value, (cv) => {
4805
4935
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4806
4936
  key: cv.id,
@@ -4811,15 +4941,30 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4811
4941
  onClick: ($event) => setCustomView(cv.id)
4812
4942
  }, [(0, vue.createElementVNode)("span", _hoisted_27$1, (0, vue.toDisplayString)(cv.icon || (cv.target === "mobile" ? "smartphone" : "dashboard_customize")), 1), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(cv.label), 1)], 10, _hoisted_26$1);
4813
4943
  }), 128)),
4814
- canAddCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4944
+ canAddCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
4815
4945
  key: 2,
4946
+ ref_key: "addMenuRef",
4947
+ ref: addMenuRef,
4948
+ class: "relative"
4949
+ }, [(0, vue.createElementVNode)("button", {
4816
4950
  type: "button",
4817
4951
  class: "h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50",
4818
4952
  title: (0, vue.unref)(t)("collectionsView.addView"),
4819
4953
  "aria-label": (0, vue.unref)(t)("collectionsView.addView"),
4954
+ "aria-expanded": addMenuOpen.value,
4820
4955
  "data-testid": "collection-view-add",
4821
- onClick: addCustomView
4822
- }, [..._cache[34] || (_cache[34] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_28)) : (0, vue.createCommentVNode)("", true),
4956
+ onClick: onAddViewClick
4957
+ }, [..._cache[36] || (_cache[36] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_28), addMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_29, [(0, vue.createElementVNode)("button", {
4958
+ type: "button",
4959
+ class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4960
+ "data-testid": "collection-view-add-desktop",
4961
+ onClick: _cache[5] || (_cache[5] = ($event) => addCustomView("desktop"))
4962
+ }, [_cache[37] || (_cache[37] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "dashboard_customize", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewDesktop")), 1)]), (0, vue.createElementVNode)("button", {
4963
+ type: "button",
4964
+ class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4965
+ "data-testid": "collection-view-add-mobile",
4966
+ onClick: _cache[6] || (_cache[6] = ($event) => addCustomView("mobile"))
4967
+ }, [_cache[38] || (_cache[38] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "smartphone", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewMobile")), 1)])])) : (0, vue.createCommentVNode)("", true)], 512)) : (0, vue.createCommentVNode)("", true),
4823
4968
  canConfigureViews.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4824
4969
  key: 3,
4825
4970
  type: "button",
@@ -4827,8 +4972,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4827
4972
  title: (0, vue.unref)(t)("collectionsView.config.open"),
4828
4973
  "aria-label": (0, vue.unref)(t)("collectionsView.config.open"),
4829
4974
  "data-testid": "collection-config-open",
4830
- onClick: _cache[5] || (_cache[5] = ($event) => configOpen.value = true)
4831
- }, [..._cache[35] || (_cache[35] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_29)) : (0, vue.createCommentVNode)("", true)
4975
+ onClick: _cache[7] || (_cache[7] = ($event) => configOpen.value = true)
4976
+ }, [..._cache[39] || (_cache[39] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_30)) : (0, vue.createCommentVNode)("", true)
4832
4977
  ], 8, _hoisted_22$2)) : (0, vue.createCommentVNode)("", true),
4833
4978
  calendarActive.value && dateFields.value.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
4834
4979
  key: 1,
@@ -4836,42 +4981,42 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4836
4981
  class: "h-8 px-2 rounded border border-slate-200 bg-white text-xs font-semibold text-slate-600 focus:outline-none focus:border-indigo-500 cursor-pointer",
4837
4982
  "aria-label": (0, vue.unref)(t)("collectionsView.calendarFieldLabel"),
4838
4983
  "data-testid": "collection-calendar-field",
4839
- onChange: _cache[6] || (_cache[6] = ($event) => anchorOverride.value = $event.target.value)
4984
+ onChange: _cache[8] || (_cache[8] = ($event) => anchorOverride.value = $event.target.value)
4840
4985
  }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(dateFields.value, (key) => {
4841
4986
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
4842
4987
  key,
4843
4988
  value: key
4844
- }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_31);
4845
- }), 128))], 40, _hoisted_30)) : (0, vue.createCommentVNode)("", true),
4989
+ }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_32);
4990
+ }), 128))], 40, _hoisted_31)) : (0, vue.createCommentVNode)("", true),
4846
4991
  kanbanActive.value && enumFields.value.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
4847
4992
  key: 2,
4848
4993
  value: kanbanGroupField.value,
4849
4994
  class: "h-8 px-2 rounded border border-slate-200 bg-white text-xs font-semibold text-slate-600 focus:outline-none focus:border-indigo-500 cursor-pointer",
4850
4995
  "aria-label": (0, vue.unref)(t)("collectionsView.kanbanFieldLabel"),
4851
4996
  "data-testid": "collection-kanban-field",
4852
- onChange: _cache[7] || (_cache[7] = ($event) => kanbanOverride.value = $event.target.value)
4997
+ onChange: _cache[9] || (_cache[9] = ($event) => kanbanOverride.value = $event.target.value)
4853
4998
  }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(enumFields.value, (key) => {
4854
4999
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
4855
5000
  key,
4856
5001
  value: key
4857
- }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_33);
4858
- }), 128))], 40, _hoisted_32)) : (0, vue.createCommentVNode)("", true),
4859
- items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_34, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.searchSummary", {
5002
+ }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_34);
5003
+ }), 128))], 40, _hoisted_33)) : (0, vue.createCommentVNode)("", true),
5004
+ items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_35, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.searchSummary", {
4860
5005
  shown: filteredItems.value.length,
4861
5006
  total: items.value.length
4862
5007
  })), 1)) : (0, vue.createCommentVNode)("", true)
4863
5008
  ])])) : (0, vue.createCommentVNode)("", true),
4864
- collection.value && dataIssues.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_35, [
4865
- _cache[37] || (_cache[37] = (0, vue.createElementVNode)("span", { class: "material-icons text-amber-600" }, "warning", -1)),
4866
- (0, vue.createElementVNode)("span", _hoisted_36, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
5009
+ collection.value && dataIssues.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_36, [
5010
+ _cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "material-icons text-amber-600" }, "warning", -1)),
5011
+ (0, vue.createElementVNode)("span", _hoisted_37, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
4867
5012
  (0, vue.createElementVNode)("button", {
4868
5013
  type: "button",
4869
5014
  class: "h-8 px-2.5 flex items-center gap-1 rounded border border-amber-300 bg-white hover:bg-amber-100 text-amber-700 font-bold text-xs transition-colors",
4870
5015
  "data-testid": "collections-repair",
4871
5016
  onClick: repairCollection
4872
- }, [_cache[36] || (_cache[36] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "build", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.repair")), 1)])
5017
+ }, [_cache[40] || (_cache[40] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "build", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.repair")), 1)])
4873
5018
  ])) : (0, vue.createCommentVNode)("", true),
4874
- (0, vue.createElementVNode)("div", _hoisted_37, [loading.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_38, [_cache[38] || (_cache[38] = (0, vue.createElementVNode)("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.loading")), 1)])) : loadError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_39, [_cache[39] || (_cache[39] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(loadError.value === "not-found" ? (0, vue.unref)(t)("collectionsView.notFound") : `${(0, vue.unref)(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_40)) : calendarActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_41, [(0, vue.createVNode)(CollectionCalendarView_default, {
5019
+ (0, vue.createElementVNode)("div", _hoisted_38, [loading.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_39, [_cache[42] || (_cache[42] = (0, vue.createElementVNode)("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.loading")), 1)])) : loadError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_40, [_cache[43] || (_cache[43] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(loadError.value === "not-found" ? (0, vue.unref)(t)("collectionsView.notFound") : `${(0, vue.unref)(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_41)) : calendarActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42, [(0, vue.createVNode)(CollectionCalendarView_default, {
4875
5020
  schema: collection.value.schema,
4876
5021
  items: filteredItems.value,
4877
5022
  "anchor-field": calendarAnchorField.value,
@@ -4907,7 +5052,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4907
5052
  }, {
4908
5053
  detail: (0, vue.withCtx)(() => [(0, vue.createVNode)(CollectionRecordPanel_default, {
4909
5054
  editing: editing.value,
4910
- "onUpdate:editing": _cache[8] || (_cache[8] = ($event) => editing.value = $event),
5055
+ "onUpdate:editing": _cache[10] || (_cache[10] = ($event) => editing.value = $event),
4911
5056
  collection: collection.value,
4912
5057
  viewing: viewing.value,
4913
5058
  saving: saving.value,
@@ -4925,7 +5070,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4925
5070
  onCancel: cancelEditor,
4926
5071
  onEdit: editFromView,
4927
5072
  onClose: onDayClose,
4928
- onDelete: _cache[9] || (_cache[9] = ($event) => viewing.value && confirmDelete(viewing.value)),
5073
+ onDelete: _cache[11] || (_cache[11] = ($event) => viewing.value && confirmDelete(viewing.value)),
4929
5074
  onRunAction: runAction,
4930
5075
  onItemChat
4931
5076
  }, null, 8, [
@@ -4956,16 +5101,16 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4956
5101
  "selected",
4957
5102
  "can-create",
4958
5103
  "show-detail"
4959
- ])) : (0, vue.createCommentVNode)("", true)])) : kanbanActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_43, [
4960
- _cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
4961
- (0, vue.createElementVNode)("span", _hoisted_44, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5104
+ ])) : (0, vue.createCommentVNode)("", true)])) : kanbanActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_43, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_44, [
5105
+ _cache[45] || (_cache[45] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5106
+ (0, vue.createElementVNode)("span", _hoisted_45, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
4962
5107
  (0, vue.createElementVNode)("button", {
4963
5108
  type: "button",
4964
5109
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
4965
5110
  "aria-label": (0, vue.unref)(t)("common.close"),
4966
- onClick: _cache[10] || (_cache[10] = ($event) => inlineError.value = null)
4967
- }, [..._cache[40] || (_cache[40] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_45)
4968
- ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_46, [(0, vue.createVNode)(CollectionKanbanView_default, {
5111
+ onClick: _cache[12] || (_cache[12] = ($event) => inlineError.value = null)
5112
+ }, [..._cache[44] || (_cache[44] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_46)
5113
+ ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_47, [(0, vue.createVNode)(CollectionKanbanView_default, {
4969
5114
  schema: collection.value.schema,
4970
5115
  items: filteredItems.value,
4971
5116
  "group-field": kanbanGroupField.value,
@@ -4979,7 +5124,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4979
5124
  "group-field",
4980
5125
  "selected",
4981
5126
  "notified"
4982
- ])])])) : activeCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_47, [activeCustomView.value.target === "mobile" ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRemoteViewPreview_default, {
5127
+ ])])])) : activeCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [activeCustomView.value.target === "mobile" ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRemoteViewPreview_default, {
4983
5128
  key: 0,
4984
5129
  slug: collection.value.slug,
4985
5130
  view: activeCustomView.value,
@@ -4990,32 +5135,32 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4990
5135
  view: activeCustomView.value,
4991
5136
  onOpenItem: onCustomViewOpenItem,
4992
5137
  onStartChat: onCustomViewStartChat
4993
- }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [_cache[42] || (_cache[42] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), (0, vue.createElementVNode)("p", _hoisted_49, (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_50, [
4994
- _cache[43] || (_cache[43] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
4995
- (0, vue.createElementVNode)("p", _hoisted_51, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noMatchingItems")), 1),
5138
+ }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_49, [_cache[46] || (_cache[46] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), (0, vue.createElementVNode)("p", _hoisted_50, (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_51, [
5139
+ _cache[47] || (_cache[47] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
5140
+ (0, vue.createElementVNode)("p", _hoisted_52, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noMatchingItems")), 1),
4996
5141
  (0, vue.createElementVNode)("button", {
4997
5142
  type: "button",
4998
5143
  class: "text-xs text-indigo-600 font-semibold hover:underline",
4999
- onClick: _cache[11] || (_cache[11] = ($event) => searchQuery.value = "")
5144
+ onClick: _cache[13] || (_cache[13] = ($event) => searchQuery.value = "")
5000
5145
  }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.clearSearch")), 1)
5001
- ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_52, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_53, [
5002
- _cache[45] || (_cache[45] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5003
- (0, vue.createElementVNode)("span", _hoisted_54, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5146
+ ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_53, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_54, [
5147
+ _cache[49] || (_cache[49] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5148
+ (0, vue.createElementVNode)("span", _hoisted_55, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5004
5149
  (0, vue.createElementVNode)("button", {
5005
5150
  type: "button",
5006
5151
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
5007
5152
  "aria-label": (0, vue.unref)(t)("common.close"),
5008
- onClick: _cache[12] || (_cache[12] = ($event) => inlineError.value = null)
5009
- }, [..._cache[44] || (_cache[44] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_55)
5010
- ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("table", _hoisted_56, [(0, vue.createElementVNode)("thead", null, [(0, vue.createElementVNode)("tr", _hoisted_57, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(listColumnFields.value, ([key, field]) => {
5153
+ onClick: _cache[14] || (_cache[14] = ($event) => inlineError.value = null)
5154
+ }, [..._cache[48] || (_cache[48] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_56)
5155
+ ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("table", _hoisted_57, [(0, vue.createElementVNode)("thead", null, [(0, vue.createElementVNode)("tr", _hoisted_58, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(listColumnFields.value, ([key, field]) => {
5011
5156
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("th", {
5012
5157
  key,
5013
5158
  "aria-sort": (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? sortAriaValue(key) : void 0,
5014
5159
  class: "px-5 py-3 font-bold text-slate-500 text-left uppercase tracking-wider whitespace-nowrap"
5015
- }, [(0, vue.createElementVNode)("div", _hoisted_59, [(0, vue.createElementVNode)("span", {
5160
+ }, [(0, vue.createElementVNode)("div", _hoisted_60, [(0, vue.createElementVNode)("span", {
5016
5161
  class: "truncate max-w-[14rem]",
5017
5162
  title: field.label
5018
- }, (0, vue.toDisplayString)(field.label), 9, _hoisted_60), (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
5163
+ }, (0, vue.toDisplayString)(field.label), 9, _hoisted_61), (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
5019
5164
  key: 0,
5020
5165
  type: "button",
5021
5166
  class: (0, vue.normalizeClass)(["inline-flex items-center justify-center rounded p-0.5 -my-1 leading-none transition-colors", sortButtonClass(key)]),
@@ -5023,9 +5168,9 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5023
5168
  "aria-label": (0, vue.unref)(t)("collectionsView.sortBy", { field: field.label }),
5024
5169
  onClick: (0, vue.withModifiers)(($event) => cycleSort(key), ["stop"]),
5025
5170
  onPointerenter: ($event) => hoveredSortKey.value = key,
5026
- onPointerleave: _cache[13] || (_cache[13] = ($event) => hoveredSortKey.value = null)
5027
- }, [(0, vue.createElementVNode)("span", _hoisted_62, (0, vue.toDisplayString)(sortIconName(key)), 1)], 42, _hoisted_61)) : (0, vue.createCommentVNode)("", true)])], 8, _hoisted_58);
5028
- }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_63, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(sortedItems.value, (item) => {
5171
+ onPointerleave: _cache[15] || (_cache[15] = ($event) => hoveredSortKey.value = null)
5172
+ }, [(0, vue.createElementVNode)("span", _hoisted_63, (0, vue.toDisplayString)(sortIconName(key)), 1)], 42, _hoisted_62)) : (0, vue.createCommentVNode)("", true)])], 8, _hoisted_59);
5173
+ }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_64, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(sortedItems.value, (item) => {
5029
5174
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
5030
5175
  key: String(item[collection.value.schema.primaryKey] ?? ""),
5031
5176
  class: (0, vue.normalizeClass)(["hover:bg-slate-50/70 cursor-pointer transition-colors focus:outline-none focus:bg-indigo-50/30", isRowOpen(item) || isEditingRow(item) ? "bg-indigo-50/40" : ""]),
@@ -5047,9 +5192,9 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5047
5192
  class: "h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer align-middle disabled:opacity-50 disabled:cursor-not-allowed",
5048
5193
  "data-testid": `collections-inline-toggle-${key}-${item[collection.value.schema.primaryKey]}`,
5049
5194
  "aria-label": field.label,
5050
- onClick: _cache[14] || (_cache[14] = (0, vue.withModifiers)(() => {}, ["stop"])),
5195
+ onClick: _cache[16] || (_cache[16] = (0, vue.withModifiers)(() => {}, ["stop"])),
5051
5196
  onChange: ($event) => commitToggle(item, field)
5052
- }, null, 40, _hoisted_65)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
5197
+ }, null, 40, _hoisted_66)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
5053
5198
  key: 1,
5054
5199
  type: "checkbox",
5055
5200
  checked: item[key] === true,
@@ -5057,9 +5202,9 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5057
5202
  class: "h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer align-middle disabled:opacity-50 disabled:cursor-not-allowed",
5058
5203
  "data-testid": `collections-inline-bool-${key}-${item[collection.value.schema.primaryKey]}`,
5059
5204
  "aria-label": field.label,
5060
- onClick: _cache[15] || (_cache[15] = (0, vue.withModifiers)(() => {}, ["stop"])),
5205
+ onClick: _cache[17] || (_cache[17] = (0, vue.withModifiers)(() => {}, ["stop"])),
5061
5206
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.checked)
5062
- }, null, 40, _hoisted_66)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_67, [(0, vue.createElementVNode)("a", {
5207
+ }, null, 40, _hoisted_67)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_68, [(0, vue.createElementVNode)("a", {
5063
5208
  href: (0, vue.unref)(cui).recordHref?.(field.to, String(item[key])),
5064
5209
  tabindex: (0, vue.unref)(cui).recordHref?.(field.to, String(item[key])) ? void 0 : 0,
5065
5210
  role: "link",
@@ -5067,44 +5212,44 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5067
5212
  "data-testid": `collections-ref-link-${key}-${item[key]}`,
5068
5213
  onClick: ($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true),
5069
5214
  onKeydown: [(0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true), ["enter"]), (0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true), ["space"])]
5070
- }, (0, vue.toDisplayString)((0, vue.unref)(refDisplay)(field.to, String(item[key]))), 41, _hoisted_68)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
5215
+ }, (0, vue.toDisplayString)((0, vue.unref)(refDisplay)(field.to, String(item[key]))), 41, _hoisted_69)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
5071
5216
  key: 3,
5072
5217
  value: item[key] == null ? "" : String(item[key]),
5073
5218
  disabled: isRowInlineSaving(item),
5074
5219
  class: (0, vue.normalizeClass)(["rounded-lg border px-2 py-0.5 text-[11px] font-semibold focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", enumControlClass(String(key), item[key])]),
5075
5220
  "data-testid": `collections-inline-enum-${key}-${item[collection.value.schema.primaryKey]}`,
5076
5221
  "aria-label": field.label,
5077
- onClick: _cache[16] || (_cache[16] = (0, vue.withModifiers)(() => {}, ["stop"])),
5222
+ onClick: _cache[18] || (_cache[18] = (0, vue.withModifiers)(() => {}, ["stop"])),
5078
5223
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.value)
5079
- }, [showEnumPlaceholder(item, String(key)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("option", _hoisted_70, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1)) : (0, vue.createCommentVNode)("", true), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
5224
+ }, [showEnumPlaceholder(item, String(key)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("option", _hoisted_71, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1)) : (0, vue.createCommentVNode)("", true), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
5080
5225
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
5081
5226
  key: value,
5082
5227
  value
5083
- }, (0, vue.toDisplayString)(value), 9, _hoisted_71);
5084
- }), 128))], 42, _hoisted_69)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_72, (0, vue.toDisplayString)((0, vue.unref)(formatMoney)(item[key], (0, vue.unref)(resolveCurrency)(field, item), (0, vue.unref)(locale))), 1)) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_73, [_cache[46] || (_cache[46] = (0, vue.createElementVNode)("span", { class: "material-icons text-[11px]" }, "list", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(tableSummary(item[key])), 1)])) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_74, (0, vue.toDisplayString)((0, vue.unref)(derivedDisplay)(field, (0, vue.unref)(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type !== "file" && (0, vue.unref)(isExternalUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5228
+ }, (0, vue.toDisplayString)(value), 9, _hoisted_72);
5229
+ }), 128))], 42, _hoisted_70)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_73, (0, vue.toDisplayString)((0, vue.unref)(formatMoney)(item[key], (0, vue.unref)(resolveCurrency)(field, item), (0, vue.unref)(locale))), 1)) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_74, [_cache[50] || (_cache[50] = (0, vue.createElementVNode)("span", { class: "material-icons text-[11px]" }, "list", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(tableSummary(item[key])), 1)])) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_75, (0, vue.toDisplayString)((0, vue.unref)(derivedDisplay)(field, (0, vue.unref)(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type !== "file" && (0, vue.unref)(isExternalUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5085
5230
  key: 7,
5086
5231
  href: String(item[key]),
5087
5232
  target: "_blank",
5088
5233
  rel: "noopener noreferrer",
5089
5234
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5090
5235
  "data-testid": `collections-url-link-${key}-${item[collection.value.schema.primaryKey]}`,
5091
- onClick: _cache[17] || (_cache[17] = (0, vue.withModifiers)(() => {}, ["stop"]))
5092
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_75)) : field.type === "file" && (0, vue.unref)(artifactUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5236
+ onClick: _cache[19] || (_cache[19] = (0, vue.withModifiers)(() => {}, ["stop"]))
5237
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_76)) : field.type === "file" && (0, vue.unref)(artifactUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5093
5238
  key: 8,
5094
5239
  href: (0, vue.unref)(artifactUrl)(item[key]) ?? void 0,
5095
5240
  target: "_blank",
5096
5241
  rel: "noopener noreferrer",
5097
5242
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5098
5243
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5099
- onClick: _cache[18] || (_cache[18] = (0, vue.withModifiers)(() => {}, ["stop"]))
5100
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_76)) : field.type === "file" && (0, vue.unref)(fileRoutePath)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5244
+ onClick: _cache[20] || (_cache[20] = (0, vue.withModifiers)(() => {}, ["stop"]))
5245
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_77)) : field.type === "file" && (0, vue.unref)(fileRoutePath)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5101
5246
  key: 9,
5102
5247
  href: (0, vue.unref)(fileRoutePath)(item[key]) ?? void 0,
5103
5248
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5104
5249
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5105
5250
  onClick: ($event) => (0, vue.unref)(activatePathLink)($event, (0, vue.unref)(fileRoutePath)(item[key]) ?? "", true)
5106
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_77)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_78, (0, vue.toDisplayString)((0, vue.unref)(formatCell)(item[key], field.type)), 1))], 64)) : (0, vue.createCommentVNode)("", true)]);
5107
- }), 128))], 42, _hoisted_64);
5251
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_78)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_79, (0, vue.toDisplayString)((0, vue.unref)(formatCell)(item[key], field.type)), 1))], 64)) : (0, vue.createCommentVNode)("", true)]);
5252
+ }), 128))], 42, _hoisted_65);
5108
5253
  }), 128))])])]))]),
5109
5254
  collection.value && (viewing.value || editing.value) && !(calendarActive.value && openDay.value) ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRecordModal_default, {
5110
5255
  key: 4,
@@ -5112,7 +5257,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5112
5257
  }, {
5113
5258
  default: (0, vue.withCtx)(() => [(0, vue.createVNode)(CollectionRecordPanel_default, {
5114
5259
  editing: editing.value,
5115
- "onUpdate:editing": _cache[19] || (_cache[19] = ($event) => editing.value = $event),
5260
+ "onUpdate:editing": _cache[21] || (_cache[21] = ($event) => editing.value = $event),
5116
5261
  collection: collection.value,
5117
5262
  viewing: viewing.value,
5118
5263
  saving: saving.value,
@@ -5130,7 +5275,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5130
5275
  onCancel: cancelEditor,
5131
5276
  onEdit: editFromView,
5132
5277
  onClose: closeView,
5133
- onDelete: _cache[20] || (_cache[20] = ($event) => viewing.value && confirmDelete(viewing.value)),
5278
+ onDelete: _cache[22] || (_cache[22] = ($event) => viewing.value && confirmDelete(viewing.value)),
5134
5279
  onRunAction: runAction,
5135
5280
  onItemChat
5136
5281
  }, null, 8, [
@@ -5157,7 +5302,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5157
5302
  title: collection.value.title,
5158
5303
  views: customViews.value,
5159
5304
  onChanged: onViewsChanged,
5160
- onClose: _cache[21] || (_cache[21] = ($event) => configOpen.value = false)
5305
+ onClose: _cache[23] || (_cache[23] = ($event) => configOpen.value = false)
5161
5306
  }, null, 8, [
5162
5307
  "slug",
5163
5308
  "title",
@@ -5172,29 +5317,29 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5172
5317
  "data-testid": "collections-chat-modal",
5173
5318
  onClick: (0, vue.withModifiers)(closeChat, ["self"]),
5174
5319
  onKeydown: (0, vue.withKeys)(closeChat, ["esc"])
5175
- }, [(0, vue.createElementVNode)("div", _hoisted_79, [
5176
- (0, vue.createElementVNode)("header", _hoisted_80, [
5177
- _cache[48] || (_cache[48] = (0, vue.createElementVNode)("div", { class: "h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "forum")], -1)),
5178
- (0, vue.createElementVNode)("div", _hoisted_81, [(0, vue.createElementVNode)("h2", _hoisted_82, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatTitle")), 1), (0, vue.createElementVNode)("span", _hoisted_83, (0, vue.toDisplayString)(collection.value.title), 1)]),
5320
+ }, [(0, vue.createElementVNode)("div", _hoisted_80, [
5321
+ (0, vue.createElementVNode)("header", _hoisted_81, [
5322
+ _cache[52] || (_cache[52] = (0, vue.createElementVNode)("div", { class: "h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "forum")], -1)),
5323
+ (0, vue.createElementVNode)("div", _hoisted_82, [(0, vue.createElementVNode)("h2", _hoisted_83, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatTitle")), 1), (0, vue.createElementVNode)("span", _hoisted_84, (0, vue.toDisplayString)(collection.value.title), 1)]),
5179
5324
  (0, vue.createElementVNode)("button", {
5180
5325
  type: "button",
5181
5326
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-200/50 hover:text-slate-600 transition-colors",
5182
5327
  "aria-label": (0, vue.unref)(t)("common.close"),
5183
5328
  "data-testid": "collections-chat-close",
5184
5329
  onClick: closeChat
5185
- }, [..._cache[47] || (_cache[47] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_84)
5330
+ }, [..._cache[51] || (_cache[51] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_85)
5186
5331
  ]),
5187
- (0, vue.createElementVNode)("div", _hoisted_85, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
5332
+ (0, vue.createElementVNode)("div", _hoisted_86, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
5188
5333
  ref_key: "chatInputEl",
5189
5334
  ref: chatInputEl,
5190
- "onUpdate:modelValue": _cache[22] || (_cache[22] = ($event) => chatMessage.value = $event),
5335
+ "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => chatMessage.value = $event),
5191
5336
  rows: "4",
5192
5337
  placeholder: (0, vue.unref)(t)("collectionsView.chatPlaceholder"),
5193
5338
  class: "w-full bg-slate-50 border border-slate-200/80 rounded-xl px-3 py-2.5 text-sm 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",
5194
5339
  "data-testid": "collections-chat-input",
5195
5340
  onKeydown: [(0, vue.withKeys)((0, vue.withModifiers)(submitChat, ["meta"]), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(submitChat, ["ctrl"]), ["enter"])]
5196
- }, null, 40, _hoisted_86), [[vue.vModelText, chatMessage.value]])]),
5197
- (0, vue.createElementVNode)("footer", _hoisted_87, [(0, vue.createElementVNode)("button", {
5341
+ }, null, 40, _hoisted_87), [[vue.vModelText, chatMessage.value]])]),
5342
+ (0, vue.createElementVNode)("footer", _hoisted_88, [(0, vue.createElementVNode)("button", {
5198
5343
  type: "button",
5199
5344
  class: "h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors",
5200
5345
  "data-testid": "collections-chat-cancel",
@@ -5205,7 +5350,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5205
5350
  disabled: !chatMessage.value.trim(),
5206
5351
  "data-testid": "collections-chat-send",
5207
5352
  onClick: submitChat
5208
- }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatStart")), 9, _hoisted_88)])
5353
+ }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatStart")), 9, _hoisted_89)])
5209
5354
  ])], 32)) : (0, vue.createCommentVNode)("", true)
5210
5355
  ]);
5211
5356
  };