@mulmoclaude/collection-plugin 0.3.0 → 0.4.1
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/core/errorMessage.d.ts +5 -0
- package/dist/core/errorMessage.d.ts.map +1 -0
- package/dist/core/promptSafety.d.ts +2 -0
- package/dist/core/promptSafety.d.ts.map +1 -0
- package/dist/core/shortHexId.d.ts +9 -0
- package/dist/core/shortHexId.d.ts.map +1 -0
- package/dist/core/uiTypes.d.ts +26 -1
- package/dist/core/uiTypes.d.ts.map +1 -1
- package/dist/index.cjs +45 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -86
- package/dist/index.js.map +1 -1
- package/dist/{calendarGrid-DZeLwBj9.cjs → promptSafety-BSO2gdsg.cjs} +184 -1
- package/dist/promptSafety-BSO2gdsg.cjs.map +1 -0
- package/dist/{calendarGrid-ggmFTZju.js → promptSafety-DShN_VCG.js} +113 -2
- package/dist/promptSafety-DShN_VCG.js.map +1 -0
- package/dist/style.css +544 -1
- package/dist/vue/collectionViewMode.d.ts +12 -0
- package/dist/vue/collectionViewMode.d.ts.map +1 -0
- package/dist/vue/components/CollectionCustomView.vue.d.ts +9 -0
- package/dist/vue/components/CollectionCustomView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionEmbedView.vue.d.ts.map +1 -1
- package/dist/vue/components/CollectionView.vue.d.ts +41 -0
- package/dist/vue/components/CollectionView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionViewConfigModal.vue.d.ts +16 -0
- package/dist/vue/components/CollectionViewConfigModal.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionsIndexView.vue.d.ts +4 -0
- package/dist/vue/components/CollectionsIndexView.vue.d.ts.map +1 -0
- package/dist/vue/components/FeedsView.vue.d.ts +4 -0
- package/dist/vue/components/FeedsView.vue.d.ts.map +1 -0
- package/dist/vue/index.d.ts +7 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/lang/de.d.ts +4 -0
- package/dist/vue/lang/de.d.ts.map +1 -0
- package/dist/vue/lang/en.d.ts +99 -0
- package/dist/vue/lang/en.d.ts.map +1 -0
- package/dist/vue/lang/es.d.ts +4 -0
- package/dist/vue/lang/es.d.ts.map +1 -0
- package/dist/vue/lang/fr.d.ts +4 -0
- package/dist/vue/lang/fr.d.ts.map +1 -0
- package/dist/vue/lang/index.d.ts +112 -0
- package/dist/vue/lang/index.d.ts.map +1 -0
- package/dist/vue/lang/ja.d.ts +4 -0
- package/dist/vue/lang/ja.d.ts.map +1 -0
- package/dist/vue/lang/ko.d.ts +4 -0
- package/dist/vue/lang/ko.d.ts.map +1 -0
- package/dist/vue/lang/ptBR.d.ts +4 -0
- package/dist/vue/lang/ptBR.d.ts.map +1 -0
- package/dist/vue/lang/zh.d.ts +4 -0
- package/dist/vue/lang/zh.d.ts.map +1 -0
- package/dist/vue/uiContext.d.ts +143 -4
- package/dist/vue/uiContext.d.ts.map +1 -1
- package/dist/vue/useCollectionRendering.d.ts.map +1 -1
- package/dist/vue.cjs +3335 -238
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +3305 -217
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
- package/dist/calendarGrid-DZeLwBj9.cjs.map +0 -1
- package/dist/calendarGrid-ggmFTZju.js.map +0 -1
package/dist/vue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue.js","names":[],"sources":["../src/vue/uiContext.ts","../src/vue/useCollectionRendering.ts","../src/vue/components/CollectionRecordModal.vue","../src/vue/components/CollectionRecordModal.vue","../src/vue/components/CollectionEmbedView.vue","../src/vue/components/CollectionEmbedView.vue","../src/vue/components/CollectionCalendarView.vue","../src/vue/components/CollectionCalendarView.vue","../src/vue/components/CollectionDayView.vue","../src/vue/components/CollectionDayView.vue","../src/vue/components/CollectionKanbanView.vue","../src/vue/components/CollectionKanbanView.vue","../src/vue/components/CollectionRecordPanel.vue","../src/vue/components/CollectionRecordPanel.vue"],"sourcesContent":["// Host-provided UI capabilities the collection view layer needs but a package\n// can't own: data fetching over the host's collection REST API, and the host's\n// asset-URL scheme. Each host (MulmoClaude, MulmoTerminal) configures this once\n// at app startup via `configureCollectionUi`; the view layer reads it through\n// `collectionUi()`. Mirrors the server-side `configureCollectionHost` binding.\n//\n// This grows as more of the View moves into the package (navigation, chat,\n// confirm, …); today it's the two capabilities `useCollectionRendering` needs.\n\nimport type { CollectionDetailResponse } from \"../core/uiTypes\";\n\n/** Result of a host data fetch — structurally a subset of the host's own\n * `ApiResult` (so the host can pass `apiGet` straight through). The view layer\n * treats `ok: false` as a skip, never throwing on one failed target. */\nexport type CollectionFetchResult<T> = { ok: true; data: T } | { ok: false };\n\nexport interface CollectionUi {\n /** Fetch a collection's detail (schema + records) by slug — backs ref/embed\n * resolution. Replaces the host's `apiGet(API_ROUTES.collections.detail)`. */\n fetchCollectionDetail: (slug: string) => Promise<CollectionFetchResult<CollectionDetailResponse>>;\n /** Browser-loadable URL for a file/image asset value (an html/svg artifact),\n * or null when the value isn't a renderable asset path. Replaces\n * `isValidFilePath` + `htmlPreviewUrlFor`/`svgPreviewUrlFor`. */\n fileAssetUrl: (value: unknown) => string | null;\n /** In-app File-Explorer route for a workspace file path (the fallback for\n * `file` values that aren't a directly-served artifact), or null when the\n * value isn't a valid in-workspace path. */\n fileRoutePath: (value: unknown) => string | null;\n /** Browser `<img src>` for a stored image value (a workspace file path), via\n * the host's raw-file endpoint. Replaces the host's `resolveImageSrc`. */\n imageSrc: (imageData: string) => string;\n}\n\nlet current: CollectionUi | null = null;\n\n/** Wire the collection view layer to a host. Call once at app startup. */\nexport function configureCollectionUi(capabilities: CollectionUi): void {\n current = capabilities;\n}\n\nexport function collectionUi(): CollectionUi {\n if (current === null) {\n throw new Error(\"@mulmoclaude/collection-plugin/vue: configureCollectionUi() was not called by the host\");\n }\n return current;\n}\n","// Rendering + linked-data layer for collection surfaces, extracted from\n// CollectionView.vue so the list/detail view AND the calendar view's\n// record panel share one implementation (and one set of ref/embed\n// caches). Owns: the per-target caches, the fan-out fetch that fills\n// them, and every helper that turns a stored value into something the\n// templates render (ref labels, money/currency, derived formulas, embed\n// rows). Pure-but-stateful: instantiate ONCE per collection surface and\n// pass the returned object down to child panels.\n\nimport { computed, ref, type ComputedRef, type Ref } from \"vue\";\nimport { collectionUi } from \"./uiContext\";\nimport { deriveAll } from \"../core/deriveAll\";\nimport type { CollectionDetail, CollectionItem, CollectionSchema, CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType } from \"../core/schema\";\nimport type {\n CollectionDetailResponse,\n EmbedCache,\n EmbedRow,\n EmbedView,\n RefCache,\n RefDisplayMap,\n RefOption,\n RefRecordCache,\n RefRecordMap,\n} from \"../core/uiTypes\";\n\nexport interface CollectionRendering {\n refCache: Ref<RefCache>;\n refRecordCache: Ref<RefRecordCache>;\n embedCache: Ref<EmbedCache>;\n resetLinkedCaches: () => void;\n loadLinkedCollections: (schema: CollectionSchema, expectedSlug: string) => Promise<void>;\n refDisplay: (targetSlug: string, itemSlug: string) => string;\n refOptions: (targetSlug: string) => RefOption[];\n embedViews: ComputedRef<Record<string, EmbedView>>;\n resolveCurrency: (field: FieldSpec, record: CollectionItem | null | undefined) => string | undefined;\n currencySymbol: (currency: string | undefined) => string;\n formatMoney: (value: unknown, currency: string | undefined, displayLocale: string) => string;\n formatCell: (value: unknown, type: FieldType) => string;\n detailText: (value: unknown) => string;\n isExternalUrl: (value: unknown) => boolean;\n artifactUrl: (value: unknown) => string | null;\n fileRoutePath: (value: unknown) => string | null;\n tableRows: (value: unknown) => Record<string, unknown>[];\n hasTableRows: (value: unknown) => boolean;\n formatSubCell: (subField: FieldSpec, value: unknown, record: CollectionItem | null) => string;\n inputTypeFor: (type: FieldType) => string;\n deriveAll: (schema: CollectionSchema, base: CollectionItem, refRecords: RefRecordCache) => CollectionItem;\n evaluateDerivedAgainstItem: (field: FieldSpec, fieldKey: string, item: CollectionItem) => number | null;\n derivedDisplay: (field: FieldSpec, computedValue: unknown, record: CollectionItem | null) => string;\n}\n\nexport function useCollectionRendering(collection: Ref<CollectionDetail | null>, locale: Ref<string>): CollectionRendering {\n const refCache = ref<RefCache>({});\n const refRecordCache = ref<RefRecordCache>({});\n const embedCache = ref<EmbedCache>({});\n\n function resetLinkedCaches(): void {\n refCache.value = {};\n refRecordCache.value = {};\n embedCache.value = {};\n }\n\n function uniqueRefTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n const walk = (fields: Record<string, FieldSpec>): void => {\n for (const field of Object.values(fields)) {\n if (field.type === \"ref\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n // Sub-fields of a table can also be refs; walk one level deep\n // (nested tables are schema-rejected, so one recursion suffices).\n if (field.type === \"table\" && field.of) walk(field.of);\n }\n };\n walk(schema.fields);\n return [...targets];\n }\n\n function uniqueEmbedTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n // Embeds are top-level only (the schema rejects `embed` inside a\n // table's `of`), so no recursion.\n for (const field of Object.values(schema.fields)) {\n if (field.type === \"embed\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n }\n return [...targets];\n }\n\n function buildRefDisplayMap(detail: CollectionDetailResponse): RefDisplayMap {\n const { fields, primaryKey } = detail.collection.schema;\n const displayField = \"name\" in fields ? \"name\" : \"title\" in fields ? \"title\" : primaryKey;\n const map: RefDisplayMap = {};\n for (const item of detail.items) {\n const slugRaw = item[primaryKey];\n if (typeof slugRaw !== \"string\" || slugRaw.length === 0) continue;\n const displayRaw = item[displayField];\n map[slugRaw] = typeof displayRaw === \"string\" && displayRaw.length > 0 ? displayRaw : slugRaw;\n }\n return map;\n }\n\n function buildRefRecordMap(detail: CollectionDetailResponse): RefRecordMap {\n const { schema } = detail.collection;\n const map: RefRecordMap = {};\n for (const item of detail.items) {\n const slugRaw = item[schema.primaryKey];\n if (typeof slugRaw === \"string\" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});\n }\n return map;\n }\n\n async function loadLinkedCollections(schema: CollectionSchema, expectedSlug: string): Promise<void> {\n const refTargets = new Set(uniqueRefTargets(schema));\n const embedTargets = new Set(uniqueEmbedTargets(schema));\n const allTargets = [...new Set([...refTargets, ...embedTargets])];\n if (allTargets.length === 0) return;\n const results = await Promise.all(\n allTargets.map((target) =>\n collectionUi()\n .fetchCollectionDetail(target)\n .then((result) => ({ target, result })),\n ),\n );\n // Stale-write guard: a quicker subsequent load may have replaced\n // `collection.value`; dropping the write avoids surfacing the\n // previous collection's linked data on the current one.\n if (collection.value?.slug !== expectedSlug) return;\n const nextRef: RefCache = {};\n const nextRefRecords: RefRecordCache = {};\n const nextEmbed: EmbedCache = {};\n for (const { target, result } of results) {\n if (!result.ok) continue;\n if (refTargets.has(target)) {\n nextRef[target] = buildRefDisplayMap(result.data);\n nextRefRecords[target] = buildRefRecordMap(result.data);\n }\n if (embedTargets.has(target)) nextEmbed[target] = { schema: result.data.collection.schema, items: result.data.items };\n }\n refCache.value = nextRef;\n refRecordCache.value = nextRefRecords;\n embedCache.value = nextEmbed;\n }\n\n function refDisplay(targetSlug: string, itemSlug: string): string {\n const map = refCache.value[targetSlug];\n return (map && map[itemSlug]) || itemSlug;\n }\n\n function refOptions(targetSlug: string): RefOption[] {\n const map = refCache.value[targetSlug];\n if (!map) return [];\n return Object.entries(map)\n .map(([slug, display]) => ({ slug, display }))\n .sort((left, right) => left.display.localeCompare(right.display));\n }\n\n function resolveEmbed(field: FieldSpec): { schema: CollectionSchema | null; item: CollectionItem | null } {\n if (field.type !== \"embed\" || !field.to || !field.id) return { schema: null, item: null };\n const data = embedCache.value[field.to];\n if (!data) return { schema: null, item: null };\n const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? \"\") === field.id) ?? null;\n return { schema: data.schema, item };\n }\n\n function embedValue(field: FieldSpec, value: unknown, record: CollectionItem | null): string {\n if (field.type === \"money\") return formatMoney(value, resolveCurrency(field, record), locale.value);\n return detailText(value);\n }\n\n const embedViews = computed<Record<string, EmbedView>>(() => {\n const out: Record<string, EmbedView> = {};\n if (!collection.value) return out;\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n if (field.type !== \"embed\") continue;\n const { schema, item } = resolveEmbed(field);\n const rows: EmbedRow[] = [];\n if (schema && item) {\n for (const [subKey, subField] of Object.entries(schema.fields)) {\n const value = item[subKey];\n // Skip empty fields — the embed is a read-only summary, so\n // unfilled optionals would just be \"—\" noise.\n if (value === undefined || value === null || value === \"\") continue;\n rows.push({ key: subKey, label: subField.label, type: subField.type, value, display: embedValue(subField, value, item) });\n }\n }\n out[key] = { found: Boolean(item), rows, targetSlug: field.to ?? \"\", recordId: field.id ?? \"\" };\n }\n return out;\n });\n\n function resolveCurrency(field: FieldSpec, record: CollectionItem | null | undefined): string | undefined {\n if (field.currencyField && record) {\n const code = record[field.currencyField];\n if (typeof code === \"string\" && code.trim().length > 0) return code;\n }\n return field.currency;\n }\n\n function currencySymbol(currency: string | undefined): string {\n const code = currency && currency.length > 0 ? currency : \"USD\";\n try {\n const parts = new Intl.NumberFormat(locale.value, { style: \"currency\", currency: code }).formatToParts(0);\n return parts.find((entry) => entry.type === \"currency\")?.value ?? code;\n } catch {\n return code;\n }\n }\n\n function formatMoney(value: unknown, currency: string | undefined, displayLocale: string): string {\n if (value === undefined || value === \"\") return \"—\";\n const amount = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(amount)) return String(value);\n const currencyCode = currency && currency.length > 0 ? currency : \"USD\";\n try {\n return new Intl.NumberFormat(displayLocale, { style: \"currency\", currency: currencyCode }).format(amount);\n } catch {\n return String(amount);\n }\n }\n\n function formatCell(value: unknown, type: FieldType): string {\n if (value === undefined || value === null || value === \"\") return \"—\";\n if (type === \"markdown\" && typeof value === \"string\") return value.length > 80 ? `${value.slice(0, 80)}…` : value;\n if (typeof value === \"string\" || typeof value === \"number\") return String(value);\n return JSON.stringify(value);\n }\n\n function isExternalUrl(value: unknown): boolean {\n return typeof value === \"string\" && /^https?:\\/\\//i.test(value);\n }\n\n // A `file` field holds a workspace-relative path. When it points at an\n // HTML/SVG artifact the server serves directly, return that served URL\n // so the rendered app can open in a new tab; otherwise null. Reject\n // absolute / `..`-traversing paths first (same guard as fileRoutePath)\n // — the preview-URL builders don't, so a `..` would normalize out of\n // the intended mount.\n function artifactUrl(value: unknown): string | null {\n return collectionUi().fileAssetUrl(value);\n }\n\n // In-app File Explorer route for a workspace path — the fallback for\n // `file` values that aren't a directly-served artifact. The host owns the\n // path validity + route scheme.\n function fileRoutePath(value: unknown): string | null {\n return collectionUi().fileRoutePath(value);\n }\n\n function detailText(value: unknown): string {\n if (value === undefined || value === null || value === \"\") return \"—\";\n return String(value);\n }\n\n function tableRows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) return [];\n return value.filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === \"object\" && !Array.isArray(row));\n }\n\n function hasTableRows(value: unknown): boolean {\n return tableRows(value).length > 0;\n }\n\n function formatSubCell(subField: FieldSpec, value: unknown, record: CollectionItem | null): string {\n if (subField.type === \"money\") return formatMoney(value, resolveCurrency(subField, record), locale.value);\n if (subField.type === \"ref\" && subField.to && typeof value === \"string\" && value.length > 0) return refDisplay(subField.to, value);\n return formatCell(value, subField.type);\n }\n\n function inputTypeFor(type: FieldType): string {\n if (type === \"email\") return \"email\";\n if (type === \"number\") return \"number\";\n if (type === \"money\") return \"number\";\n if (type === \"date\") return \"date\";\n if (type === \"datetime\") return \"datetime-local\";\n return \"text\";\n }\n\n // The derive loop itself lives in `utils/collections/deriveAll.ts`,\n // shared with the server's manageCollection enrichment so both sides\n // compute identical values. This composable re-exposes it (typed with\n // the richer client types via structural assignability) plus the\n // collection-bound convenience wrappers below.\n\n function evaluateDerivedAgainstItem(field: FieldSpec, fieldKey: string, item: CollectionItem): number | null {\n if (!field.formula || !collection.value) return null;\n const enriched = deriveAll(collection.value.schema, item, refRecordCache.value);\n const result = enriched[fieldKey];\n return typeof result === \"number\" && Number.isFinite(result) ? result : null;\n }\n\n function derivedDisplay(field: FieldSpec, computedValue: unknown, record: CollectionItem | null): string {\n if (computedValue === null || computedValue === undefined) return \"—\";\n if (field.display === \"money\") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);\n return formatCell(computedValue, field.display ?? \"number\");\n }\n\n return {\n refCache,\n refRecordCache,\n embedCache,\n resetLinkedCaches,\n loadLinkedCollections,\n refDisplay,\n refOptions,\n embedViews,\n resolveCurrency,\n currencySymbol,\n formatMoney,\n formatCell,\n detailText,\n isExternalUrl,\n artifactUrl,\n fileRoutePath,\n tableRows,\n hasTableRows,\n formatSubCell,\n inputTypeFor,\n deriveAll,\n evaluateDerivedAgainstItem,\n derivedDisplay,\n };\n}\n","<template>\n <!-- Centered modal shell for a collection record's open/edit panel. Used\n by every non-calendar view mode (table / kanban) and the\n calendar's undated tray, so opening an item is a consistent popup\n everywhere. Calendar's dated records keep their own day-view modal\n (CollectionDayView), which embeds the same panel on its right. Teleported\n to <body> so an embedded card's transformed ancestor can't trap the\n fixed overlay. Backdrop click / Escape both emit `close`; the host\n decides whether that cancels an edit or closes the detail.\n\n Focus is contained while open (Tab/Shift+Tab wrap inside the dialog)\n and restored to the trigger on close, so keyboard users can't reach\n the controls behind the overlay (WCAG focus containment). -->\n <Teleport to=\"body\">\n <div class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\" data-testid=\"collections-record-modal\" @click.self=\"emit('close')\">\n <div\n ref=\"dialogEl\"\n class=\"flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl bg-white shadow-xl focus:outline-none\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n @keydown.esc=\"emit('close')\"\n @keydown.tab=\"onTab\"\n >\n <slot />\n </div>\n </div>\n </Teleport>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst emit = defineEmits<{ close: [] }>();\n\nconst dialogEl = ref<HTMLDivElement | null>(null);\n\n// The control that had focus before the modal opened (usually the row /\n// card the user activated). Restored when the modal unmounts.\nlet previouslyFocused: HTMLElement | null = null;\n\nconst FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/** Visible, focusable controls inside the dialog, in DOM order. */\nfunction focusableItems(): HTMLElement[] {\n if (!dialogEl.value) return [];\n return Array.from(dialogEl.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((node) => node.offsetParent !== null);\n}\n\n/** Trap Tab / Shift+Tab inside the dialog so focus can't escape to the\n * page behind the overlay. Wraps at both ends; the dialog container\n * itself (tabindex -1) counts as \"before the first item\". */\nfunction onTab(event: KeyboardEvent): void {\n const items = focusableItems();\n if (items.length === 0) {\n event.preventDefault();\n dialogEl.value?.focus();\n return;\n }\n const [first] = items;\n const last = items[items.length - 1];\n const active = document.activeElement;\n if (event.shiftKey) {\n if (active === first || active === dialogEl.value) {\n event.preventDefault();\n last.focus();\n }\n } else if (active === last) {\n event.preventDefault();\n first.focus();\n }\n}\n\n// Focus the dialog on open so Escape (bound on the dialog) fires even\n// before the user clicks into a field, and focus leaves the row behind it.\nonMounted(async () => {\n previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n await nextTick();\n dialogEl.value?.focus();\n});\n\n// Restore focus to the trigger so keyboard users land back where they were.\nonBeforeUnmount(() => {\n previouslyFocused?.focus?.();\n});\n</script>\n","<template>\n <!-- Centered modal shell for a collection record's open/edit panel. Used\n by every non-calendar view mode (table / kanban) and the\n calendar's undated tray, so opening an item is a consistent popup\n everywhere. Calendar's dated records keep their own day-view modal\n (CollectionDayView), which embeds the same panel on its right. Teleported\n to <body> so an embedded card's transformed ancestor can't trap the\n fixed overlay. Backdrop click / Escape both emit `close`; the host\n decides whether that cancels an edit or closes the detail.\n\n Focus is contained while open (Tab/Shift+Tab wrap inside the dialog)\n and restored to the trigger on close, so keyboard users can't reach\n the controls behind the overlay (WCAG focus containment). -->\n <Teleport to=\"body\">\n <div class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\" data-testid=\"collections-record-modal\" @click.self=\"emit('close')\">\n <div\n ref=\"dialogEl\"\n class=\"flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl bg-white shadow-xl focus:outline-none\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n @keydown.esc=\"emit('close')\"\n @keydown.tab=\"onTab\"\n >\n <slot />\n </div>\n </div>\n </Teleport>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst emit = defineEmits<{ close: [] }>();\n\nconst dialogEl = ref<HTMLDivElement | null>(null);\n\n// The control that had focus before the modal opened (usually the row /\n// card the user activated). Restored when the modal unmounts.\nlet previouslyFocused: HTMLElement | null = null;\n\nconst FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/** Visible, focusable controls inside the dialog, in DOM order. */\nfunction focusableItems(): HTMLElement[] {\n if (!dialogEl.value) return [];\n return Array.from(dialogEl.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((node) => node.offsetParent !== null);\n}\n\n/** Trap Tab / Shift+Tab inside the dialog so focus can't escape to the\n * page behind the overlay. Wraps at both ends; the dialog container\n * itself (tabindex -1) counts as \"before the first item\". */\nfunction onTab(event: KeyboardEvent): void {\n const items = focusableItems();\n if (items.length === 0) {\n event.preventDefault();\n dialogEl.value?.focus();\n return;\n }\n const [first] = items;\n const last = items[items.length - 1];\n const active = document.activeElement;\n if (event.shiftKey) {\n if (active === first || active === dialogEl.value) {\n event.preventDefault();\n last.focus();\n }\n } else if (active === last) {\n event.preventDefault();\n first.focus();\n }\n}\n\n// Focus the dialog on open so Escape (bound on the dialog) fires even\n// before the user clicks into a field, and focus leaves the row behind it.\nonMounted(async () => {\n previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n await nextTick();\n dialogEl.value?.focus();\n});\n\n// Restore focus to the trigger so keyboard users land back where they were.\nonBeforeUnmount(() => {\n previouslyFocused?.focus?.();\n});\n</script>\n","<template>\n <!-- Found: the whole card links to the embedded record's detail view,\n like a normal `ref` link (record → record hop). -->\n <router-link\n v-if=\"view.found\"\n :to=\"{ path: `/collections/${view.targetSlug}`, query: { selected: view.recordId } }\"\n class=\"group block relative rounded-xl border border-slate-200 bg-slate-50/50 p-4 pl-5 space-y-3 hover:bg-indigo-50/20 hover:border-indigo-200 transition-all duration-300 shadow-sm\"\n :data-testid=\"`collections-embed-${fieldKey}`\"\n >\n <!-- Left Accent Stripe -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-indigo-500 rounded-l-xl transition-all duration-300 group-hover:w-1.5 group-hover:bg-indigo-600\"></div>\n\n <!-- Header Reference Badge -->\n <div class=\"flex items-center justify-between text-[10px] font-bold text-indigo-600/90 tracking-wider uppercase\">\n <div class=\"flex items-center gap-1.5\">\n <span class=\"material-icons text-sm\">link</span>\n <span>{{ view.targetSlug }}</span>\n </div>\n <span class=\"bg-indigo-100/60 text-indigo-700 px-1.5 py-0.5 rounded font-mono font-medium lowercase\">{{ view.recordId }}</span>\n </div>\n\n <!-- Grid-based detail fields -->\n <div class=\"grid gap-x-4 gap-y-3 grid-cols-2\">\n <div v-for=\"row in view.rows\" :key=\"row.key\" class=\"space-y-0.5\">\n <div class=\"text-[10px] font-semibold text-slate-400 uppercase tracking-wide\">{{ row.label }}</div>\n <div class=\"text-xs text-slate-700 font-medium break-words\" :data-testid=\"`collections-embed-${fieldKey}-${row.key}`\">\n <template v-if=\"row.type === 'boolean'\">\n <span v-if=\"row.value === true\" class=\"material-icons text-emerald-600 text-sm align-middle\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph, same treatment as the other read-only detail branches. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <p v-else-if=\"row.type === 'markdown'\" class=\"whitespace-pre-wrap font-normal text-slate-600\">{{ row.display }}</p>\n <span v-else>{{ row.display }}</span>\n </div>\n </div>\n </div>\n </router-link>\n\n <div v-else class=\"relative rounded-xl border border-red-100 bg-red-50/30 p-4 pl-5 shadow-sm\" :data-testid=\"`collections-embed-${fieldKey}`\">\n <!-- Left Accent Stripe for Error/Missing -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-red-400 rounded-l-xl\"></div>\n <div class=\"flex items-start gap-3\">\n <span class=\"material-icons text-red-500 text-lg mt-0.5\">error_outline</span>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-xs font-semibold text-red-800 uppercase tracking-wider mb-1\">{{ t(\"collectionsView.embedMissingTitle\") }}</p>\n <p class=\"text-xs text-red-600\" :data-testid=\"`collections-embed-missing-${fieldKey}`\">\n {{ t(\"collectionsView.embedMissing\", { collection: view.targetSlug, id: view.recordId }) }}\n </p>\n <router-link\n v-if=\"view.targetSlug\"\n :to=\"{ path: `/collections/${view.targetSlug}` }\"\n class=\"inline-flex items-center gap-0.5 text-xs text-indigo-600 hover:text-indigo-800 font-semibold mt-2 hover:underline\"\n >\n <span>{{ t(\"collectionsView.embedCreate\") }}</span>\n <span class=\"material-icons text-xs\">arrow_forward</span>\n </router-link>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n// `<router-link>` is a host-provided global (vue-router). MulmoClaude registers\n// it app-wide; a host embedding this card must do the same and own a\n// `/collections/:slug` route. Translation keys (`collectionsView.*`) resolve\n// against the host's vue-i18n instance — the host must define them.\nimport { useI18n } from \"vue-i18n\";\nimport type { EmbedView } from \"../../core/uiTypes\";\n\ndefineProps<{ view: EmbedView; fieldKey: string }>();\n\nconst { t } = useI18n();\n</script>\n","<template>\n <!-- Found: the whole card links to the embedded record's detail view,\n like a normal `ref` link (record → record hop). -->\n <router-link\n v-if=\"view.found\"\n :to=\"{ path: `/collections/${view.targetSlug}`, query: { selected: view.recordId } }\"\n class=\"group block relative rounded-xl border border-slate-200 bg-slate-50/50 p-4 pl-5 space-y-3 hover:bg-indigo-50/20 hover:border-indigo-200 transition-all duration-300 shadow-sm\"\n :data-testid=\"`collections-embed-${fieldKey}`\"\n >\n <!-- Left Accent Stripe -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-indigo-500 rounded-l-xl transition-all duration-300 group-hover:w-1.5 group-hover:bg-indigo-600\"></div>\n\n <!-- Header Reference Badge -->\n <div class=\"flex items-center justify-between text-[10px] font-bold text-indigo-600/90 tracking-wider uppercase\">\n <div class=\"flex items-center gap-1.5\">\n <span class=\"material-icons text-sm\">link</span>\n <span>{{ view.targetSlug }}</span>\n </div>\n <span class=\"bg-indigo-100/60 text-indigo-700 px-1.5 py-0.5 rounded font-mono font-medium lowercase\">{{ view.recordId }}</span>\n </div>\n\n <!-- Grid-based detail fields -->\n <div class=\"grid gap-x-4 gap-y-3 grid-cols-2\">\n <div v-for=\"row in view.rows\" :key=\"row.key\" class=\"space-y-0.5\">\n <div class=\"text-[10px] font-semibold text-slate-400 uppercase tracking-wide\">{{ row.label }}</div>\n <div class=\"text-xs text-slate-700 font-medium break-words\" :data-testid=\"`collections-embed-${fieldKey}-${row.key}`\">\n <template v-if=\"row.type === 'boolean'\">\n <span v-if=\"row.value === true\" class=\"material-icons text-emerald-600 text-sm align-middle\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph, same treatment as the other read-only detail branches. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <p v-else-if=\"row.type === 'markdown'\" class=\"whitespace-pre-wrap font-normal text-slate-600\">{{ row.display }}</p>\n <span v-else>{{ row.display }}</span>\n </div>\n </div>\n </div>\n </router-link>\n\n <div v-else class=\"relative rounded-xl border border-red-100 bg-red-50/30 p-4 pl-5 shadow-sm\" :data-testid=\"`collections-embed-${fieldKey}`\">\n <!-- Left Accent Stripe for Error/Missing -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-red-400 rounded-l-xl\"></div>\n <div class=\"flex items-start gap-3\">\n <span class=\"material-icons text-red-500 text-lg mt-0.5\">error_outline</span>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-xs font-semibold text-red-800 uppercase tracking-wider mb-1\">{{ t(\"collectionsView.embedMissingTitle\") }}</p>\n <p class=\"text-xs text-red-600\" :data-testid=\"`collections-embed-missing-${fieldKey}`\">\n {{ t(\"collectionsView.embedMissing\", { collection: view.targetSlug, id: view.recordId }) }}\n </p>\n <router-link\n v-if=\"view.targetSlug\"\n :to=\"{ path: `/collections/${view.targetSlug}` }\"\n class=\"inline-flex items-center gap-0.5 text-xs text-indigo-600 hover:text-indigo-800 font-semibold mt-2 hover:underline\"\n >\n <span>{{ t(\"collectionsView.embedCreate\") }}</span>\n <span class=\"material-icons text-xs\">arrow_forward</span>\n </router-link>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n// `<router-link>` is a host-provided global (vue-router). MulmoClaude registers\n// it app-wide; a host embedding this card must do the same and own a\n// `/collections/:slug` route. Translation keys (`collectionsView.*`) resolve\n// against the host's vue-i18n instance — the host must define them.\nimport { useI18n } from \"vue-i18n\";\nimport type { EmbedView } from \"../../core/uiTypes\";\n\ndefineProps<{ view: EmbedView; fieldKey: string }>();\n\nconst { t } = useI18n();\n</script>\n","<template>\n <div class=\"flex flex-col gap-3\" data-testid=\"collection-calendar\">\n <!-- Month nav -->\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarPrevMonth')\"\n data-testid=\"collection-calendar-prev\"\n @click=\"stepMonth(-1)\"\n >\n <span class=\"material-icons text-lg\">chevron_left</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarNextMonth')\"\n data-testid=\"collection-calendar-next\"\n @click=\"stepMonth(1)\"\n >\n <span class=\"material-icons text-lg\">chevron_right</span>\n </button>\n <h3 class=\"text-sm font-bold text-slate-800 flex-1\" data-testid=\"collection-calendar-month\">{{ monthLabel }}</h3>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors\"\n data-testid=\"collection-calendar-today\"\n @click=\"goToday\"\n >\n {{ t(\"collectionsView.calendarToday\") }}\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none\">\n <div v-for=\"(label, idx) in weekdayLabels\" :key=\"idx\" class=\"px-1 py-1 text-center\">{{ label }}</div>\n </div>\n\n <!-- Day grid. Every cell is a keyboard-operable button that opens the day\n (time-allocation) view; its record chips are nested interactive\n elements that `@click.stop` to select instead. Creating a record now\n happens from inside the day view's + button. -->\n <div class=\"grid grid-cols-7 gap-1\">\n <div\n v-for=\"{ cell, entries } in cells\"\n :key=\"cell.key\"\n class=\"min-h-[5.5rem] rounded-lg border p-1 flex flex-col gap-1 overflow-hidden transition-colors cursor-pointer hover:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n :class=\"cell.inMonth ? 'bg-white border-slate-200' : 'bg-slate-50/50 border-slate-100'\"\n role=\"button\"\n :tabindex=\"0\"\n :aria-label=\"t('collectionsView.dayViewOpen', { date: cell.key })\"\n :data-testid=\"`collection-calendar-day-${cell.key}`\"\n @click=\"emit('openDay', cell.ymd)\"\n @keydown.enter.self.prevent=\"emit('openDay', cell.ymd)\"\n @keydown.space.self.prevent=\"emit('openDay', cell.ymd)\"\n >\n <div class=\"flex items-center justify-end\">\n <span\n class=\"text-[11px] font-bold h-5 min-w-5 px-1 inline-flex items-center justify-center rounded-full\"\n :class=\"cell.key === todayKey ? 'bg-indigo-600 text-white' : cell.inMonth ? 'text-slate-500' : 'text-slate-300'\"\n >{{ cell.ymd.day }}</span\n >\n </div>\n <button\n v-for=\"entry in entries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-left text-[11px] leading-tight font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, DAY_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-chip-${entry.id}`\"\n @click.stop=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Records with no usable anchor date — listed rather than dropped. -->\n <div v-if=\"bucketed.noDate.length > 0\" class=\"flex flex-wrap items-center gap-1.5 pt-1\" data-testid=\"collection-calendar-no-date\">\n <span class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider mr-1\">{{ t(\"collectionsView.calendarNoDate\") }}</span>\n <button\n v-for=\"entry in undatedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-[11px] font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, UNDATED_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-undated-${entry.id}`\"\n @click=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport { bucketRecords, buildMonthGrid, ymdKey, daySlice, MINUTES_PER_DAY, type Ymd, type RecordSpan, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n /** The `date`/`datetime` field whose value places each record on the grid. */\n anchorField: string;\n /** Optional second `date`/`datetime` field — records span anchor→end inclusive. */\n endField?: string;\n /** Optional free-form time-string field driving the day (time-allocation) view. */\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour.\n * Empty / unset → the default indigo styling. */\n colorField?: string;\n /** Primary-key of the currently-open record (highlighted chip). */\n selected?: string;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** A day cell was activated → the host opens the time-allocation popup. */\n openDay: [day: Ymd];\n}>();\n\nconst { t, locale } = useI18n();\n\n// Visible month, 1-12. Initial value is the current local month — app\n// code, so `new Date()` is fine (the pure grid helpers stay clock-free).\nconst now = new Date();\nconst viewYear = ref(now.getFullYear());\nconst viewMonth = ref(now.getMonth() + 1);\n\nconst todayKey = ymdKey({ year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() });\n\nconst grid = computed(() => buildMonthGrid(viewYear.value, viewMonth.value));\n\nconst bucketed = computed(() => bucketRecords(props.items, props.anchorField, props.endField, props.timeField));\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\ninterface CalendarEntry {\n id: string;\n label: string;\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\ninterface DayPair {\n span: RecordSpan<CollectionItem>;\n slice: DaySlice;\n}\n\n/** Sort key for ordering a day's chips by start time: earliest first, with\n * clock-less all-day records sinking to the bottom (matching the day view). */\nfunction sliceStartKey(slice: DaySlice): number {\n return slice.kind === \"allDay\" ? MINUTES_PER_DAY + 1 : slice.startMin;\n}\n\n/** Records whose span covers a given day, ordered by start time so the month\n * grid stacks chips the same way the day (time-allocation) view does. */\nfunction recordsOnDay(day: Ymd): CalendarEntry[] {\n return bucketed.value.spans\n .map((span) => ({ span, slice: daySlice(span, day) }))\n .filter((pair): pair is DayPair => pair.slice !== null)\n .sort((left, right) => sliceStartKey(left.slice) - sliceStartKey(right.slice))\n .map(({ span }) => ({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n color: colorOf(span.item),\n }));\n}\n\n/** Grid cells paired with the records that land on them, computed once per\n * render. Clicking any cell opens the day view (create happens there). */\nconst cells = computed(() => grid.value.map((cell) => ({ cell, entries: recordsOnDay(cell.ymd) })));\n\nconst undatedEntries = computed<CalendarEntry[]>(() =>\n bucketed.value.noDate.map((item) => ({\n id: itemIdOf(item, props.schema),\n label: itemLabelOf(item, props.schema, labelField.value),\n color: colorOf(item),\n })),\n);\n\nconst DAY_CHIP_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-100 hover:bg-indigo-100\";\nconst UNDATED_CHIP_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\n/** Chip classes: the selected chip keeps the solid indigo highlight; otherwise\n * a record with a resolved colour tints the chip, and one with none (no colour\n * field) falls back to `uncolored`. */\nfunction chipClass(entry: CalendarEntry, uncolored: string): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return uncolored;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nconst monthLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(viewYear.value, viewMonth.value - 1, 1)),\n );\n } catch {\n return `${viewYear.value}-${String(viewMonth.value).padStart(2, \"0\")}`;\n }\n});\n\n/** Localized short weekday names, Sunday-first (matches the grid). */\nconst weekdayLabels = computed<string[]>(() => {\n try {\n const formatter = new Intl.DateTimeFormat(locale.value, { weekday: \"short\", timeZone: \"UTC\" });\n // 2024-01-07 is a Sunday — anchor the week there.\n return Array.from({ length: 7 }, (_, idx) => formatter.format(new Date(Date.UTC(2024, 0, 7 + idx))));\n } catch {\n return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n }\n});\n\nfunction stepMonth(delta: number): void {\n const next = viewMonth.value + delta;\n if (next < 1) {\n viewMonth.value = 12;\n viewYear.value -= 1;\n } else if (next > 12) {\n viewMonth.value = 1;\n viewYear.value += 1;\n } else {\n viewMonth.value = next;\n }\n}\n\nfunction goToday(): void {\n viewYear.value = now.getFullYear();\n viewMonth.value = now.getMonth() + 1;\n}\n</script>\n","<template>\n <div class=\"flex flex-col gap-3\" data-testid=\"collection-calendar\">\n <!-- Month nav -->\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarPrevMonth')\"\n data-testid=\"collection-calendar-prev\"\n @click=\"stepMonth(-1)\"\n >\n <span class=\"material-icons text-lg\">chevron_left</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarNextMonth')\"\n data-testid=\"collection-calendar-next\"\n @click=\"stepMonth(1)\"\n >\n <span class=\"material-icons text-lg\">chevron_right</span>\n </button>\n <h3 class=\"text-sm font-bold text-slate-800 flex-1\" data-testid=\"collection-calendar-month\">{{ monthLabel }}</h3>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors\"\n data-testid=\"collection-calendar-today\"\n @click=\"goToday\"\n >\n {{ t(\"collectionsView.calendarToday\") }}\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none\">\n <div v-for=\"(label, idx) in weekdayLabels\" :key=\"idx\" class=\"px-1 py-1 text-center\">{{ label }}</div>\n </div>\n\n <!-- Day grid. Every cell is a keyboard-operable button that opens the day\n (time-allocation) view; its record chips are nested interactive\n elements that `@click.stop` to select instead. Creating a record now\n happens from inside the day view's + button. -->\n <div class=\"grid grid-cols-7 gap-1\">\n <div\n v-for=\"{ cell, entries } in cells\"\n :key=\"cell.key\"\n class=\"min-h-[5.5rem] rounded-lg border p-1 flex flex-col gap-1 overflow-hidden transition-colors cursor-pointer hover:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n :class=\"cell.inMonth ? 'bg-white border-slate-200' : 'bg-slate-50/50 border-slate-100'\"\n role=\"button\"\n :tabindex=\"0\"\n :aria-label=\"t('collectionsView.dayViewOpen', { date: cell.key })\"\n :data-testid=\"`collection-calendar-day-${cell.key}`\"\n @click=\"emit('openDay', cell.ymd)\"\n @keydown.enter.self.prevent=\"emit('openDay', cell.ymd)\"\n @keydown.space.self.prevent=\"emit('openDay', cell.ymd)\"\n >\n <div class=\"flex items-center justify-end\">\n <span\n class=\"text-[11px] font-bold h-5 min-w-5 px-1 inline-flex items-center justify-center rounded-full\"\n :class=\"cell.key === todayKey ? 'bg-indigo-600 text-white' : cell.inMonth ? 'text-slate-500' : 'text-slate-300'\"\n >{{ cell.ymd.day }}</span\n >\n </div>\n <button\n v-for=\"entry in entries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-left text-[11px] leading-tight font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, DAY_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-chip-${entry.id}`\"\n @click.stop=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Records with no usable anchor date — listed rather than dropped. -->\n <div v-if=\"bucketed.noDate.length > 0\" class=\"flex flex-wrap items-center gap-1.5 pt-1\" data-testid=\"collection-calendar-no-date\">\n <span class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider mr-1\">{{ t(\"collectionsView.calendarNoDate\") }}</span>\n <button\n v-for=\"entry in undatedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-[11px] font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, UNDATED_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-undated-${entry.id}`\"\n @click=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport { bucketRecords, buildMonthGrid, ymdKey, daySlice, MINUTES_PER_DAY, type Ymd, type RecordSpan, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n /** The `date`/`datetime` field whose value places each record on the grid. */\n anchorField: string;\n /** Optional second `date`/`datetime` field — records span anchor→end inclusive. */\n endField?: string;\n /** Optional free-form time-string field driving the day (time-allocation) view. */\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour.\n * Empty / unset → the default indigo styling. */\n colorField?: string;\n /** Primary-key of the currently-open record (highlighted chip). */\n selected?: string;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** A day cell was activated → the host opens the time-allocation popup. */\n openDay: [day: Ymd];\n}>();\n\nconst { t, locale } = useI18n();\n\n// Visible month, 1-12. Initial value is the current local month — app\n// code, so `new Date()` is fine (the pure grid helpers stay clock-free).\nconst now = new Date();\nconst viewYear = ref(now.getFullYear());\nconst viewMonth = ref(now.getMonth() + 1);\n\nconst todayKey = ymdKey({ year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() });\n\nconst grid = computed(() => buildMonthGrid(viewYear.value, viewMonth.value));\n\nconst bucketed = computed(() => bucketRecords(props.items, props.anchorField, props.endField, props.timeField));\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\ninterface CalendarEntry {\n id: string;\n label: string;\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\ninterface DayPair {\n span: RecordSpan<CollectionItem>;\n slice: DaySlice;\n}\n\n/** Sort key for ordering a day's chips by start time: earliest first, with\n * clock-less all-day records sinking to the bottom (matching the day view). */\nfunction sliceStartKey(slice: DaySlice): number {\n return slice.kind === \"allDay\" ? MINUTES_PER_DAY + 1 : slice.startMin;\n}\n\n/** Records whose span covers a given day, ordered by start time so the month\n * grid stacks chips the same way the day (time-allocation) view does. */\nfunction recordsOnDay(day: Ymd): CalendarEntry[] {\n return bucketed.value.spans\n .map((span) => ({ span, slice: daySlice(span, day) }))\n .filter((pair): pair is DayPair => pair.slice !== null)\n .sort((left, right) => sliceStartKey(left.slice) - sliceStartKey(right.slice))\n .map(({ span }) => ({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n color: colorOf(span.item),\n }));\n}\n\n/** Grid cells paired with the records that land on them, computed once per\n * render. Clicking any cell opens the day view (create happens there). */\nconst cells = computed(() => grid.value.map((cell) => ({ cell, entries: recordsOnDay(cell.ymd) })));\n\nconst undatedEntries = computed<CalendarEntry[]>(() =>\n bucketed.value.noDate.map((item) => ({\n id: itemIdOf(item, props.schema),\n label: itemLabelOf(item, props.schema, labelField.value),\n color: colorOf(item),\n })),\n);\n\nconst DAY_CHIP_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-100 hover:bg-indigo-100\";\nconst UNDATED_CHIP_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\n/** Chip classes: the selected chip keeps the solid indigo highlight; otherwise\n * a record with a resolved colour tints the chip, and one with none (no colour\n * field) falls back to `uncolored`. */\nfunction chipClass(entry: CalendarEntry, uncolored: string): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return uncolored;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nconst monthLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(viewYear.value, viewMonth.value - 1, 1)),\n );\n } catch {\n return `${viewYear.value}-${String(viewMonth.value).padStart(2, \"0\")}`;\n }\n});\n\n/** Localized short weekday names, Sunday-first (matches the grid). */\nconst weekdayLabels = computed<string[]>(() => {\n try {\n const formatter = new Intl.DateTimeFormat(locale.value, { weekday: \"short\", timeZone: \"UTC\" });\n // 2024-01-07 is a Sunday — anchor the week there.\n return Array.from({ length: 7 }, (_, idx) => formatter.format(new Date(Date.UTC(2024, 0, 7 + idx))));\n } catch {\n return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n }\n});\n\nfunction stepMonth(delta: number): void {\n const next = viewMonth.value + delta;\n if (next < 1) {\n viewMonth.value = 12;\n viewYear.value -= 1;\n } else if (next > 12) {\n viewMonth.value = 1;\n viewYear.value += 1;\n } else {\n viewMonth.value = next;\n }\n}\n\nfunction goToday(): void {\n viewYear.value = now.getFullYear();\n viewMonth.value = now.getMonth() + 1;\n}\n</script>\n","<template>\n <!-- Modal overlay: a time-allocation view of one day. Backdrop click and\n Escape close it. Selecting a record expands the modal to two columns —\n the timeline on the left, the record's detail (the `#detail` slot) on\n the right. -->\n <div\n class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\"\n data-testid=\"collection-day-view\"\n @click.self=\"emit('close')\"\n @keydown.esc=\"emit('close')\"\n >\n <div\n ref=\"dialogEl\"\n tabindex=\"-1\"\n class=\"flex max-h-[85vh] w-full flex-row rounded-2xl bg-white shadow-xl focus:outline-none\"\n :class=\"showDetail ? 'max-w-4xl' : 'max-w-md'\"\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <!-- Left column: the time-allocation timeline. Shrinks to a fixed width\n when a record detail is shown alongside it, else fills the modal. -->\n <div class=\"flex min-h-0 flex-col\" :class=\"showDetail ? 'w-80 shrink-0 border-r border-slate-200' : 'w-full'\">\n <!-- Header -->\n <div class=\"flex items-center gap-2 border-b border-slate-200 px-4 py-3\">\n <h3 class=\"flex-1 text-sm font-bold text-slate-800\" data-testid=\"collection-day-view-title\">{{ dayLabel }}</h3>\n <button\n v-if=\"canCreate\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarCreateOn', { date: dayKey })\"\n data-testid=\"collection-day-view-create\"\n @click=\"onCreate\"\n >\n <span class=\"material-icons text-lg\">add</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.dayViewClose')\"\n data-testid=\"collection-day-view-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n\n <!-- Empty state -->\n <div v-if=\"timedEntries.length === 0 && allDayEntries.length === 0\" class=\"px-4 py-10 text-center text-sm text-slate-400\">\n {{ t(\"collectionsView.dayViewEmpty\") }}\n </div>\n\n <!-- Timeline -->\n <div v-else ref=\"scrollEl\" class=\"flex-1 overflow-y-auto px-2 py-2\">\n <div class=\"relative\" :style=\"{ height: `${TOTAL_HEIGHT}px` }\" data-testid=\"collection-day-view-timeline\">\n <!-- Hour gridlines + labels -->\n <div v-for=\"hour in 24\" :key=\"hour\" class=\"absolute left-0 right-0 border-t border-slate-100\" :style=\"{ top: `${(hour - 1) * HOUR_PX}px` }\">\n <span class=\"absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400\">{{ hourLabel(hour - 1) }}</span>\n </div>\n\n <!-- Event track (right of the hour gutter) -->\n <div class=\"absolute inset-y-0 right-0\" style=\"left: 2.75rem\">\n <button\n v-for=\"entry in timedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"absolute overflow-hidden rounded border px-1.5 py-0.5 text-left transition-colors\"\n :class=\"timedChipClass(entry)\"\n :style=\"entry.style\"\n :data-testid=\"`collection-day-view-chip-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n <span class=\"block truncate text-[11px] font-semibold leading-tight\">\n <span v-if=\"entry.slice.bleedsBefore\" aria-hidden=\"true\">▲ </span>{{ entry.label\n }}<span v-if=\"entry.slice.bleedsAfter\" aria-hidden=\"true\"> ▼</span>\n </span>\n <!-- A few non-date/time fields under the title. The chip's height\n stays proportional to its duration; extra lines just clip. -->\n <span v-for=\"(text, i) in entry.secondary\" :key=\"i\" class=\"block truncate text-[10px] leading-tight opacity-70\">{{ text }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- All-day strip (records with no clock) at the bottom -->\n <div\n v-if=\"allDayEntries.length > 0\"\n class=\"flex flex-wrap items-center gap-1.5 border-t border-slate-200 px-4 py-2\"\n data-testid=\"collection-day-view-all-day\"\n >\n <span class=\"mr-1 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.dayViewAllDay\") }}</span>\n <button\n v-for=\"entry in allDayEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"truncate rounded border px-1.5 py-0.5 text-[11px] font-semibold transition-colors\"\n :class=\"allDayChipClass(entry)\"\n :data-testid=\"`collection-day-view-allday-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Right column: the selected (or being-created) record's detail panel,\n supplied by the host so selection no longer hands off to a panel\n below the calendar. -->\n <div v-if=\"showDetail\" class=\"min-w-0 flex-1 overflow-y-auto\" data-testid=\"collection-day-view-detail\">\n <slot name=\"detail\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport { bucketRecords, daySlice, assignLanes, ymdKey, MINUTES_PER_DAY, type Ymd, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n day: Ymd;\n anchorField: string;\n endField?: string;\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour\n * (matching the month view). Empty / unset → default indigo/slate styling. */\n colorField?: string;\n selected?: string;\n canCreate: boolean;\n /** When true, expand the modal to two columns and render the `#detail`\n * slot (the selected/created record) to the right of the timeline. */\n showDetail?: boolean;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n createOn: [iso: string];\n close: [];\n}>();\n\nconst { t, locale } = useI18n();\n\n// One hour = 48px tall; the full day is 24 of them. A point-in-time event\n// (start, no end) has no duration to size by, so it gets a fixed one-line-tall\n// box (`LINE_PX`) — enough to read its time + label — and a `LANE_MIN_MINUTES`\n// footprint so two near-simultaneous events still split into lanes.\nconst HOUR_PX = 48;\nconst TOTAL_HEIGHT = HOUR_PX * 24;\nconst PX_PER_MIN = HOUR_PX / 60;\nconst MIN_BLOCK_PX = 16;\nconst LINE_PX = 20;\nconst LANE_MIN_MINUTES = 30;\n\nconst scrollEl = ref<HTMLElement | null>(null);\nconst dialogEl = ref<HTMLElement | null>(null);\n\nconst dayKey = computed<string>(() => ymdKey(props.day));\n\nconst dayLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(props.day.year, props.day.month - 1, props.day.day)),\n );\n } catch {\n return dayKey.value;\n }\n});\n\nfunction hourLabel(hour: number): string {\n return `${String(hour).padStart(2, \"0\")}:00`;\n}\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\n// Field types with no compact inline representation for a chip subtitle.\nconst CHIP_SKIP_TYPES = new Set<string>([\"date\", \"datetime\", \"table\", \"embed\", \"image\", \"markdown\"]);\nconst MAX_CHIP_FIELDS = 3;\n\n/** A few scalar field values to show under a chip's title — excludes the label\n * (already the title), the primary key, the date/time fields that position the\n * record, and non-scalar field types. */\nfunction secondaryFieldsOf(item: CollectionItem): string[] {\n const out: string[] = [];\n for (const [key, field] of Object.entries(props.schema.fields)) {\n if (out.length >= MAX_CHIP_FIELDS) break;\n if (key === props.schema.primaryKey || key === labelField.value) continue;\n if (key === props.anchorField || key === props.endField || key === props.timeField) continue;\n if (CHIP_SKIP_TYPES.has(field.type)) continue;\n const value = item[key];\n if (value === undefined || value === null || typeof value === \"object\") continue;\n const text = String(value);\n if (text.length > 0) out.push(text);\n }\n return out;\n}\n\ninterface DayEntry {\n id: string;\n label: string;\n secondary: string[];\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n slice: DaySlice;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\n// Every record whose span covers this day, projected onto it.\nconst dayEntries = computed<DayEntry[]>(() => {\n const { spans } = bucketRecords(props.items, props.anchorField, props.endField, props.timeField);\n const entries: DayEntry[] = [];\n for (const span of spans) {\n const slice = daySlice(span, props.day);\n if (!slice) continue;\n entries.push({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n secondary: secondaryFieldsOf(span.item),\n color: colorOf(span.item),\n slice,\n });\n }\n return entries;\n});\n\nconst allDayEntries = computed<DayEntry[]>(() => dayEntries.value.filter((entry) => entry.slice.kind === \"allDay\"));\n\ninterface TimedEntry extends DayEntry {\n style: Record<string, string>;\n}\n\nconst timedEntries = computed<TimedEntry[]>(() => {\n const timed = dayEntries.value.filter((entry) => entry.slice.kind !== \"allDay\");\n const lanes = assignLanes(\n timed.map((entry) => ({ startMin: entry.slice.startMin, endMin: Math.max(entry.slice.endMin, entry.slice.startMin + LANE_MIN_MINUTES) })),\n );\n return timed.map((entry, index) => {\n const { lane, lanes: laneCount } = lanes[index];\n const widthPct = 100 / laneCount;\n const heightPx = entry.slice.kind === \"line\" ? LINE_PX : Math.max((entry.slice.endMin - entry.slice.startMin) * PX_PER_MIN, MIN_BLOCK_PX);\n return {\n ...entry,\n style: {\n top: `${entry.slice.startMin * PX_PER_MIN}px`,\n height: `${heightPx}px`,\n left: `${lane * widthPct}%`,\n width: `calc(${widthPct}% - 3px)`,\n },\n };\n });\n});\n\n// Chip styling. The selected chip keeps the solid indigo highlight; otherwise\n// a record with a resolved colour tints the chip (palette badge + border), and\n// one with none (no colour field) falls back to the kind's default — indigo on\n// the timeline, slate in the all-day strip. Mirrors the month view's\n// `chipClass` so the two surfaces colour records identically.\nconst TIMED_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100\";\nconst ALL_DAY_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\nfunction timedChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600 z-10\";\n if (!entry.color) return TIMED_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nfunction allDayChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return ALL_DAY_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\n// Select a record: report it to the host (which shows it in the right pane).\n// Unlike before, the modal stays open so the timeline and detail sit\n// side-by-side and the user can hop between records.\nfunction onSelect(itemId: string): void {\n emit(\"select\", itemId);\n}\n\n// Start a create for this day. The popup stays open so the new-item form\n// renders in the right pane (like the open/edit detail) — closing here would\n// drop the form to the panel below the grid.\nfunction onCreate(): void {\n emit(\"createOn\", dayKey.value);\n}\n\n// On open: move focus into the dialog (so Escape/Tab act on the modal, not the\n// background day cell), then auto-scroll the timeline to the earliest timed\n// event (less one hour of lead-in) so an afternoon-heavy day doesn't open on\n// an empty morning.\nonMounted(async () => {\n await nextTick();\n dialogEl.value?.focus();\n const earliest = timedEntries.value.reduce((min, entry) => Math.min(min, entry.slice.startMin), MINUTES_PER_DAY);\n if (earliest >= MINUTES_PER_DAY) return;\n if (scrollEl.value) scrollEl.value.scrollTop = Math.max(0, (earliest - 60) * PX_PER_MIN);\n});\n</script>\n","<template>\n <!-- Modal overlay: a time-allocation view of one day. Backdrop click and\n Escape close it. Selecting a record expands the modal to two columns —\n the timeline on the left, the record's detail (the `#detail` slot) on\n the right. -->\n <div\n class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\"\n data-testid=\"collection-day-view\"\n @click.self=\"emit('close')\"\n @keydown.esc=\"emit('close')\"\n >\n <div\n ref=\"dialogEl\"\n tabindex=\"-1\"\n class=\"flex max-h-[85vh] w-full flex-row rounded-2xl bg-white shadow-xl focus:outline-none\"\n :class=\"showDetail ? 'max-w-4xl' : 'max-w-md'\"\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <!-- Left column: the time-allocation timeline. Shrinks to a fixed width\n when a record detail is shown alongside it, else fills the modal. -->\n <div class=\"flex min-h-0 flex-col\" :class=\"showDetail ? 'w-80 shrink-0 border-r border-slate-200' : 'w-full'\">\n <!-- Header -->\n <div class=\"flex items-center gap-2 border-b border-slate-200 px-4 py-3\">\n <h3 class=\"flex-1 text-sm font-bold text-slate-800\" data-testid=\"collection-day-view-title\">{{ dayLabel }}</h3>\n <button\n v-if=\"canCreate\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarCreateOn', { date: dayKey })\"\n data-testid=\"collection-day-view-create\"\n @click=\"onCreate\"\n >\n <span class=\"material-icons text-lg\">add</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.dayViewClose')\"\n data-testid=\"collection-day-view-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n\n <!-- Empty state -->\n <div v-if=\"timedEntries.length === 0 && allDayEntries.length === 0\" class=\"px-4 py-10 text-center text-sm text-slate-400\">\n {{ t(\"collectionsView.dayViewEmpty\") }}\n </div>\n\n <!-- Timeline -->\n <div v-else ref=\"scrollEl\" class=\"flex-1 overflow-y-auto px-2 py-2\">\n <div class=\"relative\" :style=\"{ height: `${TOTAL_HEIGHT}px` }\" data-testid=\"collection-day-view-timeline\">\n <!-- Hour gridlines + labels -->\n <div v-for=\"hour in 24\" :key=\"hour\" class=\"absolute left-0 right-0 border-t border-slate-100\" :style=\"{ top: `${(hour - 1) * HOUR_PX}px` }\">\n <span class=\"absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400\">{{ hourLabel(hour - 1) }}</span>\n </div>\n\n <!-- Event track (right of the hour gutter) -->\n <div class=\"absolute inset-y-0 right-0\" style=\"left: 2.75rem\">\n <button\n v-for=\"entry in timedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"absolute overflow-hidden rounded border px-1.5 py-0.5 text-left transition-colors\"\n :class=\"timedChipClass(entry)\"\n :style=\"entry.style\"\n :data-testid=\"`collection-day-view-chip-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n <span class=\"block truncate text-[11px] font-semibold leading-tight\">\n <span v-if=\"entry.slice.bleedsBefore\" aria-hidden=\"true\">▲ </span>{{ entry.label\n }}<span v-if=\"entry.slice.bleedsAfter\" aria-hidden=\"true\"> ▼</span>\n </span>\n <!-- A few non-date/time fields under the title. The chip's height\n stays proportional to its duration; extra lines just clip. -->\n <span v-for=\"(text, i) in entry.secondary\" :key=\"i\" class=\"block truncate text-[10px] leading-tight opacity-70\">{{ text }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- All-day strip (records with no clock) at the bottom -->\n <div\n v-if=\"allDayEntries.length > 0\"\n class=\"flex flex-wrap items-center gap-1.5 border-t border-slate-200 px-4 py-2\"\n data-testid=\"collection-day-view-all-day\"\n >\n <span class=\"mr-1 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.dayViewAllDay\") }}</span>\n <button\n v-for=\"entry in allDayEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"truncate rounded border px-1.5 py-0.5 text-[11px] font-semibold transition-colors\"\n :class=\"allDayChipClass(entry)\"\n :data-testid=\"`collection-day-view-allday-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Right column: the selected (or being-created) record's detail panel,\n supplied by the host so selection no longer hands off to a panel\n below the calendar. -->\n <div v-if=\"showDetail\" class=\"min-w-0 flex-1 overflow-y-auto\" data-testid=\"collection-day-view-detail\">\n <slot name=\"detail\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport { bucketRecords, daySlice, assignLanes, ymdKey, MINUTES_PER_DAY, type Ymd, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n day: Ymd;\n anchorField: string;\n endField?: string;\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour\n * (matching the month view). Empty / unset → default indigo/slate styling. */\n colorField?: string;\n selected?: string;\n canCreate: boolean;\n /** When true, expand the modal to two columns and render the `#detail`\n * slot (the selected/created record) to the right of the timeline. */\n showDetail?: boolean;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n createOn: [iso: string];\n close: [];\n}>();\n\nconst { t, locale } = useI18n();\n\n// One hour = 48px tall; the full day is 24 of them. A point-in-time event\n// (start, no end) has no duration to size by, so it gets a fixed one-line-tall\n// box (`LINE_PX`) — enough to read its time + label — and a `LANE_MIN_MINUTES`\n// footprint so two near-simultaneous events still split into lanes.\nconst HOUR_PX = 48;\nconst TOTAL_HEIGHT = HOUR_PX * 24;\nconst PX_PER_MIN = HOUR_PX / 60;\nconst MIN_BLOCK_PX = 16;\nconst LINE_PX = 20;\nconst LANE_MIN_MINUTES = 30;\n\nconst scrollEl = ref<HTMLElement | null>(null);\nconst dialogEl = ref<HTMLElement | null>(null);\n\nconst dayKey = computed<string>(() => ymdKey(props.day));\n\nconst dayLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(props.day.year, props.day.month - 1, props.day.day)),\n );\n } catch {\n return dayKey.value;\n }\n});\n\nfunction hourLabel(hour: number): string {\n return `${String(hour).padStart(2, \"0\")}:00`;\n}\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\n// Field types with no compact inline representation for a chip subtitle.\nconst CHIP_SKIP_TYPES = new Set<string>([\"date\", \"datetime\", \"table\", \"embed\", \"image\", \"markdown\"]);\nconst MAX_CHIP_FIELDS = 3;\n\n/** A few scalar field values to show under a chip's title — excludes the label\n * (already the title), the primary key, the date/time fields that position the\n * record, and non-scalar field types. */\nfunction secondaryFieldsOf(item: CollectionItem): string[] {\n const out: string[] = [];\n for (const [key, field] of Object.entries(props.schema.fields)) {\n if (out.length >= MAX_CHIP_FIELDS) break;\n if (key === props.schema.primaryKey || key === labelField.value) continue;\n if (key === props.anchorField || key === props.endField || key === props.timeField) continue;\n if (CHIP_SKIP_TYPES.has(field.type)) continue;\n const value = item[key];\n if (value === undefined || value === null || typeof value === \"object\") continue;\n const text = String(value);\n if (text.length > 0) out.push(text);\n }\n return out;\n}\n\ninterface DayEntry {\n id: string;\n label: string;\n secondary: string[];\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n slice: DaySlice;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\n// Every record whose span covers this day, projected onto it.\nconst dayEntries = computed<DayEntry[]>(() => {\n const { spans } = bucketRecords(props.items, props.anchorField, props.endField, props.timeField);\n const entries: DayEntry[] = [];\n for (const span of spans) {\n const slice = daySlice(span, props.day);\n if (!slice) continue;\n entries.push({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n secondary: secondaryFieldsOf(span.item),\n color: colorOf(span.item),\n slice,\n });\n }\n return entries;\n});\n\nconst allDayEntries = computed<DayEntry[]>(() => dayEntries.value.filter((entry) => entry.slice.kind === \"allDay\"));\n\ninterface TimedEntry extends DayEntry {\n style: Record<string, string>;\n}\n\nconst timedEntries = computed<TimedEntry[]>(() => {\n const timed = dayEntries.value.filter((entry) => entry.slice.kind !== \"allDay\");\n const lanes = assignLanes(\n timed.map((entry) => ({ startMin: entry.slice.startMin, endMin: Math.max(entry.slice.endMin, entry.slice.startMin + LANE_MIN_MINUTES) })),\n );\n return timed.map((entry, index) => {\n const { lane, lanes: laneCount } = lanes[index];\n const widthPct = 100 / laneCount;\n const heightPx = entry.slice.kind === \"line\" ? LINE_PX : Math.max((entry.slice.endMin - entry.slice.startMin) * PX_PER_MIN, MIN_BLOCK_PX);\n return {\n ...entry,\n style: {\n top: `${entry.slice.startMin * PX_PER_MIN}px`,\n height: `${heightPx}px`,\n left: `${lane * widthPct}%`,\n width: `calc(${widthPct}% - 3px)`,\n },\n };\n });\n});\n\n// Chip styling. The selected chip keeps the solid indigo highlight; otherwise\n// a record with a resolved colour tints the chip (palette badge + border), and\n// one with none (no colour field) falls back to the kind's default — indigo on\n// the timeline, slate in the all-day strip. Mirrors the month view's\n// `chipClass` so the two surfaces colour records identically.\nconst TIMED_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100\";\nconst ALL_DAY_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\nfunction timedChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600 z-10\";\n if (!entry.color) return TIMED_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nfunction allDayChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return ALL_DAY_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\n// Select a record: report it to the host (which shows it in the right pane).\n// Unlike before, the modal stays open so the timeline and detail sit\n// side-by-side and the user can hop between records.\nfunction onSelect(itemId: string): void {\n emit(\"select\", itemId);\n}\n\n// Start a create for this day. The popup stays open so the new-item form\n// renders in the right pane (like the open/edit detail) — closing here would\n// drop the form to the panel below the grid.\nfunction onCreate(): void {\n emit(\"createOn\", dayKey.value);\n}\n\n// On open: move focus into the dialog (so Escape/Tab act on the modal, not the\n// background day cell), then auto-scroll the timeline to the earliest timed\n// event (less one hour of lead-in) so an afternoon-heavy day doesn't open on\n// an empty morning.\nonMounted(async () => {\n await nextTick();\n dialogEl.value?.focus();\n const earliest = timedEntries.value.reduce((min, entry) => Math.min(min, entry.slice.startMin), MINUTES_PER_DAY);\n if (earliest >= MINUTES_PER_DAY) return;\n if (scrollEl.value) scrollEl.value.scrollTop = Math.max(0, (earliest - 60) * PX_PER_MIN);\n});\n</script>\n","<template>\n <div class=\"h-full overflow-x-auto overflow-y-hidden\" data-testid=\"collection-kanban\">\n <div class=\"flex gap-3 h-full p-1 min-w-max\">\n <div\n v-for=\"column in columns\"\n :key=\"column.value\"\n :data-testid=\"`collection-kanban-column-${column.value || 'uncategorized'}`\"\n class=\"w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg\"\n >\n <!-- Column header (columns are NOT draggable: order is fixed by the\n enum's declared `values`). -->\n <div class=\"flex items-center justify-between px-3 py-2 border-b border-slate-200\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <span class=\"w-2 h-2 rounded-full shrink-0\" :class=\"resolveEnumColor(schema, groupField, column.value).dot\" />\n <span class=\"font-semibold text-xs text-slate-600 truncate\" :title=\"column.label\">{{ column.label }}</span>\n </div>\n <span class=\"text-[11px] text-slate-400 shrink-0\">{{ itemsByColumn(column.value).length }}</span>\n </div>\n\n <!-- Cards. Dragging a card to another column writes the group field\n (no manual ordering within a column). -->\n <draggable\n :model-value=\"itemsByColumn(column.value)\"\n :item-key=\"schema.primaryKey\"\n group=\"collection-kanban-cards\"\n class=\"flex-1 overflow-y-auto p-2 space-y-2 min-h-[2rem]\"\n :animation=\"150\"\n @change=\"(e: DragChangeEvent) => onDragChange(column.value, e)\"\n >\n <template #item=\"{ element }: { element: CollectionItem }\">\n <div\n :data-testid=\"`collection-kanban-card-${itemId(element)}`\"\n tabindex=\"0\"\n role=\"button\"\n :aria-label=\"t('collectionsView.kanbanOpenCard', { label: itemLabel(element) })\"\n class=\"bg-white border border-slate-200 rounded shadow-sm p-2 cursor-grab hover:shadow active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400\"\n :class=\"[itemId(element) === selected ? 'ring-2 ring-indigo-500 border-indigo-300' : '', notifyAccentClass(element)]\"\n @click=\"emit('select', itemId(element))\"\n @keydown.enter.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n @keydown.space.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n >\n <div class=\"flex items-start gap-2\">\n <!-- Toggle checkbox (when the schema has a toggle projecting\n this board's group field). Checking it sets the group\n field, so the card also moves columns. -->\n <input\n v-if=\"cardToggle\"\n type=\"checkbox\"\n :checked=\"cardChecked(element)\"\n class=\"mt-0.5 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer shrink-0\"\n :aria-label=\"cardToggle.label\"\n :data-testid=\"`collection-kanban-toggle-${itemId(element)}`\"\n @click.stop\n @change=\"onCardToggle(element)\"\n />\n <div class=\"text-sm font-medium text-slate-800 truncate\">{{ itemLabel(element) }}</div>\n </div>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport draggable from \"vuedraggable\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport type { CollectionNotifySeverity as NotifierSeverity } from \"../../core/uiTypes\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\n// vuedraggable @change shape — same three keys as the todo board. We act\n// only on \"added\" (the destination column): a cross-column move emits a\n// paired \"removed\" on the source, and \"moved\" is a within-column reorder\n// we deliberately ignore (no manual ordering).\ninterface DragChangeEvent {\n added?: { newIndex: number; element: CollectionItem };\n moved?: { newIndex: number; oldIndex: number; element: CollectionItem };\n removed?: { oldIndex: number; element: CollectionItem };\n}\n\nconst props = defineProps<{\n schema: CollectionSchema;\n /** The `enum` field whose value groups records into columns. */\n groupField: string;\n items: CollectionItem[];\n /** Primary-key of the currently-open record (highlighted card). */\n selected?: string;\n /** Primary-key → active-notification severity. Cards with a notification get\n * a left accent in the matching bell colour (urgent red / nudge amber). */\n notified?: Map<string, NotifierSeverity>;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** Card dropped in a column: set the group field to `value` (the empty\n * string means the Uncategorized column → clear the field). */\n move: [id: string, value: string];\n}>();\n\nconst { t } = useI18n();\n\n/** The Uncategorized column uses the empty string as its sentinel value. */\nconst UNCATEGORIZED = \"\";\n\ninterface KanbanColumn {\n value: string;\n label: string;\n}\n\nconst groupSpec = computed(() => props.schema.fields[props.groupField]);\n\n/** Declared enum values become columns in order, with a trailing\n * Uncategorized column for empty/unknown values (also a drop target that\n * clears the field). The Uncategorized column is omitted when the group\n * field is `required` — there's no valid \"no value\" state to drop into,\n * and clearing via it would only produce a rejected PUT. */\nconst columns = computed<KanbanColumn[]>(() => {\n const values = groupSpec.value?.values ?? [];\n const declared = values.map((value) => ({ value, label: value }));\n // Skip the trailing Uncategorized column when the group field is\n // `required` (no valid \"no value\" state), or when the enum already\n // declares an empty-string value (it would collide with the\n // Uncategorized sentinel's `value`/`:key`).\n if (groupSpec.value?.required || values.includes(UNCATEGORIZED)) return declared;\n return [...declared, { value: UNCATEGORIZED, label: t(\"collectionsView.kanbanUncategorized\") }];\n});\n\nfunction itemId(item: CollectionItem): string {\n return String(item[props.schema.primaryKey] ?? \"\");\n}\n\n// Left-accent class per notification severity — the same red/amber the bell\n// uses (see NotificationBell's severity colours), so a flagged card matches\n// the badge. Empty string when the record has no active notification.\nconst NOTIFY_ACCENT: Record<NotifierSeverity, string> = {\n urgent: \"border-l-4 border-l-red-500\",\n nudge: \"border-l-4 border-l-amber-500\",\n info: \"border-l-4 border-l-slate-400\",\n};\n\nfunction notifyAccentClass(item: CollectionItem): string {\n const severity = props.notified?.get(itemId(item));\n return severity ? NOTIFY_ACCENT[severity] : \"\";\n}\n\n/** Card label: the schema's `displayField` value, else the primary key. */\nfunction itemLabel(item: CollectionItem): string {\n const field = props.schema.displayField;\n if (field) {\n const value = item[field];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return itemId(item);\n}\n\n/** Which column a record belongs to: its group value when that value is\n * one of the declared enum values, else Uncategorized. */\nfunction columnOf(item: CollectionItem): string {\n const raw = item[props.groupField];\n if (raw === undefined || raw === null || raw === \"\") return UNCATEGORIZED;\n const value = String(raw);\n return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;\n}\n\n// Records to place on the board. A record whose group field is hidden by a\n// `when` predicate is dropped entirely (its column membership is undefined\n// while hidden), per the Kanban spec.\nconst visibleItems = computed<CollectionItem[]>(() => (groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []));\n\nconst itemsByColumnMap = computed<Map<string, CollectionItem[]>>(() => {\n const map = new Map<string, CollectionItem[]>();\n for (const column of columns.value) map.set(column.value, []);\n for (const item of visibleItems.value) {\n const value = columnOf(item);\n (map.get(value) ?? map.get(UNCATEGORIZED))?.push(item);\n }\n return map;\n});\n\nfunction itemsByColumn(value: string): CollectionItem[] {\n return itemsByColumnMap.value.get(value) ?? [];\n}\n\nfunction onDragChange(columnValue: string, event: DragChangeEvent): void {\n if (event.added) emit(\"move\", itemId(event.added.element), columnValue);\n}\n\n// A `toggle` field that projects THIS board's group field — rendered as a\n// per-card checkbox. Checking it writes the group field (so the card also\n// changes column), reusing the same `move` event as a drag.\nconst cardToggle = computed(() => {\n for (const spec of Object.values(props.schema.fields)) {\n if (spec.type === \"toggle\" && spec.field === props.groupField) return spec;\n }\n return null;\n});\n\nfunction cardChecked(item: CollectionItem): boolean {\n const toggle = cardToggle.value;\n return toggle !== null && String(item[props.groupField] ?? \"\") === toggle.onValue;\n}\n\nfunction onCardToggle(item: CollectionItem): void {\n const toggle = cardToggle.value;\n if (!toggle) return;\n const next = cardChecked(item) ? toggle.offValue : toggle.onValue;\n if (next === undefined) return;\n emit(\"move\", itemId(item), next);\n}\n</script>\n","<template>\n <div class=\"h-full overflow-x-auto overflow-y-hidden\" data-testid=\"collection-kanban\">\n <div class=\"flex gap-3 h-full p-1 min-w-max\">\n <div\n v-for=\"column in columns\"\n :key=\"column.value\"\n :data-testid=\"`collection-kanban-column-${column.value || 'uncategorized'}`\"\n class=\"w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg\"\n >\n <!-- Column header (columns are NOT draggable: order is fixed by the\n enum's declared `values`). -->\n <div class=\"flex items-center justify-between px-3 py-2 border-b border-slate-200\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <span class=\"w-2 h-2 rounded-full shrink-0\" :class=\"resolveEnumColor(schema, groupField, column.value).dot\" />\n <span class=\"font-semibold text-xs text-slate-600 truncate\" :title=\"column.label\">{{ column.label }}</span>\n </div>\n <span class=\"text-[11px] text-slate-400 shrink-0\">{{ itemsByColumn(column.value).length }}</span>\n </div>\n\n <!-- Cards. Dragging a card to another column writes the group field\n (no manual ordering within a column). -->\n <draggable\n :model-value=\"itemsByColumn(column.value)\"\n :item-key=\"schema.primaryKey\"\n group=\"collection-kanban-cards\"\n class=\"flex-1 overflow-y-auto p-2 space-y-2 min-h-[2rem]\"\n :animation=\"150\"\n @change=\"(e: DragChangeEvent) => onDragChange(column.value, e)\"\n >\n <template #item=\"{ element }: { element: CollectionItem }\">\n <div\n :data-testid=\"`collection-kanban-card-${itemId(element)}`\"\n tabindex=\"0\"\n role=\"button\"\n :aria-label=\"t('collectionsView.kanbanOpenCard', { label: itemLabel(element) })\"\n class=\"bg-white border border-slate-200 rounded shadow-sm p-2 cursor-grab hover:shadow active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400\"\n :class=\"[itemId(element) === selected ? 'ring-2 ring-indigo-500 border-indigo-300' : '', notifyAccentClass(element)]\"\n @click=\"emit('select', itemId(element))\"\n @keydown.enter.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n @keydown.space.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n >\n <div class=\"flex items-start gap-2\">\n <!-- Toggle checkbox (when the schema has a toggle projecting\n this board's group field). Checking it sets the group\n field, so the card also moves columns. -->\n <input\n v-if=\"cardToggle\"\n type=\"checkbox\"\n :checked=\"cardChecked(element)\"\n class=\"mt-0.5 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer shrink-0\"\n :aria-label=\"cardToggle.label\"\n :data-testid=\"`collection-kanban-toggle-${itemId(element)}`\"\n @click.stop\n @change=\"onCardToggle(element)\"\n />\n <div class=\"text-sm font-medium text-slate-800 truncate\">{{ itemLabel(element) }}</div>\n </div>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport draggable from \"vuedraggable\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport type { CollectionNotifySeverity as NotifierSeverity } from \"../../core/uiTypes\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\n// vuedraggable @change shape — same three keys as the todo board. We act\n// only on \"added\" (the destination column): a cross-column move emits a\n// paired \"removed\" on the source, and \"moved\" is a within-column reorder\n// we deliberately ignore (no manual ordering).\ninterface DragChangeEvent {\n added?: { newIndex: number; element: CollectionItem };\n moved?: { newIndex: number; oldIndex: number; element: CollectionItem };\n removed?: { oldIndex: number; element: CollectionItem };\n}\n\nconst props = defineProps<{\n schema: CollectionSchema;\n /** The `enum` field whose value groups records into columns. */\n groupField: string;\n items: CollectionItem[];\n /** Primary-key of the currently-open record (highlighted card). */\n selected?: string;\n /** Primary-key → active-notification severity. Cards with a notification get\n * a left accent in the matching bell colour (urgent red / nudge amber). */\n notified?: Map<string, NotifierSeverity>;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** Card dropped in a column: set the group field to `value` (the empty\n * string means the Uncategorized column → clear the field). */\n move: [id: string, value: string];\n}>();\n\nconst { t } = useI18n();\n\n/** The Uncategorized column uses the empty string as its sentinel value. */\nconst UNCATEGORIZED = \"\";\n\ninterface KanbanColumn {\n value: string;\n label: string;\n}\n\nconst groupSpec = computed(() => props.schema.fields[props.groupField]);\n\n/** Declared enum values become columns in order, with a trailing\n * Uncategorized column for empty/unknown values (also a drop target that\n * clears the field). The Uncategorized column is omitted when the group\n * field is `required` — there's no valid \"no value\" state to drop into,\n * and clearing via it would only produce a rejected PUT. */\nconst columns = computed<KanbanColumn[]>(() => {\n const values = groupSpec.value?.values ?? [];\n const declared = values.map((value) => ({ value, label: value }));\n // Skip the trailing Uncategorized column when the group field is\n // `required` (no valid \"no value\" state), or when the enum already\n // declares an empty-string value (it would collide with the\n // Uncategorized sentinel's `value`/`:key`).\n if (groupSpec.value?.required || values.includes(UNCATEGORIZED)) return declared;\n return [...declared, { value: UNCATEGORIZED, label: t(\"collectionsView.kanbanUncategorized\") }];\n});\n\nfunction itemId(item: CollectionItem): string {\n return String(item[props.schema.primaryKey] ?? \"\");\n}\n\n// Left-accent class per notification severity — the same red/amber the bell\n// uses (see NotificationBell's severity colours), so a flagged card matches\n// the badge. Empty string when the record has no active notification.\nconst NOTIFY_ACCENT: Record<NotifierSeverity, string> = {\n urgent: \"border-l-4 border-l-red-500\",\n nudge: \"border-l-4 border-l-amber-500\",\n info: \"border-l-4 border-l-slate-400\",\n};\n\nfunction notifyAccentClass(item: CollectionItem): string {\n const severity = props.notified?.get(itemId(item));\n return severity ? NOTIFY_ACCENT[severity] : \"\";\n}\n\n/** Card label: the schema's `displayField` value, else the primary key. */\nfunction itemLabel(item: CollectionItem): string {\n const field = props.schema.displayField;\n if (field) {\n const value = item[field];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return itemId(item);\n}\n\n/** Which column a record belongs to: its group value when that value is\n * one of the declared enum values, else Uncategorized. */\nfunction columnOf(item: CollectionItem): string {\n const raw = item[props.groupField];\n if (raw === undefined || raw === null || raw === \"\") return UNCATEGORIZED;\n const value = String(raw);\n return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;\n}\n\n// Records to place on the board. A record whose group field is hidden by a\n// `when` predicate is dropped entirely (its column membership is undefined\n// while hidden), per the Kanban spec.\nconst visibleItems = computed<CollectionItem[]>(() => (groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []));\n\nconst itemsByColumnMap = computed<Map<string, CollectionItem[]>>(() => {\n const map = new Map<string, CollectionItem[]>();\n for (const column of columns.value) map.set(column.value, []);\n for (const item of visibleItems.value) {\n const value = columnOf(item);\n (map.get(value) ?? map.get(UNCATEGORIZED))?.push(item);\n }\n return map;\n});\n\nfunction itemsByColumn(value: string): CollectionItem[] {\n return itemsByColumnMap.value.get(value) ?? [];\n}\n\nfunction onDragChange(columnValue: string, event: DragChangeEvent): void {\n if (event.added) emit(\"move\", itemId(event.added.element), columnValue);\n}\n\n// A `toggle` field that projects THIS board's group field — rendered as a\n// per-card checkbox. Checking it writes the group field (so the card also\n// changes column), reusing the same `move` event as a drag.\nconst cardToggle = computed(() => {\n for (const spec of Object.values(props.schema.fields)) {\n if (spec.type === \"toggle\" && spec.field === props.groupField) return spec;\n }\n return null;\n});\n\nfunction cardChecked(item: CollectionItem): boolean {\n const toggle = cardToggle.value;\n return toggle !== null && String(item[props.groupField] ?? \"\") === toggle.onValue;\n}\n\nfunction onCardToggle(item: CollectionItem): void {\n const toggle = cardToggle.value;\n if (!toggle) return;\n const next = cardChecked(item) ? toggle.offValue : toggle.onValue;\n if (next === undefined) return;\n emit(\"move\", itemId(item), next);\n}\n</script>\n","<template>\n <!-- One record panel for both open (read-only) and edit/create. The\n layout is IDENTICAL across modes — same header skeleton, same field\n grid, same per-field cell geometry — and only the inner control of\n each cell swaps: a formatted value when viewing, an input when\n editing. The root is a <form> while editing (so the Save button\n submits) and a <div> when viewing. The host (modal / calendar day\n view) supplies the surrounding container. -->\n <component :is=\"editing ? 'form' : 'div'\" class=\"px-6 py-5 max-h-[60vh] overflow-y-auto\" :data-testid=\"rootTestid\" @submit.prevent=\"emit('submit')\">\n <!-- Header: title block (left) is identical in both modes; only the\n right-hand button cluster swaps (Cancel/Save ↔ actions/Edit/Delete/\n Close). Same height + margin so nothing shifts on toggle. -->\n <div class=\"flex items-center gap-2 mb-4\">\n <div class=\"flex-1 min-w-0\">\n <span class=\"block text-[9px] font-bold text-slate-400 uppercase tracking-wider\">{{ collection.title }}</span>\n <h2 class=\"text-base font-bold text-slate-800 truncate\" :data-testid=\"editing ? 'collections-edit-title' : 'collections-detail-title'\">\n {{ headerTitle }}\n </h2>\n </div>\n\n <!-- Edit/create actions -->\n <template v-if=\"editing\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-editor-cancel\"\n @click=\"emit('cancel')\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"submit\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"saving\"\n data-testid=\"collections-editor-save\"\n >\n {{ saving ? t(\"common.saving\") : t(\"common.save\") }}\n </button>\n </template>\n\n <!-- Read-only actions -->\n <div v-else class=\"flex items-center gap-2\">\n <button\n v-for=\"action in visibleActions\"\n :key=\"action.id\"\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-indigo-200 bg-indigo-50/50 text-indigo-600 hover:bg-indigo-600 hover:text-white hover:border-indigo-600 font-bold text-xs transition-all flex items-center gap-1 disabled:opacity-50\"\n :disabled=\"actionPending\"\n :data-testid=\"`collections-detail-action-${action.id}`\"\n @click=\"emit('runAction', action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-edit\"\n @click=\"emit('edit')\"\n >\n <span class=\"material-icons text-sm\">edit</span>\n <span>{{ t(\"collectionsView.editItem\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-remove\"\n @click=\"emit('delete')\"\n >\n <span class=\"material-icons text-sm\">delete</span>\n <span>{{ t(\"common.remove\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-detail-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n </div>\n\n <p\n v-if=\"!editing && actionError\"\n class=\"mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm\"\n data-testid=\"collections-detail-action-error\"\n >\n {{ actionError }}\n </p>\n\n <!-- Field grid: same columns + per-field cell in both modes. Each cell\n renders its edit control while editing (and the field is editable),\n else its read-only display. -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 bg-white rounded-2xl border border-slate-200/60 p-6 shadow-sm\">\n <template v-for=\"(field, key) in collection.schema.fields\" :key=\"key\">\n <div v-if=\"cellVisible(field)\" class=\"flex flex-col gap-1.5\" :class=\"colSpanClass(field)\">\n <label class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1\" :for=\"`collections-field-${key}`\">\n {{ field.label }}\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"*\" is a universal required-field glyph; treating it as i18n copy would force eight translations of the same symbol. -->\n <span v-if=\"editing && field.required\" class=\"text-rose-500 font-bold\">*</span>\n </label>\n\n <!-- ===== EDIT CONTROLS (editable field types only) ===== -->\n <template v-if=\"editing && isEditableType(field.type)\">\n <!-- Boolean checkbox -->\n <label v-if=\"field.type === 'boolean'\" class=\"inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none\">\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.bool[key]\"\n type=\"checkbox\"\n class=\"h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n :data-testid=\"`collections-input-${key}`\"\n @change=\"markBoolTouched(String(key))\"\n />\n <span class=\"text-xs font-semibold\" :class=\"editing.bool[key] ? 'text-indigo-600' : 'text-slate-500'\">\n {{ editing.bool[key] ? t(\"common.yes\") : t(\"common.no\") }}\n </span>\n </label>\n\n <!-- Ref selector -->\n <select\n v-else-if=\"field.type === 'ref' && field.to && render.refOptions(field.to).length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-50 hover:bg-slate-50/50 focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium text-slate-700\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(field.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n\n <!-- Enum selector -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border px-3 py-2 text-xs focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium\"\n :class=\"enumControlClass(String(key), editing.text[key])\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Nested Table editor -->\n <div\n v-else-if=\"field.type === 'table' && field.of\"\n class=\"border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3\"\n :data-testid=\"`collections-table-${key}`\"\n >\n <div v-if=\"editing.table[key] && editing.table[key].length > 0\" class=\"overflow-hidden border border-slate-200 rounded-lg shadow-sm\">\n <table class=\"w-full text-xs text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-3 py-2 font-bold\">{{ subField.label }}</th>\n <th class=\"w-9\"></th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in editing.table[key]\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-2 py-1.5 align-middle\">\n <input\n v-if=\"subField.type === 'boolean'\"\n v-model=\"row.bool[subKey]\"\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n @change=\"markRowBoolTouched(row, String(subKey))\"\n />\n <select\n v-else-if=\"subField.type === 'enum' && Array.isArray(subField.values) && subField.values.length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in subField.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n <select\n v-else-if=\"subField.type === 'ref' && subField.to && render.refOptions(subField.to).length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(subField.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n <div v-else-if=\"subField.type === 'money'\" class=\"relative flex items-center\">\n <span class=\"absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200\">{{\n render.currencySymbol(render.resolveCurrency(subField, liveRecord))\n }}</span>\n <input\n v-model=\"row.text[subKey]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 pl-6 pr-1.5 py-1 text-xs focus:border-indigo-500 focus:outline-none font-semibold text-slate-800\"\n />\n </div>\n <input\n v-else\n v-model=\"row.text[subKey]\"\n :type=\"render.inputTypeFor(subField.type)\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700\"\n />\n </td>\n <td class=\"text-center px-1\">\n <button\n type=\"button\"\n class=\"h-7 w-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 transition-colors\"\n :aria-label=\"t('collectionsView.removeRow')\"\n :data-testid=\"`collections-table-${key}-remove-${rowIdx}`\"\n @click=\"removeTableRow(String(key), rowIdx)\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <p v-else class=\"text-xs text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</p>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-table-${key}-add`\"\n @click=\"addTableRow(String(key), field.of)\"\n >\n <span class=\"material-icons text-xs\">add</span>\n <span>{{ t(\"collectionsView.addRow\") }}</span>\n </button>\n </div>\n\n <!-- Money input field -->\n <div v-else-if=\"field.type === 'money'\" class=\"relative flex items-center\">\n <div class=\"absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200\">\n {{ render.currencySymbol(render.resolveCurrency(field, liveRecord)) }}\n </div>\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 pl-11 pr-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-semibold text-slate-800 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </div>\n\n <!-- Scalar inputs -->\n <input\n v-else-if=\"['string', 'email', 'number', 'date', 'datetime', 'ref', 'image', 'file'].includes(field.type)\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :type=\"render.inputTypeFor(field.type)\"\n :required=\"isFieldRequiredInUi(field)\"\n :disabled=\"field.primary === true && (editing.mode === 'edit' || isSingleton)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n\n <!-- Markdown or long text -->\n <textarea\n v-else\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :rows=\"field.type === 'markdown' ? 5 : 3\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </template>\n\n <!-- ===== READ-ONLY DISPLAY (viewing, or non-editable types) ===== -->\n <div v-else class=\"text-xs font-medium text-slate-700 break-words\" :data-testid=\"`collections-detail-value-${key}`\">\n <!-- Toggle state (read-only reflection of the projected enum). -->\n <template v-if=\"field.type === 'toggle'\">\n <span\n v-if=\"field.field !== undefined && String(detailRecord[field.field] ?? '') === field.onValue\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n </template>\n\n <!-- Boolean state -->\n <template v-else-if=\"field.type === 'boolean'\">\n <span\n v-if=\"detailRecord[key] === true\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else-if=\"detailRecord[key] === false\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" for an omitted boolean: distinct from an explicit false. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n\n <!-- Ref details link -->\n <router-link\n v-else-if=\"field.type === 'ref' && field.to && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(detailRecord[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-detail-ref-${key}`\"\n >{{ render.refDisplay(field.to, String(detailRecord[key])) }}</router-link\n >\n\n <!-- Money format -->\n <span v-else-if=\"field.type === 'money'\" class=\"font-semibold text-slate-900 tabular-nums text-sm\">{{\n render.formatMoney(detailRecord[key], render.resolveCurrency(field, detailRecord), locale)\n }}</span>\n\n <!-- Derived formula badge -->\n <span\n v-else-if=\"field.type === 'derived'\"\n class=\"inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-2 py-0.5 rounded border border-indigo-100/50\"\n >{{ render.derivedDisplay(field, render.evaluateDerivedAgainstItem(field, String(key), detailRecord), detailRecord) }}</span\n >\n\n <!-- Sub table -->\n <div\n v-else-if=\"field.type === 'table' && field.of && render.hasTableRows(detailRecord[key])\"\n class=\"border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1\"\n >\n <table class=\"w-full text-[11px] text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-4 py-2 font-bold\">{{ subField.label }}</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in render.tableRows(detailRecord[key])\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-4 py-2 align-middle font-medium\">\n <template v-if=\"subField.type === 'boolean'\">\n <span v-if=\"row[subKey] === true\" class=\"material-icons text-emerald-600 text-base\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph (boolean=false), same as elsewhere. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <span v-else :class=\"[subField.type === 'money' ? 'font-bold text-slate-800 tabular-nums' : '']\">{{\n render.formatSubCell(subField, row[subKey], detailRecord)\n }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <span v-else-if=\"field.type === 'table'\" class=\"text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</span>\n\n <!-- Markdown blocks with scroll area -->\n <div\n v-else-if=\"field.type === 'markdown'\"\n class=\"bg-slate-50 rounded-xl p-4 border border-slate-200/60 text-slate-600 text-xs whitespace-pre-wrap leading-relaxed max-h-[30vh] overflow-y-auto\"\n >\n {{ render.detailText(detailRecord[key]) }}\n </div>\n\n <!-- Embed view -->\n <CollectionEmbedView v-else-if=\"field.type === 'embed' && embedViews[key]\" :view=\"embedViews[key]\" :field-key=\"String(key)\" />\n\n <!-- Image (workspace-relative path → <img> via auth-exempt /api/files/raw) -->\n <img\n v-else-if=\"field.type === 'image' && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :src=\"resolveImageSrc(String(detailRecord[key]))\"\n :alt=\"field.label\"\n class=\"max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50\"\n :data-testid=\"`collections-detail-image-${key}`\"\n />\n\n <!-- URL string → external link (new tab). -->\n <a\n v-else-if=\"field.type !== 'file' && render.isExternalUrl(detailRecord[key])\"\n :href=\"String(detailRecord[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-url-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered app in a new tab. -->\n <a\n v-else-if=\"field.type === 'file' && render.artifactUrl(detailRecord[key])\"\n :href=\"render.artifactUrl(detailRecord[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && render.fileRoutePath(detailRecord[key])\"\n :to=\"render.fileRoutePath(detailRecord[key]) ?? ''\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</router-link\n >\n\n <!-- Fallback text styling -->\n <span v-else class=\"text-slate-800 font-semibold\">{{ render.formatCell(detailRecord[key], field.type) }}</span>\n </div>\n </div>\n </template>\n\n <p v-if=\"editing && saveError\" class=\"col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl\">\n {{ saveError }}\n </p>\n </div>\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport CollectionEmbedView from \"./CollectionEmbedView.vue\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { emptyRow } from \"../../core/draft\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionRendering } from \"../useCollectionRendering\";\nimport type { CollectionAction, CollectionDetail, CollectionItem, CollectionFieldSpec as FieldSpec } from \"../../core/schema\";\nimport type { EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n// `resolveImageSrc` lives host-side (it points at the host's raw-file endpoint);\n// the view layer reaches it through the UI binding. Kept under the same local\n// name so the template's `:src` binding is unchanged.\nconst resolveImageSrc = collectionUi().imageSrc;\n\nconst props = defineProps<{\n collection: CollectionDetail;\n /** Open record in read-only mode, or null. */\n viewing: CollectionItem | null;\n saving: boolean;\n saveError: string | null;\n actionError: string | null;\n actionPending: boolean;\n visibleActions: CollectionAction[];\n /** Live record computed from the draft (drives derived previews). */\n liveRecord: CollectionItem | null;\n /** Live record with derived fields resolved. */\n liveDerived: CollectionItem | null;\n viewTitle: string;\n isSingleton: boolean;\n /** Shared rendering/derivation helpers + ref/embed caches. */\n render: CollectionRendering;\n locale: string;\n}>();\n\n// The edit/create draft is a two-way model: the form's v-model bindings and\n// the table-row mutators write into its nested reactive state (the parent\n// owns the object identity, so in-place edits flow straight back). A model\n// rather than a prop so `vue/no-mutating-props` doesn't fire on the form.\nconst editing = defineModel<EditState | null>(\"editing\", { required: true });\n\nconst emit = defineEmits<{\n submit: [];\n cancel: [];\n edit: [];\n close: [];\n delete: [];\n runAction: [action: CollectionAction];\n}>();\n\nconst { t } = useI18n();\n\n// `embedViews` is a ComputedRef nested in the `render` object, so it isn't\n// auto-unwrapped in the template — re-expose it as a top-level computed.\nconst embedViews = computed(() => props.render.embedViews.value);\n\n/** The record the read-only displays render from: the live draft while\n * editing (so non-editable cells like derived/embed preview the in-flight\n * values), else the open record. Always an object so `[key]` lookups are\n * safe in the template. */\nconst detailRecord = computed<CollectionItem>(() => (editing.value ? (props.liveDerived ?? props.liveRecord ?? {}) : (props.viewing ?? {})));\n\n/** Title for the header: the create label, the edited record's id, or the\n * open record's title — same h2 slot in every mode. */\nconst headerTitle = computed<string>(() => {\n if (editing.value) return editing.value.mode === \"create\" ? t(\"collectionsView.createTitle\") : (editing.value.originalId ?? \"\");\n return props.viewTitle;\n});\n\nconst rootTestid = computed<string>(() => {\n if (!editing.value) return \"collections-detail\";\n return editing.value.mode === \"create\" ? \"collections-create\" : \"collections-edit\";\n});\n\n/** Whether a field gets an editable control in edit mode. Toggle (a\n * projection of an enum that has its own input), derived (computed), and\n * embed (a foreign record) stay read-only in both modes, so the cell\n * geometry never changes on the view↔edit toggle. */\nfunction isEditableType(type: FieldSpec[\"type\"]): boolean {\n return type !== \"toggle\" && type !== \"derived\" && type !== \"embed\";\n}\n\n/** Wide field types span the full grid width in BOTH modes — keeping\n * `image` full-width here (not just when viewing) is what stops a field\n * from jumping columns when editing starts. */\nfunction colSpanClass(field: FieldSpec): \"col-span-full\" | \"col-span-1\" {\n return [\"table\", \"markdown\", \"embed\", \"image\"].includes(field.type) ? \"col-span-full\" : \"col-span-1\";\n}\n\n/** Whether to render a field's cell. Identical rule in both modes so no\n * cell appears or disappears on toggle: respect the `when` predicate\n * (against the active record) and hide the primary key except while\n * creating. */\nfunction cellVisible(field: FieldSpec): boolean {\n if (field.primary && editing.value?.mode !== \"create\") return false;\n return fieldVisible(field, detailRecord.value);\n}\n\n/** Mirror of the create-mode primary-key carve-out: drop the HTML5\n * `required` flag on the primary field while creating so the browser\n * doesn't block an intentionally-blank primary (server generates the id). */\nfunction isFieldRequiredInUi(field: FieldSpec): boolean {\n if (!field.required) return false;\n if (editing.value?.mode === \"create\" && field.primary === true) return false;\n return true;\n}\n\n/** Tailwind fill/text/border classes tinting an enum `<select>` by its current\n * value's colour (palette, or notification red/amber/grey when the field is\n * the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const cls = resolveEnumColor(props.collection.schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n// The edit-draft mutators write the model's nested reactive state — the same\n// object the form's v-model bindings mutate, so no parent round-trip needed.\nfunction markBoolTouched(key: string): void {\n if (editing.value) editing.value.boolTouched[key] = true;\n}\n\nfunction markRowBoolTouched(row: TableRowDraft, subKey: string): void {\n row.boolTouched[subKey] = true;\n}\n\nfunction addTableRow(key: string, subFields: Record<string, FieldSpec>): void {\n if (!editing.value) return;\n const rows = editing.value.table[key] ?? [];\n rows.push(emptyRow(subFields));\n editing.value.table[key] = rows;\n}\n\nfunction removeTableRow(key: string, index: number): void {\n if (!editing.value) return;\n const rows = editing.value.table[key];\n if (rows) rows.splice(index, 1);\n}\n</script>\n","<template>\n <!-- One record panel for both open (read-only) and edit/create. The\n layout is IDENTICAL across modes — same header skeleton, same field\n grid, same per-field cell geometry — and only the inner control of\n each cell swaps: a formatted value when viewing, an input when\n editing. The root is a <form> while editing (so the Save button\n submits) and a <div> when viewing. The host (modal / calendar day\n view) supplies the surrounding container. -->\n <component :is=\"editing ? 'form' : 'div'\" class=\"px-6 py-5 max-h-[60vh] overflow-y-auto\" :data-testid=\"rootTestid\" @submit.prevent=\"emit('submit')\">\n <!-- Header: title block (left) is identical in both modes; only the\n right-hand button cluster swaps (Cancel/Save ↔ actions/Edit/Delete/\n Close). Same height + margin so nothing shifts on toggle. -->\n <div class=\"flex items-center gap-2 mb-4\">\n <div class=\"flex-1 min-w-0\">\n <span class=\"block text-[9px] font-bold text-slate-400 uppercase tracking-wider\">{{ collection.title }}</span>\n <h2 class=\"text-base font-bold text-slate-800 truncate\" :data-testid=\"editing ? 'collections-edit-title' : 'collections-detail-title'\">\n {{ headerTitle }}\n </h2>\n </div>\n\n <!-- Edit/create actions -->\n <template v-if=\"editing\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-editor-cancel\"\n @click=\"emit('cancel')\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"submit\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"saving\"\n data-testid=\"collections-editor-save\"\n >\n {{ saving ? t(\"common.saving\") : t(\"common.save\") }}\n </button>\n </template>\n\n <!-- Read-only actions -->\n <div v-else class=\"flex items-center gap-2\">\n <button\n v-for=\"action in visibleActions\"\n :key=\"action.id\"\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-indigo-200 bg-indigo-50/50 text-indigo-600 hover:bg-indigo-600 hover:text-white hover:border-indigo-600 font-bold text-xs transition-all flex items-center gap-1 disabled:opacity-50\"\n :disabled=\"actionPending\"\n :data-testid=\"`collections-detail-action-${action.id}`\"\n @click=\"emit('runAction', action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-edit\"\n @click=\"emit('edit')\"\n >\n <span class=\"material-icons text-sm\">edit</span>\n <span>{{ t(\"collectionsView.editItem\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-remove\"\n @click=\"emit('delete')\"\n >\n <span class=\"material-icons text-sm\">delete</span>\n <span>{{ t(\"common.remove\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-detail-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n </div>\n\n <p\n v-if=\"!editing && actionError\"\n class=\"mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm\"\n data-testid=\"collections-detail-action-error\"\n >\n {{ actionError }}\n </p>\n\n <!-- Field grid: same columns + per-field cell in both modes. Each cell\n renders its edit control while editing (and the field is editable),\n else its read-only display. -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 bg-white rounded-2xl border border-slate-200/60 p-6 shadow-sm\">\n <template v-for=\"(field, key) in collection.schema.fields\" :key=\"key\">\n <div v-if=\"cellVisible(field)\" class=\"flex flex-col gap-1.5\" :class=\"colSpanClass(field)\">\n <label class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1\" :for=\"`collections-field-${key}`\">\n {{ field.label }}\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"*\" is a universal required-field glyph; treating it as i18n copy would force eight translations of the same symbol. -->\n <span v-if=\"editing && field.required\" class=\"text-rose-500 font-bold\">*</span>\n </label>\n\n <!-- ===== EDIT CONTROLS (editable field types only) ===== -->\n <template v-if=\"editing && isEditableType(field.type)\">\n <!-- Boolean checkbox -->\n <label v-if=\"field.type === 'boolean'\" class=\"inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none\">\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.bool[key]\"\n type=\"checkbox\"\n class=\"h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n :data-testid=\"`collections-input-${key}`\"\n @change=\"markBoolTouched(String(key))\"\n />\n <span class=\"text-xs font-semibold\" :class=\"editing.bool[key] ? 'text-indigo-600' : 'text-slate-500'\">\n {{ editing.bool[key] ? t(\"common.yes\") : t(\"common.no\") }}\n </span>\n </label>\n\n <!-- Ref selector -->\n <select\n v-else-if=\"field.type === 'ref' && field.to && render.refOptions(field.to).length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-50 hover:bg-slate-50/50 focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium text-slate-700\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(field.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n\n <!-- Enum selector -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border px-3 py-2 text-xs focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium\"\n :class=\"enumControlClass(String(key), editing.text[key])\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Nested Table editor -->\n <div\n v-else-if=\"field.type === 'table' && field.of\"\n class=\"border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3\"\n :data-testid=\"`collections-table-${key}`\"\n >\n <div v-if=\"editing.table[key] && editing.table[key].length > 0\" class=\"overflow-hidden border border-slate-200 rounded-lg shadow-sm\">\n <table class=\"w-full text-xs text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-3 py-2 font-bold\">{{ subField.label }}</th>\n <th class=\"w-9\"></th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in editing.table[key]\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-2 py-1.5 align-middle\">\n <input\n v-if=\"subField.type === 'boolean'\"\n v-model=\"row.bool[subKey]\"\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n @change=\"markRowBoolTouched(row, String(subKey))\"\n />\n <select\n v-else-if=\"subField.type === 'enum' && Array.isArray(subField.values) && subField.values.length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in subField.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n <select\n v-else-if=\"subField.type === 'ref' && subField.to && render.refOptions(subField.to).length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(subField.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n <div v-else-if=\"subField.type === 'money'\" class=\"relative flex items-center\">\n <span class=\"absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200\">{{\n render.currencySymbol(render.resolveCurrency(subField, liveRecord))\n }}</span>\n <input\n v-model=\"row.text[subKey]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 pl-6 pr-1.5 py-1 text-xs focus:border-indigo-500 focus:outline-none font-semibold text-slate-800\"\n />\n </div>\n <input\n v-else\n v-model=\"row.text[subKey]\"\n :type=\"render.inputTypeFor(subField.type)\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700\"\n />\n </td>\n <td class=\"text-center px-1\">\n <button\n type=\"button\"\n class=\"h-7 w-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 transition-colors\"\n :aria-label=\"t('collectionsView.removeRow')\"\n :data-testid=\"`collections-table-${key}-remove-${rowIdx}`\"\n @click=\"removeTableRow(String(key), rowIdx)\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <p v-else class=\"text-xs text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</p>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-table-${key}-add`\"\n @click=\"addTableRow(String(key), field.of)\"\n >\n <span class=\"material-icons text-xs\">add</span>\n <span>{{ t(\"collectionsView.addRow\") }}</span>\n </button>\n </div>\n\n <!-- Money input field -->\n <div v-else-if=\"field.type === 'money'\" class=\"relative flex items-center\">\n <div class=\"absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200\">\n {{ render.currencySymbol(render.resolveCurrency(field, liveRecord)) }}\n </div>\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 pl-11 pr-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-semibold text-slate-800 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </div>\n\n <!-- Scalar inputs -->\n <input\n v-else-if=\"['string', 'email', 'number', 'date', 'datetime', 'ref', 'image', 'file'].includes(field.type)\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :type=\"render.inputTypeFor(field.type)\"\n :required=\"isFieldRequiredInUi(field)\"\n :disabled=\"field.primary === true && (editing.mode === 'edit' || isSingleton)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n\n <!-- Markdown or long text -->\n <textarea\n v-else\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :rows=\"field.type === 'markdown' ? 5 : 3\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </template>\n\n <!-- ===== READ-ONLY DISPLAY (viewing, or non-editable types) ===== -->\n <div v-else class=\"text-xs font-medium text-slate-700 break-words\" :data-testid=\"`collections-detail-value-${key}`\">\n <!-- Toggle state (read-only reflection of the projected enum). -->\n <template v-if=\"field.type === 'toggle'\">\n <span\n v-if=\"field.field !== undefined && String(detailRecord[field.field] ?? '') === field.onValue\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n </template>\n\n <!-- Boolean state -->\n <template v-else-if=\"field.type === 'boolean'\">\n <span\n v-if=\"detailRecord[key] === true\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else-if=\"detailRecord[key] === false\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" for an omitted boolean: distinct from an explicit false. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n\n <!-- Ref details link -->\n <router-link\n v-else-if=\"field.type === 'ref' && field.to && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(detailRecord[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-detail-ref-${key}`\"\n >{{ render.refDisplay(field.to, String(detailRecord[key])) }}</router-link\n >\n\n <!-- Money format -->\n <span v-else-if=\"field.type === 'money'\" class=\"font-semibold text-slate-900 tabular-nums text-sm\">{{\n render.formatMoney(detailRecord[key], render.resolveCurrency(field, detailRecord), locale)\n }}</span>\n\n <!-- Derived formula badge -->\n <span\n v-else-if=\"field.type === 'derived'\"\n class=\"inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-2 py-0.5 rounded border border-indigo-100/50\"\n >{{ render.derivedDisplay(field, render.evaluateDerivedAgainstItem(field, String(key), detailRecord), detailRecord) }}</span\n >\n\n <!-- Sub table -->\n <div\n v-else-if=\"field.type === 'table' && field.of && render.hasTableRows(detailRecord[key])\"\n class=\"border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1\"\n >\n <table class=\"w-full text-[11px] text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-4 py-2 font-bold\">{{ subField.label }}</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in render.tableRows(detailRecord[key])\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-4 py-2 align-middle font-medium\">\n <template v-if=\"subField.type === 'boolean'\">\n <span v-if=\"row[subKey] === true\" class=\"material-icons text-emerald-600 text-base\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph (boolean=false), same as elsewhere. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <span v-else :class=\"[subField.type === 'money' ? 'font-bold text-slate-800 tabular-nums' : '']\">{{\n render.formatSubCell(subField, row[subKey], detailRecord)\n }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <span v-else-if=\"field.type === 'table'\" class=\"text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</span>\n\n <!-- Markdown blocks with scroll area -->\n <div\n v-else-if=\"field.type === 'markdown'\"\n class=\"bg-slate-50 rounded-xl p-4 border border-slate-200/60 text-slate-600 text-xs whitespace-pre-wrap leading-relaxed max-h-[30vh] overflow-y-auto\"\n >\n {{ render.detailText(detailRecord[key]) }}\n </div>\n\n <!-- Embed view -->\n <CollectionEmbedView v-else-if=\"field.type === 'embed' && embedViews[key]\" :view=\"embedViews[key]\" :field-key=\"String(key)\" />\n\n <!-- Image (workspace-relative path → <img> via auth-exempt /api/files/raw) -->\n <img\n v-else-if=\"field.type === 'image' && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :src=\"resolveImageSrc(String(detailRecord[key]))\"\n :alt=\"field.label\"\n class=\"max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50\"\n :data-testid=\"`collections-detail-image-${key}`\"\n />\n\n <!-- URL string → external link (new tab). -->\n <a\n v-else-if=\"field.type !== 'file' && render.isExternalUrl(detailRecord[key])\"\n :href=\"String(detailRecord[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-url-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered app in a new tab. -->\n <a\n v-else-if=\"field.type === 'file' && render.artifactUrl(detailRecord[key])\"\n :href=\"render.artifactUrl(detailRecord[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && render.fileRoutePath(detailRecord[key])\"\n :to=\"render.fileRoutePath(detailRecord[key]) ?? ''\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</router-link\n >\n\n <!-- Fallback text styling -->\n <span v-else class=\"text-slate-800 font-semibold\">{{ render.formatCell(detailRecord[key], field.type) }}</span>\n </div>\n </div>\n </template>\n\n <p v-if=\"editing && saveError\" class=\"col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl\">\n {{ saveError }}\n </p>\n </div>\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useI18n } from \"vue-i18n\";\nimport CollectionEmbedView from \"./CollectionEmbedView.vue\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { emptyRow } from \"../../core/draft\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionRendering } from \"../useCollectionRendering\";\nimport type { CollectionAction, CollectionDetail, CollectionItem, CollectionFieldSpec as FieldSpec } from \"../../core/schema\";\nimport type { EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n// `resolveImageSrc` lives host-side (it points at the host's raw-file endpoint);\n// the view layer reaches it through the UI binding. Kept under the same local\n// name so the template's `:src` binding is unchanged.\nconst resolveImageSrc = collectionUi().imageSrc;\n\nconst props = defineProps<{\n collection: CollectionDetail;\n /** Open record in read-only mode, or null. */\n viewing: CollectionItem | null;\n saving: boolean;\n saveError: string | null;\n actionError: string | null;\n actionPending: boolean;\n visibleActions: CollectionAction[];\n /** Live record computed from the draft (drives derived previews). */\n liveRecord: CollectionItem | null;\n /** Live record with derived fields resolved. */\n liveDerived: CollectionItem | null;\n viewTitle: string;\n isSingleton: boolean;\n /** Shared rendering/derivation helpers + ref/embed caches. */\n render: CollectionRendering;\n locale: string;\n}>();\n\n// The edit/create draft is a two-way model: the form's v-model bindings and\n// the table-row mutators write into its nested reactive state (the parent\n// owns the object identity, so in-place edits flow straight back). A model\n// rather than a prop so `vue/no-mutating-props` doesn't fire on the form.\nconst editing = defineModel<EditState | null>(\"editing\", { required: true });\n\nconst emit = defineEmits<{\n submit: [];\n cancel: [];\n edit: [];\n close: [];\n delete: [];\n runAction: [action: CollectionAction];\n}>();\n\nconst { t } = useI18n();\n\n// `embedViews` is a ComputedRef nested in the `render` object, so it isn't\n// auto-unwrapped in the template — re-expose it as a top-level computed.\nconst embedViews = computed(() => props.render.embedViews.value);\n\n/** The record the read-only displays render from: the live draft while\n * editing (so non-editable cells like derived/embed preview the in-flight\n * values), else the open record. Always an object so `[key]` lookups are\n * safe in the template. */\nconst detailRecord = computed<CollectionItem>(() => (editing.value ? (props.liveDerived ?? props.liveRecord ?? {}) : (props.viewing ?? {})));\n\n/** Title for the header: the create label, the edited record's id, or the\n * open record's title — same h2 slot in every mode. */\nconst headerTitle = computed<string>(() => {\n if (editing.value) return editing.value.mode === \"create\" ? t(\"collectionsView.createTitle\") : (editing.value.originalId ?? \"\");\n return props.viewTitle;\n});\n\nconst rootTestid = computed<string>(() => {\n if (!editing.value) return \"collections-detail\";\n return editing.value.mode === \"create\" ? \"collections-create\" : \"collections-edit\";\n});\n\n/** Whether a field gets an editable control in edit mode. Toggle (a\n * projection of an enum that has its own input), derived (computed), and\n * embed (a foreign record) stay read-only in both modes, so the cell\n * geometry never changes on the view↔edit toggle. */\nfunction isEditableType(type: FieldSpec[\"type\"]): boolean {\n return type !== \"toggle\" && type !== \"derived\" && type !== \"embed\";\n}\n\n/** Wide field types span the full grid width in BOTH modes — keeping\n * `image` full-width here (not just when viewing) is what stops a field\n * from jumping columns when editing starts. */\nfunction colSpanClass(field: FieldSpec): \"col-span-full\" | \"col-span-1\" {\n return [\"table\", \"markdown\", \"embed\", \"image\"].includes(field.type) ? \"col-span-full\" : \"col-span-1\";\n}\n\n/** Whether to render a field's cell. Identical rule in both modes so no\n * cell appears or disappears on toggle: respect the `when` predicate\n * (against the active record) and hide the primary key except while\n * creating. */\nfunction cellVisible(field: FieldSpec): boolean {\n if (field.primary && editing.value?.mode !== \"create\") return false;\n return fieldVisible(field, detailRecord.value);\n}\n\n/** Mirror of the create-mode primary-key carve-out: drop the HTML5\n * `required` flag on the primary field while creating so the browser\n * doesn't block an intentionally-blank primary (server generates the id). */\nfunction isFieldRequiredInUi(field: FieldSpec): boolean {\n if (!field.required) return false;\n if (editing.value?.mode === \"create\" && field.primary === true) return false;\n return true;\n}\n\n/** Tailwind fill/text/border classes tinting an enum `<select>` by its current\n * value's colour (palette, or notification red/amber/grey when the field is\n * the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const cls = resolveEnumColor(props.collection.schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n// The edit-draft mutators write the model's nested reactive state — the same\n// object the form's v-model bindings mutate, so no parent round-trip needed.\nfunction markBoolTouched(key: string): void {\n if (editing.value) editing.value.boolTouched[key] = true;\n}\n\nfunction markRowBoolTouched(row: TableRowDraft, subKey: string): void {\n row.boolTouched[subKey] = true;\n}\n\nfunction addTableRow(key: string, subFields: Record<string, FieldSpec>): void {\n if (!editing.value) return;\n const rows = editing.value.table[key] ?? [];\n rows.push(emptyRow(subFields));\n editing.value.table[key] = rows;\n}\n\nfunction removeTableRow(key: string, index: number): void {\n if (!editing.value) return;\n const rows = editing.value.table[key];\n if (rows) rows.splice(index, 1);\n}\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vue.js","names":[],"sources":["../src/vue/uiContext.ts","../src/vue/useCollectionRendering.ts","../src/vue/collectionViewMode.ts","../src/vue/components/CollectionRecordModal.vue","../src/vue/components/CollectionRecordModal.vue","../src/vue/lang/en.ts","../src/vue/lang/ja.ts","../src/vue/lang/zh.ts","../src/vue/lang/ko.ts","../src/vue/lang/es.ts","../src/vue/lang/ptBR.ts","../src/vue/lang/fr.ts","../src/vue/lang/de.ts","../src/vue/lang/index.ts","../src/vue/components/CollectionEmbedView.vue","../src/vue/components/CollectionEmbedView.vue","../src/vue/components/CollectionCalendarView.vue","../src/vue/components/CollectionCalendarView.vue","../src/vue/components/CollectionDayView.vue","../src/vue/components/CollectionDayView.vue","../src/vue/components/CollectionKanbanView.vue","../src/vue/components/CollectionKanbanView.vue","../src/vue/components/CollectionRecordPanel.vue","../src/vue/components/CollectionRecordPanel.vue","../src/vue/components/CollectionViewConfigModal.vue","../src/vue/components/CollectionViewConfigModal.vue","../src/vue/components/CollectionCustomView.vue","../src/vue/components/CollectionCustomView.vue","../src/vue/components/CollectionView.vue","../src/vue/components/CollectionView.vue","../src/vue/components/CollectionsIndexView.vue","../src/vue/components/CollectionsIndexView.vue","../src/vue/components/FeedsView.vue","../src/vue/components/FeedsView.vue"],"sourcesContent":["// Host-provided UI capabilities the collection view layer needs but a package\n// can't own: data fetching over the host's collection REST API, and the host's\n// asset-URL scheme. Each host (MulmoClaude, MulmoTerminal) configures this once\n// at app startup via `configureCollectionUi`; the view layer reads it through\n// `collectionUi()`. Mirrors the server-side `configureCollectionHost` binding.\n//\n// This grows as more of the View moves into the package (navigation, chat,\n// confirm, …) as components migrate.\n\nimport type { Component } from \"vue\";\nimport type {\n CollectionDetailResponse,\n ItemMutationResponse,\n CollectionNotifySeverity,\n CollectionsListResponse,\n FeedsListResponse,\n CollectionShortcutInfo,\n} from \"../core/uiTypes\";\nimport type { CollectionItem } from \"../core/schema\";\n\n/** Result of a host data fetch — structurally a subset of the host's own\n * `ApiResult` (so the host can pass `apiGet` straight through). The view layer\n * treats `ok: false` as a skip, never throwing on one failed target. */\nexport type CollectionFetchResult<T> = { ok: true; data: T } | { ok: false };\n\n/** Result of a host write (delete / create / update / action) — the normalised\n * `ApiResult` shape, so the host passes `apiDelete`/`apiPost`/… straight through.\n * Carries the host's error string on failure for inline display. */\nexport type CollectionMutationResult = { ok: true } | { ok: false; error: string };\n\n/** Full host `ApiResult<T>` (data on success, error + HTTP status on failure) —\n * matches the host's `ApiResult` exactly, so `apiGet`/`apiPost`/`apiPut` pass\n * straight through. `status` lets the view distinguish 404 (not-found) from a\n * generic failure. */\nexport type CollectionApiResult<T> = { ok: true; data: T } | { ok: false; error: string; status: number };\n\n/** A collection / item action's result — a seed prompt + role for a new chat. */\nexport interface CollectionActionResult {\n prompt: string;\n role: string;\n}\n\n/** A feed refresh's result — counts + per-source errors. */\nexport interface CollectionRefreshResult {\n refreshed: boolean;\n written: number;\n errors: string[];\n}\n\n/** Scoped capability token for a sandboxed custom view (mirrors the host's mint\n * response) — the iframe reads/writes the collection through it. */\nexport interface CollectionViewToken {\n token: string;\n exp: number;\n dataUrl: string;\n capabilities: string[];\n}\n\n/** Result of fetching a custom view's HTML — status-only failure (the host\n * attaches the global bearer; a non-2xx is surfaced as `HTTP <status>`). */\nexport type CollectionViewHtmlResult = { ok: true; html: string } | { ok: false; status: number };\n\n/** Inputs the host needs to wrap a custom view's HTML into a sandboxed srcdoc\n * (token + data URL injected, CSP applied — the host owns the CSP policy). */\nexport interface CollectionViewSrcdocBoot {\n slug: string;\n token: string;\n dataUrl: string;\n origin: string;\n}\n\n/** Options for the host's confirm dialog — structurally matches the host's own\n * `ConfirmOptions`, so `confirm` can forward to `useConfirm().openConfirm`. */\nexport interface CollectionConfirmOptions {\n message: string;\n confirmText?: string;\n cancelText?: string;\n variant?: \"primary\" | \"success\" | \"danger\";\n}\n\nexport interface CollectionUi {\n /** Fetch a collection's detail (schema + records) by slug — backs both the\n * View's own load (reads `status` for 404 → not-found) and ref/embed\n * resolution (treats `!ok` as a skip). Replaces `apiGet(…collections.detail)`. */\n fetchCollectionDetail: (slug: string) => Promise<CollectionApiResult<CollectionDetailResponse>>;\n /** Browser-loadable URL for a file/image asset value (an html/svg artifact),\n * or null when the value isn't a renderable asset path. Replaces\n * `isValidFilePath` + `htmlPreviewUrlFor`/`svgPreviewUrlFor`. */\n fileAssetUrl: (value: unknown) => string | null;\n /** In-app File-Explorer route for a workspace file path (the fallback for\n * `file` values that aren't a directly-served artifact), or null when the\n * value isn't a valid in-workspace path. */\n fileRoutePath: (value: unknown) => string | null;\n /** Browser `<img src>` for a stored image value (a workspace file path), via\n * the host's raw-file endpoint. Replaces the host's `resolveImageSrc`. */\n imageSrc: (imageData: string) => string;\n /** Open the host's confirm dialog; resolves true if confirmed. Replaces\n * `useConfirm().openConfirm`. */\n confirm: (options: CollectionConfirmOptions) => Promise<boolean>;\n /** Delete a collection's custom view by id. Replaces the host's\n * `apiDelete(API_ROUTES.collections.viewDelete)`. */\n deleteView: (slug: string, viewId: string) => Promise<CollectionMutationResult>;\n /** Mint a scoped capability token for a custom view (host: `apiPost` over\n * `API_ROUTES.collections.viewToken`). */\n mintViewToken: (slug: string, viewId: string) => Promise<CollectionApiResult<CollectionViewToken>>;\n /** Fetch a custom view's raw HTML (host: `apiFetchRaw` over\n * `API_ROUTES.collections.viewFile`, global bearer attached). */\n fetchViewHtml: (slug: string, viewId: string) => Promise<CollectionViewHtmlResult>;\n /** Wrap a custom view's HTML in a sandboxed `<iframe srcdoc>` with the token +\n * data URL injected and the host's CSP applied. Replaces the host's\n * `buildCustomViewSrcdoc`. */\n buildViewSrcdoc: (html: string, boot: CollectionViewSrcdocBoot) => string;\n\n // ── record CRUD + actions (host: api{Post,Put,Delete} over API_ROUTES.collections) ──\n /** Create a record (`apiPost` over `…collections.items`). */\n createItem: (slug: string, record: CollectionItem) => Promise<CollectionApiResult<ItemMutationResponse>>;\n /** Update a record (`apiPut` over `…collections.item`). */\n updateItem: (slug: string, itemId: string, record: CollectionItem) => Promise<CollectionApiResult<ItemMutationResponse>>;\n /** Delete a record (`apiDelete` over `…collections.item`). */\n deleteItem: (slug: string, itemId: string) => Promise<CollectionMutationResult>;\n /** Delete a whole collection (`apiDelete` over `…collections.detail`). */\n deleteCollection: (slug: string) => Promise<CollectionMutationResult>;\n /** Delete a feed via the project-scope feed-delete route (`…feeds.detail`). */\n deleteFeed: (slug: string) => Promise<CollectionMutationResult>;\n /** Run a per-record action (`apiPost` over `…collections.itemAction`). */\n runItemAction: (slug: string, itemId: string, actionId: string) => Promise<CollectionApiResult<CollectionActionResult>>;\n /** Run a collection-level action (`apiPost` over `…collections.collectionAction`). */\n runCollectionAction: (slug: string, actionId: string) => Promise<CollectionApiResult<CollectionActionResult>>;\n /** Refresh a feed-backed collection (`apiPost` over `…collections.refresh`). */\n refreshCollection: (slug: string) => Promise<CollectionApiResult<CollectionRefreshResult>>;\n\n // ── routing (host: the vue-router instance) ──\n /** Current route's `:slug` param (standalone page), or undefined. */\n routeSlug: () => string | undefined;\n /** Current route's `?selected=` query (deep-linked record), or undefined. */\n routeSelectedId: () => string | undefined;\n /** True when the standalone page is the feeds route (vs collections). */\n isFeedRoute: () => boolean;\n /** Set/clear the `?selected=` deep-link (router.replace, no history entry). */\n setSelectedId: (itemId: string | null) => void;\n /** Navigate to the collections / feeds index after a delete. */\n gotoIndex: (kind: \"collection\" | \"feed\") => void;\n /** Navigate to a specific collection / feed detail page (from an index card). */\n gotoDetail: (kind: \"collection\" | \"feed\", slug: string) => void;\n\n // ── index pages (the browsable /collections + /feeds lists) ──\n /** List skill-backed collections (`apiGet` over `…collections.list`). */\n listCollections: () => Promise<CollectionApiResult<CollectionsListResponse>>;\n /** List feed-backed collections (`apiGet` over `…feeds.list`). */\n listFeeds: () => Promise<CollectionApiResult<FeedsListResponse>>;\n /** Bulk-reconcile pinned launcher shortcuts of one kind against the\n * authoritative list — prune dead slugs, refresh stale labels\n * (`useShortcuts().reconcile`). */\n reconcileShortcuts: (kind: \"collection\" | \"feed\", live: CollectionShortcutInfo[]) => Promise<void>;\n\n // ── app integration ──\n /** Start a new chat with a seed prompt + role (host: `useAppApi().startNewChat`). */\n startChat: (prompt: string, role: string) => void;\n /** The host's active i18n locale tag (e.g. \"en\", \"ja\"), read reactively — the\n * plugin syncs its own self-contained i18n instance to it. */\n localeTag: () => string;\n /** The host's \"general\" role id, for chats seeded without a specific role. */\n generalRoleId: string;\n /** The host's \"personal\" role id (the feed-add chat seeds into it). */\n personalRoleId: string;\n /** Remove a pinned launcher shortcut for a 404'd collection/feed\n * (`useShortcuts().unpin`). */\n unpin: (kind: \"collection\" | \"feed\", slug: string) => Promise<boolean>;\n /** Active-notification severity per record id, for accenting flagged rows/cards\n * (`collectionNotifiedSeverities` over the host's live notifier entries). */\n notifiedSeverities: (slug: string) => Map<string, CollectionNotifySeverity>;\n\n // ── injected host component ──\n /** The host's pin/unpin toggle (couples to the host's shortcut store + is\n * shared with other host views), rendered in the View header via\n * `<component :is>`. Props: `kind`, `slug`, `title`, `icon`. */\n pinToggle: Component;\n}\n\nlet current: CollectionUi | null = null;\n\n/** Wire the collection view layer to a host. Call once at app startup. */\nexport function configureCollectionUi(capabilities: CollectionUi): void {\n current = capabilities;\n}\n\nexport function collectionUi(): CollectionUi {\n if (current === null) {\n throw new Error(\"@mulmoclaude/collection-plugin/vue: configureCollectionUi() was not called by the host\");\n }\n return current;\n}\n","// Rendering + linked-data layer for collection surfaces, extracted from\n// CollectionView.vue so the list/detail view AND the calendar view's\n// record panel share one implementation (and one set of ref/embed\n// caches). Owns: the per-target caches, the fan-out fetch that fills\n// them, and every helper that turns a stored value into something the\n// templates render (ref labels, money/currency, derived formulas, embed\n// rows). Pure-but-stateful: instantiate ONCE per collection surface and\n// pass the returned object down to child panels.\n\nimport { computed, ref, type ComputedRef, type Ref } from \"vue\";\nimport { collectionUi } from \"./uiContext\";\nimport { deriveAll } from \"../core/deriveAll\";\nimport type { CollectionDetail, CollectionItem, CollectionSchema, CollectionFieldSpec as FieldSpec, CollectionFieldType as FieldType } from \"../core/schema\";\nimport type {\n CollectionDetailResponse,\n EmbedCache,\n EmbedRow,\n EmbedView,\n RefCache,\n RefDisplayMap,\n RefOption,\n RefRecordCache,\n RefRecordMap,\n} from \"../core/uiTypes\";\n\nexport interface CollectionRendering {\n refCache: Ref<RefCache>;\n refRecordCache: Ref<RefRecordCache>;\n embedCache: Ref<EmbedCache>;\n resetLinkedCaches: () => void;\n loadLinkedCollections: (schema: CollectionSchema, expectedSlug: string) => Promise<void>;\n refDisplay: (targetSlug: string, itemSlug: string) => string;\n refOptions: (targetSlug: string) => RefOption[];\n embedViews: ComputedRef<Record<string, EmbedView>>;\n resolveCurrency: (field: FieldSpec, record: CollectionItem | null | undefined) => string | undefined;\n currencySymbol: (currency: string | undefined) => string;\n formatMoney: (value: unknown, currency: string | undefined, displayLocale: string) => string;\n formatCell: (value: unknown, type: FieldType) => string;\n detailText: (value: unknown) => string;\n isExternalUrl: (value: unknown) => boolean;\n artifactUrl: (value: unknown) => string | null;\n fileRoutePath: (value: unknown) => string | null;\n tableRows: (value: unknown) => Record<string, unknown>[];\n hasTableRows: (value: unknown) => boolean;\n formatSubCell: (subField: FieldSpec, value: unknown, record: CollectionItem | null) => string;\n inputTypeFor: (type: FieldType) => string;\n deriveAll: (schema: CollectionSchema, base: CollectionItem, refRecords: RefRecordCache) => CollectionItem;\n evaluateDerivedAgainstItem: (field: FieldSpec, fieldKey: string, item: CollectionItem) => number | null;\n derivedDisplay: (field: FieldSpec, computedValue: unknown, record: CollectionItem | null) => string;\n}\n\nexport function useCollectionRendering(collection: Ref<CollectionDetail | null>, locale: Ref<string>): CollectionRendering {\n const refCache = ref<RefCache>({});\n const refRecordCache = ref<RefRecordCache>({});\n const embedCache = ref<EmbedCache>({});\n\n function resetLinkedCaches(): void {\n refCache.value = {};\n refRecordCache.value = {};\n embedCache.value = {};\n }\n\n function uniqueRefTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n const walk = (fields: Record<string, FieldSpec>): void => {\n for (const field of Object.values(fields)) {\n if (field.type === \"ref\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n // Sub-fields of a table can also be refs; walk one level deep\n // (nested tables are schema-rejected, so one recursion suffices).\n if (field.type === \"table\" && field.of) walk(field.of);\n }\n };\n walk(schema.fields);\n return [...targets];\n }\n\n function uniqueEmbedTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n // Embeds are top-level only (the schema rejects `embed` inside a\n // table's `of`), so no recursion.\n for (const field of Object.values(schema.fields)) {\n if (field.type === \"embed\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n }\n return [...targets];\n }\n\n function buildRefDisplayMap(detail: CollectionDetailResponse): RefDisplayMap {\n const { fields, primaryKey } = detail.collection.schema;\n const displayField = \"name\" in fields ? \"name\" : \"title\" in fields ? \"title\" : primaryKey;\n const map: RefDisplayMap = {};\n for (const item of detail.items) {\n const slugRaw = item[primaryKey];\n if (typeof slugRaw !== \"string\" || slugRaw.length === 0) continue;\n const displayRaw = item[displayField];\n map[slugRaw] = typeof displayRaw === \"string\" && displayRaw.length > 0 ? displayRaw : slugRaw;\n }\n return map;\n }\n\n function buildRefRecordMap(detail: CollectionDetailResponse): RefRecordMap {\n const { schema } = detail.collection;\n const map: RefRecordMap = {};\n for (const item of detail.items) {\n const slugRaw = item[schema.primaryKey];\n if (typeof slugRaw === \"string\" && slugRaw.length > 0) map[slugRaw] = deriveAll(schema, item, {});\n }\n return map;\n }\n\n async function loadLinkedCollections(schema: CollectionSchema, expectedSlug: string): Promise<void> {\n const refTargets = new Set(uniqueRefTargets(schema));\n const embedTargets = new Set(uniqueEmbedTargets(schema));\n const allTargets = [...new Set([...refTargets, ...embedTargets])];\n if (allTargets.length === 0) return;\n // Best-effort: a single target whose fetch *rejects* (vs. resolving to\n // `{ ok: false }`) must not abort the others, so coerce a throw to a skip.\n const binding = collectionUi();\n const results = await Promise.all(\n allTargets.map(async (target) => {\n try {\n return { target, result: await binding.fetchCollectionDetail(target) };\n } catch {\n return { target, result: { ok: false as const } };\n }\n }),\n );\n // Stale-write guard: a quicker subsequent load may have replaced\n // `collection.value`; dropping the write avoids surfacing the\n // previous collection's linked data on the current one.\n if (collection.value?.slug !== expectedSlug) return;\n const nextRef: RefCache = {};\n const nextRefRecords: RefRecordCache = {};\n const nextEmbed: EmbedCache = {};\n for (const { target, result } of results) {\n if (!result.ok) continue;\n if (refTargets.has(target)) {\n nextRef[target] = buildRefDisplayMap(result.data);\n nextRefRecords[target] = buildRefRecordMap(result.data);\n }\n if (embedTargets.has(target)) nextEmbed[target] = { schema: result.data.collection.schema, items: result.data.items };\n }\n refCache.value = nextRef;\n refRecordCache.value = nextRefRecords;\n embedCache.value = nextEmbed;\n }\n\n function refDisplay(targetSlug: string, itemSlug: string): string {\n const map = refCache.value[targetSlug];\n return (map && map[itemSlug]) || itemSlug;\n }\n\n function refOptions(targetSlug: string): RefOption[] {\n const map = refCache.value[targetSlug];\n if (!map) return [];\n return Object.entries(map)\n .map(([slug, display]) => ({ slug, display }))\n .sort((left, right) => left.display.localeCompare(right.display));\n }\n\n function resolveEmbed(field: FieldSpec): { schema: CollectionSchema | null; item: CollectionItem | null } {\n if (field.type !== \"embed\" || !field.to || !field.id) return { schema: null, item: null };\n const data = embedCache.value[field.to];\n if (!data) return { schema: null, item: null };\n const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? \"\") === field.id) ?? null;\n return { schema: data.schema, item };\n }\n\n function embedValue(field: FieldSpec, value: unknown, record: CollectionItem | null): string {\n if (field.type === \"money\") return formatMoney(value, resolveCurrency(field, record), locale.value);\n return detailText(value);\n }\n\n const embedViews = computed<Record<string, EmbedView>>(() => {\n const out: Record<string, EmbedView> = {};\n if (!collection.value) return out;\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n if (field.type !== \"embed\") continue;\n const { schema, item } = resolveEmbed(field);\n const rows: EmbedRow[] = [];\n if (schema && item) {\n for (const [subKey, subField] of Object.entries(schema.fields)) {\n const value = item[subKey];\n // Skip empty fields — the embed is a read-only summary, so\n // unfilled optionals would just be \"—\" noise.\n if (value === undefined || value === null || value === \"\") continue;\n rows.push({ key: subKey, label: subField.label, type: subField.type, value, display: embedValue(subField, value, item) });\n }\n }\n out[key] = { found: Boolean(item), rows, targetSlug: field.to ?? \"\", recordId: field.id ?? \"\" };\n }\n return out;\n });\n\n function resolveCurrency(field: FieldSpec, record: CollectionItem | null | undefined): string | undefined {\n if (field.currencyField && record) {\n const code = record[field.currencyField];\n if (typeof code === \"string\" && code.trim().length > 0) return code;\n }\n return field.currency;\n }\n\n function currencySymbol(currency: string | undefined): string {\n const code = currency && currency.length > 0 ? currency : \"USD\";\n try {\n const parts = new Intl.NumberFormat(locale.value, { style: \"currency\", currency: code }).formatToParts(0);\n return parts.find((entry) => entry.type === \"currency\")?.value ?? code;\n } catch {\n return code;\n }\n }\n\n function formatMoney(value: unknown, currency: string | undefined, displayLocale: string): string {\n if (value === undefined || value === \"\") return \"—\";\n const amount = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(amount)) return String(value);\n const currencyCode = currency && currency.length > 0 ? currency : \"USD\";\n try {\n return new Intl.NumberFormat(displayLocale, { style: \"currency\", currency: currencyCode }).format(amount);\n } catch {\n return String(amount);\n }\n }\n\n function formatCell(value: unknown, type: FieldType): string {\n if (value === undefined || value === null || value === \"\") return \"—\";\n if (type === \"markdown\" && typeof value === \"string\") return value.length > 80 ? `${value.slice(0, 80)}…` : value;\n if (typeof value === \"string\" || typeof value === \"number\") return String(value);\n return JSON.stringify(value);\n }\n\n function isExternalUrl(value: unknown): boolean {\n return typeof value === \"string\" && /^https?:\\/\\//i.test(value);\n }\n\n // A `file` field holds a workspace-relative path. When it points at an\n // HTML/SVG artifact the server serves directly, return that served URL\n // so the rendered app can open in a new tab; otherwise null. Reject\n // absolute / `..`-traversing paths first (same guard as fileRoutePath)\n // — the preview-URL builders don't, so a `..` would normalize out of\n // the intended mount.\n function artifactUrl(value: unknown): string | null {\n return collectionUi().fileAssetUrl(value);\n }\n\n // In-app File Explorer route for a workspace path — the fallback for\n // `file` values that aren't a directly-served artifact. The host owns the\n // path validity + route scheme.\n function fileRoutePath(value: unknown): string | null {\n return collectionUi().fileRoutePath(value);\n }\n\n function detailText(value: unknown): string {\n if (value === undefined || value === null || value === \"\") return \"—\";\n return String(value);\n }\n\n function tableRows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) return [];\n return value.filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === \"object\" && !Array.isArray(row));\n }\n\n function hasTableRows(value: unknown): boolean {\n return tableRows(value).length > 0;\n }\n\n function formatSubCell(subField: FieldSpec, value: unknown, record: CollectionItem | null): string {\n if (subField.type === \"money\") return formatMoney(value, resolveCurrency(subField, record), locale.value);\n if (subField.type === \"ref\" && subField.to && typeof value === \"string\" && value.length > 0) return refDisplay(subField.to, value);\n return formatCell(value, subField.type);\n }\n\n function inputTypeFor(type: FieldType): string {\n if (type === \"email\") return \"email\";\n if (type === \"number\") return \"number\";\n if (type === \"money\") return \"number\";\n if (type === \"date\") return \"date\";\n if (type === \"datetime\") return \"datetime-local\";\n return \"text\";\n }\n\n // The derive loop itself lives in `utils/collections/deriveAll.ts`,\n // shared with the server's manageCollection enrichment so both sides\n // compute identical values. This composable re-exposes it (typed with\n // the richer client types via structural assignability) plus the\n // collection-bound convenience wrappers below.\n\n function evaluateDerivedAgainstItem(field: FieldSpec, fieldKey: string, item: CollectionItem): number | null {\n if (!field.formula || !collection.value) return null;\n const enriched = deriveAll(collection.value.schema, item, refRecordCache.value);\n const result = enriched[fieldKey];\n return typeof result === \"number\" && Number.isFinite(result) ? result : null;\n }\n\n function derivedDisplay(field: FieldSpec, computedValue: unknown, record: CollectionItem | null): string {\n if (computedValue === null || computedValue === undefined) return \"—\";\n if (field.display === \"money\") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);\n return formatCell(computedValue, field.display ?? \"number\");\n }\n\n return {\n refCache,\n refRecordCache,\n embedCache,\n resetLinkedCaches,\n loadLinkedCollections,\n refDisplay,\n refOptions,\n embedViews,\n resolveCurrency,\n currencySymbol,\n formatMoney,\n formatCell,\n detailText,\n isExternalUrl,\n artifactUrl,\n fileRoutePath,\n tableRows,\n hasTableRows,\n formatSubCell,\n inputTypeFor,\n deriveAll,\n evaluateDerivedAgainstItem,\n derivedDisplay,\n };\n}\n","// Per-collection view preferences (the view mode, and the table's active\n// column sort) persisted to localStorage, keyed by collection slug. Lets the\n// standalone `/collections/:slug` page reopen in the last-used view and sort\n// instead of resetting. Embedded chat cards seed from these but persist their\n// own copy in the tool-result `viewState` (they read, never write — so a\n// stale card can't clobber the shared preference).\n\nimport type { SortState } from \"../core/sortItems\";\n\n/** The host's built-in, field-derived view modes. */\nexport type BuiltInViewMode = \"table\" | \"calendar\" | \"kanban\";\n/** A custom (LLM-authored) view's selector key: `custom:<viewId>`. */\nexport type CustomViewMode = `custom:${string}`;\nexport type CollectionViewMode = BuiltInViewMode | CustomViewMode;\n\nconst STORAGE_KEY = \"collection_view_modes\";\nconst SORT_STORAGE_KEY = \"collection_sorts\";\n\nconst BUILT_IN_MODES: readonly BuiltInViewMode[] = [\"table\", \"calendar\", \"kanban\"];\n\n/** A persisted mode is valid if it's a known built-in OR any `custom:<id>`\n * key (the id is validated against the live schema at render time, so an\n * unknown custom id simply collapses to the table there). Takes `unknown`\n * and type-guards `string` first: a corrupted localStorage entry could hold a\n * number/object, and calling `.startsWith` on that would throw. */\nfunction isValidViewMode(value: unknown): value is CollectionViewMode {\n return typeof value === \"string\" && (BUILT_IN_MODES.includes(value as BuiltInViewMode) || value.startsWith(\"custom:\"));\n}\n\ntype ViewModeMap = Record<string, CollectionViewMode>;\n\nfunction readAll(): ViewModeMap {\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (!raw) return {};\n const parsed: unknown = JSON.parse(raw);\n // Plain object only — an array would pass `typeof === \"object\"` and then\n // let writeCollectionViewMode write string keys onto it.\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as ViewModeMap) : {};\n } catch {\n return {};\n }\n}\n\nexport function readCollectionViewMode(slug: string): CollectionViewMode | null {\n const stored: unknown = readAll()[slug];\n return isValidViewMode(stored) ? stored : null;\n}\n\nexport function writeCollectionViewMode(slug: string, view: CollectionViewMode): void {\n try {\n const all = readAll();\n all[slug] = view;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(all));\n } catch {\n // localStorage unavailable / quota exceeded — the preference is\n // best-effort, so silently skip rather than break the view.\n }\n}\n\n// ── Active column sort (table view) ──────────────────────────────────\n\ntype SortMap = Record<string, SortState>;\n\nfunction isSortState(value: unknown): value is SortState {\n if (!value || typeof value !== \"object\") return false;\n const rec = value as Record<string, unknown>;\n return typeof rec.field === \"string\" && (rec.direction === \"asc\" || rec.direction === \"desc\");\n}\n\nfunction readAllSorts(): SortMap {\n try {\n const raw = localStorage.getItem(SORT_STORAGE_KEY);\n if (!raw) return {};\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n // Drop any entry whose stored shape no longer validates (e.g. a schema\n // whose field was renamed away leaves a stale value) so callers only ever\n // see a well-formed SortState.\n const out: SortMap = {};\n for (const [slug, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (isSortState(value)) out[slug] = value;\n }\n return out;\n } catch {\n return {};\n }\n}\n\nexport function readCollectionSort(slug: string): SortState | null {\n return readAllSorts()[slug] ?? null;\n}\n\n/** Persist (or, when `sort` is null, clear) the slug's active column sort. */\nexport function writeCollectionSort(slug: string, sort: SortState | null): void {\n try {\n // Rebuild without the slug rather than `delete all[slug]` (dynamic-delete),\n // then re-add it when a sort is set — clearing leaves no stale key behind.\n const all = Object.fromEntries(Object.entries(readAllSorts()).filter(([key]) => key !== slug));\n if (sort) all[slug] = sort;\n localStorage.setItem(SORT_STORAGE_KEY, JSON.stringify(all));\n } catch {\n // Best-effort, same as the view-mode store.\n }\n}\n","<template>\n <!-- Centered modal shell for a collection record's open/edit panel. Used\n by every non-calendar view mode (table / kanban) and the\n calendar's undated tray, so opening an item is a consistent popup\n everywhere. Calendar's dated records keep their own day-view modal\n (CollectionDayView), which embeds the same panel on its right. Teleported\n to <body> so an embedded card's transformed ancestor can't trap the\n fixed overlay. Backdrop click / Escape both emit `close`; the host\n decides whether that cancels an edit or closes the detail.\n\n Focus is contained while open (Tab/Shift+Tab wrap inside the dialog)\n and restored to the trigger on close, so keyboard users can't reach\n the controls behind the overlay (WCAG focus containment). -->\n <Teleport to=\"body\">\n <div class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\" data-testid=\"collections-record-modal\" @click.self=\"emit('close')\">\n <div\n ref=\"dialogEl\"\n class=\"flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl bg-white shadow-xl focus:outline-none\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n @keydown.esc=\"emit('close')\"\n @keydown.tab=\"onTab\"\n >\n <slot />\n </div>\n </div>\n </Teleport>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst emit = defineEmits<{ close: [] }>();\n\nconst dialogEl = ref<HTMLDivElement | null>(null);\n\n// The control that had focus before the modal opened (usually the row /\n// card the user activated). Restored when the modal unmounts.\nlet previouslyFocused: HTMLElement | null = null;\n\nconst FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/** Visible, focusable controls inside the dialog, in DOM order. */\nfunction focusableItems(): HTMLElement[] {\n if (!dialogEl.value) return [];\n return Array.from(dialogEl.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((node) => node.offsetParent !== null);\n}\n\n/** Trap Tab / Shift+Tab inside the dialog so focus can't escape to the\n * page behind the overlay. Wraps at both ends; the dialog container\n * itself (tabindex -1) counts as \"before the first item\". */\nfunction onTab(event: KeyboardEvent): void {\n const items = focusableItems();\n if (items.length === 0) {\n event.preventDefault();\n dialogEl.value?.focus();\n return;\n }\n const [first] = items;\n const last = items[items.length - 1];\n const active = document.activeElement;\n if (event.shiftKey) {\n if (active === first || active === dialogEl.value) {\n event.preventDefault();\n last.focus();\n }\n } else if (active === last) {\n event.preventDefault();\n first.focus();\n }\n}\n\n// Focus the dialog on open so Escape (bound on the dialog) fires even\n// before the user clicks into a field, and focus leaves the row behind it.\nonMounted(async () => {\n previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n await nextTick();\n dialogEl.value?.focus();\n});\n\n// Restore focus to the trigger so keyboard users land back where they were.\nonBeforeUnmount(() => {\n previouslyFocused?.focus?.();\n});\n</script>\n","<template>\n <!-- Centered modal shell for a collection record's open/edit panel. Used\n by every non-calendar view mode (table / kanban) and the\n calendar's undated tray, so opening an item is a consistent popup\n everywhere. Calendar's dated records keep their own day-view modal\n (CollectionDayView), which embeds the same panel on its right. Teleported\n to <body> so an embedded card's transformed ancestor can't trap the\n fixed overlay. Backdrop click / Escape both emit `close`; the host\n decides whether that cancels an edit or closes the detail.\n\n Focus is contained while open (Tab/Shift+Tab wrap inside the dialog)\n and restored to the trigger on close, so keyboard users can't reach\n the controls behind the overlay (WCAG focus containment). -->\n <Teleport to=\"body\">\n <div class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\" data-testid=\"collections-record-modal\" @click.self=\"emit('close')\">\n <div\n ref=\"dialogEl\"\n class=\"flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl bg-white shadow-xl focus:outline-none\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n @keydown.esc=\"emit('close')\"\n @keydown.tab=\"onTab\"\n >\n <slot />\n </div>\n </div>\n </Teleport>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref } from \"vue\";\n\nconst emit = defineEmits<{ close: [] }>();\n\nconst dialogEl = ref<HTMLDivElement | null>(null);\n\n// The control that had focus before the modal opened (usually the row /\n// card the user activated). Restored when the modal unmounts.\nlet previouslyFocused: HTMLElement | null = null;\n\nconst FOCUSABLE_SELECTOR = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\n/** Visible, focusable controls inside the dialog, in DOM order. */\nfunction focusableItems(): HTMLElement[] {\n if (!dialogEl.value) return [];\n return Array.from(dialogEl.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((node) => node.offsetParent !== null);\n}\n\n/** Trap Tab / Shift+Tab inside the dialog so focus can't escape to the\n * page behind the overlay. Wraps at both ends; the dialog container\n * itself (tabindex -1) counts as \"before the first item\". */\nfunction onTab(event: KeyboardEvent): void {\n const items = focusableItems();\n if (items.length === 0) {\n event.preventDefault();\n dialogEl.value?.focus();\n return;\n }\n const [first] = items;\n const last = items[items.length - 1];\n const active = document.activeElement;\n if (event.shiftKey) {\n if (active === first || active === dialogEl.value) {\n event.preventDefault();\n last.focus();\n }\n } else if (active === last) {\n event.preventDefault();\n first.focus();\n }\n}\n\n// Focus the dialog on open so Escape (bound on the dialog) fires even\n// before the user clicks into a field, and focus leaves the row behind it.\nonMounted(async () => {\n previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n await nextTick();\n dialogEl.value?.focus();\n});\n\n// Restore focus to the trigger so keyboard users land back where they were.\nonBeforeUnmount(() => {\n previouslyFocused?.focus?.();\n});\n</script>\n","// Auto-extracted from the host src/lang/en.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nconst enMessages = {\n collectionsView: {\n addCollectionLabel: \"Collection\",\n addCollectionPrompt:\n \"Help me create a new collection. First read `config/helps/collection-skills.md` for the schema-driven collection conventions. Then use the `presentForm` tool (do not use AskUserQuestion) to ask me what kind of data I want to track, and author the schema.json and SKILL.md from my answers.\",\n addFeedTitle: \"Add a feed\",\n addFeedHint: \"Paste a feed or API URL — I'll fetch it and work out the title and fields for you.\",\n addFeedPrompt:\n \"Add a new data-source feed from this URL: {url}\\n\\nFirst Read `config/helps/feeds.md` and follow it exactly. Fetch that URL yourself, inspect the response to infer a sensible title and fields, then author `feeds/<slug>/schema.json` as the help describes — do NOT ask me any questions; work it all out from the data. Opening the feed loads its items automatically; when you're done, tell me it's registered (no need to mention Refresh).\",\n title: \"Collections\",\n backToIndex: \"Back to collections\",\n indexEmpty: \"No collections installed. Star a skill that ships a schema from the Skills page to see it here.\",\n editItem: \"Edit\",\n openItem: \"Open {id}\",\n confirmDelete: \"Delete this item? This cannot be undone.\",\n deleteFeed: \"Delete feed\",\n confirmDeleteFeed: 'Delete the \"{title}\" feed and all its fetched records? This cannot be undone.',\n deleteCollection: \"Delete collection\",\n confirmDeleteCollection: 'Delete the entire \"{title}\" collection, including all its records? A restorable backup is archived first.',\n itemsEmpty: \"No items yet. Click + to add one.\",\n notFound: \"Collection not found\",\n loadFailed: \"Failed to load\",\n requiredField: \"This field is required\",\n selectPlaceholder: \"Select…\",\n inlineSaveFailed: \"Couldn't save change: {error}\",\n addRow: \"Add row\",\n removeRow: \"Remove row\",\n noRows: \"No rows yet\",\n tableSummary: \"{count} items\",\n embedMissing: \"No “{id}” record found in {collection}.\",\n embedCreate: \"Set it up\",\n searchPlaceholder: \"Search records…\",\n searchSummary: \"Showing {shown} of {total}\",\n noMatchingItems: \"No matching items\",\n clearSearch: \"Clear search\",\n sortBy: \"Sort by {field}\",\n openCollection: \"Open {title}\",\n createTitle: \"Add new\",\n derivedLabel: \"Derived\",\n embedMissingTitle: \"Embedded reference missing\",\n chat: \"Chat\",\n refreshFeed: \"Refresh\",\n refreshFailed: \"Refresh failed: {error}\",\n feedChatSeed:\n 'The \"{slug}\" feed is defined by the schema at `feeds/{slug}/schema.json` and its records live in `{dataPath}/` (one `<id>.json` per record). Using that schema and data, respond to this request: {message}',\n feedsTitle: \"Data-source feeds\",\n feedsEmpty: \"No feeds registered yet.\",\n chatTitle: \"Start a chat\",\n chatPlaceholder: \"Describe what you want to do with this collection…\",\n chatStart: \"Start chat\",\n viewToggle: \"View\",\n viewTable: \"Table\",\n viewCalendar: \"Calendar\",\n calendarFieldLabel: \"Calendar date field\",\n calendarPrevMonth: \"Previous month\",\n calendarNextMonth: \"Next month\",\n calendarToday: \"Today\",\n calendarNoDate: \"No date\",\n calendarCreateOn: \"Create on {date}\",\n dayViewOpen: \"Open day view for {date}\",\n dayViewAllDay: \"All day\",\n dayViewEmpty: \"No items on this day\",\n dayViewClose: \"Close day view\",\n viewKanban: \"Kanban\",\n kanbanFieldLabel: \"Kanban group field\",\n kanbanUncategorized: \"Uncategorized\",\n kanbanOpenCard: \"Open {label}\",\n addView: \"Add view\",\n config: {\n open: \"Collection settings\",\n title: \"{title} · settings\",\n viewsHeading: \"Custom views\",\n deleteView: \"Delete {label}\",\n confirmDelete: 'Delete the \"{label}\" view? This removes its HTML file and unregisters it.',\n empty: \"No custom views yet.\",\n },\n customViewLoading: \"Loading view…\",\n customViewError: \"Couldn't load this view: {error}\",\n addViewPrompt:\n '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.',\n repair: \"Repair\",\n dataIssuesDetected: \"{count} record file(s) have data problems and may be missing from this view.\",\n repairPrompt:\n '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.',\n source: {\n user: \"User\",\n project: \"Project\",\n },\n },\n common: {\n close: \"Close\",\n add: \"Add\",\n cancel: \"Cancel\",\n loading: \"Loading...\",\n no: \"No\",\n remove: \"Remove\",\n save: \"Save\",\n saving: \"Saving...\",\n yes: \"Yes\",\n },\n};\n\nexport default enMessages;\n\nexport type CollectionMessages = typeof enMessages;\n","// Auto-extracted from the host src/lang/ja.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst jaMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"コレクション\",\n addCollectionPrompt:\n \"新しいコレクションを作成したいです。まず `config/helps/collection-skills.md` を読んでスキーマ駆動コレクションの規約を確認してください。次に `presentForm` ツールを使って(AskUserQuestion は使わないで)どんなデータを管理したいか質問し、その回答をもとに schema.json と SKILL.md を作成してください。\",\n addFeedTitle: \"フィードを追加\",\n addFeedHint: \"フィードまたは API の URL を貼り付けてください。取得してタイトルとフィールドを自動で判断します。\",\n addFeedPrompt:\n \"次の URL からデータソースフィードを追加してください: {url}\\n\\nまず `config/helps/feeds.md` を読み、その指示に正確に従ってください。その URL を自分で取得してレスポンスを調べ、ふさわしいタイトルとフィールドを推測し、ヘルプの説明どおりに `feeds/<slug>/schema.json` を作成してください。私には質問せず、データからすべて判断してください。フィードを開くとアイテムは自動で読み込まれます。完了したら登録できたことを伝えてください(Refresh を押すよう案内する必要はありません)。\",\n title: \"コレクション\",\n backToIndex: \"コレクション一覧に戻る\",\n indexEmpty: \"インストール済みのコレクションがありません。スキーマを含むスキルを Skills ページからスター付けすると、ここに表示されます。\",\n editItem: \"編集\",\n openItem: \"{id} を開く\",\n confirmDelete: \"この項目を削除しますか?元に戻せません。\",\n deleteFeed: \"フィードを削除\",\n confirmDeleteFeed: \"フィード「{title}」と取得済みのすべてのレコードを削除しますか?この操作は取り消せません。\",\n deleteCollection: \"コレクションを削除\",\n confirmDeleteCollection: \"コレクション「{title}」とそのすべてのレコードを削除しますか?削除前に復元可能なバックアップが保存されます。\",\n itemsEmpty: \"まだ項目がありません。+ を押して追加してください。\",\n notFound: \"コレクションが見つかりません\",\n loadFailed: \"読み込みに失敗しました\",\n requiredField: \"この項目は必須です\",\n selectPlaceholder: \"選択…\",\n inlineSaveFailed: \"変更を保存できませんでした: {error}\",\n addRow: \"行を追加\",\n removeRow: \"行を削除\",\n noRows: \"行がありません\",\n tableSummary: \"{count}件\",\n embedMissing: \"{collection} に「{id}」のレコードが見つかりません。\",\n embedCreate: \"設定する\",\n searchPlaceholder: \"レコードを検索…\",\n searchSummary: \"{total} 件中 {shown} 件を表示\",\n noMatchingItems: \"一致する項目がありません\",\n clearSearch: \"検索をクリア\",\n sortBy: \"{field}で並べ替え\",\n openCollection: \"{title} を開く\",\n createTitle: \"新規追加\",\n derivedLabel: \"計算値\",\n embedMissingTitle: \"埋め込み参照が見つかりません\",\n chat: \"チャット\",\n refreshFeed: \"更新\",\n refreshFailed: \"更新に失敗しました: {error}\",\n feedChatSeed:\n \"フィード「{slug}」はスキーマ `feeds/{slug}/schema.json` で定義され、レコードは `{dataPath}/`(1 レコードにつき `<id>.json` 1 ファイル)に保存されています。このスキーマとデータを使って、次のリクエストに応えてください: {message}\",\n feedsTitle: \"データソースフィード\",\n feedsEmpty: \"登録されたフィードはありません。\",\n chatTitle: \"チャットを開始\",\n chatPlaceholder: \"このコレクションで行いたいことを入力してください…\",\n chatStart: \"チャットを開始\",\n viewToggle: \"表示\",\n viewTable: \"テーブル\",\n viewCalendar: \"カレンダー\",\n calendarFieldLabel: \"カレンダーの日付フィールド\",\n calendarPrevMonth: \"前の月\",\n calendarNextMonth: \"次の月\",\n calendarToday: \"今日\",\n calendarNoDate: \"日付なし\",\n calendarCreateOn: \"{date} に作成\",\n dayViewOpen: \"{date} の日表示を開く\",\n dayViewAllDay: \"終日\",\n dayViewEmpty: \"この日の項目はありません\",\n dayViewClose: \"日表示を閉じる\",\n viewKanban: \"カンバン\",\n kanbanFieldLabel: \"カンバンのグループフィールド\",\n kanbanUncategorized: \"未分類\",\n kanbanOpenCard: \"{label} を開く\",\n addView: \"ビューを追加\",\n config: {\n open: \"コレクション設定\",\n title: \"{title} · 設定\",\n viewsHeading: \"カスタムビュー\",\n deleteView: \"{label} を削除\",\n confirmDelete: \"ビュー「{label}」を削除しますか? HTML ファイルを削除し、登録を解除します。\",\n empty: \"カスタムビューはまだありません。\",\n },\n customViewLoading: \"ビューを読み込み中…\",\n customViewError: \"このビューを読み込めませんでした: {error}\",\n addViewPrompt:\n '「{title}」コレクションにカスタムビューを追加したい。何を見たい/編集したいか質問してから、HTML ビューファイルを {base}/views/your-view.html に作成し、{base}/schema.json の `views[]` に登録して(読み取り専用なら capabilities [\"read\"]、レコードを編集するなら [\"read\",\"write\"])。データ契約は custom-view ヘルプに従ってください。',\n repair: \"修復\",\n dataIssuesDetected: \"{count} 件のレコードファイルにデータの問題があり、この表示に出てこない可能性があります。\",\n repairPrompt:\n \"コレクション「{title}」に、表示されない原因となるデータの問題を持つレコードファイルが {count} 件あります。それぞれを修正してください — ファイルを Read し、修正してから Write し直します:\\n{issues}\\n\\n完了したら presentCollection を呼び出して、レコードが読み込めることを確認してください。\",\n source: {\n user: \"ユーザー\",\n project: \"プロジェクト\",\n },\n },\n common: {\n close: \"閉じる\",\n add: \"追加\",\n cancel: \"キャンセル\",\n loading: \"読み込み中...\",\n no: \"いいえ\",\n remove: \"削除\",\n save: \"保存\",\n saving: \"保存中...\",\n yes: \"はい\",\n },\n};\n\nexport default jaMessages;\n","// Auto-extracted from the host src/lang/zh.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst zhMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"集合\",\n addCollectionPrompt:\n \"帮我创建一个新的集合。请先阅读 `config/helps/collection-skills.md` 了解基于 schema 的集合约定。然后使用 `presentForm` 工具(不要使用 AskUserQuestion)询问我想跟踪哪种数据,并根据我的回答编写 schema.json 和 SKILL.md。\",\n addFeedTitle: \"添加订阅源\",\n addFeedHint: \"粘贴订阅源或 API 的 URL,我会抓取并自动推断标题和字段。\",\n addFeedPrompt:\n \"从以下 URL 添加一个数据源订阅:{url}\\n\\n请先阅读 `config/helps/feeds.md` 并严格按其说明操作。自己抓取该 URL 并检查响应,推断合适的标题和字段,然后按帮助说明编写 `feeds/<slug>/schema.json`。不要向我提问——完全根据数据判断。打开订阅源会自动加载条目。完成后告诉我已注册(无需提示点击 Refresh)。\",\n title: \"集合\",\n backToIndex: \"返回集合列表\",\n indexEmpty: \"尚未安装任何集合。在「技能」页面对带有 schema 的技能加星即可在此显示。\",\n editItem: \"编辑\",\n openItem: \"打开 {id}\",\n confirmDelete: \"删除此项?此操作无法撤销。\",\n deleteFeed: \"删除订阅源\",\n confirmDeleteFeed: \"删除订阅源「{title}」及其所有已抓取的记录?此操作无法撤销。\",\n deleteCollection: \"删除集合\",\n confirmDeleteCollection: \"删除整个“{title}”集合及其所有记录?删除前会先归档一份可恢复的备份。\",\n itemsEmpty: \"暂无项目。点击 + 添加一个。\",\n notFound: \"未找到集合\",\n loadFailed: \"加载失败\",\n requiredField: \"此字段为必填项\",\n selectPlaceholder: \"请选择…\",\n inlineSaveFailed: \"无法保存更改:{error}\",\n addRow: \"添加行\",\n removeRow: \"删除行\",\n noRows: \"暂无行\",\n tableSummary: \"{count} 项\",\n embedMissing: \"在 {collection} 中找不到「{id}」记录。\",\n embedCreate: \"去设置\",\n searchPlaceholder: \"搜索记录…\",\n searchSummary: \"显示 {total} 条中的 {shown} 条\",\n noMatchingItems: \"没有匹配的项目\",\n clearSearch: \"清除搜索\",\n sortBy: \"按{field}排序\",\n openCollection: \"打开 {title}\",\n createTitle: \"新增\",\n derivedLabel: \"派生\",\n embedMissingTitle: \"缺少嵌入引用\",\n chat: \"对话\",\n refreshFeed: \"刷新\",\n refreshFailed: \"刷新失败:{error}\",\n feedChatSeed:\n \"订阅源“{slug}”由 schema `feeds/{slug}/schema.json` 定义,其记录保存在 `{dataPath}/`(每条记录一个 `<id>.json` 文件)。请使用该 schema 和数据来响应以下请求:{message}\",\n feedsTitle: \"数据源订阅\",\n feedsEmpty: \"尚未注册任何订阅源。\",\n chatTitle: \"开始对话\",\n chatPlaceholder: \"描述你想对这个集合做什么…\",\n chatStart: \"开始对话\",\n viewToggle: \"视图\",\n viewTable: \"表格\",\n viewCalendar: \"日历\",\n calendarFieldLabel: \"日历日期字段\",\n calendarPrevMonth: \"上个月\",\n calendarNextMonth: \"下个月\",\n calendarToday: \"今天\",\n calendarNoDate: \"无日期\",\n calendarCreateOn: \"在 {date} 创建\",\n dayViewOpen: \"打开 {date} 的日视图\",\n dayViewAllDay: \"全天\",\n dayViewEmpty: \"当天没有项目\",\n dayViewClose: \"关闭日视图\",\n viewKanban: \"看板\",\n kanbanFieldLabel: \"看板分组字段\",\n kanbanUncategorized: \"未分类\",\n kanbanOpenCard: \"打开 {label}\",\n addView: \"添加视图\",\n config: {\n open: \"集合设置\",\n title: \"{title} · 设置\",\n viewsHeading: \"自定义视图\",\n deleteView: \"删除 {label}\",\n confirmDelete: \"删除视图“{label}”?这将删除其 HTML 文件并取消注册。\",\n empty: \"暂无自定义视图。\",\n },\n customViewLoading: \"正在加载视图…\",\n customViewError: \"无法加载此视图:{error}\",\n addViewPrompt:\n '我想为“{title}”集合添加一个自定义视图。先问我想查看或编辑什么,然后在 {base}/views/your-view.html 创建 HTML 视图文件,并在 {base}/schema.json 的 `views[]` 中注册(只读视图用 capabilities [\"read\"],需要编辑记录则用 [\"read\",\"write\"])。数据契约请遵循 custom-view 帮助文档。',\n repair: \"修复\",\n dataIssuesDetected: \"有 {count} 个记录文件存在数据问题,可能未显示在此视图中。\",\n repairPrompt:\n \"集合 {title} 有 {count} 个记录文件存在导致无法显示的数据问题。请逐一修复——用 Read 读取该文件,更正后再用 Write 写回:\\n{issues}\\n\\n完成后,调用 presentCollection 确认记录可以正常加载。\",\n source: {\n user: \"用户\",\n project: \"项目\",\n },\n },\n common: {\n close: \"关闭\",\n add: \"添加\",\n cancel: \"取消\",\n loading: \"加载中...\",\n no: \"否\",\n remove: \"移除\",\n save: \"保存\",\n saving: \"保存中...\",\n yes: \"是\",\n },\n};\n\nexport default zhMessages;\n","// Auto-extracted from the host src/lang/ko.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst koMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"컬렉션\",\n addCollectionPrompt:\n \"새 컬렉션을 만들고 싶어요. 먼저 `config/helps/collection-skills.md`를 읽고 스키마 기반 컬렉션 규칙을 확인하세요. 그런 다음 `presentForm` 도구를 사용해(AskUserQuestion은 사용하지 말고) 어떤 데이터를 관리하고 싶은지 물어보고, 제 답변을 바탕으로 schema.json과 SKILL.md를 작성해 주세요.\",\n addFeedTitle: \"피드 추가\",\n addFeedHint: \"피드 또는 API URL을 붙여넣으세요. 가져와서 제목과 필드를 자동으로 추론합니다.\",\n addFeedPrompt:\n \"다음 URL에서 데이터 소스 피드를 추가하세요: {url}\\n\\n먼저 `config/helps/feeds.md`를 읽고 그대로 따르세요. 해당 URL을 직접 가져와 응답을 살펴보고 적절한 제목과 필드를 추론한 다음, 도움말 설명대로 `feeds/<slug>/schema.json`을 작성하세요. 저에게 질문하지 말고 데이터에서 모두 판단하세요. 피드를 열면 항목이 자동으로 로드됩니다. 완료되면 등록되었다고 알려주세요(Refresh를 누르라고 안내할 필요 없음).\",\n title: \"컬렉션\",\n backToIndex: \"컬렉션 목록으로 돌아가기\",\n indexEmpty: \"설치된 컬렉션이 없습니다. Skills 페이지에서 스키마를 포함한 스킬에 별표를 추가하면 여기에 표시됩니다.\",\n editItem: \"편집\",\n openItem: \"{id} 열기\",\n confirmDelete: \"이 항목을 삭제하시겠습니까? 되돌릴 수 없습니다.\",\n deleteFeed: \"피드 삭제\",\n confirmDeleteFeed: '\"{title}\" 피드와 가져온 모든 레코드를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',\n deleteCollection: \"컬렉션 삭제\",\n confirmDeleteCollection: '\"{title}\" 컬렉션과 모든 레코드를 삭제하시겠습니까? 삭제 전에 복원 가능한 백업이 보관됩니다.',\n itemsEmpty: \"아직 항목이 없습니다. + 를 눌러 추가하세요.\",\n notFound: \"컬렉션을 찾을 수 없습니다\",\n loadFailed: \"불러오기에 실패했습니다\",\n requiredField: \"이 필드는 필수입니다\",\n selectPlaceholder: \"선택…\",\n inlineSaveFailed: \"변경 사항을 저장하지 못했습니다: {error}\",\n addRow: \"행 추가\",\n removeRow: \"행 삭제\",\n noRows: \"행이 없습니다\",\n tableSummary: \"{count}개\",\n embedMissing: \"{collection}에 '{id}' 레코드가 없습니다.\",\n embedCreate: \"설정하기\",\n searchPlaceholder: \"레코드 검색…\",\n searchSummary: \"{total}개 중 {shown}개 표시\",\n noMatchingItems: \"일치하는 항목이 없습니다\",\n clearSearch: \"검색 지우기\",\n sortBy: \"{field} 기준 정렬\",\n openCollection: \"{title} 열기\",\n createTitle: \"새로 추가\",\n derivedLabel: \"파생\",\n embedMissingTitle: \"임베드된 참조 없음\",\n chat: \"채팅\",\n refreshFeed: \"새로고침\",\n refreshFailed: \"새로고침 실패: {error}\",\n feedChatSeed:\n \"“{slug}” 피드는 스키마 `feeds/{slug}/schema.json`로 정의되며, 레코드는 `{dataPath}/`(레코드당 `<id>.json` 파일 하나)에 저장됩니다. 이 스키마와 데이터를 사용하여 다음 요청에 응답하세요: {message}\",\n feedsTitle: \"데이터 소스 피드\",\n feedsEmpty: \"등록된 피드가 없습니다.\",\n chatTitle: \"채팅 시작\",\n chatPlaceholder: \"이 컬렉션으로 하고 싶은 작업을 설명하세요…\",\n chatStart: \"채팅 시작\",\n viewToggle: \"보기\",\n viewTable: \"표\",\n viewCalendar: \"캘린더\",\n calendarFieldLabel: \"캘린더 날짜 필드\",\n calendarPrevMonth: \"이전 달\",\n calendarNextMonth: \"다음 달\",\n calendarToday: \"오늘\",\n calendarNoDate: \"날짜 없음\",\n calendarCreateOn: \"{date}에 생성\",\n dayViewOpen: \"{date} 일 보기 열기\",\n dayViewAllDay: \"종일\",\n dayViewEmpty: \"이 날에는 항목이 없습니다\",\n dayViewClose: \"일 보기 닫기\",\n viewKanban: \"칸반\",\n kanbanFieldLabel: \"칸반 그룹 필드\",\n kanbanUncategorized: \"미분류\",\n kanbanOpenCard: \"{label} 열기\",\n addView: \"보기 추가\",\n config: {\n open: \"컬렉션 설정\",\n title: \"{title} · 설정\",\n viewsHeading: \"사용자 지정 보기\",\n deleteView: \"{label} 삭제\",\n confirmDelete: \"“{label}” 보기를 삭제할까요? HTML 파일을 삭제하고 등록을 해제합니다.\",\n empty: \"아직 사용자 지정 보기가 없습니다.\",\n },\n customViewLoading: \"보기를 불러오는 중…\",\n customViewError: \"이 보기를 불러오지 못했습니다: {error}\",\n addViewPrompt:\n '{title} 컬렉션에 사용자 지정 보기를 추가하고 싶어요. 무엇을 보거나 편집하고 싶은지 먼저 물어본 뒤, {base}/views/your-view.html 에 HTML 보기 파일을 만들고 {base}/schema.json의 `views[]`에 등록해 주세요(읽기 전용 보기는 capabilities [\"read\"], 레코드를 편집하면 [\"read\",\"write\"]). 데이터 계약은 custom-view 도움말을 따르세요.',\n repair: \"복구\",\n dataIssuesDetected: \"{count}개의 레코드 파일에 데이터 문제가 있어 이 보기에 표시되지 않을 수 있습니다.\",\n repairPrompt:\n \"{title} 컬렉션에 표시되지 않는 원인이 되는 데이터 문제가 있는 레코드 파일이 {count}개 있습니다. 각 파일을 수정하세요 — Read로 파일을 읽고 수정한 뒤 Write로 다시 저장하세요:\\n{issues}\\n\\n완료되면 presentCollection을 호출하여 레코드가 로드되는지 확인하세요.\",\n source: {\n user: \"사용자\",\n project: \"프로젝트\",\n },\n },\n common: {\n close: \"닫기\",\n add: \"추가\",\n cancel: \"취소\",\n loading: \"불러오는 중...\",\n no: \"아니오\",\n remove: \"삭제\",\n save: \"저장\",\n saving: \"저장 중...\",\n yes: \"예\",\n },\n};\n\nexport default koMessages;\n","// Auto-extracted from the host src/lang/es.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst esMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"Colección\",\n addCollectionPrompt:\n \"Ayúdame a crear una nueva colección. Primero lee `config/helps/collection-skills.md` para conocer las convenciones de las colecciones basadas en esquemas. Luego usa la herramienta `presentForm` (no uses AskUserQuestion) para preguntarme qué tipo de datos quiero registrar, y crea el schema.json y el SKILL.md a partir de mis respuestas.\",\n addFeedTitle: \"Añadir un feed\",\n addFeedHint: \"Pega la URL de un feed o una API; la obtendré y deduciré el título y los campos por ti.\",\n addFeedPrompt:\n \"Añade una nueva fuente de datos (feed) desde esta URL: {url}\\n\\nPrimero lee `config/helps/feeds.md` y síguelo exactamente. Obtén esa URL tú mismo, inspecciona la respuesta para deducir un título adecuado y los campos, y luego crea `feeds/<slug>/schema.json` como indica la ayuda. NO me hagas preguntas: dedúcelo todo a partir de los datos. Al abrir el feed sus elementos se cargan automáticamente; cuando termines, dime que está registrado (no hace falta mencionar Refresh).\",\n title: \"Colecciones\",\n backToIndex: \"Volver a colecciones\",\n indexEmpty: \"No hay colecciones instaladas. Marca con estrella una skill que incluya un schema desde la página Skills para verla aquí.\",\n editItem: \"Editar\",\n openItem: \"Abrir {id}\",\n confirmDelete: \"¿Eliminar este elemento? Esta acción no se puede deshacer.\",\n deleteFeed: \"Eliminar feed\",\n confirmDeleteFeed: '¿Eliminar el feed \"{title}\" y todos sus registros descargados? Esta acción no se puede deshacer.',\n deleteCollection: \"Eliminar colección\",\n confirmDeleteCollection: '¿Eliminar toda la colección \"{title}\", incluidos todos sus registros? Antes se archiva una copia de seguridad restaurable.',\n itemsEmpty: \"Aún no hay elementos. Pulsa + para añadir uno.\",\n notFound: \"Colección no encontrada\",\n loadFailed: \"Error al cargar\",\n requiredField: \"Este campo es obligatorio\",\n selectPlaceholder: \"Seleccionar…\",\n inlineSaveFailed: \"No se pudo guardar el cambio: {error}\",\n addRow: \"Añadir fila\",\n removeRow: \"Quitar fila\",\n noRows: \"Aún no hay filas\",\n tableSummary: \"{count} elementos\",\n embedMissing: \"No se encontró el registro «{id}» en {collection}.\",\n embedCreate: \"Configurarlo\",\n searchPlaceholder: \"Buscar registros…\",\n searchSummary: \"Mostrando {shown} de {total}\",\n noMatchingItems: \"No hay elementos coincidentes\",\n clearSearch: \"Borrar búsqueda\",\n sortBy: \"Ordenar por {field}\",\n openCollection: \"Abrir {title}\",\n createTitle: \"Añadir nuevo\",\n derivedLabel: \"Derivado\",\n embedMissingTitle: \"Falta la referencia incrustada\",\n chat: \"Chat\",\n refreshFeed: \"Actualizar\",\n refreshFailed: \"Error al actualizar: {error}\",\n feedChatSeed:\n \"El feed «{slug}» está definido por el esquema `feeds/{slug}/schema.json` y sus registros se guardan en `{dataPath}/` (un archivo `<id>.json` por registro). Usa ese esquema y esos datos para responder a esta solicitud: {message}\",\n feedsTitle: \"Fuentes de datos\",\n feedsEmpty: \"Aún no hay fuentes registradas.\",\n chatTitle: \"Iniciar un chat\",\n chatPlaceholder: \"Describe qué quieres hacer con esta colección…\",\n chatStart: \"Iniciar chat\",\n viewToggle: \"Vista\",\n viewTable: \"Tabla\",\n viewCalendar: \"Calendario\",\n calendarFieldLabel: \"Campo de fecha del calendario\",\n calendarPrevMonth: \"Mes anterior\",\n calendarNextMonth: \"Mes siguiente\",\n calendarToday: \"Hoy\",\n calendarNoDate: \"Sin fecha\",\n calendarCreateOn: \"Crear el {date}\",\n dayViewOpen: \"Abrir vista de día para {date}\",\n dayViewAllDay: \"Todo el día\",\n dayViewEmpty: \"No hay elementos este día\",\n dayViewClose: \"Cerrar vista de día\",\n viewKanban: \"Kanban\",\n kanbanFieldLabel: \"Campo de agrupación Kanban\",\n kanbanUncategorized: \"Sin categoría\",\n kanbanOpenCard: \"Abrir {label}\",\n addView: \"Añadir vista\",\n config: {\n open: \"Ajustes de la colección\",\n title: \"{title} · ajustes\",\n viewsHeading: \"Vistas personalizadas\",\n deleteView: \"Eliminar {label}\",\n confirmDelete: '¿Eliminar la vista \"{label}\"? Se borrará su archivo HTML y se anulará su registro.',\n empty: \"Aún no hay vistas personalizadas.\",\n },\n customViewLoading: \"Cargando vista…\",\n customViewError: \"No se pudo cargar esta vista: {error}\",\n addViewPrompt:\n '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.',\n repair: \"Reparar\",\n dataIssuesDetected: \"{count} archivo(s) de registro tienen problemas de datos y podrían no aparecer en esta vista.\",\n repairPrompt:\n \"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.\",\n source: {\n user: \"Usuario\",\n project: \"Proyecto\",\n },\n },\n common: {\n close: \"Cerrar\",\n add: \"Añadir\",\n cancel: \"Cancelar\",\n loading: \"Cargando...\",\n no: \"No\",\n remove: \"Quitar\",\n save: \"Guardar\",\n saving: \"Guardando...\",\n yes: \"Sí\",\n },\n};\n\nexport default esMessages;\n","// Auto-extracted from the host src/lang/pt-BR.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst ptBRMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"Coleção\",\n addCollectionPrompt:\n \"Ajude-me a criar uma nova coleção. Primeiro leia `config/helps/collection-skills.md` para conhecer as convenções de coleções baseadas em esquema. Depois use a ferramenta `presentForm` (não use AskUserQuestion) para perguntar que tipo de dados quero acompanhar, e crie o schema.json e o SKILL.md a partir das minhas respostas.\",\n addFeedTitle: \"Adicionar um feed\",\n addFeedHint: \"Cole a URL de um feed ou API; vou buscá-la e deduzir o título e os campos para você.\",\n addFeedPrompt:\n \"Adicione um novo feed de fonte de dados a partir desta URL: {url}\\n\\nPrimeiro leia `config/helps/feeds.md` e siga-o exatamente. Busque essa URL você mesmo, inspecione a resposta para deduzir um título adequado e os campos, e então crie `feeds/<slug>/schema.json` como a ajuda descreve. NÃO me faça perguntas: deduza tudo a partir dos dados. Ao abrir o feed os itens são carregados automaticamente; quando terminar, diga-me que está registrado (não precisa mencionar Refresh).\",\n title: \"Coleções\",\n backToIndex: \"Voltar para coleções\",\n indexEmpty: \"Nenhuma coleção instalada. Marque com estrela uma skill que inclua um schema na página Skills para vê-la aqui.\",\n editItem: \"Editar\",\n openItem: \"Abrir {id}\",\n confirmDelete: \"Excluir este item? Esta ação não pode ser desfeita.\",\n deleteFeed: \"Excluir feed\",\n confirmDeleteFeed: 'Excluir o feed \"{title}\" e todos os seus registros baixados? Esta ação não pode ser desfeita.',\n deleteCollection: \"Excluir coleção\",\n confirmDeleteCollection: 'Excluir toda a coleção \"{title}\", incluindo todos os seus registros? Um backup restaurável é arquivado antes.',\n itemsEmpty: \"Ainda não há itens. Clique em + para adicionar um.\",\n notFound: \"Coleção não encontrada\",\n loadFailed: \"Falha ao carregar\",\n requiredField: \"Este campo é obrigatório\",\n selectPlaceholder: \"Selecionar…\",\n inlineSaveFailed: \"Não foi possível salvar a alteração: {error}\",\n addRow: \"Adicionar linha\",\n removeRow: \"Remover linha\",\n noRows: \"Ainda não há linhas\",\n tableSummary: \"{count} itens\",\n embedMissing: \"Nenhum registro '{id}' encontrado em {collection}.\",\n embedCreate: \"Configurar\",\n searchPlaceholder: \"Buscar registros…\",\n searchSummary: \"Mostrando {shown} de {total}\",\n noMatchingItems: \"Nenhum item correspondente\",\n clearSearch: \"Limpar busca\",\n sortBy: \"Ordenar por {field}\",\n openCollection: \"Abrir {title}\",\n createTitle: \"Adicionar novo\",\n derivedLabel: \"Derivado\",\n embedMissingTitle: \"Referência incorporada ausente\",\n chat: \"Chat\",\n refreshFeed: \"Atualizar\",\n refreshFailed: \"Falha ao atualizar: {error}\",\n feedChatSeed:\n 'O feed \"{slug}\" é definido pelo esquema `feeds/{slug}/schema.json` e seus registros ficam em `{dataPath}/` (um arquivo `<id>.json` por registro). Use esse esquema e esses dados para responder a esta solicitação: {message}',\n feedsTitle: \"Feeds de dados\",\n feedsEmpty: \"Nenhum feed registrado ainda.\",\n chatTitle: \"Iniciar um chat\",\n chatPlaceholder: \"Descreva o que você quer fazer com esta coleção…\",\n chatStart: \"Iniciar chat\",\n viewToggle: \"Visualização\",\n viewTable: \"Tabela\",\n viewCalendar: \"Calendário\",\n calendarFieldLabel: \"Campo de data do calendário\",\n calendarPrevMonth: \"Mês anterior\",\n calendarNextMonth: \"Próximo mês\",\n calendarToday: \"Hoje\",\n calendarNoDate: \"Sem data\",\n calendarCreateOn: \"Criar em {date}\",\n dayViewOpen: \"Abrir visualização do dia para {date}\",\n dayViewAllDay: \"Dia inteiro\",\n dayViewEmpty: \"Nenhum item neste dia\",\n dayViewClose: \"Fechar visualização do dia\",\n viewKanban: \"Kanban\",\n kanbanFieldLabel: \"Campo de agrupamento do Kanban\",\n kanbanUncategorized: \"Sem categoria\",\n kanbanOpenCard: \"Abrir {label}\",\n addView: \"Adicionar visualização\",\n config: {\n open: \"Configurações da coleção\",\n title: \"{title} · configurações\",\n viewsHeading: \"Visualizações personalizadas\",\n deleteView: \"Excluir {label}\",\n confirmDelete: 'Excluir a visualização \"{label}\"? Isso remove o arquivo HTML e cancela o registro.',\n empty: \"Ainda não há visualizações personalizadas.\",\n },\n customViewLoading: \"Carregando visualização…\",\n customViewError: \"Não foi possível carregar esta visualização: {error}\",\n addViewPrompt:\n '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.',\n repair: \"Reparar\",\n dataIssuesDetected: \"{count} arquivo(s) de registro têm problemas de dados e podem não aparecer nesta visualização.\",\n repairPrompt:\n \"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.\",\n source: {\n user: \"Usuário\",\n project: \"Projeto\",\n },\n },\n common: {\n close: \"Fechar\",\n add: \"Adicionar\",\n cancel: \"Cancelar\",\n loading: \"Carregando...\",\n no: \"Não\",\n remove: \"Remover\",\n save: \"Salvar\",\n saving: \"Salvando...\",\n yes: \"Sim\",\n },\n};\n\nexport default ptBRMessages;\n","// Auto-extracted from the host src/lang/fr.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst frMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"Collection\",\n addCollectionPrompt:\n \"Aide-moi à créer une nouvelle collection. Lis d'abord `config/helps/collection-skills.md` pour les conventions des collections basées sur un schéma. Utilise ensuite l'outil `presentForm` (n'utilise pas AskUserQuestion) pour me demander quel type de données je veux suivre, et crée le schema.json et le SKILL.md à partir de mes réponses.\",\n addFeedTitle: \"Ajouter un flux\",\n addFeedHint: \"Collez l'URL d'un flux ou d'une API ; je la récupère et déduis le titre et les champs pour vous.\",\n addFeedPrompt:\n \"Ajoute un nouveau flux de source de données depuis cette URL : {url}\\n\\nLis d'abord `config/helps/feeds.md` et suis-le exactement. Récupère cette URL toi-même, inspecte la réponse pour déduire un titre pertinent et les champs, puis crée `feeds/<slug>/schema.json` comme l'explique l'aide. Ne me pose AUCUNE question — déduis tout à partir des données. À l'ouverture du flux, ses éléments se chargent automatiquement ; quand tu as terminé, dis-moi qu'il est enregistré (inutile de mentionner Refresh).\",\n title: \"Collections\",\n backToIndex: \"Retour aux collections\",\n indexEmpty: \"Aucune collection installée. Mettez une étoile sur une compétence avec un schema depuis la page Skills pour la voir ici.\",\n editItem: \"Modifier\",\n openItem: \"Ouvrir {id}\",\n confirmDelete: \"Supprimer cet élément ? Cette action est irréversible.\",\n deleteFeed: \"Supprimer le flux\",\n confirmDeleteFeed: \"Supprimer le flux « {title} » et tous ses enregistrements récupérés ? Cette action est irréversible.\",\n deleteCollection: \"Supprimer la collection\",\n confirmDeleteCollection:\n \"Supprimer toute la collection « {title} », y compris tous ses enregistrements ? Une sauvegarde restaurable est archivée au préalable.\",\n itemsEmpty: \"Aucun élément pour l'instant. Cliquez sur + pour en ajouter un.\",\n notFound: \"Collection introuvable\",\n loadFailed: \"Échec du chargement\",\n requiredField: \"Ce champ est obligatoire\",\n selectPlaceholder: \"Sélectionner…\",\n inlineSaveFailed: \"Impossible d'enregistrer la modification : {error}\",\n addRow: \"Ajouter une ligne\",\n removeRow: \"Supprimer la ligne\",\n noRows: \"Aucune ligne pour l'instant\",\n tableSummary: \"{count} éléments\",\n embedMissing: \"Aucun enregistrement « {id} » trouvé dans {collection}.\",\n embedCreate: \"Le configurer\",\n searchPlaceholder: \"Rechercher des enregistrements…\",\n searchSummary: \"Affichage de {shown} sur {total}\",\n noMatchingItems: \"Aucun élément correspondant\",\n clearSearch: \"Effacer la recherche\",\n sortBy: \"Trier par {field}\",\n openCollection: \"Ouvrir {title}\",\n createTitle: \"Ajouter\",\n derivedLabel: \"Calculé\",\n embedMissingTitle: \"Référence intégrée manquante\",\n chat: \"Discussion\",\n refreshFeed: \"Actualiser\",\n refreshFailed: \"Échec de l'actualisation : {error}\",\n feedChatSeed:\n \"Le flux « {slug} » est défini par le schéma `feeds/{slug}/schema.json` et ses enregistrements se trouvent dans `{dataPath}/` (un fichier `<id>.json` par enregistrement). Utilise ce schéma et ces données pour répondre à cette demande : {message}\",\n feedsTitle: \"Flux de données\",\n feedsEmpty: \"Aucun flux enregistré pour le moment.\",\n chatTitle: \"Démarrer une discussion\",\n chatPlaceholder: \"Décrivez ce que vous voulez faire avec cette collection…\",\n chatStart: \"Démarrer la discussion\",\n viewToggle: \"Affichage\",\n viewTable: \"Tableau\",\n viewCalendar: \"Calendrier\",\n calendarFieldLabel: \"Champ de date du calendrier\",\n calendarPrevMonth: \"Mois précédent\",\n calendarNextMonth: \"Mois suivant\",\n calendarToday: \"Aujourd'hui\",\n calendarNoDate: \"Sans date\",\n calendarCreateOn: \"Créer le {date}\",\n dayViewOpen: \"Ouvrir la vue du jour pour {date}\",\n dayViewAllDay: \"Toute la journée\",\n dayViewEmpty: \"Aucun élément ce jour\",\n dayViewClose: \"Fermer la vue du jour\",\n viewKanban: \"Kanban\",\n kanbanFieldLabel: \"Champ de regroupement Kanban\",\n kanbanUncategorized: \"Non classé\",\n kanbanOpenCard: \"Ouvrir {label}\",\n addView: \"Ajouter une vue\",\n config: {\n open: \"Paramètres de la collection\",\n title: \"{title} · paramètres\",\n viewsHeading: \"Vues personnalisées\",\n deleteView: \"Supprimer {label}\",\n confirmDelete: \"Supprimer la vue « {label} » ? Cela supprime son fichier HTML et la désenregistre.\",\n empty: \"Aucune vue personnalisée pour le moment.\",\n },\n customViewLoading: \"Chargement de la vue…\",\n customViewError: \"Impossible de charger cette vue : {error}\",\n addViewPrompt:\n '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.',\n repair: \"Réparer\",\n dataIssuesDetected: \"{count} fichier(s) d'enregistrement présentent des problèmes de données et peuvent être absents de cette vue.\",\n repairPrompt:\n \"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.\",\n source: {\n user: \"Utilisateur\",\n project: \"Projet\",\n },\n },\n common: {\n close: \"Fermer\",\n add: \"Ajouter\",\n cancel: \"Annuler\",\n loading: \"Chargement...\",\n no: \"Non\",\n remove: \"Supprimer\",\n save: \"Enregistrer\",\n saving: \"Enregistrement...\",\n yes: \"Oui\",\n },\n};\n\nexport default frMessages;\n","// Auto-extracted from the host src/lang/de.ts during the collection-plugin\n// i18n migration. The plugin owns its own copy so it uses NO host i18n resources.\nimport type { CollectionMessages } from \"./en\";\n\nconst deMessages: CollectionMessages = {\n collectionsView: {\n addCollectionLabel: \"Sammlung\",\n addCollectionPrompt:\n \"Hilf mir, eine neue Sammlung zu erstellen. Lies zuerst `config/helps/collection-skills.md` für die Konventionen schemabasierter Sammlungen. Verwende dann das Tool `presentForm` (nutze nicht AskUserQuestion), um mich zu fragen, welche Art von Daten ich verfolgen möchte, und erstelle die schema.json und SKILL.md aus meinen Antworten.\",\n addFeedTitle: \"Feed hinzufügen\",\n addFeedHint: \"Füge die URL eines Feeds oder einer API ein; ich rufe sie ab und ermittle Titel und Felder für dich.\",\n addFeedPrompt:\n \"Füge einen neuen Datenquellen-Feed von dieser URL hinzu: {url}\\n\\nLies zuerst `config/helps/feeds.md` und befolge es genau. Rufe diese URL selbst ab, untersuche die Antwort, um einen sinnvollen Titel und die Felder abzuleiten, und erstelle dann `feeds/<slug>/schema.json` wie in der Hilfe beschrieben. Stelle mir KEINE Fragen - leite alles aus den Daten ab. Beim Öffnen des Feeds werden seine Einträge automatisch geladen; sag mir Bescheid, wenn er registriert ist (Refresh muss nicht erwähnt werden).\",\n title: \"Sammlungen\",\n backToIndex: \"Zurück zu Sammlungen\",\n indexEmpty: \"Keine Sammlungen installiert. Markiere auf der Skills-Seite eine Skill mit Schema, um sie hier zu sehen.\",\n editItem: \"Bearbeiten\",\n openItem: \"{id} öffnen\",\n confirmDelete: \"Diesen Eintrag löschen? Das kann nicht rückgängig gemacht werden.\",\n deleteFeed: \"Feed löschen\",\n confirmDeleteFeed: \"Den Feed {title} und alle abgerufenen Datensätze löschen? Dies kann nicht rückgängig gemacht werden.\",\n deleteCollection: \"Sammlung löschen\",\n confirmDeleteCollection: \"Die gesamte Sammlung „{title}“ einschließlich aller Datensätze löschen? Zuvor wird eine wiederherstellbare Sicherung archiviert.\",\n itemsEmpty: \"Noch keine Einträge. Klicke auf +, um einen hinzuzufügen.\",\n notFound: \"Sammlung nicht gefunden\",\n loadFailed: \"Laden fehlgeschlagen\",\n requiredField: \"Dieses Feld ist erforderlich\",\n selectPlaceholder: \"Auswählen…\",\n inlineSaveFailed: \"Änderung konnte nicht gespeichert werden: {error}\",\n addRow: \"Zeile hinzufügen\",\n removeRow: \"Zeile entfernen\",\n noRows: \"Noch keine Zeilen\",\n tableSummary: \"{count} Einträge\",\n embedMissing: \"Kein Datensatz {id} in {collection} gefunden.\",\n embedCreate: \"Einrichten\",\n searchPlaceholder: \"Datensätze suchen…\",\n searchSummary: \"{shown} von {total} werden angezeigt\",\n noMatchingItems: \"Keine passenden Einträge\",\n clearSearch: \"Suche zurücksetzen\",\n sortBy: \"Nach {field} sortieren\",\n openCollection: \"{title} öffnen\",\n createTitle: \"Neu hinzufügen\",\n derivedLabel: \"Abgeleitet\",\n embedMissingTitle: \"Eingebettete Referenz fehlt\",\n chat: \"Chat\",\n refreshFeed: \"Aktualisieren\",\n refreshFailed: \"Aktualisierung fehlgeschlagen: {error}\",\n feedChatSeed:\n \"Der Feed {slug} ist durch das Schema `feeds/{slug}/schema.json` definiert und seine Datensätze liegen in `{dataPath}/` (eine `<id>.json`-Datei pro Datensatz). Nutze dieses Schema und diese Daten, um auf die folgende Anfrage zu antworten: {message}\",\n feedsTitle: \"Datenquellen-Feeds\",\n feedsEmpty: \"Noch keine Feeds registriert.\",\n chatTitle: \"Chat starten\",\n chatPlaceholder: \"Beschreibe, was du mit dieser Sammlung tun möchtest…\",\n chatStart: \"Chat starten\",\n viewToggle: \"Ansicht\",\n viewTable: \"Tabelle\",\n viewCalendar: \"Kalender\",\n calendarFieldLabel: \"Kalender-Datumsfeld\",\n calendarPrevMonth: \"Voriger Monat\",\n calendarNextMonth: \"Nächster Monat\",\n calendarToday: \"Heute\",\n calendarNoDate: \"Kein Datum\",\n calendarCreateOn: \"Am {date} erstellen\",\n dayViewOpen: \"Tagesansicht für {date} öffnen\",\n dayViewAllDay: \"Ganztägig\",\n dayViewEmpty: \"Keine Einträge an diesem Tag\",\n dayViewClose: \"Tagesansicht schließen\",\n viewKanban: \"Kanban\",\n kanbanFieldLabel: \"Kanban-Gruppierungsfeld\",\n kanbanUncategorized: \"Nicht kategorisiert\",\n kanbanOpenCard: \"{label} öffnen\",\n addView: \"Ansicht hinzufügen\",\n config: {\n open: \"Sammlungseinstellungen\",\n title: \"{title} · Einstellungen\",\n viewsHeading: \"Benutzerdefinierte Ansichten\",\n deleteView: \"{label} löschen\",\n confirmDelete: \"Ansicht {label} löschen? Die HTML-Datei wird entfernt und die Registrierung aufgehoben.\",\n empty: \"Noch keine benutzerdefinierten Ansichten.\",\n },\n customViewLoading: \"Ansicht wird geladen…\",\n customViewError: \"Diese Ansicht konnte nicht geladen werden: {error}\",\n addViewPrompt:\n '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.',\n repair: \"Reparieren\",\n dataIssuesDetected: \"{count} Datensatzdatei(en) haben Datenprobleme und fehlen möglicherweise in dieser Ansicht.\",\n repairPrompt:\n \"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.\",\n source: {\n user: \"Benutzer\",\n project: \"Projekt\",\n },\n },\n common: {\n close: \"Schließen\",\n add: \"Hinzufügen\",\n cancel: \"Abbrechen\",\n loading: \"Wird geladen...\",\n no: \"Nein\",\n remove: \"Entfernen\",\n save: \"Speichern\",\n saving: \"Wird gespeichert...\",\n yes: \"Ja\",\n },\n};\n\nexport default deMessages;\n","// The collection plugin's OWN vue-i18n instance — fully self-contained, sharing\n// no i18n resources with the host. Components call `useT()` (the `t` function)\n// and `useLocale()` instead of vue-i18n's `useI18n()`, so the keys\n// (`collectionsView.*`, `common.*`) stay identical — only the source changes.\n//\n// The active locale is fed through the CollectionUi binding (`localeTag()`), not\n// gui-chat-protocol's PLUGIN_RUNTIME_KEY: the collection pages mount both inside\n// chat (where the runtime exists) AND on standalone routes (where it doesn't),\n// and the binding is available in both. One detached, app-lifetime effect keeps\n// this instance's locale in step with the host's.\n\nimport { createI18n } from \"vue-i18n\";\nimport { effectScope, watchEffect } from \"vue\";\nimport { collectionUi } from \"../uiContext\";\nimport enMessages, { type CollectionMessages } from \"./en\";\nimport jaMessages from \"./ja\";\nimport zhMessages from \"./zh\";\nimport koMessages from \"./ko\";\nimport esMessages from \"./es\";\nimport ptBRMessages from \"./ptBR\";\nimport frMessages from \"./fr\";\nimport deMessages from \"./de\";\n\nconst i18n = createI18n<[CollectionMessages], string, false>({\n legacy: false,\n locale: \"en\",\n fallbackLocale: \"en\",\n messages: {\n en: enMessages,\n ja: jaMessages,\n zh: zhMessages,\n ko: koMessages,\n es: esMessages,\n \"pt-BR\": ptBRMessages,\n fr: frMessages,\n de: deMessages,\n },\n});\n\nconst syncScope = effectScope(true);\nlet syncing = false;\n\n/** Mirror this instance's locale to the host's (via the binding) exactly once,\n * in a detached effect so it lives for the app's lifetime rather than a single\n * component's. Called lazily on the first `useT()` — by then App.vue's setup has\n * configured the binding, so `collectionUi()` resolves. */\nfunction ensureLocaleSync(): void {\n if (syncing) return;\n // Flip the flag only after the effect is wired — if the first locale read\n // throws (e.g. the binding isn't configured yet), a later call can retry\n // rather than being locked out forever.\n syncScope.run(() => {\n watchEffect(() => {\n i18n.global.locale.value = collectionUi().localeTag();\n });\n });\n syncing = true;\n}\n\n/** The plugin's i18n composable — a drop-in for vue-i18n's `useI18n()` over the\n * plugin's own self-contained instance. Returns `{ t, locale }` (destructured at\n * the call site, exactly like `useI18n()`), with `t` reading the plugin's keys\n * and `locale` the reactive tag for date/number formatting. */\nexport function useCollectionI18n(): { t: (typeof i18n.global)[\"t\"]; locale: (typeof i18n.global)[\"locale\"] } {\n ensureLocaleSync();\n return { t: i18n.global.t, locale: i18n.global.locale };\n}\n","<template>\n <!-- Found: the whole card links to the embedded record's detail view,\n like a normal `ref` link (record → record hop). -->\n <router-link\n v-if=\"view.found\"\n :to=\"{ path: `/collections/${view.targetSlug}`, query: { selected: view.recordId } }\"\n class=\"group block relative rounded-xl border border-slate-200 bg-slate-50/50 p-4 pl-5 space-y-3 hover:bg-indigo-50/20 hover:border-indigo-200 transition-all duration-300 shadow-sm\"\n :data-testid=\"`collections-embed-${fieldKey}`\"\n >\n <!-- Left Accent Stripe -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-indigo-500 rounded-l-xl transition-all duration-300 group-hover:w-1.5 group-hover:bg-indigo-600\"></div>\n\n <!-- Header Reference Badge -->\n <div class=\"flex items-center justify-between text-[10px] font-bold text-indigo-600/90 tracking-wider uppercase\">\n <div class=\"flex items-center gap-1.5\">\n <span class=\"material-icons text-sm\">link</span>\n <span>{{ view.targetSlug }}</span>\n </div>\n <span class=\"bg-indigo-100/60 text-indigo-700 px-1.5 py-0.5 rounded font-mono font-medium lowercase\">{{ view.recordId }}</span>\n </div>\n\n <!-- Grid-based detail fields -->\n <div class=\"grid gap-x-4 gap-y-3 grid-cols-2\">\n <div v-for=\"row in view.rows\" :key=\"row.key\" class=\"space-y-0.5\">\n <div class=\"text-[10px] font-semibold text-slate-400 uppercase tracking-wide\">{{ row.label }}</div>\n <div class=\"text-xs text-slate-700 font-medium break-words\" :data-testid=\"`collections-embed-${fieldKey}-${row.key}`\">\n <template v-if=\"row.type === 'boolean'\">\n <span v-if=\"row.value === true\" class=\"material-icons text-emerald-600 text-sm align-middle\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph, same treatment as the other read-only detail branches. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <p v-else-if=\"row.type === 'markdown'\" class=\"whitespace-pre-wrap font-normal text-slate-600\">{{ row.display }}</p>\n <span v-else>{{ row.display }}</span>\n </div>\n </div>\n </div>\n </router-link>\n\n <div v-else class=\"relative rounded-xl border border-red-100 bg-red-50/30 p-4 pl-5 shadow-sm\" :data-testid=\"`collections-embed-${fieldKey}`\">\n <!-- Left Accent Stripe for Error/Missing -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-red-400 rounded-l-xl\"></div>\n <div class=\"flex items-start gap-3\">\n <span class=\"material-icons text-red-500 text-lg mt-0.5\">error_outline</span>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-xs font-semibold text-red-800 uppercase tracking-wider mb-1\">{{ t(\"collectionsView.embedMissingTitle\") }}</p>\n <p class=\"text-xs text-red-600\" :data-testid=\"`collections-embed-missing-${fieldKey}`\">\n {{ t(\"collectionsView.embedMissing\", { collection: view.targetSlug, id: view.recordId }) }}\n </p>\n <router-link\n v-if=\"view.targetSlug\"\n :to=\"{ path: `/collections/${view.targetSlug}` }\"\n class=\"inline-flex items-center gap-0.5 text-xs text-indigo-600 hover:text-indigo-800 font-semibold mt-2 hover:underline\"\n >\n <span>{{ t(\"collectionsView.embedCreate\") }}</span>\n <span class=\"material-icons text-xs\">arrow_forward</span>\n </router-link>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n// `<router-link>` is a host-provided global (vue-router). MulmoClaude registers\n// it app-wide; a host embedding this card must do the same and own a\n// `/collections/:slug` route. Translation keys resolve through the plugin's own\n// `useCollectionI18n()` instance (self-contained); a host only feeds the active\n// locale via `collectionUi().localeTag()`.\nimport { useCollectionI18n } from \"../lang\";\nimport type { EmbedView } from \"../../core/uiTypes\";\n\ndefineProps<{ view: EmbedView; fieldKey: string }>();\n\nconst { t } = useCollectionI18n();\n</script>\n","<template>\n <!-- Found: the whole card links to the embedded record's detail view,\n like a normal `ref` link (record → record hop). -->\n <router-link\n v-if=\"view.found\"\n :to=\"{ path: `/collections/${view.targetSlug}`, query: { selected: view.recordId } }\"\n class=\"group block relative rounded-xl border border-slate-200 bg-slate-50/50 p-4 pl-5 space-y-3 hover:bg-indigo-50/20 hover:border-indigo-200 transition-all duration-300 shadow-sm\"\n :data-testid=\"`collections-embed-${fieldKey}`\"\n >\n <!-- Left Accent Stripe -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-indigo-500 rounded-l-xl transition-all duration-300 group-hover:w-1.5 group-hover:bg-indigo-600\"></div>\n\n <!-- Header Reference Badge -->\n <div class=\"flex items-center justify-between text-[10px] font-bold text-indigo-600/90 tracking-wider uppercase\">\n <div class=\"flex items-center gap-1.5\">\n <span class=\"material-icons text-sm\">link</span>\n <span>{{ view.targetSlug }}</span>\n </div>\n <span class=\"bg-indigo-100/60 text-indigo-700 px-1.5 py-0.5 rounded font-mono font-medium lowercase\">{{ view.recordId }}</span>\n </div>\n\n <!-- Grid-based detail fields -->\n <div class=\"grid gap-x-4 gap-y-3 grid-cols-2\">\n <div v-for=\"row in view.rows\" :key=\"row.key\" class=\"space-y-0.5\">\n <div class=\"text-[10px] font-semibold text-slate-400 uppercase tracking-wide\">{{ row.label }}</div>\n <div class=\"text-xs text-slate-700 font-medium break-words\" :data-testid=\"`collections-embed-${fieldKey}-${row.key}`\">\n <template v-if=\"row.type === 'boolean'\">\n <span v-if=\"row.value === true\" class=\"material-icons text-emerald-600 text-sm align-middle\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph, same treatment as the other read-only detail branches. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <p v-else-if=\"row.type === 'markdown'\" class=\"whitespace-pre-wrap font-normal text-slate-600\">{{ row.display }}</p>\n <span v-else>{{ row.display }}</span>\n </div>\n </div>\n </div>\n </router-link>\n\n <div v-else class=\"relative rounded-xl border border-red-100 bg-red-50/30 p-4 pl-5 shadow-sm\" :data-testid=\"`collections-embed-${fieldKey}`\">\n <!-- Left Accent Stripe for Error/Missing -->\n <div class=\"absolute left-0 top-0 bottom-0 w-1 bg-red-400 rounded-l-xl\"></div>\n <div class=\"flex items-start gap-3\">\n <span class=\"material-icons text-red-500 text-lg mt-0.5\">error_outline</span>\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-xs font-semibold text-red-800 uppercase tracking-wider mb-1\">{{ t(\"collectionsView.embedMissingTitle\") }}</p>\n <p class=\"text-xs text-red-600\" :data-testid=\"`collections-embed-missing-${fieldKey}`\">\n {{ t(\"collectionsView.embedMissing\", { collection: view.targetSlug, id: view.recordId }) }}\n </p>\n <router-link\n v-if=\"view.targetSlug\"\n :to=\"{ path: `/collections/${view.targetSlug}` }\"\n class=\"inline-flex items-center gap-0.5 text-xs text-indigo-600 hover:text-indigo-800 font-semibold mt-2 hover:underline\"\n >\n <span>{{ t(\"collectionsView.embedCreate\") }}</span>\n <span class=\"material-icons text-xs\">arrow_forward</span>\n </router-link>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n// `<router-link>` is a host-provided global (vue-router). MulmoClaude registers\n// it app-wide; a host embedding this card must do the same and own a\n// `/collections/:slug` route. Translation keys resolve through the plugin's own\n// `useCollectionI18n()` instance (self-contained); a host only feeds the active\n// locale via `collectionUi().localeTag()`.\nimport { useCollectionI18n } from \"../lang\";\nimport type { EmbedView } from \"../../core/uiTypes\";\n\ndefineProps<{ view: EmbedView; fieldKey: string }>();\n\nconst { t } = useCollectionI18n();\n</script>\n","<template>\n <div class=\"flex flex-col gap-3\" data-testid=\"collection-calendar\">\n <!-- Month nav -->\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarPrevMonth')\"\n data-testid=\"collection-calendar-prev\"\n @click=\"stepMonth(-1)\"\n >\n <span class=\"material-icons text-lg\">chevron_left</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarNextMonth')\"\n data-testid=\"collection-calendar-next\"\n @click=\"stepMonth(1)\"\n >\n <span class=\"material-icons text-lg\">chevron_right</span>\n </button>\n <h3 class=\"text-sm font-bold text-slate-800 flex-1\" data-testid=\"collection-calendar-month\">{{ monthLabel }}</h3>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors\"\n data-testid=\"collection-calendar-today\"\n @click=\"goToday\"\n >\n {{ t(\"collectionsView.calendarToday\") }}\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none\">\n <div v-for=\"(label, idx) in weekdayLabels\" :key=\"idx\" class=\"px-1 py-1 text-center\">{{ label }}</div>\n </div>\n\n <!-- Day grid. Every cell is a keyboard-operable button that opens the day\n (time-allocation) view; its record chips are nested interactive\n elements that `@click.stop` to select instead. Creating a record now\n happens from inside the day view's + button. -->\n <div class=\"grid grid-cols-7 gap-1\">\n <div\n v-for=\"{ cell, entries } in cells\"\n :key=\"cell.key\"\n class=\"min-h-[5.5rem] rounded-lg border p-1 flex flex-col gap-1 overflow-hidden transition-colors cursor-pointer hover:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n :class=\"cell.inMonth ? 'bg-white border-slate-200' : 'bg-slate-50/50 border-slate-100'\"\n role=\"button\"\n :tabindex=\"0\"\n :aria-label=\"t('collectionsView.dayViewOpen', { date: cell.key })\"\n :data-testid=\"`collection-calendar-day-${cell.key}`\"\n @click=\"emit('openDay', cell.ymd)\"\n @keydown.enter.self.prevent=\"emit('openDay', cell.ymd)\"\n @keydown.space.self.prevent=\"emit('openDay', cell.ymd)\"\n >\n <div class=\"flex items-center justify-end\">\n <span\n class=\"text-[11px] font-bold h-5 min-w-5 px-1 inline-flex items-center justify-center rounded-full\"\n :class=\"cell.key === todayKey ? 'bg-indigo-600 text-white' : cell.inMonth ? 'text-slate-500' : 'text-slate-300'\"\n >{{ cell.ymd.day }}</span\n >\n </div>\n <button\n v-for=\"entry in entries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-left text-[11px] leading-tight font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, DAY_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-chip-${entry.id}`\"\n @click.stop=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Records with no usable anchor date — listed rather than dropped. -->\n <div v-if=\"bucketed.noDate.length > 0\" class=\"flex flex-wrap items-center gap-1.5 pt-1\" data-testid=\"collection-calendar-no-date\">\n <span class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider mr-1\">{{ t(\"collectionsView.calendarNoDate\") }}</span>\n <button\n v-for=\"entry in undatedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-[11px] font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, UNDATED_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-undated-${entry.id}`\"\n @click=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { bucketRecords, buildMonthGrid, ymdKey, daySlice, MINUTES_PER_DAY, type Ymd, type RecordSpan, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n /** The `date`/`datetime` field whose value places each record on the grid. */\n anchorField: string;\n /** Optional second `date`/`datetime` field — records span anchor→end inclusive. */\n endField?: string;\n /** Optional free-form time-string field driving the day (time-allocation) view. */\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour.\n * Empty / unset → the default indigo styling. */\n colorField?: string;\n /** Primary-key of the currently-open record (highlighted chip). */\n selected?: string;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** A day cell was activated → the host opens the time-allocation popup. */\n openDay: [day: Ymd];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n\n// Visible month, 1-12. Initial value is the current local month — app\n// code, so `new Date()` is fine (the pure grid helpers stay clock-free).\nconst now = new Date();\nconst viewYear = ref(now.getFullYear());\nconst viewMonth = ref(now.getMonth() + 1);\n\nconst todayKey = ymdKey({ year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() });\n\nconst grid = computed(() => buildMonthGrid(viewYear.value, viewMonth.value));\n\nconst bucketed = computed(() => bucketRecords(props.items, props.anchorField, props.endField, props.timeField));\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\ninterface CalendarEntry {\n id: string;\n label: string;\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\ninterface DayPair {\n span: RecordSpan<CollectionItem>;\n slice: DaySlice;\n}\n\n/** Sort key for ordering a day's chips by start time: earliest first, with\n * clock-less all-day records sinking to the bottom (matching the day view). */\nfunction sliceStartKey(slice: DaySlice): number {\n return slice.kind === \"allDay\" ? MINUTES_PER_DAY + 1 : slice.startMin;\n}\n\n/** Records whose span covers a given day, ordered by start time so the month\n * grid stacks chips the same way the day (time-allocation) view does. */\nfunction recordsOnDay(day: Ymd): CalendarEntry[] {\n return bucketed.value.spans\n .map((span) => ({ span, slice: daySlice(span, day) }))\n .filter((pair): pair is DayPair => pair.slice !== null)\n .sort((left, right) => sliceStartKey(left.slice) - sliceStartKey(right.slice))\n .map(({ span }) => ({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n color: colorOf(span.item),\n }));\n}\n\n/** Grid cells paired with the records that land on them, computed once per\n * render. Clicking any cell opens the day view (create happens there). */\nconst cells = computed(() => grid.value.map((cell) => ({ cell, entries: recordsOnDay(cell.ymd) })));\n\nconst undatedEntries = computed<CalendarEntry[]>(() =>\n bucketed.value.noDate.map((item) => ({\n id: itemIdOf(item, props.schema),\n label: itemLabelOf(item, props.schema, labelField.value),\n color: colorOf(item),\n })),\n);\n\nconst DAY_CHIP_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-100 hover:bg-indigo-100\";\nconst UNDATED_CHIP_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\n/** Chip classes: the selected chip keeps the solid indigo highlight; otherwise\n * a record with a resolved colour tints the chip, and one with none (no colour\n * field) falls back to `uncolored`. */\nfunction chipClass(entry: CalendarEntry, uncolored: string): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return uncolored;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nconst monthLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(viewYear.value, viewMonth.value - 1, 1)),\n );\n } catch {\n return `${viewYear.value}-${String(viewMonth.value).padStart(2, \"0\")}`;\n }\n});\n\n/** Localized short weekday names, Sunday-first (matches the grid). */\nconst weekdayLabels = computed<string[]>(() => {\n try {\n const formatter = new Intl.DateTimeFormat(locale.value, { weekday: \"short\", timeZone: \"UTC\" });\n // 2024-01-07 is a Sunday — anchor the week there.\n return Array.from({ length: 7 }, (_, idx) => formatter.format(new Date(Date.UTC(2024, 0, 7 + idx))));\n } catch {\n return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n }\n});\n\nfunction stepMonth(delta: number): void {\n const next = viewMonth.value + delta;\n if (next < 1) {\n viewMonth.value = 12;\n viewYear.value -= 1;\n } else if (next > 12) {\n viewMonth.value = 1;\n viewYear.value += 1;\n } else {\n viewMonth.value = next;\n }\n}\n\nfunction goToday(): void {\n viewYear.value = now.getFullYear();\n viewMonth.value = now.getMonth() + 1;\n}\n</script>\n","<template>\n <div class=\"flex flex-col gap-3\" data-testid=\"collection-calendar\">\n <!-- Month nav -->\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarPrevMonth')\"\n data-testid=\"collection-calendar-prev\"\n @click=\"stepMonth(-1)\"\n >\n <span class=\"material-icons text-lg\">chevron_left</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarNextMonth')\"\n data-testid=\"collection-calendar-next\"\n @click=\"stepMonth(1)\"\n >\n <span class=\"material-icons text-lg\">chevron_right</span>\n </button>\n <h3 class=\"text-sm font-bold text-slate-800 flex-1\" data-testid=\"collection-calendar-month\">{{ monthLabel }}</h3>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 text-xs font-bold transition-colors\"\n data-testid=\"collection-calendar-today\"\n @click=\"goToday\"\n >\n {{ t(\"collectionsView.calendarToday\") }}\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none\">\n <div v-for=\"(label, idx) in weekdayLabels\" :key=\"idx\" class=\"px-1 py-1 text-center\">{{ label }}</div>\n </div>\n\n <!-- Day grid. Every cell is a keyboard-operable button that opens the day\n (time-allocation) view; its record chips are nested interactive\n elements that `@click.stop` to select instead. Creating a record now\n happens from inside the day view's + button. -->\n <div class=\"grid grid-cols-7 gap-1\">\n <div\n v-for=\"{ cell, entries } in cells\"\n :key=\"cell.key\"\n class=\"min-h-[5.5rem] rounded-lg border p-1 flex flex-col gap-1 overflow-hidden transition-colors cursor-pointer hover:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/30\"\n :class=\"cell.inMonth ? 'bg-white border-slate-200' : 'bg-slate-50/50 border-slate-100'\"\n role=\"button\"\n :tabindex=\"0\"\n :aria-label=\"t('collectionsView.dayViewOpen', { date: cell.key })\"\n :data-testid=\"`collection-calendar-day-${cell.key}`\"\n @click=\"emit('openDay', cell.ymd)\"\n @keydown.enter.self.prevent=\"emit('openDay', cell.ymd)\"\n @keydown.space.self.prevent=\"emit('openDay', cell.ymd)\"\n >\n <div class=\"flex items-center justify-end\">\n <span\n class=\"text-[11px] font-bold h-5 min-w-5 px-1 inline-flex items-center justify-center rounded-full\"\n :class=\"cell.key === todayKey ? 'bg-indigo-600 text-white' : cell.inMonth ? 'text-slate-500' : 'text-slate-300'\"\n >{{ cell.ymd.day }}</span\n >\n </div>\n <button\n v-for=\"entry in entries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-left text-[11px] leading-tight font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, DAY_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-chip-${entry.id}`\"\n @click.stop=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Records with no usable anchor date — listed rather than dropped. -->\n <div v-if=\"bucketed.noDate.length > 0\" class=\"flex flex-wrap items-center gap-1.5 pt-1\" data-testid=\"collection-calendar-no-date\">\n <span class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider mr-1\">{{ t(\"collectionsView.calendarNoDate\") }}</span>\n <button\n v-for=\"entry in undatedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"text-[11px] font-semibold truncate rounded px-1.5 py-0.5 border transition-colors\"\n :class=\"chipClass(entry, UNDATED_CHIP_DEFAULT)\"\n :data-testid=\"`collection-calendar-undated-${entry.id}`\"\n @click=\"emit('select', entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { bucketRecords, buildMonthGrid, ymdKey, daySlice, MINUTES_PER_DAY, type Ymd, type RecordSpan, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n /** The `date`/`datetime` field whose value places each record on the grid. */\n anchorField: string;\n /** Optional second `date`/`datetime` field — records span anchor→end inclusive. */\n endField?: string;\n /** Optional free-form time-string field driving the day (time-allocation) view. */\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour.\n * Empty / unset → the default indigo styling. */\n colorField?: string;\n /** Primary-key of the currently-open record (highlighted chip). */\n selected?: string;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** A day cell was activated → the host opens the time-allocation popup. */\n openDay: [day: Ymd];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n\n// Visible month, 1-12. Initial value is the current local month — app\n// code, so `new Date()` is fine (the pure grid helpers stay clock-free).\nconst now = new Date();\nconst viewYear = ref(now.getFullYear());\nconst viewMonth = ref(now.getMonth() + 1);\n\nconst todayKey = ymdKey({ year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate() });\n\nconst grid = computed(() => buildMonthGrid(viewYear.value, viewMonth.value));\n\nconst bucketed = computed(() => bucketRecords(props.items, props.anchorField, props.endField, props.timeField));\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\ninterface CalendarEntry {\n id: string;\n label: string;\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\ninterface DayPair {\n span: RecordSpan<CollectionItem>;\n slice: DaySlice;\n}\n\n/** Sort key for ordering a day's chips by start time: earliest first, with\n * clock-less all-day records sinking to the bottom (matching the day view). */\nfunction sliceStartKey(slice: DaySlice): number {\n return slice.kind === \"allDay\" ? MINUTES_PER_DAY + 1 : slice.startMin;\n}\n\n/** Records whose span covers a given day, ordered by start time so the month\n * grid stacks chips the same way the day (time-allocation) view does. */\nfunction recordsOnDay(day: Ymd): CalendarEntry[] {\n return bucketed.value.spans\n .map((span) => ({ span, slice: daySlice(span, day) }))\n .filter((pair): pair is DayPair => pair.slice !== null)\n .sort((left, right) => sliceStartKey(left.slice) - sliceStartKey(right.slice))\n .map(({ span }) => ({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n color: colorOf(span.item),\n }));\n}\n\n/** Grid cells paired with the records that land on them, computed once per\n * render. Clicking any cell opens the day view (create happens there). */\nconst cells = computed(() => grid.value.map((cell) => ({ cell, entries: recordsOnDay(cell.ymd) })));\n\nconst undatedEntries = computed<CalendarEntry[]>(() =>\n bucketed.value.noDate.map((item) => ({\n id: itemIdOf(item, props.schema),\n label: itemLabelOf(item, props.schema, labelField.value),\n color: colorOf(item),\n })),\n);\n\nconst DAY_CHIP_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-100 hover:bg-indigo-100\";\nconst UNDATED_CHIP_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\n/** Chip classes: the selected chip keeps the solid indigo highlight; otherwise\n * a record with a resolved colour tints the chip, and one with none (no colour\n * field) falls back to `uncolored`. */\nfunction chipClass(entry: CalendarEntry, uncolored: string): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return uncolored;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nconst monthLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(viewYear.value, viewMonth.value - 1, 1)),\n );\n } catch {\n return `${viewYear.value}-${String(viewMonth.value).padStart(2, \"0\")}`;\n }\n});\n\n/** Localized short weekday names, Sunday-first (matches the grid). */\nconst weekdayLabels = computed<string[]>(() => {\n try {\n const formatter = new Intl.DateTimeFormat(locale.value, { weekday: \"short\", timeZone: \"UTC\" });\n // 2024-01-07 is a Sunday — anchor the week there.\n return Array.from({ length: 7 }, (_, idx) => formatter.format(new Date(Date.UTC(2024, 0, 7 + idx))));\n } catch {\n return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n }\n});\n\nfunction stepMonth(delta: number): void {\n const next = viewMonth.value + delta;\n if (next < 1) {\n viewMonth.value = 12;\n viewYear.value -= 1;\n } else if (next > 12) {\n viewMonth.value = 1;\n viewYear.value += 1;\n } else {\n viewMonth.value = next;\n }\n}\n\nfunction goToday(): void {\n viewYear.value = now.getFullYear();\n viewMonth.value = now.getMonth() + 1;\n}\n</script>\n","<template>\n <!-- Modal overlay: a time-allocation view of one day. Backdrop click and\n Escape close it. Selecting a record expands the modal to two columns —\n the timeline on the left, the record's detail (the `#detail` slot) on\n the right. -->\n <div\n class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\"\n data-testid=\"collection-day-view\"\n @click.self=\"emit('close')\"\n @keydown.esc=\"emit('close')\"\n >\n <div\n ref=\"dialogEl\"\n tabindex=\"-1\"\n class=\"flex max-h-[85vh] w-full flex-row rounded-2xl bg-white shadow-xl focus:outline-none\"\n :class=\"showDetail ? 'max-w-4xl' : 'max-w-md'\"\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <!-- Left column: the time-allocation timeline. Shrinks to a fixed width\n when a record detail is shown alongside it, else fills the modal. -->\n <div class=\"flex min-h-0 flex-col\" :class=\"showDetail ? 'w-80 shrink-0 border-r border-slate-200' : 'w-full'\">\n <!-- Header -->\n <div class=\"flex items-center gap-2 border-b border-slate-200 px-4 py-3\">\n <h3 class=\"flex-1 text-sm font-bold text-slate-800\" data-testid=\"collection-day-view-title\">{{ dayLabel }}</h3>\n <button\n v-if=\"canCreate\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarCreateOn', { date: dayKey })\"\n data-testid=\"collection-day-view-create\"\n @click=\"onCreate\"\n >\n <span class=\"material-icons text-lg\">add</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.dayViewClose')\"\n data-testid=\"collection-day-view-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n\n <!-- Empty state -->\n <div v-if=\"timedEntries.length === 0 && allDayEntries.length === 0\" class=\"px-4 py-10 text-center text-sm text-slate-400\">\n {{ t(\"collectionsView.dayViewEmpty\") }}\n </div>\n\n <!-- Timeline -->\n <div v-else ref=\"scrollEl\" class=\"flex-1 overflow-y-auto px-2 py-2\">\n <div class=\"relative\" :style=\"{ height: `${TOTAL_HEIGHT}px` }\" data-testid=\"collection-day-view-timeline\">\n <!-- Hour gridlines + labels -->\n <div v-for=\"hour in 24\" :key=\"hour\" class=\"absolute left-0 right-0 border-t border-slate-100\" :style=\"{ top: `${(hour - 1) * HOUR_PX}px` }\">\n <span class=\"absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400\">{{ hourLabel(hour - 1) }}</span>\n </div>\n\n <!-- Event track (right of the hour gutter) -->\n <div class=\"absolute inset-y-0 right-0\" style=\"left: 2.75rem\">\n <button\n v-for=\"entry in timedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"absolute overflow-hidden rounded border px-1.5 py-0.5 text-left transition-colors\"\n :class=\"timedChipClass(entry)\"\n :style=\"entry.style\"\n :data-testid=\"`collection-day-view-chip-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n <span class=\"block truncate text-[11px] font-semibold leading-tight\">\n <span v-if=\"entry.slice.bleedsBefore\" aria-hidden=\"true\">▲ </span>{{ entry.label\n }}<span v-if=\"entry.slice.bleedsAfter\" aria-hidden=\"true\"> ▼</span>\n </span>\n <!-- A few non-date/time fields under the title. The chip's height\n stays proportional to its duration; extra lines just clip. -->\n <span v-for=\"(text, i) in entry.secondary\" :key=\"i\" class=\"block truncate text-[10px] leading-tight opacity-70\">{{ text }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- All-day strip (records with no clock) at the bottom -->\n <div\n v-if=\"allDayEntries.length > 0\"\n class=\"flex flex-wrap items-center gap-1.5 border-t border-slate-200 px-4 py-2\"\n data-testid=\"collection-day-view-all-day\"\n >\n <span class=\"mr-1 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.dayViewAllDay\") }}</span>\n <button\n v-for=\"entry in allDayEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"truncate rounded border px-1.5 py-0.5 text-[11px] font-semibold transition-colors\"\n :class=\"allDayChipClass(entry)\"\n :data-testid=\"`collection-day-view-allday-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Right column: the selected (or being-created) record's detail panel,\n supplied by the host so selection no longer hands off to a panel\n below the calendar. -->\n <div v-if=\"showDetail\" class=\"min-w-0 flex-1 overflow-y-auto\" data-testid=\"collection-day-view-detail\">\n <slot name=\"detail\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { bucketRecords, daySlice, assignLanes, ymdKey, MINUTES_PER_DAY, type Ymd, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n day: Ymd;\n anchorField: string;\n endField?: string;\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour\n * (matching the month view). Empty / unset → default indigo/slate styling. */\n colorField?: string;\n selected?: string;\n canCreate: boolean;\n /** When true, expand the modal to two columns and render the `#detail`\n * slot (the selected/created record) to the right of the timeline. */\n showDetail?: boolean;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n createOn: [iso: string];\n close: [];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n\n// One hour = 48px tall; the full day is 24 of them. A point-in-time event\n// (start, no end) has no duration to size by, so it gets a fixed one-line-tall\n// box (`LINE_PX`) — enough to read its time + label — and a `LANE_MIN_MINUTES`\n// footprint so two near-simultaneous events still split into lanes.\nconst HOUR_PX = 48;\nconst TOTAL_HEIGHT = HOUR_PX * 24;\nconst PX_PER_MIN = HOUR_PX / 60;\nconst MIN_BLOCK_PX = 16;\nconst LINE_PX = 20;\nconst LANE_MIN_MINUTES = 30;\n\nconst scrollEl = ref<HTMLElement | null>(null);\nconst dialogEl = ref<HTMLElement | null>(null);\n\nconst dayKey = computed<string>(() => ymdKey(props.day));\n\nconst dayLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(props.day.year, props.day.month - 1, props.day.day)),\n );\n } catch {\n return dayKey.value;\n }\n});\n\nfunction hourLabel(hour: number): string {\n return `${String(hour).padStart(2, \"0\")}:00`;\n}\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\n// Field types with no compact inline representation for a chip subtitle.\nconst CHIP_SKIP_TYPES = new Set<string>([\"date\", \"datetime\", \"table\", \"embed\", \"image\", \"markdown\"]);\nconst MAX_CHIP_FIELDS = 3;\n\n/** A few scalar field values to show under a chip's title — excludes the label\n * (already the title), the primary key, the date/time fields that position the\n * record, and non-scalar field types. */\nfunction secondaryFieldsOf(item: CollectionItem): string[] {\n const out: string[] = [];\n for (const [key, field] of Object.entries(props.schema.fields)) {\n if (out.length >= MAX_CHIP_FIELDS) break;\n if (key === props.schema.primaryKey || key === labelField.value) continue;\n if (key === props.anchorField || key === props.endField || key === props.timeField) continue;\n if (CHIP_SKIP_TYPES.has(field.type)) continue;\n const value = item[key];\n if (value === undefined || value === null || typeof value === \"object\") continue;\n const text = String(value);\n if (text.length > 0) out.push(text);\n }\n return out;\n}\n\ninterface DayEntry {\n id: string;\n label: string;\n secondary: string[];\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n slice: DaySlice;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\n// Every record whose span covers this day, projected onto it.\nconst dayEntries = computed<DayEntry[]>(() => {\n const { spans } = bucketRecords(props.items, props.anchorField, props.endField, props.timeField);\n const entries: DayEntry[] = [];\n for (const span of spans) {\n const slice = daySlice(span, props.day);\n if (!slice) continue;\n entries.push({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n secondary: secondaryFieldsOf(span.item),\n color: colorOf(span.item),\n slice,\n });\n }\n return entries;\n});\n\nconst allDayEntries = computed<DayEntry[]>(() => dayEntries.value.filter((entry) => entry.slice.kind === \"allDay\"));\n\ninterface TimedEntry extends DayEntry {\n style: Record<string, string>;\n}\n\nconst timedEntries = computed<TimedEntry[]>(() => {\n const timed = dayEntries.value.filter((entry) => entry.slice.kind !== \"allDay\");\n const lanes = assignLanes(\n timed.map((entry) => ({ startMin: entry.slice.startMin, endMin: Math.max(entry.slice.endMin, entry.slice.startMin + LANE_MIN_MINUTES) })),\n );\n return timed.map((entry, index) => {\n const { lane, lanes: laneCount } = lanes[index];\n const widthPct = 100 / laneCount;\n const heightPx = entry.slice.kind === \"line\" ? LINE_PX : Math.max((entry.slice.endMin - entry.slice.startMin) * PX_PER_MIN, MIN_BLOCK_PX);\n return {\n ...entry,\n style: {\n top: `${entry.slice.startMin * PX_PER_MIN}px`,\n height: `${heightPx}px`,\n left: `${lane * widthPct}%`,\n width: `calc(${widthPct}% - 3px)`,\n },\n };\n });\n});\n\n// Chip styling. The selected chip keeps the solid indigo highlight; otherwise\n// a record with a resolved colour tints the chip (palette badge + border), and\n// one with none (no colour field) falls back to the kind's default — indigo on\n// the timeline, slate in the all-day strip. Mirrors the month view's\n// `chipClass` so the two surfaces colour records identically.\nconst TIMED_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100\";\nconst ALL_DAY_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\nfunction timedChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600 z-10\";\n if (!entry.color) return TIMED_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nfunction allDayChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return ALL_DAY_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\n// Select a record: report it to the host (which shows it in the right pane).\n// Unlike before, the modal stays open so the timeline and detail sit\n// side-by-side and the user can hop between records.\nfunction onSelect(itemId: string): void {\n emit(\"select\", itemId);\n}\n\n// Start a create for this day. The popup stays open so the new-item form\n// renders in the right pane (like the open/edit detail) — closing here would\n// drop the form to the panel below the grid.\nfunction onCreate(): void {\n emit(\"createOn\", dayKey.value);\n}\n\n// On open: move focus into the dialog (so Escape/Tab act on the modal, not the\n// background day cell), then auto-scroll the timeline to the earliest timed\n// event (less one hour of lead-in) so an afternoon-heavy day doesn't open on\n// an empty morning.\nonMounted(async () => {\n await nextTick();\n dialogEl.value?.focus();\n const earliest = timedEntries.value.reduce((min, entry) => Math.min(min, entry.slice.startMin), MINUTES_PER_DAY);\n if (earliest >= MINUTES_PER_DAY) return;\n if (scrollEl.value) scrollEl.value.scrollTop = Math.max(0, (earliest - 60) * PX_PER_MIN);\n});\n</script>\n","<template>\n <!-- Modal overlay: a time-allocation view of one day. Backdrop click and\n Escape close it. Selecting a record expands the modal to two columns —\n the timeline on the left, the record's detail (the `#detail` slot) on\n the right. -->\n <div\n class=\"fixed inset-0 z-40 flex items-center justify-center bg-slate-900/40 p-4\"\n data-testid=\"collection-day-view\"\n @click.self=\"emit('close')\"\n @keydown.esc=\"emit('close')\"\n >\n <div\n ref=\"dialogEl\"\n tabindex=\"-1\"\n class=\"flex max-h-[85vh] w-full flex-row rounded-2xl bg-white shadow-xl focus:outline-none\"\n :class=\"showDetail ? 'max-w-4xl' : 'max-w-md'\"\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <!-- Left column: the time-allocation timeline. Shrinks to a fixed width\n when a record detail is shown alongside it, else fills the modal. -->\n <div class=\"flex min-h-0 flex-col\" :class=\"showDetail ? 'w-80 shrink-0 border-r border-slate-200' : 'w-full'\">\n <!-- Header -->\n <div class=\"flex items-center gap-2 border-b border-slate-200 px-4 py-3\">\n <h3 class=\"flex-1 text-sm font-bold text-slate-800\" data-testid=\"collection-day-view-title\">{{ dayLabel }}</h3>\n <button\n v-if=\"canCreate\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.calendarCreateOn', { date: dayKey })\"\n data-testid=\"collection-day-view-create\"\n @click=\"onCreate\"\n >\n <span class=\"material-icons text-lg\">add</span>\n </button>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors\"\n :aria-label=\"t('collectionsView.dayViewClose')\"\n data-testid=\"collection-day-view-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n\n <!-- Empty state -->\n <div v-if=\"timedEntries.length === 0 && allDayEntries.length === 0\" class=\"px-4 py-10 text-center text-sm text-slate-400\">\n {{ t(\"collectionsView.dayViewEmpty\") }}\n </div>\n\n <!-- Timeline -->\n <div v-else ref=\"scrollEl\" class=\"flex-1 overflow-y-auto px-2 py-2\">\n <div class=\"relative\" :style=\"{ height: `${TOTAL_HEIGHT}px` }\" data-testid=\"collection-day-view-timeline\">\n <!-- Hour gridlines + labels -->\n <div v-for=\"hour in 24\" :key=\"hour\" class=\"absolute left-0 right-0 border-t border-slate-100\" :style=\"{ top: `${(hour - 1) * HOUR_PX}px` }\">\n <span class=\"absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400\">{{ hourLabel(hour - 1) }}</span>\n </div>\n\n <!-- Event track (right of the hour gutter) -->\n <div class=\"absolute inset-y-0 right-0\" style=\"left: 2.75rem\">\n <button\n v-for=\"entry in timedEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"absolute overflow-hidden rounded border px-1.5 py-0.5 text-left transition-colors\"\n :class=\"timedChipClass(entry)\"\n :style=\"entry.style\"\n :data-testid=\"`collection-day-view-chip-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n <span class=\"block truncate text-[11px] font-semibold leading-tight\">\n <span v-if=\"entry.slice.bleedsBefore\" aria-hidden=\"true\">▲ </span>{{ entry.label\n }}<span v-if=\"entry.slice.bleedsAfter\" aria-hidden=\"true\"> ▼</span>\n </span>\n <!-- A few non-date/time fields under the title. The chip's height\n stays proportional to its duration; extra lines just clip. -->\n <span v-for=\"(text, i) in entry.secondary\" :key=\"i\" class=\"block truncate text-[10px] leading-tight opacity-70\">{{ text }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- All-day strip (records with no clock) at the bottom -->\n <div\n v-if=\"allDayEntries.length > 0\"\n class=\"flex flex-wrap items-center gap-1.5 border-t border-slate-200 px-4 py-2\"\n data-testid=\"collection-day-view-all-day\"\n >\n <span class=\"mr-1 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.dayViewAllDay\") }}</span>\n <button\n v-for=\"entry in allDayEntries\"\n :key=\"entry.id\"\n type=\"button\"\n class=\"truncate rounded border px-1.5 py-0.5 text-[11px] font-semibold transition-colors\"\n :class=\"allDayChipClass(entry)\"\n :data-testid=\"`collection-day-view-allday-${entry.id}`\"\n @click=\"onSelect(entry.id)\"\n >\n {{ entry.label }}\n </button>\n </div>\n </div>\n\n <!-- Right column: the selected (or being-created) record's detail panel,\n supplied by the host so selection no longer hands off to a panel\n below the calendar. -->\n <div v-if=\"showDetail\" class=\"min-w-0 flex-1 overflow-y-auto\" data-testid=\"collection-day-view-detail\">\n <slot name=\"detail\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { bucketRecords, daySlice, assignLanes, ymdKey, MINUTES_PER_DAY, type Ymd, type DaySlice } from \"../../core/calendarGrid\";\nimport { resolveEnumColor, type EnumColorClasses } from \"../../core/enumColors\";\nimport { labelFieldFor, itemIdOf, itemLabelOf } from \"../../core/itemLabel\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\nconst props = defineProps<{\n schema: CollectionSchema;\n items: CollectionItem[];\n day: Ymd;\n anchorField: string;\n endField?: string;\n timeField?: string;\n /** Optional `enum` field tinting each chip by its value's palette colour\n * (matching the month view). Empty / unset → default indigo/slate styling. */\n colorField?: string;\n selected?: string;\n canCreate: boolean;\n /** When true, expand the modal to two columns and render the `#detail`\n * slot (the selected/created record) to the right of the timeline. */\n showDetail?: boolean;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n createOn: [iso: string];\n close: [];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n\n// One hour = 48px tall; the full day is 24 of them. A point-in-time event\n// (start, no end) has no duration to size by, so it gets a fixed one-line-tall\n// box (`LINE_PX`) — enough to read its time + label — and a `LANE_MIN_MINUTES`\n// footprint so two near-simultaneous events still split into lanes.\nconst HOUR_PX = 48;\nconst TOTAL_HEIGHT = HOUR_PX * 24;\nconst PX_PER_MIN = HOUR_PX / 60;\nconst MIN_BLOCK_PX = 16;\nconst LINE_PX = 20;\nconst LANE_MIN_MINUTES = 30;\n\nconst scrollEl = ref<HTMLElement | null>(null);\nconst dialogEl = ref<HTMLElement | null>(null);\n\nconst dayKey = computed<string>(() => ymdKey(props.day));\n\nconst dayLabel = computed<string>(() => {\n try {\n return new Intl.DateTimeFormat(locale.value, { weekday: \"long\", year: \"numeric\", month: \"long\", day: \"numeric\", timeZone: \"UTC\" }).format(\n new Date(Date.UTC(props.day.year, props.day.month - 1, props.day.day)),\n );\n } catch {\n return dayKey.value;\n }\n});\n\nfunction hourLabel(hour: number): string {\n return `${String(hour).padStart(2, \"0\")}:00`;\n}\n\nconst labelField = computed<string | null>(() => labelFieldFor(props.schema));\n\n// Field types with no compact inline representation for a chip subtitle.\nconst CHIP_SKIP_TYPES = new Set<string>([\"date\", \"datetime\", \"table\", \"embed\", \"image\", \"markdown\"]);\nconst MAX_CHIP_FIELDS = 3;\n\n/** A few scalar field values to show under a chip's title — excludes the label\n * (already the title), the primary key, the date/time fields that position the\n * record, and non-scalar field types. */\nfunction secondaryFieldsOf(item: CollectionItem): string[] {\n const out: string[] = [];\n for (const [key, field] of Object.entries(props.schema.fields)) {\n if (out.length >= MAX_CHIP_FIELDS) break;\n if (key === props.schema.primaryKey || key === labelField.value) continue;\n if (key === props.anchorField || key === props.endField || key === props.timeField) continue;\n if (CHIP_SKIP_TYPES.has(field.type)) continue;\n const value = item[key];\n if (value === undefined || value === null || typeof value === \"object\") continue;\n const text = String(value);\n if (text.length > 0) out.push(text);\n }\n return out;\n}\n\ninterface DayEntry {\n id: string;\n label: string;\n secondary: string[];\n /** Resolved chip colour from the record's `colorField` value, or null when\n * no colour field is set → default styling. */\n color: EnumColorClasses | null;\n slice: DaySlice;\n}\n\n/** A record's chip colour from its `colorField` value (palette, or\n * notification red/amber/grey on a notification enum); null when unset. */\nfunction colorOf(item: CollectionItem): EnumColorClasses | null {\n return props.colorField ? resolveEnumColor(props.schema, props.colorField, item[props.colorField]) : null;\n}\n\n// Every record whose span covers this day, projected onto it.\nconst dayEntries = computed<DayEntry[]>(() => {\n const { spans } = bucketRecords(props.items, props.anchorField, props.endField, props.timeField);\n const entries: DayEntry[] = [];\n for (const span of spans) {\n const slice = daySlice(span, props.day);\n if (!slice) continue;\n entries.push({\n id: itemIdOf(span.item, props.schema),\n label: itemLabelOf(span.item, props.schema, labelField.value),\n secondary: secondaryFieldsOf(span.item),\n color: colorOf(span.item),\n slice,\n });\n }\n return entries;\n});\n\nconst allDayEntries = computed<DayEntry[]>(() => dayEntries.value.filter((entry) => entry.slice.kind === \"allDay\"));\n\ninterface TimedEntry extends DayEntry {\n style: Record<string, string>;\n}\n\nconst timedEntries = computed<TimedEntry[]>(() => {\n const timed = dayEntries.value.filter((entry) => entry.slice.kind !== \"allDay\");\n const lanes = assignLanes(\n timed.map((entry) => ({ startMin: entry.slice.startMin, endMin: Math.max(entry.slice.endMin, entry.slice.startMin + LANE_MIN_MINUTES) })),\n );\n return timed.map((entry, index) => {\n const { lane, lanes: laneCount } = lanes[index];\n const widthPct = 100 / laneCount;\n const heightPx = entry.slice.kind === \"line\" ? LINE_PX : Math.max((entry.slice.endMin - entry.slice.startMin) * PX_PER_MIN, MIN_BLOCK_PX);\n return {\n ...entry,\n style: {\n top: `${entry.slice.startMin * PX_PER_MIN}px`,\n height: `${heightPx}px`,\n left: `${lane * widthPct}%`,\n width: `calc(${widthPct}% - 3px)`,\n },\n };\n });\n});\n\n// Chip styling. The selected chip keeps the solid indigo highlight; otherwise\n// a record with a resolved colour tints the chip (palette badge + border), and\n// one with none (no colour field) falls back to the kind's default — indigo on\n// the timeline, slate in the all-day strip. Mirrors the month view's\n// `chipClass` so the two surfaces colour records identically.\nconst TIMED_DEFAULT = \"bg-indigo-50 text-indigo-700 border-indigo-200 hover:bg-indigo-100\";\nconst ALL_DAY_DEFAULT = \"bg-slate-50 text-slate-600 border-slate-200 hover:bg-slate-100\";\n\nfunction timedChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600 z-10\";\n if (!entry.color) return TIMED_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\nfunction allDayChipClass(entry: DayEntry): string {\n if (entry.id === props.selected) return \"bg-indigo-600 text-white border-indigo-600\";\n if (!entry.color) return ALL_DAY_DEFAULT;\n return `${entry.color.badge} ${entry.color.border} hover:brightness-95`;\n}\n\n// Select a record: report it to the host (which shows it in the right pane).\n// Unlike before, the modal stays open so the timeline and detail sit\n// side-by-side and the user can hop between records.\nfunction onSelect(itemId: string): void {\n emit(\"select\", itemId);\n}\n\n// Start a create for this day. The popup stays open so the new-item form\n// renders in the right pane (like the open/edit detail) — closing here would\n// drop the form to the panel below the grid.\nfunction onCreate(): void {\n emit(\"createOn\", dayKey.value);\n}\n\n// On open: move focus into the dialog (so Escape/Tab act on the modal, not the\n// background day cell), then auto-scroll the timeline to the earliest timed\n// event (less one hour of lead-in) so an afternoon-heavy day doesn't open on\n// an empty morning.\nonMounted(async () => {\n await nextTick();\n dialogEl.value?.focus();\n const earliest = timedEntries.value.reduce((min, entry) => Math.min(min, entry.slice.startMin), MINUTES_PER_DAY);\n if (earliest >= MINUTES_PER_DAY) return;\n if (scrollEl.value) scrollEl.value.scrollTop = Math.max(0, (earliest - 60) * PX_PER_MIN);\n});\n</script>\n","<template>\n <div class=\"h-full overflow-x-auto overflow-y-hidden\" data-testid=\"collection-kanban\">\n <div class=\"flex gap-3 h-full p-1 min-w-max\">\n <div\n v-for=\"column in columns\"\n :key=\"column.value\"\n :data-testid=\"`collection-kanban-column-${column.value || 'uncategorized'}`\"\n class=\"w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg\"\n >\n <!-- Column header (columns are NOT draggable: order is fixed by the\n enum's declared `values`). -->\n <div class=\"flex items-center justify-between px-3 py-2 border-b border-slate-200\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <span class=\"w-2 h-2 rounded-full shrink-0\" :class=\"resolveEnumColor(schema, groupField, column.value).dot\" />\n <span class=\"font-semibold text-xs text-slate-600 truncate\" :title=\"column.label\">{{ column.label }}</span>\n </div>\n <span class=\"text-[11px] text-slate-400 shrink-0\">{{ itemsByColumn(column.value).length }}</span>\n </div>\n\n <!-- Cards. Dragging a card to another column writes the group field\n (no manual ordering within a column). -->\n <draggable\n :model-value=\"itemsByColumn(column.value)\"\n :item-key=\"schema.primaryKey\"\n group=\"collection-kanban-cards\"\n class=\"flex-1 overflow-y-auto p-2 space-y-2 min-h-[2rem]\"\n :animation=\"150\"\n @change=\"(e: DragChangeEvent) => onDragChange(column.value, e)\"\n >\n <template #item=\"{ element }: { element: CollectionItem }\">\n <div\n :data-testid=\"`collection-kanban-card-${itemId(element)}`\"\n tabindex=\"0\"\n role=\"button\"\n :aria-label=\"t('collectionsView.kanbanOpenCard', { label: itemLabel(element) })\"\n class=\"bg-white border border-slate-200 rounded shadow-sm p-2 cursor-grab hover:shadow active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400\"\n :class=\"[itemId(element) === selected ? 'ring-2 ring-indigo-500 border-indigo-300' : '', notifyAccentClass(element)]\"\n @click=\"emit('select', itemId(element))\"\n @keydown.enter.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n @keydown.space.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n >\n <div class=\"flex items-start gap-2\">\n <!-- Toggle checkbox (when the schema has a toggle projecting\n this board's group field). Checking it sets the group\n field, so the card also moves columns. -->\n <input\n v-if=\"cardToggle\"\n type=\"checkbox\"\n :checked=\"cardChecked(element)\"\n class=\"mt-0.5 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer shrink-0\"\n :aria-label=\"cardToggle.label\"\n :data-testid=\"`collection-kanban-toggle-${itemId(element)}`\"\n @click.stop\n @change=\"onCardToggle(element)\"\n />\n <div class=\"text-sm font-medium text-slate-800 truncate\">{{ itemLabel(element) }}</div>\n </div>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport draggable from \"vuedraggable\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport type { CollectionNotifySeverity as NotifierSeverity } from \"../../core/uiTypes\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\n// vuedraggable @change shape — same three keys as the todo board. We act\n// only on \"added\" (the destination column): a cross-column move emits a\n// paired \"removed\" on the source, and \"moved\" is a within-column reorder\n// we deliberately ignore (no manual ordering).\ninterface DragChangeEvent {\n added?: { newIndex: number; element: CollectionItem };\n moved?: { newIndex: number; oldIndex: number; element: CollectionItem };\n removed?: { oldIndex: number; element: CollectionItem };\n}\n\nconst props = defineProps<{\n schema: CollectionSchema;\n /** The `enum` field whose value groups records into columns. */\n groupField: string;\n items: CollectionItem[];\n /** Primary-key of the currently-open record (highlighted card). */\n selected?: string;\n /** Primary-key → active-notification severity. Cards with a notification get\n * a left accent in the matching bell colour (urgent red / nudge amber). */\n notified?: Map<string, NotifierSeverity>;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** Card dropped in a column: set the group field to `value` (the empty\n * string means the Uncategorized column → clear the field). */\n move: [id: string, value: string];\n}>();\n\nconst { t } = useCollectionI18n();\n\n/** The Uncategorized column uses the empty string as its sentinel value. */\nconst UNCATEGORIZED = \"\";\n\ninterface KanbanColumn {\n value: string;\n label: string;\n}\n\nconst groupSpec = computed(() => props.schema.fields[props.groupField]);\n\n/** Declared enum values become columns in order, with a trailing\n * Uncategorized column for empty/unknown values (also a drop target that\n * clears the field). The Uncategorized column is omitted when the group\n * field is `required` — there's no valid \"no value\" state to drop into,\n * and clearing via it would only produce a rejected PUT. */\nconst columns = computed<KanbanColumn[]>(() => {\n const values = groupSpec.value?.values ?? [];\n const declared = values.map((value) => ({ value, label: value }));\n // Skip the trailing Uncategorized column when the group field is\n // `required` (no valid \"no value\" state), or when the enum already\n // declares an empty-string value (it would collide with the\n // Uncategorized sentinel's `value`/`:key`).\n if (groupSpec.value?.required || values.includes(UNCATEGORIZED)) return declared;\n return [...declared, { value: UNCATEGORIZED, label: t(\"collectionsView.kanbanUncategorized\") }];\n});\n\nfunction itemId(item: CollectionItem): string {\n return String(item[props.schema.primaryKey] ?? \"\");\n}\n\n// Left-accent class per notification severity — the same red/amber the bell\n// uses (see NotificationBell's severity colours), so a flagged card matches\n// the badge. Empty string when the record has no active notification.\nconst NOTIFY_ACCENT: Record<NotifierSeverity, string> = {\n urgent: \"border-l-4 border-l-red-500\",\n nudge: \"border-l-4 border-l-amber-500\",\n info: \"border-l-4 border-l-slate-400\",\n};\n\nfunction notifyAccentClass(item: CollectionItem): string {\n const severity = props.notified?.get(itemId(item));\n return severity ? NOTIFY_ACCENT[severity] : \"\";\n}\n\n/** Card label: the schema's `displayField` value, else the primary key. */\nfunction itemLabel(item: CollectionItem): string {\n const field = props.schema.displayField;\n if (field) {\n const value = item[field];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return itemId(item);\n}\n\n/** Which column a record belongs to: its group value when that value is\n * one of the declared enum values, else Uncategorized. */\nfunction columnOf(item: CollectionItem): string {\n const raw = item[props.groupField];\n if (raw === undefined || raw === null || raw === \"\") return UNCATEGORIZED;\n const value = String(raw);\n return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;\n}\n\n// Records to place on the board. A record whose group field is hidden by a\n// `when` predicate is dropped entirely (its column membership is undefined\n// while hidden), per the Kanban spec.\nconst visibleItems = computed<CollectionItem[]>(() => (groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []));\n\nconst itemsByColumnMap = computed<Map<string, CollectionItem[]>>(() => {\n const map = new Map<string, CollectionItem[]>();\n for (const column of columns.value) map.set(column.value, []);\n for (const item of visibleItems.value) {\n const value = columnOf(item);\n (map.get(value) ?? map.get(UNCATEGORIZED))?.push(item);\n }\n return map;\n});\n\nfunction itemsByColumn(value: string): CollectionItem[] {\n return itemsByColumnMap.value.get(value) ?? [];\n}\n\nfunction onDragChange(columnValue: string, event: DragChangeEvent): void {\n if (event.added) emit(\"move\", itemId(event.added.element), columnValue);\n}\n\n// A `toggle` field that projects THIS board's group field — rendered as a\n// per-card checkbox. Checking it writes the group field (so the card also\n// changes column), reusing the same `move` event as a drag.\nconst cardToggle = computed(() => {\n for (const spec of Object.values(props.schema.fields)) {\n if (spec.type === \"toggle\" && spec.field === props.groupField) return spec;\n }\n return null;\n});\n\nfunction cardChecked(item: CollectionItem): boolean {\n const toggle = cardToggle.value;\n return toggle !== null && String(item[props.groupField] ?? \"\") === toggle.onValue;\n}\n\nfunction onCardToggle(item: CollectionItem): void {\n const toggle = cardToggle.value;\n if (!toggle) return;\n const next = cardChecked(item) ? toggle.offValue : toggle.onValue;\n if (next === undefined) return;\n emit(\"move\", itemId(item), next);\n}\n</script>\n","<template>\n <div class=\"h-full overflow-x-auto overflow-y-hidden\" data-testid=\"collection-kanban\">\n <div class=\"flex gap-3 h-full p-1 min-w-max\">\n <div\n v-for=\"column in columns\"\n :key=\"column.value\"\n :data-testid=\"`collection-kanban-column-${column.value || 'uncategorized'}`\"\n class=\"w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg\"\n >\n <!-- Column header (columns are NOT draggable: order is fixed by the\n enum's declared `values`). -->\n <div class=\"flex items-center justify-between px-3 py-2 border-b border-slate-200\">\n <div class=\"flex items-center gap-2 min-w-0\">\n <span class=\"w-2 h-2 rounded-full shrink-0\" :class=\"resolveEnumColor(schema, groupField, column.value).dot\" />\n <span class=\"font-semibold text-xs text-slate-600 truncate\" :title=\"column.label\">{{ column.label }}</span>\n </div>\n <span class=\"text-[11px] text-slate-400 shrink-0\">{{ itemsByColumn(column.value).length }}</span>\n </div>\n\n <!-- Cards. Dragging a card to another column writes the group field\n (no manual ordering within a column). -->\n <draggable\n :model-value=\"itemsByColumn(column.value)\"\n :item-key=\"schema.primaryKey\"\n group=\"collection-kanban-cards\"\n class=\"flex-1 overflow-y-auto p-2 space-y-2 min-h-[2rem]\"\n :animation=\"150\"\n @change=\"(e: DragChangeEvent) => onDragChange(column.value, e)\"\n >\n <template #item=\"{ element }: { element: CollectionItem }\">\n <div\n :data-testid=\"`collection-kanban-card-${itemId(element)}`\"\n tabindex=\"0\"\n role=\"button\"\n :aria-label=\"t('collectionsView.kanbanOpenCard', { label: itemLabel(element) })\"\n class=\"bg-white border border-slate-200 rounded shadow-sm p-2 cursor-grab hover:shadow active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400\"\n :class=\"[itemId(element) === selected ? 'ring-2 ring-indigo-500 border-indigo-300' : '', notifyAccentClass(element)]\"\n @click=\"emit('select', itemId(element))\"\n @keydown.enter.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n @keydown.space.prevent.self=\"(e) => !e.repeat && emit('select', itemId(element))\"\n >\n <div class=\"flex items-start gap-2\">\n <!-- Toggle checkbox (when the schema has a toggle projecting\n this board's group field). Checking it sets the group\n field, so the card also moves columns. -->\n <input\n v-if=\"cardToggle\"\n type=\"checkbox\"\n :checked=\"cardChecked(element)\"\n class=\"mt-0.5 h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer shrink-0\"\n :aria-label=\"cardToggle.label\"\n :data-testid=\"`collection-kanban-toggle-${itemId(element)}`\"\n @click.stop\n @change=\"onCardToggle(element)\"\n />\n <div class=\"text-sm font-medium text-slate-800 truncate\">{{ itemLabel(element) }}</div>\n </div>\n </div>\n </template>\n </draggable>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport draggable from \"vuedraggable\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport type { CollectionNotifySeverity as NotifierSeverity } from \"../../core/uiTypes\";\nimport type { CollectionItem, CollectionSchema } from \"../../core/schema\";\n\n// vuedraggable @change shape — same three keys as the todo board. We act\n// only on \"added\" (the destination column): a cross-column move emits a\n// paired \"removed\" on the source, and \"moved\" is a within-column reorder\n// we deliberately ignore (no manual ordering).\ninterface DragChangeEvent {\n added?: { newIndex: number; element: CollectionItem };\n moved?: { newIndex: number; oldIndex: number; element: CollectionItem };\n removed?: { oldIndex: number; element: CollectionItem };\n}\n\nconst props = defineProps<{\n schema: CollectionSchema;\n /** The `enum` field whose value groups records into columns. */\n groupField: string;\n items: CollectionItem[];\n /** Primary-key of the currently-open record (highlighted card). */\n selected?: string;\n /** Primary-key → active-notification severity. Cards with a notification get\n * a left accent in the matching bell colour (urgent red / nudge amber). */\n notified?: Map<string, NotifierSeverity>;\n}>();\n\nconst emit = defineEmits<{\n select: [id: string | null];\n /** Card dropped in a column: set the group field to `value` (the empty\n * string means the Uncategorized column → clear the field). */\n move: [id: string, value: string];\n}>();\n\nconst { t } = useCollectionI18n();\n\n/** The Uncategorized column uses the empty string as its sentinel value. */\nconst UNCATEGORIZED = \"\";\n\ninterface KanbanColumn {\n value: string;\n label: string;\n}\n\nconst groupSpec = computed(() => props.schema.fields[props.groupField]);\n\n/** Declared enum values become columns in order, with a trailing\n * Uncategorized column for empty/unknown values (also a drop target that\n * clears the field). The Uncategorized column is omitted when the group\n * field is `required` — there's no valid \"no value\" state to drop into,\n * and clearing via it would only produce a rejected PUT. */\nconst columns = computed<KanbanColumn[]>(() => {\n const values = groupSpec.value?.values ?? [];\n const declared = values.map((value) => ({ value, label: value }));\n // Skip the trailing Uncategorized column when the group field is\n // `required` (no valid \"no value\" state), or when the enum already\n // declares an empty-string value (it would collide with the\n // Uncategorized sentinel's `value`/`:key`).\n if (groupSpec.value?.required || values.includes(UNCATEGORIZED)) return declared;\n return [...declared, { value: UNCATEGORIZED, label: t(\"collectionsView.kanbanUncategorized\") }];\n});\n\nfunction itemId(item: CollectionItem): string {\n return String(item[props.schema.primaryKey] ?? \"\");\n}\n\n// Left-accent class per notification severity — the same red/amber the bell\n// uses (see NotificationBell's severity colours), so a flagged card matches\n// the badge. Empty string when the record has no active notification.\nconst NOTIFY_ACCENT: Record<NotifierSeverity, string> = {\n urgent: \"border-l-4 border-l-red-500\",\n nudge: \"border-l-4 border-l-amber-500\",\n info: \"border-l-4 border-l-slate-400\",\n};\n\nfunction notifyAccentClass(item: CollectionItem): string {\n const severity = props.notified?.get(itemId(item));\n return severity ? NOTIFY_ACCENT[severity] : \"\";\n}\n\n/** Card label: the schema's `displayField` value, else the primary key. */\nfunction itemLabel(item: CollectionItem): string {\n const field = props.schema.displayField;\n if (field) {\n const value = item[field];\n if (typeof value === \"string\" && value.length > 0) return value;\n }\n return itemId(item);\n}\n\n/** Which column a record belongs to: its group value when that value is\n * one of the declared enum values, else Uncategorized. */\nfunction columnOf(item: CollectionItem): string {\n const raw = item[props.groupField];\n if (raw === undefined || raw === null || raw === \"\") return UNCATEGORIZED;\n const value = String(raw);\n return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;\n}\n\n// Records to place on the board. A record whose group field is hidden by a\n// `when` predicate is dropped entirely (its column membership is undefined\n// while hidden), per the Kanban spec.\nconst visibleItems = computed<CollectionItem[]>(() => (groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []));\n\nconst itemsByColumnMap = computed<Map<string, CollectionItem[]>>(() => {\n const map = new Map<string, CollectionItem[]>();\n for (const column of columns.value) map.set(column.value, []);\n for (const item of visibleItems.value) {\n const value = columnOf(item);\n (map.get(value) ?? map.get(UNCATEGORIZED))?.push(item);\n }\n return map;\n});\n\nfunction itemsByColumn(value: string): CollectionItem[] {\n return itemsByColumnMap.value.get(value) ?? [];\n}\n\nfunction onDragChange(columnValue: string, event: DragChangeEvent): void {\n if (event.added) emit(\"move\", itemId(event.added.element), columnValue);\n}\n\n// A `toggle` field that projects THIS board's group field — rendered as a\n// per-card checkbox. Checking it writes the group field (so the card also\n// changes column), reusing the same `move` event as a drag.\nconst cardToggle = computed(() => {\n for (const spec of Object.values(props.schema.fields)) {\n if (spec.type === \"toggle\" && spec.field === props.groupField) return spec;\n }\n return null;\n});\n\nfunction cardChecked(item: CollectionItem): boolean {\n const toggle = cardToggle.value;\n return toggle !== null && String(item[props.groupField] ?? \"\") === toggle.onValue;\n}\n\nfunction onCardToggle(item: CollectionItem): void {\n const toggle = cardToggle.value;\n if (!toggle) return;\n const next = cardChecked(item) ? toggle.offValue : toggle.onValue;\n if (next === undefined) return;\n emit(\"move\", itemId(item), next);\n}\n</script>\n","<template>\n <!-- One record panel for both open (read-only) and edit/create. The\n layout is IDENTICAL across modes — same header skeleton, same field\n grid, same per-field cell geometry — and only the inner control of\n each cell swaps: a formatted value when viewing, an input when\n editing. The root is a <form> while editing (so the Save button\n submits) and a <div> when viewing. The host (modal / calendar day\n view) supplies the surrounding container. -->\n <component :is=\"editing ? 'form' : 'div'\" class=\"px-6 py-5 max-h-[60vh] overflow-y-auto\" :data-testid=\"rootTestid\" @submit.prevent=\"emit('submit')\">\n <!-- Header: title block (left) is identical in both modes; only the\n right-hand button cluster swaps (Cancel/Save ↔ actions/Edit/Delete/\n Close). Same height + margin so nothing shifts on toggle. -->\n <div class=\"flex items-center gap-2 mb-4\">\n <div class=\"flex-1 min-w-0\">\n <span class=\"block text-[9px] font-bold text-slate-400 uppercase tracking-wider\">{{ collection.title }}</span>\n <h2 class=\"text-base font-bold text-slate-800 truncate\" :data-testid=\"editing ? 'collections-edit-title' : 'collections-detail-title'\">\n {{ headerTitle }}\n </h2>\n </div>\n\n <!-- Edit/create actions -->\n <template v-if=\"editing\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-editor-cancel\"\n @click=\"emit('cancel')\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"submit\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"saving\"\n data-testid=\"collections-editor-save\"\n >\n {{ saving ? t(\"common.saving\") : t(\"common.save\") }}\n </button>\n </template>\n\n <!-- Read-only actions -->\n <div v-else class=\"flex items-center gap-2\">\n <button\n v-for=\"action in visibleActions\"\n :key=\"action.id\"\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-indigo-200 bg-indigo-50/50 text-indigo-600 hover:bg-indigo-600 hover:text-white hover:border-indigo-600 font-bold text-xs transition-all flex items-center gap-1 disabled:opacity-50\"\n :disabled=\"actionPending\"\n :data-testid=\"`collections-detail-action-${action.id}`\"\n @click=\"emit('runAction', action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-edit\"\n @click=\"emit('edit')\"\n >\n <span class=\"material-icons text-sm\">edit</span>\n <span>{{ t(\"collectionsView.editItem\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-remove\"\n @click=\"emit('delete')\"\n >\n <span class=\"material-icons text-sm\">delete</span>\n <span>{{ t(\"common.remove\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-detail-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n </div>\n\n <p\n v-if=\"!editing && actionError\"\n class=\"mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm\"\n data-testid=\"collections-detail-action-error\"\n >\n {{ actionError }}\n </p>\n\n <!-- Field grid: same columns + per-field cell in both modes. Each cell\n renders its edit control while editing (and the field is editable),\n else its read-only display. -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 bg-white rounded-2xl border border-slate-200/60 p-6 shadow-sm\">\n <template v-for=\"(field, key) in collection.schema.fields\" :key=\"key\">\n <div v-if=\"cellVisible(field)\" class=\"flex flex-col gap-1.5\" :class=\"colSpanClass(field)\">\n <label class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1\" :for=\"`collections-field-${key}`\">\n {{ field.label }}\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"*\" is a universal required-field glyph; treating it as i18n copy would force eight translations of the same symbol. -->\n <span v-if=\"editing && field.required\" class=\"text-rose-500 font-bold\">*</span>\n </label>\n\n <!-- ===== EDIT CONTROLS (editable field types only) ===== -->\n <template v-if=\"editing && isEditableType(field.type)\">\n <!-- Boolean checkbox -->\n <label v-if=\"field.type === 'boolean'\" class=\"inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none\">\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.bool[key]\"\n type=\"checkbox\"\n class=\"h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n :data-testid=\"`collections-input-${key}`\"\n @change=\"markBoolTouched(String(key))\"\n />\n <span class=\"text-xs font-semibold\" :class=\"editing.bool[key] ? 'text-indigo-600' : 'text-slate-500'\">\n {{ editing.bool[key] ? t(\"common.yes\") : t(\"common.no\") }}\n </span>\n </label>\n\n <!-- Ref selector -->\n <select\n v-else-if=\"field.type === 'ref' && field.to && render.refOptions(field.to).length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-50 hover:bg-slate-50/50 focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium text-slate-700\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(field.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n\n <!-- Enum selector -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border px-3 py-2 text-xs focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium\"\n :class=\"enumControlClass(String(key), editing.text[key])\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Nested Table editor -->\n <div\n v-else-if=\"field.type === 'table' && field.of\"\n class=\"border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3\"\n :data-testid=\"`collections-table-${key}`\"\n >\n <div v-if=\"editing.table[key] && editing.table[key].length > 0\" class=\"overflow-hidden border border-slate-200 rounded-lg shadow-sm\">\n <table class=\"w-full text-xs text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-3 py-2 font-bold\">{{ subField.label }}</th>\n <th class=\"w-9\"></th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in editing.table[key]\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-2 py-1.5 align-middle\">\n <input\n v-if=\"subField.type === 'boolean'\"\n v-model=\"row.bool[subKey]\"\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n @change=\"markRowBoolTouched(row, String(subKey))\"\n />\n <select\n v-else-if=\"subField.type === 'enum' && Array.isArray(subField.values) && subField.values.length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in subField.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n <select\n v-else-if=\"subField.type === 'ref' && subField.to && render.refOptions(subField.to).length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(subField.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n <div v-else-if=\"subField.type === 'money'\" class=\"relative flex items-center\">\n <span class=\"absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200\">{{\n render.currencySymbol(render.resolveCurrency(subField, liveRecord))\n }}</span>\n <input\n v-model=\"row.text[subKey]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 pl-6 pr-1.5 py-1 text-xs focus:border-indigo-500 focus:outline-none font-semibold text-slate-800\"\n />\n </div>\n <input\n v-else\n v-model=\"row.text[subKey]\"\n :type=\"render.inputTypeFor(subField.type)\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700\"\n />\n </td>\n <td class=\"text-center px-1\">\n <button\n type=\"button\"\n class=\"h-7 w-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 transition-colors\"\n :aria-label=\"t('collectionsView.removeRow')\"\n :data-testid=\"`collections-table-${key}-remove-${rowIdx}`\"\n @click=\"removeTableRow(String(key), rowIdx)\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <p v-else class=\"text-xs text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</p>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-table-${key}-add`\"\n @click=\"addTableRow(String(key), field.of)\"\n >\n <span class=\"material-icons text-xs\">add</span>\n <span>{{ t(\"collectionsView.addRow\") }}</span>\n </button>\n </div>\n\n <!-- Money input field -->\n <div v-else-if=\"field.type === 'money'\" class=\"relative flex items-center\">\n <div class=\"absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200\">\n {{ render.currencySymbol(render.resolveCurrency(field, liveRecord)) }}\n </div>\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 pl-11 pr-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-semibold text-slate-800 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </div>\n\n <!-- Scalar inputs -->\n <input\n v-else-if=\"['string', 'email', 'number', 'date', 'datetime', 'ref', 'image', 'file'].includes(field.type)\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :type=\"render.inputTypeFor(field.type)\"\n :required=\"isFieldRequiredInUi(field)\"\n :disabled=\"field.primary === true && (editing.mode === 'edit' || isSingleton)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n\n <!-- Markdown or long text -->\n <textarea\n v-else\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :rows=\"field.type === 'markdown' ? 5 : 3\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </template>\n\n <!-- ===== READ-ONLY DISPLAY (viewing, or non-editable types) ===== -->\n <div v-else class=\"text-xs font-medium text-slate-700 break-words\" :data-testid=\"`collections-detail-value-${key}`\">\n <!-- Toggle state (read-only reflection of the projected enum). -->\n <template v-if=\"field.type === 'toggle'\">\n <span\n v-if=\"field.field !== undefined && String(detailRecord[field.field] ?? '') === field.onValue\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n </template>\n\n <!-- Boolean state -->\n <template v-else-if=\"field.type === 'boolean'\">\n <span\n v-if=\"detailRecord[key] === true\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else-if=\"detailRecord[key] === false\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" for an omitted boolean: distinct from an explicit false. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n\n <!-- Ref details link -->\n <router-link\n v-else-if=\"field.type === 'ref' && field.to && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(detailRecord[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-detail-ref-${key}`\"\n >{{ render.refDisplay(field.to, String(detailRecord[key])) }}</router-link\n >\n\n <!-- Money format -->\n <span v-else-if=\"field.type === 'money'\" class=\"font-semibold text-slate-900 tabular-nums text-sm\">{{\n render.formatMoney(detailRecord[key], render.resolveCurrency(field, detailRecord), locale)\n }}</span>\n\n <!-- Derived formula badge -->\n <span\n v-else-if=\"field.type === 'derived'\"\n class=\"inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-2 py-0.5 rounded border border-indigo-100/50\"\n >{{ render.derivedDisplay(field, render.evaluateDerivedAgainstItem(field, String(key), detailRecord), detailRecord) }}</span\n >\n\n <!-- Sub table -->\n <div\n v-else-if=\"field.type === 'table' && field.of && render.hasTableRows(detailRecord[key])\"\n class=\"border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1\"\n >\n <table class=\"w-full text-[11px] text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-4 py-2 font-bold\">{{ subField.label }}</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in render.tableRows(detailRecord[key])\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-4 py-2 align-middle font-medium\">\n <template v-if=\"subField.type === 'boolean'\">\n <span v-if=\"row[subKey] === true\" class=\"material-icons text-emerald-600 text-base\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph (boolean=false), same as elsewhere. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <span v-else :class=\"[subField.type === 'money' ? 'font-bold text-slate-800 tabular-nums' : '']\">{{\n render.formatSubCell(subField, row[subKey], detailRecord)\n }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <span v-else-if=\"field.type === 'table'\" class=\"text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</span>\n\n <!-- Markdown blocks with scroll area -->\n <div\n v-else-if=\"field.type === 'markdown'\"\n class=\"bg-slate-50 rounded-xl p-4 border border-slate-200/60 text-slate-600 text-xs whitespace-pre-wrap leading-relaxed max-h-[30vh] overflow-y-auto\"\n >\n {{ render.detailText(detailRecord[key]) }}\n </div>\n\n <!-- Embed view -->\n <CollectionEmbedView v-else-if=\"field.type === 'embed' && embedViews[key]\" :view=\"embedViews[key]\" :field-key=\"String(key)\" />\n\n <!-- Image (workspace-relative path → <img> via auth-exempt /api/files/raw) -->\n <img\n v-else-if=\"field.type === 'image' && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :src=\"resolveImageSrc(String(detailRecord[key]))\"\n :alt=\"field.label\"\n class=\"max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50\"\n :data-testid=\"`collections-detail-image-${key}`\"\n />\n\n <!-- URL string → external link (new tab). -->\n <a\n v-else-if=\"field.type !== 'file' && render.isExternalUrl(detailRecord[key])\"\n :href=\"String(detailRecord[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-url-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered app in a new tab. -->\n <a\n v-else-if=\"field.type === 'file' && render.artifactUrl(detailRecord[key])\"\n :href=\"render.artifactUrl(detailRecord[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && render.fileRoutePath(detailRecord[key])\"\n :to=\"render.fileRoutePath(detailRecord[key]) ?? ''\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</router-link\n >\n\n <!-- Fallback text styling -->\n <span v-else class=\"text-slate-800 font-semibold\">{{ render.formatCell(detailRecord[key], field.type) }}</span>\n </div>\n </div>\n </template>\n\n <p v-if=\"editing && saveError\" class=\"col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl\">\n {{ saveError }}\n </p>\n </div>\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionEmbedView from \"./CollectionEmbedView.vue\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { emptyRow } from \"../../core/draft\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionRendering } from \"../useCollectionRendering\";\nimport type { CollectionAction, CollectionDetail, CollectionItem, CollectionFieldSpec as FieldSpec } from \"../../core/schema\";\nimport type { EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n// `resolveImageSrc` lives host-side (it points at the host's raw-file endpoint);\n// the view layer reaches it through the UI binding. Kept under the same local\n// name so the template's `:src` binding is unchanged.\nconst resolveImageSrc = collectionUi().imageSrc;\n\nconst props = defineProps<{\n collection: CollectionDetail;\n /** Open record in read-only mode, or null. */\n viewing: CollectionItem | null;\n saving: boolean;\n saveError: string | null;\n actionError: string | null;\n actionPending: boolean;\n visibleActions: CollectionAction[];\n /** Live record computed from the draft (drives derived previews). */\n liveRecord: CollectionItem | null;\n /** Live record with derived fields resolved. */\n liveDerived: CollectionItem | null;\n viewTitle: string;\n isSingleton: boolean;\n /** Shared rendering/derivation helpers + ref/embed caches. */\n render: CollectionRendering;\n locale: string;\n}>();\n\n// The edit/create draft is a two-way model: the form's v-model bindings and\n// the table-row mutators write into its nested reactive state (the parent\n// owns the object identity, so in-place edits flow straight back). A model\n// rather than a prop so `vue/no-mutating-props` doesn't fire on the form.\nconst editing = defineModel<EditState | null>(\"editing\", { required: true });\n\nconst emit = defineEmits<{\n submit: [];\n cancel: [];\n edit: [];\n close: [];\n delete: [];\n runAction: [action: CollectionAction];\n}>();\n\nconst { t } = useCollectionI18n();\n\n// `embedViews` is a ComputedRef nested in the `render` object, so it isn't\n// auto-unwrapped in the template — re-expose it as a top-level computed.\nconst embedViews = computed(() => props.render.embedViews.value);\n\n/** The record the read-only displays render from: the live draft while\n * editing (so non-editable cells like derived/embed preview the in-flight\n * values), else the open record. Always an object so `[key]` lookups are\n * safe in the template. */\nconst detailRecord = computed<CollectionItem>(() => (editing.value ? (props.liveDerived ?? props.liveRecord ?? {}) : (props.viewing ?? {})));\n\n/** Title for the header: the create label, the edited record's id, or the\n * open record's title — same h2 slot in every mode. */\nconst headerTitle = computed<string>(() => {\n if (editing.value) return editing.value.mode === \"create\" ? t(\"collectionsView.createTitle\") : (editing.value.originalId ?? \"\");\n return props.viewTitle;\n});\n\nconst rootTestid = computed<string>(() => {\n if (!editing.value) return \"collections-detail\";\n return editing.value.mode === \"create\" ? \"collections-create\" : \"collections-edit\";\n});\n\n/** Whether a field gets an editable control in edit mode. Toggle (a\n * projection of an enum that has its own input), derived (computed), and\n * embed (a foreign record) stay read-only in both modes, so the cell\n * geometry never changes on the view↔edit toggle. */\nfunction isEditableType(type: FieldSpec[\"type\"]): boolean {\n return type !== \"toggle\" && type !== \"derived\" && type !== \"embed\";\n}\n\n/** Wide field types span the full grid width in BOTH modes — keeping\n * `image` full-width here (not just when viewing) is what stops a field\n * from jumping columns when editing starts. */\nfunction colSpanClass(field: FieldSpec): \"col-span-full\" | \"col-span-1\" {\n return [\"table\", \"markdown\", \"embed\", \"image\"].includes(field.type) ? \"col-span-full\" : \"col-span-1\";\n}\n\n/** Whether to render a field's cell. Identical rule in both modes so no\n * cell appears or disappears on toggle: respect the `when` predicate\n * (against the active record) and hide the primary key except while\n * creating. */\nfunction cellVisible(field: FieldSpec): boolean {\n if (field.primary && editing.value?.mode !== \"create\") return false;\n return fieldVisible(field, detailRecord.value);\n}\n\n/** Mirror of the create-mode primary-key carve-out: drop the HTML5\n * `required` flag on the primary field while creating so the browser\n * doesn't block an intentionally-blank primary (server generates the id). */\nfunction isFieldRequiredInUi(field: FieldSpec): boolean {\n if (!field.required) return false;\n if (editing.value?.mode === \"create\" && field.primary === true) return false;\n return true;\n}\n\n/** Tailwind fill/text/border classes tinting an enum `<select>` by its current\n * value's colour (palette, or notification red/amber/grey when the field is\n * the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const cls = resolveEnumColor(props.collection.schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n// The edit-draft mutators write the model's nested reactive state — the same\n// object the form's v-model bindings mutate, so no parent round-trip needed.\nfunction markBoolTouched(key: string): void {\n if (editing.value) editing.value.boolTouched[key] = true;\n}\n\nfunction markRowBoolTouched(row: TableRowDraft, subKey: string): void {\n row.boolTouched[subKey] = true;\n}\n\nfunction addTableRow(key: string, subFields: Record<string, FieldSpec>): void {\n if (!editing.value) return;\n const rows = editing.value.table[key] ?? [];\n rows.push(emptyRow(subFields));\n editing.value.table[key] = rows;\n}\n\nfunction removeTableRow(key: string, index: number): void {\n if (!editing.value) return;\n const rows = editing.value.table[key];\n if (rows) rows.splice(index, 1);\n}\n</script>\n","<template>\n <!-- One record panel for both open (read-only) and edit/create. The\n layout is IDENTICAL across modes — same header skeleton, same field\n grid, same per-field cell geometry — and only the inner control of\n each cell swaps: a formatted value when viewing, an input when\n editing. The root is a <form> while editing (so the Save button\n submits) and a <div> when viewing. The host (modal / calendar day\n view) supplies the surrounding container. -->\n <component :is=\"editing ? 'form' : 'div'\" class=\"px-6 py-5 max-h-[60vh] overflow-y-auto\" :data-testid=\"rootTestid\" @submit.prevent=\"emit('submit')\">\n <!-- Header: title block (left) is identical in both modes; only the\n right-hand button cluster swaps (Cancel/Save ↔ actions/Edit/Delete/\n Close). Same height + margin so nothing shifts on toggle. -->\n <div class=\"flex items-center gap-2 mb-4\">\n <div class=\"flex-1 min-w-0\">\n <span class=\"block text-[9px] font-bold text-slate-400 uppercase tracking-wider\">{{ collection.title }}</span>\n <h2 class=\"text-base font-bold text-slate-800 truncate\" :data-testid=\"editing ? 'collections-edit-title' : 'collections-detail-title'\">\n {{ headerTitle }}\n </h2>\n </div>\n\n <!-- Edit/create actions -->\n <template v-if=\"editing\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-editor-cancel\"\n @click=\"emit('cancel')\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"submit\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"saving\"\n data-testid=\"collections-editor-save\"\n >\n {{ saving ? t(\"common.saving\") : t(\"common.save\") }}\n </button>\n </template>\n\n <!-- Read-only actions -->\n <div v-else class=\"flex items-center gap-2\">\n <button\n v-for=\"action in visibleActions\"\n :key=\"action.id\"\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-indigo-200 bg-indigo-50/50 text-indigo-600 hover:bg-indigo-600 hover:text-white hover:border-indigo-600 font-bold text-xs transition-all flex items-center gap-1 disabled:opacity-50\"\n :disabled=\"actionPending\"\n :data-testid=\"`collections-detail-action-${action.id}`\"\n @click=\"emit('runAction', action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-slate-200 bg-white text-slate-700 hover:bg-slate-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-edit\"\n @click=\"emit('edit')\"\n >\n <span class=\"material-icons text-sm\">edit</span>\n <span>{{ t(\"collectionsView.editItem\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 font-bold text-xs transition-all flex items-center gap-1\"\n data-testid=\"collections-detail-remove\"\n @click=\"emit('delete')\"\n >\n <span class=\"material-icons text-sm\">delete</span>\n <span>{{ t(\"common.remove\") }}</span>\n </button>\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-detail-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </div>\n </div>\n\n <p\n v-if=\"!editing && actionError\"\n class=\"mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm\"\n data-testid=\"collections-detail-action-error\"\n >\n {{ actionError }}\n </p>\n\n <!-- Field grid: same columns + per-field cell in both modes. Each cell\n renders its edit control while editing (and the field is editable),\n else its read-only display. -->\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 bg-white rounded-2xl border border-slate-200/60 p-6 shadow-sm\">\n <template v-for=\"(field, key) in collection.schema.fields\" :key=\"key\">\n <div v-if=\"cellVisible(field)\" class=\"flex flex-col gap-1.5\" :class=\"colSpanClass(field)\">\n <label class=\"text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1\" :for=\"`collections-field-${key}`\">\n {{ field.label }}\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"*\" is a universal required-field glyph; treating it as i18n copy would force eight translations of the same symbol. -->\n <span v-if=\"editing && field.required\" class=\"text-rose-500 font-bold\">*</span>\n </label>\n\n <!-- ===== EDIT CONTROLS (editable field types only) ===== -->\n <template v-if=\"editing && isEditableType(field.type)\">\n <!-- Boolean checkbox -->\n <label v-if=\"field.type === 'boolean'\" class=\"inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none\">\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.bool[key]\"\n type=\"checkbox\"\n class=\"h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n :data-testid=\"`collections-input-${key}`\"\n @change=\"markBoolTouched(String(key))\"\n />\n <span class=\"text-xs font-semibold\" :class=\"editing.bool[key] ? 'text-indigo-600' : 'text-slate-500'\">\n {{ editing.bool[key] ? t(\"common.yes\") : t(\"common.no\") }}\n </span>\n </label>\n\n <!-- Ref selector -->\n <select\n v-else-if=\"field.type === 'ref' && field.to && render.refOptions(field.to).length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-50 hover:bg-slate-50/50 focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium text-slate-700\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(field.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n\n <!-- Enum selector -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border px-3 py-2 text-xs focus:bg-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none transition-all cursor-pointer font-medium\"\n :class=\"enumControlClass(String(key), editing.text[key])\"\n :data-testid=\"`collections-input-${key}`\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Nested Table editor -->\n <div\n v-else-if=\"field.type === 'table' && field.of\"\n class=\"border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3\"\n :data-testid=\"`collections-table-${key}`\"\n >\n <div v-if=\"editing.table[key] && editing.table[key].length > 0\" class=\"overflow-hidden border border-slate-200 rounded-lg shadow-sm\">\n <table class=\"w-full text-xs text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-3 py-2 font-bold\">{{ subField.label }}</th>\n <th class=\"w-9\"></th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in editing.table[key]\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-2 py-1.5 align-middle\">\n <input\n v-if=\"subField.type === 'boolean'\"\n v-model=\"row.bool[subKey]\"\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer\"\n @change=\"markRowBoolTouched(row, String(subKey))\"\n />\n <select\n v-else-if=\"subField.type === 'enum' && Array.isArray(subField.values) && subField.values.length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in subField.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n <select\n v-else-if=\"subField.type === 'ref' && subField.to && render.refOptions(subField.to).length > 0\"\n v-model=\"row.text[subKey]\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none cursor-pointer bg-slate-50 font-medium\"\n >\n <option value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"opt in render.refOptions(subField.to)\" :key=\"opt.slug\" :value=\"opt.slug\">{{ opt.display }}</option>\n </select>\n <div v-else-if=\"subField.type === 'money'\" class=\"relative flex items-center\">\n <span class=\"absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200\">{{\n render.currencySymbol(render.resolveCurrency(subField, liveRecord))\n }}</span>\n <input\n v-model=\"row.text[subKey]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 pl-6 pr-1.5 py-1 text-xs focus:border-indigo-500 focus:outline-none font-semibold text-slate-800\"\n />\n </div>\n <input\n v-else\n v-model=\"row.text[subKey]\"\n :type=\"render.inputTypeFor(subField.type)\"\n :required=\"subField.required\"\n class=\"w-full rounded-lg border border-slate-200 px-2 py-1 text-xs focus:border-indigo-500 focus:outline-none font-medium text-slate-700\"\n />\n </td>\n <td class=\"text-center px-1\">\n <button\n type=\"button\"\n class=\"h-7 w-7 flex items-center justify-center rounded-lg text-slate-400 hover:text-rose-600 hover:bg-rose-50 transition-colors\"\n :aria-label=\"t('collectionsView.removeRow')\"\n :data-testid=\"`collections-table-${key}-remove-${rowIdx}`\"\n @click=\"removeTableRow(String(key), rowIdx)\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <p v-else class=\"text-xs text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</p>\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-table-${key}-add`\"\n @click=\"addTableRow(String(key), field.of)\"\n >\n <span class=\"material-icons text-xs\">add</span>\n <span>{{ t(\"collectionsView.addRow\") }}</span>\n </button>\n </div>\n\n <!-- Money input field -->\n <div v-else-if=\"field.type === 'money'\" class=\"relative flex items-center\">\n <div class=\"absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200\">\n {{ render.currencySymbol(render.resolveCurrency(field, liveRecord)) }}\n </div>\n <input\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n type=\"number\"\n step=\"0.01\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 pl-11 pr-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-semibold text-slate-800 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </div>\n\n <!-- Scalar inputs -->\n <input\n v-else-if=\"['string', 'email', 'number', 'date', 'datetime', 'ref', 'image', 'file'].includes(field.type)\"\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :type=\"render.inputTypeFor(field.type)\"\n :required=\"isFieldRequiredInUi(field)\"\n :disabled=\"field.primary === true && (editing.mode === 'edit' || isSingleton)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none disabled:bg-slate-100 disabled:text-slate-400 font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n\n <!-- Markdown or long text -->\n <textarea\n v-else\n :id=\"`collections-field-${key}`\"\n v-model=\"editing.text[key]\"\n :rows=\"field.type === 'markdown' ? 5 : 3\"\n :required=\"isFieldRequiredInUi(field)\"\n class=\"w-full rounded-xl border border-slate-200 px-3 py-2 text-xs focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 focus:outline-none font-medium text-slate-700 transition-all\"\n :data-testid=\"`collections-input-${key}`\"\n />\n </template>\n\n <!-- ===== READ-ONLY DISPLAY (viewing, or non-editable types) ===== -->\n <div v-else class=\"text-xs font-medium text-slate-700 break-words\" :data-testid=\"`collections-detail-value-${key}`\">\n <!-- Toggle state (read-only reflection of the projected enum). -->\n <template v-if=\"field.type === 'toggle'\">\n <span\n v-if=\"field.field !== undefined && String(detailRecord[field.field] ?? '') === field.onValue\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n </template>\n\n <!-- Boolean state -->\n <template v-else-if=\"field.type === 'boolean'\">\n <span\n v-if=\"detailRecord[key] === true\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200/40\"\n >\n <span class=\"h-1.5 w-1.5 rounded-full bg-emerald-500\"></span>\n {{ t(\"common.yes\") }}\n </span>\n <span\n v-else-if=\"detailRecord[key] === false\"\n class=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-50 text-slate-400 border border-slate-200/20\"\n >\n {{ t(\"common.no\") }}\n </span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" for an omitted boolean: distinct from an explicit false. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n\n <!-- Ref details link -->\n <router-link\n v-else-if=\"field.type === 'ref' && field.to && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(detailRecord[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 font-bold hover:underline\"\n :data-testid=\"`collections-detail-ref-${key}`\"\n >{{ render.refDisplay(field.to, String(detailRecord[key])) }}</router-link\n >\n\n <!-- Money format -->\n <span v-else-if=\"field.type === 'money'\" class=\"font-semibold text-slate-900 tabular-nums text-sm\">{{\n render.formatMoney(detailRecord[key], render.resolveCurrency(field, detailRecord), locale)\n }}</span>\n\n <!-- Derived formula badge -->\n <span\n v-else-if=\"field.type === 'derived'\"\n class=\"inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-2 py-0.5 rounded border border-indigo-100/50\"\n >{{ render.derivedDisplay(field, render.evaluateDerivedAgainstItem(field, String(key), detailRecord), detailRecord) }}</span\n >\n\n <!-- Sub table -->\n <div\n v-else-if=\"field.type === 'table' && field.of && render.hasTableRows(detailRecord[key])\"\n class=\"border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1\"\n >\n <table class=\"w-full text-[11px] text-slate-600 bg-white\">\n <thead class=\"bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider\">\n <tr>\n <th v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"text-left px-4 py-2 font-bold\">{{ subField.label }}</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100\">\n <tr v-for=\"(row, rowIdx) in render.tableRows(detailRecord[key])\" :key=\"rowIdx\" class=\"hover:bg-slate-50/50\">\n <td v-for=\"(subField, subKey) in field.of\" :key=\"subKey\" class=\"px-4 py-2 align-middle font-medium\">\n <template v-if=\"subField.type === 'boolean'\">\n <span v-if=\"row[subKey] === true\" class=\"material-icons text-emerald-600 text-base\">check_circle</span>\n <!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -- bare \"—\" empty-value glyph (boolean=false), same as elsewhere. -->\n <span v-else class=\"text-slate-300\">—</span>\n </template>\n <span v-else :class=\"[subField.type === 'money' ? 'font-bold text-slate-800 tabular-nums' : '']\">{{\n render.formatSubCell(subField, row[subKey], detailRecord)\n }}</span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <span v-else-if=\"field.type === 'table'\" class=\"text-slate-400 italic\">{{ t(\"collectionsView.noRows\") }}</span>\n\n <!-- Markdown blocks with scroll area -->\n <div\n v-else-if=\"field.type === 'markdown'\"\n class=\"bg-slate-50 rounded-xl p-4 border border-slate-200/60 text-slate-600 text-xs whitespace-pre-wrap leading-relaxed max-h-[30vh] overflow-y-auto\"\n >\n {{ render.detailText(detailRecord[key]) }}\n </div>\n\n <!-- Embed view -->\n <CollectionEmbedView v-else-if=\"field.type === 'embed' && embedViews[key]\" :view=\"embedViews[key]\" :field-key=\"String(key)\" />\n\n <!-- Image (workspace-relative path → <img> via auth-exempt /api/files/raw) -->\n <img\n v-else-if=\"field.type === 'image' && typeof detailRecord[key] === 'string' && detailRecord[key]\"\n :src=\"resolveImageSrc(String(detailRecord[key]))\"\n :alt=\"field.label\"\n class=\"max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50\"\n :data-testid=\"`collections-detail-image-${key}`\"\n />\n\n <!-- URL string → external link (new tab). -->\n <a\n v-else-if=\"field.type !== 'file' && render.isExternalUrl(detailRecord[key])\"\n :href=\"String(detailRecord[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-url-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered app in a new tab. -->\n <a\n v-else-if=\"field.type === 'file' && render.artifactUrl(detailRecord[key])\"\n :href=\"render.artifactUrl(detailRecord[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && render.fileRoutePath(detailRecord[key])\"\n :to=\"render.fileRoutePath(detailRecord[key]) ?? ''\"\n class=\"text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all\"\n :data-testid=\"`collections-detail-file-${key}`\"\n >{{ String(detailRecord[key]) }}</router-link\n >\n\n <!-- Fallback text styling -->\n <span v-else class=\"text-slate-800 font-semibold\">{{ render.formatCell(detailRecord[key], field.type) }}</span>\n </div>\n </div>\n </template>\n\n <p v-if=\"editing && saveError\" class=\"col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl\">\n {{ saveError }}\n </p>\n </div>\n </component>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionEmbedView from \"./CollectionEmbedView.vue\";\nimport { fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { emptyRow } from \"../../core/draft\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionRendering } from \"../useCollectionRendering\";\nimport type { CollectionAction, CollectionDetail, CollectionItem, CollectionFieldSpec as FieldSpec } from \"../../core/schema\";\nimport type { EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n// `resolveImageSrc` lives host-side (it points at the host's raw-file endpoint);\n// the view layer reaches it through the UI binding. Kept under the same local\n// name so the template's `:src` binding is unchanged.\nconst resolveImageSrc = collectionUi().imageSrc;\n\nconst props = defineProps<{\n collection: CollectionDetail;\n /** Open record in read-only mode, or null. */\n viewing: CollectionItem | null;\n saving: boolean;\n saveError: string | null;\n actionError: string | null;\n actionPending: boolean;\n visibleActions: CollectionAction[];\n /** Live record computed from the draft (drives derived previews). */\n liveRecord: CollectionItem | null;\n /** Live record with derived fields resolved. */\n liveDerived: CollectionItem | null;\n viewTitle: string;\n isSingleton: boolean;\n /** Shared rendering/derivation helpers + ref/embed caches. */\n render: CollectionRendering;\n locale: string;\n}>();\n\n// The edit/create draft is a two-way model: the form's v-model bindings and\n// the table-row mutators write into its nested reactive state (the parent\n// owns the object identity, so in-place edits flow straight back). A model\n// rather than a prop so `vue/no-mutating-props` doesn't fire on the form.\nconst editing = defineModel<EditState | null>(\"editing\", { required: true });\n\nconst emit = defineEmits<{\n submit: [];\n cancel: [];\n edit: [];\n close: [];\n delete: [];\n runAction: [action: CollectionAction];\n}>();\n\nconst { t } = useCollectionI18n();\n\n// `embedViews` is a ComputedRef nested in the `render` object, so it isn't\n// auto-unwrapped in the template — re-expose it as a top-level computed.\nconst embedViews = computed(() => props.render.embedViews.value);\n\n/** The record the read-only displays render from: the live draft while\n * editing (so non-editable cells like derived/embed preview the in-flight\n * values), else the open record. Always an object so `[key]` lookups are\n * safe in the template. */\nconst detailRecord = computed<CollectionItem>(() => (editing.value ? (props.liveDerived ?? props.liveRecord ?? {}) : (props.viewing ?? {})));\n\n/** Title for the header: the create label, the edited record's id, or the\n * open record's title — same h2 slot in every mode. */\nconst headerTitle = computed<string>(() => {\n if (editing.value) return editing.value.mode === \"create\" ? t(\"collectionsView.createTitle\") : (editing.value.originalId ?? \"\");\n return props.viewTitle;\n});\n\nconst rootTestid = computed<string>(() => {\n if (!editing.value) return \"collections-detail\";\n return editing.value.mode === \"create\" ? \"collections-create\" : \"collections-edit\";\n});\n\n/** Whether a field gets an editable control in edit mode. Toggle (a\n * projection of an enum that has its own input), derived (computed), and\n * embed (a foreign record) stay read-only in both modes, so the cell\n * geometry never changes on the view↔edit toggle. */\nfunction isEditableType(type: FieldSpec[\"type\"]): boolean {\n return type !== \"toggle\" && type !== \"derived\" && type !== \"embed\";\n}\n\n/** Wide field types span the full grid width in BOTH modes — keeping\n * `image` full-width here (not just when viewing) is what stops a field\n * from jumping columns when editing starts. */\nfunction colSpanClass(field: FieldSpec): \"col-span-full\" | \"col-span-1\" {\n return [\"table\", \"markdown\", \"embed\", \"image\"].includes(field.type) ? \"col-span-full\" : \"col-span-1\";\n}\n\n/** Whether to render a field's cell. Identical rule in both modes so no\n * cell appears or disappears on toggle: respect the `when` predicate\n * (against the active record) and hide the primary key except while\n * creating. */\nfunction cellVisible(field: FieldSpec): boolean {\n if (field.primary && editing.value?.mode !== \"create\") return false;\n return fieldVisible(field, detailRecord.value);\n}\n\n/** Mirror of the create-mode primary-key carve-out: drop the HTML5\n * `required` flag on the primary field while creating so the browser\n * doesn't block an intentionally-blank primary (server generates the id). */\nfunction isFieldRequiredInUi(field: FieldSpec): boolean {\n if (!field.required) return false;\n if (editing.value?.mode === \"create\" && field.primary === true) return false;\n return true;\n}\n\n/** Tailwind fill/text/border classes tinting an enum `<select>` by its current\n * value's colour (palette, or notification red/amber/grey when the field is\n * the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const cls = resolveEnumColor(props.collection.schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n// The edit-draft mutators write the model's nested reactive state — the same\n// object the form's v-model bindings mutate, so no parent round-trip needed.\nfunction markBoolTouched(key: string): void {\n if (editing.value) editing.value.boolTouched[key] = true;\n}\n\nfunction markRowBoolTouched(row: TableRowDraft, subKey: string): void {\n row.boolTouched[subKey] = true;\n}\n\nfunction addTableRow(key: string, subFields: Record<string, FieldSpec>): void {\n if (!editing.value) return;\n const rows = editing.value.table[key] ?? [];\n rows.push(emptyRow(subFields));\n editing.value.table[key] = rows;\n}\n\nfunction removeTableRow(key: string, index: number): void {\n if (!editing.value) return;\n const rows = editing.value.table[key];\n if (rows) rows.splice(index, 1);\n}\n</script>\n","<template>\n <!-- Per-collection config. v1 manages custom views (delete only; the header\n \"+\" stays the discoverable add entry point). Reuses the shared record\n modal shell for the overlay, focus trap, and Escape handling. -->\n <CollectionRecordModal @close=\"emit('close')\">\n <div data-testid=\"collection-config-modal\" class=\"flex flex-col overflow-hidden\">\n <header class=\"flex items-center justify-between gap-2 border-b border-slate-100 px-5 py-3\">\n <h2 class=\"text-sm font-bold text-slate-700\">{{ t(\"collectionsView.config.title\", { title }) }}</h2>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-50 hover:text-slate-600\"\n :title=\"t('common.close')\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collection-config-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-sm\">close</span>\n </button>\n </header>\n\n <div class=\"overflow-y-auto px-5 py-4\">\n <h3 class=\"mb-2 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.config.viewsHeading\") }}</h3>\n\n <p\n v-if=\"error\"\n class=\"mb-3 rounded border border-rose-200 bg-rose-50 px-3 py-2 text-xs font-medium text-rose-600\"\n data-testid=\"collection-config-error\"\n >\n {{ error }}\n </p>\n\n <ul v-if=\"views.length > 0\" class=\"flex flex-col gap-1\">\n <li v-for=\"view in views\" :key=\"view.id\" class=\"flex items-center gap-2 rounded border border-slate-200 bg-white px-3 py-2\">\n <span class=\"material-icons text-base text-slate-400\">{{ view.icon || \"dashboard_customize\" }}</span>\n <span class=\"flex-1 truncate text-sm font-semibold text-slate-700\">{{ view.label }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 disabled:opacity-50\"\n :title=\"t('collectionsView.config.deleteView', { label: view.label })\"\n :aria-label=\"t('collectionsView.config.deleteView', { label: view.label })\"\n :data-testid=\"`collection-view-delete-${view.id}`\"\n :disabled=\"deleting !== null\"\n @click=\"onDelete(view)\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n </li>\n </ul>\n\n <p v-else class=\"text-xs text-slate-400\" data-testid=\"collection-config-empty\">{{ t(\"collectionsView.config.empty\") }}</p>\n </div>\n </div>\n </CollectionRecordModal>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionRecordModal from \"./CollectionRecordModal.vue\";\nimport type { CollectionCustomView } from \"../../core/schema\";\nimport { errorMessage } from \"../../core/errorMessage\";\nimport { collectionUi } from \"../uiContext\";\n\nconst props = defineProps<{ slug: string; title: string; views: CollectionCustomView[] }>();\nconst emit = defineEmits<{ close: []; changed: [] }>();\n\nconst { t } = useCollectionI18n();\n\n// The id of the view whose delete is in flight (disables the other buttons),\n// and the last delete error (HTTP or network), shown inline.\nconst deleting = ref<string | null>(null);\nconst error = ref<string | null>(null);\n\nasync function onDelete(view: CollectionCustomView): Promise<void> {\n const ok = await collectionUi().confirm({\n message: t(\"collectionsView.config.confirmDelete\", { label: view.label }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n error.value = null;\n deleting.value = view.id;\n try {\n const result = await collectionUi().deleteView(props.slug, view.id);\n if (!result.ok) {\n error.value = result.error;\n return;\n }\n // Parent reloads the collection detail; the `views` prop updates reactively.\n emit(\"changed\");\n } catch (err) {\n // deleteView normalises network/HTTP errors into a result, so this only\n // catches the unexpected — but a `finally` guarantees the row never stays\n // stuck disabled.\n error.value = errorMessage(err);\n } finally {\n deleting.value = null;\n }\n}\n</script>\n","<template>\n <!-- Per-collection config. v1 manages custom views (delete only; the header\n \"+\" stays the discoverable add entry point). Reuses the shared record\n modal shell for the overlay, focus trap, and Escape handling. -->\n <CollectionRecordModal @close=\"emit('close')\">\n <div data-testid=\"collection-config-modal\" class=\"flex flex-col overflow-hidden\">\n <header class=\"flex items-center justify-between gap-2 border-b border-slate-100 px-5 py-3\">\n <h2 class=\"text-sm font-bold text-slate-700\">{{ t(\"collectionsView.config.title\", { title }) }}</h2>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-50 hover:text-slate-600\"\n :title=\"t('common.close')\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collection-config-close\"\n @click=\"emit('close')\"\n >\n <span class=\"material-icons text-sm\">close</span>\n </button>\n </header>\n\n <div class=\"overflow-y-auto px-5 py-4\">\n <h3 class=\"mb-2 text-[10px] font-bold uppercase tracking-wider text-slate-400\">{{ t(\"collectionsView.config.viewsHeading\") }}</h3>\n\n <p\n v-if=\"error\"\n class=\"mb-3 rounded border border-rose-200 bg-rose-50 px-3 py-2 text-xs font-medium text-rose-600\"\n data-testid=\"collection-config-error\"\n >\n {{ error }}\n </p>\n\n <ul v-if=\"views.length > 0\" class=\"flex flex-col gap-1\">\n <li v-for=\"view in views\" :key=\"view.id\" class=\"flex items-center gap-2 rounded border border-slate-200 bg-white px-3 py-2\">\n <span class=\"material-icons text-base text-slate-400\">{{ view.icon || \"dashboard_customize\" }}</span>\n <span class=\"flex-1 truncate text-sm font-semibold text-slate-700\">{{ view.label }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 disabled:opacity-50\"\n :title=\"t('collectionsView.config.deleteView', { label: view.label })\"\n :aria-label=\"t('collectionsView.config.deleteView', { label: view.label })\"\n :data-testid=\"`collection-view-delete-${view.id}`\"\n :disabled=\"deleting !== null\"\n @click=\"onDelete(view)\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n </li>\n </ul>\n\n <p v-else class=\"text-xs text-slate-400\" data-testid=\"collection-config-empty\">{{ t(\"collectionsView.config.empty\") }}</p>\n </div>\n </div>\n </CollectionRecordModal>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionRecordModal from \"./CollectionRecordModal.vue\";\nimport type { CollectionCustomView } from \"../../core/schema\";\nimport { errorMessage } from \"../../core/errorMessage\";\nimport { collectionUi } from \"../uiContext\";\n\nconst props = defineProps<{ slug: string; title: string; views: CollectionCustomView[] }>();\nconst emit = defineEmits<{ close: []; changed: [] }>();\n\nconst { t } = useCollectionI18n();\n\n// The id of the view whose delete is in flight (disables the other buttons),\n// and the last delete error (HTTP or network), shown inline.\nconst deleting = ref<string | null>(null);\nconst error = ref<string | null>(null);\n\nasync function onDelete(view: CollectionCustomView): Promise<void> {\n const ok = await collectionUi().confirm({\n message: t(\"collectionsView.config.confirmDelete\", { label: view.label }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n error.value = null;\n deleting.value = view.id;\n try {\n const result = await collectionUi().deleteView(props.slug, view.id);\n if (!result.ok) {\n error.value = result.error;\n return;\n }\n // Parent reloads the collection detail; the `views` prop updates reactively.\n emit(\"changed\");\n } catch (err) {\n // deleteView normalises network/HTTP errors into a result, so this only\n // catches the unexpected — but a `finally` guarantees the row never stays\n // stuck disabled.\n error.value = errorMessage(err);\n } finally {\n deleting.value = null;\n }\n}\n</script>\n","<template>\n <div class=\"custom-view-container\">\n <div v-if=\"error\" class=\"custom-view-message\" role=\"alert\" data-testid=\"collection-custom-view-error\">\n {{ t(\"collectionsView.customViewError\", { error }) }}\n </div>\n <div v-else-if=\"loading\" class=\"custom-view-message\" data-testid=\"collection-custom-view-loading\">\n {{ t(\"collectionsView.customViewLoading\") }}\n </div>\n <!-- Sandboxed: NO `allow-same-origin`, so the view keeps an opaque origin\n and cannot read the parent's token / localStorage — its data reaches it\n only via the scoped token injected into __MC_VIEW. `allow-popups` +\n `allow-popups-to-escape-sandbox` let a view open an external link\n (`<a target=\"_blank\">` / `window.open`) as a normal new tab — e.g. a\n feed card linking to its article. Opening requires a user gesture and\n `target=\"_blank\"` defaults to `noopener`, so the popup can't reach back\n into the view; the token stays isolated. -->\n <iframe\n v-else-if=\"srcdoc\"\n :key=\"view.id\"\n data-testid=\"collection-custom-view-iframe\"\n :title=\"view.label\"\n :srcdoc=\"srcdoc\"\n sandbox=\"allow-scripts allow-popups allow-popups-to-escape-sandbox\"\n class=\"w-full h-full border-0\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch, onBeforeUnmount } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { errorMessage } from \"../../core/errorMessage\";\nimport type { CollectionCustomView } from \"../../core/schema\";\nimport { collectionUi } from \"../uiContext\";\n\nconst { t } = useCollectionI18n();\n\nconst props = defineProps<{\n slug: string;\n view: CollectionCustomView;\n}>();\n\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst srcdoc = ref<string | null>(null);\n\n// The injected token expires (VIEW_TOKEN_TTL_MS, 1h). The sandboxed view can't\n// re-mint itself (it has no global bearer), so a view left mounted past expiry\n// would 401 on its next read/write. Schedule a re-mint + reload shortly before\n// `exp` so the iframe always holds a fresh token.\nconst REMINT_LEAD_MS = 60_000;\nconst MIN_REMINT_DELAY_MS = 10_000;\nlet refreshTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction clearRefresh(): void {\n if (refreshTimer !== undefined) {\n clearTimeout(refreshTimer);\n refreshTimer = undefined;\n }\n}\n\nfunction scheduleRefresh(expMs: number): void {\n clearRefresh();\n const delay = Math.max(expMs - Date.now() - REMINT_LEAD_MS, MIN_REMINT_DELAY_MS);\n refreshTimer = setTimeout(() => void load(), delay);\n}\n\n// Monotonic load id: a switch/refresh that starts a newer load() must win, so\n// a slower in-flight load can't clobber the current view's srcdoc when it\n// finally resolves. Each load captures its id and bails on every commit if a\n// newer load has started.\nlet loadSeq = 0;\n\nasync function load(): Promise<void> {\n clearRefresh();\n const seq = ++loadSeq;\n const stale = (): boolean => seq !== loadSeq;\n loading.value = true;\n error.value = null;\n srcdoc.value = null;\n const binding = collectionUi();\n try {\n // 1. Mint a scoped token for this view's declared capabilities.\n const mint = await binding.mintViewToken(props.slug, props.view.id);\n if (stale()) return;\n if (!mint.ok) {\n error.value = mint.error;\n return;\n }\n // Re-mint + reload before this token expires (the iframe can't do it itself).\n scheduleRefresh(mint.data.exp);\n // 2. Fetch the view's HTML (global-bearer; attached by the host).\n const resp = await binding.fetchViewHtml(props.slug, props.view.id);\n if (stale()) return;\n if (!resp.ok) {\n error.value = `HTTP ${resp.status}`;\n return;\n }\n // 3. Render it sandboxed with the token + CSP injected.\n srcdoc.value = binding.buildViewSrcdoc(resp.html, {\n slug: props.slug,\n token: mint.data.token,\n dataUrl: mint.data.dataUrl,\n origin: window.location.origin,\n });\n } catch (err) {\n if (!stale()) error.value = errorMessage(err);\n } finally {\n if (!stale()) loading.value = false;\n }\n}\n\n// Reload (re-mint + re-fetch) whenever the selected view or collection changes.\nwatch([() => props.slug, () => props.view.id], () => void load(), { immediate: true });\n\nonBeforeUnmount(clearRefresh);\n</script>\n\n<style scoped>\n.custom-view-container {\n width: 100%;\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: white;\n overflow: hidden;\n}\n\n.custom-view-message {\n padding: 1rem;\n font-size: 0.875rem;\n color: #64748b;\n}\n\n[role=\"alert\"].custom-view-message {\n color: #b71c1c;\n}\n</style>\n","<template>\n <div class=\"custom-view-container\">\n <div v-if=\"error\" class=\"custom-view-message\" role=\"alert\" data-testid=\"collection-custom-view-error\">\n {{ t(\"collectionsView.customViewError\", { error }) }}\n </div>\n <div v-else-if=\"loading\" class=\"custom-view-message\" data-testid=\"collection-custom-view-loading\">\n {{ t(\"collectionsView.customViewLoading\") }}\n </div>\n <!-- Sandboxed: NO `allow-same-origin`, so the view keeps an opaque origin\n and cannot read the parent's token / localStorage — its data reaches it\n only via the scoped token injected into __MC_VIEW. `allow-popups` +\n `allow-popups-to-escape-sandbox` let a view open an external link\n (`<a target=\"_blank\">` / `window.open`) as a normal new tab — e.g. a\n feed card linking to its article. Opening requires a user gesture and\n `target=\"_blank\"` defaults to `noopener`, so the popup can't reach back\n into the view; the token stays isolated. -->\n <iframe\n v-else-if=\"srcdoc\"\n :key=\"view.id\"\n data-testid=\"collection-custom-view-iframe\"\n :title=\"view.label\"\n :srcdoc=\"srcdoc\"\n sandbox=\"allow-scripts allow-popups allow-popups-to-escape-sandbox\"\n class=\"w-full h-full border-0\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch, onBeforeUnmount } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { errorMessage } from \"../../core/errorMessage\";\nimport type { CollectionCustomView } from \"../../core/schema\";\nimport { collectionUi } from \"../uiContext\";\n\nconst { t } = useCollectionI18n();\n\nconst props = defineProps<{\n slug: string;\n view: CollectionCustomView;\n}>();\n\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst srcdoc = ref<string | null>(null);\n\n// The injected token expires (VIEW_TOKEN_TTL_MS, 1h). The sandboxed view can't\n// re-mint itself (it has no global bearer), so a view left mounted past expiry\n// would 401 on its next read/write. Schedule a re-mint + reload shortly before\n// `exp` so the iframe always holds a fresh token.\nconst REMINT_LEAD_MS = 60_000;\nconst MIN_REMINT_DELAY_MS = 10_000;\nlet refreshTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction clearRefresh(): void {\n if (refreshTimer !== undefined) {\n clearTimeout(refreshTimer);\n refreshTimer = undefined;\n }\n}\n\nfunction scheduleRefresh(expMs: number): void {\n clearRefresh();\n const delay = Math.max(expMs - Date.now() - REMINT_LEAD_MS, MIN_REMINT_DELAY_MS);\n refreshTimer = setTimeout(() => void load(), delay);\n}\n\n// Monotonic load id: a switch/refresh that starts a newer load() must win, so\n// a slower in-flight load can't clobber the current view's srcdoc when it\n// finally resolves. Each load captures its id and bails on every commit if a\n// newer load has started.\nlet loadSeq = 0;\n\nasync function load(): Promise<void> {\n clearRefresh();\n const seq = ++loadSeq;\n const stale = (): boolean => seq !== loadSeq;\n loading.value = true;\n error.value = null;\n srcdoc.value = null;\n const binding = collectionUi();\n try {\n // 1. Mint a scoped token for this view's declared capabilities.\n const mint = await binding.mintViewToken(props.slug, props.view.id);\n if (stale()) return;\n if (!mint.ok) {\n error.value = mint.error;\n return;\n }\n // Re-mint + reload before this token expires (the iframe can't do it itself).\n scheduleRefresh(mint.data.exp);\n // 2. Fetch the view's HTML (global-bearer; attached by the host).\n const resp = await binding.fetchViewHtml(props.slug, props.view.id);\n if (stale()) return;\n if (!resp.ok) {\n error.value = `HTTP ${resp.status}`;\n return;\n }\n // 3. Render it sandboxed with the token + CSP injected.\n srcdoc.value = binding.buildViewSrcdoc(resp.html, {\n slug: props.slug,\n token: mint.data.token,\n dataUrl: mint.data.dataUrl,\n origin: window.location.origin,\n });\n } catch (err) {\n if (!stale()) error.value = errorMessage(err);\n } finally {\n if (!stale()) loading.value = false;\n }\n}\n\n// Reload (re-mint + re-fetch) whenever the selected view or collection changes.\nwatch([() => props.slug, () => props.view.id], () => void load(), { immediate: true });\n\nonBeforeUnmount(clearRefresh);\n</script>\n\n<style scoped>\n.custom-view-container {\n width: 100%;\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: white;\n overflow: hidden;\n}\n\n.custom-view-message {\n padding: 1rem;\n font-size: 0.875rem;\n color: #64748b;\n}\n\n[role=\"alert\"].custom-view-message {\n color: #b71c1c;\n}\n</style>\n","<template>\n <div class=\"h-full flex flex-col bg-slate-50/30\">\n <header class=\"flex items-center gap-3 px-6 py-2 border-b border-slate-200 bg-white\">\n <button\n v-if=\"!embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-50 hover:text-slate-800 transition-colors\"\n :title=\"t('collectionsView.backToIndex')\"\n :aria-label=\"t('collectionsView.backToIndex')\"\n data-testid=\"collections-back\"\n @click=\"goBack\"\n >\n <span class=\"material-icons text-lg\">arrow_back</span>\n </button>\n\n <div v-if=\"collection\" class=\"h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100\">\n <span class=\"material-symbols-outlined text-xl\">{{ collection.icon }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <h1 class=\"text-base font-bold text-slate-800 truncate\">\n {{ collection?.title ?? t(\"collectionsView.title\") }}\n </h1>\n <span v-if=\"collection\" class=\"block text-[10px] text-slate-400 font-bold uppercase tracking-wider\">\n {{ collection.slug }}\n </span>\n </div>\n\n <component\n :is=\"pinToggle\"\n v-if=\"collection && !embedded\"\n :kind=\"isFeedRoute ? 'feed' : 'collection'\"\n :slug=\"collection.slug\"\n :title=\"collection.title\"\n :icon=\"collection.icon\"\n />\n\n <button\n v-if=\"collection?.schema.ingest\"\n type=\"button\"\n 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 disabled:opacity-50\"\n :disabled=\"refreshing\"\n data-testid=\"collections-refresh-feed\"\n @click=\"refreshFeed\"\n >\n <span class=\"material-icons text-sm\">{{ refreshing ? \"hourglass_empty\" : \"refresh\" }}</span>\n <span>{{ t(\"collectionsView.refreshFeed\") }}</span>\n </button>\n\n <button\n v-if=\"collection\"\n type=\"button\"\n 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\"\n data-testid=\"collections-chat\"\n @click=\"openChat\"\n >\n <span class=\"material-icons text-sm\">forum</span>\n <span>{{ t(\"collectionsView.chat\") }}</span>\n </button>\n\n <!-- Collection-level actions (schema `collectionActions`). No record\n context: each seeds a chat with a progress summary of all items. -->\n <button\n v-for=\"action in collectionActions\"\n :key=\"action.id\"\n type=\"button\"\n 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 disabled:opacity-50\"\n :disabled=\"collectionActionPending\"\n :data-testid=\"`collections-action-${action.id}`\"\n @click=\"runCollectionAction(action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <!-- Hidden in calendar view: there, creation happens via the day view's\n + button, which opens the new-item form in the popup's right pane. -->\n <button\n v-if=\"canCreate && !calendarActive\"\n type=\"button\"\n 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\"\n data-testid=\"collections-add-item\"\n @click=\"openCreate\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"common.add\") }}</span>\n </button>\n\n <button\n v-if=\"canDeleteCollection && !embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 transition-colors\"\n :title=\"t('collectionsView.deleteCollection')\"\n :aria-label=\"t('collectionsView.deleteCollection')\"\n data-testid=\"collections-delete\"\n @click=\"confirmCollectionDelete\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n\n <button\n v-if=\"canDeleteFeed && !embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 transition-colors\"\n :title=\"t('collectionsView.deleteFeed')\"\n :aria-label=\"t('collectionsView.deleteFeed')\"\n data-testid=\"feeds-delete\"\n @click=\"confirmFeedDelete\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n </header>\n\n <!-- Search Toolbar. Shown when there are items to search OR when a view\n toggle is available — the toggle must reach an empty date-bearing\n collection (empty-day create) and a collection whose only views are\n custom ones (so its buttons + the \"+\" stay reachable). -->\n <div\n v-if=\"collection && (items.length > 0 || hasCalendar || hasKanban || hasCustomViews || canAddCustomView)\"\n class=\"px-6 py-3 bg-white border-b border-slate-100 flex items-center justify-between gap-4\"\n >\n <div v-if=\"items.length > 0\" class=\"relative flex-1 max-w-md\">\n <span class=\"absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none\">\n <span class=\"material-icons text-lg\">search</span>\n </span>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n :placeholder=\"t('collectionsView.searchPlaceholder')\"\n :aria-label=\"t('collectionsView.searchPlaceholder')\"\n class=\"w-full bg-slate-50 border border-slate-200/80 rounded-xl pl-9 pr-8 py-1.5 text-xs placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all font-medium\"\n />\n <button\n v-if=\"searchQuery\"\n type=\"button\"\n :aria-label=\"t('collectionsView.clearSearch')\"\n class=\"absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-400 hover:text-slate-600\"\n @click=\"searchQuery = ''\"\n >\n <span class=\"material-icons text-sm\">close</span>\n </button>\n </div>\n <div class=\"flex items-center gap-2\">\n <!-- View toggle: table ↔ calendar ↔ kanban. Calendar shows only when\n the schema has a `date` field, kanban only with an `enum` field;\n local UI state, never persisted. -->\n <div\n v-if=\"hasCalendar || hasKanban || hasCustomViews || canAddCustomView\"\n class=\"flex gap-0.5\"\n role=\"group\"\n :aria-label=\"t('collectionsView.viewToggle')\"\n >\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'table' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'table'\"\n data-testid=\"collection-view-toggle-table\"\n @click=\"setView('table')\"\n >\n <span class=\"material-icons text-sm\">table_rows</span>\n <span>{{ t(\"collectionsView.viewTable\") }}</span>\n </button>\n <button\n v-if=\"hasCalendar\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'calendar' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'calendar'\"\n data-testid=\"collection-view-toggle-calendar\"\n @click=\"setView('calendar')\"\n >\n <span class=\"material-icons text-sm\">calendar_month</span>\n <span>{{ t(\"collectionsView.viewCalendar\") }}</span>\n </button>\n <button\n v-if=\"hasKanban\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'kanban' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'kanban'\"\n data-testid=\"collection-view-toggle-kanban\"\n @click=\"setView('kanban')\"\n >\n <span class=\"material-icons text-sm\">view_kanban</span>\n <span>{{ t(\"collectionsView.viewKanban\") }}</span>\n </button>\n <!-- Custom (LLM-authored) views declared on the schema. -->\n <button\n v-for=\"cv in customViews\"\n :key=\"cv.id\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === customViewKey(cv.id) ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === customViewKey(cv.id)\"\n :data-testid=\"`collection-view-custom-${cv.id}`\"\n @click=\"setCustomView(cv.id)\"\n >\n <span class=\"material-icons text-sm\">{{ cv.icon || \"dashboard_customize\" }}</span>\n <span>{{ cv.label }}</span>\n </button>\n <!-- \"+\" — ask Claude to author a new custom view for this collection. -->\n <button\n v-if=\"canAddCustomView\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50\"\n :title=\"t('collectionsView.addView')\"\n :aria-label=\"t('collectionsView.addView')\"\n data-testid=\"collection-view-add\"\n @click=\"addCustomView\"\n >\n <span class=\"material-icons text-sm\">add</span>\n </button>\n <!-- Gear — per-collection config (currently: manage/delete custom\n views). Standalone only, and only when there's a view to manage. -->\n <button\n v-if=\"canConfigureViews\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50\"\n :title=\"t('collectionsView.config.open')\"\n :aria-label=\"t('collectionsView.config.open')\"\n data-testid=\"collection-config-open\"\n @click=\"configOpen = true\"\n >\n <span class=\"material-icons text-sm\">settings</span>\n </button>\n </div>\n <!-- Which date field anchors the grid (only when >1 date field). -->\n <select\n v-if=\"calendarActive && dateFields.length > 1\"\n :value=\"calendarAnchorField\"\n 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\"\n :aria-label=\"t('collectionsView.calendarFieldLabel')\"\n data-testid=\"collection-calendar-field\"\n @change=\"anchorOverride = ($event.target as HTMLSelectElement).value\"\n >\n <option v-for=\"key in dateFields\" :key=\"key\" :value=\"key\">{{ collection?.schema.fields[key]?.label ?? key }}</option>\n </select>\n <!-- Which enum field groups the board (only when >1 enum field). -->\n <select\n v-if=\"kanbanActive && enumFields.length > 1\"\n :value=\"kanbanGroupField\"\n 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\"\n :aria-label=\"t('collectionsView.kanbanFieldLabel')\"\n data-testid=\"collection-kanban-field\"\n @change=\"kanbanOverride = ($event.target as HTMLSelectElement).value\"\n >\n <option v-for=\"key in enumFields\" :key=\"key\" :value=\"key\">{{ collection?.schema.fields[key]?.label ?? key }}</option>\n </select>\n <div v-if=\"items.length > 0\" class=\"text-[10px] text-slate-400 font-bold uppercase tracking-wider select-none\">\n {{ t(\"collectionsView.searchSummary\", { shown: filteredItems.length, total: items.length }) }}\n </div>\n </div>\n </div>\n\n <!-- Repair banner: the server flagged record files that won't load /\n violate the schema and are silently skipped. The button reports\n them back to the LLM (same path presentCollection uses) so it\n fixes the files. View-independent, so it sits above the body. -->\n <div\n v-if=\"collection && dataIssues.length > 0\"\n 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\"\n data-testid=\"collections-data-issues\"\n >\n <span class=\"material-icons text-amber-600\">warning</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.dataIssuesDetected\", { count: dataIssues.length }) }}</span>\n <button\n type=\"button\"\n 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\"\n data-testid=\"collections-repair\"\n @click=\"repairCollection\"\n >\n <span class=\"material-icons text-sm\">build</span>\n <span>{{ t(\"collectionsView.repair\") }}</span>\n </button>\n </div>\n\n <div class=\"flex-1 overflow-auto\">\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" 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\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ loadError === \"not-found\" ? t(\"collectionsView.notFound\") : `${t(\"collectionsView.loadFailed\")}: ${loadError}` }}</span>\n </div>\n\n <div v-else-if=\"!collection\">\n <!-- defensive: loading=false, error=null, collection=null -->\n </div>\n\n <!-- Calendar body: an alternative to the table for date-bearing\n collections. Shown whenever active (even when empty) so the\n empty-cell create affordance stays available. -->\n <div v-else-if=\"calendarActive\" class=\"p-4\">\n <CollectionCalendarView\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :anchor-field=\"calendarAnchorField\"\n :end-field=\"calendarEndField\"\n :time-field=\"calendarTimeField\"\n :color-field=\"hasKanban ? kanbanGroupField : ''\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n @select=\"onCalendarSelect\"\n @open-day=\"onOpenDay\"\n />\n\n <!-- Day (time-allocation) popup. Selecting a record opens it on the\n right of this modal (the `#detail` slot), replacing the old panel\n that sat below the grid. -->\n <CollectionDayView\n v-if=\"openDay\"\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :day=\"openDay\"\n :anchor-field=\"calendarAnchorField\"\n :end-field=\"calendarEndField\"\n :time-field=\"calendarTimeField\"\n :color-field=\"hasKanban ? kanbanGroupField : ''\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n :can-create=\"canCreate\"\n :show-detail=\"Boolean(viewing || editing)\"\n @select=\"onCalendarSelect\"\n @create-on=\"createOnDate\"\n @close=\"onDayClose\"\n >\n <template #detail>\n <CollectionRecordPanel\n v-model:editing=\"editing\"\n :collection=\"collection\"\n :viewing=\"viewing\"\n :saving=\"saving\"\n :save-error=\"saveError\"\n :action-error=\"actionError\"\n :action-pending=\"actionPending\"\n :visible-actions=\"visibleActions\"\n :live-record=\"liveRecord\"\n :live-derived=\"liveDerived\"\n :view-title=\"viewTitle\"\n :is-singleton=\"isSingleton\"\n :render=\"render\"\n :locale=\"locale\"\n @submit=\"saveEditor\"\n @cancel=\"cancelEditor\"\n @edit=\"editFromView\"\n @close=\"onDayClose\"\n @delete=\"viewing && confirmDelete(viewing)\"\n @run-action=\"runAction\"\n />\n </template>\n </CollectionDayView>\n\n <!-- Undated records (the \"no date\" tray) have no timeline slot, so\n they open in the shared record modal (rendered once at the View\n root) instead of the day view. -->\n </div>\n\n <!-- Kanban body: an alternative to the table for enum-bearing\n collections. The board groups records into columns by the chosen\n enum field; dragging a card between columns writes that field. -->\n <div v-else-if=\"kanbanActive\" class=\"h-full flex flex-col\">\n <!-- Inline-edit failure banner: a card drop (group-field write) was\n rolled back. The detail panel's `saveError` isn't shown during a\n drag, so inline edits surface their own — same as the table. -->\n <div\n v-if=\"inlineError\"\n 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\"\n data-testid=\"collections-inline-error\"\n >\n <span class=\"material-icons text-red-600\">error</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.inlineSaveFailed\", { error: inlineError }) }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100\"\n :aria-label=\"t('common.close')\"\n @click=\"inlineError = null\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </div>\n <div class=\"flex-1 min-h-0 px-3 py-2\">\n <CollectionKanbanView\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :group-field=\"kanbanGroupField\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n :notified=\"notifiedSeverities\"\n @select=\"onCalendarSelect\"\n @move=\"onKanbanMove\"\n />\n </div>\n </div>\n\n <!-- Custom (LLM-authored) HTML view, rendered in a sandboxed iframe over\n the collection's records. Placed before the empty states so it shows\n even for an empty collection (e.g. a still-empty year grid). -->\n <div v-else-if=\"activeCustomView\" class=\"h-full\" data-testid=\"collection-custom-view-body\">\n <CollectionCustomView :slug=\"collection.slug\" :view=\"activeCustomView\" />\n </div>\n\n <div v-else-if=\"items.length === 0 && editing?.mode !== 'create'\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2\">\n <span class=\"material-icons text-4xl text-slate-300\">folder_open</span>\n <p class=\"font-semibold text-slate-600\">{{ t(\"collectionsView.itemsEmpty\") }}</p>\n </div>\n\n <div\n v-else-if=\"filteredItems.length === 0 && editing?.mode !== 'create'\"\n class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2\"\n >\n <span class=\"material-icons text-4xl text-slate-300\">search_off</span>\n <p class=\"font-semibold text-slate-600\">{{ t(\"collectionsView.noMatchingItems\") }}</p>\n <button type=\"button\" class=\"text-xs text-indigo-600 font-semibold hover:underline\" @click=\"searchQuery = ''\">\n {{ t(\"collectionsView.clearSearch\") }}\n </button>\n </div>\n\n <div v-else class=\"overflow-x-auto [container-type:inline-size]\">\n <!-- Inline-edit failure banner: a cell write (checkbox/dropdown)\n was rolled back; the detail panel's `saveError` isn't visible\n here so inline edits surface their own. -->\n <div\n v-if=\"inlineError\"\n 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\"\n data-testid=\"collections-inline-error\"\n >\n <span class=\"material-icons text-red-600\">error</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.inlineSaveFailed\", { error: inlineError }) }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100\"\n :aria-label=\"t('common.close')\"\n @click=\"inlineError = null\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </div>\n <table class=\"min-w-full text-xs\">\n <thead>\n <tr class=\"bg-slate-50 border-b border-slate-200\">\n <th\n v-for=\"[key, field] in listColumnFields\"\n :key=\"key\"\n :aria-sort=\"isSortableField(field) ? sortAriaValue(key) : undefined\"\n class=\"px-5 py-3 font-bold text-slate-500 text-left uppercase tracking-wider whitespace-nowrap\"\n >\n <div class=\"flex items-center gap-1\">\n <span class=\"truncate max-w-[14rem]\" :title=\"field.label\">{{ field.label }}</span>\n <button\n v-if=\"isSortableField(field)\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded p-0.5 -my-1 leading-none transition-colors\"\n :class=\"sortButtonClass(key)\"\n :data-testid=\"`collections-sort-${key}`\"\n :aria-label=\"t('collectionsView.sortBy', { field: field.label })\"\n @click.stop=\"cycleSort(key)\"\n @pointerenter=\"hoveredSortKey = key\"\n @pointerleave=\"hoveredSortKey = null\"\n >\n <span class=\"material-icons text-base align-middle\">{{ sortIconName(key) }}</span>\n </button>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100 bg-white\">\n <template v-for=\"item in sortedItems\" :key=\"String(item[collection.schema.primaryKey] ?? '')\">\n <tr\n class=\"hover:bg-slate-50/70 cursor-pointer transition-colors focus:outline-none focus:bg-indigo-50/30\"\n :class=\"isRowOpen(item) || isEditingRow(item) ? 'bg-indigo-50/40' : ''\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t('collectionsView.openItem', { id: String(item[collection.schema.primaryKey] ?? '') })\"\n :data-testid=\"`collections-row-${item[collection.schema.primaryKey]}`\"\n @click=\"openView(item)\"\n @keydown.enter.self=\"openView(item)\"\n @keydown.space.self.prevent=\"openView(item)\"\n >\n <td v-for=\"[key, field] in listColumnFields\" :key=\"key\" class=\"px-5 py-2 text-slate-700 align-middle max-w-xs font-medium\">\n <!-- Conditionally hidden field (`when` predicate) → blank cell. -->\n <template v-if=\"fieldVisible(field, item)\">\n <!-- Toggle → inline checkbox projecting an enum field.\n Stores nothing itself; toggling writes onValue/\n offValue to the projected field via the same PUT. -->\n <input\n v-if=\"field.type === 'toggle'\"\n type=\"checkbox\"\n :checked=\"toggleChecked(item, field)\"\n :disabled=\"isRowInlineSaving(item)\"\n 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\"\n :data-testid=\"`collections-inline-toggle-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitToggle(item, field)\"\n />\n\n <!-- Boolean → inline checkbox. Tap toggles + saves\n immediately; `@click.stop` so it doesn't open the\n row's detail panel. Unset (undefined) and explicit\n false both render unchecked. -->\n <input\n v-else-if=\"field.type === 'boolean'\"\n type=\"checkbox\"\n :checked=\"item[key] === true\"\n :disabled=\"isRowInlineSaving(item)\"\n 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\"\n :data-testid=\"`collections-inline-bool-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitInlineEdit(item, String(key), field, ($event.target as HTMLInputElement).checked)\"\n />\n\n <!-- Ref router-link badge -->\n <span v-else-if=\"field.type === 'ref' && field.to && typeof item[key] === 'string' && item[key]\" class=\"block truncate\">\n <router-link\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(item[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 hover:underline font-semibold\"\n :data-testid=\"`collections-ref-link-${key}-${item[key]}`\"\n @click.stop\n >{{ refDisplay(field.to, String(item[key])) }}</router-link\n >\n </span>\n\n <!-- Enum → inline dropdown. Selecting writes + saves\n immediately; the empty placeholder clears the field.\n `@click.stop` keeps the row's detail panel closed. -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :value=\"item[key] == null ? '' : String(item[key])\"\n :disabled=\"isRowInlineSaving(item)\"\n class=\"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\"\n :class=\"enumControlClass(String(key), item[key])\"\n :data-testid=\"`collections-inline-enum-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitInlineEdit(item, String(key), field, ($event.target as HTMLSelectElement).value)\"\n >\n <option v-if=\"showEnumPlaceholder(item, String(key))\" value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Money -->\n <span v-else-if=\"field.type === 'money'\" class=\"block truncate tabular-nums font-semibold text-slate-900\">{{\n formatMoney(item[key], resolveCurrency(field, item), locale)\n }}</span>\n\n <!-- Table summary counter -->\n <span\n v-else-if=\"field.type === 'table'\"\n 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\"\n >\n <span class=\"material-icons text-[11px]\">list</span>\n <span>{{ tableSummary(item[key]) }}</span>\n </span>\n\n <!-- Derived formula fields -->\n <span\n v-else-if=\"field.type === 'derived'\"\n 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\"\n >{{ derivedDisplay(field, evaluateDerivedAgainstItem(field, String(key), item), item) }}</span\n >\n\n <!-- URL string → external link (new tab). `@click.stop` so\n clicking the link doesn't also open the row's detail. -->\n <a\n v-else-if=\"field.type !== 'file' && isExternalUrl(item[key])\"\n :href=\"String(item[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-url-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered\n app in a new tab. `@click.stop` keeps the row's\n detail panel from also opening. -->\n <a\n v-else-if=\"field.type === 'file' && artifactUrl(item[key])\"\n :href=\"artifactUrl(item[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-file-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && fileRoutePath(item[key])\"\n :to=\"fileRoutePath(item[key]) ?? ''\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-file-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</router-link\n >\n\n <span v-else class=\"block truncate text-slate-600\">{{ formatCell(item[key], field.type) }}</span>\n </template>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n </div>\n\n <!-- Shared record modal — the single open/edit surface for every view\n mode (table / kanban) and the calendar's undated tray.\n Calendar's DATED records keep their day-view modal (which embeds the\n same panel on its right), so this is suppressed while that's open. -->\n <CollectionRecordModal v-if=\"collection && (viewing || editing) && !(calendarActive && openDay)\" @close=\"closeRecordModal\">\n <CollectionRecordPanel\n v-model:editing=\"editing\"\n :collection=\"collection\"\n :viewing=\"viewing\"\n :saving=\"saving\"\n :save-error=\"saveError\"\n :action-error=\"actionError\"\n :action-pending=\"actionPending\"\n :visible-actions=\"visibleActions\"\n :live-record=\"liveRecord\"\n :live-derived=\"liveDerived\"\n :view-title=\"viewTitle\"\n :is-singleton=\"isSingleton\"\n :render=\"render\"\n :locale=\"locale\"\n @submit=\"saveEditor\"\n @cancel=\"cancelEditor\"\n @edit=\"editFromView\"\n @close=\"closeView\"\n @delete=\"viewing && confirmDelete(viewing)\"\n @run-action=\"runAction\"\n />\n </CollectionRecordModal>\n\n <!-- Per-collection config (gear): manage/delete custom views. -->\n <CollectionViewConfigModal\n v-if=\"configOpen && collection\"\n :slug=\"collection.slug\"\n :title=\"collection.title\"\n :views=\"customViews\"\n @changed=\"onViewsChanged\"\n @close=\"configOpen = false\"\n />\n\n <!-- Chat modal — collect a message and start a new general-role chat\n seeded with the collection's skill command (`/<slug> <message>`). -->\n <div\n v-if=\"chatOpen && collection\"\n class=\"fixed inset-0 z-30 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm p-4 transition-all duration-300\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"collections-chat-title\"\n data-testid=\"collections-chat-modal\"\n @click.self=\"closeChat\"\n @keydown.esc=\"closeChat\"\n >\n <div class=\"bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden\">\n <header class=\"px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50\">\n <div class=\"h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50\">\n <span class=\"material-icons text-lg\">forum</span>\n </div>\n <div class=\"flex-1\">\n <h2 id=\"collections-chat-title\" class=\"text-sm font-bold text-slate-800 uppercase tracking-wide\">{{ t(\"collectionsView.chatTitle\") }}</h2>\n <span class=\"text-xs text-slate-400 font-semibold\">{{ collection.title }}</span>\n </div>\n <button\n type=\"button\"\n 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\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-chat-close\"\n @click=\"closeChat\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </header>\n\n <div class=\"px-6 py-5\">\n <textarea\n ref=\"chatInputEl\"\n v-model=\"chatMessage\"\n rows=\"4\"\n :placeholder=\"t('collectionsView.chatPlaceholder')\"\n 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\"\n data-testid=\"collections-chat-input\"\n @keydown.meta.enter=\"submitChat\"\n @keydown.ctrl.enter=\"submitChat\"\n ></textarea>\n </div>\n\n <footer class=\"px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-chat-cancel\"\n @click=\"closeChat\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"!chatMessage.trim()\"\n data-testid=\"collections-chat-send\"\n @click=\"submitChat\"\n >\n {{ t(\"collectionsView.chatStart\") }}\n </button>\n </footer>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionRecordModal from \"./CollectionRecordModal.vue\";\nimport CollectionCalendarView from \"./CollectionCalendarView.vue\";\nimport CollectionDayView from \"./CollectionDayView.vue\";\nimport CollectionKanbanView from \"./CollectionKanbanView.vue\";\nimport CollectionRecordPanel from \"./CollectionRecordPanel.vue\";\nimport CollectionViewConfigModal from \"./CollectionViewConfigModal.vue\";\nimport CollectionCustomView from \"./CollectionCustomView.vue\";\nimport { useCollectionRendering } from \"../useCollectionRendering\";\nimport {\n readCollectionViewMode,\n writeCollectionViewMode,\n readCollectionSort,\n writeCollectionSort,\n type CollectionViewMode,\n type BuiltInViewMode,\n} from \"../collectionViewMode\";\nimport { collectionUi } from \"../uiContext\";\nimport { dateOf, type Ymd } from \"../../core/calendarGrid\";\nimport {\n isSortableField,\n nextSortDirection,\n sortItems,\n numericSortValue,\n stringSortValue,\n dateSortValue,\n enumSortValue,\n boolSortValue,\n type SortState,\n type SortValue,\n} from \"../../core/sortItems\";\nimport { shortHexId } from \"../../core/shortHexId\";\nimport { defangForPrompt } from \"../../core/promptSafety\";\nimport { actionVisible, fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { buildUpdatedRecord, coerceInlineValue, draftToRecord, firstMissingRequiredField, rowFromItem } from \"../../core/draft\";\nimport type {\n CollectionAction,\n CollectionCustomView as CustomViewSpec,\n CollectionDetail,\n CollectionItem,\n CollectionFieldSpec as FieldSpec,\n} from \"../../core/schema\";\nimport type { CollectionRecordIssue, CollectionNotifySeverity, EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n/** `slug` / `selected` are supplied only in EMBEDDED mode (the\n * `presentCollection` chat card mounts this component and drives both\n * from the tool result). In standalone route mode (the\n * `/collections/:slug` page) both are undefined and the component reads\n * `route.params.slug` / `route.query.selected` as before.\n *\n * `sendTextMessage` is forwarded ONLY by the chat card — its presence\n * is our \"rendered inside a chat\" signal. When set, chat-triggering\n * actions send into the current session instead of spawning a new\n * chat (see `runAction` / `submitChat`). */\nconst props = defineProps<{\n slug?: string;\n selected?: string;\n sendTextMessage?: (text?: string) => void;\n /** Embedded mode only: initial view / anchor / group restored from the\n * card's persisted `viewState` so a switch to calendar or kanban\n * survives a remount. (The table sort is NOT a card prop — it's a shared\n * per-collection localStorage preference, read by both modes.) */\n initialView?: BuiltInViewMode;\n initialAnchorField?: string;\n initialGroupField?: string;\n}>();\n\nconst emit = defineEmits<{\n /** Embedded mode only: the open record changed (id) or closed (null).\n * The card persists this in its tool-result `viewState` so the open\n * item survives a re-render. */\n select: [id: string | null];\n /** Embedded mode only: the view mode / calendar anchor / kanban group\n * changed. The card persists these alongside `selected` so the calendar\n * and kanban stick. (The table sort is shared via localStorage instead.) */\n viewStateChange: [state: { view: BuiltInViewMode; anchorField: string; groupField: string }];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n// All host couplings (data, routing, confirm, chat, shortcuts, notifications,\n// the pin toggle) come through the injected CollectionUi binding. The aliases\n// keep the body's call sites unchanged where the host shape matched 1:1.\nconst cui = collectionUi();\nconst { confirm: openConfirm, unpin, pinToggle, startChat } = cui;\nconst appApi = { startNewChat: startChat };\n\n/** Embedded when a `slug` prop is supplied; standalone (route-driven)\n * otherwise. Switches the slug/selected source and the open/close\n * navigation behaviour. */\nconst embedded = computed<boolean>(() => props.slug !== undefined);\n\n/** Active collection slug: the prop in embedded mode, else the route\n * param. */\nconst activeSlug = computed<string | undefined>(() => {\n if (props.slug !== undefined) return props.slug;\n const slug = cui.routeSlug();\n return slug !== undefined && slug.length > 0 ? slug : undefined;\n});\n\n/** Active open-record id: the prop in embedded mode (may be undefined),\n * else the `?selected=` query. */\nconst activeSelected = computed<string | undefined>(() => {\n if (embedded.value) return props.selected;\n return cui.routeSelectedId();\n});\n\nconst collection = ref<CollectionDetail | null>(null);\nconst items = ref<CollectionItem[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\n// Record files the server flagged as malformed/invalid (silently skipped\n// at read time). When non-empty the view shows a Repair banner whose\n// button reports them back to the LLM. See `repairCollection`.\nconst dataIssues = ref<CollectionRecordIssue[]>([]);\n\n// Primary-key → notification severity for this collection's records that\n// currently have an active bell notification — passed to the Kanban board so\n// it can flag those cards in the matching bell colour (urgent red / nudge amber).\nconst notifiedSeverities = computed<Map<string, CollectionNotifySeverity>>(() => {\n const slug = collection.value?.slug;\n return slug ? cui.notifiedSeverities(slug) : new Map<string, CollectionNotifySeverity>();\n});\n/** True while a feed collection's manual refresh is in flight. */\nconst refreshing = ref(false);\n/** Slug already auto-refreshed on first open — prevents a reload loop\n * (the auto-refresh reloads the view, which would re-trigger otherwise). */\nconst autoRefreshedSlug = ref<string | null>(null);\nconst editing = ref<EditState | null>(null);\n/** The record currently shown in read-only \"open\" mode. Distinct\n * from `editing`: open mode renders formatted values (no inputs)\n * and is what a `/collections/<slug>?selected=<id>` deep link\n * lands on. Mutually exclusive with `editing` in practice —\n * `editFromView` hands off from one to the other. */\nconst viewing = ref<CollectionItem | null>(null);\n/** The calendar day whose time-allocation popup is open, or null. The\n * selected record (`viewing`) renders in that popup's right pane; a record\n * with no resolvable day falls back to the panel below the grid. */\nconst openDay = ref<Ymd | null>(null);\nconst saving = ref(false);\nconst saveError = ref<string | null>(null);\n/** Error from an inline table-cell edit (checkbox/dropdown). Distinct\n * from `saveError` (rendered only inside the detail panel, which is\n * closed during inline editing) — shown as a banner above the table. */\nconst inlineError = ref<string | null>(null);\n/** Per-load snapshot of enum cells that had NO value when fetched\n * (keyed `<rowId>:<fieldKey>`). Only these cells offer the empty\n * placeholder option in their inline dropdown — a cell that already\n * has a value can't be blanked inline (use the edit form for that). */\nconst enumOriginallyEmpty = ref<Set<string>>(new Set());\n/** Rows with an inline cell save in flight (by `rowId`). While a row is\n * here its inline controls are disabled, so two quick edits to the same\n * row can't race two full-record PUTs — an older PUT landing last would\n * otherwise clobber the newer field on disk while the UI shows the\n * newer optimistic value (Codex PR #1599 P2). */\nconst inlineSavingRows = ref<Set<string>>(new Set());\nconst actionPending = ref(false);\nconst actionError = ref<string | null>(null);\nconst collectionActionPending = ref(false);\nconst chatOpen = ref(false);\nconst chatMessage = ref(\"\");\nconst chatInputEl = ref<HTMLTextAreaElement | null>(null);\n\n// Shared rendering + linked-data layer: owns the ref/embed caches and\n// every value-formatting helper, reused by the extracted record panel\n// (table + calendar) so there's one implementation. Destructure the\n// helpers the list table renders with; pass the whole object to the\n// panel as its `render` prop.\nconst render = useCollectionRendering(collection, locale);\nconst {\n refRecordCache,\n refDisplay,\n formatMoney,\n resolveCurrency,\n derivedDisplay,\n evaluateDerivedAgainstItem,\n formatCell,\n isExternalUrl,\n artifactUrl,\n fileRoutePath,\n} = render;\n\nconst searchQuery = ref(\"\");\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object-valued fields (table rows, nested records) are skipped —\n * they don't render as searchable text in the list table. */\nfunction itemMatchesQuery(item: CollectionItem, query: string): boolean {\n return Object.values(item).some((val) => {\n if (val === undefined || val === null || typeof val === \"object\") return false;\n return String(val).toLowerCase().includes(query);\n });\n}\n\nconst filteredItems = computed<CollectionItem[]>(() => {\n const query = searchQuery.value.trim().toLowerCase();\n if (!query) return items.value;\n return items.value.filter((item) => itemMatchesQuery(item, query));\n});\n\n// ── List-table sort (single active column, header toggle) ─────────\n// Calendar / kanban keep their own ordering; only the table consumes\n// `sortedItems`. The active sort is a single SHARED per-collection\n// preference in localStorage — both the standalone page and embedded chat\n// cards read AND write it, so a sort set anywhere is consistent the next\n// time the collection is viewed. Resets only when a DIFFERENT collection\n// loads (the slug watch), so the sort survives a refresh / edit / remount.\nfunction storedSortFor(slug: string | undefined): SortState | null {\n return (slug && readCollectionSort(slug)) || null;\n}\nconst sortState = ref<SortState | null>(storedSortFor(activeSlug.value));\n// The column whose sort button is currently hovered (at most one). Hover\n// previews the NEXT click's state, so descending visibly fades back to the\n// light-grey \"off\" look — signalling the next click clears the sort.\nconst hoveredSortKey = ref<string | null>(null);\n\nfunction sortDirectionFor(key: string): \"asc\" | \"desc\" | null {\n return sortState.value?.field === key ? sortState.value.direction : null;\n}\n\n/** The direction whose visuals to render: on hover, preview the next\n * click's state; otherwise show the column's actual state. */\nfunction effectiveSortDir(key: string): \"asc\" | \"desc\" | null {\n const current = sortDirectionFor(key);\n return hoveredSortKey.value === key ? nextSortDirection(current) : current;\n}\n\n/** Cycle a column none → asc → desc → none; activating one clears the rest. */\nfunction cycleSort(key: string): void {\n const next = nextSortDirection(sortDirectionFor(key));\n sortState.value = next ? { field: key, direction: next } : null;\n}\n\nfunction sortIconName(key: string): string {\n return effectiveSortDir(key) === \"desc\" ? \"arrow_downward\" : \"arrow_upward\";\n}\n\n// Dark grey while a direction is active; light grey for the \"off\" state —\n// so hovering a descending column previews the cleared look.\nfunction sortButtonClass(key: string): string {\n return effectiveSortDir(key) ? \"text-slate-600\" : \"text-slate-300\";\n}\n\n/** ARIA `aria-sort` token for a column's header cell. */\nfunction sortAriaValue(key: string): \"ascending\" | \"descending\" | \"none\" {\n const dir = sortDirectionFor(key);\n return dir === \"asc\" ? \"ascending\" : dir === \"desc\" ? \"descending\" : \"none\";\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nfunction scalarSortValue(field: FieldSpec, raw: unknown): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(refDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Comparable value for one row under the active field. Toggle and derived\n * need the whole record; every other type keys off the raw cell. */\nfunction sortValueOf(field: FieldSpec, key: string, item: CollectionItem): SortValue {\n if (field.type === \"toggle\") return boolSortValue(toggleChecked(item, field));\n if (field.type === \"derived\") return derivedSortValue(field, key, item);\n return scalarSortValue(field, item[key]);\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date/datetime → epoch, anything else → the enriched value as a string. */\nfunction derivedSortValue(field: FieldSpec, key: string, item: CollectionItem): SortValue {\n const { display } = field;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(evaluateDerivedAgainstItem(field, key, item));\n }\n const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;\n if (display === \"date\" || display === \"datetime\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\nconst sortedItems = computed<CollectionItem[]>(() => {\n const state = sortState.value;\n const field = state ? collection.value?.schema.fields[state.field] : undefined;\n if (!state || !field) return filteredItems.value;\n return sortItems(filteredItems.value, state.direction, (item) => sortValueOf(field, state.field, item));\n});\n\n// ────────────────────────────────────────────────────────────────\n// Open / edit record panel (shared modal + calendar day view)\n// ────────────────────────────────────────────────────────────────\n// Detail, edit, and create all render `CollectionRecordPanel` inside the\n// shared `CollectionRecordModal` (or the calendar day view for dated\n// records). One panel open at a time (`viewing` / `editing` are single\n// refs). The list table only highlights the open/edited row.\n\n/** Stringified primary-key value for a row (the row's stable identity). */\nfunction rowId(item: CollectionItem): string {\n const primaryKey = collection.value?.schema.primaryKey;\n return primaryKey ? String(item[primaryKey] ?? \"\") : \"\";\n}\n\n/** Stable key for one cell in the `enumOriginallyEmpty` snapshot. */\nfunction cellKey(rowIdValue: string, fieldKey: string): string {\n return `${rowIdValue}:${fieldKey}`;\n}\n\n/** Build the set of enum cells that were empty in the freshly-fetched\n * records — the only cells whose inline dropdown offers an empty option. */\nfunction snapshotEmptyEnums(schema: CollectionDetail[\"schema\"], records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = String(record[schema.primaryKey] ?? \"\");\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n\n/** Whether an inline enum dropdown should render its empty placeholder\n * option: only for cells with no value at load time. */\nfunction showEnumPlaceholder(item: CollectionItem, fieldKey: string): boolean {\n return enumOriginallyEmpty.value.has(cellKey(rowId(item), fieldKey));\n}\n\n/** Tailwind fill/text/border classes tinting an inline enum `<select>` by its\n * current value's colour (palette, or notification red/amber/grey when the\n * field is the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const schema = collection.value?.schema;\n if (!schema) return \"\";\n const cls = resolveEnumColor(schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n/** This row is the one open in read-only detail. */\nfunction isRowOpen(item: CollectionItem): boolean {\n return viewing.value !== null && rowId(viewing.value) === rowId(item);\n}\n\n/** This row is the one being edited (highlights it in the list while the\n * edit modal is open). Create mode has no backing row, so nothing matches. */\nfunction isEditingRow(item: CollectionItem): boolean {\n const draft = editing.value;\n if (!draft || draft.mode === \"create\") return false;\n return draft.originalId === rowId(item);\n}\n\n/** Re-run a feed collection's retrieval now, then reload its records.\n * Only reachable when `schema.ingest` is present (button is gated). */\nasync function refreshFeed(): Promise<void> {\n const current = collection.value;\n if (!current?.schema.ingest || refreshing.value) return;\n refreshing.value = true;\n inlineError.value = null;\n const result = await cui.refreshCollection(current.slug);\n refreshing.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n await loadCollection(current.slug);\n // refreshOne reports retriever failures via `errors` even on HTTP 200, so\n // surface them — otherwise a failed refresh looks like success.\n if (result.data.errors.length > 0) {\n inlineError.value = t(\"collectionsView.refreshFailed\", { error: result.data.errors.join(\"; \") });\n }\n}\n\n/** Collection-level header actions. No `when` predicate (no record). */\nconst collectionActions = computed<CollectionAction[]>(() => collection.value?.schema.collectionActions ?? []);\n\n/** Run a collection-level action: ask the server to assemble the seed\n * prompt (a progress summary of all records + the template), then start\n * a new chat in the action's role with it. Generic — no domain knowledge. */\nasync function runCollectionAction(action: CollectionAction): Promise<void> {\n const current = collection.value;\n if (!current || collectionActionPending.value) return;\n collectionActionPending.value = true;\n inlineError.value = null;\n const result = await cui.runCollectionAction(current.slug, action.id);\n collectionActionPending.value = false;\n if (!result.ok) {\n inlineError.value = result.error;\n return;\n }\n if (props.sendTextMessage) {\n props.sendTextMessage(result.data.prompt);\n return;\n }\n appApi.startNewChat(result.data.prompt, result.data.role);\n}\n\n/** Report the server-detected record data problems back to the LLM so it\n * fixes the offending files. Mirrors the `presentCollection` validation\n * path (`dispatchPresentCollection`), but user-initiated via the Repair\n * button instead of fired automatically after a write. Dispatches into\n * the current chat when embedded, else seeds a new General chat. */\nfunction repairCollection(): void {\n const current = collection.value;\n if (!current || dataIssues.value.length === 0) return;\n // Issue text carries record-controlled values (ids, enum values), so defang\n // structural injection vectors before it rides into the LLM prompt. Shared\n // with the server's presentCollection path via `defangForPrompt` so the two\n // can't drift (it also collapses whitespace, closing a newline-injection gap).\n const lines = dataIssues.value.map((issue) => `- ${defangForPrompt(issue.file)}: ${defangForPrompt(issue.problem)}`).join(\"\\n\");\n const prompt = t(\"collectionsView.repairPrompt\", { title: current.title, count: dataIssues.value.length, issues: lines });\n if (props.sendTextMessage) {\n props.sendTextMessage(prompt);\n return;\n }\n appApi.startNewChat(prompt, cui.generalRoleId);\n}\n\n/** Actions whose optional `when` predicate matches the open record.\n * Status-driven buttons (e.g. invoice \"Record payment\") stay hidden\n * until the record reaches the matching state. */\nconst visibleActions = computed<CollectionAction[]>(() => {\n const record = viewing.value;\n if (!record) return [];\n return (collection.value?.schema.actions ?? []).filter((action) => actionVisible(action, record));\n});\n\n/** Run a schema-declared action on the open record: ask the server to\n * assemble the seed prompt, then start a new chat in the action's\n * role with it. Generic — no knowledge of what the action does. */\nasync function runAction(action: CollectionAction): Promise<void> {\n if (!collection.value || !viewing.value) return;\n const itemId = String(viewing.value[collection.value.schema.primaryKey] ?? \"\");\n if (!itemId) return;\n actionPending.value = true;\n actionError.value = null;\n const result = await cui.runItemAction(collection.value.slug, itemId, action.id);\n actionPending.value = false;\n if (!result.ok) {\n actionError.value = result.error;\n return;\n }\n // In a chat card we have a channel into the current session — send\n // the seed prompt there rather than spawning a new chat. Standalone\n // route mode has no such channel, so start a fresh chat in the\n // action's role (which carries the tools the action needs).\n if (props.sendTextMessage) {\n props.sendTextMessage(result.data.prompt);\n return;\n }\n appApi.startNewChat(result.data.prompt, result.data.role);\n}\n\n/** Open the chat modal, blanking any prior draft and focusing the input. */\nfunction openChat(): void {\n chatMessage.value = \"\";\n chatOpen.value = true;\n void nextTick(() => chatInputEl.value?.focus());\n}\n\nfunction closeChat(): void {\n chatOpen.value = false;\n}\n\n/** Build the chat seed text for the current view.\n *\n * A collection IS a skill, so its slug doubles as a slash command:\n * \"I want to create an item\" on `mc_worklog` becomes\n * `/mc_worklog I want to create an item`.\n *\n * A feed is data-only — it has NO skill, so `/<slug>` would resolve to\n * nothing. Instead, point the agent at the feed's schema + records\n * (`feeds/<slug>/schema.json` and `<dataPath>/`) and let it act on the\n * request directly. */\nfunction buildChatSeed(slug: string, message: string): string {\n const schema = collection.value?.schema;\n // A feed carries an `ingest` block; a plain collection does not. Checked\n // here (rather than via the `isFeed` computed, defined further down) to\n // keep this helper self-contained and avoid a use-before-define.\n if (!schema?.ingest) return `/${slug} ${message}`;\n const dataPath = schema.dataPath ?? `data/feeds/${slug}`;\n return t(\"collectionsView.feedChatSeed\", { slug, dataPath, message });\n}\n\n/** Start a new general-role chat seeded from the current view. */\nfunction submitChat(): void {\n if (!collection.value) return;\n const message = chatMessage.value.trim();\n if (!message) return;\n closeChat();\n const text = buildChatSeed(collection.value.slug, message);\n // Chat card → send into the current session; standalone → new chat.\n if (props.sendTextMessage) {\n props.sendTextMessage(text);\n return;\n }\n appApi.startNewChat(text, cui.generalRoleId);\n}\n\nasync function loadCollection(slug: string): Promise<void> {\n // Snapshot the shortcut kind BEFORE the await — if the user navigates\n // between /feeds/:slug and /collections/:slug while the fetch is in\n // flight, reading route.name in the 404 branch could unpin the wrong\n // (kind, slug) pair.\n const requestedKind = !embedded.value && cui.isFeedRoute() ? \"feed\" : \"collection\";\n loading.value = true;\n loadError.value = null;\n collection.value = null;\n items.value = [];\n dataIssues.value = []; // never carry a previous collection's issues over\n searchQuery.value = \"\"; // Reset search query on collection load\n // NOTE: the active column sort is NOT reset here — it's part of the view\n // state, so it must survive a refresh / edit reload and an embedded card\n // remount. The collection-SWITCH reset lives in the `activeSlug` watch.\n render.resetLinkedCaches();\n viewing.value = null;\n openDay.value = null; // never carry a previous collection's open day over\n const result = await cui.fetchCollectionDetail(slug);\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.status === 404 ? \"not-found\" : result.error;\n // Dead-click safety net: a pinned shortcut for a collection/feed\n // deleted out-of-band (e.g. via chat) lands here. Self-prune it so\n // the launcher doesn't keep a button that 404s. Standalone only\n // (embedded cards carry no shortcut), and only if we're still on the\n // slug that triggered this fetch.\n if (result.status === 404 && !embedded.value && activeSlug.value === slug) {\n void unpin(requestedKind, slug);\n }\n return;\n }\n collection.value = result.data.collection;\n items.value = result.data.items;\n dataIssues.value = result.data.issues ?? [];\n enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);\n // Fan out to fetch each unique target collection so the table can\n // render ref values as display names (not slugs) and the form\n // dropdown has options. Failures fall back gracefully — the table\n // cell shows the raw slug and the form falls back to text input.\n // Pass the slug that triggered THIS load so the helper can drop\n // its result if a faster subsequent load has already switched us\n // to a different collection (Codex P1 review on PR #1495).\n await render.loadLinkedCollections(result.data.collection.schema, slug);\n // A `?selected=<id>` deep link opens that record in read-only\n // mode once its items are available. Guard against a stale load:\n // only act if we're still on the slug that triggered this fetch.\n if (collection.value?.slug === slug) {\n syncViewToSelected();\n maybeOpenCalendarForSelected();\n }\n maybeAutoRefreshFeed(slug);\n}\n\n// First-open auto-refresh: when a feed view opens with no records yet\n// (e.g. a just-registered feed that hasn't hit the scheduler), fetch once\n// so data appears without a manual Refresh. Guarded per slug so the reload\n// `refreshFeed` triggers can't loop; the view re-mounts per slug, so each\n// open retries at most once.\nfunction maybeAutoRefreshFeed(slug: string): void {\n if (embedded.value) return;\n const current = collection.value;\n if (current?.slug !== slug || !current.schema.ingest) return;\n if (items.value.length > 0 || autoRefreshedSlug.value === slug) return;\n autoRefreshedSlug.value = slug;\n void refreshFeed();\n}\n\n/** Schema fields excluding display-only `embed` fields — used by the\n * list table only (a whole embedded record doesn't fit a table cell,\n * and it'd be identical in every row). The detail modal and the edit\n * form iterate the full `schema.fields` so embeds render there too. */\n// Fields shown as columns in the list table. Excludes `embed`\n// (display-only fixed record, no per-record value), `image` — a\n// per-row <img> fetches one file each, too expensive for a collection\n// with many records, and the image is shown in the detail view anyway —\n// and the primary key (an id is plumbing, not data: it identifies the\n// row via data-testid / ref links but doesn't earn a column).\nconst listColumnFields = computed<[string, FieldSpec][]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields).filter(\n ([key, field]) => field.type !== \"embed\" && field.type !== \"image\" && key !== collection.value?.schema.primaryKey,\n )\n : [],\n);\n\n/** True when the current collection declares `schema.singleton` —\n * exactly one record, its primary key fixed to the declared value. */\nconst isSingleton = computed<boolean>(() => Boolean(collection.value?.schema.singleton));\n\n/** Whether the Add button should show. Always for a normal collection;\n * for a singleton only until its one record exists. */\nconst canCreate = computed<boolean>(() => {\n if (!collection.value) return false;\n return !(isSingleton.value && items.value.length > 0);\n});\n\n// A collection is deletable only when it's project-scope AND not a\n// preset (`mc-*`) — mirrors the server-side rule in\n// `deleteCollection`. User-scope skills are read-only from MulmoClaude;\n// presets re-seed on restart so deleting them is futile.\nconst canDeleteCollection = computed<boolean>(() => {\n const current = collection.value;\n if (!current) return false;\n return current.source === \"project\" && !current.slug.startsWith(\"mc-\");\n});\n\n// True when this view was opened as a feed (`/feeds/:slug`): the schema\n// carries an `ingest` block. Feeds are deleted via DELETE /api/feeds/:slug,\n// not the project-scope collection delete above.\nconst isFeed = computed<boolean>(() => Boolean(collection.value?.schema.ingest));\nconst canDeleteFeed = computed<boolean>(() => isFeed.value && !embedded.value);\n\n// Which list to return to from the back arrow: feeds opened via /feeds\n// go back to the feed list; everything else to the collections index.\nconst isFeedRoute = computed<boolean>(() => !embedded.value && cui.isFeedRoute());\n\n// ── View mode (table | calendar | kanban) ─────────────────────────\n// Local UI state only — NEVER persisted to schema. The user toggles it;\n// the host never flips it programmatically. The calendar is offered only\n// when the schema has a `date` field and the kanban only when it has an\n// `enum` field, so plain collections and the initial load are unchanged\n// (default \"table\").\n//\n// Standalone route mode persists the last-used mode per collection in\n// localStorage so reopening `/collections/:slug` restores the prior view\n// instead of always starting on the table. Embedded chat cards restore from\n// the card's own `initialView` first; lacking that (a freshly-rendered\n// presentCollection card), they fall back to the same per-collection store\n// the standalone page uses, so a card also opens in the last-used view.\n// `CollectionViewMode` (\"table\" | \"calendar\" | \"kanban\" | \"dashboard\" |\n// `custom:<id>`) is imported from the view-mode util.\n\n/** The view to open with: the embedded card's restored `initialView` if\n * present (its own persisted state wins), else the slug's stored\n * preference, else \"table\". Embedded cards READ the store but never WRITE\n * it (the persist watch only emits `viewStateChange` for them), so a stale\n * card re-rendering can't clobber the shared preference. */\nfunction initialViewMode(): CollectionViewMode {\n if (props.initialView) return props.initialView;\n const slug = activeSlug.value;\n return (slug && readCollectionViewMode(slug)) || \"table\";\n}\nconst view = ref<CollectionViewMode>(initialViewMode());\n\n/** `date` / `datetime` fields in declaration order — the calendar can anchor\n * on any (a `datetime` anchor also carries the clock for the day view). */\nconst dateFields = computed<string[]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields)\n .filter(([, field]) => field.type === \"date\" || field.type === \"datetime\")\n .map(([key]) => key)\n : [],\n);\n\n/** Whether the table ↔ calendar toggle is offered. */\nconst hasCalendar = computed<boolean>(() => dateFields.value.length > 0);\n\n/** `enum` fields in declaration order — the kanban can group on any. */\nconst enumFields = computed<string[]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([key]) => key)\n : [],\n);\n\n/** Whether the kanban toggle is offered (needs an `enum` field to group on). */\nconst hasKanban = computed<boolean>(() => enumFields.value.length > 0);\n\n/** The effective view, collapsing any stale mode whose enabling field\n * vanished (e.g. `view = \"kanban\"` after switching to an enum-less\n * collection) back to \"table\". Single source of truth for the toggle and\n * the body branches. */\n/** Custom (LLM-authored) HTML views declared on the schema. */\nconst customViews = computed<CustomViewSpec[]>(() => collection.value?.schema.views ?? []);\nconst hasCustomViews = computed<boolean>(() => customViews.value.length > 0);\n\nconst activeView = computed<CollectionViewMode>(() => {\n if (view.value === \"calendar\" && hasCalendar.value) return \"calendar\";\n if (view.value === \"kanban\" && hasKanban.value) return \"kanban\";\n if (view.value.startsWith(\"custom:\")) {\n const viewId = view.value.slice(\"custom:\".length);\n if (customViews.value.some((entry) => entry.id === viewId)) return view.value;\n }\n return \"table\";\n});\n\n/** The selected custom view's spec, or null when a built-in view is active. */\nconst activeCustomView = computed<CustomViewSpec | null>(() => {\n const mode = activeView.value;\n if (!mode.startsWith(\"custom:\")) return null;\n const viewId = mode.slice(\"custom:\".length);\n return customViews.value.find((entry) => entry.id === viewId) ?? null;\n});\n\n/** Narrow a (possibly custom) mode to a built-in one, used where only the\n * built-in views are representable (the embedded card's viewState). */\nfunction builtInViewOrTable(mode: CollectionViewMode): BuiltInViewMode {\n return mode === \"calendar\" || mode === \"kanban\" ? mode : \"table\";\n}\n\n/** Whether to offer the \"+\" (author a new custom view) button. Standalone\n * page only (the seed starts a chat). Feeds qualify too — their views are\n * authored under feeds/<slug>/ and the seed prompt points there. */\nconst canAddCustomView = computed<boolean>(() => Boolean(collection.value) && !embedded.value);\n\n/** Seed a chat asking Claude to author a new custom view for this collection.\n * Reuses the same chat-seed path as collection actions — the host injects a\n * templated prompt; Claude asks, authors the HTML, and registers it. The\n * authoring base is source-aware: a feed lives under `feeds/<slug>/`, every\n * other collection under the `data/skills/<slug>/` staging dir. */\nfunction addCustomView(): void {\n const current = collection.value;\n if (!current) return;\n const base = current.schema.ingest ? `feeds/${current.slug}` : `data/skills/${current.slug}`;\n const prompt = t(\"collectionsView.addViewPrompt\", { title: current.title, base });\n if (props.sendTextMessage) {\n props.sendTextMessage(prompt);\n return;\n }\n appApi.startNewChat(prompt, cui.generalRoleId);\n}\n\n// ── Per-collection config (gear → manage custom views) ──────────────\nconst configOpen = ref<boolean>(false);\n\n/** Whether to offer the config gear. Standalone page only, and only when\n * there's a deletable custom view to manage — i.e. the collection is one\n * whose views the server will delete (project non-preset, or a feed; never a\n * read-only user-scope skill). Mirrors the server's refusal rules. */\nconst canConfigureViews = computed<boolean>(() => !embedded.value && hasCustomViews.value && (canDeleteCollection.value || isFeed.value));\n\n/** Reload the collection after the config modal deletes a view so the toggle\n * row + the modal's own list reflect the removal. */\nasync function onViewsChanged(): Promise<void> {\n const current = collection.value;\n if (current) await loadCollection(current.slug);\n}\n\n/** True when the calendar is the active body. */\nconst calendarActive = computed<boolean>(() => activeView.value === \"calendar\");\n\n/** True when the kanban is the active body. */\nconst kanbanActive = computed<boolean>(() => activeView.value === \"kanban\");\n\n// In-view override for which enum field groups the board; null ⇒ the schema\n// hint, else the first enum field.\nconst kanbanOverride = ref<string | null>(props.initialGroupField ?? null);\nconst kanbanGroupField = computed<string>(() => {\n if (kanbanOverride.value && enumFields.value.includes(kanbanOverride.value)) return kanbanOverride.value;\n const hint = collection.value?.schema.kanbanField;\n if (hint && enumFields.value.includes(hint)) return hint;\n return enumFields.value[0] ?? \"\";\n});\n\n// In-view override for which date field anchors the grid; null ⇒ the\n// schema hint, else the first date field.\nconst anchorOverride = ref<string | null>(props.initialAnchorField ?? null);\nconst calendarAnchorField = computed<string>(() => {\n if (anchorOverride.value && dateFields.value.includes(anchorOverride.value)) return anchorOverride.value;\n const hint = collection.value?.schema.calendarField;\n if (hint && dateFields.value.includes(hint)) return hint;\n return dateFields.value[0] ?? \"\";\n});\n// The end field pairs with `schema.calendarField`. If the user switches the\n// in-view anchor to a different date field, the span no longer applies —\n// drop it so chips don't render from the new start to the original end.\nconst calendarEndField = computed<string | undefined>(() => {\n const schema = collection.value?.schema;\n if (!schema?.calendarEndField) return undefined;\n return calendarAnchorField.value === schema.calendarField ? schema.calendarEndField : undefined;\n});\n// The time-string field (e.g. ENGAGEMENTS' \"time\") that places records on the\n// day view. Like the end field, it pairs with the schema's `calendarField` —\n// dropped when the in-view anchor is switched to a different date field.\nconst calendarTimeField = computed<string | undefined>(() => {\n const schema = collection.value?.schema;\n if (!schema?.calendarTimeField) return undefined;\n return calendarAnchorField.value === schema.calendarField ? schema.calendarTimeField : undefined;\n});\n\nfunction setView(next: CollectionViewMode): void {\n view.value = next;\n}\n\n/** Select a custom view by id (builds the `custom:<id>` mode key). */\nfunction setCustomView(viewId: string): void {\n const mode: CollectionViewMode = `custom:${viewId}`;\n view.value = mode;\n}\n\n/** Selector-key for a custom view, for active-state comparison in the template. */\nfunction customViewKey(viewId: string): CollectionViewMode {\n return `custom:${viewId}`;\n}\n\n/** A short, slug-safe id not already used by a loaded record. Collisions\n * are astronomically unlikely (32 bits), but we still re-roll a few\n * times against the in-memory set before giving up and using the last\n * candidate (the server's overwrite guard is the final backstop). */\nfunction generateUniqueItemId(primaryKey: string): string {\n const existing = new Set(items.value.map((item) => String(item[primaryKey] ?? \"\")));\n let candidate = shortHexId();\n for (let attempt = 0; attempt < 8 && existing.has(candidate); attempt++) {\n candidate = shortHexId();\n }\n return candidate;\n}\n\nfunction openCreate(): void {\n if (!collection.value) return;\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n const table: Record<string, TableRowDraft[]> = {};\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n if (field.type === \"boolean\") {\n bool[key] = false;\n // New record — no boolean was originally present.\n boolOriginallyPresent[key] = false;\n boolTouched[key] = false;\n } else if (field.type === \"table\") {\n table[key] = [];\n } else if (field.type !== \"derived\" && field.type !== \"embed\" && field.type !== \"toggle\") {\n text[key] = \"\";\n }\n // derived (computed), embed (display-only, foreign record), and toggle\n // (projection of an enum field) have no draft slot.\n }\n // Singleton collections fix the primary key to the schema-declared\n // value (e.g. \"me\") so the first Add can't pick an arbitrary id.\n // Otherwise pre-fill a unique, editable id so the user doesn't have to\n // invent one — the primary-key input stays enabled in create mode, so\n // they can still override it before saving. Matches the id shape the\n // server would generate for a blank-id POST (`generateItemId`).\n const { singleton, primaryKey } = collection.value.schema;\n if (singleton) {\n text[primaryKey] = singleton;\n } else if (primaryKey in text) {\n text[primaryKey] = generateUniqueItemId(primaryKey);\n }\n viewing.value = null; // one panel open at a time\n editing.value = { mode: \"create\", text, bool, boolOriginallyPresent, boolTouched, table, originalId: null };\n saveError.value = null;\n}\n\nfunction openEdit(item: CollectionItem): void {\n if (!collection.value) return;\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n const table: Record<string, TableRowDraft[]> = {};\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n const raw = item[key];\n if (field.type === \"boolean\") {\n bool[key] = raw === true;\n // Track whether the key was present in the source record so\n // we can preserve \"omitted\" through a save that doesn't\n // touch this field. `typeof raw === \"boolean\"` is more\n // defensive than `key in item` because a wrong-typed value\n // (e.g. `billable: \"yes\"`) shouldn't be treated as a real\n // existing boolean state.\n boolOriginallyPresent[key] = typeof raw === \"boolean\";\n boolTouched[key] = false;\n } else if (field.type === \"table\" && field.of) {\n const sub = field.of;\n const rows = Array.isArray(raw) ? raw : [];\n table[key] = rows\n .filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === \"object\" && !Array.isArray(row))\n .map((row) => rowFromItem(row, sub));\n } else if (field.type !== \"derived\" && field.type !== \"embed\" && field.type !== \"toggle\") {\n text[key] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n const primaryRaw = item[collection.value.schema.primaryKey];\n const originalId = typeof primaryRaw === \"string\" ? primaryRaw : String(primaryRaw ?? \"\");\n viewing.value = null; // one panel open at a time\n editing.value = { mode: \"edit\", text, bool, boolOriginallyPresent, boolTouched, table, originalId };\n saveError.value = null;\n}\n\nfunction closeEditor(): void {\n editing.value = null;\n saving.value = false;\n saveError.value = null;\n}\n\n/** Cancel the editor. Edit → reopen the record's read-only detail (don't\n * collapse the panel); create → just close (no prior detail to show). */\nfunction cancelEditor(): void {\n const draft = editing.value;\n const returnTo = draft && draft.mode === \"edit\" ? draft.originalId : null;\n closeEditor();\n if (returnTo) {\n const item = findItemById(returnTo);\n if (item) showDetail(item);\n }\n}\n\n/** Open mode (read-only detail). Toggles: clicking the already-open row\n * collapses it. Opening a row cancels any in-progress edit (one panel\n * open at a time). In embedded mode, report the open id so the host\n * card can persist it in `viewState`. */\nfunction openView(item: CollectionItem): void {\n if (isRowOpen(item) && !editing.value) {\n closeView();\n return;\n }\n if (editing.value) closeEditor();\n showDetail(item);\n}\n\n/** Open the read-only detail for a record WITHOUT the click-toggle. Used\n * when reopening detail programmatically (after save / cancel), where\n * `openView`'s \"click the open row to collapse\" guard would otherwise\n * immediately close a row the embedded `viewState` sync just reopened. */\nfunction showDetail(item: CollectionItem): void {\n viewing.value = item;\n actionError.value = null;\n if (embedded.value && collection.value) {\n emit(\"select\", String(item[collection.value.schema.primaryKey] ?? \"\"));\n }\n}\n\n/** Close open mode. Embedded mode reports the close via `select(null)`\n * (the card clears its `viewState`); standalone mode drops the\n * `?selected=` query param so a refresh / back-button doesn't reopen\n * the record and the URL reflects the closed state. */\nfunction closeView(): void {\n viewing.value = null;\n actionError.value = null;\n if (embedded.value) {\n emit(\"select\", null);\n return;\n }\n if (cui.routeSelectedId() !== undefined) {\n cui.setSelectedId(null);\n }\n}\n\n/** Backdrop click / Escape on the shared record modal. While editing this\n * cancels the draft (reopening the detail, matching the in-panel Cancel\n * button — so a stray click never silently discards edits); while viewing\n * it closes the detail. */\nfunction closeRecordModal(): void {\n if (editing.value) {\n cancelEditor();\n return;\n }\n closeView();\n}\n\n/** Hand off from open mode to the editor for the same record. */\nfunction editFromView(): void {\n const item = viewing.value;\n if (!item) return;\n viewing.value = null;\n openEdit(item);\n}\n\nfunction findItemById(itemId: string): CollectionItem | undefined {\n if (!collection.value) return undefined;\n const { primaryKey } = collection.value.schema;\n return items.value.find((item) => String(item[primaryKey] ?? \"\") === itemId);\n}\n\n/** Reconcile the open-mode view with the `?selected=<id>` query —\n * the single source of truth for which record is open. Opens the\n * matching record, or closes the modal when the param is absent /\n * empty / points at an id that isn't loaded (deleted record, stale\n * link). Keeping `viewing` in lockstep with the URL means browser\n * back / forward and a removed param both close the modal instead\n * of leaving stale UI on screen (Codex P2 + CodeRabbit on #1502). */\nfunction syncViewToSelected(): void {\n const selected = activeSelected.value;\n if (typeof selected !== \"string\" || selected.length === 0) {\n viewing.value = null;\n return;\n }\n const match = findItemById(selected) ?? null;\n viewing.value = match;\n // A deep link / notification opens the record in the shared modal, which\n // is centred regardless of where the row sits in a long list — no scroll\n // needed (the inline-expansion era required one).\n}\n\n/** Title for the open-mode header: the record's primary-key value\n * (e.g. `INV-2026-0001`), falling back to the collection title.\n * Non-string primary keys (numeric ids) are stringified rather\n * than discarded (CodeRabbit on #1502). */\nconst viewTitle = computed<string>(() => {\n if (!viewing.value || !collection.value) return \"\";\n const pkValue = viewing.value[collection.value.schema.primaryKey];\n if (pkValue === undefined || pkValue === null || pkValue === \"\") return collection.value.title ?? \"\";\n return String(pkValue);\n});\n\n/** Live computed record from the current draft. Drives derived\n * field displays in the form so subtotal/tax/total update as\n * the user edits line items. */\nconst liveRecord = computed<CollectionItem | null>(() => {\n if (!collection.value || !editing.value) return null;\n return draftToRecord(editing.value, collection.value.schema);\n});\n\n/** Live record with derived fields resolved (drives the form's\n * read-only derived inputs). Derivation lives in the shared\n * rendering composable; this binds it to the current draft. */\nconst liveDerived = computed<CollectionItem | null>(() => {\n if (!collection.value || !liveRecord.value) return null;\n return render.deriveAll(collection.value.schema, liveRecord.value, refRecordCache.value);\n});\n\n/** Short summary for a `table`-typed cell in the main collection\n * table. Counts rows; nothing fancier yet (per-row preview is\n * hard to fit in a single cell). */\nfunction tableSummary(value: unknown): string {\n if (!Array.isArray(value)) return \"—\";\n if (value.length === 0) return \"—\";\n return t(\"collectionsView.tableSummary\", { count: value.length });\n}\n\nasync function saveEditor(): Promise<void> {\n if (!collection.value || !editing.value) return;\n // Snapshot mutable refs before any await — route changes during\n // the save (e.g. user navigates away) can null `collection.value`\n // and would throw on the post-await `loadCollection(...)`.\n const { slug, schema } = collection.value;\n const draft = editing.value;\n saveError.value = null;\n\n const missing = firstMissingRequiredField(draft, schema);\n if (missing) {\n saveError.value = `${missing}: ${t(\"collectionsView.requiredField\")}`;\n return;\n }\n\n saving.value = true;\n const record = draftToRecord(draft, schema);\n const isCreate = draft.mode === \"create\";\n const result = isCreate ? await cui.createItem(slug, record) : await cui.updateItem(slug, draft.originalId ?? \"\", record);\n saving.value = false;\n if (!result.ok) {\n saveError.value = result.error;\n return;\n }\n const savedId = result.data.itemId;\n closeEditor();\n await loadCollection(slug);\n // Return to the saved record's read-only detail (for create, this is the\n // newly added row) in the shared modal.\n const saved = findItemById(savedId);\n if (saved) showDetail(saved);\n}\n\n/** Write a single cell's value directly onto the live `items` entry.\n * Reactive in Vue 3 (proxy), so the bound checkbox/select re-renders.\n * `undefined` (enum cleared to the placeholder) renders as the empty\n * option; the PUT body omits the key via `buildUpdatedRecord`. */\nfunction applyInlineValue(item: CollectionItem, key: string, value: unknown): void {\n item[key] = value;\n}\n\n/** True while this row has an inline cell save in flight — its inline\n * controls render disabled to serialize edits (one PUT per row). */\nfunction isRowInlineSaving(item: CollectionItem): boolean {\n return inlineSavingRows.value.has(rowId(item));\n}\n\n/** Inline table-cell edit (boolean checkbox / enum dropdown): optimistic\n * update, then PUT the full record. Gated per row so a second edit can't\n * race the in-flight one. On failure, roll the cell back and surface the\n * error. Bypasses the detail/edit panel entirely. */\nasync function commitInlineEdit(item: CollectionItem, key: string, field: FieldSpec, raw: boolean | string): Promise<void> {\n if (!collection.value) return;\n const { slug } = collection.value;\n const itemId = rowId(item);\n if (!itemId || inlineSavingRows.value.has(itemId)) return;\n const previous = item[key];\n const coerced = coerceInlineValue(field, raw);\n applyInlineValue(item, key, coerced);\n inlineError.value = null;\n inlineSavingRows.value.add(itemId);\n const result = await cui.updateItem(slug, itemId, buildUpdatedRecord(item, key, coerced));\n inlineSavingRows.value.delete(itemId);\n if (!result.ok) {\n applyInlineValue(item, key, previous);\n inlineError.value = result.error;\n }\n}\n\n/** Whether a `toggle` field reads as checked: its projected enum field\n * currently equals `onValue`. The toggle stores nothing itself. */\nfunction toggleChecked(item: CollectionItem, field: FieldSpec): boolean {\n return field.field !== undefined && String(item[field.field] ?? \"\") === field.onValue;\n}\n\n/** Flip a `toggle`: write the projected enum field to `offValue` when\n * currently checked, else `onValue`. Reuses the inline-edit PUT path\n * (optimistic + rollback) — the toggle has no value of its own. */\nfunction commitToggle(item: CollectionItem, field: FieldSpec): void {\n const targetKey = field.field;\n if (!targetKey || !collection.value) return;\n const enumField = collection.value.schema.fields[targetKey];\n if (!enumField) return;\n const next = toggleChecked(item, field) ? field.offValue : field.onValue;\n if (next === undefined) return;\n void commitInlineEdit(item, targetKey, enumField, next);\n}\n\nasync function confirmDelete(item: CollectionItem): Promise<void> {\n if (!collection.value) return;\n // Snapshot before any await (see saveEditor) — confirm dialog\n // awaits user input, plenty of time for the route to change.\n const { slug } = collection.value;\n const { primaryKey } = collection.value.schema;\n const idRaw = item[primaryKey];\n const itemId = typeof idRaw === \"string\" ? idRaw : String(idRaw ?? \"\");\n if (!itemId) return;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDelete\"),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteItem(slug, itemId);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n await loadCollection(slug);\n}\n\n// Delete the whole collection (skill + records), not just one item.\n// The server archives a restorable copy first; on success we leave the\n// now-gone collection's route for the index.\nasync function confirmCollectionDelete(): Promise<void> {\n const current = collection.value;\n if (!current) return;\n // Snapshot before the await — the confirm dialog yields control and\n // the route could change underneath us (see confirmDelete).\n const { slug, title } = current;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDeleteCollection\", { title }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteCollection(slug);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n cui.gotoIndex(\"collection\");\n}\n\nfunction goBack(): void {\n cui.gotoIndex(isFeedRoute.value ? \"feed\" : \"collection\");\n}\n\n// Delete a feed: remove its feeds/<slug>/ registry entry (records on disk\n// are retained), then return to the feed list. Distinct from\n// `confirmCollectionDelete`, which archives + deletes a skill-backed\n// collection through the project-scope collection-delete route.\nasync function confirmFeedDelete(): Promise<void> {\n const current = collection.value;\n if (!current) return;\n const { slug, title } = current;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDeleteFeed\", { title }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteFeed(slug);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n cui.gotoIndex(\"feed\");\n}\n\n// Load on slug change, immediate so the initial value (route param or\n// prop) triggers the first fetch — replaces the old `onMounted` +\n// separate slug watch. Works identically for route mode (reads\n// `route.params.slug`) and embedded mode (reads the `slug` prop).\n/** Open the create form with the clicked calendar day prefilled into the\n * anchor field. The calendar day view's + affordance; the create flow itself\n * is the same one the Add button uses. A `datetime` anchor renders as a\n * `datetime-local` input, which rejects a bare `YYYY-MM-DD` — seed midnight\n * so the chosen day actually survives the prefill. */\nfunction createOnDate(iso: string): void {\n if (!canCreate.value) return;\n openCreate();\n const anchor = calendarAnchorField.value;\n if (!editing.value || !anchor) return;\n const anchorType = collection.value?.schema.fields[anchor]?.type;\n editing.value.text[anchor] = anchorType === \"datetime\" ? `${iso}T00:00` : iso;\n}\n\n/** The civil day a record sits on, from its calendar anchor field (handles\n * both `date` and `datetime`). Null for undated records. */\nfunction dayOfItem(item: CollectionItem): Ymd | null {\n return dateOf(item[calendarAnchorField.value]);\n}\n\n/** Mirror the open record into the `?selected=<id>` query (standalone mode)\n * so the calendar's day-view + selection is a copy-pasteable link. In-app\n * selection didn't previously touch the URL; the calendar now does. */\nfunction writeSelectedToUrl(itemId: string): void {\n if (embedded.value || cui.routeSelectedId() === itemId) return;\n cui.setSelectedId(itemId);\n}\n\n/** Calendar chip / kanban card click → open that record's detail. In the\n * calendar it opens the day (time-allocation) popup on the record's day with\n * the detail in the right pane; an undated record falls back to the panel\n * below the grid. Unlike `openView`, this never toggles — a second click on\n * the same record keeps it open. */\nfunction onCalendarSelect(itemId: string | null): void {\n if (!itemId) {\n closeView();\n return;\n }\n const item = findItemById(itemId);\n if (!item) return;\n if (editing.value) closeEditor();\n // Anchor the popup on the record's day; null for an undated record, which\n // closes the popup so its detail falls back to the panel below the grid.\n if (calendarActive.value) openDay.value = dayOfItem(item);\n showDetail(item);\n writeSelectedToUrl(itemId);\n}\n\n/** A calendar day cell was activated → open its popup on a clean slate\n * (clear any prior selection so the popup opens timeline-only). */\nfunction onOpenDay(day: Ymd): void {\n if (editing.value) closeEditor();\n closeView();\n openDay.value = day;\n}\n\n/** Close the day popup: drop the open day, the selection, AND any in-progress\n * draft together. Clearing `editing` matters because the shared record modal\n * shows whenever `editing` is set and no day is open — so without this, an\n * edit/create started inside the day popup would re-appear in the centred\n * modal the instant the popup closed (Codex P2 on #1656). */\nfunction onDayClose(): void {\n openDay.value = null;\n if (editing.value) closeEditor();\n closeView();\n}\n\n/** Deep-link entry: a `?selected=<id>` link to a calendar-capable collection\n * opens in calendar view with the popup focused on the record's day. Runs\n * on load / slug change only (not on in-app selection), so table users\n * aren't forced into the calendar. */\nfunction maybeOpenCalendarForSelected(): void {\n if (embedded.value || !hasCalendar.value || !viewing.value) return;\n const day = dayOfItem(viewing.value);\n if (!day) return;\n view.value = \"calendar\";\n openDay.value = day;\n}\n\n/** Kanban card dropped in a column → set the record's group field to the\n * column value (the empty string clears it for the Uncategorized column).\n * Reuses the inline-edit path (optimistic write + PUT + rollback). */\nfunction onKanbanMove(itemId: string, value: string): void {\n const item = findItemById(itemId);\n const key = kanbanGroupField.value;\n const field = collection.value?.schema.fields[key];\n if (!item || !field) return;\n void commitInlineEdit(item, key, field, value);\n}\n\nwatch(\n activeSlug,\n (slug, prevSlug) => {\n // Reset view state when switching BETWEEN collections — but not on the\n // initial run (prevSlug undefined), so an embedded card's restored\n // `initialView` / `initialAnchorField` survive the first load. Both modes\n // restore the new collection's stored mode (else \"table\"); the axis\n // fields always reset to their schema defaults.\n if (prevSlug !== undefined && slug !== prevSlug) {\n view.value = (slug && readCollectionViewMode(slug)) || \"table\";\n anchorOverride.value = null;\n kanbanOverride.value = null;\n // A sort belongs to a collection's own schema, so don't carry it across —\n // restore the new collection's stored (shared) sort instead.\n sortState.value = storedSortFor(slug);\n }\n if (slug) {\n loadCollection(slug);\n } else {\n collection.value = null;\n items.value = [];\n enumOriginallyEmpty.value = new Set();\n inlineSavingRows.value = new Set();\n searchQuery.value = \"\"; // Reset search query\n loading.value = false;\n }\n },\n { immediate: true },\n);\n\n// Embedded mode: report view/anchor changes so the chat card persists them\n// in `viewState` (alongside `selected`). Standalone mode: persist the view\n// mode per slug in localStorage so reopening restores it.\n// `loading` is a dependency so the write re-runs when the collection finishes\n// loading: that's the point where a stored mode unsupported by this schema\n// (its date/enum field gone) has collapsed to \"table\" and must be normalized\n// back into storage — otherwise no other dependency changes and it lingers.\nwatch([activeView, calendarAnchorField, kanbanGroupField, sortState, loading], () => {\n // Persist the EFFECTIVE view (activeView), not the raw `view` ref — a\n // stale \"calendar\"/\"kanban\" that has fallen back to \"table\" (its enabling\n // field gone) must not be saved as an impossible mode.\n if (embedded.value) {\n // Embedded cards persist only the built-in view in v1 — a custom view\n // collapses to \"table\" for the card's restore state (custom views are a\n // standalone-page feature; widening the card viewState is a follow-up).\n emit(\"viewStateChange\", { view: builtInViewOrTable(activeView.value), anchorField: calendarAnchorField.value, groupField: kanbanGroupField.value });\n }\n // Don't write during the load window: until the collection resolves,\n // `hasCalendar`/`hasKanban` are false so `activeView` reads \"table\",\n // which would clobber a stored \"calendar\"/\"kanban\" before it can apply.\n if (activeSlug.value && !loading.value && collection.value) {\n // View mode stays standalone-authored — embedded reads but never writes it,\n // so a stale card can't clobber the shared mode. The table SORT, by\n // contrast, IS shared both ways: a card always re-reads it on mount, so\n // there's no per-card value to go stale and clobber the store.\n if (!embedded.value) writeCollectionViewMode(activeSlug.value, activeView.value);\n writeCollectionSort(activeSlug.value, sortState.value);\n }\n});\n\n// React to the active selection changing while already on this\n// collection: follow it to open the new record, OR close the modal when\n// it's cleared (browser back / card close) or points at a missing id.\n// The initial / cross-collection case is handled by `loadCollection`;\n// here we only act once items are loaded.\nwatch(activeSelected, () => {\n if (loading.value || !collection.value) return;\n syncViewToSelected();\n // Keep the calendar-owned openDay in step with the selection — re-anchor it on\n // the selected record's day, or clear it when the selection is gone. Do this\n // even when the calendar isn't the active view: openDay is calendar state, so\n // a selection cleared in the table must not survive into a later calendar\n // visit. Never force a view switch here — that's loadCollection's deep-link job.\n openDay.value = viewing.value ? dayOfItem(viewing.value) : null;\n});\n</script>\n","<template>\n <div class=\"h-full flex flex-col bg-slate-50/30\">\n <header class=\"flex items-center gap-3 px-6 py-2 border-b border-slate-200 bg-white\">\n <button\n v-if=\"!embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-50 hover:text-slate-800 transition-colors\"\n :title=\"t('collectionsView.backToIndex')\"\n :aria-label=\"t('collectionsView.backToIndex')\"\n data-testid=\"collections-back\"\n @click=\"goBack\"\n >\n <span class=\"material-icons text-lg\">arrow_back</span>\n </button>\n\n <div v-if=\"collection\" class=\"h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100\">\n <span class=\"material-symbols-outlined text-xl\">{{ collection.icon }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <h1 class=\"text-base font-bold text-slate-800 truncate\">\n {{ collection?.title ?? t(\"collectionsView.title\") }}\n </h1>\n <span v-if=\"collection\" class=\"block text-[10px] text-slate-400 font-bold uppercase tracking-wider\">\n {{ collection.slug }}\n </span>\n </div>\n\n <component\n :is=\"pinToggle\"\n v-if=\"collection && !embedded\"\n :kind=\"isFeedRoute ? 'feed' : 'collection'\"\n :slug=\"collection.slug\"\n :title=\"collection.title\"\n :icon=\"collection.icon\"\n />\n\n <button\n v-if=\"collection?.schema.ingest\"\n type=\"button\"\n 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 disabled:opacity-50\"\n :disabled=\"refreshing\"\n data-testid=\"collections-refresh-feed\"\n @click=\"refreshFeed\"\n >\n <span class=\"material-icons text-sm\">{{ refreshing ? \"hourglass_empty\" : \"refresh\" }}</span>\n <span>{{ t(\"collectionsView.refreshFeed\") }}</span>\n </button>\n\n <button\n v-if=\"collection\"\n type=\"button\"\n 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\"\n data-testid=\"collections-chat\"\n @click=\"openChat\"\n >\n <span class=\"material-icons text-sm\">forum</span>\n <span>{{ t(\"collectionsView.chat\") }}</span>\n </button>\n\n <!-- Collection-level actions (schema `collectionActions`). No record\n context: each seeds a chat with a progress summary of all items. -->\n <button\n v-for=\"action in collectionActions\"\n :key=\"action.id\"\n type=\"button\"\n 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 disabled:opacity-50\"\n :disabled=\"collectionActionPending\"\n :data-testid=\"`collections-action-${action.id}`\"\n @click=\"runCollectionAction(action)\"\n >\n <span v-if=\"action.icon\" class=\"material-icons text-sm\">{{ action.icon }}</span>\n <span>{{ action.label }}</span>\n </button>\n\n <!-- Hidden in calendar view: there, creation happens via the day view's\n + button, which opens the new-item form in the popup's right pane. -->\n <button\n v-if=\"canCreate && !calendarActive\"\n type=\"button\"\n 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\"\n data-testid=\"collections-add-item\"\n @click=\"openCreate\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"common.add\") }}</span>\n </button>\n\n <button\n v-if=\"canDeleteCollection && !embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 transition-colors\"\n :title=\"t('collectionsView.deleteCollection')\"\n :aria-label=\"t('collectionsView.deleteCollection')\"\n data-testid=\"collections-delete\"\n @click=\"confirmCollectionDelete\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n\n <button\n v-if=\"canDeleteFeed && !embedded\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded border border-rose-200 bg-white text-rose-600 hover:bg-rose-50 transition-colors\"\n :title=\"t('collectionsView.deleteFeed')\"\n :aria-label=\"t('collectionsView.deleteFeed')\"\n data-testid=\"feeds-delete\"\n @click=\"confirmFeedDelete\"\n >\n <span class=\"material-icons text-sm\">delete_forever</span>\n </button>\n </header>\n\n <!-- Search Toolbar. Shown when there are items to search OR when a view\n toggle is available — the toggle must reach an empty date-bearing\n collection (empty-day create) and a collection whose only views are\n custom ones (so its buttons + the \"+\" stay reachable). -->\n <div\n v-if=\"collection && (items.length > 0 || hasCalendar || hasKanban || hasCustomViews || canAddCustomView)\"\n class=\"px-6 py-3 bg-white border-b border-slate-100 flex items-center justify-between gap-4\"\n >\n <div v-if=\"items.length > 0\" class=\"relative flex-1 max-w-md\">\n <span class=\"absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none\">\n <span class=\"material-icons text-lg\">search</span>\n </span>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n :placeholder=\"t('collectionsView.searchPlaceholder')\"\n :aria-label=\"t('collectionsView.searchPlaceholder')\"\n class=\"w-full bg-slate-50 border border-slate-200/80 rounded-xl pl-9 pr-8 py-1.5 text-xs placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all font-medium\"\n />\n <button\n v-if=\"searchQuery\"\n type=\"button\"\n :aria-label=\"t('collectionsView.clearSearch')\"\n class=\"absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-400 hover:text-slate-600\"\n @click=\"searchQuery = ''\"\n >\n <span class=\"material-icons text-sm\">close</span>\n </button>\n </div>\n <div class=\"flex items-center gap-2\">\n <!-- View toggle: table ↔ calendar ↔ kanban. Calendar shows only when\n the schema has a `date` field, kanban only with an `enum` field;\n local UI state, never persisted. -->\n <div\n v-if=\"hasCalendar || hasKanban || hasCustomViews || canAddCustomView\"\n class=\"flex gap-0.5\"\n role=\"group\"\n :aria-label=\"t('collectionsView.viewToggle')\"\n >\n <button\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'table' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'table'\"\n data-testid=\"collection-view-toggle-table\"\n @click=\"setView('table')\"\n >\n <span class=\"material-icons text-sm\">table_rows</span>\n <span>{{ t(\"collectionsView.viewTable\") }}</span>\n </button>\n <button\n v-if=\"hasCalendar\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'calendar' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'calendar'\"\n data-testid=\"collection-view-toggle-calendar\"\n @click=\"setView('calendar')\"\n >\n <span class=\"material-icons text-sm\">calendar_month</span>\n <span>{{ t(\"collectionsView.viewCalendar\") }}</span>\n </button>\n <button\n v-if=\"hasKanban\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === 'kanban' ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === 'kanban'\"\n data-testid=\"collection-view-toggle-kanban\"\n @click=\"setView('kanban')\"\n >\n <span class=\"material-icons text-sm\">view_kanban</span>\n <span>{{ t(\"collectionsView.viewKanban\") }}</span>\n </button>\n <!-- Custom (LLM-authored) views declared on the schema. -->\n <button\n v-for=\"cv in customViews\"\n :key=\"cv.id\"\n type=\"button\"\n class=\"h-8 px-2.5 flex items-center gap-1 rounded text-xs font-bold transition-colors\"\n :class=\"activeView === customViewKey(cv.id) ? 'bg-indigo-600 text-white' : 'bg-white text-slate-500 border border-slate-200 hover:bg-slate-50'\"\n :aria-pressed=\"activeView === customViewKey(cv.id)\"\n :data-testid=\"`collection-view-custom-${cv.id}`\"\n @click=\"setCustomView(cv.id)\"\n >\n <span class=\"material-icons text-sm\">{{ cv.icon || \"dashboard_customize\" }}</span>\n <span>{{ cv.label }}</span>\n </button>\n <!-- \"+\" — ask Claude to author a new custom view for this collection. -->\n <button\n v-if=\"canAddCustomView\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50\"\n :title=\"t('collectionsView.addView')\"\n :aria-label=\"t('collectionsView.addView')\"\n data-testid=\"collection-view-add\"\n @click=\"addCustomView\"\n >\n <span class=\"material-icons text-sm\">add</span>\n </button>\n <!-- Gear — per-collection config (currently: manage/delete custom\n views). Standalone only, and only when there's a view to manage. -->\n <button\n v-if=\"canConfigureViews\"\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded bg-white text-slate-500 border border-slate-200 hover:bg-slate-50\"\n :title=\"t('collectionsView.config.open')\"\n :aria-label=\"t('collectionsView.config.open')\"\n data-testid=\"collection-config-open\"\n @click=\"configOpen = true\"\n >\n <span class=\"material-icons text-sm\">settings</span>\n </button>\n </div>\n <!-- Which date field anchors the grid (only when >1 date field). -->\n <select\n v-if=\"calendarActive && dateFields.length > 1\"\n :value=\"calendarAnchorField\"\n 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\"\n :aria-label=\"t('collectionsView.calendarFieldLabel')\"\n data-testid=\"collection-calendar-field\"\n @change=\"anchorOverride = ($event.target as HTMLSelectElement).value\"\n >\n <option v-for=\"key in dateFields\" :key=\"key\" :value=\"key\">{{ collection?.schema.fields[key]?.label ?? key }}</option>\n </select>\n <!-- Which enum field groups the board (only when >1 enum field). -->\n <select\n v-if=\"kanbanActive && enumFields.length > 1\"\n :value=\"kanbanGroupField\"\n 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\"\n :aria-label=\"t('collectionsView.kanbanFieldLabel')\"\n data-testid=\"collection-kanban-field\"\n @change=\"kanbanOverride = ($event.target as HTMLSelectElement).value\"\n >\n <option v-for=\"key in enumFields\" :key=\"key\" :value=\"key\">{{ collection?.schema.fields[key]?.label ?? key }}</option>\n </select>\n <div v-if=\"items.length > 0\" class=\"text-[10px] text-slate-400 font-bold uppercase tracking-wider select-none\">\n {{ t(\"collectionsView.searchSummary\", { shown: filteredItems.length, total: items.length }) }}\n </div>\n </div>\n </div>\n\n <!-- Repair banner: the server flagged record files that won't load /\n violate the schema and are silently skipped. The button reports\n them back to the LLM (same path presentCollection uses) so it\n fixes the files. View-independent, so it sits above the body. -->\n <div\n v-if=\"collection && dataIssues.length > 0\"\n 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\"\n data-testid=\"collections-data-issues\"\n >\n <span class=\"material-icons text-amber-600\">warning</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.dataIssuesDetected\", { count: dataIssues.length }) }}</span>\n <button\n type=\"button\"\n 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\"\n data-testid=\"collections-repair\"\n @click=\"repairCollection\"\n >\n <span class=\"material-icons text-sm\">build</span>\n <span>{{ t(\"collectionsView.repair\") }}</span>\n </button>\n </div>\n\n <div class=\"flex-1 overflow-auto\">\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" 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\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ loadError === \"not-found\" ? t(\"collectionsView.notFound\") : `${t(\"collectionsView.loadFailed\")}: ${loadError}` }}</span>\n </div>\n\n <div v-else-if=\"!collection\">\n <!-- defensive: loading=false, error=null, collection=null -->\n </div>\n\n <!-- Calendar body: an alternative to the table for date-bearing\n collections. Shown whenever active (even when empty) so the\n empty-cell create affordance stays available. -->\n <div v-else-if=\"calendarActive\" class=\"p-4\">\n <CollectionCalendarView\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :anchor-field=\"calendarAnchorField\"\n :end-field=\"calendarEndField\"\n :time-field=\"calendarTimeField\"\n :color-field=\"hasKanban ? kanbanGroupField : ''\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n @select=\"onCalendarSelect\"\n @open-day=\"onOpenDay\"\n />\n\n <!-- Day (time-allocation) popup. Selecting a record opens it on the\n right of this modal (the `#detail` slot), replacing the old panel\n that sat below the grid. -->\n <CollectionDayView\n v-if=\"openDay\"\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :day=\"openDay\"\n :anchor-field=\"calendarAnchorField\"\n :end-field=\"calendarEndField\"\n :time-field=\"calendarTimeField\"\n :color-field=\"hasKanban ? kanbanGroupField : ''\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n :can-create=\"canCreate\"\n :show-detail=\"Boolean(viewing || editing)\"\n @select=\"onCalendarSelect\"\n @create-on=\"createOnDate\"\n @close=\"onDayClose\"\n >\n <template #detail>\n <CollectionRecordPanel\n v-model:editing=\"editing\"\n :collection=\"collection\"\n :viewing=\"viewing\"\n :saving=\"saving\"\n :save-error=\"saveError\"\n :action-error=\"actionError\"\n :action-pending=\"actionPending\"\n :visible-actions=\"visibleActions\"\n :live-record=\"liveRecord\"\n :live-derived=\"liveDerived\"\n :view-title=\"viewTitle\"\n :is-singleton=\"isSingleton\"\n :render=\"render\"\n :locale=\"locale\"\n @submit=\"saveEditor\"\n @cancel=\"cancelEditor\"\n @edit=\"editFromView\"\n @close=\"onDayClose\"\n @delete=\"viewing && confirmDelete(viewing)\"\n @run-action=\"runAction\"\n />\n </template>\n </CollectionDayView>\n\n <!-- Undated records (the \"no date\" tray) have no timeline slot, so\n they open in the shared record modal (rendered once at the View\n root) instead of the day view. -->\n </div>\n\n <!-- Kanban body: an alternative to the table for enum-bearing\n collections. The board groups records into columns by the chosen\n enum field; dragging a card between columns writes that field. -->\n <div v-else-if=\"kanbanActive\" class=\"h-full flex flex-col\">\n <!-- Inline-edit failure banner: a card drop (group-field write) was\n rolled back. The detail panel's `saveError` isn't shown during a\n drag, so inline edits surface their own — same as the table. -->\n <div\n v-if=\"inlineError\"\n 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\"\n data-testid=\"collections-inline-error\"\n >\n <span class=\"material-icons text-red-600\">error</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.inlineSaveFailed\", { error: inlineError }) }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100\"\n :aria-label=\"t('common.close')\"\n @click=\"inlineError = null\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </div>\n <div class=\"flex-1 min-h-0 px-3 py-2\">\n <CollectionKanbanView\n :schema=\"collection.schema\"\n :items=\"filteredItems\"\n :group-field=\"kanbanGroupField\"\n :selected=\"viewing ? String(viewing[collection.schema.primaryKey] ?? '') : undefined\"\n :notified=\"notifiedSeverities\"\n @select=\"onCalendarSelect\"\n @move=\"onKanbanMove\"\n />\n </div>\n </div>\n\n <!-- Custom (LLM-authored) HTML view, rendered in a sandboxed iframe over\n the collection's records. Placed before the empty states so it shows\n even for an empty collection (e.g. a still-empty year grid). -->\n <div v-else-if=\"activeCustomView\" class=\"h-full\" data-testid=\"collection-custom-view-body\">\n <CollectionCustomView :slug=\"collection.slug\" :view=\"activeCustomView\" />\n </div>\n\n <div v-else-if=\"items.length === 0 && editing?.mode !== 'create'\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2\">\n <span class=\"material-icons text-4xl text-slate-300\">folder_open</span>\n <p class=\"font-semibold text-slate-600\">{{ t(\"collectionsView.itemsEmpty\") }}</p>\n </div>\n\n <div\n v-else-if=\"filteredItems.length === 0 && editing?.mode !== 'create'\"\n class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2\"\n >\n <span class=\"material-icons text-4xl text-slate-300\">search_off</span>\n <p class=\"font-semibold text-slate-600\">{{ t(\"collectionsView.noMatchingItems\") }}</p>\n <button type=\"button\" class=\"text-xs text-indigo-600 font-semibold hover:underline\" @click=\"searchQuery = ''\">\n {{ t(\"collectionsView.clearSearch\") }}\n </button>\n </div>\n\n <div v-else class=\"overflow-x-auto [container-type:inline-size]\">\n <!-- Inline-edit failure banner: a cell write (checkbox/dropdown)\n was rolled back; the detail panel's `saveError` isn't visible\n here so inline edits surface their own. -->\n <div\n v-if=\"inlineError\"\n 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\"\n data-testid=\"collections-inline-error\"\n >\n <span class=\"material-icons text-red-600\">error</span>\n <span class=\"flex-1\">{{ t(\"collectionsView.inlineSaveFailed\", { error: inlineError }) }}</span>\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100\"\n :aria-label=\"t('common.close')\"\n @click=\"inlineError = null\"\n >\n <span class=\"material-icons text-base\">close</span>\n </button>\n </div>\n <table class=\"min-w-full text-xs\">\n <thead>\n <tr class=\"bg-slate-50 border-b border-slate-200\">\n <th\n v-for=\"[key, field] in listColumnFields\"\n :key=\"key\"\n :aria-sort=\"isSortableField(field) ? sortAriaValue(key) : undefined\"\n class=\"px-5 py-3 font-bold text-slate-500 text-left uppercase tracking-wider whitespace-nowrap\"\n >\n <div class=\"flex items-center gap-1\">\n <span class=\"truncate max-w-[14rem]\" :title=\"field.label\">{{ field.label }}</span>\n <button\n v-if=\"isSortableField(field)\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded p-0.5 -my-1 leading-none transition-colors\"\n :class=\"sortButtonClass(key)\"\n :data-testid=\"`collections-sort-${key}`\"\n :aria-label=\"t('collectionsView.sortBy', { field: field.label })\"\n @click.stop=\"cycleSort(key)\"\n @pointerenter=\"hoveredSortKey = key\"\n @pointerleave=\"hoveredSortKey = null\"\n >\n <span class=\"material-icons text-base align-middle\">{{ sortIconName(key) }}</span>\n </button>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-100 bg-white\">\n <template v-for=\"item in sortedItems\" :key=\"String(item[collection.schema.primaryKey] ?? '')\">\n <tr\n class=\"hover:bg-slate-50/70 cursor-pointer transition-colors focus:outline-none focus:bg-indigo-50/30\"\n :class=\"isRowOpen(item) || isEditingRow(item) ? 'bg-indigo-50/40' : ''\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t('collectionsView.openItem', { id: String(item[collection.schema.primaryKey] ?? '') })\"\n :data-testid=\"`collections-row-${item[collection.schema.primaryKey]}`\"\n @click=\"openView(item)\"\n @keydown.enter.self=\"openView(item)\"\n @keydown.space.self.prevent=\"openView(item)\"\n >\n <td v-for=\"[key, field] in listColumnFields\" :key=\"key\" class=\"px-5 py-2 text-slate-700 align-middle max-w-xs font-medium\">\n <!-- Conditionally hidden field (`when` predicate) → blank cell. -->\n <template v-if=\"fieldVisible(field, item)\">\n <!-- Toggle → inline checkbox projecting an enum field.\n Stores nothing itself; toggling writes onValue/\n offValue to the projected field via the same PUT. -->\n <input\n v-if=\"field.type === 'toggle'\"\n type=\"checkbox\"\n :checked=\"toggleChecked(item, field)\"\n :disabled=\"isRowInlineSaving(item)\"\n 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\"\n :data-testid=\"`collections-inline-toggle-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitToggle(item, field)\"\n />\n\n <!-- Boolean → inline checkbox. Tap toggles + saves\n immediately; `@click.stop` so it doesn't open the\n row's detail panel. Unset (undefined) and explicit\n false both render unchecked. -->\n <input\n v-else-if=\"field.type === 'boolean'\"\n type=\"checkbox\"\n :checked=\"item[key] === true\"\n :disabled=\"isRowInlineSaving(item)\"\n 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\"\n :data-testid=\"`collections-inline-bool-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitInlineEdit(item, String(key), field, ($event.target as HTMLInputElement).checked)\"\n />\n\n <!-- Ref router-link badge -->\n <span v-else-if=\"field.type === 'ref' && field.to && typeof item[key] === 'string' && item[key]\" class=\"block truncate\">\n <router-link\n :to=\"{ path: `/collections/${field.to}`, query: { selected: String(item[key]) } }\"\n class=\"text-indigo-600 hover:text-indigo-800 hover:underline font-semibold\"\n :data-testid=\"`collections-ref-link-${key}-${item[key]}`\"\n @click.stop\n >{{ refDisplay(field.to, String(item[key])) }}</router-link\n >\n </span>\n\n <!-- Enum → inline dropdown. Selecting writes + saves\n immediately; the empty placeholder clears the field.\n `@click.stop` keeps the row's detail panel closed. -->\n <select\n v-else-if=\"field.type === 'enum' && Array.isArray(field.values) && field.values.length > 0\"\n :value=\"item[key] == null ? '' : String(item[key])\"\n :disabled=\"isRowInlineSaving(item)\"\n class=\"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\"\n :class=\"enumControlClass(String(key), item[key])\"\n :data-testid=\"`collections-inline-enum-${key}-${item[collection.schema.primaryKey]}`\"\n :aria-label=\"field.label\"\n @click.stop\n @change=\"commitInlineEdit(item, String(key), field, ($event.target as HTMLSelectElement).value)\"\n >\n <option v-if=\"showEnumPlaceholder(item, String(key))\" value=\"\">{{ t(\"collectionsView.selectPlaceholder\") }}</option>\n <option v-for=\"value in field.values\" :key=\"value\" :value=\"value\">{{ value }}</option>\n </select>\n\n <!-- Money -->\n <span v-else-if=\"field.type === 'money'\" class=\"block truncate tabular-nums font-semibold text-slate-900\">{{\n formatMoney(item[key], resolveCurrency(field, item), locale)\n }}</span>\n\n <!-- Table summary counter -->\n <span\n v-else-if=\"field.type === 'table'\"\n 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\"\n >\n <span class=\"material-icons text-[11px]\">list</span>\n <span>{{ tableSummary(item[key]) }}</span>\n </span>\n\n <!-- Derived formula fields -->\n <span\n v-else-if=\"field.type === 'derived'\"\n 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\"\n >{{ derivedDisplay(field, evaluateDerivedAgainstItem(field, String(key), item), item) }}</span\n >\n\n <!-- URL string → external link (new tab). `@click.stop` so\n clicking the link doesn't also open the row's detail. -->\n <a\n v-else-if=\"field.type !== 'file' && isExternalUrl(item[key])\"\n :href=\"String(item[key])\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-url-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</a\n >\n\n <!-- File: served HTML/SVG artifact → open the rendered\n app in a new tab. `@click.stop` keeps the row's\n detail panel from also opening. -->\n <a\n v-else-if=\"field.type === 'file' && artifactUrl(item[key])\"\n :href=\"artifactUrl(item[key]) ?? undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-file-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</a\n >\n\n <!-- File: any other workspace path → open in File Explorer. -->\n <router-link\n v-else-if=\"field.type === 'file' && fileRoutePath(item[key])\"\n :to=\"fileRoutePath(item[key]) ?? ''\"\n class=\"block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold\"\n :data-testid=\"`collections-file-link-${key}-${item[collection.schema.primaryKey]}`\"\n @click.stop\n >{{ String(item[key]) }}</router-link\n >\n\n <span v-else class=\"block truncate text-slate-600\">{{ formatCell(item[key], field.type) }}</span>\n </template>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n </div>\n\n <!-- Shared record modal — the single open/edit surface for every view\n mode (table / kanban) and the calendar's undated tray.\n Calendar's DATED records keep their day-view modal (which embeds the\n same panel on its right), so this is suppressed while that's open. -->\n <CollectionRecordModal v-if=\"collection && (viewing || editing) && !(calendarActive && openDay)\" @close=\"closeRecordModal\">\n <CollectionRecordPanel\n v-model:editing=\"editing\"\n :collection=\"collection\"\n :viewing=\"viewing\"\n :saving=\"saving\"\n :save-error=\"saveError\"\n :action-error=\"actionError\"\n :action-pending=\"actionPending\"\n :visible-actions=\"visibleActions\"\n :live-record=\"liveRecord\"\n :live-derived=\"liveDerived\"\n :view-title=\"viewTitle\"\n :is-singleton=\"isSingleton\"\n :render=\"render\"\n :locale=\"locale\"\n @submit=\"saveEditor\"\n @cancel=\"cancelEditor\"\n @edit=\"editFromView\"\n @close=\"closeView\"\n @delete=\"viewing && confirmDelete(viewing)\"\n @run-action=\"runAction\"\n />\n </CollectionRecordModal>\n\n <!-- Per-collection config (gear): manage/delete custom views. -->\n <CollectionViewConfigModal\n v-if=\"configOpen && collection\"\n :slug=\"collection.slug\"\n :title=\"collection.title\"\n :views=\"customViews\"\n @changed=\"onViewsChanged\"\n @close=\"configOpen = false\"\n />\n\n <!-- Chat modal — collect a message and start a new general-role chat\n seeded with the collection's skill command (`/<slug> <message>`). -->\n <div\n v-if=\"chatOpen && collection\"\n class=\"fixed inset-0 z-30 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm p-4 transition-all duration-300\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"collections-chat-title\"\n data-testid=\"collections-chat-modal\"\n @click.self=\"closeChat\"\n @keydown.esc=\"closeChat\"\n >\n <div class=\"bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden\">\n <header class=\"px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50\">\n <div class=\"h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50\">\n <span class=\"material-icons text-lg\">forum</span>\n </div>\n <div class=\"flex-1\">\n <h2 id=\"collections-chat-title\" class=\"text-sm font-bold text-slate-800 uppercase tracking-wide\">{{ t(\"collectionsView.chatTitle\") }}</h2>\n <span class=\"text-xs text-slate-400 font-semibold\">{{ collection.title }}</span>\n </div>\n <button\n type=\"button\"\n 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\"\n :aria-label=\"t('common.close')\"\n data-testid=\"collections-chat-close\"\n @click=\"closeChat\"\n >\n <span class=\"material-icons text-lg\">close</span>\n </button>\n </header>\n\n <div class=\"px-6 py-5\">\n <textarea\n ref=\"chatInputEl\"\n v-model=\"chatMessage\"\n rows=\"4\"\n :placeholder=\"t('collectionsView.chatPlaceholder')\"\n 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\"\n data-testid=\"collections-chat-input\"\n @keydown.meta.enter=\"submitChat\"\n @keydown.ctrl.enter=\"submitChat\"\n ></textarea>\n </div>\n\n <footer class=\"px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50\">\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors\"\n data-testid=\"collections-chat-cancel\"\n @click=\"closeChat\"\n >\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"button\"\n class=\"h-8 px-2.5 rounded bg-indigo-600 text-white font-bold text-xs hover:bg-indigo-700 disabled:opacity-50 transition-all shadow-sm shadow-indigo-600/10\"\n :disabled=\"!chatMessage.trim()\"\n data-testid=\"collections-chat-send\"\n @click=\"submitChat\"\n >\n {{ t(\"collectionsView.chatStart\") }}\n </button>\n </footer>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport CollectionRecordModal from \"./CollectionRecordModal.vue\";\nimport CollectionCalendarView from \"./CollectionCalendarView.vue\";\nimport CollectionDayView from \"./CollectionDayView.vue\";\nimport CollectionKanbanView from \"./CollectionKanbanView.vue\";\nimport CollectionRecordPanel from \"./CollectionRecordPanel.vue\";\nimport CollectionViewConfigModal from \"./CollectionViewConfigModal.vue\";\nimport CollectionCustomView from \"./CollectionCustomView.vue\";\nimport { useCollectionRendering } from \"../useCollectionRendering\";\nimport {\n readCollectionViewMode,\n writeCollectionViewMode,\n readCollectionSort,\n writeCollectionSort,\n type CollectionViewMode,\n type BuiltInViewMode,\n} from \"../collectionViewMode\";\nimport { collectionUi } from \"../uiContext\";\nimport { dateOf, type Ymd } from \"../../core/calendarGrid\";\nimport {\n isSortableField,\n nextSortDirection,\n sortItems,\n numericSortValue,\n stringSortValue,\n dateSortValue,\n enumSortValue,\n boolSortValue,\n type SortState,\n type SortValue,\n} from \"../../core/sortItems\";\nimport { shortHexId } from \"../../core/shortHexId\";\nimport { defangForPrompt } from \"../../core/promptSafety\";\nimport { actionVisible, fieldVisible } from \"../../core/actionVisible\";\nimport { resolveEnumColor } from \"../../core/enumColors\";\nimport { buildUpdatedRecord, coerceInlineValue, draftToRecord, firstMissingRequiredField, rowFromItem } from \"../../core/draft\";\nimport type {\n CollectionAction,\n CollectionCustomView as CustomViewSpec,\n CollectionDetail,\n CollectionItem,\n CollectionFieldSpec as FieldSpec,\n} from \"../../core/schema\";\nimport type { CollectionRecordIssue, CollectionNotifySeverity, EditState, TableRowDraft } from \"../../core/uiTypes\";\n\n/** `slug` / `selected` are supplied only in EMBEDDED mode (the\n * `presentCollection` chat card mounts this component and drives both\n * from the tool result). In standalone route mode (the\n * `/collections/:slug` page) both are undefined and the component reads\n * `route.params.slug` / `route.query.selected` as before.\n *\n * `sendTextMessage` is forwarded ONLY by the chat card — its presence\n * is our \"rendered inside a chat\" signal. When set, chat-triggering\n * actions send into the current session instead of spawning a new\n * chat (see `runAction` / `submitChat`). */\nconst props = defineProps<{\n slug?: string;\n selected?: string;\n sendTextMessage?: (text?: string) => void;\n /** Embedded mode only: initial view / anchor / group restored from the\n * card's persisted `viewState` so a switch to calendar or kanban\n * survives a remount. (The table sort is NOT a card prop — it's a shared\n * per-collection localStorage preference, read by both modes.) */\n initialView?: BuiltInViewMode;\n initialAnchorField?: string;\n initialGroupField?: string;\n}>();\n\nconst emit = defineEmits<{\n /** Embedded mode only: the open record changed (id) or closed (null).\n * The card persists this in its tool-result `viewState` so the open\n * item survives a re-render. */\n select: [id: string | null];\n /** Embedded mode only: the view mode / calendar anchor / kanban group\n * changed. The card persists these alongside `selected` so the calendar\n * and kanban stick. (The table sort is shared via localStorage instead.) */\n viewStateChange: [state: { view: BuiltInViewMode; anchorField: string; groupField: string }];\n}>();\n\nconst { t, locale } = useCollectionI18n();\n// All host couplings (data, routing, confirm, chat, shortcuts, notifications,\n// the pin toggle) come through the injected CollectionUi binding. The aliases\n// keep the body's call sites unchanged where the host shape matched 1:1.\nconst cui = collectionUi();\nconst { confirm: openConfirm, unpin, pinToggle, startChat } = cui;\nconst appApi = { startNewChat: startChat };\n\n/** Embedded when a `slug` prop is supplied; standalone (route-driven)\n * otherwise. Switches the slug/selected source and the open/close\n * navigation behaviour. */\nconst embedded = computed<boolean>(() => props.slug !== undefined);\n\n/** Active collection slug: the prop in embedded mode, else the route\n * param. */\nconst activeSlug = computed<string | undefined>(() => {\n if (props.slug !== undefined) return props.slug;\n const slug = cui.routeSlug();\n return slug !== undefined && slug.length > 0 ? slug : undefined;\n});\n\n/** Active open-record id: the prop in embedded mode (may be undefined),\n * else the `?selected=` query. */\nconst activeSelected = computed<string | undefined>(() => {\n if (embedded.value) return props.selected;\n return cui.routeSelectedId();\n});\n\nconst collection = ref<CollectionDetail | null>(null);\nconst items = ref<CollectionItem[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\n// Record files the server flagged as malformed/invalid (silently skipped\n// at read time). When non-empty the view shows a Repair banner whose\n// button reports them back to the LLM. See `repairCollection`.\nconst dataIssues = ref<CollectionRecordIssue[]>([]);\n\n// Primary-key → notification severity for this collection's records that\n// currently have an active bell notification — passed to the Kanban board so\n// it can flag those cards in the matching bell colour (urgent red / nudge amber).\nconst notifiedSeverities = computed<Map<string, CollectionNotifySeverity>>(() => {\n const slug = collection.value?.slug;\n return slug ? cui.notifiedSeverities(slug) : new Map<string, CollectionNotifySeverity>();\n});\n/** True while a feed collection's manual refresh is in flight. */\nconst refreshing = ref(false);\n/** Slug already auto-refreshed on first open — prevents a reload loop\n * (the auto-refresh reloads the view, which would re-trigger otherwise). */\nconst autoRefreshedSlug = ref<string | null>(null);\nconst editing = ref<EditState | null>(null);\n/** The record currently shown in read-only \"open\" mode. Distinct\n * from `editing`: open mode renders formatted values (no inputs)\n * and is what a `/collections/<slug>?selected=<id>` deep link\n * lands on. Mutually exclusive with `editing` in practice —\n * `editFromView` hands off from one to the other. */\nconst viewing = ref<CollectionItem | null>(null);\n/** The calendar day whose time-allocation popup is open, or null. The\n * selected record (`viewing`) renders in that popup's right pane; a record\n * with no resolvable day falls back to the panel below the grid. */\nconst openDay = ref<Ymd | null>(null);\nconst saving = ref(false);\nconst saveError = ref<string | null>(null);\n/** Error from an inline table-cell edit (checkbox/dropdown). Distinct\n * from `saveError` (rendered only inside the detail panel, which is\n * closed during inline editing) — shown as a banner above the table. */\nconst inlineError = ref<string | null>(null);\n/** Per-load snapshot of enum cells that had NO value when fetched\n * (keyed `<rowId>:<fieldKey>`). Only these cells offer the empty\n * placeholder option in their inline dropdown — a cell that already\n * has a value can't be blanked inline (use the edit form for that). */\nconst enumOriginallyEmpty = ref<Set<string>>(new Set());\n/** Rows with an inline cell save in flight (by `rowId`). While a row is\n * here its inline controls are disabled, so two quick edits to the same\n * row can't race two full-record PUTs — an older PUT landing last would\n * otherwise clobber the newer field on disk while the UI shows the\n * newer optimistic value (Codex PR #1599 P2). */\nconst inlineSavingRows = ref<Set<string>>(new Set());\nconst actionPending = ref(false);\nconst actionError = ref<string | null>(null);\nconst collectionActionPending = ref(false);\nconst chatOpen = ref(false);\nconst chatMessage = ref(\"\");\nconst chatInputEl = ref<HTMLTextAreaElement | null>(null);\n\n// Shared rendering + linked-data layer: owns the ref/embed caches and\n// every value-formatting helper, reused by the extracted record panel\n// (table + calendar) so there's one implementation. Destructure the\n// helpers the list table renders with; pass the whole object to the\n// panel as its `render` prop.\nconst render = useCollectionRendering(collection, locale);\nconst {\n refRecordCache,\n refDisplay,\n formatMoney,\n resolveCurrency,\n derivedDisplay,\n evaluateDerivedAgainstItem,\n formatCell,\n isExternalUrl,\n artifactUrl,\n fileRoutePath,\n} = render;\n\nconst searchQuery = ref(\"\");\n\n/** Case-insensitive substring match across an item's scalar fields.\n * Object-valued fields (table rows, nested records) are skipped —\n * they don't render as searchable text in the list table. */\nfunction itemMatchesQuery(item: CollectionItem, query: string): boolean {\n return Object.values(item).some((val) => {\n if (val === undefined || val === null || typeof val === \"object\") return false;\n return String(val).toLowerCase().includes(query);\n });\n}\n\nconst filteredItems = computed<CollectionItem[]>(() => {\n const query = searchQuery.value.trim().toLowerCase();\n if (!query) return items.value;\n return items.value.filter((item) => itemMatchesQuery(item, query));\n});\n\n// ── List-table sort (single active column, header toggle) ─────────\n// Calendar / kanban keep their own ordering; only the table consumes\n// `sortedItems`. The active sort is a single SHARED per-collection\n// preference in localStorage — both the standalone page and embedded chat\n// cards read AND write it, so a sort set anywhere is consistent the next\n// time the collection is viewed. Resets only when a DIFFERENT collection\n// loads (the slug watch), so the sort survives a refresh / edit / remount.\nfunction storedSortFor(slug: string | undefined): SortState | null {\n return (slug && readCollectionSort(slug)) || null;\n}\nconst sortState = ref<SortState | null>(storedSortFor(activeSlug.value));\n// The column whose sort button is currently hovered (at most one). Hover\n// previews the NEXT click's state, so descending visibly fades back to the\n// light-grey \"off\" look — signalling the next click clears the sort.\nconst hoveredSortKey = ref<string | null>(null);\n\nfunction sortDirectionFor(key: string): \"asc\" | \"desc\" | null {\n return sortState.value?.field === key ? sortState.value.direction : null;\n}\n\n/** The direction whose visuals to render: on hover, preview the next\n * click's state; otherwise show the column's actual state. */\nfunction effectiveSortDir(key: string): \"asc\" | \"desc\" | null {\n const current = sortDirectionFor(key);\n return hoveredSortKey.value === key ? nextSortDirection(current) : current;\n}\n\n/** Cycle a column none → asc → desc → none; activating one clears the rest. */\nfunction cycleSort(key: string): void {\n const next = nextSortDirection(sortDirectionFor(key));\n sortState.value = next ? { field: key, direction: next } : null;\n}\n\nfunction sortIconName(key: string): string {\n return effectiveSortDir(key) === \"desc\" ? \"arrow_downward\" : \"arrow_upward\";\n}\n\n// Dark grey while a direction is active; light grey for the \"off\" state —\n// so hovering a descending column previews the cleared look.\nfunction sortButtonClass(key: string): string {\n return effectiveSortDir(key) ? \"text-slate-600\" : \"text-slate-300\";\n}\n\n/** ARIA `aria-sort` token for a column's header cell. */\nfunction sortAriaValue(key: string): \"ascending\" | \"descending\" | \"none\" {\n const dir = sortDirectionFor(key);\n return dir === \"asc\" ? \"ascending\" : dir === \"desc\" ? \"descending\" : \"none\";\n}\n\n/** Comparable value for scalar fields that key off the raw cell value. */\nfunction scalarSortValue(field: FieldSpec, raw: unknown): SortValue {\n switch (field.type) {\n case \"number\":\n case \"money\":\n return numericSortValue(raw);\n case \"date\":\n case \"datetime\":\n return dateSortValue(raw);\n case \"enum\":\n return enumSortValue(field.values, raw);\n case \"boolean\":\n return boolSortValue(raw === true);\n case \"ref\":\n return field.to && typeof raw === \"string\" && raw ? stringSortValue(refDisplay(field.to, raw)) : stringSortValue(raw);\n default:\n return stringSortValue(raw);\n }\n}\n\n/** Comparable value for one row under the active field. Toggle and derived\n * need the whole record; every other type keys off the raw cell. */\nfunction sortValueOf(field: FieldSpec, key: string, item: CollectionItem): SortValue {\n if (field.type === \"toggle\") return boolSortValue(toggleChecked(item, field));\n if (field.type === \"derived\") return derivedSortValue(field, key, item);\n return scalarSortValue(field, item[key]);\n}\n\n/** Derived rows sort by their display type: money/number → numeric,\n * date/datetime → epoch, anything else → the enriched value as a string. */\nfunction derivedSortValue(field: FieldSpec, key: string, item: CollectionItem): SortValue {\n const { display } = field;\n if (display === undefined || display === \"number\" || display === \"money\") {\n return numericSortValue(evaluateDerivedAgainstItem(field, key, item));\n }\n const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;\n if (display === \"date\" || display === \"datetime\") return dateSortValue(enriched[key]);\n return stringSortValue(enriched[key]);\n}\n\nconst sortedItems = computed<CollectionItem[]>(() => {\n const state = sortState.value;\n const field = state ? collection.value?.schema.fields[state.field] : undefined;\n if (!state || !field) return filteredItems.value;\n return sortItems(filteredItems.value, state.direction, (item) => sortValueOf(field, state.field, item));\n});\n\n// ────────────────────────────────────────────────────────────────\n// Open / edit record panel (shared modal + calendar day view)\n// ────────────────────────────────────────────────────────────────\n// Detail, edit, and create all render `CollectionRecordPanel` inside the\n// shared `CollectionRecordModal` (or the calendar day view for dated\n// records). One panel open at a time (`viewing` / `editing` are single\n// refs). The list table only highlights the open/edited row.\n\n/** Stringified primary-key value for a row (the row's stable identity). */\nfunction rowId(item: CollectionItem): string {\n const primaryKey = collection.value?.schema.primaryKey;\n return primaryKey ? String(item[primaryKey] ?? \"\") : \"\";\n}\n\n/** Stable key for one cell in the `enumOriginallyEmpty` snapshot. */\nfunction cellKey(rowIdValue: string, fieldKey: string): string {\n return `${rowIdValue}:${fieldKey}`;\n}\n\n/** Build the set of enum cells that were empty in the freshly-fetched\n * records — the only cells whose inline dropdown offers an empty option. */\nfunction snapshotEmptyEnums(schema: CollectionDetail[\"schema\"], records: CollectionItem[]): Set<string> {\n const empty = new Set<string>();\n const enumKeys = Object.entries(schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([fieldKey]) => fieldKey);\n if (enumKeys.length === 0) return empty;\n for (const record of records) {\n const recordId = String(record[schema.primaryKey] ?? \"\");\n for (const fieldKey of enumKeys) {\n if (record[fieldKey] == null || record[fieldKey] === \"\") empty.add(cellKey(recordId, fieldKey));\n }\n }\n return empty;\n}\n\n/** Whether an inline enum dropdown should render its empty placeholder\n * option: only for cells with no value at load time. */\nfunction showEnumPlaceholder(item: CollectionItem, fieldKey: string): boolean {\n return enumOriginallyEmpty.value.has(cellKey(rowId(item), fieldKey));\n}\n\n/** Tailwind fill/text/border classes tinting an inline enum `<select>` by its\n * current value's colour (palette, or notification red/amber/grey when the\n * field is the schema's notifyWhen target). */\nfunction enumControlClass(fieldKey: string, value: unknown): string {\n const schema = collection.value?.schema;\n if (!schema) return \"\";\n const cls = resolveEnumColor(schema, fieldKey, value);\n return `${cls.badge} ${cls.border}`;\n}\n\n/** This row is the one open in read-only detail. */\nfunction isRowOpen(item: CollectionItem): boolean {\n return viewing.value !== null && rowId(viewing.value) === rowId(item);\n}\n\n/** This row is the one being edited (highlights it in the list while the\n * edit modal is open). Create mode has no backing row, so nothing matches. */\nfunction isEditingRow(item: CollectionItem): boolean {\n const draft = editing.value;\n if (!draft || draft.mode === \"create\") return false;\n return draft.originalId === rowId(item);\n}\n\n/** Re-run a feed collection's retrieval now, then reload its records.\n * Only reachable when `schema.ingest` is present (button is gated). */\nasync function refreshFeed(): Promise<void> {\n const current = collection.value;\n if (!current?.schema.ingest || refreshing.value) return;\n refreshing.value = true;\n inlineError.value = null;\n const result = await cui.refreshCollection(current.slug);\n refreshing.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n await loadCollection(current.slug);\n // refreshOne reports retriever failures via `errors` even on HTTP 200, so\n // surface them — otherwise a failed refresh looks like success.\n if (result.data.errors.length > 0) {\n inlineError.value = t(\"collectionsView.refreshFailed\", { error: result.data.errors.join(\"; \") });\n }\n}\n\n/** Collection-level header actions. No `when` predicate (no record). */\nconst collectionActions = computed<CollectionAction[]>(() => collection.value?.schema.collectionActions ?? []);\n\n/** Run a collection-level action: ask the server to assemble the seed\n * prompt (a progress summary of all records + the template), then start\n * a new chat in the action's role with it. Generic — no domain knowledge. */\nasync function runCollectionAction(action: CollectionAction): Promise<void> {\n const current = collection.value;\n if (!current || collectionActionPending.value) return;\n collectionActionPending.value = true;\n inlineError.value = null;\n const result = await cui.runCollectionAction(current.slug, action.id);\n collectionActionPending.value = false;\n if (!result.ok) {\n inlineError.value = result.error;\n return;\n }\n if (props.sendTextMessage) {\n props.sendTextMessage(result.data.prompt);\n return;\n }\n appApi.startNewChat(result.data.prompt, result.data.role);\n}\n\n/** Report the server-detected record data problems back to the LLM so it\n * fixes the offending files. Mirrors the `presentCollection` validation\n * path (`dispatchPresentCollection`), but user-initiated via the Repair\n * button instead of fired automatically after a write. Dispatches into\n * the current chat when embedded, else seeds a new General chat. */\nfunction repairCollection(): void {\n const current = collection.value;\n if (!current || dataIssues.value.length === 0) return;\n // Issue text carries record-controlled values (ids, enum values), so defang\n // structural injection vectors before it rides into the LLM prompt. Shared\n // with the server's presentCollection path via `defangForPrompt` so the two\n // can't drift (it also collapses whitespace, closing a newline-injection gap).\n const lines = dataIssues.value.map((issue) => `- ${defangForPrompt(issue.file)}: ${defangForPrompt(issue.problem)}`).join(\"\\n\");\n const prompt = t(\"collectionsView.repairPrompt\", { title: current.title, count: dataIssues.value.length, issues: lines });\n if (props.sendTextMessage) {\n props.sendTextMessage(prompt);\n return;\n }\n appApi.startNewChat(prompt, cui.generalRoleId);\n}\n\n/** Actions whose optional `when` predicate matches the open record.\n * Status-driven buttons (e.g. invoice \"Record payment\") stay hidden\n * until the record reaches the matching state. */\nconst visibleActions = computed<CollectionAction[]>(() => {\n const record = viewing.value;\n if (!record) return [];\n return (collection.value?.schema.actions ?? []).filter((action) => actionVisible(action, record));\n});\n\n/** Run a schema-declared action on the open record: ask the server to\n * assemble the seed prompt, then start a new chat in the action's\n * role with it. Generic — no knowledge of what the action does. */\nasync function runAction(action: CollectionAction): Promise<void> {\n if (!collection.value || !viewing.value) return;\n const itemId = String(viewing.value[collection.value.schema.primaryKey] ?? \"\");\n if (!itemId) return;\n actionPending.value = true;\n actionError.value = null;\n const result = await cui.runItemAction(collection.value.slug, itemId, action.id);\n actionPending.value = false;\n if (!result.ok) {\n actionError.value = result.error;\n return;\n }\n // In a chat card we have a channel into the current session — send\n // the seed prompt there rather than spawning a new chat. Standalone\n // route mode has no such channel, so start a fresh chat in the\n // action's role (which carries the tools the action needs).\n if (props.sendTextMessage) {\n props.sendTextMessage(result.data.prompt);\n return;\n }\n appApi.startNewChat(result.data.prompt, result.data.role);\n}\n\n/** Open the chat modal, blanking any prior draft and focusing the input. */\nfunction openChat(): void {\n chatMessage.value = \"\";\n chatOpen.value = true;\n void nextTick(() => chatInputEl.value?.focus());\n}\n\nfunction closeChat(): void {\n chatOpen.value = false;\n}\n\n/** Build the chat seed text for the current view.\n *\n * A collection IS a skill, so its slug doubles as a slash command:\n * \"I want to create an item\" on `mc_worklog` becomes\n * `/mc_worklog I want to create an item`.\n *\n * A feed is data-only — it has NO skill, so `/<slug>` would resolve to\n * nothing. Instead, point the agent at the feed's schema + records\n * (`feeds/<slug>/schema.json` and `<dataPath>/`) and let it act on the\n * request directly. */\nfunction buildChatSeed(slug: string, message: string): string {\n const schema = collection.value?.schema;\n // A feed carries an `ingest` block; a plain collection does not. Checked\n // here (rather than via the `isFeed` computed, defined further down) to\n // keep this helper self-contained and avoid a use-before-define.\n if (!schema?.ingest) return `/${slug} ${message}`;\n const dataPath = schema.dataPath ?? `data/feeds/${slug}`;\n return t(\"collectionsView.feedChatSeed\", { slug, dataPath, message });\n}\n\n/** Start a new general-role chat seeded from the current view. */\nfunction submitChat(): void {\n if (!collection.value) return;\n const message = chatMessage.value.trim();\n if (!message) return;\n closeChat();\n const text = buildChatSeed(collection.value.slug, message);\n // Chat card → send into the current session; standalone → new chat.\n if (props.sendTextMessage) {\n props.sendTextMessage(text);\n return;\n }\n appApi.startNewChat(text, cui.generalRoleId);\n}\n\nasync function loadCollection(slug: string): Promise<void> {\n // Snapshot the shortcut kind BEFORE the await — if the user navigates\n // between /feeds/:slug and /collections/:slug while the fetch is in\n // flight, reading route.name in the 404 branch could unpin the wrong\n // (kind, slug) pair.\n const requestedKind = !embedded.value && cui.isFeedRoute() ? \"feed\" : \"collection\";\n loading.value = true;\n loadError.value = null;\n collection.value = null;\n items.value = [];\n dataIssues.value = []; // never carry a previous collection's issues over\n searchQuery.value = \"\"; // Reset search query on collection load\n // NOTE: the active column sort is NOT reset here — it's part of the view\n // state, so it must survive a refresh / edit reload and an embedded card\n // remount. The collection-SWITCH reset lives in the `activeSlug` watch.\n render.resetLinkedCaches();\n viewing.value = null;\n openDay.value = null; // never carry a previous collection's open day over\n const result = await cui.fetchCollectionDetail(slug);\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.status === 404 ? \"not-found\" : result.error;\n // Dead-click safety net: a pinned shortcut for a collection/feed\n // deleted out-of-band (e.g. via chat) lands here. Self-prune it so\n // the launcher doesn't keep a button that 404s. Standalone only\n // (embedded cards carry no shortcut), and only if we're still on the\n // slug that triggered this fetch.\n if (result.status === 404 && !embedded.value && activeSlug.value === slug) {\n void unpin(requestedKind, slug);\n }\n return;\n }\n collection.value = result.data.collection;\n items.value = result.data.items;\n dataIssues.value = result.data.issues ?? [];\n enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);\n // Fan out to fetch each unique target collection so the table can\n // render ref values as display names (not slugs) and the form\n // dropdown has options. Failures fall back gracefully — the table\n // cell shows the raw slug and the form falls back to text input.\n // Pass the slug that triggered THIS load so the helper can drop\n // its result if a faster subsequent load has already switched us\n // to a different collection (Codex P1 review on PR #1495).\n await render.loadLinkedCollections(result.data.collection.schema, slug);\n // A `?selected=<id>` deep link opens that record in read-only\n // mode once its items are available. Guard against a stale load:\n // only act if we're still on the slug that triggered this fetch.\n if (collection.value?.slug === slug) {\n syncViewToSelected();\n maybeOpenCalendarForSelected();\n }\n maybeAutoRefreshFeed(slug);\n}\n\n// First-open auto-refresh: when a feed view opens with no records yet\n// (e.g. a just-registered feed that hasn't hit the scheduler), fetch once\n// so data appears without a manual Refresh. Guarded per slug so the reload\n// `refreshFeed` triggers can't loop; the view re-mounts per slug, so each\n// open retries at most once.\nfunction maybeAutoRefreshFeed(slug: string): void {\n if (embedded.value) return;\n const current = collection.value;\n if (current?.slug !== slug || !current.schema.ingest) return;\n if (items.value.length > 0 || autoRefreshedSlug.value === slug) return;\n autoRefreshedSlug.value = slug;\n void refreshFeed();\n}\n\n/** Schema fields excluding display-only `embed` fields — used by the\n * list table only (a whole embedded record doesn't fit a table cell,\n * and it'd be identical in every row). The detail modal and the edit\n * form iterate the full `schema.fields` so embeds render there too. */\n// Fields shown as columns in the list table. Excludes `embed`\n// (display-only fixed record, no per-record value), `image` — a\n// per-row <img> fetches one file each, too expensive for a collection\n// with many records, and the image is shown in the detail view anyway —\n// and the primary key (an id is plumbing, not data: it identifies the\n// row via data-testid / ref links but doesn't earn a column).\nconst listColumnFields = computed<[string, FieldSpec][]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields).filter(\n ([key, field]) => field.type !== \"embed\" && field.type !== \"image\" && key !== collection.value?.schema.primaryKey,\n )\n : [],\n);\n\n/** True when the current collection declares `schema.singleton` —\n * exactly one record, its primary key fixed to the declared value. */\nconst isSingleton = computed<boolean>(() => Boolean(collection.value?.schema.singleton));\n\n/** Whether the Add button should show. Always for a normal collection;\n * for a singleton only until its one record exists. */\nconst canCreate = computed<boolean>(() => {\n if (!collection.value) return false;\n return !(isSingleton.value && items.value.length > 0);\n});\n\n// A collection is deletable only when it's project-scope AND not a\n// preset (`mc-*`) — mirrors the server-side rule in\n// `deleteCollection`. User-scope skills are read-only from MulmoClaude;\n// presets re-seed on restart so deleting them is futile.\nconst canDeleteCollection = computed<boolean>(() => {\n const current = collection.value;\n if (!current) return false;\n return current.source === \"project\" && !current.slug.startsWith(\"mc-\");\n});\n\n// True when this view was opened as a feed (`/feeds/:slug`): the schema\n// carries an `ingest` block. Feeds are deleted via DELETE /api/feeds/:slug,\n// not the project-scope collection delete above.\nconst isFeed = computed<boolean>(() => Boolean(collection.value?.schema.ingest));\nconst canDeleteFeed = computed<boolean>(() => isFeed.value && !embedded.value);\n\n// Which list to return to from the back arrow: feeds opened via /feeds\n// go back to the feed list; everything else to the collections index.\nconst isFeedRoute = computed<boolean>(() => !embedded.value && cui.isFeedRoute());\n\n// ── View mode (table | calendar | kanban) ─────────────────────────\n// Local UI state only — NEVER persisted to schema. The user toggles it;\n// the host never flips it programmatically. The calendar is offered only\n// when the schema has a `date` field and the kanban only when it has an\n// `enum` field, so plain collections and the initial load are unchanged\n// (default \"table\").\n//\n// Standalone route mode persists the last-used mode per collection in\n// localStorage so reopening `/collections/:slug` restores the prior view\n// instead of always starting on the table. Embedded chat cards restore from\n// the card's own `initialView` first; lacking that (a freshly-rendered\n// presentCollection card), they fall back to the same per-collection store\n// the standalone page uses, so a card also opens in the last-used view.\n// `CollectionViewMode` (\"table\" | \"calendar\" | \"kanban\" | \"dashboard\" |\n// `custom:<id>`) is imported from the view-mode util.\n\n/** The view to open with: the embedded card's restored `initialView` if\n * present (its own persisted state wins), else the slug's stored\n * preference, else \"table\". Embedded cards READ the store but never WRITE\n * it (the persist watch only emits `viewStateChange` for them), so a stale\n * card re-rendering can't clobber the shared preference. */\nfunction initialViewMode(): CollectionViewMode {\n if (props.initialView) return props.initialView;\n const slug = activeSlug.value;\n return (slug && readCollectionViewMode(slug)) || \"table\";\n}\nconst view = ref<CollectionViewMode>(initialViewMode());\n\n/** `date` / `datetime` fields in declaration order — the calendar can anchor\n * on any (a `datetime` anchor also carries the clock for the day view). */\nconst dateFields = computed<string[]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields)\n .filter(([, field]) => field.type === \"date\" || field.type === \"datetime\")\n .map(([key]) => key)\n : [],\n);\n\n/** Whether the table ↔ calendar toggle is offered. */\nconst hasCalendar = computed<boolean>(() => dateFields.value.length > 0);\n\n/** `enum` fields in declaration order — the kanban can group on any. */\nconst enumFields = computed<string[]>(() =>\n collection.value\n ? Object.entries(collection.value.schema.fields)\n .filter(([, field]) => field.type === \"enum\")\n .map(([key]) => key)\n : [],\n);\n\n/** Whether the kanban toggle is offered (needs an `enum` field to group on). */\nconst hasKanban = computed<boolean>(() => enumFields.value.length > 0);\n\n/** The effective view, collapsing any stale mode whose enabling field\n * vanished (e.g. `view = \"kanban\"` after switching to an enum-less\n * collection) back to \"table\". Single source of truth for the toggle and\n * the body branches. */\n/** Custom (LLM-authored) HTML views declared on the schema. */\nconst customViews = computed<CustomViewSpec[]>(() => collection.value?.schema.views ?? []);\nconst hasCustomViews = computed<boolean>(() => customViews.value.length > 0);\n\nconst activeView = computed<CollectionViewMode>(() => {\n if (view.value === \"calendar\" && hasCalendar.value) return \"calendar\";\n if (view.value === \"kanban\" && hasKanban.value) return \"kanban\";\n if (view.value.startsWith(\"custom:\")) {\n const viewId = view.value.slice(\"custom:\".length);\n if (customViews.value.some((entry) => entry.id === viewId)) return view.value;\n }\n return \"table\";\n});\n\n/** The selected custom view's spec, or null when a built-in view is active. */\nconst activeCustomView = computed<CustomViewSpec | null>(() => {\n const mode = activeView.value;\n if (!mode.startsWith(\"custom:\")) return null;\n const viewId = mode.slice(\"custom:\".length);\n return customViews.value.find((entry) => entry.id === viewId) ?? null;\n});\n\n/** Narrow a (possibly custom) mode to a built-in one, used where only the\n * built-in views are representable (the embedded card's viewState). */\nfunction builtInViewOrTable(mode: CollectionViewMode): BuiltInViewMode {\n return mode === \"calendar\" || mode === \"kanban\" ? mode : \"table\";\n}\n\n/** Whether to offer the \"+\" (author a new custom view) button. Standalone\n * page only (the seed starts a chat). Feeds qualify too — their views are\n * authored under feeds/<slug>/ and the seed prompt points there. */\nconst canAddCustomView = computed<boolean>(() => Boolean(collection.value) && !embedded.value);\n\n/** Seed a chat asking Claude to author a new custom view for this collection.\n * Reuses the same chat-seed path as collection actions — the host injects a\n * templated prompt; Claude asks, authors the HTML, and registers it. The\n * authoring base is source-aware: a feed lives under `feeds/<slug>/`, every\n * other collection under the `data/skills/<slug>/` staging dir. */\nfunction addCustomView(): void {\n const current = collection.value;\n if (!current) return;\n const base = current.schema.ingest ? `feeds/${current.slug}` : `data/skills/${current.slug}`;\n const prompt = t(\"collectionsView.addViewPrompt\", { title: current.title, base });\n if (props.sendTextMessage) {\n props.sendTextMessage(prompt);\n return;\n }\n appApi.startNewChat(prompt, cui.generalRoleId);\n}\n\n// ── Per-collection config (gear → manage custom views) ──────────────\nconst configOpen = ref<boolean>(false);\n\n/** Whether to offer the config gear. Standalone page only, and only when\n * there's a deletable custom view to manage — i.e. the collection is one\n * whose views the server will delete (project non-preset, or a feed; never a\n * read-only user-scope skill). Mirrors the server's refusal rules. */\nconst canConfigureViews = computed<boolean>(() => !embedded.value && hasCustomViews.value && (canDeleteCollection.value || isFeed.value));\n\n/** Reload the collection after the config modal deletes a view so the toggle\n * row + the modal's own list reflect the removal. */\nasync function onViewsChanged(): Promise<void> {\n const current = collection.value;\n if (current) await loadCollection(current.slug);\n}\n\n/** True when the calendar is the active body. */\nconst calendarActive = computed<boolean>(() => activeView.value === \"calendar\");\n\n/** True when the kanban is the active body. */\nconst kanbanActive = computed<boolean>(() => activeView.value === \"kanban\");\n\n// In-view override for which enum field groups the board; null ⇒ the schema\n// hint, else the first enum field.\nconst kanbanOverride = ref<string | null>(props.initialGroupField ?? null);\nconst kanbanGroupField = computed<string>(() => {\n if (kanbanOverride.value && enumFields.value.includes(kanbanOverride.value)) return kanbanOverride.value;\n const hint = collection.value?.schema.kanbanField;\n if (hint && enumFields.value.includes(hint)) return hint;\n return enumFields.value[0] ?? \"\";\n});\n\n// In-view override for which date field anchors the grid; null ⇒ the\n// schema hint, else the first date field.\nconst anchorOverride = ref<string | null>(props.initialAnchorField ?? null);\nconst calendarAnchorField = computed<string>(() => {\n if (anchorOverride.value && dateFields.value.includes(anchorOverride.value)) return anchorOverride.value;\n const hint = collection.value?.schema.calendarField;\n if (hint && dateFields.value.includes(hint)) return hint;\n return dateFields.value[0] ?? \"\";\n});\n// The end field pairs with `schema.calendarField`. If the user switches the\n// in-view anchor to a different date field, the span no longer applies —\n// drop it so chips don't render from the new start to the original end.\nconst calendarEndField = computed<string | undefined>(() => {\n const schema = collection.value?.schema;\n if (!schema?.calendarEndField) return undefined;\n return calendarAnchorField.value === schema.calendarField ? schema.calendarEndField : undefined;\n});\n// The time-string field (e.g. ENGAGEMENTS' \"time\") that places records on the\n// day view. Like the end field, it pairs with the schema's `calendarField` —\n// dropped when the in-view anchor is switched to a different date field.\nconst calendarTimeField = computed<string | undefined>(() => {\n const schema = collection.value?.schema;\n if (!schema?.calendarTimeField) return undefined;\n return calendarAnchorField.value === schema.calendarField ? schema.calendarTimeField : undefined;\n});\n\nfunction setView(next: CollectionViewMode): void {\n view.value = next;\n}\n\n/** Select a custom view by id (builds the `custom:<id>` mode key). */\nfunction setCustomView(viewId: string): void {\n const mode: CollectionViewMode = `custom:${viewId}`;\n view.value = mode;\n}\n\n/** Selector-key for a custom view, for active-state comparison in the template. */\nfunction customViewKey(viewId: string): CollectionViewMode {\n return `custom:${viewId}`;\n}\n\n/** A short, slug-safe id not already used by a loaded record. Collisions\n * are astronomically unlikely (32 bits), but we still re-roll a few\n * times against the in-memory set before giving up and using the last\n * candidate (the server's overwrite guard is the final backstop). */\nfunction generateUniqueItemId(primaryKey: string): string {\n const existing = new Set(items.value.map((item) => String(item[primaryKey] ?? \"\")));\n let candidate = shortHexId();\n for (let attempt = 0; attempt < 8 && existing.has(candidate); attempt++) {\n candidate = shortHexId();\n }\n return candidate;\n}\n\nfunction openCreate(): void {\n if (!collection.value) return;\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n const table: Record<string, TableRowDraft[]> = {};\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n if (field.type === \"boolean\") {\n bool[key] = false;\n // New record — no boolean was originally present.\n boolOriginallyPresent[key] = false;\n boolTouched[key] = false;\n } else if (field.type === \"table\") {\n table[key] = [];\n } else if (field.type !== \"derived\" && field.type !== \"embed\" && field.type !== \"toggle\") {\n text[key] = \"\";\n }\n // derived (computed), embed (display-only, foreign record), and toggle\n // (projection of an enum field) have no draft slot.\n }\n // Singleton collections fix the primary key to the schema-declared\n // value (e.g. \"me\") so the first Add can't pick an arbitrary id.\n // Otherwise pre-fill a unique, editable id so the user doesn't have to\n // invent one — the primary-key input stays enabled in create mode, so\n // they can still override it before saving. Matches the id shape the\n // server would generate for a blank-id POST (`generateItemId`).\n const { singleton, primaryKey } = collection.value.schema;\n if (singleton) {\n text[primaryKey] = singleton;\n } else if (primaryKey in text) {\n text[primaryKey] = generateUniqueItemId(primaryKey);\n }\n viewing.value = null; // one panel open at a time\n editing.value = { mode: \"create\", text, bool, boolOriginallyPresent, boolTouched, table, originalId: null };\n saveError.value = null;\n}\n\nfunction openEdit(item: CollectionItem): void {\n if (!collection.value) return;\n const text: Record<string, string> = {};\n const bool: Record<string, boolean> = {};\n const boolOriginallyPresent: Record<string, boolean> = {};\n const boolTouched: Record<string, boolean> = {};\n const table: Record<string, TableRowDraft[]> = {};\n for (const [key, field] of Object.entries(collection.value.schema.fields)) {\n const raw = item[key];\n if (field.type === \"boolean\") {\n bool[key] = raw === true;\n // Track whether the key was present in the source record so\n // we can preserve \"omitted\" through a save that doesn't\n // touch this field. `typeof raw === \"boolean\"` is more\n // defensive than `key in item` because a wrong-typed value\n // (e.g. `billable: \"yes\"`) shouldn't be treated as a real\n // existing boolean state.\n boolOriginallyPresent[key] = typeof raw === \"boolean\";\n boolTouched[key] = false;\n } else if (field.type === \"table\" && field.of) {\n const sub = field.of;\n const rows = Array.isArray(raw) ? raw : [];\n table[key] = rows\n .filter((row): row is Record<string, unknown> => Boolean(row) && typeof row === \"object\" && !Array.isArray(row))\n .map((row) => rowFromItem(row, sub));\n } else if (field.type !== \"derived\" && field.type !== \"embed\" && field.type !== \"toggle\") {\n text[key] = raw === undefined || raw === null ? \"\" : String(raw);\n }\n }\n const primaryRaw = item[collection.value.schema.primaryKey];\n const originalId = typeof primaryRaw === \"string\" ? primaryRaw : String(primaryRaw ?? \"\");\n viewing.value = null; // one panel open at a time\n editing.value = { mode: \"edit\", text, bool, boolOriginallyPresent, boolTouched, table, originalId };\n saveError.value = null;\n}\n\nfunction closeEditor(): void {\n editing.value = null;\n saving.value = false;\n saveError.value = null;\n}\n\n/** Cancel the editor. Edit → reopen the record's read-only detail (don't\n * collapse the panel); create → just close (no prior detail to show). */\nfunction cancelEditor(): void {\n const draft = editing.value;\n const returnTo = draft && draft.mode === \"edit\" ? draft.originalId : null;\n closeEditor();\n if (returnTo) {\n const item = findItemById(returnTo);\n if (item) showDetail(item);\n }\n}\n\n/** Open mode (read-only detail). Toggles: clicking the already-open row\n * collapses it. Opening a row cancels any in-progress edit (one panel\n * open at a time). In embedded mode, report the open id so the host\n * card can persist it in `viewState`. */\nfunction openView(item: CollectionItem): void {\n if (isRowOpen(item) && !editing.value) {\n closeView();\n return;\n }\n if (editing.value) closeEditor();\n showDetail(item);\n}\n\n/** Open the read-only detail for a record WITHOUT the click-toggle. Used\n * when reopening detail programmatically (after save / cancel), where\n * `openView`'s \"click the open row to collapse\" guard would otherwise\n * immediately close a row the embedded `viewState` sync just reopened. */\nfunction showDetail(item: CollectionItem): void {\n viewing.value = item;\n actionError.value = null;\n if (embedded.value && collection.value) {\n emit(\"select\", String(item[collection.value.schema.primaryKey] ?? \"\"));\n }\n}\n\n/** Close open mode. Embedded mode reports the close via `select(null)`\n * (the card clears its `viewState`); standalone mode drops the\n * `?selected=` query param so a refresh / back-button doesn't reopen\n * the record and the URL reflects the closed state. */\nfunction closeView(): void {\n viewing.value = null;\n actionError.value = null;\n if (embedded.value) {\n emit(\"select\", null);\n return;\n }\n if (cui.routeSelectedId() !== undefined) {\n cui.setSelectedId(null);\n }\n}\n\n/** Backdrop click / Escape on the shared record modal. While editing this\n * cancels the draft (reopening the detail, matching the in-panel Cancel\n * button — so a stray click never silently discards edits); while viewing\n * it closes the detail. */\nfunction closeRecordModal(): void {\n if (editing.value) {\n cancelEditor();\n return;\n }\n closeView();\n}\n\n/** Hand off from open mode to the editor for the same record. */\nfunction editFromView(): void {\n const item = viewing.value;\n if (!item) return;\n viewing.value = null;\n openEdit(item);\n}\n\nfunction findItemById(itemId: string): CollectionItem | undefined {\n if (!collection.value) return undefined;\n const { primaryKey } = collection.value.schema;\n return items.value.find((item) => String(item[primaryKey] ?? \"\") === itemId);\n}\n\n/** Reconcile the open-mode view with the `?selected=<id>` query —\n * the single source of truth for which record is open. Opens the\n * matching record, or closes the modal when the param is absent /\n * empty / points at an id that isn't loaded (deleted record, stale\n * link). Keeping `viewing` in lockstep with the URL means browser\n * back / forward and a removed param both close the modal instead\n * of leaving stale UI on screen (Codex P2 + CodeRabbit on #1502). */\nfunction syncViewToSelected(): void {\n const selected = activeSelected.value;\n if (typeof selected !== \"string\" || selected.length === 0) {\n viewing.value = null;\n return;\n }\n const match = findItemById(selected) ?? null;\n viewing.value = match;\n // A deep link / notification opens the record in the shared modal, which\n // is centred regardless of where the row sits in a long list — no scroll\n // needed (the inline-expansion era required one).\n}\n\n/** Title for the open-mode header: the record's primary-key value\n * (e.g. `INV-2026-0001`), falling back to the collection title.\n * Non-string primary keys (numeric ids) are stringified rather\n * than discarded (CodeRabbit on #1502). */\nconst viewTitle = computed<string>(() => {\n if (!viewing.value || !collection.value) return \"\";\n const pkValue = viewing.value[collection.value.schema.primaryKey];\n if (pkValue === undefined || pkValue === null || pkValue === \"\") return collection.value.title ?? \"\";\n return String(pkValue);\n});\n\n/** Live computed record from the current draft. Drives derived\n * field displays in the form so subtotal/tax/total update as\n * the user edits line items. */\nconst liveRecord = computed<CollectionItem | null>(() => {\n if (!collection.value || !editing.value) return null;\n return draftToRecord(editing.value, collection.value.schema);\n});\n\n/** Live record with derived fields resolved (drives the form's\n * read-only derived inputs). Derivation lives in the shared\n * rendering composable; this binds it to the current draft. */\nconst liveDerived = computed<CollectionItem | null>(() => {\n if (!collection.value || !liveRecord.value) return null;\n return render.deriveAll(collection.value.schema, liveRecord.value, refRecordCache.value);\n});\n\n/** Short summary for a `table`-typed cell in the main collection\n * table. Counts rows; nothing fancier yet (per-row preview is\n * hard to fit in a single cell). */\nfunction tableSummary(value: unknown): string {\n if (!Array.isArray(value)) return \"—\";\n if (value.length === 0) return \"—\";\n return t(\"collectionsView.tableSummary\", { count: value.length });\n}\n\nasync function saveEditor(): Promise<void> {\n if (!collection.value || !editing.value) return;\n // Snapshot mutable refs before any await — route changes during\n // the save (e.g. user navigates away) can null `collection.value`\n // and would throw on the post-await `loadCollection(...)`.\n const { slug, schema } = collection.value;\n const draft = editing.value;\n saveError.value = null;\n\n const missing = firstMissingRequiredField(draft, schema);\n if (missing) {\n saveError.value = `${missing}: ${t(\"collectionsView.requiredField\")}`;\n return;\n }\n\n saving.value = true;\n const record = draftToRecord(draft, schema);\n const isCreate = draft.mode === \"create\";\n const result = isCreate ? await cui.createItem(slug, record) : await cui.updateItem(slug, draft.originalId ?? \"\", record);\n saving.value = false;\n if (!result.ok) {\n saveError.value = result.error;\n return;\n }\n const savedId = result.data.itemId;\n closeEditor();\n await loadCollection(slug);\n // Return to the saved record's read-only detail (for create, this is the\n // newly added row) in the shared modal.\n const saved = findItemById(savedId);\n if (saved) showDetail(saved);\n}\n\n/** Write a single cell's value directly onto the live `items` entry.\n * Reactive in Vue 3 (proxy), so the bound checkbox/select re-renders.\n * `undefined` (enum cleared to the placeholder) renders as the empty\n * option; the PUT body omits the key via `buildUpdatedRecord`. */\nfunction applyInlineValue(item: CollectionItem, key: string, value: unknown): void {\n item[key] = value;\n}\n\n/** True while this row has an inline cell save in flight — its inline\n * controls render disabled to serialize edits (one PUT per row). */\nfunction isRowInlineSaving(item: CollectionItem): boolean {\n return inlineSavingRows.value.has(rowId(item));\n}\n\n/** Inline table-cell edit (boolean checkbox / enum dropdown): optimistic\n * update, then PUT the full record. Gated per row so a second edit can't\n * race the in-flight one. On failure, roll the cell back and surface the\n * error. Bypasses the detail/edit panel entirely. */\nasync function commitInlineEdit(item: CollectionItem, key: string, field: FieldSpec, raw: boolean | string): Promise<void> {\n if (!collection.value) return;\n const { slug } = collection.value;\n const itemId = rowId(item);\n if (!itemId || inlineSavingRows.value.has(itemId)) return;\n const previous = item[key];\n const coerced = coerceInlineValue(field, raw);\n applyInlineValue(item, key, coerced);\n inlineError.value = null;\n inlineSavingRows.value.add(itemId);\n const result = await cui.updateItem(slug, itemId, buildUpdatedRecord(item, key, coerced));\n inlineSavingRows.value.delete(itemId);\n if (!result.ok) {\n applyInlineValue(item, key, previous);\n inlineError.value = result.error;\n }\n}\n\n/** Whether a `toggle` field reads as checked: its projected enum field\n * currently equals `onValue`. The toggle stores nothing itself. */\nfunction toggleChecked(item: CollectionItem, field: FieldSpec): boolean {\n return field.field !== undefined && String(item[field.field] ?? \"\") === field.onValue;\n}\n\n/** Flip a `toggle`: write the projected enum field to `offValue` when\n * currently checked, else `onValue`. Reuses the inline-edit PUT path\n * (optimistic + rollback) — the toggle has no value of its own. */\nfunction commitToggle(item: CollectionItem, field: FieldSpec): void {\n const targetKey = field.field;\n if (!targetKey || !collection.value) return;\n const enumField = collection.value.schema.fields[targetKey];\n if (!enumField) return;\n const next = toggleChecked(item, field) ? field.offValue : field.onValue;\n if (next === undefined) return;\n void commitInlineEdit(item, targetKey, enumField, next);\n}\n\nasync function confirmDelete(item: CollectionItem): Promise<void> {\n if (!collection.value) return;\n // Snapshot before any await (see saveEditor) — confirm dialog\n // awaits user input, plenty of time for the route to change.\n const { slug } = collection.value;\n const { primaryKey } = collection.value.schema;\n const idRaw = item[primaryKey];\n const itemId = typeof idRaw === \"string\" ? idRaw : String(idRaw ?? \"\");\n if (!itemId) return;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDelete\"),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteItem(slug, itemId);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n await loadCollection(slug);\n}\n\n// Delete the whole collection (skill + records), not just one item.\n// The server archives a restorable copy first; on success we leave the\n// now-gone collection's route for the index.\nasync function confirmCollectionDelete(): Promise<void> {\n const current = collection.value;\n if (!current) return;\n // Snapshot before the await — the confirm dialog yields control and\n // the route could change underneath us (see confirmDelete).\n const { slug, title } = current;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDeleteCollection\", { title }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteCollection(slug);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n cui.gotoIndex(\"collection\");\n}\n\nfunction goBack(): void {\n cui.gotoIndex(isFeedRoute.value ? \"feed\" : \"collection\");\n}\n\n// Delete a feed: remove its feeds/<slug>/ registry entry (records on disk\n// are retained), then return to the feed list. Distinct from\n// `confirmCollectionDelete`, which archives + deletes a skill-backed\n// collection through the project-scope collection-delete route.\nasync function confirmFeedDelete(): Promise<void> {\n const current = collection.value;\n if (!current) return;\n const { slug, title } = current;\n const ok = await openConfirm({\n message: t(\"collectionsView.confirmDeleteFeed\", { title }),\n confirmText: t(\"common.remove\"),\n cancelText: t(\"common.cancel\"),\n variant: \"danger\",\n });\n if (!ok) return;\n const result = await cui.deleteFeed(slug);\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n cui.gotoIndex(\"feed\");\n}\n\n// Load on slug change, immediate so the initial value (route param or\n// prop) triggers the first fetch — replaces the old `onMounted` +\n// separate slug watch. Works identically for route mode (reads\n// `route.params.slug`) and embedded mode (reads the `slug` prop).\n/** Open the create form with the clicked calendar day prefilled into the\n * anchor field. The calendar day view's + affordance; the create flow itself\n * is the same one the Add button uses. A `datetime` anchor renders as a\n * `datetime-local` input, which rejects a bare `YYYY-MM-DD` — seed midnight\n * so the chosen day actually survives the prefill. */\nfunction createOnDate(iso: string): void {\n if (!canCreate.value) return;\n openCreate();\n const anchor = calendarAnchorField.value;\n if (!editing.value || !anchor) return;\n const anchorType = collection.value?.schema.fields[anchor]?.type;\n editing.value.text[anchor] = anchorType === \"datetime\" ? `${iso}T00:00` : iso;\n}\n\n/** The civil day a record sits on, from its calendar anchor field (handles\n * both `date` and `datetime`). Null for undated records. */\nfunction dayOfItem(item: CollectionItem): Ymd | null {\n return dateOf(item[calendarAnchorField.value]);\n}\n\n/** Mirror the open record into the `?selected=<id>` query (standalone mode)\n * so the calendar's day-view + selection is a copy-pasteable link. In-app\n * selection didn't previously touch the URL; the calendar now does. */\nfunction writeSelectedToUrl(itemId: string): void {\n if (embedded.value || cui.routeSelectedId() === itemId) return;\n cui.setSelectedId(itemId);\n}\n\n/** Calendar chip / kanban card click → open that record's detail. In the\n * calendar it opens the day (time-allocation) popup on the record's day with\n * the detail in the right pane; an undated record falls back to the panel\n * below the grid. Unlike `openView`, this never toggles — a second click on\n * the same record keeps it open. */\nfunction onCalendarSelect(itemId: string | null): void {\n if (!itemId) {\n closeView();\n return;\n }\n const item = findItemById(itemId);\n if (!item) return;\n if (editing.value) closeEditor();\n // Anchor the popup on the record's day; null for an undated record, which\n // closes the popup so its detail falls back to the panel below the grid.\n if (calendarActive.value) openDay.value = dayOfItem(item);\n showDetail(item);\n writeSelectedToUrl(itemId);\n}\n\n/** A calendar day cell was activated → open its popup on a clean slate\n * (clear any prior selection so the popup opens timeline-only). */\nfunction onOpenDay(day: Ymd): void {\n if (editing.value) closeEditor();\n closeView();\n openDay.value = day;\n}\n\n/** Close the day popup: drop the open day, the selection, AND any in-progress\n * draft together. Clearing `editing` matters because the shared record modal\n * shows whenever `editing` is set and no day is open — so without this, an\n * edit/create started inside the day popup would re-appear in the centred\n * modal the instant the popup closed (Codex P2 on #1656). */\nfunction onDayClose(): void {\n openDay.value = null;\n if (editing.value) closeEditor();\n closeView();\n}\n\n/** Deep-link entry: a `?selected=<id>` link to a calendar-capable collection\n * opens in calendar view with the popup focused on the record's day. Runs\n * on load / slug change only (not on in-app selection), so table users\n * aren't forced into the calendar. */\nfunction maybeOpenCalendarForSelected(): void {\n if (embedded.value || !hasCalendar.value || !viewing.value) return;\n const day = dayOfItem(viewing.value);\n if (!day) return;\n view.value = \"calendar\";\n openDay.value = day;\n}\n\n/** Kanban card dropped in a column → set the record's group field to the\n * column value (the empty string clears it for the Uncategorized column).\n * Reuses the inline-edit path (optimistic write + PUT + rollback). */\nfunction onKanbanMove(itemId: string, value: string): void {\n const item = findItemById(itemId);\n const key = kanbanGroupField.value;\n const field = collection.value?.schema.fields[key];\n if (!item || !field) return;\n void commitInlineEdit(item, key, field, value);\n}\n\nwatch(\n activeSlug,\n (slug, prevSlug) => {\n // Reset view state when switching BETWEEN collections — but not on the\n // initial run (prevSlug undefined), so an embedded card's restored\n // `initialView` / `initialAnchorField` survive the first load. Both modes\n // restore the new collection's stored mode (else \"table\"); the axis\n // fields always reset to their schema defaults.\n if (prevSlug !== undefined && slug !== prevSlug) {\n view.value = (slug && readCollectionViewMode(slug)) || \"table\";\n anchorOverride.value = null;\n kanbanOverride.value = null;\n // A sort belongs to a collection's own schema, so don't carry it across —\n // restore the new collection's stored (shared) sort instead.\n sortState.value = storedSortFor(slug);\n }\n if (slug) {\n loadCollection(slug);\n } else {\n collection.value = null;\n items.value = [];\n enumOriginallyEmpty.value = new Set();\n inlineSavingRows.value = new Set();\n searchQuery.value = \"\"; // Reset search query\n loading.value = false;\n }\n },\n { immediate: true },\n);\n\n// Embedded mode: report view/anchor changes so the chat card persists them\n// in `viewState` (alongside `selected`). Standalone mode: persist the view\n// mode per slug in localStorage so reopening restores it.\n// `loading` is a dependency so the write re-runs when the collection finishes\n// loading: that's the point where a stored mode unsupported by this schema\n// (its date/enum field gone) has collapsed to \"table\" and must be normalized\n// back into storage — otherwise no other dependency changes and it lingers.\nwatch([activeView, calendarAnchorField, kanbanGroupField, sortState, loading], () => {\n // Persist the EFFECTIVE view (activeView), not the raw `view` ref — a\n // stale \"calendar\"/\"kanban\" that has fallen back to \"table\" (its enabling\n // field gone) must not be saved as an impossible mode.\n if (embedded.value) {\n // Embedded cards persist only the built-in view in v1 — a custom view\n // collapses to \"table\" for the card's restore state (custom views are a\n // standalone-page feature; widening the card viewState is a follow-up).\n emit(\"viewStateChange\", { view: builtInViewOrTable(activeView.value), anchorField: calendarAnchorField.value, groupField: kanbanGroupField.value });\n }\n // Don't write during the load window: until the collection resolves,\n // `hasCalendar`/`hasKanban` are false so `activeView` reads \"table\",\n // which would clobber a stored \"calendar\"/\"kanban\" before it can apply.\n if (activeSlug.value && !loading.value && collection.value) {\n // View mode stays standalone-authored — embedded reads but never writes it,\n // so a stale card can't clobber the shared mode. The table SORT, by\n // contrast, IS shared both ways: a card always re-reads it on mount, so\n // there's no per-card value to go stale and clobber the store.\n if (!embedded.value) writeCollectionViewMode(activeSlug.value, activeView.value);\n writeCollectionSort(activeSlug.value, sortState.value);\n }\n});\n\n// React to the active selection changing while already on this\n// collection: follow it to open the new record, OR close the modal when\n// it's cleared (browser back / card close) or points at a missing id.\n// The initial / cross-collection case is handled by `loadCollection`;\n// here we only act once items are loaded.\nwatch(activeSelected, () => {\n if (loading.value || !collection.value) return;\n syncViewToSelected();\n // Keep the calendar-owned openDay in step with the selection — re-anchor it on\n // the selected record's day, or clear it when the selection is gone. Do this\n // even when the calendar isn't the active view: openDay is calendar state, so\n // a selection cleared in the table must not survive into a later calendar\n // visit. Never force a view switch here — that's loadCollection's deep-link job.\n openDay.value = viewing.value ? dayOfItem(viewing.value) : null;\n});\n</script>\n","<template>\n <div class=\"h-full overflow-y-auto bg-slate-50/50 px-6 py-6\" data-testid=\"collections-view-root\">\n <div class=\"max-w-4xl mx-auto\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-xl font-semibold text-slate-800\">\n {{ t(\"collectionsView.title\") }}\n </h1>\n <button\n type=\"button\"\n 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\"\n data-testid=\"collections-add-collection\"\n @click=\"startCreateCollectionChat\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"collectionsView.addCollectionLabel\") }}</span>\n </button>\n </div>\n\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" class=\"rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ t(\"collectionsView.loadFailed\") }}: {{ loadError }}</span>\n </div>\n\n <div v-else-if=\"collections.length === 0\" class=\"rounded-xl border border-slate-200 bg-white px-6 py-12 text-center text-sm text-slate-500 shadow-sm\">\n <span class=\"material-icons text-4xl text-slate-300 mb-2\">dashboard_customize</span>\n <p class=\"font-medium text-slate-700\">{{ t(\"collectionsView.indexEmpty\") }}</p>\n </div>\n\n <div v-else class=\"grid gap-4 sm:grid-cols-2\">\n <div\n v-for=\"collection in collections\"\n :key=\"collection.slug\"\n class=\"group relative rounded-xl border border-slate-200 bg-white p-5 shadow-sm hover:shadow-md hover:border-indigo-300 transition-all duration-300 cursor-pointer flex items-center gap-4 focus:outline-none focus:ring-2 focus:ring-indigo-500/20\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t('collectionsView.openCollection', { title: collection.title })\"\n :data-testid=\"`collections-index-card-${collection.slug}`\"\n @click=\"openCollection(collection.slug)\"\n @keydown.enter.self=\"openCollection(collection.slug)\"\n @keydown.space.self.prevent=\"openCollection(collection.slug)\"\n >\n <!-- Left border color line showing source -->\n <div\n class=\"absolute left-0 top-0 bottom-0 w-1 rounded-l-xl transition-all duration-300 group-hover:w-1.5\"\n :class=\"collection.source === 'project' ? 'bg-indigo-600' : 'bg-violet-600'\"\n ></div>\n\n <!-- Styled icon badge -->\n <div\n class=\"h-12 w-12 flex items-center justify-center rounded-xl transition-all duration-300 group-hover:scale-105 shadow-sm\"\n :class=\"\n collection.source === 'project'\n ? 'bg-indigo-50 text-indigo-600 group-hover:bg-indigo-100/80 border border-indigo-100/50'\n : 'bg-violet-50 text-violet-600 group-hover:bg-violet-100/80 border border-violet-100/50'\n \"\n >\n <span class=\"material-symbols-outlined text-2xl\">{{ collection.icon }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <span class=\"block font-semibold text-slate-800 text-[15px] group-hover:text-indigo-950 transition-colors truncate\">\n {{ collection.title }}\n </span>\n <span class=\"block text-[10px] text-slate-400 mt-1 tracking-wider font-semibold uppercase flex items-center gap-1.5\">\n <span class=\"h-1.5 w-1.5 rounded-full\" :class=\"collection.source === 'project' ? 'bg-indigo-500' : 'bg-violet-500'\"></span>\n {{ t(`collectionsView.source.${collection.source}`) }} ·\n <code class=\"text-[10px] bg-slate-100 px-1 rounded lowercase text-slate-500 font-mono font-normal\">{{ collection.slug }}</code>\n </span>\n </div>\n\n <component :is=\"pinToggle\" kind=\"collection\" :slug=\"collection.slug\" :title=\"collection.title\" :icon=\"collection.icon\" />\n\n <div\n class=\"h-8 w-8 flex items-center justify-center rounded-lg bg-slate-50 group-hover:bg-indigo-50 text-slate-400 group-hover:text-indigo-600 transition-all duration-300\"\n >\n <span class=\"material-icons text-lg transition-transform duration-300 group-hover:translate-x-0.5\">chevron_right</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionSummary } from \"../../core/schema\";\n\nconst { t } = useCollectionI18n();\n// Host couplings (list/navigate/chat/shortcuts/pin) via the injected binding.\nconst cui = collectionUi();\nconst { pinToggle, reconcileShortcuts } = cui;\n\nconst collections = ref<CollectionSummary[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\n\nasync function loadCollections(): Promise<void> {\n loading.value = true;\n loadError.value = null;\n const result = await cui.listCollections();\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n // Feeds (source \"feed\") have their own /feeds surface — keep the\n // Collections index to skill-backed collections so they don't double-list.\n collections.value = result.data.collections.filter((collection) => collection.source !== \"feed\");\n // Bulk-reconcile pinned collection shortcuts against this authoritative\n // list (free — we already fetched it): prune dead slugs, refresh stale\n // titles/icons, self-heal the file. Feed shortcuts are left to FeedsView.\n void reconcileShortcuts(\n \"collection\",\n collections.value.map((collection) => ({ slug: collection.slug, title: collection.title, icon: collection.icon })),\n );\n}\n\nfunction openCollection(slug: string): void {\n cui.gotoDetail(\"collection\", slug);\n}\n\nfunction startCreateCollectionChat(): void {\n cui.startChat(t(\"collectionsView.addCollectionPrompt\"), cui.generalRoleId);\n}\n\nonMounted(loadCollections);\n</script>\n","<template>\n <div class=\"h-full overflow-y-auto bg-slate-50/50 px-6 py-6\" data-testid=\"collections-view-root\">\n <div class=\"max-w-4xl mx-auto\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-xl font-semibold text-slate-800\">\n {{ t(\"collectionsView.title\") }}\n </h1>\n <button\n type=\"button\"\n 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\"\n data-testid=\"collections-add-collection\"\n @click=\"startCreateCollectionChat\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"collectionsView.addCollectionLabel\") }}</span>\n </button>\n </div>\n\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" class=\"rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ t(\"collectionsView.loadFailed\") }}: {{ loadError }}</span>\n </div>\n\n <div v-else-if=\"collections.length === 0\" class=\"rounded-xl border border-slate-200 bg-white px-6 py-12 text-center text-sm text-slate-500 shadow-sm\">\n <span class=\"material-icons text-4xl text-slate-300 mb-2\">dashboard_customize</span>\n <p class=\"font-medium text-slate-700\">{{ t(\"collectionsView.indexEmpty\") }}</p>\n </div>\n\n <div v-else class=\"grid gap-4 sm:grid-cols-2\">\n <div\n v-for=\"collection in collections\"\n :key=\"collection.slug\"\n class=\"group relative rounded-xl border border-slate-200 bg-white p-5 shadow-sm hover:shadow-md hover:border-indigo-300 transition-all duration-300 cursor-pointer flex items-center gap-4 focus:outline-none focus:ring-2 focus:ring-indigo-500/20\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"t('collectionsView.openCollection', { title: collection.title })\"\n :data-testid=\"`collections-index-card-${collection.slug}`\"\n @click=\"openCollection(collection.slug)\"\n @keydown.enter.self=\"openCollection(collection.slug)\"\n @keydown.space.self.prevent=\"openCollection(collection.slug)\"\n >\n <!-- Left border color line showing source -->\n <div\n class=\"absolute left-0 top-0 bottom-0 w-1 rounded-l-xl transition-all duration-300 group-hover:w-1.5\"\n :class=\"collection.source === 'project' ? 'bg-indigo-600' : 'bg-violet-600'\"\n ></div>\n\n <!-- Styled icon badge -->\n <div\n class=\"h-12 w-12 flex items-center justify-center rounded-xl transition-all duration-300 group-hover:scale-105 shadow-sm\"\n :class=\"\n collection.source === 'project'\n ? 'bg-indigo-50 text-indigo-600 group-hover:bg-indigo-100/80 border border-indigo-100/50'\n : 'bg-violet-50 text-violet-600 group-hover:bg-violet-100/80 border border-violet-100/50'\n \"\n >\n <span class=\"material-symbols-outlined text-2xl\">{{ collection.icon }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <span class=\"block font-semibold text-slate-800 text-[15px] group-hover:text-indigo-950 transition-colors truncate\">\n {{ collection.title }}\n </span>\n <span class=\"block text-[10px] text-slate-400 mt-1 tracking-wider font-semibold uppercase flex items-center gap-1.5\">\n <span class=\"h-1.5 w-1.5 rounded-full\" :class=\"collection.source === 'project' ? 'bg-indigo-500' : 'bg-violet-500'\"></span>\n {{ t(`collectionsView.source.${collection.source}`) }} ·\n <code class=\"text-[10px] bg-slate-100 px-1 rounded lowercase text-slate-500 font-mono font-normal\">{{ collection.slug }}</code>\n </span>\n </div>\n\n <component :is=\"pinToggle\" kind=\"collection\" :slug=\"collection.slug\" :title=\"collection.title\" :icon=\"collection.icon\" />\n\n <div\n class=\"h-8 w-8 flex items-center justify-center rounded-lg bg-slate-50 group-hover:bg-indigo-50 text-slate-400 group-hover:text-indigo-600 transition-all duration-300\"\n >\n <span class=\"material-icons text-lg transition-transform duration-300 group-hover:translate-x-0.5\">chevron_right</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { collectionUi } from \"../uiContext\";\nimport type { CollectionSummary } from \"../../core/schema\";\n\nconst { t } = useCollectionI18n();\n// Host couplings (list/navigate/chat/shortcuts/pin) via the injected binding.\nconst cui = collectionUi();\nconst { pinToggle, reconcileShortcuts } = cui;\n\nconst collections = ref<CollectionSummary[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\n\nasync function loadCollections(): Promise<void> {\n loading.value = true;\n loadError.value = null;\n const result = await cui.listCollections();\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n // Feeds (source \"feed\") have their own /feeds surface — keep the\n // Collections index to skill-backed collections so they don't double-list.\n collections.value = result.data.collections.filter((collection) => collection.source !== \"feed\");\n // Bulk-reconcile pinned collection shortcuts against this authoritative\n // list (free — we already fetched it): prune dead slugs, refresh stale\n // titles/icons, self-heal the file. Feed shortcuts are left to FeedsView.\n void reconcileShortcuts(\n \"collection\",\n collections.value.map((collection) => ({ slug: collection.slug, title: collection.title, icon: collection.icon })),\n );\n}\n\nfunction openCollection(slug: string): void {\n cui.gotoDetail(\"collection\", slug);\n}\n\nfunction startCreateCollectionChat(): void {\n cui.startChat(t(\"collectionsView.addCollectionPrompt\"), cui.generalRoleId);\n}\n\nonMounted(loadCollections);\n</script>\n","<template>\n <div class=\"h-full overflow-y-auto bg-slate-50/50 px-6 py-6\" data-testid=\"feeds-view-root\">\n <div class=\"max-w-4xl mx-auto\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-xl font-semibold text-slate-800\">{{ t(\"collectionsView.feedsTitle\") }}</h1>\n <button\n type=\"button\"\n 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\"\n data-testid=\"feeds-add\"\n @click=\"startAddFeedChat\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"common.add\") }}</span>\n </button>\n </div>\n\n <div\n v-if=\"refreshError\"\n class=\"mb-4 flex items-center gap-2 rounded-xl border border-red-200 bg-red-50/50 p-3 text-sm text-red-800 shadow-sm\"\n data-testid=\"feeds-refresh-error\"\n >\n <span class=\"material-icons text-base text-red-600\">error</span>\n <span>{{ refreshError }}</span>\n </div>\n\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" class=\"rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ t(\"collectionsView.loadFailed\") }}: {{ loadError }}</span>\n </div>\n\n <div v-else-if=\"feeds.length === 0\" class=\"rounded-xl border border-slate-200 bg-white px-6 py-12 text-center text-sm text-slate-500 shadow-sm\">\n <span class=\"material-icons text-4xl text-slate-300 mb-2\">dynamic_feed</span>\n <p class=\"font-medium text-slate-700\">{{ t(\"collectionsView.feedsEmpty\") }}</p>\n </div>\n\n <div v-else class=\"grid gap-4 sm:grid-cols-2\">\n <div\n v-for=\"feed in feeds\"\n :key=\"feed.slug\"\n class=\"group relative rounded-xl border border-slate-200 bg-white p-5 shadow-sm hover:shadow-md hover:border-indigo-300 transition-all duration-300 cursor-pointer flex items-center gap-4 focus:outline-none focus:ring-2 focus:ring-indigo-500/20\"\n role=\"button\"\n tabindex=\"0\"\n :data-testid=\"`feeds-card-${feed.slug}`\"\n @click=\"open(feed.slug)\"\n @keydown.enter.self=\"open(feed.slug)\"\n @keydown.space.self.prevent=\"open(feed.slug)\"\n >\n <div class=\"h-12 w-12 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50\">\n <span class=\"material-symbols-outlined text-2xl\">{{ feed.icon || \"dynamic_feed\" }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <span class=\"block font-semibold text-slate-800 text-[15px] truncate\">{{ feed.title }}</span>\n <span class=\"block text-[10px] text-slate-400 mt-1 tracking-wider font-semibold uppercase\">\n {{ feed.kind }} · {{ feed.schedule }}\n <template v-if=\"feed.lastFetchedAt\"> · {{ formatTime(feed.lastFetchedAt) }}</template>\n </span>\n </div>\n\n <component :is=\"pinToggle\" kind=\"feed\" :slug=\"feed.slug\" :title=\"feed.title\" :icon=\"feed.icon || 'dynamic_feed'\" />\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded-lg bg-slate-50 hover:bg-indigo-50 text-slate-400 hover:text-indigo-600 transition-all duration-300 disabled:opacity-50\"\n :disabled=\"refreshingSlug === feed.slug\"\n :title=\"t('collectionsView.refreshFeed')\"\n :aria-label=\"t('collectionsView.refreshFeed')\"\n :data-testid=\"`feeds-refresh-${feed.slug}`\"\n @click.stop=\"refresh(feed.slug)\"\n >\n <span class=\"material-icons text-lg\">{{ refreshingSlug === feed.slug ? \"hourglass_empty\" : \"refresh\" }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- Add-feed prompt: the user supplies only a URL; the agent fetches\n it, infers the title + fields, and registers the feed itself. -->\n <div v-if=\"addOpen\" class=\"fixed inset-0 z-40 flex items-center justify-center bg-black/30 p-4\" @click.self=\"closeAdd\">\n <div class=\"w-full max-w-md rounded-xl bg-white p-5 shadow-xl\">\n <h2 class=\"text-sm font-semibold text-slate-800 mb-1\">{{ t(\"collectionsView.addFeedTitle\") }}</h2>\n <p class=\"text-xs text-slate-500 mb-3\">{{ t(\"collectionsView.addFeedHint\") }}</p>\n <input\n ref=\"addInputEl\"\n v-model=\"addUrl\"\n type=\"url\"\n placeholder=\"https://example.com/feed.xml\"\n class=\"w-full rounded border border-slate-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500\"\n data-testid=\"feeds-add-url\"\n @keydown.enter=\"submitAdd\"\n @keydown.esc=\"closeAdd\"\n />\n <div class=\"mt-4 flex justify-end gap-2\">\n <button type=\"button\" class=\"h-8 px-3 rounded text-xs font-medium text-slate-600 hover:bg-slate-100\" @click=\"closeAdd\">\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"button\"\n class=\"h-8 px-3 rounded bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold disabled:opacity-50\"\n :disabled=\"!addUrl.trim()\"\n data-testid=\"feeds-add-submit\"\n @click=\"submitAdd\"\n >\n {{ t(\"common.add\") }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { collectionUi } from \"../uiContext\";\nimport type { FeedSummary } from \"../../core/uiTypes\";\n\nconst { t } = useCollectionI18n();\n// Host couplings (list/refresh/navigate/chat/shortcuts/pin) via the binding.\nconst cui = collectionUi();\nconst { pinToggle, reconcileShortcuts } = cui;\n\nconst feeds = ref<FeedSummary[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\nconst refreshingSlug = ref<string | null>(null);\n/** Non-destructive banner for a per-feed Refresh that failed (the\n * endpoint reports retriever errors via `errors` even on HTTP 200). */\nconst refreshError = ref<string | null>(null);\n\n// Add-feed prompt state: the user types a URL, the agent does the rest.\nconst addOpen = ref(false);\nconst addUrl = ref(\"\");\nconst addInputEl = ref<HTMLInputElement | null>(null);\n\nasync function load(): Promise<void> {\n loading.value = true;\n loadError.value = null;\n const result = await cui.listFeeds();\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n feeds.value = result.data.feeds;\n // Bulk-reconcile pinned feed shortcuts against this authoritative list:\n // prune dead slugs, refresh stale titles/icons, self-heal the file.\n void reconcileShortcuts(\n \"feed\",\n feeds.value.map((feed) => ({ slug: feed.slug, title: feed.title, icon: feed.icon || \"dynamic_feed\" })),\n );\n}\n\nfunction open(slug: string): void {\n cui.gotoDetail(\"feed\", slug);\n}\n\nasync function refresh(slug: string): Promise<void> {\n refreshingSlug.value = slug;\n refreshError.value = null;\n const result = await cui.refreshCollection(slug);\n refreshingSlug.value = null;\n if (!result.ok) {\n refreshError.value = t(\"collectionsView.refreshFailed\", { error: result.error });\n return;\n }\n await load(); // reload to refresh lastFetchedAt\n // refreshOne reports retriever failures via `errors` even on HTTP 200.\n if (result.data.errors.length > 0) {\n refreshError.value = t(\"collectionsView.refreshFailed\", { error: result.data.errors.join(\"; \") });\n }\n}\n\nfunction startAddFeedChat(): void {\n addUrl.value = \"\";\n addOpen.value = true;\n void nextTick(() => addInputEl.value?.focus());\n}\n\nfunction closeAdd(): void {\n addOpen.value = false;\n}\n\n// Hand the URL to the agent with an autonomous seed prompt: it reads\n// config/helps/feeds.md, fetches the URL, infers the schema from the data,\n// and writes feeds/<slug>/schema.json — no follow-up questions, no tool.\nfunction submitAdd(): void {\n const url = addUrl.value.trim();\n if (!url) return;\n addOpen.value = false;\n cui.startChat(t(\"collectionsView.addFeedPrompt\", { url }), cui.personalRoleId);\n}\n\nfunction formatTime(iso: string): string {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? iso : date.toLocaleString();\n}\n\nonMounted(load);\n</script>\n","<template>\n <div class=\"h-full overflow-y-auto bg-slate-50/50 px-6 py-6\" data-testid=\"feeds-view-root\">\n <div class=\"max-w-4xl mx-auto\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-xl font-semibold text-slate-800\">{{ t(\"collectionsView.feedsTitle\") }}</h1>\n <button\n type=\"button\"\n 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\"\n data-testid=\"feeds-add\"\n @click=\"startAddFeedChat\"\n >\n <span class=\"material-icons text-sm\">add</span>\n <span>{{ t(\"common.add\") }}</span>\n </button>\n </div>\n\n <div\n v-if=\"refreshError\"\n class=\"mb-4 flex items-center gap-2 rounded-xl border border-red-200 bg-red-50/50 p-3 text-sm text-red-800 shadow-sm\"\n data-testid=\"feeds-refresh-error\"\n >\n <span class=\"material-icons text-base text-red-600\">error</span>\n <span>{{ refreshError }}</span>\n </div>\n\n <div v-if=\"loading\" class=\"flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3\">\n <div class=\"h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin\"></div>\n <span>{{ t(\"common.loading\") }}</span>\n </div>\n\n <div v-else-if=\"loadError\" class=\"rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3\">\n <span class=\"material-icons text-red-600\">error</span>\n <span>{{ t(\"collectionsView.loadFailed\") }}: {{ loadError }}</span>\n </div>\n\n <div v-else-if=\"feeds.length === 0\" class=\"rounded-xl border border-slate-200 bg-white px-6 py-12 text-center text-sm text-slate-500 shadow-sm\">\n <span class=\"material-icons text-4xl text-slate-300 mb-2\">dynamic_feed</span>\n <p class=\"font-medium text-slate-700\">{{ t(\"collectionsView.feedsEmpty\") }}</p>\n </div>\n\n <div v-else class=\"grid gap-4 sm:grid-cols-2\">\n <div\n v-for=\"feed in feeds\"\n :key=\"feed.slug\"\n class=\"group relative rounded-xl border border-slate-200 bg-white p-5 shadow-sm hover:shadow-md hover:border-indigo-300 transition-all duration-300 cursor-pointer flex items-center gap-4 focus:outline-none focus:ring-2 focus:ring-indigo-500/20\"\n role=\"button\"\n tabindex=\"0\"\n :data-testid=\"`feeds-card-${feed.slug}`\"\n @click=\"open(feed.slug)\"\n @keydown.enter.self=\"open(feed.slug)\"\n @keydown.space.self.prevent=\"open(feed.slug)\"\n >\n <div class=\"h-12 w-12 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50\">\n <span class=\"material-symbols-outlined text-2xl\">{{ feed.icon || \"dynamic_feed\" }}</span>\n </div>\n\n <div class=\"flex-1 min-w-0\">\n <span class=\"block font-semibold text-slate-800 text-[15px] truncate\">{{ feed.title }}</span>\n <span class=\"block text-[10px] text-slate-400 mt-1 tracking-wider font-semibold uppercase\">\n {{ feed.kind }} · {{ feed.schedule }}\n <template v-if=\"feed.lastFetchedAt\"> · {{ formatTime(feed.lastFetchedAt) }}</template>\n </span>\n </div>\n\n <component :is=\"pinToggle\" kind=\"feed\" :slug=\"feed.slug\" :title=\"feed.title\" :icon=\"feed.icon || 'dynamic_feed'\" />\n\n <button\n type=\"button\"\n class=\"h-8 w-8 flex items-center justify-center rounded-lg bg-slate-50 hover:bg-indigo-50 text-slate-400 hover:text-indigo-600 transition-all duration-300 disabled:opacity-50\"\n :disabled=\"refreshingSlug === feed.slug\"\n :title=\"t('collectionsView.refreshFeed')\"\n :aria-label=\"t('collectionsView.refreshFeed')\"\n :data-testid=\"`feeds-refresh-${feed.slug}`\"\n @click.stop=\"refresh(feed.slug)\"\n >\n <span class=\"material-icons text-lg\">{{ refreshingSlug === feed.slug ? \"hourglass_empty\" : \"refresh\" }}</span>\n </button>\n </div>\n </div>\n </div>\n\n <!-- Add-feed prompt: the user supplies only a URL; the agent fetches\n it, infers the title + fields, and registers the feed itself. -->\n <div v-if=\"addOpen\" class=\"fixed inset-0 z-40 flex items-center justify-center bg-black/30 p-4\" @click.self=\"closeAdd\">\n <div class=\"w-full max-w-md rounded-xl bg-white p-5 shadow-xl\">\n <h2 class=\"text-sm font-semibold text-slate-800 mb-1\">{{ t(\"collectionsView.addFeedTitle\") }}</h2>\n <p class=\"text-xs text-slate-500 mb-3\">{{ t(\"collectionsView.addFeedHint\") }}</p>\n <input\n ref=\"addInputEl\"\n v-model=\"addUrl\"\n type=\"url\"\n placeholder=\"https://example.com/feed.xml\"\n class=\"w-full rounded border border-slate-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500\"\n data-testid=\"feeds-add-url\"\n @keydown.enter=\"submitAdd\"\n @keydown.esc=\"closeAdd\"\n />\n <div class=\"mt-4 flex justify-end gap-2\">\n <button type=\"button\" class=\"h-8 px-3 rounded text-xs font-medium text-slate-600 hover:bg-slate-100\" @click=\"closeAdd\">\n {{ t(\"common.cancel\") }}\n </button>\n <button\n type=\"button\"\n class=\"h-8 px-3 rounded bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold disabled:opacity-50\"\n :disabled=\"!addUrl.trim()\"\n data-testid=\"feeds-add-submit\"\n @click=\"submitAdd\"\n >\n {{ t(\"common.add\") }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref } from \"vue\";\nimport { useCollectionI18n } from \"../lang\";\nimport { collectionUi } from \"../uiContext\";\nimport type { FeedSummary } from \"../../core/uiTypes\";\n\nconst { t } = useCollectionI18n();\n// Host couplings (list/refresh/navigate/chat/shortcuts/pin) via the binding.\nconst cui = collectionUi();\nconst { pinToggle, reconcileShortcuts } = cui;\n\nconst feeds = ref<FeedSummary[]>([]);\nconst loading = ref(true);\nconst loadError = ref<string | null>(null);\nconst refreshingSlug = ref<string | null>(null);\n/** Non-destructive banner for a per-feed Refresh that failed (the\n * endpoint reports retriever errors via `errors` even on HTTP 200). */\nconst refreshError = ref<string | null>(null);\n\n// Add-feed prompt state: the user types a URL, the agent does the rest.\nconst addOpen = ref(false);\nconst addUrl = ref(\"\");\nconst addInputEl = ref<HTMLInputElement | null>(null);\n\nasync function load(): Promise<void> {\n loading.value = true;\n loadError.value = null;\n const result = await cui.listFeeds();\n loading.value = false;\n if (!result.ok) {\n loadError.value = result.error;\n return;\n }\n feeds.value = result.data.feeds;\n // Bulk-reconcile pinned feed shortcuts against this authoritative list:\n // prune dead slugs, refresh stale titles/icons, self-heal the file.\n void reconcileShortcuts(\n \"feed\",\n feeds.value.map((feed) => ({ slug: feed.slug, title: feed.title, icon: feed.icon || \"dynamic_feed\" })),\n );\n}\n\nfunction open(slug: string): void {\n cui.gotoDetail(\"feed\", slug);\n}\n\nasync function refresh(slug: string): Promise<void> {\n refreshingSlug.value = slug;\n refreshError.value = null;\n const result = await cui.refreshCollection(slug);\n refreshingSlug.value = null;\n if (!result.ok) {\n refreshError.value = t(\"collectionsView.refreshFailed\", { error: result.error });\n return;\n }\n await load(); // reload to refresh lastFetchedAt\n // refreshOne reports retriever failures via `errors` even on HTTP 200.\n if (result.data.errors.length > 0) {\n refreshError.value = t(\"collectionsView.refreshFailed\", { error: result.data.errors.join(\"; \") });\n }\n}\n\nfunction startAddFeedChat(): void {\n addUrl.value = \"\";\n addOpen.value = true;\n void nextTick(() => addInputEl.value?.focus());\n}\n\nfunction closeAdd(): void {\n addOpen.value = false;\n}\n\n// Hand the URL to the agent with an autonomous seed prompt: it reads\n// config/helps/feeds.md, fetches the URL, infers the schema from the data,\n// and writes feeds/<slug>/schema.json — no follow-up questions, no tool.\nfunction submitAdd(): void {\n const url = addUrl.value.trim();\n if (!url) return;\n addOpen.value = false;\n cui.startChat(t(\"collectionsView.addFeedPrompt\", { url }), cui.personalRoleId);\n}\n\nfunction formatTime(iso: string): string {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? iso : date.toLocaleString();\n}\n\nonMounted(load);\n</script>\n"],"mappings":"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"}
|