@ai-matrx/records-ui 0.79.0 → 0.81.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/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.81.0
4
+
5
+ **The relation picker stops cutting a customer's name in half, and a grid mount stops
6
+ screaming at the console.** (lane FIX-14, from FIX-13's "named, not fixed" list.)
7
+
8
+ **1. `Create “Marisol Okonkwo — 418 Calle Pu`.** The picker pasted a per-surface `w-64`
9
+ over the design system's popover, and the Create offer had no truncation of its own, so the
10
+ one control that exists to show the name you are about to create cut it mid-word. Fixed
11
+ where it belongs: `@ai-matrx/design-system` 0.35.0 gained `sizing="content"` and decides
12
+ the width; this package sets no width at all. The offer and every candidate row truncate
13
+ with a real ellipsis and carry the whole name as a `title`, and `RecordChip` does the same
14
+ everywhere it is drawn. Measured headlessly against the live store: 408px showing the whole
15
+ name at 1600×1000, 343px with a real `text-overflow: ellipsis` at 375 wide, inside the
16
+ viewport both times, 0 page errors.
17
+
18
+ **2. `custom.enrich_cells … does not exist` on every grid mount.** The DOOR was never
19
+ missing — `custom.enrich_cells(uuid, uuid, text[], uuid[])` is live, granted to
20
+ `authenticated`, and answers 320 rows for a real organization from the seat. `demo/branch-server.ts`
21
+ formatted every non-scalar argument as `'<json>'::jsonb`, because `typeof [] === "object"`,
22
+ so no door with a Postgres array parameter could ever be matched; that 42883 was recorded
23
+ into `demo/fixtures/doors.json` and replayed on every mount. The seam now asks `pg_proc`
24
+ what each parameter is declared as and formats by that — the class, not the instance — and
25
+ an array whose type it cannot read is named loudly instead of quietly re-sent as jsonb.
26
+
27
+ * New guard `demo/a-grid-mount-logs-no-console-error.test.tsx`: mounts `Grid` against the
28
+ LIVE store and fails on ANY `console.error`, with one named exclusion (React's jsdom-only
29
+ `act()` warning). RED on the pre-fix bytes with exactly the 42883 above, GREEN on these.
30
+ * The demo harness seeds a PLAIN relation column ("Related proposal", a self-relation on
31
+ Northbay Commercial Solar's proposals table) — until now every relation it seeded carried
32
+ `parity: "member"` and routed to `PersonPicker`, so `RelationPicker` had no browser
33
+ surface at all and its only coverage was jsdom, which has no geometry.
34
+
35
+ Suite: 51 files / 319 tests green, `tsc --noEmit` clean.
36
+
37
+ ## 0.80.0
38
+
39
+ **An import's report outlives the run, and its refusals outlive the panel.** (lane FIX-12,
40
+ finding F2 from VERIFIER-12; published by PUBLISH-SWEEP-2, aidream `675cc61fe8`.)
41
+
42
+ VERIFIER-12 watched a 40-row file land 28 rows and refuse 12 against the live store and saw
43
+ NOTHING on screen: no summary, no refusal list, and afterwards nothing on any of that
44
+ table's screens naming the twelve rows. The store had been right the whole time — the
45
+ ledger said seen 40 / written 28 / refusals 12, and every refused row was kept with a
46
+ sentence a person understands. The report was not the defect. `importFinish` called
47
+ `onDone`, every host wires `onDone` to close the rail, and a run that fits in ONE call
48
+ finishes between two paints — so the report was destroyed in the tick it became true. A long
49
+ file only looked better because there was time to read the progress.
50
+
51
+ * The wizard tells the host `onWrote` when rows land; `onDone` is now the person pressing
52
+ **Done** on the report, and nothing else closes the panel.
53
+ * The report says where the refused rows now live.
54
+ * **"Imports into this table"** — every run this table has had, newest first, off
55
+ `custom.io_imports`; a run with refusals opens them off `custom.io_import_report`, with
56
+ the store's own reason and the line of the file. Both doors have existed since lane
57
+ IMPORT; no screen had ever asked.
58
+ * Guard: two clauses in `src/import-wizard.test.tsx`, mounted the way a HOST mounts the
59
+ wizard (closing on `onDone`). RED on the old bytes ("expected 'the import is closed' not
60
+ to contain 'the import is closed'"; the past run never on screen), GREEN on these.
61
+
62
+ Built against `@ai-matrx/records@0.54.0`. Suite at publish: 317/317 across 50 files,
63
+ including `demo/live-smoke.test.tsx`, this package's blocking live publish gate.
64
+
3
65
  ## 0.79.0
4
66
 
5
67
  **A joined column says what joins it.** (lane STORE-TAILS-2, VERIFIER-11 finding F5.)
package/dist/index.cjs CHANGED
@@ -1082,7 +1082,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1082
1082
  )),
1083
1083
  disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.Popover, { open, onOpenChange: setOpen, children: [
1084
1084
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.Button, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
1085
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.PopoverContent, { className: "w-64 p-1", align: "start", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1086
1086
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1087
1087
  import_design_system2.BasicInput,
1088
1088
  {
@@ -1106,6 +1106,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1106
1106
  {
1107
1107
  type: "button",
1108
1108
  onClick: () => toggle(row.id),
1109
+ title: titleOf(row.id),
1109
1110
  className: (0, import_design_system2.cn)(
1110
1111
  "w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
1111
1112
  picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
@@ -1151,6 +1152,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1151
1152
  type: "button",
1152
1153
  size: "sm",
1153
1154
  variant: "ghost",
1155
+ title: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`,
1154
1156
  className: "w-full justify-start text-xs",
1155
1157
  disabled: busy,
1156
1158
  onClick: () => {
@@ -1160,7 +1162,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
1160
1162
  setCreating(true);
1161
1163
  }
1162
1164
  },
1163
- children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`
1165
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
1164
1166
  }
1165
1167
  ),
1166
1168
  createRefusal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
@@ -1421,10 +1423,17 @@ function RecordChip({
1421
1423
  onRemove,
1422
1424
  className
1423
1425
  }) {
1424
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Badge, { variant: "secondary", className: (0, import_design_system3.cn)("gap-1 text-[11px]", className), children: [
1425
- title,
1426
- onRemove ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "opacity-60 hover:opacity-100", children: "\xD7" }) : null
1427
- ] });
1426
+ return (
1427
+ // A chip carries a RECORD'S OWN TITLE, which nobody here chose the length of —
1428
+ // "Marisol Okonkwo 418 Calle Puerto Vallarta, Camarillo" is an ordinary customer
1429
+ // name. So it caps at its container and ends in an ellipsis with the whole name in
1430
+ // its tooltip, rather than being cut mid-word by whatever box it landed in
1431
+ // (lane FIX-14, 2026-09-22).
1432
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Badge, { variant: "secondary", className: (0, import_design_system3.cn)("max-w-full gap-1 text-[11px]", className), title, children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 truncate", children: title }),
1434
+ onRemove ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
1435
+ ] })
1436
+ );
1428
1437
  }
1429
1438
 
1430
1439
  // src/PersonPicker.tsx
@@ -1570,7 +1579,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
1570
1579
  children: picked.length === 0 ? "Pick a person" : "Change"
1571
1580
  }
1572
1581
  ) }),
1573
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.PopoverContent, { className: "w-64 p-1", align: "start", children: [
1582
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
1574
1583
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1575
1584
  import_design_system4.BasicInput,
1576
1585
  {
@@ -3691,6 +3700,9 @@ function Grid({
3691
3700
  [client, host, refreshEnrich]
3692
3701
  );
3693
3702
  const [pastePlan, setPastePlan] = (0, import_react20.useState)(null);
3703
+ const [deleting, setDeleting] = (0, import_react20.useState)(null);
3704
+ const [deleteBusy, setDeleteBusy] = (0, import_react20.useState)(false);
3705
+ const [archived, setArchived] = (0, import_react20.useState)(null);
3694
3706
  const choiceOptions = (0, import_react20.useRef)(/* @__PURE__ */ new Map());
3695
3707
  const loadChoiceOptions = (0, import_react20.useCallback)(
3696
3708
  async (fieldList) => {
@@ -3978,6 +3990,21 @@ function Grid({
3978
3990
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ml-auto flex items-center gap-1", children: toolbarActions })
3979
3991
  ] }),
3980
3992
  records.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefusalNotice, { error: records.error }) : null,
3993
+ archived ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3994
+ "p",
3995
+ {
3996
+ "data-records-archived-notice": "",
3997
+ role: "status",
3998
+ className: "flex items-center gap-2 rounded border border-border bg-muted/40 px-2 py-1 text-xs",
3999
+ children: [
4000
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
4001
+ archived,
4002
+ " is archived. Nothing was destroyed \u2014 it is in this table\u2019s Archived items, where Restore brings it back."
4003
+ ] }),
4004
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.Button, { size: "sm", variant: "ghost", className: "ml-auto h-6", onClick: () => setArchived(null), children: "Dismiss" })
4005
+ ]
4006
+ }
4007
+ ) : null,
3981
4008
  editing.rowError ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3982
4009
  RefusalNotice,
3983
4010
  {
@@ -4094,11 +4121,13 @@ function Grid({
4094
4121
  }
4095
4122
  ) : null,
4096
4123
  canWrite && mayWriteRow(row.id) ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4097
- DeleteRecordButton,
4124
+ import_design_system10.Button,
4098
4125
  {
4099
- row,
4100
- retentionDays: table.data?.retention_days ?? null,
4101
- onDelete: () => editing.remove(row.id)
4126
+ size: "sm",
4127
+ variant: "ghost",
4128
+ title: "Delete this record",
4129
+ onClick: () => setDeleting(row),
4130
+ children: "Delete"
4102
4131
  }
4103
4132
  ) : null
4104
4133
  ] })
@@ -4141,7 +4170,32 @@ function Grid({
4141
4170
  onPage: setPage
4142
4171
  }
4143
4172
  )
4144
- ] })
4173
+ ] }),
4174
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4175
+ import_design_system10.ConfirmDialog,
4176
+ {
4177
+ open: deleting !== null,
4178
+ onOpenChange: (next) => {
4179
+ if (!next && !deleteBusy) setDeleting(null);
4180
+ },
4181
+ variant: "destructive",
4182
+ title: `Delete ${deleting ? recordNameIn(table.data, deleting.document ?? {}) : "this record"}?`,
4183
+ description: table.data?.retention_days ? `It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back, for ${table.data.retention_days} days.` : "It leaves the table and waits in Archived items. Nothing is destroyed: Restore brings it back.",
4184
+ confirmLabel: "Delete",
4185
+ cancelLabel: "Keep",
4186
+ busy: deleteBusy,
4187
+ onConfirm: async () => {
4188
+ const row = deleting;
4189
+ if (!row) return;
4190
+ const name = recordNameIn(table.data, row.document ?? {});
4191
+ setDeleteBusy(true);
4192
+ const failed = await editing.remove(row.id);
4193
+ setDeleteBusy(false);
4194
+ setDeleting(null);
4195
+ if (!failed) setArchived(name);
4196
+ }
4197
+ }
4198
+ )
4145
4199
  ] }) });
4146
4200
  }
4147
4201
  function columnLabel(fields, columnId) {
@@ -4161,42 +4215,6 @@ function AddFieldButton({ onClick }) {
4161
4215
  }
4162
4216
  );
4163
4217
  }
4164
- function DeleteRecordButton({
4165
- row,
4166
- retentionDays,
4167
- onDelete
4168
- }) {
4169
- const [asking, setAsking] = (0, import_react20.useState)(false);
4170
- if (!asking) {
4171
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4172
- import_design_system10.Button,
4173
- {
4174
- size: "sm",
4175
- variant: "ghost",
4176
- title: "Delete this record",
4177
- onClick: () => setAsking(true),
4178
- children: "Delete"
4179
- }
4180
- );
4181
- }
4182
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-1 text-[11px]", children: [
4183
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground", children: retentionDays ? `Removes it from the table. Restorable for ${retentionDays} days.` : "Removes it from the table. It can be restored from the record's history." }),
4184
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4185
- import_design_system10.Button,
4186
- {
4187
- size: "sm",
4188
- variant: "destructive",
4189
- onClick: () => {
4190
- setAsking(false);
4191
- void onDelete();
4192
- },
4193
- children: "Delete"
4194
- }
4195
- ),
4196
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_design_system10.Button, { size: "sm", variant: "ghost", onClick: () => setAsking(false), children: "Keep" }),
4197
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "sr-only", children: row.id })
4198
- ] });
4199
- }
4200
4218
  function Pager({
4201
4219
  page,
4202
4220
  pageSize,
@@ -4495,7 +4513,7 @@ async function sha256(bytes) {
4495
4513
  function typeWord(t) {
4496
4514
  return fieldTypeChoice(t)?.label ?? (t === "relation" ? "Points at another table" : t);
4497
4515
  }
4498
- function ImportWizard({ tableId, onDone, className }) {
4516
+ function ImportWizard({ tableId, onDone, onWrote, className }) {
4499
4517
  const client = (0, import_react27.useRecordsClient)();
4500
4518
  const table = (0, import_react27.useTable)(tableId);
4501
4519
  const fields = (0, import_react27.useFields)(tableId);
@@ -4521,6 +4539,46 @@ function ImportWizard({ tableId, onDone, className }) {
4521
4539
  const [columnsAdded, setColumnsAdded] = (0, import_react26.useState)(null);
4522
4540
  const [show, setShow] = (0, import_react26.useState)("all");
4523
4541
  const [ledger, setLedger] = (0, import_react26.useState)(null);
4542
+ const [runs, setRuns] = (0, import_react26.useState)(null);
4543
+ const [openRun, setOpenRun] = (0, import_react26.useState)(null);
4544
+ const [runRows, setRunRows] = (0, import_react26.useState)(null);
4545
+ const [runProblem, setRunProblem] = (0, import_react26.useState)(null);
4546
+ const loadRuns = (0, import_react26.useCallback)(async () => {
4547
+ const answered = await client.imports({ table_id: tableId, limit: 10 });
4548
+ if (answered.ok) setRuns(answered.data);
4549
+ }, [client, tableId]);
4550
+ (0, import_react26.useEffect)(() => {
4551
+ void loadRuns();
4552
+ }, [loadRuns]);
4553
+ const openRefusals = (0, import_react26.useCallback)(
4554
+ async (importId) => {
4555
+ setRunProblem(null);
4556
+ if (openRun === importId) {
4557
+ setOpenRun(null);
4558
+ setRunRows(null);
4559
+ return;
4560
+ }
4561
+ setOpenRun(importId);
4562
+ setRunRows(null);
4563
+ const answered = await client.importReport({
4564
+ import_id: importId,
4565
+ state: "refused",
4566
+ limit: 200
4567
+ });
4568
+ if (!answered.ok) {
4569
+ setRunProblem(answered.error.message);
4570
+ return;
4571
+ }
4572
+ setRunRows({
4573
+ import_id: answered.data.import_id,
4574
+ rows: answered.data.rows,
4575
+ kept: answered.data.kept,
4576
+ total: answered.data.total,
4577
+ note: answered.data.note
4578
+ });
4579
+ },
4580
+ [client, openRun]
4581
+ );
4524
4582
  const declared = fields.data ?? [];
4525
4583
  const take = (0, import_react26.useCallback)(
4526
4584
  async (file) => {
@@ -4654,8 +4712,9 @@ function ImportWizard({ tableId, onDone, className }) {
4654
4712
  final: true
4655
4713
  });
4656
4714
  setPhase("done");
4657
- onDone?.(finished.data.rows_written);
4658
- }, [client, dedupeKey, fileBytes, fileHash, fileName, format, mapping, onDuplicate, onDone, plan, rows, tableId, unmapped]);
4715
+ onWrote?.(finished.data.rows_written);
4716
+ void loadRuns();
4717
+ }, [client, dedupeKey, fileBytes, fileHash, fileName, format, loadRuns, mapping, onDuplicate, onWrote, plan, rows, tableId, unmapped]);
4659
4718
  const counts = (0, import_react26.useMemo)(() => {
4660
4719
  let landed = 0;
4661
4720
  let duplicate = 0;
@@ -4932,7 +4991,73 @@ function ImportWizard({ tableId, onDone, className }) {
4932
4991
  proposals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-0.5", children: proposals.map((p) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "text-muted-foreground", children: [
4933
4992
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium text-foreground", children: p.column }),
4934
4993
  p.state === "waiting" ? " is waiting in the approvals inbox." : p.state === "accepted" ? " was added as a column." : p.state === "ignored" ? " was left out." : p.state === "refused" ? ` could not be added: ${p.reason ?? ""}` : ` \u2014 ${p.state}`
4935
- ] }, p.column)) }) : null
4994
+ ] }, p.column)) }) : null,
4995
+ phase === "done" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
4996
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Button, { size: "sm", onClick: () => onDone?.(ledger?.written ?? 0), children: "Done" }),
4997
+ (ledger?.refused ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-muted-foreground", children: [
4998
+ "The ",
4999
+ ledger?.refused,
5000
+ " row",
5001
+ ledger?.refused === 1 ? "" : "s",
5002
+ " that ",
5003
+ ledger?.refused === 1 ? "was" : "were",
5004
+ " refused",
5005
+ " ",
5006
+ "are kept with this import \u2014 open them again from the list of imports below, with the store's reason and the line from your file on each."
5007
+ ] }) : null
5008
+ ] }) : null
5009
+ ] }) : null,
5010
+ runs && runs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5011
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
5012
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium", children: "Imports into this table" }),
5013
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", children: runs.map((r) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("li", { className: "flex flex-col gap-1", children: [
5014
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
5015
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: r.source_name ?? "a file" }),
5016
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "text-muted-foreground", children: [
5017
+ new Date(r.opened_at).toLocaleString(),
5018
+ " \xB7 ",
5019
+ r.rows_written,
5020
+ " landed \xB7",
5021
+ " ",
5022
+ r.rows_duplicate,
5023
+ " already here \xB7 ",
5024
+ r.rows_refused,
5025
+ " refused, of ",
5026
+ r.rows_seen,
5027
+ " row",
5028
+ r.rows_seen === 1 ? "" : "s",
5029
+ " offered"
5030
+ ] }),
5031
+ r.rows_refused > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
5032
+ "button",
5033
+ {
5034
+ type: "button",
5035
+ className: "underline underline-offset-2",
5036
+ "aria-expanded": openRun === r.import_id,
5037
+ onClick: () => void openRefusals(r.import_id),
5038
+ children: openRun === r.import_id ? "hide the rows it refused" : `see the ${r.rows_refused} row${r.rows_refused === 1 ? "" : "s"} it refused`
5039
+ }
5040
+ ) : null
5041
+ ] }),
5042
+ openRun === r.import_id ? runProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: runProblem }) : runRows ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
5043
+ runRows.note ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: runRows.note }) : null,
5044
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("table", { className: "w-full", children: [
5045
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
5046
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "Line in your file" }),
5047
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "What happened" }),
5048
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
5049
+ ] }) }),
5050
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: runRows.rows.map((row, i) => {
5051
+ const source = row.source ?? {};
5052
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
5053
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
5054
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1 text-destructive", children: String(row.reason ?? "") }),
5055
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: Object.entries(source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") })
5056
+ ] }, `${r.import_id}-${String(row.row ?? i)}`);
5057
+ }) })
5058
+ ] }) })
5059
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the rows this import refused\u2026" }) : null
5060
+ ] }, r.import_id)) })
4936
5061
  ] }) : null,
4937
5062
  table.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: table.error }) : null
4938
5063
  ] });
@@ -7392,8 +7517,8 @@ function PipelineBoard({
7392
7517
  const asked = await client.pipelineTransitionRefusal({ record_id: recordId, to });
7393
7518
  if (!alive.current) return;
7394
7519
  if (!asked.ok) {
7395
- setError(asked.error);
7396
- setPending({ kind: "none" });
7520
+ setPending({ kind: "clash", recordId, to, error: asked.error });
7521
+ onMoved?.();
7397
7522
  return;
7398
7523
  }
7399
7524
  const verdict = asked.data;
@@ -7406,8 +7531,8 @@ function PipelineBoard({
7406
7531
  const moved = await client.pipelineMove({ record_id: recordId, to });
7407
7532
  if (!alive.current) return;
7408
7533
  if (!moved.ok) {
7409
- setPending({ kind: "none" });
7410
- setError(moved.error);
7534
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7535
+ onMoved?.();
7411
7536
  return;
7412
7537
  }
7413
7538
  const result = moved.data;
@@ -7428,21 +7553,22 @@ function PipelineBoard({
7428
7553
  const moved = await client.pipelineMove({ record_id: recordId, to });
7429
7554
  if (!alive.current) return;
7430
7555
  if (!moved.ok) {
7431
- setPending({ kind: "none" });
7432
- setError(moved.error);
7556
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7557
+ onMoved?.();
7433
7558
  return;
7434
7559
  }
7435
7560
  setPending({ kind: "filed", recordId, to, result: moved.data });
7436
7561
  void reload();
7437
7562
  },
7438
- [client, reload]
7563
+ [client, onMoved, reload]
7439
7564
  );
7440
7565
  const moveWith = (0, import_react47.useCallback)(
7441
7566
  async (recordId, to, also) => {
7442
7567
  const moved = await client.pipelineMove({ record_id: recordId, to, also });
7443
7568
  if (!alive.current) return;
7444
7569
  if (!moved.ok) {
7445
- setError(moved.error);
7570
+ setPending({ kind: "clash", recordId, to, error: moved.error });
7571
+ onMoved?.();
7446
7572
  return;
7447
7573
  }
7448
7574
  setPending({ kind: "none" });
@@ -7570,6 +7696,16 @@ function Held({
7570
7696
  if (pending.kind === "asking") {
7571
7697
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "px-2 py-1 text-xs text-muted-foreground", children: "Asking\u2026" });
7572
7698
  }
7699
+ if (pending.kind === "clash") {
7700
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
7701
+ RefusalNotice,
7702
+ {
7703
+ className: "mt-1",
7704
+ error: pending.error,
7705
+ actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-1", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "h-6", onClick: onCancel, children: "Close" }) })
7706
+ }
7707
+ );
7708
+ }
7573
7709
  if (pending.kind === "refused") {
7574
7710
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 rounded border border-destructive/50 bg-destructive/5 p-2 text-xs", children: [
7575
7711
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: pending.verdict.why }),