@mulmoclaude/collection-plugin 0.2.1 → 0.3.0
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/calendarGrid-DZeLwBj9.cjs +778 -0
- package/dist/calendarGrid-DZeLwBj9.cjs.map +1 -0
- package/dist/calendarGrid-ggmFTZju.js +587 -0
- package/dist/calendarGrid-ggmFTZju.js.map +1 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/core/actionVisible.d.ts +1 -0
- package/dist/core/actionVisible.d.ts.map +1 -0
- package/dist/core/calendarGrid.d.ts +1 -0
- package/dist/core/calendarGrid.d.ts.map +1 -0
- package/dist/core/deriveAll.d.ts +2 -1
- package/dist/core/deriveAll.d.ts.map +1 -0
- package/dist/core/derivedFormula.d.ts +1 -0
- package/dist/core/derivedFormula.d.ts.map +1 -0
- package/dist/core/draft.d.ts +3 -2
- package/dist/core/draft.d.ts.map +1 -0
- package/dist/core/enumColors.d.ts +2 -1
- package/dist/core/enumColors.d.ts.map +1 -0
- package/dist/core/itemLabel.d.ts +2 -1
- package/dist/core/itemLabel.d.ts.map +1 -0
- package/dist/core/presentCollection.d.ts +2 -1
- package/dist/core/presentCollection.d.ts.map +1 -0
- package/dist/core/schema.d.ts +1 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/sortItems.d.ts +2 -1
- package/dist/core/sortItems.d.ts.map +1 -0
- package/dist/core/uiTypes.d.ts +29 -1
- package/dist/core/uiTypes.d.ts.map +1 -0
- package/dist/{deriveAll-B5spcUIb.js → deriveAll-BHcs1erT.js} +2 -20
- package/dist/deriveAll-BHcs1erT.js.map +1 -0
- package/dist/{deriveAll-Do0v6Xjb.cjs → deriveAll-BJ0Lvm1Q.cjs} +1 -31
- package/dist/deriveAll-BJ0Lvm1Q.cjs.map +1 -0
- package/dist/index.cjs +37 -619
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -585
- package/dist/index.js.map +1 -1
- package/dist/schema-DyfSfVzh.js +21 -0
- package/dist/schema-DyfSfVzh.js.map +1 -0
- package/dist/schema-LrmrgW12.cjs +32 -0
- package/dist/schema-LrmrgW12.cjs.map +1 -0
- package/dist/server/atomic.d.ts +1 -0
- package/dist/server/atomic.d.ts.map +1 -0
- package/dist/server/delete.d.ts +2 -1
- package/dist/server/delete.d.ts.map +1 -0
- package/dist/server/derive.d.ts +4 -3
- package/dist/server/derive.d.ts.map +1 -0
- package/dist/server/discoveredCollection.d.ts +2 -1
- package/dist/server/discoveredCollection.d.ts.map +1 -0
- package/dist/server/discovery.d.ts +4 -3
- package/dist/server/discovery.d.ts.map +1 -0
- package/dist/server/host.d.ts +4 -1
- package/dist/server/host.d.ts.map +1 -0
- package/dist/server/index.d.ts +12 -11
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/io.d.ts +3 -2
- package/dist/server/io.d.ts.map +1 -0
- package/dist/server/paths.d.ts +1 -0
- package/dist/server/paths.d.ts.map +1 -0
- package/dist/server/spawn.d.ts +3 -2
- package/dist/server/spawn.d.ts.map +1 -0
- package/dist/server/templatePath.d.ts +1 -0
- package/dist/server/templatePath.d.ts.map +1 -0
- package/dist/server/util.d.ts +1 -0
- package/dist/server/util.d.ts.map +1 -0
- package/dist/server/validate.d.ts +3 -2
- package/dist/server/validate.d.ts.map +1 -0
- package/dist/server/views.d.ts +3 -2
- package/dist/server/views.d.ts.map +1 -0
- package/dist/server.cjs +12 -29
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +8 -4
- package/dist/server.js.map +1 -1
- package/dist/style.css +2121 -0
- package/dist/vue/components/CollectionCalendarView.vue.d.ts +27 -0
- package/dist/vue/components/CollectionCalendarView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionDayView.vue.d.ts +40 -0
- package/dist/vue/components/CollectionDayView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionEmbedView.vue.d.ts +9 -0
- package/dist/vue/components/CollectionEmbedView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionKanbanView.vue.d.ts +23 -0
- package/dist/vue/components/CollectionKanbanView.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionRecordModal.vue.d.ts +18 -0
- package/dist/vue/components/CollectionRecordModal.vue.d.ts.map +1 -0
- package/dist/vue/components/CollectionRecordPanel.vue.d.ts +46 -0
- package/dist/vue/components/CollectionRecordPanel.vue.d.ts.map +1 -0
- package/dist/vue/index.d.ts +10 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/uiContext.d.ts +30 -0
- package/dist/vue/uiContext.d.ts.map +1 -0
- package/dist/vue/useCollectionRendering.d.ts +30 -0
- package/dist/vue/useCollectionRendering.d.ts.map +1 -0
- package/dist/vue.cjs +1624 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.js +1613 -0
- package/dist/vue.js.map +1 -0
- package/package.json +21 -6
- package/dist/deriveAll-B5spcUIb.js.map +0 -1
- package/dist/deriveAll-Do0v6Xjb.cjs.map +0 -1
package/dist/vue.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.cjs","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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"}
|