@ai-matrx/records-ui 0.9.0 → 0.16.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,275 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.16.0
4
+
5
+ **The grid never draws a row before it knows what the person may do.** My own
6
+ regression from 0.12.0's refresh fix, bisected rather than guessed: treating the
7
+ whole first load as "the rows have arrived" let the grid render while the
8
+ table's own read was still in flight, and `useTableRights` answers `NO_RIGHTS`
9
+ (`known: false`) then — so every cell came out as plain text with no editor and
10
+ a click on it did nothing and said nothing. The declaration, the columns and the
11
+ person's own level are answered ONCE per table, so they are a FIRST LOAD; only
12
+ the page re-read is a refresh, and it still never blanks rows somebody is
13
+ already typing in.
14
+
15
+ **A publish gate that can be believed.** This package had not published since
16
+ 0.10.3 while five versions piled up, because `pnpm test` ran the whole live
17
+ component suite against a database several lanes' harnesses share behind one
18
+ advisory lock — each run failed a different handful of timing clauses with no
19
+ overlap. `pnpm test` is now a DETERMINISTIC component suite (the real components
20
+ against recorded real-door answers at this package's own `RecordsDataSource`
21
+ seam, no network, and a call the fixture never saw throws by name) plus a SHORT
22
+ live smoke (declare a table, take a column, write, read back, page, delete).
23
+ Typecheck and both suites run in 27 seconds. The full live suite is
24
+ `pnpm test:live` and runs in its own workflow on the tag and on a schedule,
25
+ reporting red loudly and blocking nothing. Nothing was deleted or loosened.
26
+
27
+ **And the two suites that could not run on a lane's own port** —
28
+ `system-table-shape` and `walk` pinned 127.0.0.1:3005 — now read
29
+ `RECORDS_UI_DEMO_PORT`.
30
+
31
+ ### Consumer action
32
+
33
+ None. No API changed. `pnpm test:record` re-records the door fixture.
34
+
35
+ ## 0.15.0
36
+
37
+ **`ActionInbox` reads the STORE's queue, and the package-owned one is gone.**
38
+ SCR-11 shipped with its own `records_ui_action` Table: agents wrote rows into it
39
+ and it rendered them beautifully. It was a second source of truth. An approval a
40
+ person filed through the store, and the wait the server's own `field_propose`
41
+ produces under `ask`, appeared nowhere in it — which is exactly the thing
42
+ PRODUCTS.md row 6 says the product must not be ("One inbox holds human approvals
43
+ AND the agent's field and table proposals — the same queue, the same right to
44
+ approve").
45
+
46
+ It now reads `custom.work_inbox` and decides through `custom.work_approval_decide`,
47
+ which APPLIES the change in the same transaction as the decision, as the person
48
+ who approved, through the store's own doors.
49
+
50
+ * **Keyboard-first**, because Linear's inbox is the bar: `j`/`k` or the arrows
51
+ move, `a` approves, `d` declines, `o` opens, `r` refreshes. The selected row
52
+ scrolls into view and the header states the keys.
53
+ * **Level-aware, absent rather than dead.** The door answers `actionable` — which
54
+ is `custom.work_approval_may_decide` asked per row — so a row this person may
55
+ not decide renders with NO buttons and says what it is waiting for, instead of
56
+ a greyed-out Approve that fails when clicked. The person who ASKED for a change
57
+ is refused the answer to their own request, by the store, and the row says so.
58
+ * The store's own outcome sentence is shown verbatim ("Rate card is now a column
59
+ on this table"); this component never rewrites it.
60
+
61
+ **Consumer action — BREAKING for anybody who seeded the old queue.**
62
+ `ACTION_TABLE`, `actionDocument`, `QueuedAction`, `QueuedActionSpec` and the
63
+ `seed` prop are removed. Nothing seeds an inbox any more: a person or an agent
64
+ files a real request through `custom.work_approval_request`
65
+ (`client.workApprovalRequest(...)`) and it appears. `ActionInbox` keeps
66
+ `tableId`, `includeSettled`, `onOpenRecord` and `className`. Requires
67
+ `@ai-matrx/records` 0.10.0.
68
+
69
+ ## 0.12.0
70
+
71
+ **Everything 0.11.0 carried — the level-aware screens and the one refusal
72
+ formatter below — is in this release. 0.11.0 was tagged and never published: its
73
+ gate failed, and two of the four failures were real defects rather than stale
74
+ copy. They are what this version fixes.**
75
+
76
+ **A read the person did not ask for no longer takes the cell they are typing in
77
+ off the screen.** `Grid` handed `MatrxDataTable` an `isLoading` that went true on
78
+ every `reload()`, and a cell write reloads the page — while `isLoading` the data
79
+ table renders six SKELETON rows IN PLACE OF the real ones, so committing one cell
80
+ unmounted every cell on the page, the open editor included. In a browser that
81
+ costs the caret and any keystroke inside the window; in the live suite one column
82
+ of a tabbed row saved as nothing, a different one each run, which is what made it
83
+ look like the store rather than the screen. A refresh is now `isFetching` — the
84
+ data table's own refresh line, with the rows kept — and `isLoading` means only
85
+ what it says: there is nothing to show yet.
86
+
87
+ **Enter finishes a long-text cell, and Shift+Enter makes the newline.** It used
88
+ to be the other way round, with Ctrl/Cmd+Enter to save, printed in 10px under the
89
+ box: the key every grid in the world binds did nothing in the columns whose
90
+ values are longest. Airtable is the bar. A json cell takes its newline the same
91
+ way — it was not counted as multi-line at all, so Enter could not save it either.
92
+
93
+ **One version read per record, and a version only ever goes up.** Opening a cell
94
+ asked the store for the record's version, and tabbing on asked again before
95
+ either answer was back; the write between them stored the store's new version,
96
+ and whichever read landed last put the old one back. The next cell of that row
97
+ then carried a version the store had already moved past, and a person tabbing
98
+ through their own row was shown a conflict with themselves.
99
+
100
+ **A borrowed value and a total can be built over a Person or a File again.**
101
+ `ValueOnTheOtherSide` offers the columns the relation's target Table DECLARES —
102
+ and a Person column points at the person kernel, which has 441 records and zero
103
+ Field rows, so it offered nothing and said the table had no columns. The store
104
+ reads the far side as `custom.record_values(…) -> pick`, so a pick names a key
105
+ those records CARRY: when the target declares no Fields, the panel reads one of
106
+ its records through the ordinary read door and offers the keys it has.
107
+
108
+ ### Consumer action
109
+
110
+ None beyond 0.11.0's (below): no API changed. Requires `@ai-matrx/records`
111
+ >= 0.7.0 for `custom.my_levels`.
112
+
113
+ ## 0.11.0 — tagged, never published
114
+
115
+ **The screens never invite an action the ladder will refuse.** The 19–20
116
+ September verdict (sixth pass), measured against Airtable, Notion and Linear:
117
+ "None of these three products lets you start an action it already knows it will
118
+ reject."
119
+
120
+ **Every control follows the level the STORE answers.** The rights question was a
121
+ port with two honest answers and no third — nothing offered, or
122
+ `storeDecidesRights`: yes to everything for every non-kernel table, for
123
+ everybody, and let the door refuse afterwards. That second one is what every
124
+ person in the product met, and it is the defect written down. `useTableRights`
125
+ now asks `custom.my_levels` (a table is a record), `useRecordRights` asks for one
126
+ record, and `useRowRights` asks for a whole page in ONE call — and only when the
127
+ table's own level does not already admit editing, because a record shared at
128
+ Editor inside a table held at Viewer is exactly what the verdict found and the
129
+ grid labelled that row "viewer" while the store let its holder write to it.
130
+
131
+ `src/rights.ts` is the ONE place a level becomes a screen: six capabilities —
132
+ read, comment, write, remove, share, structure — each keyed to the rung the DOOR
133
+ itself asks for. Every `false` carries the sentence that says why, naming the
134
+ level it would take and who can give it: *"You are Viewer here, and it takes
135
+ Editor to change anything here. Ask somebody who is Admin on it to move you up."*
136
+ Absent AND explained. A cell a person may not write is plain text that says so
137
+ on hover; Tab walks PAST a row they may not write rather than opening an editor
138
+ in it; Delete is absent on those rows; Share is absent below Admin; the field
139
+ panel and the table settings say what changing the shape of a table takes.
140
+
141
+ **One formatter, and every refusal goes through it.** `src/plainWords.ts`;
142
+ `Refusal.tsx` renders what it returns and NOTHING else — no `error.message`, no
143
+ `error.hint`, no SQLSTATE where a person reads. Filtering the hint was not
144
+ enough, structurally: the store builds its access refusals with `format()` from
145
+ THE DOOR'S OWN NAME, so the machine identity is inside the sentence written for
146
+ the person ("You do not have access to this record, so `custom.record_update`
147
+ may not write to it"). The formatter drops the machine clause by SHAPE, keeps
148
+ the store's own words where they are a person's words, and ADDS A REMEDY keyed
149
+ off the refusal code — a refusal with nothing to do about it is a dead end with
150
+ an explanation. What it drops is kept for an engineer on the notice and in a
151
+ screen-reader-only line. Nine screens that had captured `error.message` into
152
+ their own state now go through it too.
153
+
154
+ **A borrowed value and a total no longer ask for an internal key.** "The value
155
+ to read" was a free-text box with the placeholder `name`; the relation already
156
+ says which Table it points at, so both now offer that Table's columns BY NAME.
157
+
158
+ **All sixteen column types open their own editor.** Four editor states that drew
159
+ a paragraph rather than a box — a choice list loading, a choice list whose Table
160
+ is empty, a multi-choice row, a worked-out column — carried no cell id, so the
161
+ cell could not identify itself. Verified keyboard-only against the live store:
162
+ sixteen open, nine typed kinds land, the date included.
163
+
164
+ **Internal names, swept, by a guard that reads rendered text node by node.** Gone
165
+ from where a person reads: the derived key under the field panel's name box; the
166
+ machine key as a history label and raw JSON for unknown history values; the
167
+ conflict line's key and two JSON blobs; the record panel's provenance rows as
168
+ JSON; a truncated uuid standing in for an unnamed subject's name; JSON dumps in
169
+ cells and in the grid's conflict line.
170
+
171
+ ### Consumer action
172
+
173
+ **None for the three mount sites in matrx-frontend** — they already pass
174
+ `letTheStoreDecideRights`, which now means what it says: bind no `rights` port
175
+ and the package asks the store. `storeDecidesRights` is DEPRECATED and answers
176
+ "not known": it is synchronous, the honest answer needs a door call, and a port
177
+ bound there would OVERRIDE the store's answer. A host that binds `rights` of its
178
+ own still wins, and `tableRightsAt(level)` is the one way to build that answer
179
+ by hand — it takes the LEVEL, never six booleans, so nobody can hand a screen a
180
+ combination the ladder cannot produce.
181
+
182
+ Requires `@ai-matrx/records` >= 0.7.0 for `custom.my_levels`.
183
+
184
+ ## 0.10.3
185
+
186
+ Reissues the unpublished 0.10.2 candidate. Its live-store release gate exposed
187
+ a race in the field-add smoke harness: it unmounted each panel immediately
188
+ after clicking Save, before the asynchronous declaration had landed. The
189
+ helper now waits until the real store reports the exact new Field before
190
+ proceeding. Package behavior is unchanged.
191
+
192
+ ### Consumer action
193
+
194
+ None.
195
+
196
+ ## 0.10.2
197
+
198
+ Reissues the unpublished 0.10.1 candidate after its live-store release gate
199
+ revealed that the demo selector read `document.title` while the actual
200
+ `custom.record` row exposes the record payload as `data.title`. The smoke test
201
+ now selects Acme Industrial from the real row shape; package behavior is unchanged.
202
+
203
+ ### Consumer action
204
+
205
+ None.
206
+
207
+ ## 0.10.1
208
+
209
+ Reissues the unpublished 0.10.0 package after its live-store smoke test selected
210
+ the oldest demo record while asserting the identity of Acme Industrial. The
211
+ test now selects that record by title, so re-seeding cannot substitute another
212
+ record. Package behavior is unchanged from the 0.10.0 candidate.
213
+
214
+ ### Consumer action
215
+
216
+ None.
217
+
218
+ ## 0.10.0
219
+
220
+ **The three things the 19 September verdict (fifth pass) said a person hits the
221
+ moment they touch a table.**
222
+
223
+ **A double-click opens the typed editor, on every cell.** The verdict: "only the
224
+ Title column can be opened by double-clicking; double-clicking any other cell
225
+ opens a developer inspector — a floating window showing the record's raw JSON,
226
+ its machine keys, a truncated id, and the headings DOCUMENT, LEVEL and HIDDEN."
227
+ Two things did that. The data table ships a side panel AND a floating window,
228
+ both falling back to `DataRowInspector`; the grid now turns both OFF, because
229
+ this package has a record panel of its own (`Peek`, through `onRowOpen`) and a
230
+ second one beside it is the parallel layer the platform forbids. And a cell now
231
+ FILLS its cell and answers `dblclick` itself — the second press used to land on
232
+ bare table cell after the first had swapped the DOM under the pointer, and a
233
+ bare cell is a row click, which is exactly why a wide column worked and every
234
+ narrow one did not.
235
+
236
+ **Tab commits the typed value before it moves.** The verdict: "when we tabbed
237
+ along a row, the first five values saved and the last four were silently
238
+ dropped." The draft used to live in the open CELL, and this screen re-renders
239
+ that cell for reasons that have nothing to do with the person typing — every
240
+ accepted write calls `reload()`, which rebuilds the rows, the columns and the
241
+ cells. A cell that remounted mid-edit came back initialised from the stored
242
+ value, so the commit wrote what was already there and the screen correctly
243
+ treated it as "nothing changed". The draft now lives in the editing session,
244
+ beside the address of the open cell and the value it started from; `commit(move)`
245
+ takes no value at all, because there is only one place the typed value can be
246
+ read from. The editor is focused once per opening rather than on every render.
247
+
248
+ **"Total of linked records" can be added, and a refusal is a sentence.** The
249
+ panel now offers EVERY relation column — including the ones that hold a single
250
+ record, marked "holds one" — because hiding "Crew lead" from the list taught a
251
+ person nothing. `whatIsMissing` asks, in the panel's own words, what has not been
252
+ answered yet ("Crew lead points at one record at a time, so there is nothing to
253
+ total. Tick 'Can hold more than one' on Crew lead, or use Borrowed value to read
254
+ its one value."), the sentence sits under the button, and the panel never sends a
255
+ declaration it already knows the store must refuse.
256
+
257
+ **A hint written for an engineer never reaches a person.** A store refusal
258
+ carries a message for the person and a hint for whoever has to fix the caller
259
+ ("FLD-11: … config.via names a relation Field of this same table, by its key").
260
+ This package printed both. `hintForAPerson` now filters the hint BY SHAPE — a
261
+ contract row id, a schema-qualified database object, a path inside a document,
262
+ a snake_case identifier or a SQLSTATE — and what it filters joins the SQLSTATE on
263
+ the notice's title, for an engineer. A hint in ordinary words is still shown
264
+ verbatim.
265
+
266
+ New exports: `whatIsMissing`, `hintForAPerson`, `hintIsMachineIdentity`.
267
+
268
+ ### Consumer action
269
+
270
+ None. `GridEditing.commit` now takes `(move)` instead of `(value, move)` and
271
+ carries `draft` / `type`; nothing outside this package calls it.
272
+
3
273
  ## 0.9.0
4
274
 
5
275
  **Sharing reaches the record and table screens (SCR-N-5).** `ShareControl` puts a