@ai-matrx/records-ui 0.42.0 → 0.44.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,59 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.44.0
4
+
5
+ **Pressing Open in the inbox landed you on the table with the record still shut.**
6
+
7
+ `ActionInbox` has always handed its host `onOpenRecord(recordId, tableId)`, and
8
+ the app has always routed that to `/data-v2/<table>?record=<record>` — and
9
+ NOTHING read the parameter. So an approval, an assignment, an agent's proposal
10
+ and (since 0.41.0) a checklist step all offered an Open that did not open
11
+ anything. Measured in the real app on a dental practice's onboarding steps.
12
+
13
+ `TablePage` takes `activeRecordId` and opens the record rail on it, moving the
14
+ rail when a later link names a different record. Absent, the page opens exactly
15
+ as it did. `openingRail` is the exported seam and `src/record-link.test.tsx`
16
+ fails on 0.43.0's bytes.
17
+
18
+ **Consumer action:** pass `activeRecordId` from your route's `?record=`
19
+ parameter, beside the `activeDashboardId` you already pass. Without it the Open
20
+ control in the inbox still goes nowhere.
21
+
22
+ ## 0.43.0
23
+
24
+ **Five headings carrying the store's real counts, over five empty columns.**
25
+
26
+ Measured in the real app (lane SCREENS-2, 2026-09-20) on a dental practice's
27
+ treatment plans, immediately after 0.42.0 made the board reachable at all: it
28
+ drew *Proposed 4*, *Insurance review 3*, *Scheduled 2 / 2* — and every column was
29
+ empty. That is the worst thing a screen can do. A heading that says four over a
30
+ column that shows none, with nothing anywhere saying the two disagree.
31
+
32
+ **A real disagreement about what a cell holds.** `PipelineStage.key` is documented
33
+ as "what a cell holds" and `custom.pipeline_board` keys its columns that way
34
+ (`stage_key: "proposed"`). `custom.read_records` answers the LABEL in the cell and
35
+ puts the key BESIDE it:
36
+
37
+ ```json
38
+ {"stage": "Proposed", "_choices": {"stage": {"key": "proposed", "label": "Proposed"}}}
39
+ ```
40
+
41
+ so `cardsByStage.get(stage.key)` matched nothing, for every row, on every pipeline.
42
+
43
+ `stageOfCard` is the one seam that decides it and it asks the ROW's own resolved
44
+ choice first, never the wording. Two fallbacks follow, because a cell may
45
+ legitimately hold the key (an agent wrote it) or only the label (an import did),
46
+ and neither is a reason to lose a card.
47
+
48
+ **And a card with nowhere to go is now SAID.** A retired stage, or a value an
49
+ import wrote that is not one of the board's choices, used to vanish — a board
50
+ quietly one card short is the same lie as the heading that disagreed with its
51
+ column. The board names them and says how to move one.
52
+
53
+ `src/pipeline-cards.test.tsx` — five clauses, all red on 0.42.0's bytes.
54
+
55
+ **Consumer action:** none.
56
+
3
57
  ## 0.42.0
4
58
 
5
59
  **A pipeline table opened as "pick a column", and the board it already had was
package/dist/index.cjs CHANGED
@@ -5029,6 +5029,27 @@ var import_react38 = require("react");
5029
5029
  var import_react39 = require("@ai-matrx/records/react");
5030
5030
  var import_design_system18 = require("@ai-matrx/design-system");
5031
5031
  var import_jsx_runtime20 = require("react/jsx-runtime");
5032
+ var UNPLACED = "\0unplaced";
5033
+ function stageOfCard(row, stageKey, stages) {
5034
+ const document2 = row.document ?? {};
5035
+ const choices = document2["_choices"];
5036
+ if (choices && typeof choices === "object") {
5037
+ const chosen = choices[stageKey];
5038
+ if (chosen && typeof chosen === "object") {
5039
+ const key = chosen["key"];
5040
+ if (typeof key === "string" && key !== "") {
5041
+ return stages.some((s) => s.key === key) ? key : null;
5042
+ }
5043
+ }
5044
+ }
5045
+ const raw = document2[stageKey];
5046
+ if (raw === null || raw === void 0 || raw === "") return null;
5047
+ const said = String(raw).trim();
5048
+ const byKey = stages.find((s) => s.key === said);
5049
+ if (byKey) return byKey.key;
5050
+ const byLabel = stages.find((s) => s.label.toLowerCase() === said.toLowerCase());
5051
+ return byLabel ? byLabel.key : null;
5052
+ }
5032
5053
  function PipelineBoard({
5033
5054
  tableId,
5034
5055
  rows,
@@ -5078,12 +5099,12 @@ function PipelineBoard({
5078
5099
  const map = /* @__PURE__ */ new Map();
5079
5100
  if (!stageKey) return map;
5080
5101
  for (const row of rows) {
5081
- const raw = (row.document ?? {})[stageKey];
5082
- const key = raw === null || raw === void 0 || raw === "" ? "" : String(raw);
5083
- map.set(key, [...map.get(key) ?? [], row]);
5102
+ const key = stageOfCard(row, stageKey, definition?.stages ?? []);
5103
+ map.set(key ?? UNPLACED, [...map.get(key ?? UNPLACED) ?? [], row]);
5084
5104
  }
5085
5105
  return map;
5086
- }, [rows, stageKey]);
5106
+ }, [rows, stageKey, definition?.stages]);
5107
+ const unplaced = cardsByStage.get(UNPLACED) ?? [];
5087
5108
  const all = fields.data ?? [];
5088
5109
  const titleField = (0, import_react38.useMemo)(
5089
5110
  () => all.find((f) => f.key !== stageKey) ?? all[0],
@@ -5230,7 +5251,14 @@ function PipelineBoard({
5230
5251
  },
5231
5252
  stage.key
5232
5253
  );
5233
- }) })
5254
+ }) }),
5255
+ unplaced.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-xs text-muted-foreground", "data-testid": "pipeline-unplaced", children: [
5256
+ unplaced.length === 1 ? "One record is" : `${unplaced.length} records are`,
5257
+ " not in any of these columns \u2014 ",
5258
+ unplaced.slice(0, 3).map((r) => rowName(r, titleField?.key ?? null, "Untitled")).join(", "),
5259
+ unplaced.length > 3 ? ` and ${unplaced.length - 3} more` : "",
5260
+ ". Their stage is not one this pipeline offers, so nothing here can draw them; open one from the grid to move it."
5261
+ ] }) : null
5234
5262
  ] });
5235
5263
  }
5236
5264
  function formatTotal(total) {
@@ -11873,6 +11901,9 @@ var VIEW_NOT_SAVED_YET = "This table has no saved view yet, so the layout you pi
11873
11901
  function defaultView(tableId) {
11874
11902
  return { name: DEFAULT_VIEW_NAME, subject: tableId, layout: "grid", isDefault: true };
11875
11903
  }
11904
+ function openingRail(activeRecordId) {
11905
+ return activeRecordId ? { rail: "record", record: activeRecordId } : { rail: "none", record: null };
11906
+ }
11876
11907
  function TablePage({
11877
11908
  tableId,
11878
11909
  pageSize = 100,
@@ -11880,6 +11911,7 @@ function TablePage({
11880
11911
  onLeave,
11881
11912
  leaveLabel = "Back to your tables",
11882
11913
  activeDashboardId,
11914
+ activeRecordId,
11883
11915
  className
11884
11916
  }) {
11885
11917
  const client = (0, import_react98.useRecordsClient)();
@@ -11887,12 +11919,18 @@ function TablePage({
11887
11919
  const rights = useTableRights(table.data);
11888
11920
  const organizationId = (0, import_react98.useRecordsClient)().config.organizationId;
11889
11921
  const [view, setView] = (0, import_react97.useState)(null);
11890
- const [rail, setRail] = (0, import_react97.useState)("none");
11922
+ const opening = openingRail(activeRecordId);
11923
+ const [rail, setRail] = (0, import_react97.useState)(opening.rail);
11891
11924
  const [asking, setAsking] = (0, import_react97.useState)(null);
11892
11925
  const [main, setMain] = (0, import_react97.useState)(activeDashboardId ? "dashboards" : "records");
11893
- const [openRecord, setOpenRecord] = (0, import_react97.useState)(null);
11926
+ const [openRecord, setOpenRecord] = (0, import_react97.useState)(opening.record);
11894
11927
  const viewVersion = useRecordVersion(view?.id ?? null);
11895
11928
  const show = (next) => setRail((current) => current === next ? "none" : next);
11929
+ (0, import_react97.useEffect)(() => {
11930
+ if (!activeRecordId) return;
11931
+ setOpenRecord(activeRecordId);
11932
+ setRail("record");
11933
+ }, [activeRecordId]);
11896
11934
  const patchView = (0, import_react97.useCallback)(
11897
11935
  async (patch) => {
11898
11936
  const current = view;