@mulmoclaude/collection-plugin 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js CHANGED
@@ -1717,7 +1717,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
1717
1717
  const emit = __emit;
1718
1718
  const { t } = useCollectionI18n();
1719
1719
  /** The Uncategorized column uses the empty string as its sentinel value. */
1720
- const groupSpec = computed(() => props.schema.fields[props.groupField]);
1720
+ const groupSpec = computed(() => {
1721
+ const spec = props.schema.fields[props.groupField];
1722
+ return spec?.type === "enum" ? spec : void 0;
1723
+ });
1721
1724
  /** Declared enum values become columns in order, with a trailing
1722
1725
  * Uncategorized column for empty/unknown values (also a drop target that
1723
1726
  * clears the field). The Uncategorized column is omitted when the group
@@ -1764,7 +1767,10 @@ var CollectionKanbanView_default = /* @__PURE__ */ defineComponent({
1764
1767
  const value = String(raw);
1765
1768
  return (groupSpec.value?.values ?? []).includes(value) ? value : UNCATEGORIZED;
1766
1769
  }
1767
- const visibleItems = computed(() => groupSpec.value ? props.items.filter((item) => fieldVisible(groupSpec.value, item)) : []);
1770
+ const visibleItems = computed(() => {
1771
+ const spec = groupSpec.value;
1772
+ return spec ? props.items.filter((item) => fieldVisible(spec, item)) : [];
1773
+ });
1768
1774
  const itemsByColumnMap = computed(() => {
1769
1775
  const map = /* @__PURE__ */ new Map();
1770
1776
  for (const column of columns.value) map.set(column.value, []);
@@ -2291,7 +2297,8 @@ var CollectionRecordPanel_default = /* @__PURE__ */ defineComponent({
2291
2297
  /** Required flag for an embed's per-record picker — read off the storage
2292
2298
  * field it writes (`idField`), since the embed itself stores nothing. */
2293
2299
  function embedPickerRequired(field) {
2294
- const target = field.idField ? props.collection.schema.fields[field.idField] : void 0;
2300
+ const idField = field.type === "embed" ? field.idField : void 0;
2301
+ const target = idField ? props.collection.schema.fields[idField] : void 0;
2295
2302
  return target ? isFieldRequiredInUi(target) : false;
2296
2303
  }
2297
2304
  /** Tailwind fill/text/border classes tinting an enum `<select>` by its current
@@ -3043,8 +3050,11 @@ function formatCell(value, type) {
3043
3050
  }
3044
3051
  /** Resolve the ISO 4217 code for a money field: a per-record
3045
3052
  * `currencyField` (when present and non-blank) wins over the field's
3046
- * literal `currency`. */
3053
+ * literal `currency`. Only `money` / `derived` variants carry currency
3054
+ * keys; any other field resolves to undefined (the formatter's USD
3055
+ * fallback), as before. */
3047
3056
  function resolveCurrency(field, record) {
3057
+ if (field.type !== "money" && field.type !== "derived") return void 0;
3048
3058
  if (field.currencyField && record) {
3049
3059
  const code = record[field.currencyField];
3050
3060
  if (typeof code === "string" && code.trim().length > 0) return code;
@@ -3149,8 +3159,60 @@ function buildEmbedOptions(schema, items) {
3149
3159
  }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3150
3160
  }
3151
3161
  //#endregion
3152
- //#region src/vue/useCollectionRendering.ts
3153
- function useCollectionRendering(collection, locale) {
3162
+ //#region src/vue/useLinkedCollectionCaches.ts
3163
+ /** The de-duplicated ref + embed target slugs a schema links to. `allTargets`
3164
+ * is the union (each target fetched once even when both ref'd and embedded). */
3165
+ function linkedTargets(schema) {
3166
+ const refTargets = new Set(uniqueRefTargets(schema));
3167
+ const embedTargets = new Set(uniqueEmbedTargets(schema));
3168
+ return {
3169
+ refTargets,
3170
+ embedTargets,
3171
+ allTargets: [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])]
3172
+ };
3173
+ }
3174
+ /** Fan-out fetch that hydrates the linked-collection caches. Best-effort: a
3175
+ * target whose fetch *rejects* (vs. resolving `{ ok: false }`) is coerced to a
3176
+ * skip and must not abort the others. Returns null when a quicker subsequent
3177
+ * load has already moved on (stale-write guard) so the caller drops the write.
3178
+ * Pure + injectable (`fetchDetail`, `currentSlug`) so both paths are testable. */
3179
+ async function fetchLinkedCaches(targets, fetchDetail, currentSlug, expectedSlug) {
3180
+ const { refTargets, embedTargets, allTargets } = targets;
3181
+ const results = await Promise.all(allTargets.map(async (target) => {
3182
+ try {
3183
+ return {
3184
+ target,
3185
+ result: await fetchDetail(target)
3186
+ };
3187
+ } catch {
3188
+ return {
3189
+ target,
3190
+ result: { ok: false }
3191
+ };
3192
+ }
3193
+ }));
3194
+ if (currentSlug() !== expectedSlug) return null;
3195
+ const refCache = {};
3196
+ const refRecordCache = {};
3197
+ const embedCache = {};
3198
+ for (const { target, result } of results) {
3199
+ if (!result.ok) continue;
3200
+ if (refTargets.has(target)) {
3201
+ refCache[target] = buildRefDisplayMap(result.data);
3202
+ refRecordCache[target] = buildRefRecordMap(result.data);
3203
+ }
3204
+ if (embedTargets.has(target)) embedCache[target] = {
3205
+ schema: result.data.collection.schema,
3206
+ items: result.data.items
3207
+ };
3208
+ }
3209
+ return {
3210
+ refCache,
3211
+ refRecordCache,
3212
+ embedCache
3213
+ };
3214
+ }
3215
+ function useLinkedCollectionCaches(collection) {
3154
3216
  const refCache = ref({});
3155
3217
  const refRecordCache = ref({});
3156
3218
  const embedCache = ref({});
@@ -3160,161 +3222,139 @@ function useCollectionRendering(collection, locale) {
3160
3222
  embedCache.value = {};
3161
3223
  }
3162
3224
  async function loadLinkedCollections(schema, expectedSlug) {
3163
- const refTargets = new Set(uniqueRefTargets(schema));
3164
- const embedTargets = new Set(uniqueEmbedTargets(schema));
3165
- const allTargets = [.../* @__PURE__ */ new Set([...refTargets, ...embedTargets])];
3166
- if (allTargets.length === 0) return;
3225
+ const targets = linkedTargets(schema);
3226
+ if (targets.allTargets.length === 0) return;
3167
3227
  const binding = collectionUi();
3168
- const results = await Promise.all(allTargets.map(async (target) => {
3169
- try {
3170
- return {
3171
- target,
3172
- result: await binding.fetchCollectionDetail(target)
3173
- };
3174
- } catch {
3175
- return {
3176
- target,
3177
- result: { ok: false }
3178
- };
3179
- }
3180
- }));
3181
- if (collection.value?.slug !== expectedSlug) return;
3182
- const nextRef = {};
3183
- const nextRefRecords = {};
3184
- const nextEmbed = {};
3185
- for (const { target, result } of results) {
3186
- if (!result.ok) continue;
3187
- if (refTargets.has(target)) {
3188
- nextRef[target] = buildRefDisplayMap(result.data);
3189
- nextRefRecords[target] = buildRefRecordMap(result.data);
3190
- }
3191
- if (embedTargets.has(target)) nextEmbed[target] = {
3192
- schema: result.data.collection.schema,
3193
- items: result.data.items
3194
- };
3195
- }
3196
- refCache.value = nextRef;
3197
- refRecordCache.value = nextRefRecords;
3198
- embedCache.value = nextEmbed;
3199
- }
3200
- function refDisplay(targetSlug, itemSlug) {
3201
- const map = refCache.value[targetSlug];
3202
- return map && map[itemSlug] || itemSlug;
3203
- }
3204
- function refOptions(targetSlug) {
3205
- const map = refCache.value[targetSlug];
3206
- return map ? sortedRefOptions(map) : [];
3207
- }
3208
- /** Dropdown options for an `embed` field's per-record picker (`idField`):
3209
- * every record in the target collection, labelled by its name/title (or
3210
- * primary key). Built from `embedCache` so it works for embed targets
3211
- * that aren't also `ref` targets (the profile collection, say). */
3212
- function embedOptions(targetSlug) {
3213
- const data = embedCache.value[targetSlug];
3214
- return data ? buildEmbedOptions(data.schema, data.items) : [];
3215
- }
3216
- function resolveEmbed(field, record) {
3217
- if (field.type !== "embed" || !field.to) return {
3218
- schema: null,
3219
- item: null
3220
- };
3221
- const targetId = embedTargetId(field, record);
3222
- const data = targetId ? embedCache.value[field.to] : void 0;
3223
- if (!data) return {
3224
- schema: null,
3225
- item: null
3226
- };
3227
- const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3228
- return {
3229
- schema: data.schema,
3230
- item
3231
- };
3232
- }
3233
- function embedValue(field, value, record) {
3234
- if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale.value);
3235
- return detailText(value);
3236
- }
3237
- /** Build the read-only embed view-models for one record. A function of
3238
- * the open record (not a bare computed) because a per-record `idField`
3239
- * embed resolves a different target per row. */
3240
- function embedViewsFor(record) {
3241
- const out = {};
3242
- if (!collection.value) return out;
3243
- for (const [key, field] of Object.entries(collection.value.schema.fields)) {
3244
- if (field.type !== "embed") continue;
3245
- const { schema, item } = resolveEmbed(field, record);
3246
- const rows = [];
3247
- if (schema && item) for (const [subKey, subField] of Object.entries(schema.fields)) {
3248
- const value = item[subKey];
3249
- if (value === void 0 || value === null || value === "") continue;
3250
- rows.push({
3251
- key: subKey,
3252
- label: subField.label,
3253
- type: subField.type,
3254
- value,
3255
- display: embedValue(subField, value, item)
3256
- });
3257
- }
3258
- out[key] = {
3259
- found: Boolean(item),
3260
- rows,
3261
- targetSlug: field.to ?? "",
3262
- recordId: embedTargetId(field, record)
3263
- };
3264
- }
3265
- return out;
3266
- }
3267
- function currencySymbol(currency) {
3268
- return currencySymbolForLocale(currency, locale.value);
3269
- }
3270
- function artifactUrl(value) {
3271
- return collectionUi().fileAssetUrl(value);
3272
- }
3273
- function fileRoutePath(value) {
3274
- return collectionUi().fileRoutePath(value);
3275
- }
3276
- function formatSubCell(subField, value, record) {
3277
- if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale.value);
3278
- if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return refDisplay(subField.to, value);
3279
- return formatCell(value, subField.type);
3280
- }
3281
- const stepFor = stepForFieldType;
3282
- function evaluateDerivedAgainstItem(field, fieldKey, item) {
3283
- if (!field.formula || !collection.value) return null;
3284
- const result = deriveAll(collection.value.schema, item, refRecordCache.value)[fieldKey];
3285
- return typeof result === "number" && Number.isFinite(result) ? result : null;
3286
- }
3287
- function derivedDisplay(field, computedValue, record) {
3288
- if (computedValue === null || computedValue === void 0) return "—";
3289
- if (field.display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale.value);
3290
- return formatCell(computedValue, field.display ?? "number");
3228
+ const snapshot = await fetchLinkedCaches(targets, (slug) => binding.fetchCollectionDetail(slug), () => collection.value?.slug, expectedSlug);
3229
+ if (!snapshot) return;
3230
+ refCache.value = snapshot.refCache;
3231
+ refRecordCache.value = snapshot.refRecordCache;
3232
+ embedCache.value = snapshot.embedCache;
3291
3233
  }
3292
3234
  return {
3293
3235
  refCache,
3294
3236
  refRecordCache,
3295
3237
  embedCache,
3296
3238
  resetLinkedCaches,
3297
- loadLinkedCollections,
3298
- refDisplay,
3299
- refOptions,
3300
- embedOptions,
3301
- embedViewsFor,
3302
- resolveCurrency,
3303
- currencySymbol,
3304
- formatMoney,
3305
- formatCell,
3306
- detailText,
3307
- isExternalUrl,
3308
- artifactUrl,
3309
- fileRoutePath,
3310
- tableRows,
3311
- hasTableRows,
3312
- formatSubCell,
3313
- inputTypeFor,
3314
- stepFor,
3315
- deriveAll,
3316
- evaluateDerivedAgainstItem,
3317
- derivedDisplay
3239
+ loadLinkedCollections
3240
+ };
3241
+ }
3242
+ //#endregion
3243
+ //#region src/vue/useCollectionRendering.renderers.ts
3244
+ function lookupRefDisplay(refCache, targetSlug, itemSlug) {
3245
+ const map = refCache[targetSlug];
3246
+ return map && map[itemSlug] || itemSlug;
3247
+ }
3248
+ function refOptionsFor(refCache, targetSlug) {
3249
+ const map = refCache[targetSlug];
3250
+ return map ? sortedRefOptions(map) : [];
3251
+ }
3252
+ /** Dropdown options for an `embed` field's per-record picker (`idField`):
3253
+ * every record in the target collection, labelled by its name/title (or
3254
+ * primary key). Built from `embedCache` so it works for embed targets
3255
+ * that aren't also `ref` targets (the profile collection, say). */
3256
+ function embedOptionsFor(embedCache, targetSlug) {
3257
+ const data = embedCache[targetSlug];
3258
+ return data ? buildEmbedOptions(data.schema, data.items) : [];
3259
+ }
3260
+ function resolveEmbed(field, record, embedCache) {
3261
+ if (field.type !== "embed" || !field.to) return {
3262
+ schema: null,
3263
+ item: null
3264
+ };
3265
+ const targetId = embedTargetId(field, record);
3266
+ const data = targetId ? embedCache[field.to] : void 0;
3267
+ if (!data) return {
3268
+ schema: null,
3269
+ item: null
3270
+ };
3271
+ const item = data.items.find((entry) => String(entry[data.schema.primaryKey] ?? "") === targetId) ?? null;
3272
+ return {
3273
+ schema: data.schema,
3274
+ item
3275
+ };
3276
+ }
3277
+ function formatEmbedValue(field, value, record, locale) {
3278
+ if (field.type === "money") return formatMoney(value, resolveCurrency(field, record), locale);
3279
+ return detailText(value);
3280
+ }
3281
+ /** Build the read-only embed view-models for one record. A function of the
3282
+ * open record (not a bare computed) because a per-record `idField` embed
3283
+ * resolves a different target per row. `schema` is the OPEN collection's
3284
+ * schema (null when no collection is loaded → no views). */
3285
+ function buildEmbedViews(schema, embedCache, record, locale) {
3286
+ const out = {};
3287
+ if (!schema) return out;
3288
+ for (const [key, field] of Object.entries(schema.fields)) {
3289
+ if (field.type !== "embed") continue;
3290
+ const { schema: targetSchema, item } = resolveEmbed(field, record, embedCache);
3291
+ const rows = [];
3292
+ if (targetSchema && item) for (const [subKey, subField] of Object.entries(targetSchema.fields)) {
3293
+ const value = item[subKey];
3294
+ if (value === void 0 || value === null || value === "") continue;
3295
+ rows.push({
3296
+ key: subKey,
3297
+ label: subField.label,
3298
+ type: subField.type,
3299
+ value,
3300
+ display: formatEmbedValue(subField, value, item, locale)
3301
+ });
3302
+ }
3303
+ out[key] = {
3304
+ found: Boolean(item),
3305
+ rows,
3306
+ targetSlug: field.to ?? "",
3307
+ recordId: embedTargetId(field, record)
3308
+ };
3309
+ }
3310
+ return out;
3311
+ }
3312
+ function renderSubCell(subField, value, record, refCache, locale) {
3313
+ if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale);
3314
+ if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return lookupRefDisplay(refCache, subField.to, value);
3315
+ return formatCell(value, subField.type);
3316
+ }
3317
+ function evaluateDerived(field, fieldKey, item, schema, refRecords) {
3318
+ if (field.type !== "derived" || !schema) return null;
3319
+ const result = deriveAll(schema, item, refRecords)[fieldKey];
3320
+ return typeof result === "number" && Number.isFinite(result) ? result : null;
3321
+ }
3322
+ function renderDerived(field, computedValue, record, locale) {
3323
+ if (computedValue === null || computedValue === void 0) return "—";
3324
+ const display = field.type === "derived" ? field.display : void 0;
3325
+ if (display === "money") return formatMoney(computedValue, resolveCurrency(field, record), locale);
3326
+ return formatCell(computedValue, display ?? "number");
3327
+ }
3328
+ //#endregion
3329
+ //#region src/vue/useCollectionRendering.ts
3330
+ var STATELESS_RENDERERS = {
3331
+ resolveCurrency,
3332
+ formatMoney,
3333
+ formatCell,
3334
+ detailText,
3335
+ isExternalUrl,
3336
+ tableRows,
3337
+ hasTableRows,
3338
+ inputTypeFor,
3339
+ stepFor: stepForFieldType,
3340
+ deriveAll
3341
+ };
3342
+ function useCollectionRendering(collection, locale) {
3343
+ const caches = useLinkedCollectionCaches(collection);
3344
+ const { refCache, refRecordCache, embedCache } = caches;
3345
+ return {
3346
+ ...caches,
3347
+ ...STATELESS_RENDERERS,
3348
+ refDisplay: (targetSlug, itemSlug) => lookupRefDisplay(refCache.value, targetSlug, itemSlug),
3349
+ refOptions: (targetSlug) => refOptionsFor(refCache.value, targetSlug),
3350
+ embedOptions: (targetSlug) => embedOptionsFor(embedCache.value, targetSlug),
3351
+ embedViewsFor: (record) => buildEmbedViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
3352
+ currencySymbol: (currency) => currencySymbolForLocale(currency, locale.value),
3353
+ artifactUrl: (value) => collectionUi().fileAssetUrl(value),
3354
+ fileRoutePath: (value) => collectionUi().fileRoutePath(value),
3355
+ formatSubCell: (subField, value, record) => renderSubCell(subField, value, record, refCache.value, locale.value),
3356
+ evaluateDerivedAgainstItem: (field, fieldKey, item) => evaluateDerived(field, fieldKey, item, collection.value?.schema ?? null, refRecordCache.value),
3357
+ derivedDisplay: (field, computedValue, record) => renderDerived(field, computedValue, record, locale.value)
3318
3358
  };
3319
3359
  }
3320
3360
  //#endregion
@@ -3805,12 +3845,12 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3805
3845
  return scalarSortValue(field, item[key]);
3806
3846
  }
3807
3847
  /** Derived rows sort by their display type: money/number → numeric,
3808
- * date/datetime → epoch, anything else → the enriched value as a string. */
3848
+ * date → epoch, anything else → the enriched value as a string. */
3809
3849
  function derivedSortValue(field, key, item) {
3810
- const { display } = field;
3850
+ const display = field.type === "derived" ? field.display : void 0;
3811
3851
  if (display === void 0 || display === "number" || display === "money") return numericSortValue(evaluateDerivedAgainstItem(field, key, item));
3812
3852
  const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;
3813
- if (display === "date" || display === "datetime") return dateSortValue(enriched[key]);
3853
+ if (display === "date") return dateSortValue(enriched[key]);
3814
3854
  return stringSortValue(enriched[key]);
3815
3855
  }
3816
3856
  const sortedItems = computed(() => {
@@ -3988,10 +4028,12 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
3988
4028
  function buildChatSeed(slug, message, itemId) {
3989
4029
  const current = collection.value;
3990
4030
  if (current?.source !== "feed") return itemId ? `/${slug} id=${itemId} ${message}` : `/${slug} ${message}`;
4031
+ const dataPath = current.schema.dataPath ?? `data/feeds/${slug}`;
4032
+ const scoped = itemId ? `(for record \`${itemId}\`) ${message}` : message;
3991
4033
  return t("collectionsView.feedChatSeed", {
3992
4034
  slug,
3993
- dataPath: current.schema.dataPath ?? `data/feeds/${slug}`,
3994
- message: itemId ? `(for record \`${itemId}\`) ${message}` : message
4035
+ dataPath,
4036
+ message: scoped
3995
4037
  });
3996
4038
  }
3997
4039
  /** Start a new general-role chat seeded from the current view. */
@@ -4066,7 +4108,10 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4066
4108
  enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
4067
4109
  await render.loadLinkedCollections(result.data.collection.schema, slug);
4068
4110
  if (activeSlug.value !== slug) return;
4069
- if (viewing.value) viewing.value = findItemById(String(viewing.value[result.data.collection.schema.primaryKey] ?? "")) ?? null;
4111
+ if (viewing.value) {
4112
+ const openId = String(viewing.value[result.data.collection.schema.primaryKey] ?? "");
4113
+ viewing.value = findItemById(openId) ?? null;
4114
+ }
4070
4115
  }
4071
4116
  function maybeAutoRefreshFeed(slug) {
4072
4117
  if (embedded.value) return;
@@ -4244,7 +4289,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4244
4289
  }
4245
4290
  /** Select a custom view by id (builds the `custom:<id>` mode key). */
4246
4291
  function setCustomView(viewId) {
4247
- view.value = `custom:${viewId}`;
4292
+ const mode = `custom:${viewId}`;
4293
+ view.value = mode;
4248
4294
  }
4249
4295
  /** A short, slug-safe id not already used by a loaded record. Collisions
4250
4296
  * are astronomically unlikely (32 bits), but we still re-roll a few
@@ -4402,7 +4448,8 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4402
4448
  viewing.value = null;
4403
4449
  return;
4404
4450
  }
4405
- viewing.value = findItemById(selected) ?? null;
4451
+ const match = findItemById(selected) ?? null;
4452
+ viewing.value = match;
4406
4453
  }
4407
4454
  /** Title for the open-mode header: the record's primary-key value
4408
4455
  * (e.g. `INV-2026-0001`), falling back to the collection title.
@@ -4496,19 +4543,17 @@ var CollectionView_default = /* @__PURE__ */ defineComponent({
4496
4543
  /** Whether a `toggle` field reads as checked: its projected enum field
4497
4544
  * currently equals `onValue`. The toggle stores nothing itself. */
4498
4545
  function toggleChecked(item, field) {
4499
- return field.field !== void 0 && String(item[field.field] ?? "") === field.onValue;
4546
+ return field.type === "toggle" && String(item[field.field] ?? "") === field.onValue;
4500
4547
  }
4501
4548
  /** Flip a `toggle`: write the projected enum field to `offValue` when
4502
4549
  * currently checked, else `onValue`. Reuses the inline-edit PUT path
4503
4550
  * (optimistic + rollback) — the toggle has no value of its own. */
4504
4551
  function commitToggle(item, field) {
4552
+ if (field.type !== "toggle" || !collection.value) return;
4505
4553
  const targetKey = field.field;
4506
- if (!targetKey || !collection.value) return;
4507
4554
  const enumField = collection.value.schema.fields[targetKey];
4508
4555
  if (!enumField) return;
4509
- const next = toggleChecked(item, field) ? field.offValue : field.onValue;
4510
- if (next === void 0) return;
4511
- commitInlineEdit(item, targetKey, enumField, next);
4556
+ commitInlineEdit(item, targetKey, enumField, toggleChecked(item, field) ? field.offValue : field.onValue);
4512
4557
  }
4513
4558
  async function confirmDelete(item) {
4514
4559
  if (!collection.value) return;