@ai-matrx/records-ui 0.41.0 → 0.42.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,37 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.42.0
4
+
5
+ **A pipeline table opened as "pick a column", and the board it already had was
6
+ unreachable.**
7
+
8
+ Measured in the real app (lane SCREENS-2, 2026-09-20) against a real pipeline —
9
+ five stages, a moves rule, a requires rule and a WIP limit of 2.
10
+ `custom.pipeline_read` answered `is_pipeline: true` with `stage_field: "stage"`,
11
+ every rule beside it, and the kanban drew
12
+
13
+ > A kanban board needs a Field to make its columns from — pick one and the board
14
+ > appears.
15
+
16
+ Two things stacked. `Board`'s "no grouping field" gate sat ABOVE its pipeline
17
+ branch, and `useIsPipeline` answered `stage.key === groupField` — so a table was
18
+ only a pipeline once somebody had ALREADY guessed which column to group by. The
19
+ store knew the answer the whole time and the screen asked the person for it. A
20
+ board nobody can reach without knowing the answer in advance is not a board.
21
+
22
+ The stage column is now the TABLE's own answer (`custom.table_stage_field`), used
23
+ both to know the table is a pipeline and to know which column its columns come
24
+ from. A view that names its own grouping column still wins — grouping a pipeline
25
+ by its owner is a real thing to want, and it draws the plain board of that column
26
+ and says so. While the table has not answered, NOTHING is claimed in either
27
+ direction: the "pick one" sentence would be a question about to answer itself.
28
+
29
+ `src/pipeline-reach.test.tsx` is at the one seam that decides it
30
+ (`boardColumnChoice`, exported for exactly that reason) and all four clauses fail
31
+ on 0.41.0's bytes.
32
+
33
+ **Consumer action:** none. `ViewSwitcher`'s props are unchanged.
34
+
3
35
  ## 0.41.0
4
36
 
5
37
  **The last two product screens that could not work at all, rewritten against the
package/dist/index.cjs CHANGED
@@ -5439,7 +5439,7 @@ function offerableFields(all, want) {
5439
5439
  return kind === "datetime" || kind === "date";
5440
5440
  });
5441
5441
  }
5442
- function useIsPipeline(tableId, groupField) {
5442
+ function useStageField(tableId) {
5443
5443
  const client = (0, import_react41.useRecordsClient)();
5444
5444
  const [stage, setStage] = (0, import_react40.useState)({
5445
5445
  asked: false,
@@ -5456,8 +5456,17 @@ function useIsPipeline(tableId, groupField) {
5456
5456
  cancelled = true;
5457
5457
  };
5458
5458
  }, [client, tableId]);
5459
- if (!stage.asked) return null;
5460
- return stage.key != null && stage.key === groupField;
5459
+ return stage;
5460
+ }
5461
+ function boardColumnChoice(fields, chosenKey, stage) {
5462
+ const has = (key) => key != null && fields.some((f) => f.key === key) ? key : null;
5463
+ const chosen = has(chosenKey);
5464
+ if (chosen) {
5465
+ return { groupKey: chosen, isPipeline: stage.asked ? stage.key === chosen : null, stillAsking: false };
5466
+ }
5467
+ if (!stage.asked) return { groupKey: null, isPipeline: null, stillAsking: true };
5468
+ const fromTable = has(stage.key);
5469
+ return { groupKey: fromTable, isPipeline: fromTable != null, stillAsking: false };
5461
5470
  }
5462
5471
  function Board({
5463
5472
  view,
@@ -5469,7 +5478,7 @@ function Board({
5469
5478
  }) {
5470
5479
  const fields = (0, import_react41.useFields)(view.subject);
5471
5480
  const records = useViewRecords(view, pageSize);
5472
- const isPipeline = useIsPipeline(view.subject, view.groupField);
5481
+ const stage = useStageField(view.subject);
5473
5482
  if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: fields.error });
5474
5483
  if (records.error) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: records.error });
5475
5484
  if (fields.loading || records.loading) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system19.Skeleton, { className: "h-40 w-full" });
@@ -5477,7 +5486,9 @@ function Board({
5477
5486
  const byKey = (key) => all.find((f) => f.key === key);
5478
5487
  const titleField = byKey(view.groupField) ? all.find((f) => f.key !== view.groupField) ?? all[0] : all[0];
5479
5488
  if (view.layout === "kanban") {
5480
- const groupField = byKey(view.groupField);
5489
+ const choice = boardColumnChoice(all, view.groupField, stage);
5490
+ const groupField = byKey(choice.groupKey);
5491
+ const isPipeline = choice.isPipeline;
5481
5492
  const picker = /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
5482
5493
  FieldPicker,
5483
5494
  {
@@ -5489,6 +5500,7 @@ function Board({
5489
5500
  onPick: (key) => onChange({ groupField: key })
5490
5501
  }
5491
5502
  );
5503
+ if (choice.stillAsking) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system19.Skeleton, { className: "h-40 w-full" });
5492
5504
  if (!groupField) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Needs, { layout: "kanban", picker });
5493
5505
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex min-h-0 flex-col gap-2", children: [
5494
5506
  picker,