@ai-matrx/records-ui 0.90.0 → 0.90.2

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,32 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.90.2
4
+ ### Unreleased
5
+
6
+ ### Changed — a removal says it can be restored from Trash (lane STORE-RESTORE-DOORS, 2026-09-26)
7
+ - Removing a column (`FieldEditor`, `TableSettings`) confirms with the shared `archiveConfirmSentence`
8
+ plus "Its values stay in every record and come back with it." — the store now brings a removed Field
9
+ back from Trash with its values (`custom.field_restore`); the old copy said the values lived only in history.
10
+ - `DashboardCanvas`: removing a dashboard takes a second press with the same sentence (it was one press
11
+ labelled "Delete"); it comes back from Trash through `custom.dashboard_restore`.
12
+ - `NotifyRuleEditor`: "Stop telling me" archives the subscription's Rule and now asks once, naming Trash
13
+ (`custom.rule_restore`).
14
+ - Consumer action: none.
15
+
16
+ ## 0.90.1 (2026-09-26 — merged grid 6n/6o live-walk fixes; needs @ai-matrx/design-system 0.44.3)
17
+
18
+ - **The header's "+" is a button of its own, not a button inside the column-reorder button**
19
+ (found live: a real click on "+" did nothing, and React reported a nested button). The column
20
+ declares `reorderable: false` (design-system 0.44.3), so its label is never a drag handle and it
21
+ never moves among the data columns.
22
+ - **Phone (6o), found live at 375 px:** the Tools button was scrolled past search and a 144 px "No
23
+ groups" picker, off the right edge. Search and Tools now sit side by side at the START of the row
24
+ (the same search query), and an ungrouped view draws no group picker on a phone.
25
+ - **A key is never spread into JSX** (found live on the merged grid): `TablePage` spread
26
+ `{ key: "share" }` into `<ShareControl>`; the key is passed directly now, and
27
+ `a-key-is-never-spread-into-jsx.test.tsx` is a census of this package's source (it names the old
28
+ line on 0.90.0).
29
+
3
30
  ## 0.90.0 (2026-09-26 — merged grid tranches 6n + 6o, choice colours by option, DOM budget; needs @ai-matrx/records 0.58.22)
4
31
 
5
32
  - **6n · a row is added where you are.** The merged grid draws an "Add row" line under the last
package/dist/index.cjs CHANGED
@@ -269,6 +269,8 @@ __export(src_exports, {
269
269
  formFromSummary: () => formFromSummary,
270
270
  formPresentation: () => formPresentation,
271
271
  formatForField: () => formatForField,
272
+ gridContextKey: () => gridContextKey,
273
+ gridContextSnapshot: () => gridContextSnapshot,
272
274
  groupInbox: () => groupInbox,
273
275
  groupLabel: () => groupLabel,
274
276
  handOrderLine: () => handOrderLine,
@@ -5349,9 +5351,8 @@ function FieldEditor({ tableId, field: field2, onSaved, onCancel, onRemoved, cla
5349
5351
  onCancel ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null,
5350
5352
  field2 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "ml-auto flex items-center gap-2", children: askingToRemove ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
5351
5353
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-destructive", children: [
5352
- "Removing ",
5353
- fieldName(field2),
5354
- " takes it off every record of this table. The values stay in the record's history and nothing else is deleted."
5354
+ archiveConfirmSentence(`the field \u201C${fieldName(field2)}\u201D`),
5355
+ " Its values stay in every record and come back with it."
5355
5356
  ] }),
5356
5357
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "destructive", disabled: mutation.saving, onClick: () => void remove(), children: "Remove it" }),
5357
5358
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system17.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(false), children: "Keep it" })
@@ -7316,6 +7317,8 @@ function addColumnColumn(onAdd) {
7316
7317
  sortable: false,
7317
7318
  filter: false,
7318
7319
  hideable: false,
7320
+ // A control, never a drag handle: no button inside the reorder button, and it stays last.
7321
+ reorderable: false,
7319
7322
  resizable: false,
7320
7323
  compact: true,
7321
7324
  width: 40
@@ -10653,6 +10656,7 @@ function Grid({
10653
10656
  const view = presentation.grouping;
10654
10657
  const field2 = view?.field ?? null;
10655
10658
  if (field2 === null && !onPresentationChange) return null;
10659
+ if (field2 === null && merged && phone) return null;
10656
10660
  const aggregates = view && Object.keys(view.aggregates ?? {}).length > 0 ? view.aggregates : field2 ? { [field2]: { kind: "count" } } : void 0;
10657
10661
  return {
10658
10662
  columnId: field2,
@@ -10683,7 +10687,7 @@ function Grid({
10683
10687
  }
10684
10688
  } : {}
10685
10689
  };
10686
- }, [presentation, onPresentationChange, writeLook]);
10690
+ }, [presentation, onPresentationChange, writeLook, merged, phone]);
10687
10691
  const labelOfField = (0, import_react57.useCallback)(
10688
10692
  (key) => {
10689
10693
  const held = (fields.data ?? []).find((f) => f.key === key);
@@ -11065,6 +11069,41 @@ function Grid({
11065
11069
  toolbar: {
11066
11070
  search: true,
11067
11071
  searchPlaceholder: "Search records",
11072
+ /*
11073
+ * A PHONE (tranche 6o): search and the one Tools button sit side by side at the START of
11074
+ * the row, so Tools is never scrolled out of reach; the search is the same query.
11075
+ */
11076
+ ...merged && phone ? {
11077
+ customSearch: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", "data-records-grid-phone-search": "", children: [
11078
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11079
+ "input",
11080
+ {
11081
+ type: "search",
11082
+ value: query.search,
11083
+ placeholder: "Search",
11084
+ "aria-label": "Search records",
11085
+ onChange: (event) => {
11086
+ const search = event.target.value;
11087
+ setQuery((held) => ({ ...held, search, page: 1 }));
11088
+ },
11089
+ className: "h-8 w-full min-w-0 rounded-md border border-input bg-background px-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
11090
+ style: { fontSize: "16px" }
11091
+ }
11092
+ ),
11093
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11094
+ import_design_system32.Button,
11095
+ {
11096
+ size: "sm",
11097
+ variant: "outline",
11098
+ className: "h-8 shrink-0",
11099
+ "data-records-grid-tools": "",
11100
+ "aria-haspopup": "dialog",
11101
+ onClick: () => setToolsOpen(true),
11102
+ children: "Tools"
11103
+ }
11104
+ )
11105
+ ] })
11106
+ } : {},
11068
11107
  /* ONE ROW (lane TABLE-PAGE-CHROME): on a table page the grid's search, columns and
11069
11108
  sort are drawn INTO the page's toolbar row, never a second bar under it. */
11070
11109
  ...toolbarSlot !== void 0 ? { portalInto: toolbarSlot, singleRow: true } : {},
@@ -11115,32 +11154,18 @@ function Grid({
11115
11154
  )
11116
11155
  ] });
11117
11156
  if (!merged || !phone) return all;
11118
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
11119
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11120
- import_design_system32.Button,
11121
- {
11122
- size: "sm",
11123
- variant: "outline",
11124
- className: "h-8 shrink-0",
11125
- "data-records-grid-tools": "",
11126
- "aria-haspopup": "dialog",
11127
- onClick: () => setToolsOpen(true),
11128
- children: "Tools"
11129
- }
11130
- ),
11131
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.BottomSheet, { open: toolsOpen, onOpenChange: setToolsOpen, title: "Table tools", surface: "solid", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11132
- "div",
11133
- {
11134
- "data-records-grid-tools-sheet": "",
11135
- className: "flex flex-col items-stretch gap-1 px-3 pb-6 [&>button]:justify-start [&_button]:min-h-10",
11136
- onClickCapture: (event) => {
11137
- const target = event.target.closest("button");
11138
- if (target && !target.hasAttribute("aria-haspopup")) setToolsOpen(false);
11139
- },
11140
- children: all
11141
- }
11142
- ) })
11143
- ] });
11157
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_jsx_runtime39.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system32.BottomSheet, { open: toolsOpen, onOpenChange: setToolsOpen, title: "Table tools", surface: "solid", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
11158
+ "div",
11159
+ {
11160
+ "data-records-grid-tools-sheet": "",
11161
+ className: "flex flex-col items-stretch gap-1 px-3 pb-6 [&>button]:justify-start [&_button]:min-h-10",
11162
+ onClickCapture: (event) => {
11163
+ const target = event.target.closest("button");
11164
+ if (target && !target.hasAttribute("aria-haspopup")) setToolsOpen(false);
11165
+ },
11166
+ children: all
11167
+ }
11168
+ ) }) });
11144
11169
  })()
11145
11170
  }
11146
11171
  },
@@ -13723,9 +13748,8 @@ function TableSettings({
13723
13748
  shape.error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(RefusalNotice, { error: shape.error }) : null,
13724
13749
  askingToRemove ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded border border-destructive/40 px-2 py-1.5", children: [
13725
13750
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "text-destructive", children: [
13726
- "Removing ",
13727
- fieldName(askingToRemove),
13728
- " takes it off every record of this table. The values stay in each record's history and nothing else is deleted."
13751
+ archiveConfirmSentence(`the field \u201C${fieldName(askingToRemove)}\u201D`),
13752
+ " Its values stay in every record and come back with it."
13729
13753
  ] }),
13730
13754
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
13731
13755
  import_design_system41.Button,
@@ -21413,6 +21437,7 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
21413
21437
  const [views, setViews] = (0, import_react126.useState)(null);
21414
21438
  const [error, setError] = (0, import_react126.useState)(null);
21415
21439
  const [busy, setBusy] = (0, import_react126.useState)(false);
21440
+ const [stopping, setStopping] = (0, import_react126.useState)(null);
21416
21441
  const load = (0, import_react126.useCallback)(async () => {
21417
21442
  const [held, offered] = await Promise.all([
21418
21443
  // THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
@@ -21509,6 +21534,7 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
21509
21534
  await load();
21510
21535
  }
21511
21536
  async function unsubscribe(ruleId) {
21537
+ setStopping(null);
21512
21538
  setBusy(true);
21513
21539
  const deleted = await client.recordDelete({ record_id: ruleId });
21514
21540
  setBusy(false);
@@ -21570,14 +21596,18 @@ function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className })
21570
21596
  children: "Use for this form"
21571
21597
  }
21572
21598
  ) : null,
21573
- rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
21599
+ rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime73.jsxs)("span", { className: (0, import_design_system65.cn)("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
21600
+ /* @__PURE__ */ (0, import_jsx_runtime73.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
21601
+ /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_design_system65.Button, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
21602
+ /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(import_design_system65.Button, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
21603
+ ] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime73.jsx)(
21574
21604
  import_design_system65.Button,
21575
21605
  {
21576
21606
  size: "sm",
21577
21607
  variant: "ghost",
21578
21608
  className: onChoose ? void 0 : "ml-auto",
21579
21609
  disabled: busy,
21580
- onClick: () => void unsubscribe(held.rule_id),
21610
+ onClick: () => setStopping(held.rule_id),
21581
21611
  children: "Stop telling me"
21582
21612
  }
21583
21613
  ) : null
@@ -23313,6 +23343,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
23313
23343
  const [boards, setBoards] = (0, import_react139.useState)(null);
23314
23344
  const [error, setError] = (0, import_react139.useState)(null);
23315
23345
  const [activeId, setActiveId] = (0, import_react139.useState)(activeDashboardId ?? null);
23346
+ const [askingToRemove, setAskingToRemove] = (0, import_react139.useState)(null);
23316
23347
  const [run, setRun] = (0, import_react139.useState)(null);
23317
23348
  const [running, setRunning] = (0, import_react139.useState)(false);
23318
23349
  const [question, setQuestion] = (0, import_react139.useState)("");
@@ -23423,6 +23454,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
23423
23454
  else setError(again.error);
23424
23455
  }, [client, activeId, filter, grain]);
23425
23456
  async function remove(target) {
23457
+ setAskingToRemove(null);
23426
23458
  const gone = await client.dashboardDelete({ dashboard_id: target.id });
23427
23459
  if (!gone.ok) {
23428
23460
  setError(gone.error);
@@ -23511,7 +23543,11 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
23511
23543
  children: scheduling ? "Done" : "Send on a schedule"
23512
23544
  }
23513
23545
  ),
23514
- rights.admin ? /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "ghost", onClick: () => void remove(board), children: "Delete" }) : null
23546
+ rights.admin && askingToRemove === board.id ? /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)(import_jsx_runtime80.Fragment, { children: [
23547
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("span", { className: "text-xs text-destructive", children: archiveConfirmSentence(`the dashboard \u201C${board.name}\u201D`) }),
23548
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "destructive", onClick: () => void remove(board), children: "Remove it" }),
23549
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
23550
+ ] }) : rights.admin ? /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_design_system72.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(board.id), children: "Remove" }) : null
23515
23551
  ] }) : null
23516
23552
  ] }),
23517
23553
  board && scheduling ? (
@@ -26929,8 +26965,9 @@ function TablePage({
26929
26965
  name: table.data?.name,
26930
26966
  may: rights.share,
26931
26967
  initiallyOpen: shareFromLink || shareAsked > 0,
26932
- ...phone ? { key: `share-${shareAsked}`, className: "hidden" } : { key: "share" }
26933
- }
26968
+ ...phone ? { className: "hidden" } : {}
26969
+ },
26970
+ phone ? `share-${shareAsked}` : "share"
26934
26971
  ),
26935
26972
  /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)(import_design_system85.DropdownMenu, { children: [
26936
26973
  /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(import_design_system85.Button, { size: "sm", variant: "ghost", "aria-label": "Table menu", title: "Table menu", "data-table-menu": "", children: /* @__PURE__ */ (0, import_jsx_runtime94.jsxs)("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "currentColor", "aria-hidden": "true", children: [