@ai-matrx/records-ui 0.16.0 → 0.28.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 +315 -0
- package/dist/index.cjs +2604 -1439
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +472 -57
- package/dist/index.d.ts +472 -57
- package/dist/index.js +2611 -1417
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,320 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
**The publish canary installs every peer this package declares.** It named
|
|
6
|
+
`react` and `react-dom` by hand, so the day a THIRD peer arrived (`recharts`,
|
|
7
|
+
with `ChartBlock`) the published bundle imported something no consumer had
|
|
8
|
+
been given and `import * as ui from "@ai-matrx/records-ui"` threw
|
|
9
|
+
`ERR_MODULE_NOT_FOUND` — the canary was right and the whole publish stopped
|
|
10
|
+
(run 35513820597). The peer list is read from the manifest, and the peers are
|
|
11
|
+
installed in a pass of their own so each brings its own peers (`react-is` is
|
|
12
|
+
`recharts`'). 0.27.0 was tagged and never published; its tag was left where it
|
|
13
|
+
is.
|
|
14
|
+
|
|
15
|
+
**A first-time admin could not make a table, and the gate could not see it.**
|
|
16
|
+
|
|
17
|
+
Creating one showed a red refusal and made a table anyway; that table opened with
|
|
18
|
+
no grid; the saved-views table behind every table page was itself unusable. One
|
|
19
|
+
cause, in three places: a Field is a record (REC-25), so this package wrote one
|
|
20
|
+
through the ordinary write door — and `custom._field_class_guard` refuses that
|
|
21
|
+
row outright (23514 / FLD-13, "a column of a table is stored as a field, and this
|
|
22
|
+
one says record"). Measured on the published screens (0.16.0) on 2026-09-20: four
|
|
23
|
+
tables in one organization, every one declaring a column NAME with no Field row
|
|
24
|
+
behind it.
|
|
25
|
+
|
|
26
|
+
- **`systemTable.ts` goes through the declare doors.** `declare` and
|
|
27
|
+
`addMissingFields` use `custom.field_declare`; the shape reconciliation uses
|
|
28
|
+
`custom.field_update`, which converts the values a behaviour change orphans and
|
|
29
|
+
writes the migration row — `record_update` wrote the two keys onto the document
|
|
30
|
+
and did none of it. `addMissingFields` no longer patches the Table's own
|
|
31
|
+
`fields` list: that is the door's job, and a client doing it separately is how a
|
|
32
|
+
Table came to declare a column nothing backed.
|
|
33
|
+
- **`records_ui_view` is why the grid was empty.** Its nine columns were refused,
|
|
34
|
+
so `ensureSystemTable` failed on every load, `ViewBar` could never read or seed
|
|
35
|
+
a view, and every table page in the organization read "Waiting for <name>'s
|
|
36
|
+
views" over an empty frame.
|
|
37
|
+
- **`ensureSystemTable`'s in-flight claim was never released on success**, which
|
|
38
|
+
turned a race guard into a permanent cache: after the first declaration, every
|
|
39
|
+
later call returned before the two reconciliation paths ran, so a package table
|
|
40
|
+
could not grow a column in the session that made it.
|
|
41
|
+
- **Creating a table is ONE act from the person's side.** A table that could not
|
|
42
|
+
take all of its columns is taken back, newest first, and a take-back that itself
|
|
43
|
+
fails SAYS SO and says what to do. Never a red sentence beside a table.
|
|
44
|
+
- **`declareTable` hands back the Home it made** (REC-14), so the caller that
|
|
45
|
+
made it can tidy it up.
|
|
46
|
+
- **`fieldDeclarationFor` carries everything the person said** — a list's choices,
|
|
47
|
+
a money column's unit, a date's day-or-day-and-time, a relation's target. Without
|
|
48
|
+
`options` the door refuses a choice list outright.
|
|
49
|
+
- **`TableSettings`' reorder goes through `custom.field_update`**, which is the
|
|
50
|
+
door for a field's own settings and the one that asks `admin` on the TABLE.
|
|
51
|
+
|
|
52
|
+
**A table with no columns is a real state, and it drew nothing at all.** The grid
|
|
53
|
+
now says so — "This table has no columns yet" — with the one control that ends it,
|
|
54
|
+
present for somebody who may change the table's shape and ABSENT with the ladder's
|
|
55
|
+
own reason for everybody else.
|
|
56
|
+
|
|
57
|
+
**The records are never held back by the bookkeeping.** `TablePage` always has a
|
|
58
|
+
view: the saved one when `ViewBar` has it, the unsaved default until then, with a
|
|
59
|
+
line saying the layout is not being kept yet. One table's plumbing can no longer
|
|
60
|
+
decide whether another table's data is visible.
|
|
61
|
+
|
|
62
|
+
**The gate now walks the first thing anybody does.** `demo/first-time-journey.ts`
|
|
63
|
+
is one walk driven twice — recorded real-door answers in `demo/deterministic.test.tsx`,
|
|
64
|
+
the real doors in `demo/live-smoke.test.tsx`: declare a table with no columns,
|
|
65
|
+
declare one with three columns of three different kinds, write a record, read it
|
|
66
|
+
back, see it in the page read, grow a package-owned table, and delete everything.
|
|
67
|
+
Red against the old code on both halves.
|
|
68
|
+
## 0.26.0 — 2026-09-20
|
|
69
|
+
|
|
70
|
+
**The owner's side of the client portal: who signs in from outside, and exactly
|
|
71
|
+
what they see.**
|
|
72
|
+
|
|
73
|
+
The portal doors could be called from 0.15.0 of `@ai-matrx/records`, and there
|
|
74
|
+
was no screen. `PortalsPanel` is that screen, and it is a RAIL ON THE TABLE'S
|
|
75
|
+
OWN PAGE beside its forms and its notifications — never a Portals area of the
|
|
76
|
+
app, for the same reason a form is not one: a portal is a way IN to Tables, so
|
|
77
|
+
it is reached from a Table.
|
|
78
|
+
|
|
79
|
+
- **The portals over this organization** — title, public address, how many
|
|
80
|
+
Tables, and the two numbers kept apart: signed in, and invited and waiting.
|
|
81
|
+
Adding them together is how an owner comes to believe a client is looking at
|
|
82
|
+
something they cannot reach.
|
|
83
|
+
- **One portal opened** — each exposed Table with the relation Field that makes
|
|
84
|
+
a record theirs (`names_via`), the fields they see, the fields they may
|
|
85
|
+
change, whether they may comment, and the most they are ever conveyed. Each
|
|
86
|
+
person with which of the three states they are in, in words.
|
|
87
|
+
- **Invite** — a picker over the portal's own client Table (a portal principal
|
|
88
|
+
IS one of those rows, so a client cannot be typed) and an email. It passes no
|
|
89
|
+
user id, and it shows the door's own sentence afterwards: they get access the
|
|
90
|
+
moment they follow the link, and until then the row holds nothing.
|
|
91
|
+
- **Remove their access** — the consequence is named BEFORE the click, by name:
|
|
92
|
+
this person can no longer sign in to this portal, and the records that named
|
|
93
|
+
their client stop reaching them. A generic "Are you sure?" fails this repo's
|
|
94
|
+
law. Afterwards the door's own past-tense sentence is shown.
|
|
95
|
+
- **View as this client** — `custom.portal_preview`'s answer, with a line saying
|
|
96
|
+
plainly that it is the same question their own screen asks. For somebody who
|
|
97
|
+
has not signed in, the door's refusal is shown rather than an empty list: an
|
|
98
|
+
empty list reads as "they see nothing" when the truth is they hold nothing yet.
|
|
99
|
+
- **Copy the portal's link** — through `portalPath`, and it shows the address to
|
|
100
|
+
copy by hand when a browser refuses the clipboard.
|
|
101
|
+
|
|
102
|
+
`PortalCardView` is exported as well: it takes a `PortalCard` and two callbacks
|
|
103
|
+
and holds no client at all, which is how `demo/portals-panel.test.tsx` mounts it
|
|
104
|
+
BARE in the blocking gate against a recorded real `custom.portal_card` answer
|
|
105
|
+
(`demo/fixtures/portal-card.json`, re-recordable with
|
|
106
|
+
`demo/record-portal-card.mjs`) with the real `custom.portal_preview` answers,
|
|
107
|
+
including that door's own refusal.
|
|
108
|
+
|
|
109
|
+
**Consumer action:** none required. A host that mounts `TablePage` gets the new
|
|
110
|
+
"Portals" rail with no change; a host that assembles its own page can mount
|
|
111
|
+
`PortalsPanel` anywhere it mounts `FormsPanel`. `RecordsUiHost.publicOrigin` is
|
|
112
|
+
what makes the copied address one a client can actually open.
|
|
113
|
+
|
|
114
|
+
## 0.21.0
|
|
115
|
+
|
|
116
|
+
**ONE dashboard object, one call, and every number clicks through to the records
|
|
117
|
+
it counts.**
|
|
118
|
+
|
|
119
|
+
`DashboardCanvas` kept its dashboards as rows of a package-owned
|
|
120
|
+
`records_ui_dashboard` Table — a second store the agent could not reach and the
|
|
121
|
+
database never validated, sitting beside the presentation-kernel dashboard that
|
|
122
|
+
`dashboard_propose` writes and `custom.dashboard_run` runs. It now reads
|
|
123
|
+
`custom.dashboards`, runs `custom.dashboard_run` and saves with
|
|
124
|
+
`custom.dashboard_declare`: the same three doors the agent uses. `DASHBOARD_TABLE`,
|
|
125
|
+
`dashboardDocument`, `dashboardFromRecord` and the seeding are gone, replaced by
|
|
126
|
+
one mapper each way — `dashboardFromSummary` / `dashboardDeclareArgs`, and
|
|
127
|
+
`specFromBlock` / `blockFromSpec` for a single block. `ChartKind` IS the store's
|
|
128
|
+
`DashboardBlockKind`; there is no second closed set of shapes.
|
|
129
|
+
|
|
130
|
+
**ONE call for the whole canvas.** Every block used to make its own
|
|
131
|
+
`custom.record_aggregate` request. The door answers all of them in one snapshot,
|
|
132
|
+
under the caller's own principal, each with its own `ms` — so eight blocks cannot
|
|
133
|
+
disagree about what the last hour did, and eight round trips are one. Each block
|
|
134
|
+
shows the milliseconds it took. A block the store REFUSED prints the store's own
|
|
135
|
+
sentence, classified by its SQLSTATE through the same formatter as every other
|
|
136
|
+
refusal, and its siblings still draw.
|
|
137
|
+
|
|
138
|
+
**The platform's chart library.** Bars, columns, lines and donuts are Recharts
|
|
139
|
+
now, not hand-cut `<rect>` and `<circle>`, with the app's own conventions:
|
|
140
|
+
`--color-<key>` variables from a `ChartConfig`-shaped map, semantic tokens only
|
|
141
|
+
(so light and dark both come out right with no second palette), and the app's
|
|
142
|
+
tooltip and legend markup. There is no `ResponsiveContainer` — the box is
|
|
143
|
+
measured with a `ResizeObserver` and falls back to a sane width when there is no
|
|
144
|
+
measurement, so a chart always has real dimensions and a component test proves
|
|
145
|
+
what a person sees. `number` and `table` stay a big number and an HTML table.
|
|
146
|
+
|
|
147
|
+
**Every number clicks through.** A bar, a slice, a table row and the big number
|
|
148
|
+
all open the records they counted, through the new `openRecords` host port, with
|
|
149
|
+
the block's own filter and the clicked group merged. Every group is also a real
|
|
150
|
+
button, so the chart works from a keyboard.
|
|
151
|
+
|
|
152
|
+
**A re-ask box.** One input at the top of the canvas — "break it down by owner" —
|
|
153
|
+
through the new `onReask` host port, which the host drives with
|
|
154
|
+
`dashboard_propose` on the server's `records` tool.
|
|
155
|
+
|
|
156
|
+
**A Dashboards tab** on `TablePage`, beside the views of the same records.
|
|
157
|
+
|
|
158
|
+
### Consumer action
|
|
159
|
+
|
|
160
|
+
- `recharts` is a PEER dependency now (`>=3.0.0`). A host already on Recharts 3
|
|
161
|
+
provides the one copy; a host that is not must add it.
|
|
162
|
+
- `DashboardCanvas` no longer takes `seed`. Dashboards come from
|
|
163
|
+
`custom.dashboards`; an agent writes one with `dashboard_propose`.
|
|
164
|
+
- `ChartBlock` takes `{ block, subject }` — one `DashboardBlockResult` out of the
|
|
165
|
+
canvas's run — instead of `{ spec }`. It opens no door of its own.
|
|
166
|
+
- `DASHBOARD_TABLE`, `dashboardDocument`, `dashboardFromRecord` and
|
|
167
|
+
`DashboardSpec` are removed.
|
|
168
|
+
- Bind `openRecords` for click-through and `onReask` for the re-ask box; unbound,
|
|
169
|
+
each says in one sentence what to bind rather than drawing a dead control.
|
|
170
|
+
- Needs `@ai-matrx/records` 0.14.0 or above.
|
|
171
|
+
## 0.20.0 — 2026-09-20
|
|
172
|
+
|
|
173
|
+
**The store's grid inherits the spreadsheet, instead of re-implementing it.**
|
|
174
|
+
|
|
175
|
+
The app's `/data` route had grown colors, rules, formats and a wide-table
|
|
176
|
+
layout, and none of it could reach the record store: that work lives in the app,
|
|
177
|
+
and this package's Grid renders through `@ai-matrx/design-system`'s data table.
|
|
178
|
+
The capabilities moved into that table (design-system 0.22.0); this release is
|
|
179
|
+
the store's half.
|
|
180
|
+
|
|
181
|
+
- **`presentation.ts`** — how a table LOOKS, as one document: `style` (color by a
|
|
182
|
+
column, live rules, manual highlights), `formats` (per-column overrides) and
|
|
183
|
+
`frozen` (the columns that stay while the rest scrolls). It lives on the SAVED
|
|
184
|
+
VIEW, because two people read one table two ways and neither reading is a fact
|
|
185
|
+
about the records — and it lives on a RECORD, not a new column, which is why
|
|
186
|
+
this needed no migration and why an agent sets it through the same
|
|
187
|
+
`record_write` it uses for everything else.
|
|
188
|
+
- **A Field's money is right with nothing configured.** `formatForField` reads
|
|
189
|
+
the Field's OWN declaration — a currency parity type and its unit, a percent, a
|
|
190
|
+
datetime's `config.kind` — so `$1,250.50` and `45%` appear on a table nobody
|
|
191
|
+
has ever styled. A view's `formats` only OVERRIDES that. A relation, a person
|
|
192
|
+
or a choice keeps its own control rather than being handed to a text formatter.
|
|
193
|
+
- **`RecordValue` and `GridCell` take a `format`.** A cell renderer beats
|
|
194
|
+
`MatrxColumnDef.format` — the table hands the cell over and steps back — so a
|
|
195
|
+
column that declared `currency` was showing a raw `1250.5` under its own
|
|
196
|
+
right-aligned header. Both now ask the primitive's formatter for the text, so
|
|
197
|
+
the read view and the table can never disagree about a number.
|
|
198
|
+
- **Color-by never does nothing.** The store keeps a list column's choices as
|
|
199
|
+
records of another Table and this grid has not read their colors, so a value's
|
|
200
|
+
color is derived from the value itself — same word, same color, always — rather
|
|
201
|
+
than leaving a setting a person just changed with nothing to show for it.
|
|
202
|
+
- **`Grid` takes `presentation`**, `ViewSwitcher` passes the open view's, and
|
|
203
|
+
`SavedViewSpec` / the `records_ui_view` Table carry it.
|
|
204
|
+
|
|
205
|
+
### Fixed — a column could not be declared at all
|
|
206
|
+
|
|
207
|
+
`addFields` wrote each Field as a plain record into the Field kernel Table, and
|
|
208
|
+
the store now refuses that out loud (FLD-13, `23514`): *"a column of a table is
|
|
209
|
+
stored as a field, and this one says record"*. Every table made through
|
|
210
|
+
`declareTable` — the create path AND import — failed at its first column. It
|
|
211
|
+
goes through `custom.field_declare` now, which is also what derives a parity
|
|
212
|
+
type, makes a choice Table for a list column, and stamps the auto Rules a url or
|
|
213
|
+
a percent carries; every column made the old way was thinner than one made
|
|
214
|
+
through the field panel.
|
|
215
|
+
|
|
216
|
+
### Consumer action
|
|
217
|
+
|
|
218
|
+
None. `presentation` is optional and its absence is today's behavior.
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
## 0.19.1
|
|
222
|
+
|
|
223
|
+
**The progress bar and the count beside it said different things.** One question
|
|
224
|
+
at a time, "1 of 4" is a POSITION, and the bar was measuring ANSWERS — so on the
|
|
225
|
+
first question the count said one and the bar sat at zero: an empty sliver in a
|
|
226
|
+
tinted track, which reads as either full or broken. Found by rendering the public
|
|
227
|
+
form, not by reading it. Typeform's bar advances as you move through the form,
|
|
228
|
+
and now so does this one. On a single page there is no position, so it keeps
|
|
229
|
+
measuring what it can: how much is answered.
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## 0.19.0
|
|
233
|
+
|
|
234
|
+
**The choice picker reads a document from any era, and writes the contract.**
|
|
235
|
+
`OptionControl` compared the cell to `option.id` and wrote `option.id`. A choice
|
|
236
|
+
value is now the option's own stable key and the read doors hand back the LABEL,
|
|
237
|
+
so a picker that compares against the id shows nothing selected. It now uses
|
|
238
|
+
`isTheChosen` from `@ai-matrx/records` — which recognises a choice by its key,
|
|
239
|
+
its label or its option id — and writes `optionKey(option)`, for both the single
|
|
240
|
+
and the multi control.
|
|
241
|
+
|
|
242
|
+
`scalarText` is unchanged and did not need to change: it resolves a value
|
|
243
|
+
through the label provider only when the value LOOKS like an id, so a label
|
|
244
|
+
falls through and renders as itself.
|
|
245
|
+
|
|
246
|
+
**Consumer action:** none beyond taking 0.19.0 with `@ai-matrx/records` 0.13.0.
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
## 0.18.0
|
|
250
|
+
|
|
251
|
+
**A person can switch off a notification an agent switched on.** "Make me an
|
|
252
|
+
intake form that tells me when a new patient arrives" creates a subscription
|
|
253
|
+
Rule, and until now nothing in any screen could see it, let alone stop it: the
|
|
254
|
+
only reader was `custom.agg_subscriptions`, which is the notifier's and is not
|
|
255
|
+
client-callable. `SubscriptionsPanel` is the owner's side — what this table is
|
|
256
|
+
telling you about, on which channel, how often, and one switch — and it opens in
|
|
257
|
+
the table's own rail beside its Forms, because a subscription watches a Table.
|
|
258
|
+
|
|
259
|
+
A row the store says you may not mute carries no switch at all rather than a
|
|
260
|
+
greyed one, and says whose it is. "Off" is the store's own word: muting is
|
|
261
|
+
honoured inside the single reader every consumer goes through, so a screen can
|
|
262
|
+
never say a subscription is off while it keeps firing.
|
|
263
|
+
|
|
264
|
+
### Consumer action
|
|
265
|
+
|
|
266
|
+
None — `TablePage` grew the rail. Needs `@ai-matrx/records` 0.12.0 or above for
|
|
267
|
+
`client.subscriptions` and `client.subscriptionMute`.
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
## 0.17.0
|
|
271
|
+
|
|
272
|
+
**There is one Form object, and it is the one with the link.** `FormBuilder` kept
|
|
273
|
+
its own form records in a package-owned `records_ui_form` Table — a second form
|
|
274
|
+
with no public link, no accept Rule and no notify Rule, sitting beside the
|
|
275
|
+
`custom.anon_form` one that `form_propose` writes and a stranger actually
|
|
276
|
+
answers. Two objects, one name, nothing reconciling them: a person tweaked one
|
|
277
|
+
form and published the other. The builder now reads `custom.forms` and writes
|
|
278
|
+
`custom.form_declare` — the same two doors the agent uses — so a tweak changes
|
|
279
|
+
the form at the link, and the screen says which link that is and whether it is
|
|
280
|
+
answering.
|
|
281
|
+
|
|
282
|
+
A save is a RE-STATEMENT of the whole form, because that is what the door is, so
|
|
283
|
+
`formDeclareArgs` hands the accept Rule, the notify Rule, the cap and the slug
|
|
284
|
+
straight back. A save that dropped `notify_rule_id` would silently stop the owner
|
|
285
|
+
being told that somebody answered — which is most of what the person asked for.
|
|
286
|
+
|
|
287
|
+
**The public form printed its own title twice**, once as the page's heading and
|
|
288
|
+
again three millimetres below it in the runner's progress row — measured in a
|
|
289
|
+
headless render, not reasoned about. The runner runs the form; the page names
|
|
290
|
+
it. A host that mounts a form has a heading of its own, and a component that
|
|
291
|
+
repeats it is a component deciding something it cannot see. `FormBuilder`'s
|
|
292
|
+
preview pane grew the one line it needed.
|
|
293
|
+
|
|
294
|
+
"New form" is back, and it starts by asking for the Table's first Field: the
|
|
295
|
+
store refuses a form that asks nothing, and it is right to — a form with no
|
|
296
|
+
questions is a link to an empty page.
|
|
297
|
+
|
|
298
|
+
### Consumer action — BREAKING, and it is three names
|
|
299
|
+
|
|
300
|
+
`FORM_TABLE`, `formDocument` and `formFromRecord` are gone, with the second store
|
|
301
|
+
they served. `SavedForm.version` is gone too (`custom.anon_form` has no record
|
|
302
|
+
version); `SavedForm` now carries `state`, `slug`, `submissionCap`,
|
|
303
|
+
`quarantineRuleId` and `notifyRuleId`. If you mapped forms yourself, the two
|
|
304
|
+
directions are `formFromSummary(summary)` and `formDeclareArgs(form)`.
|
|
305
|
+
|
|
306
|
+
## 0.16.1
|
|
307
|
+
|
|
308
|
+
**`FormSubmitOutcome` is exported.** It is the return type of
|
|
309
|
+
`FormRunnerProps.onSubmit`, so a host supplying its own submit could not type
|
|
310
|
+
its own handler: matrx-frontend's `PublicFormRunner` failed `tsc` on 0.16.0 with
|
|
311
|
+
TS2459, "declares it locally, but it is not exported". A public surface is every
|
|
312
|
+
type a consumer has to name.
|
|
313
|
+
|
|
314
|
+
### Consumer action
|
|
315
|
+
|
|
316
|
+
None beyond importing it if you implement `onSubmit`.
|
|
317
|
+
|
|
3
318
|
## 0.16.0
|
|
4
319
|
|
|
5
320
|
**The grid never draws a row before it knows what the person may do.** My own
|