@mulmoclaude/collection-plugin 0.7.6 → 0.11.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/vue.cjs CHANGED
@@ -1233,19 +1233,351 @@ var CollectionRecordModal_default = /* @__PURE__ */ (0, vue.defineComponent)({
1233
1233
  }
1234
1234
  });
1235
1235
  //#endregion
1236
+ //#region src/vue/useCollectionRendering.helpers.ts
1237
+ var EM_DASH = "—";
1238
+ var DEFAULT_CURRENCY = "USD";
1239
+ var MARKDOWN_CELL_PREVIEW_MAX = 80;
1240
+ function stepForFieldType(type) {
1241
+ if (type === "money") return "0.01";
1242
+ if (type === "number") return "any";
1243
+ }
1244
+ function inputTypeFor(type) {
1245
+ if (type === "email") return "email";
1246
+ if (type === "number") return "number";
1247
+ if (type === "money") return "number";
1248
+ if (type === "date") return "date";
1249
+ if (type === "datetime") return "datetime-local";
1250
+ return "text";
1251
+ }
1252
+ function isExternalUrl(value) {
1253
+ return typeof value === "string" && /^https?:\/\//i.test(value);
1254
+ }
1255
+ function detailText(value) {
1256
+ if (value === void 0 || value === null || value === "") return EM_DASH;
1257
+ return String(value);
1258
+ }
1259
+ function formatCell(value, type) {
1260
+ if (value === void 0 || value === null || value === "") return EM_DASH;
1261
+ if (type === "markdown" && typeof value === "string") return value.length > MARKDOWN_CELL_PREVIEW_MAX ? `${value.slice(0, MARKDOWN_CELL_PREVIEW_MAX)}…` : value;
1262
+ if (typeof value === "string" || typeof value === "number") return String(value);
1263
+ return JSON.stringify(value);
1264
+ }
1265
+ /** Resolve the ISO 4217 code for a money field: a per-record
1266
+ * `currencyField` (when present and non-blank) wins over the field's
1267
+ * literal `currency`. Only `money` / `derived` variants carry currency
1268
+ * keys; any other field resolves to undefined (the formatter's USD
1269
+ * fallback), as before. */
1270
+ function resolveCurrency(field, record) {
1271
+ if (field.type !== "money" && field.type !== "derived") return void 0;
1272
+ if (field.currencyField && record) {
1273
+ const code = record[field.currencyField];
1274
+ if (typeof code === "string" && code.trim().length > 0) return code;
1275
+ }
1276
+ return field.currency;
1277
+ }
1278
+ function formatMoney(value, currency, displayLocale) {
1279
+ if (value === void 0 || value === "") return EM_DASH;
1280
+ const amount = typeof value === "number" ? value : Number(value);
1281
+ if (!Number.isFinite(amount)) return String(value);
1282
+ const currencyCode = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
1283
+ try {
1284
+ return new Intl.NumberFormat(displayLocale, {
1285
+ style: "currency",
1286
+ currency: currencyCode
1287
+ }).format(amount);
1288
+ } catch {
1289
+ return String(amount);
1290
+ }
1291
+ }
1292
+ function currencySymbolForLocale(currency, locale) {
1293
+ const code = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
1294
+ try {
1295
+ return new Intl.NumberFormat(locale, {
1296
+ style: "currency",
1297
+ currency: code
1298
+ }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
1299
+ } catch {
1300
+ return code;
1301
+ }
1302
+ }
1303
+ function tableRows(value) {
1304
+ if (!Array.isArray(value)) return [];
1305
+ return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
1306
+ }
1307
+ function hasTableRows(value) {
1308
+ return tableRows(value).length > 0;
1309
+ }
1310
+ /** Pick the field used to label a referenced/embedded record: prefer a
1311
+ * `name` field, then `title`, else fall back to the primary key. */
1312
+ function displayFieldFor(fields, primaryKey) {
1313
+ if ("name" in fields) return "name";
1314
+ if ("title" in fields) return "title";
1315
+ return primaryKey;
1316
+ }
1317
+ function uniqueRefTargets(schema) {
1318
+ const targets = /* @__PURE__ */ new Set();
1319
+ const walk = (fields) => {
1320
+ for (const field of Object.values(fields)) {
1321
+ if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
1322
+ if (field.type === "table" && field.of) walk(field.of);
1323
+ }
1324
+ };
1325
+ walk(schema.fields);
1326
+ return [...targets];
1327
+ }
1328
+ function uniqueEmbedTargets(schema) {
1329
+ const targets = /* @__PURE__ */ new Set();
1330
+ for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
1331
+ return [...targets];
1332
+ }
1333
+ /** Slugs of every SOURCE collection a `backlinks` or `rollup` field
1334
+ * reverses over (the two share one load). Top-level only, like `embed`
1335
+ * (the schema rejects both inside a table's `of`). Mirrors the server's
1336
+ * `uniqueBacklinkSources`. */
1337
+ function uniqueBacklinkSources(schema) {
1338
+ const sources = /* @__PURE__ */ new Set();
1339
+ for (const field of Object.values(schema.fields)) if ((field.type === "backlinks" || field.type === "rollup") && field.from.length > 0) sources.add(field.from);
1340
+ return [...sources];
1341
+ }
1342
+ function buildRefDisplayMap(detail) {
1343
+ const { fields, primaryKey } = detail.collection.schema;
1344
+ const displayField = displayFieldFor(fields, primaryKey);
1345
+ const map = {};
1346
+ for (const item of detail.items) {
1347
+ const slugRaw = item[primaryKey];
1348
+ if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
1349
+ const displayRaw = item[displayField];
1350
+ map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
1351
+ }
1352
+ return map;
1353
+ }
1354
+ /** Index DERIVED records by primary key — the client mirror of the
1355
+ * server's `loadTarget` indexing: string non-empty ids only, one record
1356
+ * per id (a duplicate keeps the LAST record in its FIRST position —
1357
+ * plain-object key semantics). Shared by the ref-record cache and the
1358
+ * backlinks view so every client consumer agrees with server enrichment
1359
+ * on which source records exist. */
1360
+ function derivedRecordsById(schema, items) {
1361
+ const map = {};
1362
+ for (const item of items) {
1363
+ const slugRaw = item[schema.primaryKey];
1364
+ if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, {});
1365
+ }
1366
+ return map;
1367
+ }
1368
+ function buildRefRecordMap(detail) {
1369
+ return derivedRecordsById(detail.collection.schema, detail.items);
1370
+ }
1371
+ function sortedRefOptions(map) {
1372
+ return Object.entries(map).map(([slug, display]) => ({
1373
+ slug,
1374
+ display
1375
+ })).sort((left, right) => left.display.localeCompare(right.display));
1376
+ }
1377
+ /** Dropdown options for an `embed` field's per-record picker: every
1378
+ * record in the target collection, labelled by its name/title (or
1379
+ * primary key), skipping records without a slug and sorted by label. */
1380
+ function buildEmbedOptions(schema, items) {
1381
+ const { fields, primaryKey } = schema;
1382
+ const displayField = displayFieldFor(fields, primaryKey);
1383
+ return items.map((item) => {
1384
+ const slug = String(item[primaryKey] ?? "");
1385
+ const labelRaw = item[displayField];
1386
+ return {
1387
+ slug,
1388
+ display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
1389
+ };
1390
+ }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
1391
+ }
1392
+ //#endregion
1393
+ //#region src/vue/components/CollectionMutateParamsModal.vue?vue&type=script&setup=true&lang.ts
1394
+ var _hoisted_1$16 = { class: "flex items-center justify-between px-6 pt-5 pb-3" };
1395
+ var _hoisted_2$15 = { class: "text-sm font-bold text-slate-800 flex items-center gap-1.5" };
1396
+ var _hoisted_3$15 = {
1397
+ key: 0,
1398
+ class: "material-icons text-base text-indigo-600"
1399
+ };
1400
+ var _hoisted_4$15 = ["aria-label"];
1401
+ var _hoisted_5$14 = { class: "flex flex-col gap-4 px-6 pb-2" };
1402
+ var _hoisted_6$13 = ["for"];
1403
+ var _hoisted_7$11 = {
1404
+ key: 0,
1405
+ class: "text-rose-500 font-bold"
1406
+ };
1407
+ var _hoisted_8$11 = {
1408
+ key: 0,
1409
+ class: "inline-flex items-center gap-2.5 cursor-pointer select-none"
1410
+ };
1411
+ var _hoisted_9$11 = [
1412
+ "id",
1413
+ "onUpdate:modelValue",
1414
+ "data-testid",
1415
+ "onChange"
1416
+ ];
1417
+ var _hoisted_10$11 = [
1418
+ "id",
1419
+ "onUpdate:modelValue",
1420
+ "required",
1421
+ "data-testid"
1422
+ ];
1423
+ var _hoisted_11$11 = { value: "" };
1424
+ var _hoisted_12$10 = ["value"];
1425
+ var _hoisted_13$9 = [
1426
+ "id",
1427
+ "onUpdate:modelValue",
1428
+ "required",
1429
+ "data-testid"
1430
+ ];
1431
+ var _hoisted_14$8 = [
1432
+ "id",
1433
+ "onUpdate:modelValue",
1434
+ "type",
1435
+ "step",
1436
+ "required",
1437
+ "data-testid"
1438
+ ];
1439
+ var _hoisted_15$8 = {
1440
+ key: 0,
1441
+ class: "text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl",
1442
+ "data-testid": "collections-mutate-error"
1443
+ };
1444
+ var _hoisted_16$7 = { class: "flex items-center justify-end gap-2 px-6 py-4" };
1445
+ var _hoisted_17$7 = ["disabled"];
1446
+ var _hoisted_18$6 = {
1447
+ key: 0,
1448
+ class: "material-icons text-sm animate-spin"
1449
+ };
1450
+ //#endregion
1451
+ //#region src/vue/components/CollectionMutateParamsModal.vue
1452
+ var CollectionMutateParamsModal_default = /* @__PURE__ */ (0, vue.defineComponent)({
1453
+ __name: "CollectionMutateParamsModal",
1454
+ props: {
1455
+ action: {},
1456
+ pending: { type: Boolean },
1457
+ error: {}
1458
+ },
1459
+ emits: ["close", "submit"],
1460
+ setup(__props, { emit: __emit }) {
1461
+ const props = __props;
1462
+ const emit = __emit;
1463
+ const { t } = useCollectionI18n();
1464
+ const text = (0, vue.reactive)({});
1465
+ const bool = (0, vue.reactive)({});
1466
+ const boolTouched = (0, vue.reactive)({});
1467
+ for (const [key, spec] of Object.entries(props.action.params ?? {})) if (spec.type === "boolean") bool[key] = false;
1468
+ else text[key] = "";
1469
+ /** Convert the draft to the submitted params: numbers parsed, booleans
1470
+ * included only when touched or required, empty optionals OMITTED (the
1471
+ * server treats an absent param as "don't write" for `$params` refs —
1472
+ * merge semantics). */
1473
+ function submit() {
1474
+ const params = {};
1475
+ for (const [key, spec] of Object.entries(props.action.params ?? {})) {
1476
+ if (spec.type === "boolean") {
1477
+ if (boolTouched[key] || spec.required) params[key] = bool[key] === true;
1478
+ continue;
1479
+ }
1480
+ const raw = (text[key] ?? "").trim();
1481
+ if (raw === "") continue;
1482
+ if (spec.type === "number" || spec.type === "money") {
1483
+ const num = Number(raw);
1484
+ params[key] = Number.isFinite(num) ? num : raw;
1485
+ continue;
1486
+ }
1487
+ params[key] = raw;
1488
+ }
1489
+ emit("submit", params);
1490
+ }
1491
+ return (_ctx, _cache) => {
1492
+ return (0, vue.openBlock)(), (0, vue.createBlock)(CollectionRecordModal_default, { onClose: _cache[2] || (_cache[2] = ($event) => emit("close")) }, {
1493
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("form", {
1494
+ class: "flex flex-col overflow-y-auto",
1495
+ "data-testid": "collections-mutate-modal",
1496
+ onSubmit: (0, vue.withModifiers)(submit, ["prevent"])
1497
+ }, [
1498
+ (0, vue.createElementVNode)("div", _hoisted_1$16, [(0, vue.createElementVNode)("h2", _hoisted_2$15, [__props.action.icon ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$15, (0, vue.toDisplayString)(__props.action.icon), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.action.label), 1)]), (0, vue.createElementVNode)("button", {
1499
+ type: "button",
1500
+ class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors",
1501
+ "aria-label": (0, vue.unref)(t)("common.close"),
1502
+ "data-testid": "collections-mutate-close",
1503
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
1504
+ }, [..._cache[3] || (_cache[3] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$15)]),
1505
+ (0, vue.createElementVNode)("div", _hoisted_5$14, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.action.params, (spec, key) => {
1506
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1507
+ key,
1508
+ class: "flex flex-col gap-1.5"
1509
+ }, [(0, vue.createElementVNode)("label", {
1510
+ class: "text-[10px] font-bold text-slate-400 uppercase tracking-wider",
1511
+ for: `collections-mutate-${key}`
1512
+ }, [(0, vue.createTextVNode)((0, vue.toDisplayString)(spec.label) + " ", 1), spec.required ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_7$11, "*")) : (0, vue.createCommentVNode)("", true)], 8, _hoisted_6$13), spec.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("label", _hoisted_8$11, [(0, vue.withDirectives)((0, vue.createElementVNode)("input", {
1513
+ id: `collections-mutate-${key}`,
1514
+ "onUpdate:modelValue": ($event) => bool[key] = $event,
1515
+ type: "checkbox",
1516
+ class: "h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer",
1517
+ "data-testid": `collections-mutate-input-${key}`,
1518
+ onChange: ($event) => boolTouched[String(key)] = true
1519
+ }, null, 40, _hoisted_9$11), [[vue.vModelCheckbox, bool[key]]]), (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["text-xs font-semibold", bool[key] ? "text-indigo-600" : "text-slate-500"]) }, (0, vue.toDisplayString)(bool[key] ? (0, vue.unref)(t)("common.yes") : (0, vue.unref)(t)("common.no")), 3)])) : spec.type === "enum" ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
1520
+ key: 1,
1521
+ id: `collections-mutate-${key}`,
1522
+ "onUpdate:modelValue": ($event) => text[key] = $event,
1523
+ required: spec.required,
1524
+ class: "w-full rounded-xl border border-slate-200 px-3 py-2 text-xs bg-slate-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",
1525
+ "data-testid": `collections-mutate-input-${key}`
1526
+ }, [(0, vue.createElementVNode)("option", _hoisted_11$11, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(spec.values, (value) => {
1527
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
1528
+ key: value,
1529
+ value
1530
+ }, (0, vue.toDisplayString)(value), 9, _hoisted_12$10);
1531
+ }), 128))], 8, _hoisted_10$11)), [[vue.vModelSelect, text[key]]]) : spec.type === "text" || spec.type === "markdown" ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("textarea", {
1532
+ key: 2,
1533
+ id: `collections-mutate-${key}`,
1534
+ "onUpdate:modelValue": ($event) => text[key] = $event,
1535
+ required: spec.required,
1536
+ rows: "3",
1537
+ 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",
1538
+ "data-testid": `collections-mutate-input-${key}`
1539
+ }, null, 8, _hoisted_13$9)), [[vue.vModelText, text[key]]]) : (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
1540
+ key: 3,
1541
+ id: `collections-mutate-${key}`,
1542
+ "onUpdate:modelValue": ($event) => text[key] = $event,
1543
+ type: (0, vue.unref)(inputTypeFor)(spec.type),
1544
+ step: (0, vue.unref)(stepForFieldType)(spec.type),
1545
+ required: spec.required,
1546
+ 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",
1547
+ "data-testid": `collections-mutate-input-${key}`
1548
+ }, null, 8, _hoisted_14$8)), [[vue.vModelDynamic, text[key]]])]);
1549
+ }), 128)), __props.error ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_15$8, (0, vue.toDisplayString)(__props.error), 1)) : (0, vue.createCommentVNode)("", true)]),
1550
+ (0, vue.createElementVNode)("div", _hoisted_16$7, [(0, vue.createElementVNode)("button", {
1551
+ type: "button",
1552
+ class: "h-8 px-3 rounded border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 font-bold text-xs transition-colors",
1553
+ "data-testid": "collections-mutate-cancel",
1554
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("close"))
1555
+ }, (0, vue.toDisplayString)((0, vue.unref)(t)("common.cancel")), 1), (0, vue.createElementVNode)("button", {
1556
+ type: "submit",
1557
+ class: "h-8 px-3 rounded bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-xs transition-colors shadow-sm disabled:opacity-50 flex items-center gap-1",
1558
+ disabled: __props.pending,
1559
+ "data-testid": "collections-mutate-submit"
1560
+ }, [__props.pending ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_18$6, "progress_activity")) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(__props.action.label), 1)], 8, _hoisted_17$7)])
1561
+ ], 32)]),
1562
+ _: 1
1563
+ });
1564
+ };
1565
+ }
1566
+ });
1567
+ //#endregion
1236
1568
  //#region src/vue/components/CollectionCalendarView.vue?vue&type=script&setup=true&lang.ts
1237
- var _hoisted_1$14 = {
1569
+ var _hoisted_1$15 = {
1238
1570
  class: "flex flex-col gap-3",
1239
1571
  "data-testid": "collection-calendar"
1240
1572
  };
1241
- var _hoisted_2$13 = { class: "flex items-center gap-2" };
1242
- var _hoisted_3$13 = ["aria-label"];
1243
- var _hoisted_4$13 = ["aria-label"];
1244
- var _hoisted_5$12 = {
1573
+ var _hoisted_2$14 = { class: "flex items-center gap-2" };
1574
+ var _hoisted_3$14 = ["aria-label"];
1575
+ var _hoisted_4$14 = ["aria-label"];
1576
+ var _hoisted_5$13 = {
1245
1577
  class: "text-sm font-bold text-slate-800 flex-1",
1246
1578
  "data-testid": "collection-calendar-month"
1247
1579
  };
1248
- var _hoisted_6$11 = { class: "grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none" };
1580
+ var _hoisted_6$12 = { class: "grid grid-cols-7 gap-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider select-none" };
1249
1581
  var _hoisted_7$10 = { class: "grid grid-cols-7 gap-1" };
1250
1582
  var _hoisted_8$10 = [
1251
1583
  "aria-label",
@@ -1375,23 +1707,23 @@ var CollectionCalendarView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1375
1707
  viewMonth.value = now.getMonth() + 1;
1376
1708
  }
1377
1709
  return (_ctx, _cache) => {
1378
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$14, [
1379
- (0, vue.createElementVNode)("div", _hoisted_2$13, [
1710
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$15, [
1711
+ (0, vue.createElementVNode)("div", _hoisted_2$14, [
1380
1712
  (0, vue.createElementVNode)("button", {
1381
1713
  type: "button",
1382
1714
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
1383
1715
  "aria-label": (0, vue.unref)(t)("collectionsView.calendarPrevMonth"),
1384
1716
  "data-testid": "collection-calendar-prev",
1385
1717
  onClick: _cache[0] || (_cache[0] = ($event) => stepMonth(-1))
1386
- }, [..._cache[2] || (_cache[2] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "chevron_left", -1)])], 8, _hoisted_3$13),
1718
+ }, [..._cache[2] || (_cache[2] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "chevron_left", -1)])], 8, _hoisted_3$14),
1387
1719
  (0, vue.createElementVNode)("button", {
1388
1720
  type: "button",
1389
1721
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
1390
1722
  "aria-label": (0, vue.unref)(t)("collectionsView.calendarNextMonth"),
1391
1723
  "data-testid": "collection-calendar-next",
1392
1724
  onClick: _cache[1] || (_cache[1] = ($event) => stepMonth(1))
1393
- }, [..._cache[3] || (_cache[3] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "chevron_right", -1)])], 8, _hoisted_4$13),
1394
- (0, vue.createElementVNode)("h3", _hoisted_5$12, (0, vue.toDisplayString)(monthLabel.value), 1),
1725
+ }, [..._cache[3] || (_cache[3] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "chevron_right", -1)])], 8, _hoisted_4$14),
1726
+ (0, vue.createElementVNode)("h3", _hoisted_5$13, (0, vue.toDisplayString)(monthLabel.value), 1),
1395
1727
  (0, vue.createElementVNode)("button", {
1396
1728
  type: "button",
1397
1729
  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",
@@ -1399,7 +1731,7 @@ var CollectionCalendarView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1399
1731
  onClick: goToday
1400
1732
  }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.calendarToday")), 1)
1401
1733
  ]),
1402
- (0, vue.createElementVNode)("div", _hoisted_6$11, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(weekdayLabels.value, (label, idx) => {
1734
+ (0, vue.createElementVNode)("div", _hoisted_6$12, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(weekdayLabels.value, (label, idx) => {
1403
1735
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1404
1736
  key: idx,
1405
1737
  class: "px-1 py-1 text-center"
@@ -1440,18 +1772,18 @@ var CollectionCalendarView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1440
1772
  });
1441
1773
  //#endregion
1442
1774
  //#region src/vue/components/CollectionDayView.vue?vue&type=script&setup=true&lang.ts
1443
- var _hoisted_1$13 = { class: "flex items-center gap-2 border-b border-slate-200 px-4 py-3" };
1444
- var _hoisted_2$12 = {
1775
+ var _hoisted_1$14 = { class: "flex items-center gap-2 border-b border-slate-200 px-4 py-3" };
1776
+ var _hoisted_2$13 = {
1445
1777
  class: "flex-1 text-sm font-bold text-slate-800",
1446
1778
  "data-testid": "collection-day-view-title"
1447
1779
  };
1448
- var _hoisted_3$12 = ["aria-label"];
1449
- var _hoisted_4$12 = ["aria-label"];
1450
- var _hoisted_5$11 = {
1780
+ var _hoisted_3$13 = ["aria-label"];
1781
+ var _hoisted_4$13 = ["aria-label"];
1782
+ var _hoisted_5$12 = {
1451
1783
  key: 0,
1452
1784
  class: "px-4 py-10 text-center text-sm text-slate-400"
1453
1785
  };
1454
- var _hoisted_6$10 = { class: "absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400" };
1786
+ var _hoisted_6$11 = { class: "absolute -top-2 left-0 w-10 pr-1 text-right text-[10px] tabular-nums text-slate-400" };
1455
1787
  var _hoisted_7$9 = {
1456
1788
  class: "absolute inset-y-0 right-0",
1457
1789
  style: { "left": "2.75rem" }
@@ -1537,6 +1869,8 @@ var CollectionDayView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1537
1869
  "datetime",
1538
1870
  "table",
1539
1871
  "embed",
1872
+ "backlinks",
1873
+ "rollup",
1540
1874
  "image",
1541
1875
  "markdown"
1542
1876
  ]);
@@ -1632,8 +1966,8 @@ var CollectionDayView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1632
1966
  role: "dialog",
1633
1967
  "aria-modal": "true"
1634
1968
  }, [(0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["flex min-h-0 flex-col", __props.showDetail ? "w-80 shrink-0 border-r border-slate-200" : "w-full"]) }, [
1635
- (0, vue.createElementVNode)("div", _hoisted_1$13, [
1636
- (0, vue.createElementVNode)("h3", _hoisted_2$12, (0, vue.toDisplayString)(dayLabel.value), 1),
1969
+ (0, vue.createElementVNode)("div", _hoisted_1$14, [
1970
+ (0, vue.createElementVNode)("h3", _hoisted_2$13, (0, vue.toDisplayString)(dayLabel.value), 1),
1637
1971
  __props.canCreate ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1638
1972
  key: 0,
1639
1973
  type: "button",
@@ -1641,16 +1975,16 @@ var CollectionDayView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1641
1975
  "aria-label": (0, vue.unref)(t)("collectionsView.calendarCreateOn", { date: dayKey.value }),
1642
1976
  "data-testid": "collection-day-view-create",
1643
1977
  onClick: onCreate
1644
- }, [..._cache[3] || (_cache[3] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "add", -1)])], 8, _hoisted_3$12)) : (0, vue.createCommentVNode)("", true),
1978
+ }, [..._cache[3] || (_cache[3] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "add", -1)])], 8, _hoisted_3$13)) : (0, vue.createCommentVNode)("", true),
1645
1979
  (0, vue.createElementVNode)("button", {
1646
1980
  type: "button",
1647
1981
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-500 hover:bg-slate-100 transition-colors",
1648
1982
  "aria-label": (0, vue.unref)(t)("collectionsView.dayViewClose"),
1649
1983
  "data-testid": "collection-day-view-close",
1650
1984
  onClick: _cache[0] || (_cache[0] = ($event) => emit("close"))
1651
- }, [..._cache[4] || (_cache[4] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$12)
1985
+ }, [..._cache[4] || (_cache[4] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_4$13)
1652
1986
  ]),
1653
- timedEntries.value.length === 0 && allDayEntries.value.length === 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_5$11, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dayViewEmpty")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1987
+ timedEntries.value.length === 0 && allDayEntries.value.length === 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_5$12, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dayViewEmpty")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1654
1988
  key: 1,
1655
1989
  ref_key: "scrollEl",
1656
1990
  ref: scrollEl,
@@ -1664,7 +1998,7 @@ var CollectionDayView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1664
1998
  key: hour,
1665
1999
  class: "absolute left-0 right-0 border-t border-slate-100",
1666
2000
  style: (0, vue.normalizeStyle)({ top: `${(hour - 1) * HOUR_PX}px` })
1667
- }, [(0, vue.createElementVNode)("span", _hoisted_6$10, (0, vue.toDisplayString)(hourLabel(hour - 1)), 1)], 4);
2001
+ }, [(0, vue.createElementVNode)("span", _hoisted_6$11, (0, vue.toDisplayString)(hourLabel(hour - 1)), 1)], 4);
1668
2002
  }), 64)), (0, vue.createElementVNode)("div", _hoisted_7$9, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(timedEntries.value, (entry) => {
1669
2003
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1670
2004
  key: entry.id,
@@ -1699,15 +2033,15 @@ var CollectionDayView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1699
2033
  });
1700
2034
  //#endregion
1701
2035
  //#region src/vue/components/CollectionKanbanView.vue?vue&type=script&setup=true&lang.ts
1702
- var _hoisted_1$12 = {
2036
+ var _hoisted_1$13 = {
1703
2037
  class: "h-full overflow-x-auto overflow-y-hidden",
1704
2038
  "data-testid": "collection-kanban"
1705
2039
  };
1706
- var _hoisted_2$11 = { class: "flex gap-3 h-full p-1 min-w-max" };
1707
- var _hoisted_3$11 = ["data-testid"];
1708
- var _hoisted_4$11 = { class: "flex items-center justify-between px-3 py-2 border-b border-slate-200" };
1709
- var _hoisted_5$10 = { class: "flex items-center gap-2 min-w-0" };
1710
- var _hoisted_6$9 = ["title"];
2040
+ var _hoisted_2$12 = { class: "flex gap-3 h-full p-1 min-w-max" };
2041
+ var _hoisted_3$12 = ["data-testid"];
2042
+ var _hoisted_4$12 = { class: "flex items-center justify-between px-3 py-2 border-b border-slate-200" };
2043
+ var _hoisted_5$11 = { class: "flex items-center gap-2 min-w-0" };
2044
+ var _hoisted_6$10 = ["title"];
1711
2045
  var _hoisted_7$8 = { class: "text-[11px] text-slate-400 shrink-0" };
1712
2046
  var _hoisted_8$8 = [
1713
2047
  "data-testid",
@@ -1826,15 +2160,15 @@ var CollectionKanbanView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1826
2160
  emit("move", itemId(item), next);
1827
2161
  }
1828
2162
  return (_ctx, _cache) => {
1829
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$12, [(0, vue.createElementVNode)("div", _hoisted_2$11, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(columns.value, (column) => {
2163
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$13, [(0, vue.createElementVNode)("div", _hoisted_2$12, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(columns.value, (column) => {
1830
2164
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1831
2165
  key: column.value,
1832
2166
  "data-testid": `collection-kanban-column-${column.value || "uncategorized"}`,
1833
2167
  class: "w-72 shrink-0 flex flex-col bg-slate-100 rounded-lg"
1834
- }, [(0, vue.createElementVNode)("div", _hoisted_4$11, [(0, vue.createElementVNode)("div", _hoisted_5$10, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["w-2 h-2 rounded-full shrink-0", (0, vue.unref)(_mulmoclaude_core_collection.resolveEnumColor)(__props.schema, __props.groupField, column.value).dot]) }, null, 2), (0, vue.createElementVNode)("span", {
2168
+ }, [(0, vue.createElementVNode)("div", _hoisted_4$12, [(0, vue.createElementVNode)("div", _hoisted_5$11, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["w-2 h-2 rounded-full shrink-0", (0, vue.unref)(_mulmoclaude_core_collection.resolveEnumColor)(__props.schema, __props.groupField, column.value).dot]) }, null, 2), (0, vue.createElementVNode)("span", {
1835
2169
  class: "font-semibold text-xs text-slate-600 truncate",
1836
2170
  title: column.label
1837
- }, (0, vue.toDisplayString)(column.label), 9, _hoisted_6$9)]), (0, vue.createElementVNode)("span", _hoisted_7$8, (0, vue.toDisplayString)(itemsByColumn(column.value).length), 1)]), (0, vue.createVNode)((0, vue.unref)(vuedraggable.default), {
2171
+ }, (0, vue.toDisplayString)(column.label), 9, _hoisted_6$10)]), (0, vue.createElementVNode)("span", _hoisted_7$8, (0, vue.toDisplayString)(itemsByColumn(column.value).length), 1)]), (0, vue.createVNode)((0, vue.unref)(vuedraggable.default), {
1838
2172
  "model-value": itemsByColumn(column.value),
1839
2173
  "item-key": __props.schema.primaryKey,
1840
2174
  group: "collection-kanban-cards",
@@ -1865,7 +2199,7 @@ var CollectionKanbanView_default = /* @__PURE__ */ (0, vue.defineComponent)({
1865
2199
  "model-value",
1866
2200
  "item-key",
1867
2201
  "onChange"
1868
- ])], 8, _hoisted_3$11);
2202
+ ])], 8, _hoisted_3$12);
1869
2203
  }), 128))])]);
1870
2204
  };
1871
2205
  }
@@ -1894,6 +2228,61 @@ function activatePathLink(event, path, stop = false) {
1894
2228
  nav(path);
1895
2229
  }
1896
2230
  //#endregion
2231
+ //#region src/vue/components/CollectionBacklinksView.vue?vue&type=script&setup=true&lang.ts
2232
+ var _hoisted_1$12 = ["data-testid"];
2233
+ var _hoisted_2$11 = { class: "w-full text-[11px] text-slate-600 bg-white" };
2234
+ var _hoisted_3$11 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
2235
+ var _hoisted_4$11 = { class: "divide-y divide-slate-100" };
2236
+ var _hoisted_5$10 = [
2237
+ "data-testid",
2238
+ "onClick",
2239
+ "onKeydown"
2240
+ ];
2241
+ var _hoisted_6$9 = ["data-testid"];
2242
+ //#endregion
2243
+ //#region src/vue/components/CollectionBacklinksView.vue
2244
+ var CollectionBacklinksView_default = /* @__PURE__ */ (0, vue.defineComponent)({
2245
+ __name: "CollectionBacklinksView",
2246
+ props: {
2247
+ view: {},
2248
+ fieldKey: {}
2249
+ },
2250
+ setup(__props) {
2251
+ const { t } = useCollectionI18n();
2252
+ return (_ctx, _cache) => {
2253
+ return __props.view.rows.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2254
+ key: 0,
2255
+ class: "border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1",
2256
+ "data-testid": `collections-backlinks-${__props.fieldKey}`
2257
+ }, [(0, vue.createElementVNode)("table", _hoisted_2$11, [(0, vue.createElementVNode)("thead", _hoisted_3$11, [(0, vue.createElementVNode)("tr", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.view.columns, (column) => {
2258
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("th", {
2259
+ key: column.key,
2260
+ class: "text-left px-4 py-2 font-bold"
2261
+ }, (0, vue.toDisplayString)(column.label), 1);
2262
+ }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_4$11, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.view.rows, (row) => {
2263
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
2264
+ key: row.id,
2265
+ class: "group hover:bg-indigo-50/30 cursor-pointer transition-colors",
2266
+ role: "link",
2267
+ tabindex: "0",
2268
+ "data-testid": `collections-backlinks-${__props.fieldKey}-${row.id}`,
2269
+ onClick: ($event) => (0, vue.unref)(activateRefLink)($event, __props.view.fromSlug, row.id),
2270
+ onKeydown: [(0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, __props.view.fromSlug, row.id), ["enter"]), (0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, __props.view.fromSlug, row.id), ["space"])]
2271
+ }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(row.cells, (cell, cellIdx) => {
2272
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("td", {
2273
+ key: __props.view.columns[cellIdx]?.key ?? cellIdx,
2274
+ class: "px-4 py-2 align-middle font-medium"
2275
+ }, [(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(cellIdx === 0 ? "text-indigo-600 group-hover:text-indigo-800 font-bold" : "") }, (0, vue.toDisplayString)(cell), 3)]);
2276
+ }), 128))], 40, _hoisted_5$10);
2277
+ }), 128))])])], 8, _hoisted_1$12)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
2278
+ key: 1,
2279
+ class: "text-slate-400 italic",
2280
+ "data-testid": `collections-backlinks-${__props.fieldKey}`
2281
+ }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 9, _hoisted_6$9));
2282
+ };
2283
+ }
2284
+ });
2285
+ //#endregion
1897
2286
  //#region src/vue/components/CollectionEmbedView.vue?vue&type=script&setup=true&lang.ts
1898
2287
  var _hoisted_1$11 = [
1899
2288
  "href",
@@ -2002,111 +2391,115 @@ var _hoisted_7$6 = [
2002
2391
  ];
2003
2392
  var _hoisted_8$6 = {
2004
2393
  key: 0,
2394
+ class: "material-icons text-sm animate-spin"
2395
+ };
2396
+ var _hoisted_9$6 = {
2397
+ key: 1,
2005
2398
  class: "material-icons text-sm"
2006
2399
  };
2007
- var _hoisted_9$6 = ["aria-label"];
2008
- var _hoisted_10$6 = {
2400
+ var _hoisted_10$6 = ["aria-label"];
2401
+ var _hoisted_11$6 = {
2009
2402
  key: 0,
2010
2403
  class: "mb-3 text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl shadow-sm",
2011
2404
  "data-testid": "collections-detail-action-error"
2012
2405
  };
2013
- var _hoisted_11$6 = { 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" };
2014
- var _hoisted_12$6 = ["for"];
2015
- var _hoisted_13$5 = {
2406
+ var _hoisted_12$6 = { 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" };
2407
+ var _hoisted_13$5 = ["for"];
2408
+ var _hoisted_14$5 = {
2016
2409
  key: 0,
2017
2410
  class: "text-rose-500 font-bold"
2018
2411
  };
2019
- var _hoisted_14$5 = [
2412
+ var _hoisted_15$5 = [
2020
2413
  "id",
2021
2414
  "onUpdate:modelValue",
2022
2415
  "required",
2023
2416
  "data-testid"
2024
2417
  ];
2025
- var _hoisted_15$5 = { value: "" };
2026
- var _hoisted_16$5 = ["value"];
2027
- var _hoisted_17$5 = [
2418
+ var _hoisted_16$5 = { value: "" };
2419
+ var _hoisted_17$5 = ["value"];
2420
+ var _hoisted_18$5 = [
2028
2421
  "id",
2029
2422
  "onUpdate:modelValue",
2030
2423
  "required",
2031
2424
  "placeholder",
2032
2425
  "data-testid"
2033
2426
  ];
2034
- var _hoisted_18$5 = {
2427
+ var _hoisted_19$3 = {
2035
2428
  key: 0,
2036
2429
  class: "inline-flex items-center gap-2.5 text-sm text-slate-700 cursor-pointer select-none"
2037
2430
  };
2038
- var _hoisted_19$3 = [
2431
+ var _hoisted_20$3 = [
2039
2432
  "id",
2040
2433
  "onUpdate:modelValue",
2041
2434
  "data-testid",
2042
2435
  "onChange"
2043
2436
  ];
2044
- var _hoisted_20$3 = [
2437
+ var _hoisted_21$3 = [
2045
2438
  "id",
2046
2439
  "onUpdate:modelValue",
2047
2440
  "required",
2048
2441
  "data-testid"
2049
2442
  ];
2050
- var _hoisted_21$3 = { value: "" };
2051
- var _hoisted_22$3 = ["value"];
2052
- var _hoisted_23$3 = [
2443
+ var _hoisted_22$3 = { value: "" };
2444
+ var _hoisted_23$3 = ["value"];
2445
+ var _hoisted_24$2 = [
2053
2446
  "id",
2054
2447
  "onUpdate:modelValue",
2055
2448
  "required",
2056
2449
  "data-testid"
2057
2450
  ];
2058
- var _hoisted_24$2 = { value: "" };
2059
- var _hoisted_25$2 = ["value"];
2060
- var _hoisted_26$2 = ["data-testid"];
2061
- var _hoisted_27$2 = {
2451
+ var _hoisted_25$2 = { value: "" };
2452
+ var _hoisted_26$2 = ["value"];
2453
+ var _hoisted_27$2 = ["data-testid"];
2454
+ var _hoisted_28$1 = {
2062
2455
  key: 0,
2063
2456
  class: "overflow-hidden border border-slate-200 rounded-lg shadow-sm"
2064
2457
  };
2065
- var _hoisted_28$1 = { class: "w-full text-xs text-slate-600 bg-white" };
2066
- var _hoisted_29$1 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
2067
- var _hoisted_30$1 = { class: "divide-y divide-slate-100" };
2068
- var _hoisted_31$1 = ["onUpdate:modelValue", "onChange"];
2069
- var _hoisted_32$1 = ["onUpdate:modelValue", "required"];
2070
- var _hoisted_33$1 = { value: "" };
2071
- var _hoisted_34$1 = ["value"];
2072
- var _hoisted_35$1 = ["onUpdate:modelValue", "required"];
2073
- var _hoisted_36$1 = { value: "" };
2074
- var _hoisted_37$1 = ["value"];
2075
- var _hoisted_38$1 = {
2458
+ var _hoisted_29$1 = { class: "w-full text-xs text-slate-600 bg-white" };
2459
+ var _hoisted_30$1 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
2460
+ var _hoisted_31$1 = { class: "divide-y divide-slate-100" };
2461
+ var _hoisted_32$1 = ["onUpdate:modelValue", "onChange"];
2462
+ var _hoisted_33$1 = ["onUpdate:modelValue", "required"];
2463
+ var _hoisted_34$1 = { value: "" };
2464
+ var _hoisted_35$1 = ["value"];
2465
+ var _hoisted_36$1 = ["onUpdate:modelValue", "required"];
2466
+ var _hoisted_37$1 = { value: "" };
2467
+ var _hoisted_38$1 = ["value"];
2468
+ var _hoisted_39$1 = {
2076
2469
  key: 3,
2077
2470
  class: "relative flex items-center"
2078
2471
  };
2079
- var _hoisted_39$1 = { class: "absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200" };
2080
- var _hoisted_40$1 = ["onUpdate:modelValue", "required"];
2081
- var _hoisted_41$1 = [
2472
+ var _hoisted_40$1 = { class: "absolute left-1.5 text-[10px] text-slate-400 font-bold pr-1 border-r border-slate-200" };
2473
+ var _hoisted_41$1 = ["onUpdate:modelValue", "required"];
2474
+ var _hoisted_42$1 = [
2082
2475
  "onUpdate:modelValue",
2083
2476
  "type",
2084
2477
  "step",
2085
2478
  "required"
2086
2479
  ];
2087
- var _hoisted_42$1 = { class: "text-center px-1" };
2088
- var _hoisted_43$1 = [
2480
+ var _hoisted_43$1 = { class: "text-center px-1" };
2481
+ var _hoisted_44$1 = [
2089
2482
  "aria-label",
2090
2483
  "data-testid",
2091
2484
  "onClick"
2092
2485
  ];
2093
- var _hoisted_44$1 = {
2486
+ var _hoisted_45$1 = {
2094
2487
  key: 1,
2095
2488
  class: "text-xs text-slate-400 italic"
2096
2489
  };
2097
- var _hoisted_45$1 = ["data-testid", "onClick"];
2098
- var _hoisted_46$1 = {
2490
+ var _hoisted_46$1 = ["data-testid", "onClick"];
2491
+ var _hoisted_47$1 = {
2099
2492
  key: 4,
2100
2493
  class: "relative flex items-center"
2101
2494
  };
2102
- var _hoisted_47$1 = { class: "absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200" };
2103
- var _hoisted_48$1 = [
2495
+ var _hoisted_48$1 = { class: "absolute left-3 text-slate-400 font-bold text-xs select-none pr-1.5 border-r border-slate-200" };
2496
+ var _hoisted_49$1 = [
2104
2497
  "id",
2105
2498
  "onUpdate:modelValue",
2106
2499
  "required",
2107
2500
  "data-testid"
2108
2501
  ];
2109
- var _hoisted_49$1 = [
2502
+ var _hoisted_50$1 = [
2110
2503
  "id",
2111
2504
  "onUpdate:modelValue",
2112
2505
  "type",
@@ -2115,104 +2508,105 @@ var _hoisted_49$1 = [
2115
2508
  "disabled",
2116
2509
  "data-testid"
2117
2510
  ];
2118
- var _hoisted_50$1 = [
2511
+ var _hoisted_51$1 = [
2119
2512
  "id",
2120
2513
  "onUpdate:modelValue",
2121
2514
  "rows",
2122
2515
  "required",
2123
2516
  "data-testid"
2124
2517
  ];
2125
- var _hoisted_51$1 = ["data-testid"];
2126
- var _hoisted_52$1 = {
2518
+ var _hoisted_52$1 = ["data-testid"];
2519
+ var _hoisted_53$1 = {
2127
2520
  key: 0,
2128
2521
  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"
2129
2522
  };
2130
- var _hoisted_53$1 = {
2523
+ var _hoisted_54$1 = {
2131
2524
  key: 1,
2132
2525
  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"
2133
2526
  };
2134
- var _hoisted_54$1 = {
2527
+ var _hoisted_55$1 = {
2135
2528
  key: 0,
2136
2529
  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"
2137
2530
  };
2138
- var _hoisted_55$1 = {
2531
+ var _hoisted_56$1 = {
2139
2532
  key: 1,
2140
2533
  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"
2141
2534
  };
2142
- var _hoisted_56$1 = {
2535
+ var _hoisted_57$1 = {
2143
2536
  key: 2,
2144
2537
  class: "text-slate-300"
2145
2538
  };
2146
- var _hoisted_57$1 = [
2539
+ var _hoisted_58$1 = [
2147
2540
  "href",
2148
2541
  "tabindex",
2149
2542
  "data-testid",
2150
2543
  "onClick",
2151
2544
  "onKeydown"
2152
2545
  ];
2153
- var _hoisted_58$1 = {
2546
+ var _hoisted_59$1 = {
2154
2547
  key: 3,
2155
2548
  class: "font-semibold text-slate-900 tabular-nums text-sm"
2156
2549
  };
2157
- var _hoisted_59$1 = {
2550
+ var _hoisted_60$1 = {
2158
2551
  key: 4,
2159
2552
  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"
2160
2553
  };
2161
- var _hoisted_60$1 = {
2162
- key: 5,
2554
+ var _hoisted_61$1 = ["data-testid"];
2555
+ var _hoisted_62$1 = {
2556
+ key: 6,
2163
2557
  class: "border border-slate-200/80 rounded-xl overflow-hidden shadow-sm mt-1"
2164
2558
  };
2165
- var _hoisted_61$1 = { class: "w-full text-[11px] text-slate-600 bg-white" };
2166
- var _hoisted_62$1 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
2167
- var _hoisted_63$1 = { class: "divide-y divide-slate-100" };
2168
- var _hoisted_64$1 = {
2559
+ var _hoisted_63$1 = { class: "w-full text-[11px] text-slate-600 bg-white" };
2560
+ var _hoisted_64$1 = { class: "bg-slate-50 border-b border-slate-200 text-slate-500 font-bold uppercase tracking-wider" };
2561
+ var _hoisted_65$1 = { class: "divide-y divide-slate-100" };
2562
+ var _hoisted_66$1 = {
2169
2563
  key: 0,
2170
2564
  class: "material-icons text-emerald-600 text-base"
2171
2565
  };
2172
- var _hoisted_65$1 = {
2566
+ var _hoisted_67$1 = {
2173
2567
  key: 1,
2174
2568
  class: "text-slate-300"
2175
2569
  };
2176
- var _hoisted_66$1 = {
2177
- key: 6,
2570
+ var _hoisted_68$1 = {
2571
+ key: 7,
2178
2572
  class: "text-slate-400 italic"
2179
2573
  };
2180
- var _hoisted_67$1 = {
2181
- key: 7,
2574
+ var _hoisted_69$1 = {
2575
+ key: 8,
2182
2576
  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"
2183
2577
  };
2184
- var _hoisted_68$1 = [
2578
+ var _hoisted_70$1 = [
2185
2579
  "src",
2186
2580
  "alt",
2187
2581
  "data-testid"
2188
2582
  ];
2189
- var _hoisted_69$1 = ["href", "data-testid"];
2190
- var _hoisted_70$1 = ["href", "data-testid"];
2191
- var _hoisted_71$1 = [
2583
+ var _hoisted_71$1 = ["href", "data-testid"];
2584
+ var _hoisted_72$1 = ["href", "data-testid"];
2585
+ var _hoisted_73$1 = [
2192
2586
  "href",
2193
2587
  "data-testid",
2194
2588
  "onClick"
2195
2589
  ];
2196
- var _hoisted_72$1 = {
2197
- key: 13,
2590
+ var _hoisted_74$1 = {
2591
+ key: 15,
2198
2592
  class: "text-slate-800 font-semibold"
2199
2593
  };
2200
- var _hoisted_73$1 = {
2594
+ var _hoisted_75$1 = {
2201
2595
  key: 0,
2202
2596
  class: "col-span-full text-xs font-semibold text-red-600 bg-red-50 border border-red-100 p-2.5 rounded-xl"
2203
2597
  };
2204
- var _hoisted_74$1 = {
2598
+ var _hoisted_76$1 = {
2205
2599
  key: 1,
2206
2600
  class: "mt-5 pt-4 border-t border-slate-200/60",
2207
2601
  "data-testid": "collections-detail-chat"
2208
2602
  };
2209
- var _hoisted_75$1 = {
2603
+ var _hoisted_77$1 = {
2210
2604
  class: "block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1.5",
2211
2605
  for: "collections-detail-chat-input"
2212
2606
  };
2213
- var _hoisted_76$1 = { class: "flex items-end gap-2" };
2214
- var _hoisted_77$1 = ["placeholder", "onKeydown"];
2215
- var _hoisted_78$1 = ["disabled"];
2607
+ var _hoisted_78$1 = { class: "flex items-end gap-2" };
2608
+ var _hoisted_79$1 = ["placeholder", "onKeydown"];
2609
+ var _hoisted_80$1 = ["disabled"];
2216
2610
  //#endregion
2217
2611
  //#region src/vue/components/CollectionRecordPanel.vue
2218
2612
  var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -2225,6 +2619,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2225
2619
  actionError: {},
2226
2620
  actionPending: { type: Boolean },
2227
2621
  visibleActions: {},
2622
+ runningActionIds: {},
2228
2623
  liveRecord: {},
2229
2624
  liveDerived: {},
2230
2625
  viewTitle: {},
@@ -2267,6 +2662,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2267
2662
  * safe in the template. */
2268
2663
  const detailRecord = (0, vue.computed)(() => editing.value ? props.liveDerived ?? props.liveRecord ?? {} : props.viewing ?? {});
2269
2664
  const embedViews = (0, vue.computed)(() => props.render.embedViewsFor(detailRecord.value));
2665
+ const backlinksViews = (0, vue.computed)(() => props.render.backlinksViewsFor(detailRecord.value));
2270
2666
  const embedOwnerByKey = (0, vue.computed)(() => {
2271
2667
  const map = /* @__PURE__ */ new Map();
2272
2668
  for (const field of Object.values(props.collection.schema.fields)) if (field.type === "embed" && field.idField) map.set(field.idField, field);
@@ -2282,12 +2678,12 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2282
2678
  if (!editing.value) return "collections-detail";
2283
2679
  return editing.value.mode === "create" ? "collections-create" : "collections-edit";
2284
2680
  });
2285
- /** Whether a field gets an editable control in edit mode. Toggle (a
2286
- * projection of an enum that has its own input), derived (computed), and
2287
- * embed (a foreign record) stay read-only in both modes, so the cell
2288
- * geometry never changes on the view↔edit toggle. */
2681
+ /** Whether a field gets an editable control in edit mode. The computed /
2682
+ * projected kinds (COMPUTED_TYPES: derived, embed, backlinks, rollup,
2683
+ * toggle) stay read-only in both modes, so the cell geometry never
2684
+ * changes on the view↔edit toggle. */
2289
2685
  function isEditableType(type) {
2290
- return type !== "toggle" && type !== "derived" && type !== "embed";
2686
+ return !_mulmoclaude_core_collection.COMPUTED_TYPES.has(type);
2291
2687
  }
2292
2688
  /** Wide field types span the full grid width in BOTH modes — keeping
2293
2689
  * `image` full-width here (not just when viewing) is what stops a field
@@ -2297,6 +2693,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2297
2693
  "table",
2298
2694
  "markdown",
2299
2695
  "embed",
2696
+ "backlinks",
2300
2697
  "image"
2301
2698
  ].includes(field.type) ? "col-span-full" : "col-span-1";
2302
2699
  }
@@ -2375,10 +2772,10 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2375
2772
  key: action.id,
2376
2773
  type: "button",
2377
2774
  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",
2378
- disabled: __props.actionPending,
2775
+ disabled: __props.actionPending || __props.runningActionIds.includes(action.id),
2379
2776
  "data-testid": `collections-detail-action-${action.id}`,
2380
2777
  onClick: ($event) => emit("runAction", action)
2381
- }, [action.icon ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8$6, (0, vue.toDisplayString)(action.icon), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(action.label), 1)], 8, _hoisted_7$6);
2778
+ }, [__props.runningActionIds.includes(action.id) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8$6, "progress_activity")) : action.icon ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_9$6, (0, vue.toDisplayString)(action.icon), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(action.label), 1)], 8, _hoisted_7$6);
2382
2779
  }), 128)),
2383
2780
  (0, vue.createElementVNode)("button", {
2384
2781
  type: "button",
@@ -2398,29 +2795,29 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2398
2795
  "aria-label": (0, vue.unref)(t)("common.close"),
2399
2796
  "data-testid": "collections-detail-close",
2400
2797
  onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
2401
- }, [..._cache[8] || (_cache[8] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_9$6)
2798
+ }, [..._cache[8] || (_cache[8] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_10$6)
2402
2799
  ]))]),
2403
- !editing.value && __props.actionError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_10$6, (0, vue.toDisplayString)(__props.actionError), 1)) : (0, vue.createCommentVNode)("", true),
2404
- (0, vue.createElementVNode)("div", _hoisted_11$6, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.collection.schema.fields, (field, key) => {
2800
+ !editing.value && __props.actionError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_11$6, (0, vue.toDisplayString)(__props.actionError), 1)) : (0, vue.createCommentVNode)("", true),
2801
+ (0, vue.createElementVNode)("div", _hoisted_12$6, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.collection.schema.fields, (field, key) => {
2405
2802
  return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key }, [cellVisible(field, String(key)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2406
2803
  key: 0,
2407
2804
  class: (0, vue.normalizeClass)(["flex flex-col gap-1.5", colSpanClass(field)])
2408
2805
  }, [(0, vue.createElementVNode)("label", {
2409
2806
  class: "text-[10px] font-bold text-slate-400 uppercase tracking-wider flex items-center gap-1",
2410
2807
  for: `collections-field-${key}`
2411
- }, [(0, vue.createTextVNode)((0, vue.toDisplayString)(field.label) + " ", 1), editing.value && field.required ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_13$5, "*")) : (0, vue.createCommentVNode)("", true)], 8, _hoisted_12$6), editing.value && field.type === "embed" && field.idField && __props.render.embedOptions(field.to ?? "").length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2808
+ }, [(0, vue.createTextVNode)((0, vue.toDisplayString)(field.label) + " ", 1), editing.value && field.required ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_14$5, "*")) : (0, vue.createCommentVNode)("", true)], 8, _hoisted_13$5), editing.value && field.type === "embed" && field.idField && __props.render.embedOptions(field.to ?? "").length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2412
2809
  key: 0,
2413
2810
  id: `collections-field-${key}`,
2414
2811
  "onUpdate:modelValue": ($event) => editing.value.text[field.idField] = $event,
2415
2812
  required: embedPickerRequired(field),
2416
2813
  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",
2417
2814
  "data-testid": `collections-input-${key}`
2418
- }, [(0, vue.createElementVNode)("option", _hoisted_15$5, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.embedOptions(field.to ?? ""), (opt) => {
2815
+ }, [(0, vue.createElementVNode)("option", _hoisted_16$5, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.embedOptions(field.to ?? ""), (opt) => {
2419
2816
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
2420
2817
  key: opt.slug,
2421
2818
  value: opt.slug
2422
- }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_16$5);
2423
- }), 128))], 8, _hoisted_14$5)), [[vue.vModelSelect, editing.value.text[field.idField]]]) : editing.value && field.type === "embed" && field.idField ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
2819
+ }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_17$5);
2820
+ }), 128))], 8, _hoisted_15$5)), [[vue.vModelSelect, editing.value.text[field.idField]]]) : editing.value && field.type === "embed" && field.idField ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
2424
2821
  key: 1,
2425
2822
  id: `collections-field-${key}`,
2426
2823
  "onUpdate:modelValue": ($event) => editing.value.text[field.idField] = $event,
@@ -2429,47 +2826,47 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2429
2826
  placeholder: (0, vue.unref)(t)("collectionsView.selectPlaceholder"),
2430
2827
  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",
2431
2828
  "data-testid": `collections-input-${key}`
2432
- }, null, 8, _hoisted_17$5)), [[vue.vModelText, editing.value.text[field.idField]]]) : editing.value && isEditableType(field.type) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("label", _hoisted_18$5, [(0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2829
+ }, null, 8, _hoisted_18$5)), [[vue.vModelText, editing.value.text[field.idField]]]) : editing.value && isEditableType(field.type) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("label", _hoisted_19$3, [(0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2433
2830
  id: `collections-field-${key}`,
2434
2831
  "onUpdate:modelValue": ($event) => editing.value.bool[key] = $event,
2435
2832
  type: "checkbox",
2436
2833
  class: "h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer",
2437
2834
  "data-testid": `collections-input-${key}`,
2438
2835
  onChange: ($event) => markBoolTouched(String(key))
2439
- }, null, 40, _hoisted_19$3), [[vue.vModelCheckbox, editing.value.bool[key]]]), (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["text-xs font-semibold", editing.value.bool[key] ? "text-indigo-600" : "text-slate-500"]) }, (0, vue.toDisplayString)(editing.value.bool[key] ? (0, vue.unref)(t)("common.yes") : (0, vue.unref)(t)("common.no")), 3)])) : field.type === "ref" && field.to && __props.render.refOptions(field.to).length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2836
+ }, null, 40, _hoisted_20$3), [[vue.vModelCheckbox, editing.value.bool[key]]]), (0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["text-xs font-semibold", editing.value.bool[key] ? "text-indigo-600" : "text-slate-500"]) }, (0, vue.toDisplayString)(editing.value.bool[key] ? (0, vue.unref)(t)("common.yes") : (0, vue.unref)(t)("common.no")), 3)])) : field.type === "ref" && field.to && __props.render.refOptions(field.to).length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2440
2837
  key: 1,
2441
2838
  id: `collections-field-${key}`,
2442
2839
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2443
2840
  required: isFieldRequiredInUi(field),
2444
2841
  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",
2445
2842
  "data-testid": `collections-input-${key}`
2446
- }, [(0, vue.createElementVNode)("option", _hoisted_21$3, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.refOptions(field.to), (opt) => {
2843
+ }, [(0, vue.createElementVNode)("option", _hoisted_22$3, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.refOptions(field.to), (opt) => {
2447
2844
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
2448
2845
  key: opt.slug,
2449
2846
  value: opt.slug
2450
- }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_22$3);
2451
- }), 128))], 8, _hoisted_20$3)), [[vue.vModelSelect, editing.value.text[key]]]) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2847
+ }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_23$3);
2848
+ }), 128))], 8, _hoisted_21$3)), [[vue.vModelSelect, editing.value.text[key]]]) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2452
2849
  key: 2,
2453
2850
  id: `collections-field-${key}`,
2454
2851
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2455
2852
  required: isFieldRequiredInUi(field),
2456
2853
  class: (0, vue.normalizeClass)(["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", enumControlClass(String(key), editing.value.text[key])]),
2457
2854
  "data-testid": `collections-input-${key}`
2458
- }, [(0, vue.createElementVNode)("option", _hoisted_24$2, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
2855
+ }, [(0, vue.createElementVNode)("option", _hoisted_25$2, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
2459
2856
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
2460
2857
  key: value,
2461
2858
  value
2462
- }, (0, vue.toDisplayString)(value), 9, _hoisted_25$2);
2463
- }), 128))], 10, _hoisted_23$3)), [[vue.vModelSelect, editing.value.text[key]]]) : field.type === "table" && field.of ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2859
+ }, (0, vue.toDisplayString)(value), 9, _hoisted_26$2);
2860
+ }), 128))], 10, _hoisted_24$2)), [[vue.vModelSelect, editing.value.text[key]]]) : field.type === "table" && field.of ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2464
2861
  key: 3,
2465
2862
  class: "border border-slate-200 bg-slate-50/30 rounded-xl p-4 space-y-3",
2466
2863
  "data-testid": `collections-table-${key}`
2467
- }, [editing.value.table[key] && editing.value.table[key].length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_27$2, [(0, vue.createElementVNode)("table", _hoisted_28$1, [(0, vue.createElementVNode)("thead", _hoisted_29$1, [(0, vue.createElementVNode)("tr", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.of, (subField, subKey) => {
2864
+ }, [editing.value.table[key] && editing.value.table[key].length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_28$1, [(0, vue.createElementVNode)("table", _hoisted_29$1, [(0, vue.createElementVNode)("thead", _hoisted_30$1, [(0, vue.createElementVNode)("tr", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.of, (subField, subKey) => {
2468
2865
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("th", {
2469
2866
  key: subKey,
2470
2867
  class: "text-left px-3 py-2 font-bold"
2471
2868
  }, (0, vue.toDisplayString)(subField.label), 1);
2472
- }), 128)), _cache[9] || (_cache[9] = (0, vue.createElementVNode)("th", { class: "w-9" }, null, -1))])]), (0, vue.createElementVNode)("tbody", _hoisted_30$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(editing.value.table[key], (row, rowIdx) => {
2869
+ }), 128)), _cache[9] || (_cache[9] = (0, vue.createElementVNode)("th", { class: "w-9" }, null, -1))])]), (0, vue.createElementVNode)("tbody", _hoisted_31$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(editing.value.table[key], (row, rowIdx) => {
2473
2870
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
2474
2871
  key: rowIdx,
2475
2872
  class: "hover:bg-slate-50/50"
@@ -2483,53 +2880,53 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2483
2880
  type: "checkbox",
2484
2881
  class: "h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20 cursor-pointer",
2485
2882
  onChange: ($event) => markRowBoolTouched(row, String(subKey))
2486
- }, null, 40, _hoisted_31$1)), [[vue.vModelCheckbox, row.bool[subKey]]]) : subField.type === "enum" && Array.isArray(subField.values) && subField.values.length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2883
+ }, null, 40, _hoisted_32$1)), [[vue.vModelCheckbox, row.bool[subKey]]]) : subField.type === "enum" && Array.isArray(subField.values) && subField.values.length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2487
2884
  key: 1,
2488
2885
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2489
2886
  required: subField.required,
2490
2887
  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"
2491
- }, [(0, vue.createElementVNode)("option", _hoisted_33$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(subField.values, (value) => {
2888
+ }, [(0, vue.createElementVNode)("option", _hoisted_34$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(subField.values, (value) => {
2492
2889
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
2493
2890
  key: value,
2494
2891
  value
2495
- }, (0, vue.toDisplayString)(value), 9, _hoisted_34$1);
2496
- }), 128))], 8, _hoisted_32$1)), [[vue.vModelSelect, row.text[subKey]]]) : subField.type === "ref" && subField.to && __props.render.refOptions(subField.to).length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2892
+ }, (0, vue.toDisplayString)(value), 9, _hoisted_35$1);
2893
+ }), 128))], 8, _hoisted_33$1)), [[vue.vModelSelect, row.text[subKey]]]) : subField.type === "ref" && subField.to && __props.render.refOptions(subField.to).length > 0 ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
2497
2894
  key: 2,
2498
2895
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2499
2896
  required: subField.required,
2500
2897
  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"
2501
- }, [(0, vue.createElementVNode)("option", _hoisted_36$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.refOptions(subField.to), (opt) => {
2898
+ }, [(0, vue.createElementVNode)("option", _hoisted_37$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.refOptions(subField.to), (opt) => {
2502
2899
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
2503
2900
  key: opt.slug,
2504
2901
  value: opt.slug
2505
- }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_37$1);
2506
- }), 128))], 8, _hoisted_35$1)), [[vue.vModelSelect, row.text[subKey]]]) : subField.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_38$1, [(0, vue.createElementVNode)("span", _hoisted_39$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(subField, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2902
+ }, (0, vue.toDisplayString)(opt.display), 9, _hoisted_38$1);
2903
+ }), 128))], 8, _hoisted_36$1)), [[vue.vModelSelect, row.text[subKey]]]) : subField.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_39$1, [(0, vue.createElementVNode)("span", _hoisted_40$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(subField, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2507
2904
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2508
2905
  type: "number",
2509
2906
  step: "0.01",
2510
2907
  required: subField.required,
2511
2908
  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"
2512
- }, null, 8, _hoisted_40$1), [[vue.vModelText, row.text[subKey]]])])) : (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
2909
+ }, null, 8, _hoisted_41$1), [[vue.vModelText, row.text[subKey]]])])) : (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
2513
2910
  key: 4,
2514
2911
  "onUpdate:modelValue": ($event) => row.text[subKey] = $event,
2515
2912
  type: __props.render.inputTypeFor(subField.type),
2516
2913
  step: __props.render.stepFor(subField.type),
2517
2914
  required: subField.required,
2518
2915
  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"
2519
- }, null, 8, _hoisted_41$1)), [[vue.vModelDynamic, row.text[subKey]]])]);
2520
- }), 128)), (0, vue.createElementVNode)("td", _hoisted_42$1, [(0, vue.createElementVNode)("button", {
2916
+ }, null, 8, _hoisted_42$1)), [[vue.vModelDynamic, row.text[subKey]]])]);
2917
+ }), 128)), (0, vue.createElementVNode)("td", _hoisted_43$1, [(0, vue.createElementVNode)("button", {
2521
2918
  type: "button",
2522
2919
  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",
2523
2920
  "aria-label": (0, vue.unref)(t)("collectionsView.removeRow"),
2524
2921
  "data-testid": `collections-table-${key}-remove-${rowIdx}`,
2525
2922
  onClick: ($event) => removeTableRow(String(key), rowIdx)
2526
- }, [..._cache[10] || (_cache[10] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_43$1)])]);
2527
- }), 128))])])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_44$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 1)), (0, vue.createElementVNode)("button", {
2923
+ }, [..._cache[10] || (_cache[10] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_44$1)])]);
2924
+ }), 128))])])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_45$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 1)), (0, vue.createElementVNode)("button", {
2528
2925
  type: "button",
2529
2926
  class: "inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-bold hover:underline",
2530
2927
  "data-testid": `collections-table-${key}-add`,
2531
2928
  onClick: ($event) => addTableRow(String(key), field.of)
2532
- }, [_cache[11] || (_cache[11] = (0, vue.createElementVNode)("span", { class: "material-icons text-xs" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addRow")), 1)], 8, _hoisted_45$1)], 8, _hoisted_26$2)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_46$1, [(0, vue.createElementVNode)("div", _hoisted_47$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2929
+ }, [_cache[11] || (_cache[11] = (0, vue.createElementVNode)("span", { class: "material-icons text-xs" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addRow")), 1)], 8, _hoisted_46$1)], 8, _hoisted_27$2)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_47$1, [(0, vue.createElementVNode)("div", _hoisted_48$1, (0, vue.toDisplayString)(__props.render.currencySymbol(__props.render.resolveCurrency(field, __props.liveRecord))), 1), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2533
2930
  id: `collections-field-${key}`,
2534
2931
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
2535
2932
  type: "number",
@@ -2537,7 +2934,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2537
2934
  required: isFieldRequiredInUi(field),
2538
2935
  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",
2539
2936
  "data-testid": `collections-input-${key}`
2540
- }, null, 8, _hoisted_48$1), [[vue.vModelText, editing.value.text[key]]])])) : [
2937
+ }, null, 8, _hoisted_49$1), [[vue.vModelText, editing.value.text[key]]])])) : [
2541
2938
  "string",
2542
2939
  "email",
2543
2940
  "number",
@@ -2556,7 +2953,7 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2556
2953
  disabled: field.primary === true && (editing.value.mode === "edit" || __props.isSingleton),
2557
2954
  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",
2558
2955
  "data-testid": `collections-input-${key}`
2559
- }, null, 8, _hoisted_49$1)), [[vue.vModelDynamic, editing.value.text[key]]]) : (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("textarea", {
2956
+ }, null, 8, _hoisted_50$1)), [[vue.vModelDynamic, editing.value.text[key]]]) : (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("textarea", {
2560
2957
  key: 6,
2561
2958
  id: `collections-field-${key}`,
2562
2959
  "onUpdate:modelValue": ($event) => editing.value.text[key] = $event,
@@ -2564,11 +2961,11 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2564
2961
  required: isFieldRequiredInUi(field),
2565
2962
  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",
2566
2963
  "data-testid": `collections-input-${key}`
2567
- }, null, 8, _hoisted_50$1)), [[vue.vModelText, editing.value.text[key]]])], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2964
+ }, null, 8, _hoisted_51$1)), [[vue.vModelText, editing.value.text[key]]])], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2568
2965
  key: 3,
2569
2966
  class: "text-xs font-medium text-slate-700 break-words",
2570
2967
  "data-testid": `collections-detail-value-${key}`
2571
- }, [field.type === "toggle" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_52$1, [_cache[12] || (_cache[12] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_53$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1))], 64)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [detailRecord.value[key] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_54$1, [_cache[13] || (_cache[13] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_55$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_56$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2968
+ }, [field.type === "toggle" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [field.field !== void 0 && String(detailRecord.value[field.field] ?? "") === field.onValue ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_53$1, [_cache[12] || (_cache[12] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_54$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1))], 64)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [detailRecord.value[key] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_55$1, [_cache[13] || (_cache[13] = (0, vue.createElementVNode)("span", { class: "h-1.5 w-1.5 rounded-full bg-emerald-500" }, null, -1)), (0, vue.createTextVNode)(" " + (0, vue.toDisplayString)((0, vue.unref)(t)("common.yes")), 1)])) : detailRecord.value[key] === false ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_56$1, (0, vue.toDisplayString)((0, vue.unref)(t)("common.no")), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_57$1, "—"))], 64)) : field.type === "ref" && field.to && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2572
2969
  key: 2,
2573
2970
  href: (0, vue.unref)(cui).recordHref?.(field.to, String(detailRecord.value[key])),
2574
2971
  tabindex: (0, vue.unref)(cui).recordHref?.(field.to, String(detailRecord.value[key])) ? void 0 : 0,
@@ -2577,12 +2974,16 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2577
2974
  "data-testid": `collections-detail-ref-${key}`,
2578
2975
  onClick: ($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(detailRecord.value[key])),
2579
2976
  onKeydown: [(0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(detailRecord.value[key])), ["enter"]), (0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(detailRecord.value[key])), ["space"])]
2580
- }, (0, vue.toDisplayString)(__props.render.refDisplay(field.to, String(detailRecord.value[key]))), 41, _hoisted_57$1)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_58$1, (0, vue.toDisplayString)(__props.render.formatMoney(detailRecord.value[key], __props.render.resolveCurrency(field, detailRecord.value), __props.locale)), 1)) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_59$1, (0, vue.toDisplayString)(__props.render.derivedDisplay(field, __props.render.evaluateDerivedAgainstItem(field, String(key), detailRecord.value), detailRecord.value)), 1)) : field.type === "table" && field.of && __props.render.hasTableRows(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_60$1, [(0, vue.createElementVNode)("table", _hoisted_61$1, [(0, vue.createElementVNode)("thead", _hoisted_62$1, [(0, vue.createElementVNode)("tr", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.of, (subField, subKey) => {
2977
+ }, (0, vue.toDisplayString)(__props.render.refDisplay(field.to, String(detailRecord.value[key]))), 41, _hoisted_58$1)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_59$1, (0, vue.toDisplayString)(__props.render.formatMoney(detailRecord.value[key], __props.render.resolveCurrency(field, detailRecord.value), __props.locale)), 1)) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_60$1, (0, vue.toDisplayString)(__props.render.derivedDisplay(field, __props.render.evaluateDerivedAgainstItem(field, String(key), detailRecord.value), detailRecord.value)), 1)) : field.type === "rollup" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
2978
+ key: 5,
2979
+ 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",
2980
+ "data-testid": `collections-detail-rollup-${key}`
2981
+ }, (0, vue.toDisplayString)(__props.render.rollupDisplay(field, detailRecord.value)), 9, _hoisted_61$1)) : field.type === "table" && field.of && __props.render.hasTableRows(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_62$1, [(0, vue.createElementVNode)("table", _hoisted_63$1, [(0, vue.createElementVNode)("thead", _hoisted_64$1, [(0, vue.createElementVNode)("tr", null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.of, (subField, subKey) => {
2581
2982
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("th", {
2582
2983
  key: subKey,
2583
2984
  class: "text-left px-4 py-2 font-bold"
2584
2985
  }, (0, vue.toDisplayString)(subField.label), 1);
2585
- }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_63$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.tableRows(detailRecord.value[key]), (row, rowIdx) => {
2986
+ }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_65$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.render.tableRows(detailRecord.value[key]), (row, rowIdx) => {
2586
2987
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
2587
2988
  key: rowIdx,
2588
2989
  class: "hover:bg-slate-50/50"
@@ -2590,44 +2991,48 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2590
2991
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("td", {
2591
2992
  key: subKey,
2592
2993
  class: "px-4 py-2 align-middle font-medium"
2593
- }, [subField.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [row[subKey] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_64$1, "check_circle")) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_65$1, "—"))], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
2994
+ }, [subField.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [row[subKey] === true ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_66$1, "check_circle")) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_67$1, "—"))], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
2594
2995
  key: 1,
2595
2996
  class: (0, vue.normalizeClass)([subField.type === "money" ? "font-bold text-slate-800 tabular-nums" : ""])
2596
2997
  }, (0, vue.toDisplayString)(__props.render.formatSubCell(subField, row[subKey], detailRecord.value)), 3))]);
2597
2998
  }), 128))]);
2598
- }), 128))])])])) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_66$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 1)) : field.type === "markdown" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_67$1, (0, vue.toDisplayString)(__props.render.detailText(detailRecord.value[key])), 1)) : field.type === "embed" && embedViews.value[key] ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionEmbedView_default, {
2599
- key: 8,
2999
+ }), 128))])])])) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_68$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noRows")), 1)) : field.type === "markdown" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_69$1, (0, vue.toDisplayString)(__props.render.detailText(detailRecord.value[key])), 1)) : field.type === "embed" && embedViews.value[key] ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionEmbedView_default, {
3000
+ key: 9,
2600
3001
  view: embedViews.value[key],
2601
3002
  "field-key": String(key)
3003
+ }, null, 8, ["view", "field-key"])) : field.type === "backlinks" && backlinksViews.value[key] ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionBacklinksView_default, {
3004
+ key: 10,
3005
+ view: backlinksViews.value[key],
3006
+ "field-key": String(key)
2602
3007
  }, null, 8, ["view", "field-key"])) : field.type === "image" && typeof detailRecord.value[key] === "string" && detailRecord.value[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("img", {
2603
- key: 9,
3008
+ key: 11,
2604
3009
  src: (0, vue.unref)(resolveImageSrc)(String(detailRecord.value[key])),
2605
3010
  alt: field.label,
2606
3011
  class: "max-h-64 max-w-full object-contain rounded-lg border border-slate-200 bg-slate-50",
2607
3012
  "data-testid": `collections-detail-image-${key}`
2608
- }, null, 8, _hoisted_68$1)) : field.type !== "file" && __props.render.isExternalUrl(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2609
- key: 10,
3013
+ }, null, 8, _hoisted_70$1)) : field.type !== "file" && __props.render.isExternalUrl(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
3014
+ key: 12,
2610
3015
  href: String(detailRecord.value[key]),
2611
3016
  target: "_blank",
2612
3017
  rel: "noopener noreferrer",
2613
3018
  class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
2614
3019
  "data-testid": `collections-detail-url-${key}`
2615
- }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_69$1)) : field.type === "file" && __props.render.artifactUrl(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2616
- key: 11,
3020
+ }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_71$1)) : field.type === "file" && __props.render.artifactUrl(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
3021
+ key: 13,
2617
3022
  href: __props.render.artifactUrl(detailRecord.value[key]) ?? void 0,
2618
3023
  target: "_blank",
2619
3024
  rel: "noopener noreferrer",
2620
3025
  class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
2621
3026
  "data-testid": `collections-detail-file-${key}`
2622
- }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_70$1)) : field.type === "file" && __props.render.fileRoutePath(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
2623
- key: 12,
3027
+ }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_72$1)) : field.type === "file" && __props.render.fileRoutePath(detailRecord.value[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
3028
+ key: 14,
2624
3029
  href: __props.render.fileRoutePath(detailRecord.value[key]) ?? void 0,
2625
3030
  class: "text-blue-600 hover:text-blue-800 font-semibold hover:underline break-all",
2626
3031
  "data-testid": `collections-detail-file-${key}`,
2627
3032
  onClick: ($event) => (0, vue.unref)(activatePathLink)($event, __props.render.fileRoutePath(detailRecord.value[key]) ?? "")
2628
- }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_71$1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_72$1, (0, vue.toDisplayString)(__props.render.formatCell(detailRecord.value[key], field.type)), 1))], 8, _hoisted_51$1))], 2)) : (0, vue.createCommentVNode)("", true)], 64);
2629
- }), 128)), editing.value && __props.saveError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_73$1, (0, vue.toDisplayString)(__props.saveError), 1)) : (0, vue.createCommentVNode)("", true)]),
2630
- !editing.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_74$1, [(0, vue.createElementVNode)("label", _hoisted_75$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemChatLabel")), 1), (0, vue.createElementVNode)("div", _hoisted_76$1, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
3033
+ }, (0, vue.toDisplayString)(String(detailRecord.value[key])), 9, _hoisted_73$1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_74$1, (0, vue.toDisplayString)(__props.render.formatCell(detailRecord.value[key], field.type)), 1))], 8, _hoisted_52$1))], 2)) : (0, vue.createCommentVNode)("", true)], 64);
3034
+ }), 128)), editing.value && __props.saveError ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_75$1, (0, vue.toDisplayString)(__props.saveError), 1)) : (0, vue.createCommentVNode)("", true)]),
3035
+ !editing.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_76$1, [(0, vue.createElementVNode)("label", _hoisted_77$1, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemChatLabel")), 1), (0, vue.createElementVNode)("div", _hoisted_78$1, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
2631
3036
  id: "collections-detail-chat-input",
2632
3037
  "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => chatMessage.value = $event),
2633
3038
  rows: "2",
@@ -2635,13 +3040,13 @@ var CollectionRecordPanel_default = /* @__PURE__ */ (0, vue.defineComponent)({
2635
3040
  class: "flex-1 bg-slate-50 border border-slate-200/80 rounded-xl px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all resize-none",
2636
3041
  "data-testid": "collections-detail-chat-input",
2637
3042
  onKeydown: [(0, vue.withKeys)((0, vue.withModifiers)(submitItemChat, ["meta"]), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(submitItemChat, ["ctrl"]), ["enter"])]
2638
- }, null, 40, _hoisted_77$1), [[vue.vModelText, chatMessage.value]]), (0, vue.createElementVNode)("button", {
3043
+ }, null, 40, _hoisted_79$1), [[vue.vModelText, chatMessage.value]]), (0, vue.createElementVNode)("button", {
2639
3044
  type: "button",
2640
3045
  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 flex items-center gap-1 shrink-0",
2641
3046
  disabled: !chatMessage.value.trim(),
2642
3047
  "data-testid": "collections-detail-chat-send",
2643
3048
  onClick: submitItemChat
2644
- }, [_cache[14] || (_cache[14] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "forum", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chat")), 1)], 8, _hoisted_78$1)])])) : (0, vue.createCommentVNode)("", true)
3049
+ }, [_cache[14] || (_cache[14] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "forum", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chat")), 1)], 8, _hoisted_80$1)])])) : (0, vue.createCommentVNode)("", true)
2645
3050
  ]),
2646
3051
  _: 1
2647
3052
  }, 40, ["data-testid"]);
@@ -3043,152 +3448,16 @@ var CollectionRemoteViewPreview_default = /*#__PURE__*/ _plugin_vue_export_helpe
3043
3448
  }
3044
3449
  }), [["__scopeId", "data-v-965ade6c"]]);
3045
3450
  //#endregion
3046
- //#region src/vue/useCollectionRendering.helpers.ts
3047
- var EM_DASH = "—";
3048
- var DEFAULT_CURRENCY = "USD";
3049
- var MARKDOWN_CELL_PREVIEW_MAX = 80;
3050
- function stepForFieldType(type) {
3051
- if (type === "money") return "0.01";
3052
- if (type === "number") return "any";
3053
- }
3054
- function inputTypeFor(type) {
3055
- if (type === "email") return "email";
3056
- if (type === "number") return "number";
3057
- if (type === "money") return "number";
3058
- if (type === "date") return "date";
3059
- if (type === "datetime") return "datetime-local";
3060
- return "text";
3061
- }
3062
- function isExternalUrl(value) {
3063
- return typeof value === "string" && /^https?:\/\//i.test(value);
3064
- }
3065
- function detailText(value) {
3066
- if (value === void 0 || value === null || value === "") return EM_DASH;
3067
- return String(value);
3068
- }
3069
- function formatCell(value, type) {
3070
- if (value === void 0 || value === null || value === "") return EM_DASH;
3071
- if (type === "markdown" && typeof value === "string") return value.length > MARKDOWN_CELL_PREVIEW_MAX ? `${value.slice(0, MARKDOWN_CELL_PREVIEW_MAX)}…` : value;
3072
- if (typeof value === "string" || typeof value === "number") return String(value);
3073
- return JSON.stringify(value);
3074
- }
3075
- /** Resolve the ISO 4217 code for a money field: a per-record
3076
- * `currencyField` (when present and non-blank) wins over the field's
3077
- * literal `currency`. Only `money` / `derived` variants carry currency
3078
- * keys; any other field resolves to undefined (the formatter's USD
3079
- * fallback), as before. */
3080
- function resolveCurrency(field, record) {
3081
- if (field.type !== "money" && field.type !== "derived") return void 0;
3082
- if (field.currencyField && record) {
3083
- const code = record[field.currencyField];
3084
- if (typeof code === "string" && code.trim().length > 0) return code;
3085
- }
3086
- return field.currency;
3087
- }
3088
- function formatMoney(value, currency, displayLocale) {
3089
- if (value === void 0 || value === "") return EM_DASH;
3090
- const amount = typeof value === "number" ? value : Number(value);
3091
- if (!Number.isFinite(amount)) return String(value);
3092
- const currencyCode = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3093
- try {
3094
- return new Intl.NumberFormat(displayLocale, {
3095
- style: "currency",
3096
- currency: currencyCode
3097
- }).format(amount);
3098
- } catch {
3099
- return String(amount);
3100
- }
3101
- }
3102
- function currencySymbolForLocale(currency, locale) {
3103
- const code = currency && currency.length > 0 ? currency : DEFAULT_CURRENCY;
3104
- try {
3105
- return new Intl.NumberFormat(locale, {
3106
- style: "currency",
3107
- currency: code
3108
- }).formatToParts(0).find((entry) => entry.type === "currency")?.value ?? code;
3109
- } catch {
3110
- return code;
3111
- }
3112
- }
3113
- function tableRows(value) {
3114
- if (!Array.isArray(value)) return [];
3115
- return value.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row));
3116
- }
3117
- function hasTableRows(value) {
3118
- return tableRows(value).length > 0;
3119
- }
3120
- /** Pick the field used to label a referenced/embedded record: prefer a
3121
- * `name` field, then `title`, else fall back to the primary key. */
3122
- function displayFieldFor(fields, primaryKey) {
3123
- if ("name" in fields) return "name";
3124
- if ("title" in fields) return "title";
3125
- return primaryKey;
3126
- }
3127
- function uniqueRefTargets(schema) {
3128
- const targets = /* @__PURE__ */ new Set();
3129
- const walk = (fields) => {
3130
- for (const field of Object.values(fields)) {
3131
- if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3132
- if (field.type === "table" && field.of) walk(field.of);
3133
- }
3134
- };
3135
- walk(schema.fields);
3136
- return [...targets];
3137
- }
3138
- function uniqueEmbedTargets(schema) {
3139
- const targets = /* @__PURE__ */ new Set();
3140
- for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
3141
- return [...targets];
3142
- }
3143
- function buildRefDisplayMap(detail) {
3144
- const { fields, primaryKey } = detail.collection.schema;
3145
- const displayField = displayFieldFor(fields, primaryKey);
3146
- const map = {};
3147
- for (const item of detail.items) {
3148
- const slugRaw = item[primaryKey];
3149
- if (typeof slugRaw !== "string" || slugRaw.length === 0) continue;
3150
- const displayRaw = item[displayField];
3151
- map[slugRaw] = typeof displayRaw === "string" && displayRaw.length > 0 ? displayRaw : slugRaw;
3152
- }
3153
- return map;
3154
- }
3155
- function buildRefRecordMap(detail) {
3156
- const { schema } = detail.collection;
3157
- const map = {};
3158
- for (const item of detail.items) {
3159
- const slugRaw = item[schema.primaryKey];
3160
- if (typeof slugRaw === "string" && slugRaw.length > 0) map[slugRaw] = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, {});
3161
- }
3162
- return map;
3163
- }
3164
- function sortedRefOptions(map) {
3165
- return Object.entries(map).map(([slug, display]) => ({
3166
- slug,
3167
- display
3168
- })).sort((left, right) => left.display.localeCompare(right.display));
3169
- }
3170
- /** Dropdown options for an `embed` field's per-record picker: every
3171
- * record in the target collection, labelled by its name/title (or
3172
- * primary key), skipping records without a slug and sorted by label. */
3173
- function buildEmbedOptions(schema, items) {
3174
- const { fields, primaryKey } = schema;
3175
- const displayField = displayFieldFor(fields, primaryKey);
3176
- return items.map((item) => {
3177
- const slug = String(item[primaryKey] ?? "");
3178
- const labelRaw = item[displayField];
3179
- return {
3180
- slug,
3181
- display: typeof labelRaw === "string" && labelRaw.length > 0 ? labelRaw : slug
3182
- };
3183
- }).filter((opt) => opt.slug.length > 0).sort((left, right) => left.display.localeCompare(right.display));
3184
- }
3185
- //#endregion
3186
3451
  //#region src/vue/useLinkedCollectionCaches.ts
3187
3452
  /** The de-duplicated ref + embed target slugs a schema links to. `allTargets`
3188
- * is the union (each target fetched once even when both ref'd and embedded). */
3453
+ * is the union (each target fetched once even when both ref'd and embedded).
3454
+ * `backlinks` SOURCE collections ride in `embedTargets`: a backlink needs
3455
+ * exactly what an embed target caches (the source's schema + items in
3456
+ * `embedCache`), so reverse sources reuse that cache rather than adding a
3457
+ * parallel one. */
3189
3458
  function linkedTargets(schema) {
3190
3459
  const refTargets = new Set(uniqueRefTargets(schema));
3191
- const embedTargets = new Set(uniqueEmbedTargets(schema));
3460
+ const embedTargets = /* @__PURE__ */ new Set([...uniqueEmbedTargets(schema), ...uniqueBacklinkSources(schema)]);
3192
3461
  return {
3193
3462
  refTargets,
3194
3463
  embedTargets,
@@ -3333,14 +3602,113 @@ function buildEmbedViews(schema, embedCache, record, locale) {
3333
3602
  }
3334
3603
  return out;
3335
3604
  }
3605
+ /** One backlinks table CELL: money formatted with its currency, anything
3606
+ * else through the same `formatCell` list tables use — so a markdown
3607
+ * source column (a worklog `notes`) shows the one-line 80-char preview,
3608
+ * not the whole text. Unknown `display` key ⇒ plain text (fail-soft). */
3609
+ function formatBacklinkCell(sourceField, value, row, locale) {
3610
+ if (!sourceField) return detailText(value);
3611
+ if (sourceField.type === "money") return formatMoney(value, resolveCurrency(sourceField, row), locale);
3612
+ return formatCell(value, sourceField.type);
3613
+ }
3614
+ /** Build the read-only backlinks view-models for one record: for each
3615
+ * `backlinks` field, the rows of `from` whose `via` points at the open
3616
+ * record (matched via the SHARED `backlinkRows`, on source records
3617
+ * derived exactly like the server's enrichment — so a `display`/`filter`
3618
+ * on a derived column such as an invoice `total` agrees on both sides).
3619
+ * Source data comes out of `embedCache` (reverse sources ride the embed
3620
+ * fan-out — see `linkedTargets`). Fail-soft: an unloadable source or an
3621
+ * unknown `display` key degrades to `found: false` / a raw-key column,
3622
+ * never a throw. */
3623
+ function buildBacklinksViews(schema, embedCache, record, locale) {
3624
+ const out = {};
3625
+ if (!schema) return out;
3626
+ for (const [key, field] of Object.entries(schema.fields)) {
3627
+ if (field.type !== "backlinks") continue;
3628
+ const columns = field.display.map((col) => ({
3629
+ key: col,
3630
+ label: col
3631
+ }));
3632
+ const data = embedCache[field.from];
3633
+ if (!data) {
3634
+ out[key] = {
3635
+ found: false,
3636
+ columns,
3637
+ rows: [],
3638
+ fromSlug: field.from
3639
+ };
3640
+ continue;
3641
+ }
3642
+ for (const column of columns) column.label = data.schema.fields[column.key]?.label ?? column.key;
3643
+ out[key] = {
3644
+ found: true,
3645
+ columns,
3646
+ rows: (0, _mulmoclaude_core_collection.backlinkRows)(field, String(record?.[schema.primaryKey] ?? ""), derivedSourceItems(embedCache, field.from) ?? []).map((row) => ({
3647
+ id: String(row[data.schema.primaryKey] ?? ""),
3648
+ cells: columns.map((column) => formatBacklinkCell(data.schema.fields[column.key], row[column.key], row, locale))
3649
+ })),
3650
+ fromSlug: field.from
3651
+ };
3652
+ }
3653
+ return out;
3654
+ }
3655
+ var derivedSourceMemo = /* @__PURE__ */ new WeakMap();
3656
+ function derivedSourceItems(embedCache, from) {
3657
+ const data = embedCache[from];
3658
+ if (!data) return null;
3659
+ let bySlug = derivedSourceMemo.get(embedCache);
3660
+ if (!bySlug) {
3661
+ bySlug = /* @__PURE__ */ new Map();
3662
+ derivedSourceMemo.set(embedCache, bySlug);
3663
+ }
3664
+ let items = bySlug.get(from);
3665
+ if (!items) {
3666
+ items = Object.values(derivedRecordsById(data.schema, data.items));
3667
+ bySlug.set(from, items);
3668
+ }
3669
+ return items;
3670
+ }
3671
+ /** The rollup scalar for one record, from the reverse sources riding the
3672
+ * embed cache — the SAME index (non-empty ids, one record per id, derived
3673
+ * against themselves) and the SAME `rollupValue` the server enrichment
3674
+ * uses, so the cell and getItems can't disagree. Null (em-dash) when the
3675
+ * source collection isn't loadable; a real 0 for an empty match set. */
3676
+ function rollupValueFor(field, record, schema, embedCache) {
3677
+ if (field.type !== "rollup" || !schema || !record) return null;
3678
+ const items = derivedSourceItems(embedCache, field.from);
3679
+ if (items === null) return null;
3680
+ return (0, _mulmoclaude_core_collection.rollupValue)(field, String(record[schema.primaryKey] ?? ""), items);
3681
+ }
3682
+ /** Display string for a rollup cell: the aggregate as a plain number,
3683
+ * em-dash when the source collection couldn't be resolved. */
3684
+ function renderRollup(field, record, schema, embedCache) {
3685
+ const value = rollupValueFor(field, record, schema, embedCache);
3686
+ return value === null ? "—" : formatCell(value, "number");
3687
+ }
3688
+ /** Copy of `item` with every rollup field resolved from the reverse
3689
+ * sources — injected BEFORE the formula pass so a `derived` formula can
3690
+ * read rollup values as plain identifiers (`played = homePlayed +
3691
+ * awayPlayed`), in the same rollups-then-formulas order the server
3692
+ * enrichment runs. Returns `item` unchanged when the schema declares no
3693
+ * rollups (the overwhelmingly common case — no copy). */
3694
+ function withRollupValues(schema, item, embedCache) {
3695
+ if (!schema) return item;
3696
+ let out = item;
3697
+ for (const [key, field] of Object.entries(schema.fields)) {
3698
+ if (field.type !== "rollup") continue;
3699
+ if (out === item) out = { ...item };
3700
+ out[key] = rollupValueFor(field, item, schema, embedCache);
3701
+ }
3702
+ return out;
3703
+ }
3336
3704
  function renderSubCell(subField, value, record, refCache, locale) {
3337
3705
  if (subField.type === "money") return formatMoney(value, resolveCurrency(subField, record), locale);
3338
3706
  if (subField.type === "ref" && subField.to && typeof value === "string" && value.length > 0) return lookupRefDisplay(refCache, subField.to, value);
3339
3707
  return formatCell(value, subField.type);
3340
3708
  }
3341
- function evaluateDerived(field, fieldKey, item, schema, refRecords) {
3709
+ function evaluateDerived(field, fieldKey, item, schema, refRecords, embedCache = {}) {
3342
3710
  if (field.type !== "derived" || !schema) return null;
3343
- const result = (0, _mulmoclaude_core_collection.deriveAll)(schema, item, refRecords)[fieldKey];
3711
+ const result = (0, _mulmoclaude_core_collection.deriveAll)(schema, withRollupValues(schema, item, embedCache), refRecords)[fieldKey];
3344
3712
  return typeof result === "number" && Number.isFinite(result) ? result : null;
3345
3713
  }
3346
3714
  function renderDerived(field, computedValue, record, locale) {
@@ -3373,11 +3741,18 @@ function useCollectionRendering(collection, locale) {
3373
3741
  refOptions: (targetSlug) => refOptionsFor(refCache.value, targetSlug),
3374
3742
  embedOptions: (targetSlug) => embedOptionsFor(embedCache.value, targetSlug),
3375
3743
  embedViewsFor: (record) => buildEmbedViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
3744
+ backlinksViewsFor: (record) => buildBacklinksViews(collection.value?.schema ?? null, embedCache.value, record, locale.value),
3745
+ rollupDisplay: (field, record) => renderRollup(field, record, collection.value?.schema ?? null, embedCache.value),
3376
3746
  currencySymbol: (currency) => currencySymbolForLocale(currency, locale.value),
3377
3747
  artifactUrl: (value) => collectionUi().fileAssetUrl(value),
3378
3748
  fileRoutePath: (value) => collectionUi().fileRoutePath(value),
3379
3749
  formatSubCell: (subField, value, record) => renderSubCell(subField, value, record, refCache.value, locale.value),
3380
- evaluateDerivedAgainstItem: (field, fieldKey, item) => evaluateDerived(field, fieldKey, item, collection.value?.schema ?? null, refRecordCache.value),
3750
+ evaluateDerivedAgainstItem: (field, fieldKey, item) => evaluateDerived(field, fieldKey, item, collection.value?.schema ?? null, refRecordCache.value, embedCache.value),
3751
+ deriveRecord: (item) => {
3752
+ const schema = collection.value?.schema ?? null;
3753
+ if (!schema) return item;
3754
+ return (0, _mulmoclaude_core_collection.deriveAll)(schema, withRollupValues(schema, item, embedCache.value), refRecordCache.value);
3755
+ },
3381
3756
  derivedDisplay: (field, computedValue, record) => renderDerived(field, computedValue, record, locale.value)
3382
3757
  };
3383
3758
  }
@@ -3494,206 +3869,211 @@ var _hoisted_11$4 = [
3494
3869
  ];
3495
3870
  var _hoisted_12$4 = {
3496
3871
  key: 0,
3872
+ class: "material-icons text-sm animate-spin"
3873
+ };
3874
+ var _hoisted_13$4 = {
3875
+ key: 1,
3497
3876
  class: "material-icons text-sm"
3498
3877
  };
3499
- var _hoisted_13$4 = ["title", "aria-label"];
3500
3878
  var _hoisted_14$4 = ["title", "aria-label"];
3501
- var _hoisted_15$4 = {
3879
+ var _hoisted_15$4 = ["title", "aria-label"];
3880
+ var _hoisted_16$4 = {
3502
3881
  key: 1,
3503
3882
  class: "mx-6 mt-2 rounded-lg border border-indigo-200 bg-indigo-50/60 px-4 py-2 text-sm text-indigo-800 flex items-center gap-2",
3504
3883
  "data-testid": "collections-refresh-note"
3505
3884
  };
3506
- var _hoisted_16$4 = { class: "flex-1" };
3507
- var _hoisted_17$4 = {
3885
+ var _hoisted_17$4 = { class: "flex-1" };
3886
+ var _hoisted_18$4 = {
3508
3887
  key: 2,
3509
3888
  class: "px-6 py-3 bg-white border-b border-slate-100 flex items-center justify-between gap-4"
3510
3889
  };
3511
- var _hoisted_18$4 = {
3890
+ var _hoisted_19$2 = {
3512
3891
  key: 0,
3513
3892
  class: "relative flex-1 max-w-md"
3514
3893
  };
3515
- var _hoisted_19$2 = ["placeholder", "aria-label"];
3516
- var _hoisted_20$2 = ["aria-label"];
3517
- var _hoisted_21$2 = { class: "flex items-center gap-2" };
3518
- var _hoisted_22$2 = ["aria-label"];
3519
- var _hoisted_23$2 = ["aria-pressed"];
3894
+ var _hoisted_20$2 = ["placeholder", "aria-label"];
3895
+ var _hoisted_21$2 = ["aria-label"];
3896
+ var _hoisted_22$2 = { class: "flex items-center gap-2" };
3897
+ var _hoisted_23$2 = ["aria-label"];
3520
3898
  var _hoisted_24$1 = ["aria-pressed"];
3521
3899
  var _hoisted_25$1 = ["aria-pressed"];
3522
- var _hoisted_26$1 = [
3900
+ var _hoisted_26$1 = ["aria-pressed"];
3901
+ var _hoisted_27$1 = [
3523
3902
  "aria-pressed",
3524
3903
  "data-testid",
3525
3904
  "onClick"
3526
3905
  ];
3527
- var _hoisted_27$1 = { class: "material-icons text-sm" };
3528
- var _hoisted_28 = [
3906
+ var _hoisted_28 = { class: "material-icons text-sm" };
3907
+ var _hoisted_29 = [
3529
3908
  "title",
3530
3909
  "aria-label",
3531
3910
  "aria-expanded"
3532
3911
  ];
3533
- var _hoisted_29 = {
3912
+ var _hoisted_30 = {
3534
3913
  key: 0,
3535
3914
  class: "absolute left-0 top-full mt-1 z-20 min-w-max rounded border border-slate-200 bg-white shadow-lg py-1",
3536
3915
  "data-testid": "collection-view-add-menu"
3537
3916
  };
3538
- var _hoisted_30 = ["title", "aria-label"];
3539
- var _hoisted_31 = ["value", "aria-label"];
3540
- var _hoisted_32 = ["value"];
3541
- var _hoisted_33 = ["value", "aria-label"];
3542
- var _hoisted_34 = ["value"];
3543
- var _hoisted_35 = {
3917
+ var _hoisted_31 = ["title", "aria-label"];
3918
+ var _hoisted_32 = ["value", "aria-label"];
3919
+ var _hoisted_33 = ["value"];
3920
+ var _hoisted_34 = ["value", "aria-label"];
3921
+ var _hoisted_35 = ["value"];
3922
+ var _hoisted_36 = {
3544
3923
  key: 3,
3545
3924
  class: "text-[10px] text-slate-400 font-bold uppercase tracking-wider select-none"
3546
3925
  };
3547
- var _hoisted_36 = {
3926
+ var _hoisted_37 = {
3548
3927
  key: 3,
3549
3928
  class: "mx-6 mt-4 rounded-xl border border-amber-200 bg-amber-50/60 p-4 text-sm text-amber-900 shadow-sm flex items-center gap-3",
3550
3929
  "data-testid": "collections-data-issues"
3551
3930
  };
3552
- var _hoisted_37 = { class: "flex-1" };
3553
- var _hoisted_38 = { class: "flex-1 overflow-auto" };
3554
- var _hoisted_39 = {
3931
+ var _hoisted_38 = { class: "flex-1" };
3932
+ var _hoisted_39 = { class: "flex-1 overflow-auto" };
3933
+ var _hoisted_40 = {
3555
3934
  key: 0,
3556
3935
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-500 gap-3"
3557
3936
  };
3558
- var _hoisted_40 = {
3937
+ var _hoisted_41 = {
3559
3938
  key: 1,
3560
3939
  class: "m-6 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3"
3561
3940
  };
3562
- var _hoisted_41 = { key: 2 };
3563
- var _hoisted_42 = {
3941
+ var _hoisted_42 = { key: 2 };
3942
+ var _hoisted_43 = {
3564
3943
  key: 3,
3565
3944
  class: "p-4"
3566
3945
  };
3567
- var _hoisted_43 = {
3946
+ var _hoisted_44 = {
3568
3947
  key: 4,
3569
3948
  class: "h-full flex flex-col"
3570
3949
  };
3571
- var _hoisted_44 = {
3950
+ var _hoisted_45 = {
3572
3951
  key: 0,
3573
3952
  class: "m-3 mb-0 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3",
3574
3953
  "data-testid": "collections-inline-error"
3575
3954
  };
3576
- var _hoisted_45 = { class: "flex-1" };
3577
- var _hoisted_46 = ["aria-label"];
3578
- var _hoisted_47 = { class: "flex-1 min-h-0 px-3 py-2" };
3579
- var _hoisted_48 = {
3955
+ var _hoisted_46 = { class: "flex-1" };
3956
+ var _hoisted_47 = ["aria-label"];
3957
+ var _hoisted_48 = { class: "flex-1 min-h-0 px-3 py-2" };
3958
+ var _hoisted_49 = {
3580
3959
  key: 5,
3581
3960
  class: "h-full",
3582
3961
  "data-testid": "collection-custom-view-body"
3583
3962
  };
3584
- var _hoisted_49 = {
3963
+ var _hoisted_50 = {
3585
3964
  key: 6,
3586
3965
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3587
3966
  };
3588
- var _hoisted_50 = { class: "font-semibold text-slate-600" };
3589
- var _hoisted_51 = {
3967
+ var _hoisted_51 = { class: "font-semibold text-slate-600" };
3968
+ var _hoisted_52 = {
3590
3969
  key: 7,
3591
3970
  class: "flex flex-col items-center justify-center py-20 text-sm text-slate-400 gap-2"
3592
3971
  };
3593
- var _hoisted_52 = { class: "font-semibold text-slate-600" };
3594
- var _hoisted_53 = {
3972
+ var _hoisted_53 = { class: "font-semibold text-slate-600" };
3973
+ var _hoisted_54 = {
3595
3974
  key: 8,
3596
3975
  class: "overflow-x-auto [container-type:inline-size]"
3597
3976
  };
3598
- var _hoisted_54 = {
3977
+ var _hoisted_55 = {
3599
3978
  key: 0,
3600
3979
  class: "m-4 rounded-xl border border-red-200 bg-red-50/50 p-4 text-sm text-red-800 shadow-sm flex items-center gap-3",
3601
3980
  "data-testid": "collections-inline-error"
3602
3981
  };
3603
- var _hoisted_55 = { class: "flex-1" };
3604
- var _hoisted_56 = ["aria-label"];
3605
- var _hoisted_57 = { class: "min-w-full text-xs" };
3606
- var _hoisted_58 = { class: "bg-slate-50 border-b border-slate-200" };
3607
- var _hoisted_59 = ["aria-sort"];
3608
- var _hoisted_60 = { class: "flex items-center gap-1" };
3609
- var _hoisted_61 = ["title"];
3610
- var _hoisted_62 = [
3982
+ var _hoisted_56 = { class: "flex-1" };
3983
+ var _hoisted_57 = ["aria-label"];
3984
+ var _hoisted_58 = { class: "min-w-full text-xs" };
3985
+ var _hoisted_59 = { class: "bg-slate-50 border-b border-slate-200" };
3986
+ var _hoisted_60 = ["aria-sort"];
3987
+ var _hoisted_61 = { class: "flex items-center gap-1" };
3988
+ var _hoisted_62 = ["title"];
3989
+ var _hoisted_63 = [
3611
3990
  "data-testid",
3612
3991
  "aria-label",
3613
3992
  "onClick",
3614
3993
  "onPointerenter"
3615
3994
  ];
3616
- var _hoisted_63 = { class: "material-icons text-base align-middle" };
3617
- var _hoisted_64 = { class: "divide-y divide-slate-100 bg-white" };
3618
- var _hoisted_65 = [
3995
+ var _hoisted_64 = { class: "material-icons text-base align-middle" };
3996
+ var _hoisted_65 = { class: "divide-y divide-slate-100 bg-white" };
3997
+ var _hoisted_66 = [
3619
3998
  "aria-label",
3620
3999
  "data-testid",
3621
4000
  "onClick",
3622
4001
  "onKeydown"
3623
4002
  ];
3624
- var _hoisted_66 = [
4003
+ var _hoisted_67 = [
3625
4004
  "checked",
3626
4005
  "disabled",
3627
4006
  "data-testid",
3628
4007
  "aria-label",
3629
4008
  "onChange"
3630
4009
  ];
3631
- var _hoisted_67 = [
4010
+ var _hoisted_68 = [
3632
4011
  "checked",
3633
4012
  "disabled",
3634
4013
  "data-testid",
3635
4014
  "aria-label",
3636
4015
  "onChange"
3637
4016
  ];
3638
- var _hoisted_68 = {
4017
+ var _hoisted_69 = {
3639
4018
  key: 2,
3640
4019
  class: "block truncate"
3641
4020
  };
3642
- var _hoisted_69 = [
4021
+ var _hoisted_70 = [
3643
4022
  "href",
3644
4023
  "tabindex",
3645
4024
  "data-testid",
3646
4025
  "onClick",
3647
4026
  "onKeydown"
3648
4027
  ];
3649
- var _hoisted_70 = [
4028
+ var _hoisted_71 = [
3650
4029
  "value",
3651
4030
  "disabled",
3652
4031
  "data-testid",
3653
4032
  "aria-label",
3654
4033
  "onChange"
3655
4034
  ];
3656
- var _hoisted_71 = {
4035
+ var _hoisted_72 = {
3657
4036
  key: 0,
3658
4037
  value: ""
3659
4038
  };
3660
- var _hoisted_72 = ["value"];
3661
- var _hoisted_73 = {
4039
+ var _hoisted_73 = ["value"];
4040
+ var _hoisted_74 = {
3662
4041
  key: 4,
3663
4042
  class: "block truncate tabular-nums font-semibold text-slate-900"
3664
4043
  };
3665
- var _hoisted_74 = {
4044
+ var _hoisted_75 = {
3666
4045
  key: 5,
3667
4046
  class: "inline-flex items-center gap-1 px-2 py-0.5 rounded-lg text-[10px] font-bold bg-slate-100 text-slate-600 border border-slate-200/40"
3668
4047
  };
3669
- var _hoisted_75 = {
4048
+ var _hoisted_76 = {
3670
4049
  key: 6,
3671
4050
  class: "inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-1.5 py-0.5 rounded border border-indigo-100/50"
3672
4051
  };
3673
- var _hoisted_76 = ["href", "data-testid"];
3674
- var _hoisted_77 = ["href", "data-testid"];
3675
- var _hoisted_78 = [
4052
+ var _hoisted_77 = ["data-testid"];
4053
+ var _hoisted_78 = ["href", "data-testid"];
4054
+ var _hoisted_79 = ["href", "data-testid"];
4055
+ var _hoisted_80 = [
3676
4056
  "href",
3677
4057
  "data-testid",
3678
4058
  "onClick"
3679
4059
  ];
3680
- var _hoisted_79 = {
3681
- key: 10,
4060
+ var _hoisted_81 = {
4061
+ key: 11,
3682
4062
  class: "block truncate text-slate-600"
3683
4063
  };
3684
- var _hoisted_80 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
3685
- var _hoisted_81 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
3686
- var _hoisted_82 = { class: "flex-1" };
3687
- var _hoisted_83 = {
4064
+ var _hoisted_82 = { class: "bg-white rounded-2xl shadow-2xl w-full max-w-xl flex flex-col border border-slate-200 overflow-hidden" };
4065
+ var _hoisted_83 = { class: "px-6 py-4 border-b border-slate-100 flex items-center gap-3 bg-slate-50/50" };
4066
+ var _hoisted_84 = { class: "flex-1" };
4067
+ var _hoisted_85 = {
3688
4068
  id: "collections-chat-title",
3689
4069
  class: "text-sm font-bold text-slate-800 uppercase tracking-wide"
3690
4070
  };
3691
- var _hoisted_84 = { class: "text-xs text-slate-400 font-semibold" };
3692
- var _hoisted_85 = ["aria-label"];
3693
- var _hoisted_86 = { class: "px-6 py-5" };
3694
- var _hoisted_87 = ["placeholder", "onKeydown"];
3695
- var _hoisted_88 = { class: "px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50" };
3696
- var _hoisted_89 = ["disabled"];
4071
+ var _hoisted_86 = { class: "text-xs text-slate-400 font-semibold" };
4072
+ var _hoisted_87 = ["aria-label"];
4073
+ var _hoisted_88 = { class: "px-6 py-5" };
4074
+ var _hoisted_89 = ["placeholder", "onKeydown"];
4075
+ var _hoisted_90 = { class: "px-6 py-3.5 border-t border-slate-100 flex items-center justify-end gap-2 bg-slate-50/50" };
4076
+ var _hoisted_91 = ["disabled"];
3697
4077
  /** `slug` / `selected` are supplied only in EMBEDDED mode (the
3698
4078
  * `presentCollection` chat card mounts this component and drives both
3699
4079
  * from the tool result). In standalone route mode (the
@@ -3795,11 +4175,26 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3795
4175
  const actionPending = (0, vue.ref)(false);
3796
4176
  const actionError = (0, vue.ref)(null);
3797
4177
  const collectionActionPending = (0, vue.ref)(false);
4178
+ const runningActions = (0, vue.ref)(/* @__PURE__ */ new Set());
4179
+ let runningActionsGen = 0;
4180
+ function mutateRunningActions(mutate) {
4181
+ runningActionsGen += 1;
4182
+ const next = new Set(runningActions.value);
4183
+ mutate(next);
4184
+ runningActions.value = next;
4185
+ }
4186
+ /** Adopt a detail response's `runningActions` — only when no local
4187
+ * mutation happened while the response was in flight (stale snapshots
4188
+ * are dropped; the completion ping's refetch reconciles soon after). */
4189
+ function applyServerRunningActions(keys, genAtFetch) {
4190
+ if (runningActionsGen !== genAtFetch) return;
4191
+ runningActions.value = new Set(keys ?? []);
4192
+ }
3798
4193
  const chatOpen = (0, vue.ref)(false);
3799
4194
  const chatMessage = (0, vue.ref)("");
3800
4195
  const chatInputEl = (0, vue.ref)(null);
3801
4196
  const render = useCollectionRendering(collection, locale);
3802
- const { refRecordCache, refDisplay, formatMoney, resolveCurrency, derivedDisplay, evaluateDerivedAgainstItem, formatCell, isExternalUrl, artifactUrl, fileRoutePath } = render;
4197
+ const { refDisplay, formatMoney, resolveCurrency, derivedDisplay, evaluateDerivedAgainstItem, formatCell, isExternalUrl, artifactUrl, fileRoutePath } = render;
3803
4198
  const searchQuery = (0, vue.ref)("");
3804
4199
  /** Case-insensitive substring match across an item's scalar fields.
3805
4200
  * Object-valued fields (table rows, nested records) are skipped —
@@ -3873,7 +4268,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3873
4268
  function derivedSortValue(field, key, item) {
3874
4269
  const display = field.type === "derived" ? field.display : void 0;
3875
4270
  if (display === void 0 || display === "number" || display === "money") return (0, _mulmoclaude_core_collection.numericSortValue)(evaluateDerivedAgainstItem(field, key, item));
3876
- const enriched = collection.value ? render.deriveAll(collection.value.schema, item, render.refRecordCache.value) : item;
4271
+ const enriched = render.deriveRecord(item);
3877
4272
  if (display === "date") return (0, _mulmoclaude_core_collection.dateSortValue)(enriched[key]);
3878
4273
  return (0, _mulmoclaude_core_collection.stringSortValue)(enriched[key]);
3879
4274
  }
@@ -3961,20 +4356,33 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
3961
4356
  }
3962
4357
  /** Collection-level header actions. No `when` predicate (no record). */
3963
4358
  const collectionActions = (0, vue.computed)(() => collection.value?.schema.collectionActions ?? []);
4359
+ /** True when a `kind: "agent"` action's hidden worker is in flight —
4360
+ * drives the button spinner + disable. Keys mirror the server's
4361
+ * dispatch guard via `agentActionRunKey`. */
4362
+ function isActionRunning(actionId, itemId) {
4363
+ return runningActions.value.has((0, _mulmoclaude_core_collection.agentActionRunKey)(actionId, itemId));
4364
+ }
3964
4365
  /** Run a collection-level action: ask the server to assemble the seed
3965
- * prompt (a progress summary of all records + the template), then start
3966
- * a new chat in the action's role with it. Generic no domain knowledge. */
4366
+ * prompt (a progress summary of all records + the template). `kind:
4367
+ * "chat"` gets the seed back and starts a new chat; `kind: "agent"` is
4368
+ * dispatched server-side as a hidden worker — mark it running and let
4369
+ * the completion ping's refetch reconcile. Generic — no domain knowledge. */
3967
4370
  async function runCollectionAction(action) {
3968
4371
  const current = collection.value;
3969
- if (!current || collectionActionPending.value) return;
4372
+ if (!current || collectionActionPending.value || isActionRunning(action.id)) return;
4373
+ const runKey = action.kind === "agent" ? (0, _mulmoclaude_core_collection.agentActionRunKey)(action.id) : null;
4374
+ if (runKey) mutateRunningActions((next) => next.add(runKey));
3970
4375
  collectionActionPending.value = true;
3971
4376
  inlineError.value = null;
3972
4377
  const result = await cui.runCollectionAction(current.slug, action.id);
3973
4378
  collectionActionPending.value = false;
3974
4379
  if (!result.ok) {
4380
+ if (runKey && result.status !== 409) mutateRunningActions((next) => next.delete(runKey));
3975
4381
  inlineError.value = result.error;
3976
4382
  return;
3977
4383
  }
4384
+ if (result.data.dispatched) return;
4385
+ if (result.data.written) return;
3978
4386
  if (props.sendTextMessage) {
3979
4387
  props.sendTextMessage(result.data.prompt);
3980
4388
  return;
@@ -4009,27 +4417,88 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4009
4417
  if (!record) return [];
4010
4418
  return (collection.value?.schema.actions ?? []).filter((action) => (0, _mulmoclaude_core_collection.actionVisible)(action, record));
4011
4419
  });
4012
- /** Run a schema-declared action on the open record: ask the server to
4013
- * assemble the seed prompt, then start a new chat in the action's
4014
- * role with it. Generic — no knowledge of what the action does. */
4420
+ const mutateModal = (0, vue.ref)(null);
4421
+ const mutatePending = (0, vue.ref)(false);
4422
+ const mutateError = (0, vue.ref)(null);
4423
+ /** POST one mutate action and, on success, adopt the written record for
4424
+ * the open panel (the write's change ping refreshes the table rows).
4425
+ * Errors land in the modal when one is open, else on the panel. */
4426
+ async function executeMutate(action, itemId, params) {
4427
+ if (!collection.value || mutatePending.value) return false;
4428
+ mutatePending.value = true;
4429
+ const result = await cui.runItemAction(collection.value.slug, itemId, action.id, params);
4430
+ mutatePending.value = false;
4431
+ if (!result.ok) {
4432
+ if (mutateModal.value) mutateError.value = result.error;
4433
+ else actionError.value = result.error;
4434
+ return false;
4435
+ }
4436
+ if (result.data.written && viewing.value && String(viewing.value[collection.value.schema.primaryKey] ?? "") === itemId) viewing.value = result.data.item;
4437
+ return true;
4438
+ }
4439
+ async function submitMutateParams(params) {
4440
+ const open = mutateModal.value;
4441
+ if (!open) return;
4442
+ mutateError.value = null;
4443
+ if (await executeMutate(open.action, open.itemId, params)) mutateModal.value = null;
4444
+ }
4445
+ /** Mutate kind: open the params mini-form when the action declares one,
4446
+ * else apply the declarative write immediately. */
4447
+ async function runMutateAction(action, itemId) {
4448
+ actionError.value = null;
4449
+ if (action.params && Object.keys(action.params).length > 0) {
4450
+ mutateError.value = null;
4451
+ mutateModal.value = {
4452
+ action,
4453
+ itemId
4454
+ };
4455
+ return;
4456
+ }
4457
+ await executeMutate(action, itemId, {});
4458
+ }
4459
+ /** Run a schema-declared action on the open record. `kind: "mutate"`
4460
+ * never leaves the host: with `params` it opens the mini-form, else it
4461
+ * applies immediately. `kind: "chat"` gets the seed back and starts a
4462
+ * new chat; `kind: "agent"` is dispatched server-side as a hidden
4463
+ * worker — mark it running and let the completion ping's refetch
4464
+ * reconcile. Generic — no knowledge of what the action does. */
4015
4465
  async function runAction(action) {
4016
4466
  if (!collection.value || !viewing.value) return;
4017
4467
  const itemId = String(viewing.value[collection.value.schema.primaryKey] ?? "");
4018
- if (!itemId) return;
4468
+ if (!itemId || isActionRunning(action.id, itemId)) return;
4469
+ if (action.kind === "mutate") {
4470
+ await runMutateAction(action, itemId);
4471
+ return;
4472
+ }
4473
+ const runKey = action.kind === "agent" ? (0, _mulmoclaude_core_collection.agentActionRunKey)(action.id, itemId) : null;
4474
+ if (runKey) mutateRunningActions((next) => next.add(runKey));
4019
4475
  actionPending.value = true;
4020
4476
  actionError.value = null;
4021
4477
  const result = await cui.runItemAction(collection.value.slug, itemId, action.id);
4022
4478
  actionPending.value = false;
4023
4479
  if (!result.ok) {
4480
+ if (runKey && result.status !== 409) mutateRunningActions((next) => next.delete(runKey));
4024
4481
  actionError.value = result.error;
4025
4482
  return;
4026
4483
  }
4484
+ if (result.data.dispatched) return;
4485
+ if (result.data.written) return;
4027
4486
  if (props.sendTextMessage) {
4028
4487
  props.sendTextMessage(result.data.prompt);
4029
4488
  return;
4030
4489
  }
4031
4490
  appApi.startNewChat(result.data.prompt, result.data.role);
4032
4491
  }
4492
+ /** Ids of the open record's actions whose hidden worker is running — the
4493
+ * record panel renders those buttons with a spinner, disabled. */
4494
+ const viewingRunningActionIds = (0, vue.computed)(() => {
4495
+ const current = collection.value;
4496
+ const record = viewing.value;
4497
+ if (!current || !record) return [];
4498
+ const itemId = String(record[current.schema.primaryKey] ?? "");
4499
+ if (!itemId) return [];
4500
+ return (current.schema.actions ?? []).filter((action) => isActionRunning(action.id, itemId)).map((action) => action.id);
4501
+ });
4033
4502
  /** Open the chat modal, blanking any prior draft and focusing the input. */
4034
4503
  function openChat() {
4035
4504
  chatMessage.value = "";
@@ -4095,10 +4564,12 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4095
4564
  collection.value = null;
4096
4565
  items.value = [];
4097
4566
  dataIssues.value = [];
4567
+ mutateRunningActions((next) => next.clear());
4098
4568
  searchQuery.value = "";
4099
4569
  render.resetLinkedCaches();
4100
4570
  viewing.value = null;
4101
4571
  openDay.value = null;
4572
+ const runningGen = runningActionsGen;
4102
4573
  const result = await cui.fetchCollectionDetail(slug);
4103
4574
  loading.value = false;
4104
4575
  if (!result.ok) {
@@ -4109,6 +4580,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4109
4580
  collection.value = result.data.collection;
4110
4581
  items.value = result.data.items;
4111
4582
  dataIssues.value = result.data.issues ?? [];
4583
+ applyServerRunningActions(result.data.runningActions, runningGen);
4112
4584
  enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
4113
4585
  await render.loadLinkedCollections(result.data.collection.schema, slug);
4114
4586
  if (collection.value?.slug === slug) syncViewToSelected();
@@ -4124,11 +4596,13 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4124
4596
  * fetch is a no-op (keep the current data) — a transient blip shouldn't blank a
4125
4597
  * view the user is reading. */
4126
4598
  async function refreshItemsInPlace(slug) {
4599
+ const runningGen = runningActionsGen;
4127
4600
  const result = await cui.fetchCollectionDetail(slug);
4128
4601
  if (!result.ok || activeSlug.value !== slug) return;
4129
4602
  collection.value = result.data.collection;
4130
4603
  items.value = result.data.items;
4131
4604
  dataIssues.value = result.data.issues ?? [];
4605
+ applyServerRunningActions(result.data.runningActions, runningGen);
4132
4606
  enumOriginallyEmpty.value = snapshotEmptyEnums(result.data.collection.schema, result.data.items);
4133
4607
  await render.loadLinkedCollections(result.data.collection.schema, slug);
4134
4608
  if (activeSlug.value !== slug) return;
@@ -4149,7 +4623,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4149
4623
  * list table only (a whole embedded record doesn't fit a table cell,
4150
4624
  * and it'd be identical in every row). The detail modal and the edit
4151
4625
  * form iterate the full `schema.fields` so embeds render there too. */
4152
- const listColumnFields = (0, vue.computed)(() => collection.value ? Object.entries(collection.value.schema.fields).filter(([key, field]) => field.type !== "embed" && field.type !== "image" && key !== collection.value?.schema.primaryKey) : []);
4626
+ const listColumnFields = (0, vue.computed)(() => collection.value ? Object.entries(collection.value.schema.fields).filter(([key, field]) => field.type !== "embed" && field.type !== "backlinks" && field.type !== "image" && key !== collection.value?.schema.primaryKey) : []);
4153
4627
  /** True when the current collection declares `schema.singleton` —
4154
4628
  * exactly one record, its primary key fixed to the declared value. */
4155
4629
  const isSingleton = (0, vue.computed)(() => Boolean(collection.value?.schema.singleton));
@@ -4338,7 +4812,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4338
4812
  boolOriginallyPresent[key] = false;
4339
4813
  boolTouched[key] = false;
4340
4814
  } else if (field.type === "table") table[key] = [];
4341
- else if (field.type !== "derived" && field.type !== "embed" && field.type !== "toggle") text[key] = "";
4815
+ else if (!_mulmoclaude_core_collection.COMPUTED_TYPES.has(field.type)) text[key] = "";
4342
4816
  const { singleton, primaryKey } = collection.value.schema;
4343
4817
  if (singleton) text[primaryKey] = singleton;
4344
4818
  else if (primaryKey in text) text[primaryKey] = generateUniqueItemId(primaryKey);
@@ -4370,7 +4844,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4370
4844
  } else if (field.type === "table" && field.of) {
4371
4845
  const sub = field.of;
4372
4846
  table[key] = (Array.isArray(raw) ? raw : []).filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)).map((row) => (0, _mulmoclaude_core_collection.rowFromItem)(row, sub));
4373
- } else if (field.type !== "derived" && field.type !== "embed" && field.type !== "toggle") text[key] = raw === void 0 || raw === null ? "" : String(raw);
4847
+ } else if (!_mulmoclaude_core_collection.COMPUTED_TYPES.has(field.type)) text[key] = raw === void 0 || raw === null ? "" : String(raw);
4374
4848
  }
4375
4849
  const primaryRaw = item[collection.value.schema.primaryKey];
4376
4850
  const originalId = typeof primaryRaw === "string" ? primaryRaw : String(primaryRaw ?? "");
@@ -4497,7 +4971,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4497
4971
  * rendering composable; this binds it to the current draft. */
4498
4972
  const liveDerived = (0, vue.computed)(() => {
4499
4973
  if (!collection.value || !liveRecord.value) return null;
4500
- return render.deriveAll(collection.value.schema, liveRecord.value, refRecordCache.value);
4974
+ return render.deriveRecord(liveRecord.value);
4501
4975
  });
4502
4976
  /** Short summary for a `table`-typed cell in the main collection
4503
4977
  * table. Counts rows; nothing fancier yet (per-row preview is
@@ -4818,7 +5292,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4818
5292
  "aria-label": (0, vue.unref)(t)("collectionsView.backToIndex"),
4819
5293
  "data-testid": "collections-back",
4820
5294
  onClick: goBack
4821
- }, [..._cache[25] || (_cache[25] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : (0, vue.createCommentVNode)("", true),
5295
+ }, [..._cache[26] || (_cache[26] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "arrow_back", -1)])], 8, _hoisted_3$5)) : (0, vue.createCommentVNode)("", true),
4822
5296
  collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_4$5, [(0, vue.createElementVNode)("span", _hoisted_5$5, (0, vue.toDisplayString)(collection.value.icon), 1)])) : (0, vue.createCommentVNode)("", true),
4823
5297
  (0, vue.createElementVNode)("div", _hoisted_6$4, [(0, vue.createElementVNode)("h1", _hoisted_7$4, (0, vue.toDisplayString)(collection.value?.title ?? (0, vue.unref)(t)("collectionsView.title")), 1), collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8$4, (0, vue.toDisplayString)(collection.value.slug), 1)) : (0, vue.createCommentVNode)("", true)]),
4824
5298
  collection.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(pinToggle)), {
@@ -4847,16 +5321,16 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4847
5321
  class: "h-8 px-2.5 flex items-center gap-1 rounded border border-indigo-200 bg-white hover:bg-indigo-50 text-indigo-600 font-bold text-xs transition-colors",
4848
5322
  "data-testid": "collections-chat",
4849
5323
  onClick: openChat
4850
- }, [_cache[26] || (_cache[26] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "forum", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chat")), 1)])) : (0, vue.createCommentVNode)("", true),
5324
+ }, [_cache[27] || (_cache[27] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "forum", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chat")), 1)])) : (0, vue.createCommentVNode)("", true),
4851
5325
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(collectionActions.value, (action) => {
4852
5326
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4853
5327
  key: action.id,
4854
5328
  type: "button",
4855
5329
  class: "h-8 px-2.5 flex items-center gap-1 rounded border border-indigo-200 bg-white hover:bg-indigo-50 text-indigo-600 font-bold text-xs transition-colors disabled:opacity-50",
4856
- disabled: collectionActionPending.value,
5330
+ disabled: collectionActionPending.value || isActionRunning(action.id),
4857
5331
  "data-testid": `collections-action-${action.id}`,
4858
5332
  onClick: ($event) => runCollectionAction(action)
4859
- }, [action.icon ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_12$4, (0, vue.toDisplayString)(action.icon), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(action.label), 1)], 8, _hoisted_11$4);
5333
+ }, [isActionRunning(action.id) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_12$4, "progress_activity")) : action.icon ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_13$4, (0, vue.toDisplayString)(action.icon), 1)) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(action.label), 1)], 8, _hoisted_11$4);
4860
5334
  }), 128)),
4861
5335
  canCreate.value && !calendarActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4862
5336
  key: 5,
@@ -4864,7 +5338,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4864
5338
  class: "h-8 px-2.5 flex items-center gap-1 rounded bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-xs transition-colors shadow-sm",
4865
5339
  "data-testid": "collections-add-item",
4866
5340
  onClick: openCreate
4867
- }, [_cache[27] || (_cache[27] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.add")), 1)])) : (0, vue.createCommentVNode)("", true),
5341
+ }, [_cache[28] || (_cache[28] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.add")), 1)])) : (0, vue.createCommentVNode)("", true),
4868
5342
  canDeleteCollection.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4869
5343
  key: 6,
4870
5344
  type: "button",
@@ -4873,7 +5347,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4873
5347
  "aria-label": (0, vue.unref)(t)("collectionsView.deleteCollection"),
4874
5348
  "data-testid": "collections-delete",
4875
5349
  onClick: confirmCollectionDelete
4876
- }, [..._cache[28] || (_cache[28] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_13$4)) : (0, vue.createCommentVNode)("", true),
5350
+ }, [..._cache[29] || (_cache[29] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : (0, vue.createCommentVNode)("", true),
4877
5351
  canDeleteFeed.value && !embedded.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4878
5352
  key: 7,
4879
5353
  type: "button",
@@ -4882,26 +5356,26 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4882
5356
  "aria-label": (0, vue.unref)(t)("collectionsView.deleteFeed"),
4883
5357
  "data-testid": "feeds-delete",
4884
5358
  onClick: confirmFeedDelete
4885
- }, [..._cache[29] || (_cache[29] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_14$4)) : (0, vue.createCommentVNode)("", true)
5359
+ }, [..._cache[30] || (_cache[30] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "delete_forever", -1)])], 8, _hoisted_15$4)) : (0, vue.createCommentVNode)("", true)
4886
5360
  ])) : (0, vue.createCommentVNode)("", true),
4887
- refreshNote.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_15$4, [_cache[30] || (_cache[30] = (0, vue.createElementVNode)("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), (0, vue.createElementVNode)("span", _hoisted_16$4, (0, vue.toDisplayString)(refreshNote.value), 1)])) : (0, vue.createCommentVNode)("", true),
4888
- collection.value && (!__props.hideSearch && items.value.length > 0 || !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_17$4, [!__props.hideSearch && items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_18$4, [
4889
- _cache[32] || (_cache[32] = (0, vue.createElementVNode)("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "search")], -1)),
5361
+ refreshNote.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_16$4, [_cache[31] || (_cache[31] = (0, vue.createElementVNode)("span", { class: "material-icons text-base text-indigo-600" }, "hourglass_top", -1)), (0, vue.createElementVNode)("span", _hoisted_17$4, (0, vue.toDisplayString)(refreshNote.value), 1)])) : (0, vue.createCommentVNode)("", true),
5362
+ collection.value && (!__props.hideSearch && items.value.length > 0 || !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_18$4, [!__props.hideSearch && items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_19$2, [
5363
+ _cache[33] || (_cache[33] = (0, vue.createElementVNode)("span", { class: "absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400 pointer-events-none" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "search")], -1)),
4890
5364
  (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
4891
5365
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
4892
5366
  type: "text",
4893
5367
  placeholder: (0, vue.unref)(t)("collectionsView.searchPlaceholder"),
4894
5368
  "aria-label": (0, vue.unref)(t)("collectionsView.searchPlaceholder"),
4895
5369
  class: "w-full bg-slate-50 border border-slate-200/80 rounded-xl pl-9 pr-8 py-1.5 text-xs placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all font-medium"
4896
- }, null, 8, _hoisted_19$2), [[vue.vModelText, searchQuery.value]]),
5370
+ }, null, 8, _hoisted_20$2), [[vue.vModelText, searchQuery.value]]),
4897
5371
  searchQuery.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4898
5372
  key: 0,
4899
5373
  type: "button",
4900
5374
  "aria-label": (0, vue.unref)(t)("collectionsView.clearSearch"),
4901
5375
  class: "absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-400 hover:text-slate-600",
4902
5376
  onClick: _cache[1] || (_cache[1] = ($event) => searchQuery.value = "")
4903
- }, [..._cache[31] || (_cache[31] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_20$2)) : (0, vue.createCommentVNode)("", true)
4904
- ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_21$2, [
5377
+ }, [..._cache[32] || (_cache[32] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "close", -1)])], 8, _hoisted_21$2)) : (0, vue.createCommentVNode)("", true)
5378
+ ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_22$2, [
4905
5379
  !__props.hideViewToggle && (hasCalendar.value || hasKanban.value || hasCustomViews.value || canAddCustomView.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
4906
5380
  key: 0,
4907
5381
  class: "flex gap-0.5",
@@ -4914,7 +5388,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4914
5388
  "aria-pressed": activeView.value === "table",
4915
5389
  "data-testid": "collection-view-toggle-table",
4916
5390
  onClick: _cache[2] || (_cache[2] = ($event) => setView("table"))
4917
- }, [_cache[33] || (_cache[33] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "table_rows", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewTable")), 1)], 10, _hoisted_23$2),
5391
+ }, [_cache[34] || (_cache[34] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "table_rows", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewTable")), 1)], 10, _hoisted_24$1),
4918
5392
  hasCalendar.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4919
5393
  key: 0,
4920
5394
  type: "button",
@@ -4922,7 +5396,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4922
5396
  "aria-pressed": activeView.value === "calendar",
4923
5397
  "data-testid": "collection-view-toggle-calendar",
4924
5398
  onClick: _cache[3] || (_cache[3] = ($event) => setView("calendar"))
4925
- }, [_cache[34] || (_cache[34] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "calendar_month", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_24$1)) : (0, vue.createCommentVNode)("", true),
5399
+ }, [_cache[35] || (_cache[35] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "calendar_month", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewCalendar")), 1)], 10, _hoisted_25$1)) : (0, vue.createCommentVNode)("", true),
4926
5400
  hasKanban.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4927
5401
  key: 1,
4928
5402
  type: "button",
@@ -4930,7 +5404,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4930
5404
  "aria-pressed": activeView.value === "kanban",
4931
5405
  "data-testid": "collection-view-toggle-kanban",
4932
5406
  onClick: _cache[4] || (_cache[4] = ($event) => setView("kanban"))
4933
- }, [_cache[35] || (_cache[35] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "view_kanban", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_25$1)) : (0, vue.createCommentVNode)("", true),
5407
+ }, [_cache[36] || (_cache[36] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "view_kanban", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.viewKanban")), 1)], 10, _hoisted_26$1)) : (0, vue.createCommentVNode)("", true),
4934
5408
  ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(customViews.value, (cv) => {
4935
5409
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4936
5410
  key: cv.id,
@@ -4939,7 +5413,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4939
5413
  "aria-pressed": activeView.value === (0, vue.unref)(customViewKey)(cv.id),
4940
5414
  "data-testid": `collection-view-custom-${cv.id}`,
4941
5415
  onClick: ($event) => setCustomView(cv.id)
4942
- }, [(0, vue.createElementVNode)("span", _hoisted_27$1, (0, vue.toDisplayString)(cv.icon || (cv.target === "mobile" ? "smartphone" : "dashboard_customize")), 1), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(cv.label), 1)], 10, _hoisted_26$1);
5416
+ }, [(0, vue.createElementVNode)("span", _hoisted_28, (0, vue.toDisplayString)(cv.icon || (cv.target === "mobile" ? "smartphone" : "dashboard_customize")), 1), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(cv.label), 1)], 10, _hoisted_27$1);
4943
5417
  }), 128)),
4944
5418
  canAddCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
4945
5419
  key: 2,
@@ -4954,17 +5428,17 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4954
5428
  "aria-expanded": addMenuOpen.value,
4955
5429
  "data-testid": "collection-view-add",
4956
5430
  onClick: onAddViewClick
4957
- }, [..._cache[36] || (_cache[36] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_28), addMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_29, [(0, vue.createElementVNode)("button", {
5431
+ }, [..._cache[37] || (_cache[37] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "add", -1)])], 8, _hoisted_29), addMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_30, [(0, vue.createElementVNode)("button", {
4958
5432
  type: "button",
4959
5433
  class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4960
5434
  "data-testid": "collection-view-add-desktop",
4961
5435
  onClick: _cache[5] || (_cache[5] = ($event) => addCustomView("desktop"))
4962
- }, [_cache[37] || (_cache[37] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "dashboard_customize", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewDesktop")), 1)]), (0, vue.createElementVNode)("button", {
5436
+ }, [_cache[38] || (_cache[38] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "dashboard_customize", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewDesktop")), 1)]), (0, vue.createElementVNode)("button", {
4963
5437
  type: "button",
4964
5438
  class: "w-full h-8 px-3 flex items-center gap-2 text-xs font-bold text-slate-600 hover:bg-slate-50",
4965
5439
  "data-testid": "collection-view-add-mobile",
4966
5440
  onClick: _cache[6] || (_cache[6] = ($event) => addCustomView("mobile"))
4967
- }, [_cache[38] || (_cache[38] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "smartphone", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewMobile")), 1)])])) : (0, vue.createCommentVNode)("", true)], 512)) : (0, vue.createCommentVNode)("", true),
5441
+ }, [_cache[39] || (_cache[39] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "smartphone", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.addViewMobile")), 1)])])) : (0, vue.createCommentVNode)("", true)], 512)) : (0, vue.createCommentVNode)("", true),
4968
5442
  canConfigureViews.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
4969
5443
  key: 3,
4970
5444
  type: "button",
@@ -4973,8 +5447,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4973
5447
  "aria-label": (0, vue.unref)(t)("collectionsView.config.open"),
4974
5448
  "data-testid": "collection-config-open",
4975
5449
  onClick: _cache[7] || (_cache[7] = ($event) => configOpen.value = true)
4976
- }, [..._cache[39] || (_cache[39] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_30)) : (0, vue.createCommentVNode)("", true)
4977
- ], 8, _hoisted_22$2)) : (0, vue.createCommentVNode)("", true),
5450
+ }, [..._cache[40] || (_cache[40] = [(0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "settings", -1)])], 8, _hoisted_31)) : (0, vue.createCommentVNode)("", true)
5451
+ ], 8, _hoisted_23$2)) : (0, vue.createCommentVNode)("", true),
4978
5452
  calendarActive.value && dateFields.value.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
4979
5453
  key: 1,
4980
5454
  value: calendarAnchorField.value,
@@ -4986,8 +5460,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4986
5460
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
4987
5461
  key,
4988
5462
  value: key
4989
- }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_32);
4990
- }), 128))], 40, _hoisted_31)) : (0, vue.createCommentVNode)("", true),
5463
+ }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_33);
5464
+ }), 128))], 40, _hoisted_32)) : (0, vue.createCommentVNode)("", true),
4991
5465
  kanbanActive.value && enumFields.value.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
4992
5466
  key: 2,
4993
5467
  value: kanbanGroupField.value,
@@ -4999,24 +5473,24 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
4999
5473
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
5000
5474
  key,
5001
5475
  value: key
5002
- }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_34);
5003
- }), 128))], 40, _hoisted_33)) : (0, vue.createCommentVNode)("", true),
5004
- items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_35, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.searchSummary", {
5476
+ }, (0, vue.toDisplayString)(collection.value?.schema.fields[key]?.label ?? key), 9, _hoisted_35);
5477
+ }), 128))], 40, _hoisted_34)) : (0, vue.createCommentVNode)("", true),
5478
+ items.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_36, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.searchSummary", {
5005
5479
  shown: filteredItems.value.length,
5006
5480
  total: items.value.length
5007
5481
  })), 1)) : (0, vue.createCommentVNode)("", true)
5008
5482
  ])])) : (0, vue.createCommentVNode)("", true),
5009
- collection.value && dataIssues.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_36, [
5010
- _cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "material-icons text-amber-600" }, "warning", -1)),
5011
- (0, vue.createElementVNode)("span", _hoisted_37, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
5483
+ collection.value && dataIssues.value.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_37, [
5484
+ _cache[42] || (_cache[42] = (0, vue.createElementVNode)("span", { class: "material-icons text-amber-600" }, "warning", -1)),
5485
+ (0, vue.createElementVNode)("span", _hoisted_38, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.dataIssuesDetected", { count: dataIssues.value.length })), 1),
5012
5486
  (0, vue.createElementVNode)("button", {
5013
5487
  type: "button",
5014
5488
  class: "h-8 px-2.5 flex items-center gap-1 rounded border border-amber-300 bg-white hover:bg-amber-100 text-amber-700 font-bold text-xs transition-colors",
5015
5489
  "data-testid": "collections-repair",
5016
5490
  onClick: repairCollection
5017
- }, [_cache[40] || (_cache[40] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "build", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.repair")), 1)])
5491
+ }, [_cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "material-icons text-sm" }, "build", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.repair")), 1)])
5018
5492
  ])) : (0, vue.createCommentVNode)("", true),
5019
- (0, vue.createElementVNode)("div", _hoisted_38, [loading.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_39, [_cache[42] || (_cache[42] = (0, vue.createElementVNode)("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.loading")), 1)])) : loadError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_40, [_cache[43] || (_cache[43] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(loadError.value === "not-found" ? (0, vue.unref)(t)("collectionsView.notFound") : `${(0, vue.unref)(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_41)) : calendarActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42, [(0, vue.createVNode)(CollectionCalendarView_default, {
5493
+ (0, vue.createElementVNode)("div", _hoisted_39, [loading.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_40, [_cache[43] || (_cache[43] = (0, vue.createElementVNode)("div", { class: "h-8 w-8 border-2 border-indigo-600/20 border-t-indigo-600 rounded-full animate-spin" }, null, -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)((0, vue.unref)(t)("common.loading")), 1)])) : loadError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_41, [_cache[44] || (_cache[44] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(loadError.value === "not-found" ? (0, vue.unref)(t)("collectionsView.notFound") : `${(0, vue.unref)(t)("collectionsView.loadFailed")}: ${loadError.value}`), 1)])) : !collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_42)) : calendarActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_43, [(0, vue.createVNode)(CollectionCalendarView_default, {
5020
5494
  schema: collection.value.schema,
5021
5495
  items: filteredItems.value,
5022
5496
  "anchor-field": calendarAnchorField.value,
@@ -5060,6 +5534,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5060
5534
  "action-error": actionError.value,
5061
5535
  "action-pending": actionPending.value,
5062
5536
  "visible-actions": visibleActions.value,
5537
+ "running-action-ids": viewingRunningActionIds.value,
5063
5538
  "live-record": liveRecord.value,
5064
5539
  "live-derived": liveDerived.value,
5065
5540
  "view-title": viewTitle.value,
@@ -5082,6 +5557,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5082
5557
  "action-error",
5083
5558
  "action-pending",
5084
5559
  "visible-actions",
5560
+ "running-action-ids",
5085
5561
  "live-record",
5086
5562
  "live-derived",
5087
5563
  "view-title",
@@ -5101,16 +5577,16 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5101
5577
  "selected",
5102
5578
  "can-create",
5103
5579
  "show-detail"
5104
- ])) : (0, vue.createCommentVNode)("", true)])) : kanbanActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_43, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_44, [
5105
- _cache[45] || (_cache[45] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5106
- (0, vue.createElementVNode)("span", _hoisted_45, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5580
+ ])) : (0, vue.createCommentVNode)("", true)])) : kanbanActive.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_44, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_45, [
5581
+ _cache[46] || (_cache[46] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5582
+ (0, vue.createElementVNode)("span", _hoisted_46, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5107
5583
  (0, vue.createElementVNode)("button", {
5108
5584
  type: "button",
5109
5585
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
5110
5586
  "aria-label": (0, vue.unref)(t)("common.close"),
5111
5587
  onClick: _cache[12] || (_cache[12] = ($event) => inlineError.value = null)
5112
- }, [..._cache[44] || (_cache[44] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_46)
5113
- ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_47, [(0, vue.createVNode)(CollectionKanbanView_default, {
5588
+ }, [..._cache[45] || (_cache[45] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_47)
5589
+ ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", _hoisted_48, [(0, vue.createVNode)(CollectionKanbanView_default, {
5114
5590
  schema: collection.value.schema,
5115
5591
  items: filteredItems.value,
5116
5592
  "group-field": kanbanGroupField.value,
@@ -5124,7 +5600,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5124
5600
  "group-field",
5125
5601
  "selected",
5126
5602
  "notified"
5127
- ])])])) : activeCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [activeCustomView.value.target === "mobile" ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRemoteViewPreview_default, {
5603
+ ])])])) : activeCustomView.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_49, [activeCustomView.value.target === "mobile" ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRemoteViewPreview_default, {
5128
5604
  key: 0,
5129
5605
  slug: collection.value.slug,
5130
5606
  view: activeCustomView.value,
@@ -5135,32 +5611,32 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5135
5611
  view: activeCustomView.value,
5136
5612
  onOpenItem: onCustomViewOpenItem,
5137
5613
  onStartChat: onCustomViewStartChat
5138
- }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_49, [_cache[46] || (_cache[46] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), (0, vue.createElementVNode)("p", _hoisted_50, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_51, [
5139
- _cache[47] || (_cache[47] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
5140
- (0, vue.createElementVNode)("p", _hoisted_52, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noMatchingItems")), 1),
5614
+ }, null, 8, ["slug", "view"]))])) : items.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_50, [_cache[47] || (_cache[47] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "folder_open", -1)), (0, vue.createElementVNode)("p", _hoisted_51, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.itemsEmpty")), 1)])) : filteredItems.value.length === 0 && editing.value?.mode !== "create" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_52, [
5615
+ _cache[48] || (_cache[48] = (0, vue.createElementVNode)("span", { class: "material-icons text-4xl text-slate-300" }, "search_off", -1)),
5616
+ (0, vue.createElementVNode)("p", _hoisted_53, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.noMatchingItems")), 1),
5141
5617
  (0, vue.createElementVNode)("button", {
5142
5618
  type: "button",
5143
5619
  class: "text-xs text-indigo-600 font-semibold hover:underline",
5144
5620
  onClick: _cache[13] || (_cache[13] = ($event) => searchQuery.value = "")
5145
5621
  }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.clearSearch")), 1)
5146
- ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_53, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_54, [
5147
- _cache[49] || (_cache[49] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5148
- (0, vue.createElementVNode)("span", _hoisted_55, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5622
+ ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_54, [inlineError.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_55, [
5623
+ _cache[50] || (_cache[50] = (0, vue.createElementVNode)("span", { class: "material-icons text-red-600" }, "error", -1)),
5624
+ (0, vue.createElementVNode)("span", _hoisted_56, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.inlineSaveFailed", { error: inlineError.value })), 1),
5149
5625
  (0, vue.createElementVNode)("button", {
5150
5626
  type: "button",
5151
5627
  class: "h-8 w-8 flex items-center justify-center rounded text-red-600 hover:bg-red-100",
5152
5628
  "aria-label": (0, vue.unref)(t)("common.close"),
5153
5629
  onClick: _cache[14] || (_cache[14] = ($event) => inlineError.value = null)
5154
- }, [..._cache[48] || (_cache[48] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_56)
5155
- ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("table", _hoisted_57, [(0, vue.createElementVNode)("thead", null, [(0, vue.createElementVNode)("tr", _hoisted_58, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(listColumnFields.value, ([key, field]) => {
5630
+ }, [..._cache[49] || (_cache[49] = [(0, vue.createElementVNode)("span", { class: "material-icons text-base" }, "close", -1)])], 8, _hoisted_57)
5631
+ ])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("table", _hoisted_58, [(0, vue.createElementVNode)("thead", null, [(0, vue.createElementVNode)("tr", _hoisted_59, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(listColumnFields.value, ([key, field]) => {
5156
5632
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("th", {
5157
5633
  key,
5158
5634
  "aria-sort": (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? sortAriaValue(key) : void 0,
5159
5635
  class: "px-5 py-3 font-bold text-slate-500 text-left uppercase tracking-wider whitespace-nowrap"
5160
- }, [(0, vue.createElementVNode)("div", _hoisted_60, [(0, vue.createElementVNode)("span", {
5636
+ }, [(0, vue.createElementVNode)("div", _hoisted_61, [(0, vue.createElementVNode)("span", {
5161
5637
  class: "truncate max-w-[14rem]",
5162
5638
  title: field.label
5163
- }, (0, vue.toDisplayString)(field.label), 9, _hoisted_61), (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
5639
+ }, (0, vue.toDisplayString)(field.label), 9, _hoisted_62), (0, vue.unref)(_mulmoclaude_core_collection.isSortableField)(field) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
5164
5640
  key: 0,
5165
5641
  type: "button",
5166
5642
  class: (0, vue.normalizeClass)(["inline-flex items-center justify-center rounded p-0.5 -my-1 leading-none transition-colors", sortButtonClass(key)]),
@@ -5169,8 +5645,8 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5169
5645
  onClick: (0, vue.withModifiers)(($event) => cycleSort(key), ["stop"]),
5170
5646
  onPointerenter: ($event) => hoveredSortKey.value = key,
5171
5647
  onPointerleave: _cache[15] || (_cache[15] = ($event) => hoveredSortKey.value = null)
5172
- }, [(0, vue.createElementVNode)("span", _hoisted_63, (0, vue.toDisplayString)(sortIconName(key)), 1)], 42, _hoisted_62)) : (0, vue.createCommentVNode)("", true)])], 8, _hoisted_59);
5173
- }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_64, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(sortedItems.value, (item) => {
5648
+ }, [(0, vue.createElementVNode)("span", _hoisted_64, (0, vue.toDisplayString)(sortIconName(key)), 1)], 42, _hoisted_63)) : (0, vue.createCommentVNode)("", true)])], 8, _hoisted_60);
5649
+ }), 128))])]), (0, vue.createElementVNode)("tbody", _hoisted_65, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(sortedItems.value, (item) => {
5174
5650
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("tr", {
5175
5651
  key: String(item[collection.value.schema.primaryKey] ?? ""),
5176
5652
  class: (0, vue.normalizeClass)(["hover:bg-slate-50/70 cursor-pointer transition-colors focus:outline-none focus:bg-indigo-50/30", isRowOpen(item) || isEditingRow(item) ? "bg-indigo-50/40" : ""]),
@@ -5194,7 +5670,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5194
5670
  "aria-label": field.label,
5195
5671
  onClick: _cache[16] || (_cache[16] = (0, vue.withModifiers)(() => {}, ["stop"])),
5196
5672
  onChange: ($event) => commitToggle(item, field)
5197
- }, null, 40, _hoisted_66)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
5673
+ }, null, 40, _hoisted_67)) : field.type === "boolean" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
5198
5674
  key: 1,
5199
5675
  type: "checkbox",
5200
5676
  checked: item[key] === true,
@@ -5204,7 +5680,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5204
5680
  "aria-label": field.label,
5205
5681
  onClick: _cache[17] || (_cache[17] = (0, vue.withModifiers)(() => {}, ["stop"])),
5206
5682
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.checked)
5207
- }, null, 40, _hoisted_67)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_68, [(0, vue.createElementVNode)("a", {
5683
+ }, null, 40, _hoisted_68)) : field.type === "ref" && field.to && typeof item[key] === "string" && item[key] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_69, [(0, vue.createElementVNode)("a", {
5208
5684
  href: (0, vue.unref)(cui).recordHref?.(field.to, String(item[key])),
5209
5685
  tabindex: (0, vue.unref)(cui).recordHref?.(field.to, String(item[key])) ? void 0 : 0,
5210
5686
  role: "link",
@@ -5212,7 +5688,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5212
5688
  "data-testid": `collections-ref-link-${key}-${item[key]}`,
5213
5689
  onClick: ($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true),
5214
5690
  onKeydown: [(0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true), ["enter"]), (0, vue.withKeys)(($event) => (0, vue.unref)(activateRefLink)($event, field.to, String(item[key]), true), ["space"])]
5215
- }, (0, vue.toDisplayString)((0, vue.unref)(refDisplay)(field.to, String(item[key]))), 41, _hoisted_69)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
5691
+ }, (0, vue.toDisplayString)((0, vue.unref)(refDisplay)(field.to, String(item[key]))), 41, _hoisted_70)])) : field.type === "enum" && Array.isArray(field.values) && field.values.length > 0 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("select", {
5216
5692
  key: 3,
5217
5693
  value: item[key] == null ? "" : String(item[key]),
5218
5694
  disabled: isRowInlineSaving(item),
@@ -5221,35 +5697,39 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5221
5697
  "aria-label": field.label,
5222
5698
  onClick: _cache[18] || (_cache[18] = (0, vue.withModifiers)(() => {}, ["stop"])),
5223
5699
  onChange: ($event) => commitInlineEdit(item, String(key), field, $event.target.value)
5224
- }, [showEnumPlaceholder(item, String(key)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("option", _hoisted_71, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1)) : (0, vue.createCommentVNode)("", true), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
5700
+ }, [showEnumPlaceholder(item, String(key)) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("option", _hoisted_72, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.selectPlaceholder")), 1)) : (0, vue.createCommentVNode)("", true), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(field.values, (value) => {
5225
5701
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("option", {
5226
5702
  key: value,
5227
5703
  value
5228
- }, (0, vue.toDisplayString)(value), 9, _hoisted_72);
5229
- }), 128))], 42, _hoisted_70)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_73, (0, vue.toDisplayString)((0, vue.unref)(formatMoney)(item[key], (0, vue.unref)(resolveCurrency)(field, item), (0, vue.unref)(locale))), 1)) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_74, [_cache[50] || (_cache[50] = (0, vue.createElementVNode)("span", { class: "material-icons text-[11px]" }, "list", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(tableSummary(item[key])), 1)])) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_75, (0, vue.toDisplayString)((0, vue.unref)(derivedDisplay)(field, (0, vue.unref)(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type !== "file" && (0, vue.unref)(isExternalUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5704
+ }, (0, vue.toDisplayString)(value), 9, _hoisted_73);
5705
+ }), 128))], 42, _hoisted_71)) : field.type === "money" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_74, (0, vue.toDisplayString)((0, vue.unref)(formatMoney)(item[key], (0, vue.unref)(resolveCurrency)(field, item), (0, vue.unref)(locale))), 1)) : field.type === "table" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_75, [_cache[51] || (_cache[51] = (0, vue.createElementVNode)("span", { class: "material-icons text-[11px]" }, "list", -1)), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(tableSummary(item[key])), 1)])) : field.type === "derived" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_76, (0, vue.toDisplayString)((0, vue.unref)(derivedDisplay)(field, (0, vue.unref)(evaluateDerivedAgainstItem)(field, String(key), item), item)), 1)) : field.type === "rollup" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
5230
5706
  key: 7,
5707
+ class: "inline-block truncate tabular-nums font-bold text-indigo-900 bg-indigo-50/50 px-1.5 py-0.5 rounded border border-indigo-100/50",
5708
+ "data-testid": `collections-rollup-${key}-${item[collection.value.schema.primaryKey]}`
5709
+ }, (0, vue.toDisplayString)((0, vue.unref)(render).rollupDisplay(field, item)), 9, _hoisted_77)) : field.type !== "file" && (0, vue.unref)(isExternalUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5710
+ key: 8,
5231
5711
  href: String(item[key]),
5232
5712
  target: "_blank",
5233
5713
  rel: "noopener noreferrer",
5234
5714
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5235
5715
  "data-testid": `collections-url-link-${key}-${item[collection.value.schema.primaryKey]}`,
5236
5716
  onClick: _cache[19] || (_cache[19] = (0, vue.withModifiers)(() => {}, ["stop"]))
5237
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_76)) : field.type === "file" && (0, vue.unref)(artifactUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5238
- key: 8,
5717
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_78)) : field.type === "file" && (0, vue.unref)(artifactUrl)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5718
+ key: 9,
5239
5719
  href: (0, vue.unref)(artifactUrl)(item[key]) ?? void 0,
5240
5720
  target: "_blank",
5241
5721
  rel: "noopener noreferrer",
5242
5722
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5243
5723
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5244
5724
  onClick: _cache[20] || (_cache[20] = (0, vue.withModifiers)(() => {}, ["stop"]))
5245
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_77)) : field.type === "file" && (0, vue.unref)(fileRoutePath)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5246
- key: 9,
5725
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_79)) : field.type === "file" && (0, vue.unref)(fileRoutePath)(item[key]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("a", {
5726
+ key: 10,
5247
5727
  href: (0, vue.unref)(fileRoutePath)(item[key]) ?? void 0,
5248
5728
  class: "block truncate text-blue-600 hover:text-blue-800 hover:underline font-semibold",
5249
5729
  "data-testid": `collections-file-link-${key}-${item[collection.value.schema.primaryKey]}`,
5250
5730
  onClick: ($event) => (0, vue.unref)(activatePathLink)($event, (0, vue.unref)(fileRoutePath)(item[key]) ?? "", true)
5251
- }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_78)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_79, (0, vue.toDisplayString)((0, vue.unref)(formatCell)(item[key], field.type)), 1))], 64)) : (0, vue.createCommentVNode)("", true)]);
5252
- }), 128))], 42, _hoisted_65);
5731
+ }, (0, vue.toDisplayString)(String(item[key])), 9, _hoisted_80)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_81, (0, vue.toDisplayString)((0, vue.unref)(formatCell)(item[key], field.type)), 1))], 64)) : (0, vue.createCommentVNode)("", true)]);
5732
+ }), 128))], 42, _hoisted_66);
5253
5733
  }), 128))])])]))]),
5254
5734
  collection.value && (viewing.value || editing.value) && !(calendarActive.value && openDay.value) ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionRecordModal_default, {
5255
5735
  key: 4,
@@ -5265,6 +5745,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5265
5745
  "action-error": actionError.value,
5266
5746
  "action-pending": actionPending.value,
5267
5747
  "visible-actions": visibleActions.value,
5748
+ "running-action-ids": viewingRunningActionIds.value,
5268
5749
  "live-record": liveRecord.value,
5269
5750
  "live-derived": liveDerived.value,
5270
5751
  "view-title": viewTitle.value,
@@ -5287,6 +5768,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5287
5768
  "action-error",
5288
5769
  "action-pending",
5289
5770
  "visible-actions",
5771
+ "running-action-ids",
5290
5772
  "live-record",
5291
5773
  "live-derived",
5292
5774
  "view-title",
@@ -5296,20 +5778,32 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5296
5778
  ])]),
5297
5779
  _: 1
5298
5780
  })) : (0, vue.createCommentVNode)("", true),
5781
+ mutateModal.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionMutateParamsModal_default, {
5782
+ key: `${mutateModal.value.action.id}-${mutateModal.value.itemId}`,
5783
+ action: mutateModal.value.action,
5784
+ pending: mutatePending.value,
5785
+ error: mutateError.value,
5786
+ onClose: _cache[23] || (_cache[23] = ($event) => mutateModal.value = null),
5787
+ onSubmit: submitMutateParams
5788
+ }, null, 8, [
5789
+ "action",
5790
+ "pending",
5791
+ "error"
5792
+ ])) : (0, vue.createCommentVNode)("", true),
5299
5793
  configOpen.value && collection.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(CollectionViewConfigModal_default, {
5300
- key: 5,
5794
+ key: 6,
5301
5795
  slug: collection.value.slug,
5302
5796
  title: collection.value.title,
5303
5797
  views: customViews.value,
5304
5798
  onChanged: onViewsChanged,
5305
- onClose: _cache[23] || (_cache[23] = ($event) => configOpen.value = false)
5799
+ onClose: _cache[24] || (_cache[24] = ($event) => configOpen.value = false)
5306
5800
  }, null, 8, [
5307
5801
  "slug",
5308
5802
  "title",
5309
5803
  "views"
5310
5804
  ])) : (0, vue.createCommentVNode)("", true),
5311
5805
  chatOpen.value && collection.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
5312
- key: 6,
5806
+ key: 7,
5313
5807
  class: "fixed inset-0 z-30 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm p-4 transition-all duration-300",
5314
5808
  role: "dialog",
5315
5809
  "aria-modal": "true",
@@ -5317,29 +5811,29 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5317
5811
  "data-testid": "collections-chat-modal",
5318
5812
  onClick: (0, vue.withModifiers)(closeChat, ["self"]),
5319
5813
  onKeydown: (0, vue.withKeys)(closeChat, ["esc"])
5320
- }, [(0, vue.createElementVNode)("div", _hoisted_80, [
5321
- (0, vue.createElementVNode)("header", _hoisted_81, [
5322
- _cache[52] || (_cache[52] = (0, vue.createElementVNode)("div", { class: "h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "forum")], -1)),
5323
- (0, vue.createElementVNode)("div", _hoisted_82, [(0, vue.createElementVNode)("h2", _hoisted_83, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatTitle")), 1), (0, vue.createElementVNode)("span", _hoisted_84, (0, vue.toDisplayString)(collection.value.title), 1)]),
5814
+ }, [(0, vue.createElementVNode)("div", _hoisted_82, [
5815
+ (0, vue.createElementVNode)("header", _hoisted_83, [
5816
+ _cache[53] || (_cache[53] = (0, vue.createElementVNode)("div", { class: "h-9 w-9 flex items-center justify-center rounded-xl bg-indigo-50 text-indigo-600 border border-indigo-100/50" }, [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "forum")], -1)),
5817
+ (0, vue.createElementVNode)("div", _hoisted_84, [(0, vue.createElementVNode)("h2", _hoisted_85, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatTitle")), 1), (0, vue.createElementVNode)("span", _hoisted_86, (0, vue.toDisplayString)(collection.value.title), 1)]),
5324
5818
  (0, vue.createElementVNode)("button", {
5325
5819
  type: "button",
5326
5820
  class: "h-8 w-8 flex items-center justify-center rounded text-slate-400 hover:bg-slate-200/50 hover:text-slate-600 transition-colors",
5327
5821
  "aria-label": (0, vue.unref)(t)("common.close"),
5328
5822
  "data-testid": "collections-chat-close",
5329
5823
  onClick: closeChat
5330
- }, [..._cache[51] || (_cache[51] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_85)
5824
+ }, [..._cache[52] || (_cache[52] = [(0, vue.createElementVNode)("span", { class: "material-icons text-lg" }, "close", -1)])], 8, _hoisted_87)
5331
5825
  ]),
5332
- (0, vue.createElementVNode)("div", _hoisted_86, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
5826
+ (0, vue.createElementVNode)("div", _hoisted_88, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
5333
5827
  ref_key: "chatInputEl",
5334
5828
  ref: chatInputEl,
5335
- "onUpdate:modelValue": _cache[24] || (_cache[24] = ($event) => chatMessage.value = $event),
5829
+ "onUpdate:modelValue": _cache[25] || (_cache[25] = ($event) => chatMessage.value = $event),
5336
5830
  rows: "4",
5337
5831
  placeholder: (0, vue.unref)(t)("collectionsView.chatPlaceholder"),
5338
5832
  class: "w-full bg-slate-50 border border-slate-200/80 rounded-xl px-3 py-2.5 text-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 focus:bg-white transition-all resize-none",
5339
5833
  "data-testid": "collections-chat-input",
5340
5834
  onKeydown: [(0, vue.withKeys)((0, vue.withModifiers)(submitChat, ["meta"]), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(submitChat, ["ctrl"]), ["enter"])]
5341
- }, null, 40, _hoisted_87), [[vue.vModelText, chatMessage.value]])]),
5342
- (0, vue.createElementVNode)("footer", _hoisted_88, [(0, vue.createElementVNode)("button", {
5835
+ }, null, 40, _hoisted_89), [[vue.vModelText, chatMessage.value]])]),
5836
+ (0, vue.createElementVNode)("footer", _hoisted_90, [(0, vue.createElementVNode)("button", {
5343
5837
  type: "button",
5344
5838
  class: "h-8 px-2.5 rounded text-xs font-bold text-slate-500 hover:bg-slate-200/50 transition-colors",
5345
5839
  "data-testid": "collections-chat-cancel",
@@ -5350,7 +5844,7 @@ var CollectionView_default = /* @__PURE__ */ (0, vue.defineComponent)({
5350
5844
  disabled: !chatMessage.value.trim(),
5351
5845
  "data-testid": "collections-chat-send",
5352
5846
  onClick: submitChat
5353
- }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatStart")), 9, _hoisted_89)])
5847
+ }, (0, vue.toDisplayString)((0, vue.unref)(t)("collectionsView.chatStart")), 9, _hoisted_91)])
5354
5848
  ])], 32)) : (0, vue.createCommentVNode)("", true)
5355
5849
  ]);
5356
5850
  };