@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.js CHANGED
@@ -118,6 +118,8 @@ var i18n = createI18n({
118
118
  kanbanUncategorized: "Uncategorized",
119
119
  kanbanOpenCard: "Open {label}",
120
120
  addView: "Add view",
121
+ addViewDesktop: "Custom view",
122
+ addViewMobile: "Phone view",
121
123
  config: {
122
124
  open: "Collection settings",
123
125
  title: "{title} · settings",
@@ -129,6 +131,7 @@ var i18n = createI18n({
129
131
  customViewLoading: "Loading view…",
130
132
  customViewError: "Couldn't load this view: {error}",
131
133
  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.",
134
+ 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.",
132
135
  repair: "Repair",
133
136
  dataIssuesDetected: "{count} record file(s) have data problems and may be missing from this view.",
134
137
  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.",
@@ -251,6 +254,8 @@ var i18n = createI18n({
251
254
  kanbanUncategorized: "未分類",
252
255
  kanbanOpenCard: "{label} を開く",
253
256
  addView: "ビューを追加",
257
+ addViewDesktop: "カスタムビュー",
258
+ addViewMobile: "スマホビュー",
254
259
  config: {
255
260
  open: "コレクション設定",
256
261
  title: "{title} · 設定",
@@ -262,6 +267,7 @@ var i18n = createI18n({
262
267
  customViewLoading: "ビューを読み込み中…",
263
268
  customViewError: "このビューを読み込めませんでした: {error}",
264
269
  addViewPrompt: "「{title}」コレクションにカスタムビューを追加したい。何を見たい/編集したいか質問してから、HTML ビューファイルを {base}/views/your-view.html に作成し、{base}/schema.json の `views[]` に登録して(読み取り専用なら capabilities [\"read\"]、レコードを編集するなら [\"read\",\"write\"])。データ契約は custom-view ヘルプに従ってください。",
270
+ addMobileViewPrompt: "「{title}」コレクションにスマホ(リモートアプリ)用カスタムビューを追加したい。何を見たい/編集したいか質問してから、HTML ビューファイルを {base}/views/your-view.html に作成し、{base}/schema.json の `views[]` に `target: \"mobile\"` 付きで登録して(デフォルトは読み取り専用。レコードを編集する場合のみ `editableFields` / `allowDelete` を宣言)。データ契約は custom-view-remote ヘルプに従ってください — ビュー内での fetch は一切禁止です。",
265
271
  repair: "修復",
266
272
  dataIssuesDetected: "{count} 件のレコードファイルにデータの問題があり、この表示に出てこない可能性があります。",
267
273
  repairPrompt: "コレクション「{title}」に、表示されない原因となるデータの問題を持つレコードファイルが {count} 件あります。それぞれを修正してください — ファイルを Read し、修正してから Write し直します:\n{issues}\n\n完了したら presentCollection を呼び出して、レコードが読み込めることを確認してください。",
@@ -384,6 +390,8 @@ var i18n = createI18n({
384
390
  kanbanUncategorized: "未分类",
385
391
  kanbanOpenCard: "打开 {label}",
386
392
  addView: "添加视图",
393
+ addViewDesktop: "自定义视图",
394
+ addViewMobile: "手机视图",
387
395
  config: {
388
396
  open: "集合设置",
389
397
  title: "{title} · 设置",
@@ -395,6 +403,7 @@ var i18n = createI18n({
395
403
  customViewLoading: "正在加载视图…",
396
404
  customViewError: "无法加载此视图:{error}",
397
405
  addViewPrompt: "我想为“{title}”集合添加一个自定义视图。先问我想查看或编辑什么,然后在 {base}/views/your-view.html 创建 HTML 视图文件,并在 {base}/schema.json 的 `views[]` 中注册(只读视图用 capabilities [\"read\"],需要编辑记录则用 [\"read\",\"write\"])。数据契约请遵循 custom-view 帮助文档。",
406
+ addMobileViewPrompt: "我想为“{title}”集合添加一个手机(远程应用)自定义视图。先问我想查看或编辑什么,然后在 {base}/views/your-view.html 创建 HTML 视图文件,并在 {base}/schema.json 的 `views[]` 中以 `target: \"mobile\"` 注册(默认只读;仅在需要编辑记录时声明 `editableFields` / `allowDelete`)。数据契约请遵循 custom-view-remote 帮助文档 — 视图内禁止任何 fetch。",
398
407
  repair: "修复",
399
408
  dataIssuesDetected: "有 {count} 个记录文件存在数据问题,可能未显示在此视图中。",
400
409
  repairPrompt: "集合 {title} 有 {count} 个记录文件存在导致无法显示的数据问题。请逐一修复——用 Read 读取该文件,更正后再用 Write 写回:\n{issues}\n\n完成后,调用 presentCollection 确认记录可以正常加载。",
@@ -517,6 +526,8 @@ var i18n = createI18n({
517
526
  kanbanUncategorized: "미분류",
518
527
  kanbanOpenCard: "{label} 열기",
519
528
  addView: "보기 추가",
529
+ addViewDesktop: "사용자 지정 보기",
530
+ addViewMobile: "스마트폰 보기",
520
531
  config: {
521
532
  open: "컬렉션 설정",
522
533
  title: "{title} · 설정",
@@ -528,6 +539,7 @@ var i18n = createI18n({
528
539
  customViewLoading: "보기를 불러오는 중…",
529
540
  customViewError: "이 보기를 불러오지 못했습니다: {error}",
530
541
  addViewPrompt: "{title} 컬렉션에 사용자 지정 보기를 추가하고 싶어요. 무엇을 보거나 편집하고 싶은지 먼저 물어본 뒤, {base}/views/your-view.html 에 HTML 보기 파일을 만들고 {base}/schema.json의 `views[]`에 등록해 주세요(읽기 전용 보기는 capabilities [\"read\"], 레코드를 편집하면 [\"read\",\"write\"]). 데이터 계약은 custom-view 도움말을 따르세요.",
542
+ addMobileViewPrompt: "{title} 컬렉션에 스마트폰(리모트 앱)용 사용자 지정 보기를 추가하고 싶어요. 무엇을 보거나 편집하고 싶은지 먼저 물어본 뒤, {base}/views/your-view.html 에 HTML 보기 파일을 만들고 {base}/schema.json의 `views[]`에 `target: \"mobile\"`과 함께 등록해 주세요(기본은 읽기 전용, 레코드를 편집할 때만 `editableFields` / `allowDelete` 선언). 데이터 계약은 custom-view-remote 도움말을 따르세요 — 보기 안에서 fetch는 일절 금지입니다.",
531
543
  repair: "복구",
532
544
  dataIssuesDetected: "{count}개의 레코드 파일에 데이터 문제가 있어 이 보기에 표시되지 않을 수 있습니다.",
533
545
  repairPrompt: "{title} 컬렉션에 표시되지 않는 원인이 되는 데이터 문제가 있는 레코드 파일이 {count}개 있습니다. 각 파일을 수정하세요 — Read로 파일을 읽고 수정한 뒤 Write로 다시 저장하세요:\n{issues}\n\n완료되면 presentCollection을 호출하여 레코드가 로드되는지 확인하세요.",
@@ -650,6 +662,8 @@ var i18n = createI18n({
650
662
  kanbanUncategorized: "Sin categoría",
651
663
  kanbanOpenCard: "Abrir {label}",
652
664
  addView: "Añadir vista",
665
+ addViewDesktop: "Vista personalizada",
666
+ addViewMobile: "Vista móvil",
653
667
  config: {
654
668
  open: "Ajustes de la colección",
655
669
  title: "{title} · ajustes",
@@ -661,6 +675,7 @@ var i18n = createI18n({
661
675
  customViewLoading: "Cargando vista…",
662
676
  customViewError: "No se pudo cargar esta vista: {error}",
663
677
  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.",
678
+ 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.",
664
679
  repair: "Reparar",
665
680
  dataIssuesDetected: "{count} archivo(s) de registro tienen problemas de datos y podrían no aparecer en esta vista.",
666
681
  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.",
@@ -783,6 +798,8 @@ var i18n = createI18n({
783
798
  kanbanUncategorized: "Sem categoria",
784
799
  kanbanOpenCard: "Abrir {label}",
785
800
  addView: "Adicionar visualização",
801
+ addViewDesktop: "Visualização personalizada",
802
+ addViewMobile: "Visualização para celular",
786
803
  config: {
787
804
  open: "Configurações da coleção",
788
805
  title: "{title} · configurações",
@@ -794,6 +811,7 @@ var i18n = createI18n({
794
811
  customViewLoading: "Carregando visualização…",
795
812
  customViewError: "Não foi possível carregar esta visualização: {error}",
796
813
  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.",
814
+ 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.",
797
815
  repair: "Reparar",
798
816
  dataIssuesDetected: "{count} arquivo(s) de registro têm problemas de dados e podem não aparecer nesta visualização.",
799
817
  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.",
@@ -916,6 +934,8 @@ var i18n = createI18n({
916
934
  kanbanUncategorized: "Non classé",
917
935
  kanbanOpenCard: "Ouvrir {label}",
918
936
  addView: "Ajouter une vue",
937
+ addViewDesktop: "Vue personnalisée",
938
+ addViewMobile: "Vue mobile",
919
939
  config: {
920
940
  open: "Paramètres de la collection",
921
941
  title: "{title} · paramètres",
@@ -927,6 +947,7 @@ var i18n = createI18n({
927
947
  customViewLoading: "Chargement de la vue…",
928
948
  customViewError: "Impossible de charger cette vue : {error}",
929
949
  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.",
950
+ 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.",
930
951
  repair: "Réparer",
931
952
  dataIssuesDetected: "{count} fichier(s) d'enregistrement présentent des problèmes de données et peuvent être absents de cette vue.",
932
953
  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.",
@@ -1049,6 +1070,8 @@ var i18n = createI18n({
1049
1070
  kanbanUncategorized: "Nicht kategorisiert",
1050
1071
  kanbanOpenCard: "{label} öffnen",
1051
1072
  addView: "Ansicht hinzufügen",
1073
+ addViewDesktop: "Benutzerdefinierte Ansicht",
1074
+ addViewMobile: "Smartphone-Ansicht",
1052
1075
  config: {
1053
1076
  open: "Sammlungseinstellungen",
1054
1077
  title: "{title} · Einstellungen",
@@ -1060,6 +1083,7 @@ var i18n = createI18n({
1060
1083
  customViewLoading: "Ansicht wird geladen…",
1061
1084
  customViewError: "Diese Ansicht konnte nicht geladen werden: {error}",
1062
1085
  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.",
1086
+ 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.",
1063
1087
  repair: "Reparieren",
1064
1088
  dataIssuesDetected: "{count} Datensatzdatei(en) haben Datenprobleme und fehlen möglicherweise in dieser Ansicht.",
1065
1089
  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.",
@@ -1693,7 +1717,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
1693
1717
  const emit = __emit;
1694
1718
  const { t } = useCollectionI18n();
1695
1719
  /** The Uncategorized column uses the empty string as its sentinel value. */
1696
- const groupSpec = computed(() => props.schema.fields[props.groupField]);
1720
+ const groupSpec = computed(() => {
1721
+ const spec = props.schema.fields[props.groupField];
1722
+ return spec?.type === "enum" ? spec : void 0;
1723
+ });
1697
1724
  /** Declared enum values become columns in order, with a trailing
1698
1725
  * Uncategorized column for empty/unknown values (also a drop target that
1699
1726
  * clears the field). The Uncategorized column is omitted when the group
@@ -1740,7 +1767,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
1740
1767
  const value = String(raw);
1741
1768
  return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;
1742
1769
  }
1743
- const visibleItems = computed(() => groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []);
1770
+ const visibleItems = computed(() => {
1771
+ const spec = groupSpec.value;
1772
+ return spec ? props.items.filter((item) => fieldVisible(spec, item)) : [];
1773
+ });
1744
1774
  const itemsByColumnMap = computed(() => {
1745
1775
  const map = /* @__PURE__ */ new Map();
1746
1776
  for (const column of columns.value) map.set(column.value, []);
@@ -2267,7 +2297,8 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2267
2297
  /** Required flag for an embed's per-record picker — read off the storage
2268
2298
  * field it writes (`idField`), since the embed itself stores nothing. */
2269
2299
  function embedPickerRequired(field) {
2270
- const target = field.idField ? props.collection.schema.fields[field.idField] : void 0;
2300
+ const idField = field.type === "embed" ? field.idField : void 0;
2301
+ const target = idField ? props.collection.schema.fields[idField] : void 0;
2271
2302
  return target ? isFieldRequiredInUi(target) : false;
2272
2303
  }
2273
2304
  /** Tailwind fill/text/border classes tinting an enum `<select>` by its current
@@ -2988,284 +3019,342 @@ var CollectionRemoteViewPreview_default = /*#__PURE__*/ _plugin_vue_export_helpe
2988
3019
  }
2989
3020
  }), [["__scopeId", "data-v-965ade6c"]]);
2990
3021
  //#endregion
2991
- //#region src/vue/useCollectionRendering.ts
3022
+ //#region src/vue/useCollectionRendering.helpers.ts
3023
+ var EM_DASH = "—";
3024
+ var DEFAULT_CURRENCY = "USD";
3025
+ var MARKDOWN_CELL_PREVIEW_MAX = 80;
2992
3026
  function stepForFieldType(type) {
2993
3027
  if (type === "money") return "0.01";
2994
3028
  if (type === "number") return "any";
2995
3029
  }
2996
- function useCollectionRendering(collection, locale) {
2997
- const refCache = ref({});
2998
- const refRecordCache = ref({});
2999
- const embedCache = ref({});
3000
- function resetLinkedCaches() {
3001
- refCache.value = {};
3002
- refRecordCache.value = {};
3003
- embedCache.value = {};
3004
- }
3005
- function uniqueRefTargets(schema) {
3006
- const targets = /* @__PURE__ */ new Set();
3007
- const walk = (fields) => {
3008
- for (const field of Object.values(fields)) {
3009
- if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3010
- if (field.type === "table" && field.of) walk(field.of);
3011
- }
3012
- };
3013
- walk(schema.fields);
3014
- return [...targets];
3015
- }
3016
- function uniqueEmbedTargets(schema) {
3017
- const targets = /* @__PURE__ */ new Set();
3018
- for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3019
- return [...targets];
3030
+ function inputTypeFor(type) {
3031
+ if (type === "email") return "email";
3032
+ if (type === "number") return "number";
3033
+ if (type === "money") return "number";
3034
+ if (type === "date") return "date";
3035
+ if (type === "datetime") return "datetime-local";
3036
+ return "text";
3037
+ }
3038
+ function isExternalUrl(value) {
3039
+ return typeof value === "string" && /^https?:\/\//i.test(value);
3040
+ }
3041
+ function detailText(value) {
3042
+ if (value === void 0 || value === null || value === "") return EM_DASH;
3043
+ return String(value);
3044
+ }
3045
+ function formatCell(value, type) {
3046
+ if (value === void 0 || value === null || value === "") return EM_DASH;
3047
+ if (type === "markdown" && typeof value === "string") return value.length > MARKDOWN_CELL_PREVIEW_MAX ? `${value.slice(0, MARKDOWN_CELL_PREVIEW_MAX)}…` : value;
3048
+ if (typeof value === "string" || typeof value === "number") return String(value);
3049
+ return JSON.stringify(value);
3050
+ }
3051
+ /** Resolve the ISO 4217 code for a money field: a per-record
3052
+ * `currencyField` (when present and non-blank) wins over the field's
3053
+ * literal `currency`. Only `money` / `derived` variants carry currency
3054
+ * keys; any other field resolves to undefined (the formatter's USD
3055
+ * fallback), as before. */
3056
+ function resolveCurrency(field, record) {
3057
+ if (field.type !== "money" && field.type !== "derived") return void 0;
3058
+ if (field.currencyField && record) {
3059
+ const code = record[field.currencyField];
3060
+ if (typeof code === "string" && code.trim().length > 0) return code;
3020
3061
  }
3021
- function buildRefDisplayMap(detail) {
3022
- const { fields, primaryKey } = detail.collection.schema;
3023
- const displayField = "name" in fields ? "name" : "title" in fields ? "title" : primaryKey;
3024
- const map = {};
3025
- for (const item of detail.items) {
3026
- const slugRaw = item[primaryKey];
3027
- if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
3028
- const displayRaw = item[displayField];
3029
- map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
3030
- }
3031
- return map;
3062
+ return field.currency;
3063
+ }
3064
+ function formatMoney(value, currency, displayLocale) {
3065
+ if (value === void 0 || value === "") return EM_DASH;
3066
+ const amount = typeof value === "number" ? value : Number(value);
3067
+ if (!Number.isFinite(amount)) return String(value);
3068
+ const currencyCode = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3069
+ try {
3070
+ return new Intl.NumberFormat(displayLocale, {
3071
+ style: "currency",
3072
+ currency: currencyCode
3073
+ }).format(amount);
3074
+ } catch {
3075
+ return String(amount);
3032
3076
  }
3033
- function buildRefRecordMap(detail) {
3034
- const { schema } = detail.collection;
3035
- const map = {};
3036
- for (const item of detail.items) {
3037
- const slugRaw = item[schema.primaryKey];
3038
- if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});
3039
- }
3040
- return map;
3077
+ }
3078
+ function currencySymbolForLocale(currency, locale) {
3079
+ const code = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3080
+ try {
3081
+ return new Intl.NumberFormat(locale, {
3082
+ style: "currency",
3083
+ currency: code
3084
+ }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
3085
+ } catch {
3086
+ return code;
3041
3087
  }
3042
- async function loadLinkedCollections(schema, expectedSlug) {
3043
- const refTargets = new Set(uniqueRefTargets(schema));
3044
- const embedTargets = new Set(uniqueEmbedTargets(schema));
3045
- const allTargets = [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])];
3046
- if (allTargets.length === 0) return;
3047
- const binding = collectionUi();
3048
- const results = await Promise.all(allTargets.map(async (target) => {
3049
- try {
3050
- return {
3051
- target,
3052
- result: await binding.fetchCollectionDetail(target)
3053
- };
3054
- } catch {
3055
- return {
3056
- target,
3057
- result: { ok: false }
3058
- };
3059
- }
3060
- }));
3061
- if (collection.value?.slug !== expectedSlug) return;
3062
- const nextRef = {};
3063
- const nextRefRecords = {};
3064
- const nextEmbed = {};
3065
- for (const { target, result } of results) {
3066
- if (!result.ok) continue;
3067
- if (refTargets.has(target)) {
3068
- nextRef[target] = buildRefDisplayMap(result.data);
3069
- nextRefRecords[target] = buildRefRecordMap(result.data);
3070
- }
3071
- if (embedTargets.has(target)) nextEmbed[target] = {
3072
- schema: result.data.collection.schema,
3073
- items: result.data.items
3074
- };
3088
+ }
3089
+ function tableRows(value) {
3090
+ if (!Array.isArray(value)) return [];
3091
+ return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
3092
+ }
3093
+ function hasTableRows(value) {
3094
+ return tableRows(value).length > 0;
3095
+ }
3096
+ /** Pick the field used to label a referenced/embedded record: prefer a
3097
+ * `name` field, then `title`, else fall back to the primary key. */
3098
+ function displayFieldFor(fields, primaryKey) {
3099
+ if ("name" in fields) return "name";
3100
+ if ("title" in fields) return "title";
3101
+ return primaryKey;
3102
+ }
3103
+ function uniqueRefTargets(schema) {
3104
+ const targets = /* @__PURE__ */ new Set();
3105
+ const walk = (fields) => {
3106
+ for (const field of Object.values(fields)) {
3107
+ if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3108
+ if (field.type === "table" && field.of) walk(field.of);
3075
3109
  }
3076
- refCache.value = nextRef;
3077
- refRecordCache.value = nextRefRecords;
3078
- embedCache.value = nextEmbed;
3079
- }
3080
- function refDisplay(targetSlug, itemSlug) {
3081
- const map = refCache.value[targetSlug];
3082
- return map && map[itemSlug] || itemSlug;
3083
- }
3084
- function refOptions(targetSlug) {
3085
- const map = refCache.value[targetSlug];
3086
- if (!map) return [];
3087
- return Object.entries(map).map(([slug, display]) => ({
3088
- slug,
3089
- display
3090
- })).sort((left, right) => left.display.localeCompare(right.display));
3110
+ };
3111
+ walk(schema.fields);
3112
+ return [...targets];
3113
+ }
3114
+ function uniqueEmbedTargets(schema) {
3115
+ const targets = /* @__PURE__ */ new Set();
3116
+ for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3117
+ return [...targets];
3118
+ }
3119
+ function buildRefDisplayMap(detail) {
3120
+ const { fields, primaryKey } = detail.collection.schema;
3121
+ const displayField = displayFieldFor(fields, primaryKey);
3122
+ const map = {};
3123
+ for (const item of detail.items) {
3124
+ const slugRaw = item[primaryKey];
3125
+ if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
3126
+ const displayRaw = item[displayField];
3127
+ map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
3091
3128
  }
3092
- /** Dropdown options for an `embed` field's per-record picker (`idField`):
3093
- * every record in the target collection, labelled by its name/title (or
3094
- * primary key). Built from `embedCache` so it works for embed targets
3095
- * that aren't also `ref` targets (the profile collection, say). */
3096
- function embedOptions(targetSlug) {
3097
- const data = embedCache.value[targetSlug];
3098
- if (!data) return [];
3099
- const { fields, primaryKey } = data.schema;
3100
- const displayField = "name" in fields ? "name" : "title" in fields ? "title" : primaryKey;
3101
- return data.items.map((item) => {
3102
- const slug = String(item[primaryKey] ?? "");
3103
- const labelRaw = item[displayField];
3104
- return {
3105
- slug,
3106
- display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
3107
- };
3108
- }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3129
+ return map;
3130
+ }
3131
+ function buildRefRecordMap(detail) {
3132
+ const { schema } = detail.collection;
3133
+ const map = {};
3134
+ for (const item of detail.items) {
3135
+ const slugRaw = item[schema.primaryKey];
3136
+ if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});
3109
3137
  }
3110
- function resolveEmbed(field, record) {
3111
- if (field.type !== "embed" || !field.to) return {
3112
- schema: null,
3113
- item: null
3114
- };
3115
- const targetId = embedTargetId(field, record);
3116
- const data = targetId ? embedCache.value[field.to] : void 0;
3117
- if (!data) return {
3118
- schema: null,
3119
- item: null
3120
- };
3121
- const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3138
+ return map;
3139
+ }
3140
+ function sortedRefOptions(map) {
3141
+ return Object.entries(map).map(([slug, display]) => ({
3142
+ slug,
3143
+ display
3144
+ })).sort((left, right) => left.display.localeCompare(right.display));
3145
+ }
3146
+ /** Dropdown options for an `embed` field's per-record picker: every
3147
+ * record in the target collection, labelled by its name/title (or
3148
+ * primary key), skipping records without a slug and sorted by label. */
3149
+ function buildEmbedOptions(schema, items) {
3150
+ const { fields, primaryKey } = schema;
3151
+ const displayField = displayFieldFor(fields, primaryKey);
3152
+ return items.map((item) => {
3153
+ const slug = String(item[primaryKey] ?? "");
3154
+ const labelRaw = item[displayField];
3122
3155
  return {
3123
- schema: data.schema,
3124
- item
3156
+ slug,
3157
+ display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
3125
3158
  };
3126
- }
3127
- function embedValue(field, value, record) {
3128
- if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale.value);
3129
- return detailText(value);
3130
- }
3131
- /** Build the read-only embed view-models for one record. A function of
3132
- * the open record (not a bare computed) because a per-record `idField`
3133
- * embed resolves a different target per row. */
3134
- function embedViewsFor(record) {
3135
- const out = {};
3136
- if (!collection.value) return out;
3137
- for (const [key, field] of Object.entries(collection.value.schema.fields)) {
3138
- if (field.type !== "embed") continue;
3139
- const { schema, item } = resolveEmbed(field, record);
3140
- const rows = [];
3141
- if (schema && item) for (const [subKey, subField] of Object.entries(schema.fields)) {
3142
- const value = item[subKey];
3143
- if (value === void 0 || value === null || value === "") continue;
3144
- rows.push({
3145
- key: subKey,
3146
- label: subField.label,
3147
- type: subField.type,
3148
- value,
3149
- display: embedValue(subField, value, item)
3150
- });
3151
- }
3152
- out[key] = {
3153
- found: Boolean(item),
3154
- rows,
3155
- targetSlug: field.to ?? "",
3156
- recordId: embedTargetId(field, record)
3157
- };
3158
- }
3159
- return out;
3160
- }
3161
- function resolveCurrency(field, record) {
3162
- if (field.currencyField && record) {
3163
- const code = record[field.currencyField];
3164
- if (typeof code === "string" && code.trim().length > 0) return code;
3165
- }
3166
- return field.currency;
3167
- }
3168
- function currencySymbol(currency) {
3169
- const code = currency && currency.length > 0 ? currency : "USD";
3170
- try {
3171
- return new Intl.NumberFormat(locale.value, {
3172
- style: "currency",
3173
- currency: code
3174
- }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
3175
- } catch {
3176
- return code;
3177
- }
3178
- }
3179
- function formatMoney(value, currency, displayLocale) {
3180
- if (value === void 0 || value === "") return "—";
3181
- const amount = typeof value === "number" ? value : Number(value);
3182
- if (!Number.isFinite(amount)) return String(value);
3183
- const currencyCode = currency && currency.length > 0 ? currency : "USD";
3159
+ }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3160
+ }
3161
+ //#endregion
3162
+ //#region src/vue/useLinkedCollectionCaches.ts
3163
+ /** The de-duplicated ref + embed target slugs a schema links to. `allTargets`
3164
+ * is the union (each target fetched once even when both ref'd and embedded). */
3165
+ function linkedTargets(schema) {
3166
+ const refTargets = new Set(uniqueRefTargets(schema));
3167
+ const embedTargets = new Set(uniqueEmbedTargets(schema));
3168
+ return {
3169
+ refTargets,
3170
+ embedTargets,
3171
+ allTargets: [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])]
3172
+ };
3173
+ }
3174
+ /** Fan-out fetch that hydrates the linked-collection caches. Best-effort: a
3175
+ * target whose fetch *rejects* (vs. resolving `{ ok: false }`) is coerced to a
3176
+ * skip and must not abort the others. Returns null when a quicker subsequent
3177
+ * load has already moved on (stale-write guard) so the caller drops the write.
3178
+ * Pure + injectable (`fetchDetail`, `currentSlug`) so both paths are testable. */
3179
+ async function fetchLinkedCaches(targets, fetchDetail, currentSlug, expectedSlug) {
3180
+ const { refTargets, embedTargets, allTargets } = targets;
3181
+ const results = await Promise.all(allTargets.map(async (target) => {
3184
3182
  try {
3185
- return new Intl.NumberFormat(displayLocale, {
3186
- style: "currency",
3187
- currency: currencyCode
3188
- }).format(amount);
3183
+ return {
3184
+ target,
3185
+ result: await fetchDetail(target)
3186
+ };
3189
3187
  } catch {
3190
- return String(amount);
3188
+ return {
3189
+ target,
3190
+ result: { ok: false }
3191
+ };
3191
3192
  }
3193
+ }));
3194
+ if (currentSlug() !== expectedSlug) return null;
3195
+ const refCache = {};
3196
+ const refRecordCache = {};
3197
+ const embedCache = {};
3198
+ for (const { target, result } of results) {
3199
+ if (!result.ok) continue;
3200
+ if (refTargets.has(target)) {
3201
+ refCache[target] = buildRefDisplayMap(result.data);
3202
+ refRecordCache[target] = buildRefRecordMap(result.data);
3203
+ }
3204
+ if (embedTargets.has(target)) embedCache[target] = {
3205
+ schema: result.data.collection.schema,
3206
+ items: result.data.items
3207
+ };
3192
3208
  }
3193
- function formatCell(value, type) {
3194
- if (value === void 0 || value === null || value === "") return "—";
3195
- if (type === "markdown" && typeof value === "string") return value.length > 80 ? `${value.slice(0, 80)}…` : value;
3196
- if (typeof value === "string" || typeof value === "number") return String(value);
3197
- return JSON.stringify(value);
3198
- }
3199
- function isExternalUrl(value) {
3200
- return typeof value === "string" && /^https?:\/\//i.test(value);
3201
- }
3202
- function artifactUrl(value) {
3203
- return collectionUi().fileAssetUrl(value);
3204
- }
3205
- function fileRoutePath(value) {
3206
- return collectionUi().fileRoutePath(value);
3207
- }
3208
- function detailText(value) {
3209
- if (value === void 0 || value === null || value === "") return "—";
3210
- return String(value);
3211
- }
3212
- function tableRows(value) {
3213
- if (!Array.isArray(value)) return [];
3214
- return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
3215
- }
3216
- function hasTableRows(value) {
3217
- return tableRows(value).length > 0;
3218
- }
3219
- function formatSubCell(subField, value, record) {
3220
- if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale.value);
3221
- if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return refDisplay(subField.to, value);
3222
- return formatCell(value, subField.type);
3223
- }
3224
- function inputTypeFor(type) {
3225
- if (type === "email") return "email";
3226
- if (type === "number") return "number";
3227
- if (type === "money") return "number";
3228
- if (type === "date") return "date";
3229
- if (type === "datetime") return "datetime-local";
3230
- return "text";
3231
- }
3232
- const stepFor = stepForFieldType;
3233
- function evaluateDerivedAgainstItem(field, fieldKey, item) {
3234
- if (!field.formula || !collection.value) return null;
3235
- const result = deriveAll(collection.value.schema, item, refRecordCache.value)[fieldKey];
3236
- return typeof result === "number" && Number.isFinite(result) ? result : null;
3209
+ return {
3210
+ refCache,
3211
+ refRecordCache,
3212
+ embedCache
3213
+ };
3214
+ }
3215
+ function useLinkedCollectionCaches(collection) {
3216
+ const refCache = ref({});
3217
+ const refRecordCache = ref({});
3218
+ const embedCache = ref({});
3219
+ function resetLinkedCaches() {
3220
+ refCache.value = {};
3221
+ refRecordCache.value = {};
3222
+ embedCache.value = {};
3237
3223
  }
3238
- function derivedDisplay(field, computedValue, record) {
3239
- if (computedValue === null || computedValue === void 0) return "—";
3240
- if (field.display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);
3241
- return formatCell(computedValue, field.display ?? "number");
3224
+ async function loadLinkedCollections(schema, expectedSlug) {
3225
+ const targets = linkedTargets(schema);
3226
+ if (targets.allTargets.length === 0) return;
3227
+ const binding = collectionUi();
3228
+ const snapshot = await fetchLinkedCaches(targets, (slug) => binding.fetchCollectionDetail(slug), () => collection.value?.slug, expectedSlug);
3229
+ if (!snapshot) return;
3230
+ refCache.value = snapshot.refCache;
3231
+ refRecordCache.value = snapshot.refRecordCache;
3232
+ embedCache.value = snapshot.embedCache;
3242
3233
  }
3243
3234
  return {
3244
3235
  refCache,
3245
3236
  refRecordCache,
3246
3237
  embedCache,
3247
3238
  resetLinkedCaches,
3248
- loadLinkedCollections,
3249
- refDisplay,
3250
- refOptions,
3251
- embedOptions,
3252
- embedViewsFor,
3253
- resolveCurrency,
3254
- currencySymbol,
3255
- formatMoney,
3256
- formatCell,
3257
- detailText,
3258
- isExternalUrl,
3259
- artifactUrl,
3260
- fileRoutePath,
3261
- tableRows,
3262
- hasTableRows,
3263
- formatSubCell,
3264
- inputTypeFor,
3265
- stepFor,
3266
- deriveAll,
3267
- evaluateDerivedAgainstItem,
3268
- derivedDisplay
3239
+ loadLinkedCollections
3240
+ };
3241
+ }
3242
+ //#endregion
3243
+ //#region src/vue/useCollectionRendering.renderers.ts
3244
+ function lookupRefDisplay(refCache, targetSlug, itemSlug) {
3245
+ const map = refCache[targetSlug];
3246
+ return map && map[itemSlug] || itemSlug;
3247
+ }
3248
+ function refOptionsFor(refCache, targetSlug) {
3249
+ const map = refCache[targetSlug];
3250
+ return map ? sortedRefOptions(map) : [];
3251
+ }
3252
+ /** Dropdown options for an `embed` field's per-record picker (`idField`):
3253
+ * every record in the target collection, labelled by its name/title (or
3254
+ * primary key). Built from `embedCache` so it works for embed targets
3255
+ * that aren't also `ref` targets (the profile collection, say). */
3256
+ function embedOptionsFor(embedCache, targetSlug) {
3257
+ const data = embedCache[targetSlug];
3258
+ return data ? buildEmbedOptions(data.schema, data.items) : [];
3259
+ }
3260
+ function resolveEmbed(field, record, embedCache) {
3261
+ if (field.type !== "embed" || !field.to) return {
3262
+ schema: null,
3263
+ item: null
3264
+ };
3265
+ const targetId = embedTargetId(field, record);
3266
+ const data = targetId ? embedCache[field.to] : void 0;
3267
+ if (!data) return {
3268
+ schema: null,
3269
+ item: null
3270
+ };
3271
+ const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3272
+ return {
3273
+ schema: data.schema,
3274
+ item
3275
+ };
3276
+ }
3277
+ function formatEmbedValue(field, value, record, locale) {
3278
+ if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale);
3279
+ return detailText(value);
3280
+ }
3281
+ /** Build the read-only embed view-models for one record. A function of the
3282
+ * open record (not a bare computed) because a per-record `idField` embed
3283
+ * resolves a different target per row. `schema` is the OPEN collection's
3284
+ * schema (null when no collection is loaded → no views). */
3285
+ function buildEmbedViews(schema, embedCache, record, locale) {
3286
+ const out = {};
3287
+ if (!schema) return out;
3288
+ for (const [key, field] of Object.entries(schema.fields)) {
3289
+ if (field.type !== "embed") continue;
3290
+ const { schema: targetSchema, item } = resolveEmbed(field, record, embedCache);
3291
+ const rows = [];
3292
+ if (targetSchema && item) for (const [subKey, subField] of Object.entries(targetSchema.fields)) {
3293
+ const value = item[subKey];
3294
+ if (value === void 0 || value === null || value === "") continue;
3295
+ rows.push({
3296
+ key: subKey,
3297
+ label: subField.label,
3298
+ type: subField.type,
3299
+ value,
3300
+ display: formatEmbedValue(subField, value, item, locale)
3301
+ });
3302
+ }
3303
+ out[key] = {
3304
+ found: Boolean(item),
3305
+ rows,
3306
+ targetSlug: field.to ?? "",
3307
+ recordId: embedTargetId(field, record)
3308
+ };
3309
+ }
3310
+ return out;
3311
+ }
3312
+ function renderSubCell(subField, value, record, refCache, locale) {
3313
+ if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale);
3314
+ if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return lookupRefDisplay(refCache, subField.to, value);
3315
+ return formatCell(value, subField.type);
3316
+ }
3317
+ function evaluateDerived(field, fieldKey, item, schema, refRecords) {
3318
+ if (field.type !== "derived" || !schema) return null;
3319
+ const result = deriveAll(schema, item, refRecords)[fieldKey];
3320
+ return typeof result === "number" && Number.isFinite(result) ? result : null;
3321
+ }
3322
+ function renderDerived(field, computedValue, record, locale) {
3323
+ if (computedValue === null || computedValue === void 0) return "—";
3324
+ const display = field.type === "derived" ? field.display : void 0;
3325
+ if (display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale);
3326
+ return formatCell(computedValue, display ?? "number");
3327
+ }
3328
+ //#endregion
3329
+ //#region src/vue/useCollectionRendering.ts
3330
+ var STATELESS_RENDERERS = {
3331
+ resolveCurrency,
3332
+ formatMoney,
3333
+ formatCell,
3334
+ detailText,
3335
+ isExternalUrl,
3336
+ tableRows,
3337
+ hasTableRows,
3338
+ inputTypeFor,
3339
+ stepFor: stepForFieldType,
3340
+ deriveAll
3341
+ };
3342
+ function useCollectionRendering(collection, locale) {
3343
+ const caches = useLinkedCollectionCaches(collection);
3344
+ const { refCache, refRecordCache, embedCache } = caches;
3345
+ return {
3346
+ ...caches,
3347
+ ...STATELESS_RENDERERS,
3348
+ refDisplay: (targetSlug, itemSlug) => lookupRefDisplay(refCache.value, targetSlug, itemSlug),
3349
+ refOptions: (targetSlug) => refOptionsFor(refCache.value, targetSlug),
3350
+ embedOptions: (targetSlug) => embedOptionsFor(embedCache.value, targetSlug),
3351
+ embedViewsFor: (record) => buildEmbedViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
3352
+ currencySymbol: (currency) => currencySymbolForLocale(currency, locale.value),
3353
+ artifactUrl: (value) => collectionUi().fileAssetUrl(value),
3354
+ fileRoutePath: (value) => collectionUi().fileRoutePath(value),
3355
+ formatSubCell: (subField, value, record) => renderSubCell(subField, value, record, refCache.value, locale.value),
3356
+ evaluateDerivedAgainstItem: (field, fieldKey, item) => evaluateDerived(field, fieldKey, item, collection.value?.schema ?? null, refRecordCache.value),
3357
+ derivedDisplay: (field, computedValue, record) => renderDerived(field, computedValue, record, locale.value)
3269
3358
  };
3270
3359
  }
3271
3360
  //#endregion
@@ -3412,166 +3501,175 @@ var _hoisted_26$1 = [
3412
3501
  "onClick"
3413
3502
  ];
3414
3503
  var _hoisted_27$1 = { class: "material-icons text-sm" };
3415
- var _hoisted_28 = ["title", "aria-label"];
3416
- var _hoisted_29 = ["title", "aria-label"];
3417
- var _hoisted_30 = ["value", "aria-label"];
3418
- var _hoisted_31 = ["value"];
3419
- var _hoisted_32 = ["value", "aria-label"];
3420
- var _hoisted_33 = ["value"];
3421
- var _hoisted_34 = {
3504
+ var _hoisted_28 = [
3505
+ "title",
3506
+ "aria-label",
3507
+ "aria-expanded"
3508
+ ];
3509
+ var _hoisted_29 = {
3510
+ key: 0,
3511
+ class: "absolute left-0 top-full mt-1 z-20 min-w-max rounded border border-slate-200 bg-white shadow-lg py-1",
3512
+ "data-testid": "collection-view-add-menu"
3513
+ };
3514
+ var _hoisted_30 = ["title", "aria-label"];
3515
+ var _hoisted_31 = ["value", "aria-label"];
3516
+ var _hoisted_32 = ["value"];
3517
+ var _hoisted_33 = ["value", "aria-label"];
3518
+ var _hoisted_34 = ["value"];
3519
+ var _hoisted_35 = {
3422
3520
  key: 3,
3423
3521
  class: "text-[10px] text-slate-400 font-bold uppercase tracking-wider select-none"
3424
3522
  };
3425
- var _hoisted_35 = {
3523
+ var _hoisted_36 = {
3426
3524
  key: 3,
3427
3525
  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",
3428
3526
  "data-testid": "collections-data-issues"
3429
3527
  };
3430
- var _hoisted_36 = { class: "flex-1" };
3431
- var _hoisted_37 = { class: "flex-1 overflow-auto" };
3432
- var _hoisted_38 = {
3528
+ var _hoisted_37 = { class: "flex-1" };
3529
+ var _hoisted_38 = { class: "flex-1 overflow-auto" };
3530
+ var _hoisted_39 = {
3433
3531
  key: 0,
3434
3532
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3"
3435
3533
  };
3436
- var _hoisted_39 = {
3534
+ var _hoisted_40 = {
3437
3535
  key: 1,
3438
3536
  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"
3439
3537
  };
3440
- var _hoisted_40 = { key: 2 };
3441
- var _hoisted_41 = {
3538
+ var _hoisted_41 = { key: 2 };
3539
+ var _hoisted_42 = {
3442
3540
  key: 3,
3443
3541
  class: "p-4"
3444
3542
  };
3445
- var _hoisted_42 = {
3543
+ var _hoisted_43 = {
3446
3544
  key: 4,
3447
3545
  class: "h-full flex flex-col"
3448
3546
  };
3449
- var _hoisted_43 = {
3547
+ var _hoisted_44 = {
3450
3548
  key: 0,
3451
3549
  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",
3452
3550
  "data-testid": "collections-inline-error"
3453
3551
  };
3454
- var _hoisted_44 = { class: "flex-1" };
3455
- var _hoisted_45 = ["aria-label"];
3456
- var _hoisted_46 = { class: "flex-1 min-h-0 px-3 py-2" };
3457
- var _hoisted_47 = {
3552
+ var _hoisted_45 = { class: "flex-1" };
3553
+ var _hoisted_46 = ["aria-label"];
3554
+ var _hoisted_47 = { class: "flex-1 min-h-0 px-3 py-2" };
3555
+ var _hoisted_48 = {
3458
3556
  key: 5,
3459
3557
  class: "h-full",
3460
3558
  "data-testid": "collection-custom-view-body"
3461
3559
  };
3462
- var _hoisted_48 = {
3560
+ var _hoisted_49 = {
3463
3561
  key: 6,
3464
3562
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3465
3563
  };
3466
- var _hoisted_49 = { class: "font-semibold text-slate-600" };
3467
- var _hoisted_50 = {
3564
+ var _hoisted_50 = { class: "font-semibold text-slate-600" };
3565
+ var _hoisted_51 = {
3468
3566
  key: 7,
3469
3567
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3470
3568
  };
3471
- var _hoisted_51 = { class: "font-semibold text-slate-600" };
3472
- var _hoisted_52 = {
3569
+ var _hoisted_52 = { class: "font-semibold text-slate-600" };
3570
+ var _hoisted_53 = {
3473
3571
  key: 8,
3474
3572
  class: "overflow-x-auto [container-type:inline-size]"
3475
3573
  };
3476
- var _hoisted_53 = {
3574
+ var _hoisted_54 = {
3477
3575
  key: 0,
3478
3576
  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",
3479
3577
  "data-testid": "collections-inline-error"
3480
3578
  };
3481
- var _hoisted_54 = { class: "flex-1" };
3482
- var _hoisted_55 = ["aria-label"];
3483
- var _hoisted_56 = { class: "min-w-full text-xs" };
3484
- var _hoisted_57 = { class: "bg-slate-50 border-b border-slate-200" };
3485
- var _hoisted_58 = ["aria-sort"];
3486
- var _hoisted_59 = { class: "flex items-center gap-1" };
3487
- var _hoisted_60 = ["title"];
3488
- var _hoisted_61 = [
3579
+ var _hoisted_55 = { class: "flex-1" };
3580
+ var _hoisted_56 = ["aria-label"];
3581
+ var _hoisted_57 = { class: "min-w-full text-xs" };
3582
+ var _hoisted_58 = { class: "bg-slate-50 border-b border-slate-200" };
3583
+ var _hoisted_59 = ["aria-sort"];
3584
+ var _hoisted_60 = { class: "flex items-center gap-1" };
3585
+ var _hoisted_61 = ["title"];
3586
+ var _hoisted_62 = [
3489
3587
  "data-testid",
3490
3588
  "aria-label",
3491
3589
  "onClick",
3492
3590
  "onPointerenter"
3493
3591
  ];
3494
- var _hoisted_62 = { class: "material-icons text-base align-middle" };
3495
- var _hoisted_63 = { class: "divide-y divide-slate-100 bg-white" };
3496
- var _hoisted_64 = [
3592
+ var _hoisted_63 = { class: "material-icons text-base align-middle" };
3593
+ var _hoisted_64 = { class: "divide-y divide-slate-100 bg-white" };
3594
+ var _hoisted_65 = [
3497
3595
  "aria-label",
3498
3596
  "data-testid",
3499
3597
  "onClick",
3500
3598
  "onKeydown"
3501
3599
  ];
3502
- var _hoisted_65 = [
3600
+ var _hoisted_66 = [
3503
3601
  "checked",
3504
3602
  "disabled",
3505
3603
  "data-testid",
3506
3604
  "aria-label",
3507
3605
  "onChange"
3508
3606
  ];
3509
- var _hoisted_66 = [
3607
+ var _hoisted_67 = [
3510
3608
  "checked",
3511
3609
  "disabled",
3512
3610
  "data-testid",
3513
3611
  "aria-label",
3514
3612
  "onChange"
3515
3613
  ];
3516
- var _hoisted_67 = {
3614
+ var _hoisted_68 = {
3517
3615
  key: 2,
3518
3616
  class: "block truncate"
3519
3617
  };
3520
- var _hoisted_68 = [
3618
+ var _hoisted_69 = [
3521
3619
  "href",
3522
3620
  "tabindex",
3523
3621
  "data-testid",
3524
3622
  "onClick",
3525
3623
  "onKeydown"
3526
3624
  ];
3527
- var _hoisted_69 = [
3625
+ var _hoisted_70 = [
3528
3626
  "value",
3529
3627
  "disabled",
3530
3628
  "data-testid",
3531
3629
  "aria-label",
3532
3630
  "onChange"
3533
3631
  ];
3534
- var _hoisted_70 = {
3632
+ var _hoisted_71 = {
3535
3633
  key: 0,
3536
3634
  value: ""
3537
3635
  };
3538
- var _hoisted_71 = ["value"];
3539
- var _hoisted_72 = {
3636
+ var _hoisted_72 = ["value"];
3637
+ var _hoisted_73 = {
3540
3638
  key: 4,
3541
3639
  class: "block truncate tabular-nums font-semibold text-slate-900"
3542
3640
  };
3543
- var _hoisted_73 = {
3641
+ var _hoisted_74 = {
3544
3642
  key: 5,
3545
3643
  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"
3546
3644
  };
3547
- var _hoisted_74 = {
3645
+ var _hoisted_75 = {
3548
3646
  key: 6,
3549
3647
  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"
3550
3648
  };
3551
- var _hoisted_75 = ["href", "data-testid"];
3552
3649
  var _hoisted_76 = ["href", "data-testid"];
3553
- var _hoisted_77 = [
3650
+ var _hoisted_77 = ["href", "data-testid"];
3651
+ var _hoisted_78 = [
3554
3652
  "href",
3555
3653
  "data-testid",
3556
3654
  "onClick"
3557
3655
  ];
3558
- var _hoisted_78 = {
3656
+ var _hoisted_79 = {
3559
3657
  key: 10,
3560
3658
  class: "block truncate text-slate-600"
3561
3659
  };
3562
- var _hoisted_79 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
3563
- var _hoisted_80 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
3564
- var _hoisted_81 = { class: "flex-1" };
3565
- var _hoisted_82 = {
3660
+ var _hoisted_80 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
3661
+ var _hoisted_81 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
3662
+ var _hoisted_82 = { class: "flex-1" };
3663
+ var _hoisted_83 = {
3566
3664
  id: "collections-chat-title",
3567
3665
  class: "text-sm font-bold text-slate-800 uppercase tracking-wide"
3568
3666
  };
3569
- var _hoisted_83 = { class: "text-xs text-slate-400 font-semibold" };
3570
- var _hoisted_84 = ["aria-label"];
3571
- var _hoisted_85 = { class: "px-6 py-5" };
3572
- var _hoisted_86 = ["placeholder", "onKeydown"];
3573
- 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" };
3574
- var _hoisted_88 = ["disabled"];
3667
+ var _hoisted_84 = { class: "text-xs text-slate-400 font-semibold" };
3668
+ var _hoisted_85 = ["aria-label"];
3669
+ var _hoisted_86 = { class: "px-6 py-5" };
3670
+ var _hoisted_87 = ["placeholder", "onKeydown"];
3671
+ 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" };
3672
+ var _hoisted_89 = ["disabled"];
3575
3673
  /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3576
3674
  * `presentCollection` chat card mounts this component and drives both
3577
3675
  * from the tool result). In standalone route mode (the
@@ -3747,12 +3845,12 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3747
3845
  return scalarSortValue(field, item[key]);
3748
3846
  }
3749
3847
  /** Derived rows sort by their display type: money/number → numeric,
3750
- * date/datetime → epoch, anything else → the enriched value as a string. */
3848
+ * date → epoch, anything else → the enriched value as a string. */
3751
3849
  function derivedSortValue(field, key, item) {
3752
- const { display } = field;
3850
+ const display = field.type === "derived" ? field.display : void 0;
3753
3851
  if (display === void 0 || display === "number" || display === "money") return numericSortValue(evaluateDerivedAgainstItem(field, key, item));
3754
3852
  const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;
3755
- if (display === "date" || display === "datetime") return dateSortValue(enriched[key]);
3853
+ if (display === "date") return dateSortValue(enriched[key]);
3756
3854
  return stringSortValue(enriched[key]);
3757
3855
  }
3758
3856
  const sortedItems = computed(() => {
@@ -3930,10 +4028,12 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3930
4028
  function buildChatSeed(slug, message, itemId) {
3931
4029
  const current = collection.value;
3932
4030
  if (current?.source !== "feed") return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
4031
+ const dataPath = current.schema.dataPath ?? `data/feeds/${slug}`;
4032
+ const scoped = itemId ? `(for record \`${itemId}\`) ${message}` : message;
3933
4033
  return t("collectionsView.feedChatSeed", {
3934
4034
  slug,
3935
- dataPath: current.schema.dataPath ?? `data/feeds/${slug}`,
3936
- message: itemId ? `(for record \`${itemId}\`) ${message}` : message
4035
+ dataPath,
4036
+ message: scoped
3937
4037
  });
3938
4038
  }
3939
4039
  /** Start a new general-role chat seeded from the current view. */
@@ -4008,7 +4108,10 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4008
4108
  enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
4009
4109
  await render.loadLinkedCollections(result.data.collection.schema, slug);
4010
4110
  if (activeSlug.value !== slug) return;
4011
- if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
4111
+ if (viewing.value) {
4112
+ const openId = String(viewing.value[result.data.collection.schema.primaryKey] ?? "");
4113
+ viewing.value = findItemById(openId) ?? null;
4114
+ }
4012
4115
  }
4013
4116
  function maybeAutoRefreshFeed(slug) {
4014
4117
  if (embedded.value) return;
@@ -4097,16 +4200,41 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4097
4200
  * page only (the seed starts a chat). Feeds qualify too — their views are
4098
4201
  * authored under feeds/<slug>/ and the seed prompt points there. */
4099
4202
  const canAddCustomView = computed(() => Boolean(collection.value) && !embedded.value);
4203
+ const addMenuOpen = ref(false);
4204
+ const addMenuRef = ref(null);
4205
+ /** Whether authoring a phone (remote app) view is worth offering — mirrors
4206
+ * the selector filter above: without the host's `fetchRemoteView` binding a
4207
+ * mobile view could be authored but never shown here. */
4208
+ const canAddMobileView = computed(() => Boolean(cui.fetchRemoteView));
4209
+ /** "+" click: open the target chooser, or skip the one-item menu and seed
4210
+ * the desktop prompt directly when mobile views aren't available. */
4211
+ function onAddViewClick() {
4212
+ if (!canAddMobileView.value) {
4213
+ addCustomView("desktop");
4214
+ return;
4215
+ }
4216
+ addMenuOpen.value = !addMenuOpen.value;
4217
+ }
4218
+ function closeAddMenuOnOutsideClick(event) {
4219
+ if (!addMenuRef.value?.contains(event.target)) addMenuOpen.value = false;
4220
+ }
4221
+ watch(addMenuOpen, (open) => {
4222
+ if (open) document.addEventListener("mousedown", closeAddMenuOnOutsideClick);
4223
+ else document.removeEventListener("mousedown", closeAddMenuOnOutsideClick);
4224
+ });
4100
4225
  /** Seed a chat asking Claude to author a new custom view for this collection.
4101
4226
  * Reuses the same chat-seed path as collection actions — the host injects a
4102
4227
  * templated prompt; Claude asks, authors the HTML, and registers it. The
4103
4228
  * authoring base is source-aware: a feed lives under `feeds/<slug>/`, every
4104
- * other collection under the `data/skills/<slug>/` staging dir. */
4105
- function addCustomView() {
4229
+ * other collection under the `data/skills/<slug>/` staging dir. The prompt
4230
+ * is target-aware: phone views follow the custom-view-remote contract and
4231
+ * register with `target: "mobile"`. */
4232
+ function addCustomView(target) {
4233
+ addMenuOpen.value = false;
4106
4234
  const current = collection.value;
4107
4235
  if (!current) return;
4108
4236
  const base = current.source === "feed" ? `feeds/${current.slug}` : `data/skills/${current.slug}`;
4109
- const prompt = t("collectionsView.addViewPrompt", {
4237
+ const prompt = t(target === "mobile" ? "collectionsView.addMobileViewPrompt" : "collectionsView.addViewPrompt", {
4110
4238
  title: current.title,
4111
4239
  base
4112
4240
  });
@@ -4161,7 +4289,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4161
4289
  }
4162
4290
  /** Select a custom view by id (builds the `custom:<id>` mode key). */
4163
4291
  function setCustomView(viewId) {
4164
- view.value = `custom:${viewId}`;
4292
+ const mode = `custom:${viewId}`;
4293
+ view.value = mode;
4165
4294
  }
4166
4295
  /** A short, slug-safe id not already used by a loaded record. Collisions
4167
4296
  * are astronomically unlikely (32 bits), but we still re-roll a few
@@ -4319,7 +4448,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4319
4448
  viewing.value = null;
4320
4449
  return;
4321
4450
  }
4322
- viewing.value = findItemById(selected) ?? null;
4451
+ const match = findItemById(selected) ?? null;
4452
+ viewing.value = match;
4323
4453
  }
4324
4454
  /** Title for the open-mode header: the record's primary-key value
4325
4455
  * (e.g. `INV-2026-0001`), falling back to the collection title.
@@ -4413,19 +4543,17 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4413
4543
  /** Whether a `toggle` field reads as checked: its projected enum field
4414
4544
  * currently equals `onValue`. The toggle stores nothing itself. */
4415
4545
  function toggleChecked(item, field) {
4416
- return field.field !== void 0 && String(item[field.field] ?? "") === field.onValue;
4546
+ return field.type === "toggle" && String(item[field.field] ?? "") === field.onValue;
4417
4547
  }
4418
4548
  /** Flip a `toggle`: write the projected enum field to `offValue` when
4419
4549
  * currently checked, else `onValue`. Reuses the inline-edit PUT path
4420
4550
  * (optimistic + rollback) — the toggle has no value of its own. */
4421
4551
  function commitToggle(item, field) {
4552
+ if (field.type !== "toggle" || !collection.value) return;
4422
4553
  const targetKey = field.field;
4423
- if (!targetKey || !collection.value) return;
4424
4554
  const enumField = collection.value.schema.fields[targetKey];
4425
4555
  if (!enumField) return;
4426
- const next = toggleChecked(item, field) ? field.offValue : field.onValue;
4427
- if (next === void 0) return;
4428
- commitInlineEdit(item, targetKey, enumField, next);
4556
+ commitInlineEdit(item, targetKey, enumField, toggleChecked(item, field) ? field.offValue : field.onValue);
4429
4557
  }
4430
4558
  async function confirmDelete(item) {
4431
4559
  if (!collection.value) return;
@@ -4582,6 +4710,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4582
4710
  view.value = slug && readCollectionViewMode(slug) || "table";
4583
4711
  anchorOverride.value = null;
4584
4712
  kanbanOverride.value = null;
4713
+ addMenuOpen.value = false;
4585
4714
  sortState.value = storedSortFor(slug);
4586
4715
  }
4587
4716
  if (slug) loadCollection(slug);
@@ -4630,6 +4759,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4630
4759
  changeUnsub = null;
4631
4760
  clearLiveRefreshTimer();
4632
4761
  if (refreshNoteTimer !== void 0) clearTimeout(refreshNoteTimer);
4762
+ document.removeEventListener("mousedown", closeAddMenuOnOutsideClick);
4633
4763
  });
4634
4764
  watch([
4635
4765
  activeView,
@@ -4664,7 +4794,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4664
4794
  "aria-label": unref(t)("collectionsView.backToIndex"),
4665
4795
  "data-testid": "collections-back",
4666
4796
  onClick: goBack
4667
- }, [..._cache[23] || (_cache[23] = [createElementVNode("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : createCommentVNode("", true),
4797
+ }, [..._cache[25] || (_cache[25] = [createElementVNode("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : createCommentVNode("", true),
4668
4798
  collection.value ? (openBlock(), createElementBlock("div", _hoisted_4$5, [createElementVNode("span", _hoisted_5$5, toDisplayString(collection.value.icon), 1)])) : createCommentVNode("", true),
4669
4799
  createElementVNode("div", _hoisted_6$4, [createElementVNode("h1", _hoisted_7$4, toDisplayString(collection.value?.title ?? unref(t)("collectionsView.title")), 1), collection.value ? (openBlock(), createElementBlock("span", _hoisted_8$4, toDisplayString(collection.value.slug), 1)) : createCommentVNode("", true)]),
4670
4800
  collection.value && !embedded.value ? (openBlock(), createBlock(resolveDynamicComponent(unref(pinToggle)), {
@@ -4693,7 +4823,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4693
4823
  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",
4694
4824
  "data-testid": "collections-chat",
4695
4825
  onClick: openChat
4696
- }, [_cache[24] || (_cache[24] = createElementVNode("span", { class: "material-icons text-sm" }, "forum", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.chat")), 1)])) : createCommentVNode("", true),
4826
+ }, [_cache[26] || (_cache[26] = createElementVNode("span", { class: "material-icons text-sm" }, "forum", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.chat")), 1)])) : createCommentVNode("", true),
4697
4827
  (openBlock(true), createElementBlock(Fragment, null, renderList(collectionActions.value, (action) => {
4698
4828
  return openBlock(), createElementBlock("button", {
4699
4829
  key: action.id,
@@ -4710,7 +4840,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4710
4840
  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",
4711
4841
  "data-testid": "collections-add-item",
4712
4842
  onClick: openCreate
4713
- }, [_cache[25] || (_cache[25] = createElementVNode("span", { class: "material-icons text-sm" }, "add", -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.add")), 1)])) : createCommentVNode("", true),
4843
+ }, [_cache[27] || (_cache[27] = createElementVNode("span", { class: "material-icons text-sm" }, "add", -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.add")), 1)])) : createCommentVNode("", true),
4714
4844
  canDeleteCollection.value && !embedded.value ? (openBlock(), createElementBlock("button", {
4715
4845
  key: 6,
4716
4846
  type: "button",
@@ -4719,7 +4849,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4719
4849
  "aria-label": unref(t)("collectionsView.deleteCollection"),
4720
4850
  "data-testid": "collections-delete",
4721
4851
  onClick: confirmCollectionDelete
4722
- }, [..._cache[26] || (_cache[26] = [createElementVNode("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_13$4)) : createCommentVNode("", true),
4852
+ }, [..._cache[28] || (_cache[28] = [createElementVNode("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_13$4)) : createCommentVNode("", true),
4723
4853
  canDeleteFeed.value && !embedded.value ? (openBlock(), createElementBlock("button", {
4724
4854
  key: 7,
4725
4855
  type: "button",
@@ -4728,11 +4858,11 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4728
4858
  "aria-label": unref(t)("collectionsView.deleteFeed"),
4729
4859
  "data-testid": "feeds-delete",
4730
4860
  onClick: confirmFeedDelete
4731
- }, [..._cache[27] || (_cache[27] = [createElementVNode("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : createCommentVNode("", true)
4861
+ }, [..._cache[29] || (_cache[29] = [createElementVNode("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : createCommentVNode("", true)
4732
4862
  ])) : createCommentVNode("", true),
4733
- refreshNote.value ? (openBlock(), createElementBlock("div", _hoisted_15$4, [_cache[28] || (_cache[28] = createElementVNode("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), createElementVNode("span", _hoisted_16$4, toDisplayString(refreshNote.value), 1)])) : createCommentVNode("", true),
4863
+ refreshNote.value ? (openBlock(), createElementBlock("div", _hoisted_15$4, [_cache[30] || (_cache[30] = createElementVNode("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), createElementVNode("span", _hoisted_16$4, toDisplayString(refreshNote.value), 1)])) : createCommentVNode("", true),
4734
4864
  collection.value && (!__props.hideSearch && items.value.length > 0 || !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value)) ? (openBlock(), createElementBlock("div", _hoisted_17$4, [!__props.hideSearch && items.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_18$4, [
4735
- _cache[30] || (_cache[30] = createElementVNode("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [createElementVNode("span", { class: "material-icons text-lg" }, "search")], -1)),
4865
+ _cache[32] || (_cache[32] = createElementVNode("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [createElementVNode("span", { class: "material-icons text-lg" }, "search")], -1)),
4736
4866
  withDirectives(createElementVNode("input", {
4737
4867
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
4738
4868
  type: "text",
@@ -4746,7 +4876,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4746
4876
  "aria-label": unref(t)("collectionsView.clearSearch"),
4747
4877
  class: "absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-400 hover:text-slate-600",
4748
4878
  onClick: _cache[1] || (_cache[1] = ($event) => searchQuery.value = "")
4749
- }, [..._cache[29] || (_cache[29] = [createElementVNode("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_20$2)) : createCommentVNode("", true)
4879
+ }, [..._cache[31] || (_cache[31] = [createElementVNode("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_20$2)) : createCommentVNode("", true)
4750
4880
  ])) : createCommentVNode("", true), createElementVNode("div", _hoisted_21$2, [
4751
4881
  !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value) ? (openBlock(), createElementBlock("div", {
4752
4882
  key: 0,
@@ -4760,7 +4890,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4760
4890
  "aria-pressed": activeView.value === "table",
4761
4891
  "data-testid": "collection-view-toggle-table",
4762
4892
  onClick: _cache[2] || (_cache[2] = ($event) => setView("table"))
4763
- }, [_cache[31] || (_cache[31] = createElementVNode("span", { class: "material-icons text-sm" }, "table_rows", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewTable")), 1)], 10, _hoisted_23$2),
4893
+ }, [_cache[33] || (_cache[33] = createElementVNode("span", { class: "material-icons text-sm" }, "table_rows", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewTable")), 1)], 10, _hoisted_23$2),
4764
4894
  hasCalendar.value ? (openBlock(), createElementBlock("button", {
4765
4895
  key: 0,
4766
4896
  type: "button",
@@ -4768,7 +4898,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4768
4898
  "aria-pressed": activeView.value === "calendar",
4769
4899
  "data-testid": "collection-view-toggle-calendar",
4770
4900
  onClick: _cache[3] || (_cache[3] = ($event) => setView("calendar"))
4771
- }, [_cache[32] || (_cache[32] = createElementVNode("span", { class: "material-icons text-sm" }, "calendar_month", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_24$1)) : createCommentVNode("", true),
4901
+ }, [_cache[34] || (_cache[34] = createElementVNode("span", { class: "material-icons text-sm" }, "calendar_month", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_24$1)) : createCommentVNode("", true),
4772
4902
  hasKanban.value ? (openBlock(), createElementBlock("button", {
4773
4903
  key: 1,
4774
4904
  type: "button",
@@ -4776,7 +4906,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4776
4906
  "aria-pressed": activeView.value === "kanban",
4777
4907
  "data-testid": "collection-view-toggle-kanban",
4778
4908
  onClick: _cache[4] || (_cache[4] = ($event) => setView("kanban"))
4779
- }, [_cache[33] || (_cache[33] = createElementVNode("span", { class: "material-icons text-sm" }, "view_kanban", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_25$1)) : createCommentVNode("", true),
4909
+ }, [_cache[35] || (_cache[35] = createElementVNode("span", { class: "material-icons text-sm" }, "view_kanban", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_25$1)) : createCommentVNode("", true),
4780
4910
  (openBlock(true), createElementBlock(Fragment, null, renderList(customViews.value, (cv) => {
4781
4911
  return openBlock(), createElementBlock("button", {
4782
4912
  key: cv.id,
@@ -4787,15 +4917,30 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4787
4917
  onClick: ($event) => setCustomView(cv.id)
4788
4918
  }, [createElementVNode("span", _hoisted_27$1, toDisplayString(cv.icon || (cv.target === "mobile" ? "smartphone" : "dashboard_customize")), 1), createElementVNode("span", null, toDisplayString(cv.label), 1)], 10, _hoisted_26$1);
4789
4919
  }), 128)),
4790
- canAddCustomView.value ? (openBlock(), createElementBlock("button", {
4920
+ canAddCustomView.value ? (openBlock(), createElementBlock("div", {
4791
4921
  key: 2,
4922
+ ref_key: "addMenuRef",
4923
+ ref: addMenuRef,
4924
+ class: "relative"
4925
+ }, [createElementVNode("button", {
4792
4926
  type: "button",
4793
4927
  class: "h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50",
4794
4928
  title: unref(t)("collectionsView.addView"),
4795
4929
  "aria-label": unref(t)("collectionsView.addView"),
4930
+ "aria-expanded": addMenuOpen.value,
4796
4931
  "data-testid": "collection-view-add",
4797
- onClick: addCustomView
4798
- }, [..._cache[34] || (_cache[34] = [createElementVNode("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_28)) : createCommentVNode("", true),
4932
+ onClick: onAddViewClick
4933
+ }, [..._cache[36] || (_cache[36] = [createElementVNode("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_28), addMenuOpen.value ? (openBlock(), createElementBlock("div", _hoisted_29, [createElementVNode("button", {
4934
+ type: "button",
4935
+ class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4936
+ "data-testid": "collection-view-add-desktop",
4937
+ onClick: _cache[5] || (_cache[5] = ($event) => addCustomView("desktop"))
4938
+ }, [_cache[37] || (_cache[37] = createElementVNode("span", { class: "material-icons text-sm" }, "dashboard_customize", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.addViewDesktop")), 1)]), createElementVNode("button", {
4939
+ type: "button",
4940
+ class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4941
+ "data-testid": "collection-view-add-mobile",
4942
+ onClick: _cache[6] || (_cache[6] = ($event) => addCustomView("mobile"))
4943
+ }, [_cache[38] || (_cache[38] = createElementVNode("span", { class: "material-icons text-sm" }, "smartphone", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.addViewMobile")), 1)])])) : createCommentVNode("", true)], 512)) : createCommentVNode("", true),
4799
4944
  canConfigureViews.value ? (openBlock(), createElementBlock("button", {
4800
4945
  key: 3,
4801
4946
  type: "button",
@@ -4803,8 +4948,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4803
4948
  title: unref(t)("collectionsView.config.open"),
4804
4949
  "aria-label": unref(t)("collectionsView.config.open"),
4805
4950
  "data-testid": "collection-config-open",
4806
- onClick: _cache[5] || (_cache[5] = ($event) => configOpen.value = true)
4807
- }, [..._cache[35] || (_cache[35] = [createElementVNode("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_29)) : createCommentVNode("", true)
4951
+ onClick: _cache[7] || (_cache[7] = ($event) => configOpen.value = true)
4952
+ }, [..._cache[39] || (_cache[39] = [createElementVNode("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_30)) : createCommentVNode("", true)
4808
4953
  ], 8, _hoisted_22$2)) : createCommentVNode("", true),
4809
4954
  calendarActive.value && dateFields.value.length > 1 ? (openBlock(), createElementBlock("select", {
4810
4955
  key: 1,
@@ -4812,42 +4957,42 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4812
4957
  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",
4813
4958
  "aria-label": unref(t)("collectionsView.calendarFieldLabel"),
4814
4959
  "data-testid": "collection-calendar-field",
4815
- onChange: _cache[6] || (_cache[6] = ($event) => anchorOverride.value = $event.target.value)
4960
+ onChange: _cache[8] || (_cache[8] = ($event) => anchorOverride.value = $event.target.value)
4816
4961
  }, [(openBlock(true), createElementBlock(Fragment, null, renderList(dateFields.value, (key) => {
4817
4962
  return openBlock(), createElementBlock("option", {
4818
4963
  key,
4819
4964
  value: key
4820
- }, toDisplayString(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_31);
4821
- }), 128))], 40, _hoisted_30)) : createCommentVNode("", true),
4965
+ }, toDisplayString(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_32);
4966
+ }), 128))], 40, _hoisted_31)) : createCommentVNode("", true),
4822
4967
  kanbanActive.value && enumFields.value.length > 1 ? (openBlock(), createElementBlock("select", {
4823
4968
  key: 2,
4824
4969
  value: kanbanGroupField.value,
4825
4970
  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",
4826
4971
  "aria-label": unref(t)("collectionsView.kanbanFieldLabel"),
4827
4972
  "data-testid": "collection-kanban-field",
4828
- onChange: _cache[7] || (_cache[7] = ($event) => kanbanOverride.value = $event.target.value)
4973
+ onChange: _cache[9] || (_cache[9] = ($event) => kanbanOverride.value = $event.target.value)
4829
4974
  }, [(openBlock(true), createElementBlock(Fragment, null, renderList(enumFields.value, (key) => {
4830
4975
  return openBlock(), createElementBlock("option", {
4831
4976
  key,
4832
4977
  value: key
4833
- }, toDisplayString(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_33);
4834
- }), 128))], 40, _hoisted_32)) : createCommentVNode("", true),
4835
- items.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_34, toDisplayString(unref(t)("collectionsView.searchSummary", {
4978
+ }, toDisplayString(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_34);
4979
+ }), 128))], 40, _hoisted_33)) : createCommentVNode("", true),
4980
+ items.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_35, toDisplayString(unref(t)("collectionsView.searchSummary", {
4836
4981
  shown: filteredItems.value.length,
4837
4982
  total: items.value.length
4838
4983
  })), 1)) : createCommentVNode("", true)
4839
4984
  ])])) : createCommentVNode("", true),
4840
- collection.value && dataIssues.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_35, [
4841
- _cache[37] || (_cache[37] = createElementVNode("span", { class: "material-icons text-amber-600" }, "warning", -1)),
4842
- createElementVNode("span", _hoisted_36, toDisplayString(unref(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
4985
+ collection.value && dataIssues.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_36, [
4986
+ _cache[41] || (_cache[41] = createElementVNode("span", { class: "material-icons text-amber-600" }, "warning", -1)),
4987
+ createElementVNode("span", _hoisted_37, toDisplayString(unref(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
4843
4988
  createElementVNode("button", {
4844
4989
  type: "button",
4845
4990
  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",
4846
4991
  "data-testid": "collections-repair",
4847
4992
  onClick: repairCollection
4848
- }, [_cache[36] || (_cache[36] = createElementVNode("span", { class: "material-icons text-sm" }, "build", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.repair")), 1)])
4993
+ }, [_cache[40] || (_cache[40] = createElementVNode("span", { class: "material-icons text-sm" }, "build", -1)), createElementVNode("span", null, toDisplayString(unref(t)("collectionsView.repair")), 1)])
4849
4994
  ])) : createCommentVNode("", true),
4850
- createElementVNode("div", _hoisted_37, [loading.value ? (openBlock(), createElementBlock("div", _hoisted_38, [_cache[38] || (_cache[38] = createElementVNode("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.loading")), 1)])) : loadError.value ? (openBlock(), createElementBlock("div", _hoisted_39, [_cache[39] || (_cache[39] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)), createElementVNode("span", null, toDisplayString(loadError.value === "not-found" ? unref(t)("collectionsView.notFound") : `${unref(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? (openBlock(), createElementBlock("div", _hoisted_40)) : calendarActive.value ? (openBlock(), createElementBlock("div", _hoisted_41, [createVNode(CollectionCalendarView_default, {
4995
+ createElementVNode("div", _hoisted_38, [loading.value ? (openBlock(), createElementBlock("div", _hoisted_39, [_cache[42] || (_cache[42] = createElementVNode("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), createElementVNode("span", null, toDisplayString(unref(t)("common.loading")), 1)])) : loadError.value ? (openBlock(), createElementBlock("div", _hoisted_40, [_cache[43] || (_cache[43] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)), createElementVNode("span", null, toDisplayString(loadError.value === "not-found" ? unref(t)("collectionsView.notFound") : `${unref(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? (openBlock(), createElementBlock("div", _hoisted_41)) : calendarActive.value ? (openBlock(), createElementBlock("div", _hoisted_42, [createVNode(CollectionCalendarView_default, {
4851
4996
  schema: collection.value.schema,
4852
4997
  items: filteredItems.value,
4853
4998
  "anchor-field": calendarAnchorField.value,
@@ -4883,7 +5028,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4883
5028
  }, {
4884
5029
  detail: withCtx(() => [createVNode(CollectionRecordPanel_default, {
4885
5030
  editing: editing.value,
4886
- "onUpdate:editing": _cache[8] || (_cache[8] = ($event) => editing.value = $event),
5031
+ "onUpdate:editing": _cache[10] || (_cache[10] = ($event) => editing.value = $event),
4887
5032
  collection: collection.value,
4888
5033
  viewing: viewing.value,
4889
5034
  saving: saving.value,
@@ -4901,7 +5046,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4901
5046
  onCancel: cancelEditor,
4902
5047
  onEdit: editFromView,
4903
5048
  onClose: onDayClose,
4904
- onDelete: _cache[9] || (_cache[9] = ($event) => viewing.value && confirmDelete(viewing.value)),
5049
+ onDelete: _cache[11] || (_cache[11] = ($event) => viewing.value && confirmDelete(viewing.value)),
4905
5050
  onRunAction: runAction,
4906
5051
  onItemChat
4907
5052
  }, null, 8, [
@@ -4932,16 +5077,16 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4932
5077
  "selected",
4933
5078
  "can-create",
4934
5079
  "show-detail"
4935
- ])) : createCommentVNode("", true)])) : kanbanActive.value ? (openBlock(), createElementBlock("div", _hoisted_42, [inlineError.value ? (openBlock(), createElementBlock("div", _hoisted_43, [
4936
- _cache[41] || (_cache[41] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)),
4937
- createElementVNode("span", _hoisted_44, toDisplayString(unref(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5080
+ ])) : createCommentVNode("", true)])) : kanbanActive.value ? (openBlock(), createElementBlock("div", _hoisted_43, [inlineError.value ? (openBlock(), createElementBlock("div", _hoisted_44, [
5081
+ _cache[45] || (_cache[45] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)),
5082
+ createElementVNode("span", _hoisted_45, toDisplayString(unref(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
4938
5083
  createElementVNode("button", {
4939
5084
  type: "button",
4940
5085
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
4941
5086
  "aria-label": unref(t)("common.close"),
4942
- onClick: _cache[10] || (_cache[10] = ($event) => inlineError.value = null)
4943
- }, [..._cache[40] || (_cache[40] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_45)
4944
- ])) : createCommentVNode("", true), createElementVNode("div", _hoisted_46, [createVNode(CollectionKanbanView_default, {
5087
+ onClick: _cache[12] || (_cache[12] = ($event) => inlineError.value = null)
5088
+ }, [..._cache[44] || (_cache[44] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_46)
5089
+ ])) : createCommentVNode("", true), createElementVNode("div", _hoisted_47, [createVNode(CollectionKanbanView_default, {
4945
5090
  schema: collection.value.schema,
4946
5091
  items: filteredItems.value,
4947
5092
  "group-field": kanbanGroupField.value,
@@ -4955,7 +5100,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4955
5100
  "group-field",
4956
5101
  "selected",
4957
5102
  "notified"
4958
- ])])])) : activeCustomView.value ? (openBlock(), createElementBlock("div", _hoisted_47, [activeCustomView.value.target === "mobile" ? (openBlock(), createBlock(CollectionRemoteViewPreview_default, {
5103
+ ])])])) : activeCustomView.value ? (openBlock(), createElementBlock("div", _hoisted_48, [activeCustomView.value.target === "mobile" ? (openBlock(), createBlock(CollectionRemoteViewPreview_default, {
4959
5104
  key: 0,
4960
5105
  slug: collection.value.slug,
4961
5106
  view: activeCustomView.value,
@@ -4966,32 +5111,32 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4966
5111
  view: activeCustomView.value,
4967
5112
  onOpenItem: onCustomViewOpenItem,
4968
5113
  onStartChat: onCustomViewStartChat
4969
- }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_48, [_cache[42] || (_cache[42] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), createElementVNode("p", _hoisted_49, toDisplayString(unref(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_50, [
4970
- _cache[43] || (_cache[43] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
4971
- createElementVNode("p", _hoisted_51, toDisplayString(unref(t)("collectionsView.noMatchingItems")), 1),
5114
+ }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_49, [_cache[46] || (_cache[46] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), createElementVNode("p", _hoisted_50, toDisplayString(unref(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? (openBlock(), createElementBlock("div", _hoisted_51, [
5115
+ _cache[47] || (_cache[47] = createElementVNode("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
5116
+ createElementVNode("p", _hoisted_52, toDisplayString(unref(t)("collectionsView.noMatchingItems")), 1),
4972
5117
  createElementVNode("button", {
4973
5118
  type: "button",
4974
5119
  class: "text-xs text-indigo-600 font-semibold hover:underline",
4975
- onClick: _cache[11] || (_cache[11] = ($event) => searchQuery.value = "")
5120
+ onClick: _cache[13] || (_cache[13] = ($event) => searchQuery.value = "")
4976
5121
  }, toDisplayString(unref(t)("collectionsView.clearSearch")), 1)
4977
- ])) : (openBlock(), createElementBlock("div", _hoisted_52, [inlineError.value ? (openBlock(), createElementBlock("div", _hoisted_53, [
4978
- _cache[45] || (_cache[45] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)),
4979
- createElementVNode("span", _hoisted_54, toDisplayString(unref(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5122
+ ])) : (openBlock(), createElementBlock("div", _hoisted_53, [inlineError.value ? (openBlock(), createElementBlock("div", _hoisted_54, [
5123
+ _cache[49] || (_cache[49] = createElementVNode("span", { class: "material-icons text-red-600" }, "error", -1)),
5124
+ createElementVNode("span", _hoisted_55, toDisplayString(unref(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
4980
5125
  createElementVNode("button", {
4981
5126
  type: "button",
4982
5127
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
4983
5128
  "aria-label": unref(t)("common.close"),
4984
- onClick: _cache[12] || (_cache[12] = ($event) => inlineError.value = null)
4985
- }, [..._cache[44] || (_cache[44] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_55)
4986
- ])) : createCommentVNode("", true), createElementVNode("table", _hoisted_56, [createElementVNode("thead", null, [createElementVNode("tr", _hoisted_57, [(openBlock(true), createElementBlock(Fragment, null, renderList(listColumnFields.value, ([key, field]) => {
5129
+ onClick: _cache[14] || (_cache[14] = ($event) => inlineError.value = null)
5130
+ }, [..._cache[48] || (_cache[48] = [createElementVNode("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_56)
5131
+ ])) : createCommentVNode("", true), createElementVNode("table", _hoisted_57, [createElementVNode("thead", null, [createElementVNode("tr", _hoisted_58, [(openBlock(true), createElementBlock(Fragment, null, renderList(listColumnFields.value, ([key, field]) => {
4987
5132
  return openBlock(), createElementBlock("th", {
4988
5133
  key,
4989
5134
  "aria-sort": unref(isSortableField)(field) ? sortAriaValue(key) : void 0,
4990
5135
  class: "px-5 py-3 font-bold text-slate-500 text-left uppercase tracking-wider whitespace-nowrap"
4991
- }, [createElementVNode("div", _hoisted_59, [createElementVNode("span", {
5136
+ }, [createElementVNode("div", _hoisted_60, [createElementVNode("span", {
4992
5137
  class: "truncate max-w-[14rem]",
4993
5138
  title: field.label
4994
- }, toDisplayString(field.label), 9, _hoisted_60), unref(isSortableField)(field) ? (openBlock(), createElementBlock("button", {
5139
+ }, toDisplayString(field.label), 9, _hoisted_61), unref(isSortableField)(field) ? (openBlock(), createElementBlock("button", {
4995
5140
  key: 0,
4996
5141
  type: "button",
4997
5142
  class: normalizeClass(["inline-flex items-center justify-center rounded p-0.5 -my-1 leading-none transition-colors", sortButtonClass(key)]),
@@ -4999,9 +5144,9 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4999
5144
  "aria-label": unref(t)("collectionsView.sortBy", { field: field.label }),
5000
5145
  onClick: withModifiers(($event) => cycleSort(key), ["stop"]),
5001
5146
  onPointerenter: ($event) => hoveredSortKey.value = key,
5002
- onPointerleave: _cache[13] || (_cache[13] = ($event) => hoveredSortKey.value = null)
5003
- }, [createElementVNode("span", _hoisted_62, toDisplayString(sortIconName(key)), 1)], 42, _hoisted_61)) : createCommentVNode("", true)])], 8, _hoisted_58);
5004
- }), 128))])]), createElementVNode("tbody", _hoisted_63, [(openBlock(true), createElementBlock(Fragment, null, renderList(sortedItems.value, (item) => {
5147
+ onPointerleave: _cache[15] || (_cache[15] = ($event) => hoveredSortKey.value = null)
5148
+ }, [createElementVNode("span", _hoisted_63, toDisplayString(sortIconName(key)), 1)], 42, _hoisted_62)) : createCommentVNode("", true)])], 8, _hoisted_59);
5149
+ }), 128))])]), createElementVNode("tbody", _hoisted_64, [(openBlock(true), createElementBlock(Fragment, null, renderList(sortedItems.value, (item) => {
5005
5150
  return openBlock(), createElementBlock("tr", {
5006
5151
  key: String(item[collection.value.schema.primaryKey] ?? ""),
5007
5152
  class: 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" : ""]),
@@ -5023,9 +5168,9 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5023
5168
  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",
5024
5169
  "data-testid": `collections-inline-toggle-${key}-${item[collection.value.schema.primaryKey]}`,
5025
5170
  "aria-label": field.label,
5026
- onClick: _cache[14] || (_cache[14] = withModifiers(() => {}, ["stop"])),
5171
+ onClick: _cache[16] || (_cache[16] = withModifiers(() => {}, ["stop"])),
5027
5172
  onChange: ($event) => commitToggle(item, field)
5028
- }, null, 40, _hoisted_65)) : field.type === "boolean" ? (openBlock(), createElementBlock("input", {
5173
+ }, null, 40, _hoisted_66)) : field.type === "boolean" ? (openBlock(), createElementBlock("input", {
5029
5174
  key: 1,
5030
5175
  type: "checkbox",
5031
5176
  checked: item[key] === true,
@@ -5033,9 +5178,9 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5033
5178
  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",
5034
5179
  "data-testid": `collections-inline-bool-${key}-${item[collection.value.schema.primaryKey]}`,
5035
5180
  "aria-label": field.label,
5036
- onClick: _cache[15] || (_cache[15] = withModifiers(() => {}, ["stop"])),
5181
+ onClick: _cache[17] || (_cache[17] = withModifiers(() => {}, ["stop"])),
5037
5182
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.checked)
5038
- }, null, 40, _hoisted_66)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? (openBlock(), createElementBlock("span", _hoisted_67, [createElementVNode("a", {
5183
+ }, null, 40, _hoisted_67)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? (openBlock(), createElementBlock("span", _hoisted_68, [createElementVNode("a", {
5039
5184
  href: unref(cui).recordHref?.(field.to, String(item[key])),
5040
5185
  tabindex: unref(cui).recordHref?.(field.to, String(item[key])) ? void 0 : 0,
5041
5186
  role: "link",
@@ -5043,44 +5188,44 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5043
5188
  "data-testid": `collections-ref-link-${key}-${item[key]}`,
5044
5189
  onClick: ($event) => unref(activateRefLink)($event, field.to, String(item[key]), true),
5045
5190
  onKeydown: [withKeys(($event) => unref(activateRefLink)($event, field.to, String(item[key]), true), ["enter"]), withKeys(($event) => unref(activateRefLink)($event, field.to, String(item[key]), true), ["space"])]
5046
- }, toDisplayString(unref(refDisplay)(field.to, String(item[key]))), 41, _hoisted_68)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? (openBlock(), createElementBlock("select", {
5191
+ }, toDisplayString(unref(refDisplay)(field.to, String(item[key]))), 41, _hoisted_69)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? (openBlock(), createElementBlock("select", {
5047
5192
  key: 3,
5048
5193
  value: item[key] == null ? "" : String(item[key]),
5049
5194
  disabled: isRowInlineSaving(item),
5050
5195
  class: 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])]),
5051
5196
  "data-testid": `collections-inline-enum-${key}-${item[collection.value.schema.primaryKey]}`,
5052
5197
  "aria-label": field.label,
5053
- onClick: _cache[16] || (_cache[16] = withModifiers(() => {}, ["stop"])),
5198
+ onClick: _cache[18] || (_cache[18] = withModifiers(() => {}, ["stop"])),
5054
5199
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.value)
5055
- }, [showEnumPlaceholder(item, String(key)) ? (openBlock(), createElementBlock("option", _hoisted_70, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(field.values, (value) => {
5200
+ }, [showEnumPlaceholder(item, String(key)) ? (openBlock(), createElementBlock("option", _hoisted_71, toDisplayString(unref(t)("collectionsView.selectPlaceholder")), 1)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(field.values, (value) => {
5056
5201
  return openBlock(), createElementBlock("option", {
5057
5202
  key: value,
5058
5203
  value
5059
- }, toDisplayString(value), 9, _hoisted_71);
5060
- }), 128))], 42, _hoisted_69)) : field.type === "money" ? (openBlock(), createElementBlock("span", _hoisted_72, toDisplayString(unref(formatMoney)(item[key], unref(resolveCurrency)(field, item), unref(locale))), 1)) : field.type === "table" ? (openBlock(), createElementBlock("span", _hoisted_73, [_cache[46] || (_cache[46] = createElementVNode("span", { class: "material-icons text-[11px]" }, "list", -1)), createElementVNode("span", null, toDisplayString(tableSummary(item[key])), 1)])) : field.type === "derived" ? (openBlock(), createElementBlock("span", _hoisted_74, toDisplayString(unref(derivedDisplay)(field, unref(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type !== "file" && unref(isExternalUrl)(item[key]) ? (openBlock(), createElementBlock("a", {
5204
+ }, toDisplayString(value), 9, _hoisted_72);
5205
+ }), 128))], 42, _hoisted_70)) : field.type === "money" ? (openBlock(), createElementBlock("span", _hoisted_73, toDisplayString(unref(formatMoney)(item[key], unref(resolveCurrency)(field, item), unref(locale))), 1)) : field.type === "table" ? (openBlock(), createElementBlock("span", _hoisted_74, [_cache[50] || (_cache[50] = createElementVNode("span", { class: "material-icons text-[11px]" }, "list", -1)), createElementVNode("span", null, toDisplayString(tableSummary(item[key])), 1)])) : field.type === "derived" ? (openBlock(), createElementBlock("span", _hoisted_75, toDisplayString(unref(derivedDisplay)(field, unref(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type !== "file" && unref(isExternalUrl)(item[key]) ? (openBlock(), createElementBlock("a", {
5061
5206
  key: 7,
5062
5207
  href: String(item[key]),
5063
5208
  target: "_blank",
5064
5209
  rel: "noopener noreferrer",
5065
5210
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5066
5211
  "data-testid": `collections-url-link-${key}-${item[collection.value.schema.primaryKey]}`,
5067
- onClick: _cache[17] || (_cache[17] = withModifiers(() => {}, ["stop"]))
5068
- }, toDisplayString(String(item[key])), 9, _hoisted_75)) : field.type === "file" && unref(artifactUrl)(item[key]) ? (openBlock(), createElementBlock("a", {
5212
+ onClick: _cache[19] || (_cache[19] = withModifiers(() => {}, ["stop"]))
5213
+ }, toDisplayString(String(item[key])), 9, _hoisted_76)) : field.type === "file" && unref(artifactUrl)(item[key]) ? (openBlock(), createElementBlock("a", {
5069
5214
  key: 8,
5070
5215
  href: unref(artifactUrl)(item[key]) ?? void 0,
5071
5216
  target: "_blank",
5072
5217
  rel: "noopener noreferrer",
5073
5218
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5074
5219
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5075
- onClick: _cache[18] || (_cache[18] = withModifiers(() => {}, ["stop"]))
5076
- }, toDisplayString(String(item[key])), 9, _hoisted_76)) : field.type === "file" && unref(fileRoutePath)(item[key]) ? (openBlock(), createElementBlock("a", {
5220
+ onClick: _cache[20] || (_cache[20] = withModifiers(() => {}, ["stop"]))
5221
+ }, toDisplayString(String(item[key])), 9, _hoisted_77)) : field.type === "file" && unref(fileRoutePath)(item[key]) ? (openBlock(), createElementBlock("a", {
5077
5222
  key: 9,
5078
5223
  href: unref(fileRoutePath)(item[key]) ?? void 0,
5079
5224
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5080
5225
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5081
5226
  onClick: ($event) => unref(activatePathLink)($event, unref(fileRoutePath)(item[key]) ?? "", true)
5082
- }, toDisplayString(String(item[key])), 9, _hoisted_77)) : (openBlock(), createElementBlock("span", _hoisted_78, toDisplayString(unref(formatCell)(item[key], field.type)), 1))], 64)) : createCommentVNode("", true)]);
5083
- }), 128))], 42, _hoisted_64);
5227
+ }, toDisplayString(String(item[key])), 9, _hoisted_78)) : (openBlock(), createElementBlock("span", _hoisted_79, toDisplayString(unref(formatCell)(item[key], field.type)), 1))], 64)) : createCommentVNode("", true)]);
5228
+ }), 128))], 42, _hoisted_65);
5084
5229
  }), 128))])])]))]),
5085
5230
  collection.value && (viewing.value || editing.value) && !(calendarActive.value && openDay.value) ? (openBlock(), createBlock(CollectionRecordModal_default, {
5086
5231
  key: 4,
@@ -5088,7 +5233,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5088
5233
  }, {
5089
5234
  default: withCtx(() => [createVNode(CollectionRecordPanel_default, {
5090
5235
  editing: editing.value,
5091
- "onUpdate:editing": _cache[19] || (_cache[19] = ($event) => editing.value = $event),
5236
+ "onUpdate:editing": _cache[21] || (_cache[21] = ($event) => editing.value = $event),
5092
5237
  collection: collection.value,
5093
5238
  viewing: viewing.value,
5094
5239
  saving: saving.value,
@@ -5106,7 +5251,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5106
5251
  onCancel: cancelEditor,
5107
5252
  onEdit: editFromView,
5108
5253
  onClose: closeView,
5109
- onDelete: _cache[20] || (_cache[20] = ($event) => viewing.value && confirmDelete(viewing.value)),
5254
+ onDelete: _cache[22] || (_cache[22] = ($event) => viewing.value && confirmDelete(viewing.value)),
5110
5255
  onRunAction: runAction,
5111
5256
  onItemChat
5112
5257
  }, null, 8, [
@@ -5133,7 +5278,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5133
5278
  title: collection.value.title,
5134
5279
  views: customViews.value,
5135
5280
  onChanged: onViewsChanged,
5136
- onClose: _cache[21] || (_cache[21] = ($event) => configOpen.value = false)
5281
+ onClose: _cache[23] || (_cache[23] = ($event) => configOpen.value = false)
5137
5282
  }, null, 8, [
5138
5283
  "slug",
5139
5284
  "title",
@@ -5148,29 +5293,29 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5148
5293
  "data-testid": "collections-chat-modal",
5149
5294
  onClick: withModifiers(closeChat, ["self"]),
5150
5295
  onKeydown: withKeys(closeChat, ["esc"])
5151
- }, [createElementVNode("div", _hoisted_79, [
5152
- createElementVNode("header", _hoisted_80, [
5153
- _cache[48] || (_cache[48] = 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" }, [createElementVNode("span", { class: "material-icons text-lg" }, "forum")], -1)),
5154
- createElementVNode("div", _hoisted_81, [createElementVNode("h2", _hoisted_82, toDisplayString(unref(t)("collectionsView.chatTitle")), 1), createElementVNode("span", _hoisted_83, toDisplayString(collection.value.title), 1)]),
5296
+ }, [createElementVNode("div", _hoisted_80, [
5297
+ createElementVNode("header", _hoisted_81, [
5298
+ _cache[52] || (_cache[52] = 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" }, [createElementVNode("span", { class: "material-icons text-lg" }, "forum")], -1)),
5299
+ createElementVNode("div", _hoisted_82, [createElementVNode("h2", _hoisted_83, toDisplayString(unref(t)("collectionsView.chatTitle")), 1), createElementVNode("span", _hoisted_84, toDisplayString(collection.value.title), 1)]),
5155
5300
  createElementVNode("button", {
5156
5301
  type: "button",
5157
5302
  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",
5158
5303
  "aria-label": unref(t)("common.close"),
5159
5304
  "data-testid": "collections-chat-close",
5160
5305
  onClick: closeChat
5161
- }, [..._cache[47] || (_cache[47] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_84)
5306
+ }, [..._cache[51] || (_cache[51] = [createElementVNode("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_85)
5162
5307
  ]),
5163
- createElementVNode("div", _hoisted_85, [withDirectives(createElementVNode("textarea", {
5308
+ createElementVNode("div", _hoisted_86, [withDirectives(createElementVNode("textarea", {
5164
5309
  ref_key: "chatInputEl",
5165
5310
  ref: chatInputEl,
5166
- "onUpdate:modelValue": _cache[22] || (_cache[22] = ($event) => chatMessage.value = $event),
5311
+ "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => chatMessage.value = $event),
5167
5312
  rows: "4",
5168
5313
  placeholder: unref(t)("collectionsView.chatPlaceholder"),
5169
5314
  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",
5170
5315
  "data-testid": "collections-chat-input",
5171
5316
  onKeydown: [withKeys(withModifiers(submitChat, ["meta"]), ["enter"]), withKeys(withModifiers(submitChat, ["ctrl"]), ["enter"])]
5172
- }, null, 40, _hoisted_86), [[vModelText, chatMessage.value]])]),
5173
- createElementVNode("footer", _hoisted_87, [createElementVNode("button", {
5317
+ }, null, 40, _hoisted_87), [[vModelText, chatMessage.value]])]),
5318
+ createElementVNode("footer", _hoisted_88, [createElementVNode("button", {
5174
5319
  type: "button",
5175
5320
  class: "h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors",
5176
5321
  "data-testid": "collections-chat-cancel",
@@ -5181,7 +5326,7 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
5181
5326
  disabled: !chatMessage.value.trim(),
5182
5327
  "data-testid": "collections-chat-send",
5183
5328
  onClick: submitChat
5184
- }, toDisplayString(unref(t)("collectionsView.chatStart")), 9, _hoisted_88)])
5329
+ }, toDisplayString(unref(t)("collectionsView.chatStart")), 9, _hoisted_89)])
5185
5330
  ])], 32)) : createCommentVNode("", true)
5186
5331
  ]);
5187
5332
  };