@proteinjs/db-ui 1.13.1 → 1.14.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.
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __assign = (this && this.__assign) || function () {
18
+ __assign = Object.assign || function(t) {
19
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
20
+ s = arguments[i];
21
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
22
+ t[p] = s[p];
23
+ }
24
+ return t;
25
+ };
26
+ return __assign.apply(this, arguments);
27
+ };
28
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
29
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
30
+ return new (P || (P = Promise))(function (resolve, reject) {
31
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
32
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
33
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
34
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
35
+ });
36
+ };
37
+ var __generator = (this && this.__generator) || function (thisArg, body) {
38
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
39
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
40
+ function verb(n) { return function (v) { return step([n, v]); }; }
41
+ function step(op) {
42
+ if (f) throw new TypeError("Generator is already executing.");
43
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
44
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
45
+ if (y = 0, t) op = [op[0] & 2, t.value];
46
+ switch (op[0]) {
47
+ case 0: case 1: t = op; break;
48
+ case 4: _.label++; return { value: op[1], done: false };
49
+ case 5: _.label++; y = op[1]; op = [0]; continue;
50
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
51
+ default:
52
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
53
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
54
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
55
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
56
+ if (t[2]) _.ops.pop();
57
+ _.trys.pop(); continue;
58
+ }
59
+ op = body.call(thisArg, _);
60
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
61
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
62
+ }
63
+ };
64
+ var __importDefault = (this && this.__importDefault) || function (mod) {
65
+ return (mod && mod.__esModule) ? mod : { "default": mod };
66
+ };
67
+ Object.defineProperty(exports, "__esModule", { value: true });
68
+ /**
69
+ * @jest-environment jsdom
70
+ *
71
+ * RecordForm's field GROUPING (round 2). Contracts as OUTCOMES:
72
+ * 1. Sections derive from column type + name in a fixed order: identity (name/email/title)
73
+ * → Content (long text, structured values) → Details (everything else) → System
74
+ * (id/created/updated), and System is always last.
75
+ * 2. A column's `ui.formGroup` hint overrides the derivation; an unknown hint value becomes
76
+ * its own titled section, ordered after Details.
77
+ * 3. The record's id renders in System as a readonly value row — a record form's address is
78
+ * what an admin copies, and the table layer's `ui.hidden` (a DATA-column flag) doesn't
79
+ * bury it here.
80
+ * 4. A single-section form stays unlabeled — one group needs no header.
81
+ */
82
+ var react_1 = __importDefault(require("react"));
83
+ var moment_1 = __importDefault(require("moment"));
84
+ var client_1 = require("react-dom/client");
85
+ var test_utils_1 = require("react-dom/test-utils");
86
+ var react_router_dom_1 = require("react-router-dom");
87
+ var db_1 = require("@proteinjs/db");
88
+ require("../generated");
89
+ var RecordForm_1 = require("../src/form/RecordForm");
90
+ var mockDbService = {
91
+ get: jest.fn(),
92
+ insert: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
93
+ return [2 /*return*/, record];
94
+ }); }); }),
95
+ update: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
96
+ return [2 /*return*/, record];
97
+ }); }); }),
98
+ delete: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
99
+ return [2 /*return*/, 1];
100
+ }); }); }),
101
+ };
102
+ jest.mock('@proteinjs/db', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/db')), { getDbService: function () { return mockDbService; } })); });
103
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
104
+ var JobTable = /** @class */ (function (_super) {
105
+ __extends(JobTable, _super);
106
+ function JobTable() {
107
+ var _this = _super !== null && _super.apply(this, arguments) || this;
108
+ _this.name = 'admin_section_job';
109
+ _this.columns = (0, db_1.withRecordColumns)({
110
+ // Declared deliberately out of section order: the form must GROUP, not echo the schema.
111
+ status: new db_1.StringColumn('status'),
112
+ failureMessage: new db_1.StringColumn('failure_message', {}, 4000),
113
+ name: new db_1.StringColumn('name'),
114
+ manual: new db_1.BooleanColumn('manual'),
115
+ payload: new db_1.ObjectColumn('payload', { ui: { hidden: false } }),
116
+ });
117
+ return _this;
118
+ }
119
+ return JobTable;
120
+ }(db_1.Table));
121
+ /** Same shape, but the status column claims the Content section by hint. */
122
+ var HintedJobTable = /** @class */ (function (_super) {
123
+ __extends(HintedJobTable, _super);
124
+ function HintedJobTable() {
125
+ var _this = _super !== null && _super.apply(this, arguments) || this;
126
+ _this.name = 'admin_section_hinted_job';
127
+ _this.columns = (0, db_1.withRecordColumns)({
128
+ status: new db_1.StringColumn('status', { ui: { formGroup: 'content' } }),
129
+ failureMessage: new db_1.StringColumn('failure_message', {}, 4000),
130
+ name: new db_1.StringColumn('name'),
131
+ manual: new db_1.BooleanColumn('manual', { ui: { formGroup: 'operations' } }),
132
+ payload: new db_1.ObjectColumn('payload', { ui: { hidden: false } }),
133
+ });
134
+ return _this;
135
+ }
136
+ return HintedJobTable;
137
+ }(db_1.Table));
138
+ var created = (0, moment_1.default)('2026-01-02T03:04:05.000Z');
139
+ var updated = (0, moment_1.default)('2026-02-03T04:05:06.000Z');
140
+ function loadedRecord() {
141
+ return {
142
+ id: 'job-77',
143
+ name: 'Nightly export',
144
+ failureMessage: 'It broke',
145
+ payload: { retries: 3 },
146
+ status: 'failure',
147
+ manual: false,
148
+ created: created,
149
+ updated: updated,
150
+ };
151
+ }
152
+ describe('RecordForm sections', function () {
153
+ var container;
154
+ var root;
155
+ beforeEach(function () {
156
+ jest.clearAllMocks();
157
+ container = document.createElement('div');
158
+ document.body.appendChild(container);
159
+ root = (0, client_1.createRoot)(container);
160
+ });
161
+ afterEach(function () {
162
+ (0, test_utils_1.act)(function () { return root.unmount(); });
163
+ container.remove();
164
+ });
165
+ var mount = function (table, record) { return __awaiter(void 0, void 0, void 0, function () {
166
+ return __generator(this, function (_a) {
167
+ switch (_a.label) {
168
+ case 0: return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
169
+ return __generator(this, function (_a) {
170
+ root.render(react_1.default.createElement(react_router_dom_1.MemoryRouter, null,
171
+ react_1.default.createElement(RecordForm_1.RecordForm, { table: table, record: record })));
172
+ return [2 /*return*/];
173
+ });
174
+ }); })];
175
+ case 1:
176
+ _a.sent();
177
+ return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
178
+ return __generator(this, function (_a) {
179
+ switch (_a.label) {
180
+ case 0: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
181
+ case 1:
182
+ _a.sent();
183
+ return [2 /*return*/];
184
+ }
185
+ });
186
+ }); })];
187
+ case 2:
188
+ _a.sent();
189
+ return [2 /*return*/];
190
+ }
191
+ });
192
+ }); };
193
+ var sectionLabels = function () {
194
+ return Array.from(document.querySelectorAll('[data-form-section-label]')).map(function (el) { return el.textContent; });
195
+ };
196
+ /** The section element a field's label sits inside. */
197
+ var sectionOf = function (labelText) {
198
+ var label = Array.from(document.querySelectorAll('label')).find(function (l) { var _a; return (_a = l.textContent) === null || _a === void 0 ? void 0 : _a.startsWith(labelText); });
199
+ if (!label) {
200
+ throw new Error("No field labeled: ".concat(labelText));
201
+ }
202
+ return label.closest('[data-form-section]');
203
+ };
204
+ var sectionIndex = function (labelText) {
205
+ return Array.from(document.querySelectorAll('[data-form-section]')).indexOf(sectionOf(labelText));
206
+ };
207
+ it('groups fields into identity → Content → Details → System, in that order', function () { return __awaiter(void 0, void 0, void 0, function () {
208
+ var sections;
209
+ return __generator(this, function (_a) {
210
+ switch (_a.label) {
211
+ case 0: return [4 /*yield*/, mount(new JobTable(), loadedRecord())];
212
+ case 1:
213
+ _a.sent();
214
+ expect(sectionLabels()).toEqual(['Content', 'Details', 'System']);
215
+ // Identity leads (unlabeled), long text/structured land in Content, the rest in Details,
216
+ // server-managed meta last — regardless of the schema's declaration order.
217
+ expect(sectionIndex('Name')).toBe(0);
218
+ expect(sectionOf('Failure message').textContent).toContain('Content');
219
+ expect(sectionOf('Payload').textContent).toContain('Content');
220
+ expect(sectionOf('Status').textContent).toContain('Details');
221
+ expect(sectionOf('Manual').textContent).toContain('Details');
222
+ expect(sectionOf('Created').textContent).toContain('System');
223
+ expect(sectionOf('Updated').textContent).toContain('System');
224
+ sections = document.querySelectorAll('[data-form-section]');
225
+ expect(sectionIndex('Created')).toBe(sections.length - 1);
226
+ return [2 /*return*/];
227
+ }
228
+ });
229
+ }); });
230
+ it('renders the record id in System as a readonly value row', function () { return __awaiter(void 0, void 0, void 0, function () {
231
+ var idSection, row;
232
+ return __generator(this, function (_a) {
233
+ switch (_a.label) {
234
+ case 0: return [4 /*yield*/, mount(new JobTable(), loadedRecord())];
235
+ case 1:
236
+ _a.sent();
237
+ idSection = sectionOf('Id');
238
+ expect(idSection.textContent).toContain('System');
239
+ row = idSection.querySelector('[data-readonly-value-row]');
240
+ expect(row).not.toBeNull();
241
+ expect(idSection.textContent).toContain('job-77');
242
+ return [2 /*return*/];
243
+ }
244
+ });
245
+ }); });
246
+ it('ui.formGroup overrides the derivation; an unknown hint becomes its own section after Details', function () { return __awaiter(void 0, void 0, void 0, function () {
247
+ return __generator(this, function (_a) {
248
+ switch (_a.label) {
249
+ case 0: return [4 /*yield*/, mount(new HintedJobTable(), loadedRecord())];
250
+ case 1:
251
+ _a.sent();
252
+ // 'status' (a short string that would derive to Details) claims Content by hint.
253
+ expect(sectionOf('Status').textContent).toContain('Content');
254
+ // 'manual' claims a section of its own, humanized, ordered after Details — and with both
255
+ // would-be Details fields re-hinted, Details doesn't render at all (no empty headers).
256
+ expect(sectionLabels()).toEqual(['Content', 'Operations', 'System']);
257
+ expect(sectionOf('Manual').textContent).toContain('Operations');
258
+ return [2 /*return*/];
259
+ }
260
+ });
261
+ }); });
262
+ it('a new-record form (no System meta) stays a single unlabeled section when one group covers it', function () { return __awaiter(void 0, void 0, void 0, function () {
263
+ var SimpleTable;
264
+ return __generator(this, function (_a) {
265
+ switch (_a.label) {
266
+ case 0:
267
+ SimpleTable = /** @class */ (function (_super) {
268
+ __extends(SimpleTable, _super);
269
+ function SimpleTable() {
270
+ var _this = _super !== null && _super.apply(this, arguments) || this;
271
+ _this.name = 'admin_section_simple';
272
+ _this.columns = (0, db_1.withRecordColumns)({
273
+ name: new db_1.StringColumn('name'),
274
+ });
275
+ return _this;
276
+ }
277
+ return SimpleTable;
278
+ }(db_1.Table));
279
+ return [4 /*yield*/, mount(new SimpleTable(), undefined)];
280
+ case 1:
281
+ _a.sent();
282
+ expect(document.querySelectorAll('[data-form-section]').length).toBe(1);
283
+ expect(sectionLabels()).toEqual([]);
284
+ return [2 /*return*/];
285
+ }
286
+ });
287
+ }); });
288
+ });
289
+ //# sourceMappingURL=recordFormSections.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordFormSections.test.js","sourceRoot":"","sources":["../../test/recordFormSections.test.tsx"],"names":[],"mappings":"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"}
@@ -283,16 +283,22 @@ describe('RecordForm structured fields', function () {
283
283
  }
284
284
  });
285
285
  }); });
286
- it('readonly timestamps display compact with the relative read as the helper line', function () { return __awaiter(void 0, void 0, void 0, function () {
287
- var createdControl;
286
+ it('readonly timestamps render as a value ROW (no input chrome) — compact stamp with the relative read inline', function () { return __awaiter(void 0, void 0, void 0, function () {
287
+ var labels, createdLabel, row;
288
288
  return __generator(this, function (_a) {
289
289
  switch (_a.label) {
290
290
  case 0: return [4 /*yield*/, mount(loadedRecord())];
291
291
  case 1:
292
292
  _a.sent();
293
- createdControl = controlByLabel('Created');
294
- expect(createdControl.value).toBe(created.format('MMM D, YYYY, h:mm A'));
295
- expect(document.body.textContent).toContain(created.fromNow());
293
+ labels = Array.from(document.body.querySelectorAll('label'));
294
+ createdLabel = labels.find(function (candidate) { var _a; return (_a = candidate.textContent) === null || _a === void 0 ? void 0 : _a.startsWith('Created'); });
295
+ expect(createdLabel).toBeDefined();
296
+ // No control is wired to it: a readonly timestamp is text, not an input.
297
+ expect(createdLabel.htmlFor).toBeFalsy();
298
+ row = createdLabel.closest('[data-form-field-row]').querySelector('[data-readonly-value-row]');
299
+ expect(row).not.toBeNull();
300
+ expect(row.textContent).toContain(created.format('MMM D, YYYY, h:mm A'));
301
+ expect(row.textContent).toContain(created.fromNow());
296
302
  return [2 /*return*/];
297
303
  }
298
304
  });
@@ -1 +1 @@
1
- {"version":3,"file":"recordFormStructuredFields.test.js","sourceRoot":"","sources":["../../test/recordFormStructuredFields.test.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recordFormStructuredFields.test.js","sourceRoot":"","sources":["../../test/recordFormStructuredFields.test.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db-ui",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "Db ui components",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -26,9 +26,9 @@
26
26
  "@emotion/styled": "11.11.0",
27
27
  "@mui/icons-material": "5.14.11",
28
28
  "@mui/material": "5.14.11",
29
- "@proteinjs/db": "^1.37.0",
29
+ "@proteinjs/db": "^1.38.0",
30
30
  "@proteinjs/reflection": "^1.2.0",
31
- "@proteinjs/ui": "^4.11.0",
31
+ "@proteinjs/ui": "^4.12.0",
32
32
  "@proteinjs/util": "^1.6.1",
33
33
  "moment": "2.29.4",
34
34
  "react": "18.2.0",
@@ -56,5 +56,5 @@
56
56
  },
57
57
  "main": "./dist/generated/index.js",
58
58
  "types": "./dist/generated/index.d.ts",
59
- "gitHead": "cb0fda126d5d2bd5b17d1bc13e87c2373eb61f52"
59
+ "gitHead": "7c4ebb2ab218d48a276940140161979f0aa31730"
60
60
  }
@@ -2,7 +2,16 @@ import React from 'react';
2
2
  import S from 'string';
3
3
  import moment from 'moment';
4
4
  import { StringUtil, isInstanceOf } from '@proteinjs/util';
5
- import { Form, Fields, FieldComponentProps, textField, checkboxField, dateField, FormButtons } from '@proteinjs/ui';
5
+ import {
6
+ Form,
7
+ Fields,
8
+ FieldComponentProps,
9
+ FormFieldSection,
10
+ textField,
11
+ checkboxField,
12
+ dateField,
13
+ FormButtons,
14
+ } from '@proteinjs/ui';
6
15
  import {
7
16
  Table,
8
17
  Record,
@@ -115,16 +124,17 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
115
124
  const recordFormCustomization = getRecordFormCustomization(table.name);
116
125
  const defaultFieldLayout = fieldLayout();
117
126
  const defaultFormButtons = buttons();
127
+ // The customization contract stays FLAT rows (string[] | string[][]) — customizations
128
+ // narrow/reorder without knowing about sections; the final layout is sectioned afterwards.
129
+ const customizedFieldLayout = recordFormCustomization
130
+ ? recordFormCustomization.getFieldLayout(record, defaultFieldLayout)
131
+ : defaultFieldLayout;
118
132
 
119
133
  return (
120
134
  <Form
121
135
  name={S(table.name).humanize().s}
122
136
  createFields={createFields()}
123
- fieldLayout={
124
- recordFormCustomization
125
- ? recordFormCustomization.getFieldLayout(record, defaultFieldLayout)
126
- : defaultFieldLayout
127
- }
137
+ fieldLayout={sectionizeFieldLayout(customizedFieldLayout)}
128
138
  buttons={
129
139
  recordFormCustomization
130
140
  ? recordFormCustomization.getFormButtons(record, defaultFormButtons)
@@ -148,7 +158,12 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
148
158
 
149
159
  for (const columnPropertyName in table.columns) {
150
160
  const column = getColumn(columnPropertyName);
151
- if (columnPropertyName == 'name' || columnPropertyName == 'created' || columnPropertyName == 'updated') {
161
+ if (
162
+ columnPropertyName == 'name' ||
163
+ columnPropertyName == 'id' ||
164
+ columnPropertyName == 'created' ||
165
+ columnPropertyName == 'updated'
166
+ ) {
152
167
  continue;
153
168
  }
154
169
 
@@ -161,6 +176,12 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
161
176
  }
162
177
 
163
178
  if (!isNewRecord) {
179
+ // The System section: id (schema-hidden as a DATA column, but the record's address is
180
+ // exactly what an admin copies off a record form — it renders as a readonly mono row),
181
+ // then the stored timestamps.
182
+ if ((table.columns as any)['id']) {
183
+ columns['id'] = getColumn('id');
184
+ }
164
185
  columns['created'] = getColumn('created');
165
186
  columns['updated'] = getColumn('updated');
166
187
  }
@@ -233,10 +254,10 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
233
254
  return customField(name, label, column, fieldRenderer);
234
255
  }
235
256
 
236
- // Readonly values render as text (a native date input isn't text-selectable): copyable
237
- // ids/timestamps beat a type-specific control the user can't interact with anyway.
257
+ // Readonly values render as text ROWS (no input chrome @proteinjs/ui's ReadonlyValueRow):
258
+ // copyable ids/timestamps beat a type-specific control the user can't interact with anyway.
238
259
  if (isReadonlyField(columnPropertyName, column)) {
239
- return textField({ name, label, accessibility: { readonly: true } });
260
+ return textField({ name, label, accessibility: { readonly: true }, monospace: columnPropertyName === 'id' });
240
261
  }
241
262
 
242
263
  if (isInstanceOf(column, BooleanColumn)) {
@@ -322,14 +343,75 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
322
343
  };
323
344
  }
324
345
 
346
+ /**
347
+ * Which form section a column belongs to (the founder's grouping order: identity → content
348
+ * → details/config → system meta last). `column.options.ui.formGroup` overrides the
349
+ * derivation; unknown hint strings become their own titled sections after Details.
350
+ */
351
+ function sectionForColumn(columnPropertyName: string, column: Column<T, any> | undefined): string {
352
+ if (['id', 'created', 'updated'].includes(columnPropertyName)) {
353
+ return 'system';
354
+ }
355
+
356
+ const hint = column?.options?.ui?.formGroup;
357
+ if (hint) {
358
+ return hint;
359
+ }
360
+
361
+ // Custom-rendered fields (service-owned state like user.roles) are config their component
362
+ // presents — Details unless the column hints otherwise, never Content by storage shape.
363
+ if (getFieldRenderer(columnPropertyName)) {
364
+ return 'details';
365
+ }
366
+
367
+ if (columnPropertyName === 'name' || isIdentityColumnName(columnPropertyName)) {
368
+ return 'identity';
369
+ }
370
+
371
+ if (
372
+ column &&
373
+ (isLongTextColumn(column) ||
374
+ ((isInstanceOf(column, ObjectColumn) || isInstanceOf(column, ArrayColumn)) &&
375
+ !isInstanceOf(column, ReferenceArrayColumn)))
376
+ ) {
377
+ return 'content';
378
+ }
379
+
380
+ return 'details';
381
+ }
382
+
383
+ /** Identity-named strings promote to the top (same suffix grammar as the table's column pick). */
384
+ function isIdentityColumnName(columnPropertyName: string) {
385
+ const name = columnPropertyName.toLowerCase();
386
+ return name.endsWith('email') || name.endsWith('title') || ['label', 'subject'].includes(name);
387
+ }
388
+
389
+ /** The default flat layout, ordered by section; the customization contract stays flat rows. */
325
390
  function fieldLayout(): any {
326
391
  const columns = getColumns();
327
392
  const layoutColumns = Object.entries(columns).length > 6 ? 2 : 1;
393
+
394
+ // Stable order: identity → content → details → custom-hint sections → system.
395
+ const sectionRank = (section: string) => ({ identity: 0, content: 1, details: 2, system: 4 })[section] ?? 3;
396
+ const ordered = Object.keys(columns).sort((a, b) => {
397
+ const rankDelta = sectionRank(sectionForColumn(a, columns[a])) - sectionRank(sectionForColumn(b, columns[b]));
398
+ if (rankDelta !== 0) {
399
+ return rankDelta;
400
+ }
401
+ // Within a section, keep getColumns() order (name first, then schema order).
402
+ return Object.keys(columns).indexOf(a) - Object.keys(columns).indexOf(b);
403
+ });
404
+
328
405
  if (layoutColumns > 1) {
329
406
  const layout: (keyof T)[][] = [];
330
407
  let lastRowIsSolo = false;
331
- for (const columnPropertyName in columns) {
408
+ let lastSection: string | undefined;
409
+ for (const columnPropertyName of ordered) {
332
410
  const column = columns[columnPropertyName];
411
+ const section = sectionForColumn(columnPropertyName, column);
412
+ // Rows never straddle sections — a section boundary always starts a fresh row.
413
+ const sectionChanged = section !== lastSection;
414
+ lastSection = section;
333
415
  // Multiline fields (long text / JSON) take a full-width row of their own — half a
334
416
  // two-column row squeezes exactly the fields that hold the most text.
335
417
  const isMultiline =
@@ -338,13 +420,20 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
338
420
  (isLongTextColumn(column) ||
339
421
  ((isInstanceOf(column, ObjectColumn) || isInstanceOf(column, ArrayColumn)) &&
340
422
  !isInstanceOf(column, ReferenceArrayColumn)));
341
- if (isMultiline) {
423
+ // The record's address wants its own line — a uuid halved into a two-column row wraps.
424
+ const isSoloValue = isMultiline || columnPropertyName === 'id';
425
+ if (isSoloValue) {
342
426
  layout.push([columnPropertyName as keyof T]);
343
427
  lastRowIsSolo = true;
344
428
  continue;
345
429
  }
346
430
 
347
- if (layout.length == 0 || lastRowIsSolo || layout[layout.length - 1].length >= layoutColumns) {
431
+ if (
432
+ layout.length == 0 ||
433
+ lastRowIsSolo ||
434
+ sectionChanged ||
435
+ layout[layout.length - 1].length >= layoutColumns
436
+ ) {
348
437
  layout.push([]);
349
438
  lastRowIsSolo = false;
350
439
  }
@@ -355,7 +444,47 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
355
444
  return layout;
356
445
  }
357
446
 
358
- return Object.keys(columns) as (keyof T)[];
447
+ return ordered as (keyof T)[];
448
+ }
449
+
450
+ /**
451
+ * Wrap a FLAT layout (default or customization-returned) into the quiet form sections the
452
+ * base Form renders. Each row maps to its first field's section; contiguous runs merge, so a
453
+ * customization's reordering stays honest (an interleaved order yields repeated sections
454
+ * rather than silently re-sorting the fields it chose). A single-section result stays
455
+ * unlabeled — one group needs no header.
456
+ */
457
+ function sectionizeFieldLayout(layout: string[] | string[][]): FormFieldSection<Fields>[] {
458
+ const sectionLabels: { [section: string]: string | undefined } = {
459
+ identity: undefined,
460
+ content: 'Content',
461
+ details: 'Details',
462
+ system: 'System',
463
+ };
464
+ const rows = (layout as (string | string[])[]).map((entry) => (Array.isArray(entry) ? entry : [entry]));
465
+ const sections: { section: string; fields: string[][] }[] = [];
466
+ for (const row of rows) {
467
+ if (row.length === 0) {
468
+ continue;
469
+ }
470
+
471
+ const section = sectionForColumn(row[0], getColumn(row[0]));
472
+ const last = sections[sections.length - 1];
473
+ if (last && last.section === section) {
474
+ last.fields.push(row);
475
+ } else {
476
+ sections.push({ section, fields: [row] });
477
+ }
478
+ }
479
+
480
+ if (sections.length <= 1) {
481
+ return sections.map(({ fields }) => ({ fields }) as FormFieldSection<Fields>);
482
+ }
483
+
484
+ return sections.map(({ section, fields }) => ({
485
+ label: section in sectionLabels ? sectionLabels[section] : StringUtil.humanizeCamel(section),
486
+ fields,
487
+ })) as FormFieldSection<Fields>[];
359
488
  }
360
489
 
361
490
  function getFieldValue(columnPropertyName: string, fieldValue: unknown) {
@@ -94,7 +94,9 @@ export function ReferenceCellValue({ tableName, id }: { tableName?: string; id?:
94
94
  event.preventDefault();
95
95
  navigate(url);
96
96
  }}
97
- sx={{ whiteSpace: 'nowrap' }}
97
+ // Data ink, link affordance: a table of primary-colored names competes with the row's
98
+ // identity (round 2's fewer-competing-inks pass) — the hover underline still says door.
99
+ sx={{ whiteSpace: 'nowrap', color: 'text.primary' }}
98
100
  >
99
101
  {name !== null ? (
100
102
  <Typography variant='body2' component='span'>
@@ -170,13 +170,15 @@ describe('RecordForm', () => {
170
170
  expect(dueDate.type).toBe('date');
171
171
  expect(dueDate.value).toBe('2026-08-10');
172
172
 
173
- // Readonly fields render as text, readOnly-not-disabled (item 3): copyable
174
- // (`id` itself is ui.hidden at the column layer and never renders)
173
+ // Readonly fields render as value ROWS (round 2): selectable text with a copy control
174
+ // and no input chrome at all.
175
175
  for (const labelText of ['Created', 'Updated', 'Archived at']) {
176
- const input = inputByLabel(labelText);
177
- expect(input.type).toBe('text');
178
- expect(input.readOnly).toBe(true);
179
- expect(input.disabled).toBe(false);
176
+ const label = Array.from(document.querySelectorAll('label')).find((l) => l.textContent?.startsWith(labelText))!;
177
+ expect(label).toBeDefined();
178
+ // Scope to the field's own shell: two readonly fields can share one two-column row.
179
+ const row = label.parentElement!.parentElement!.querySelector('[data-readonly-value-row]')!;
180
+ expect(row).not.toBeNull();
181
+ expect(row.querySelector(`button[aria-label="Copy ${labelText}"]`)).not.toBeNull();
180
182
  }
181
183
  });
182
184