@ai-matrx/records-ui 0.66.0 → 0.68.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 +65 -0
- package/dist/index.cjs +39 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +39 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.68.0
|
|
4
|
+
|
|
5
|
+
**Two things a screen was still doing behind a person's back.** (lane TAILS-6, found while
|
|
6
|
+
proving 0.66.0 on the live seat.)
|
|
7
|
+
|
|
8
|
+
* **The engineer's line was being READ ALOUD.** `sr-only` does not hide a line — it hides it
|
|
9
|
+
from people with EYES and reads it to everybody else. So the one person who could not skim
|
|
10
|
+
past *"@ai-matrx/records never reached the store from checklistTemplates: TypeError: Failed
|
|
11
|
+
to fetch"* was the blind owner of the business, who had it announced in full underneath
|
|
12
|
+
"We could not reach your data". `RefusalNotice` now marks that line `aria-hidden`; it stays
|
|
13
|
+
in the DOM with the SQLSTATE on it, and on the notice's `title`, for whoever debugs it.
|
|
14
|
+
The guard asserts on what a screen reader would SAY, not on what the DOM contains.
|
|
15
|
+
* **The Group by picker contradicted the board beside it.** `offerableFields` answers what is
|
|
16
|
+
a SENSIBLE column to group by — Choice columns — and the select listed only those. A view,
|
|
17
|
+
or a `?group=` link from a dashboard number, can name a column outside that set: the board
|
|
18
|
+
grouped by it happily and the select fell back to its blank "Choose a field". Watched on
|
|
19
|
+
Rincon Plumbing Co's Jobs board with six columns headed RPC-1000, RPC-1001 … over a picker
|
|
20
|
+
saying nothing was chosen. The chosen column is now always in the list, named, and marked
|
|
21
|
+
"an unusual column to use here".
|
|
22
|
+
|
|
23
|
+
**Consumer action:** none.
|
|
24
|
+
|
|
25
|
+
## 0.67.0
|
|
26
|
+
|
|
27
|
+
**The office can copy a waiting client's own link, from the panel where it invited them.**
|
|
28
|
+
(lane TAILS-5.)
|
|
29
|
+
|
|
30
|
+
* **`PortalsPanel` renders `CopyLink` beside a pending principal**, fed from that person's
|
|
31
|
+
`accept_path` (new in `@ai-matrx/records` 0.48.0). It is the SAME `CopyLink` this panel
|
|
32
|
+
already uses for the portal's own address — the one that shows the address to copy by hand
|
|
33
|
+
when a browser refuses the clipboard — not a second copy control.
|
|
34
|
+
* **A person with no link gets a sentence, not a missing button.** Somebody who is waiting and
|
|
35
|
+
has no `accept_path` was asked some other way (the server's magic-link route binds without an
|
|
36
|
+
invitation) or their link expired, and the panel says exactly that with what to do: invite
|
|
37
|
+
them again and a fresh one is made. Drawing nothing would read as a broken control; drawing a
|
|
38
|
+
dead button would be worse.
|
|
39
|
+
* **`PortalCardView` takes an optional `origin`** so the bare-mountable view can build a whole
|
|
40
|
+
URL, falling back to the browser's own origin. It still holds no client and no network, so
|
|
41
|
+
the blocking gate can keep mounting it against a recorded `custom.portal_card` answer.
|
|
42
|
+
|
|
43
|
+
**And the two buttons that read alike no longer do.** The Jobs toolbar's control that OPENS the
|
|
44
|
+
add-a-column panel read exactly `"Add field"`, and the panel's own SAVE button read exactly
|
|
45
|
+
`"Add field"` too — `FieldEditor` said `"Save field"` only when EDITING. The toolbar control is
|
|
46
|
+
a TOGGLE, so pressing it while the panel is open CLOSES the panel and throws away everything
|
|
47
|
+
typed into it: a person who had typed a column name, chosen a type and picked the table it
|
|
48
|
+
points at could lose all of it by pressing the words they were told to press, silently, with no
|
|
49
|
+
request ever sent. Measured on 2026-09-21 by a headless walk that lost the column twice and
|
|
50
|
+
spent a round blaming the store.
|
|
51
|
+
|
|
52
|
+
* The words now live once, in `fieldTypes.ts` beside `FIELD_TYPE_CHOICES` — the module that
|
|
53
|
+
already owns what a person READS about a column — as `ADD_FIELD_OPEN_LABEL` ("Add field"),
|
|
54
|
+
`ADD_FIELD_SAVE_LABEL` ("Create field") and `EDIT_FIELD_SAVE_LABEL` ("Save field"). Both
|
|
55
|
+
surfaces import them; neither writes the literal any more.
|
|
56
|
+
* **They are NOT in `@ai-matrx/design-system`, deliberately:** "field" and "column" are records
|
|
57
|
+
vocabulary and the design system knows nothing about either, so a labels module there would
|
|
58
|
+
be a domain word in a generic package that the next person adding a button would not find.
|
|
59
|
+
* `the-opener-and-the-committer-never-read-alike.test.tsx` is a forcing function, not a style
|
|
60
|
+
note: it asserts the strings differ, that the committer never says "add", and that neither
|
|
61
|
+
file carries the literal. Shown RED (2 of 3 failing, `expected 'Add field' not to be 'Add
|
|
62
|
+
field'`) with the collision planted, then GREEN.
|
|
63
|
+
|
|
64
|
+
**Consumer action:** none for a host that mounts `PortalsPanel`. A host that mounts
|
|
65
|
+
`PortalCardView` directly and serves the app from a known origin should pass `origin`; without
|
|
66
|
+
it the view reads `window.location.origin`, which is right in a browser and empty in SSR.
|
|
67
|
+
|
|
3
68
|
## 0.66.0
|
|
4
69
|
|
|
5
70
|
**A screen stops talking to a developer.** (lane TAILS-6.)
|
package/dist/index.cjs
CHANGED
|
@@ -868,6 +868,9 @@ function isRelationFieldType(id) {
|
|
|
868
868
|
function parityTypesWithNoExplanation() {
|
|
869
869
|
return import_records2.PARITY_FIELD_TYPES.map((row) => row.parity_type).filter((type) => !BY_ID.has(type));
|
|
870
870
|
}
|
|
871
|
+
var ADD_FIELD_OPEN_LABEL = "Add field";
|
|
872
|
+
var ADD_FIELD_SAVE_LABEL = "Create field";
|
|
873
|
+
var EDIT_FIELD_SAVE_LABEL = "Save field";
|
|
871
874
|
|
|
872
875
|
// src/parity.ts
|
|
873
876
|
var kernelId = (name) => import_records3.KERNEL_TABLES.find((t) => t.name === name)?.id ?? "";
|
|
@@ -1489,7 +1492,16 @@ function RefusalNotice({
|
|
|
1489
1492
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.AlertDescription, { className: "space-y-1 text-xs", children: [
|
|
1490
1493
|
plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: plain.sentence }),
|
|
1491
1494
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "opacity-80", children: plain.remedy }),
|
|
1492
|
-
plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1495
|
+
plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1496
|
+
"p",
|
|
1497
|
+
{
|
|
1498
|
+
className: "sr-only",
|
|
1499
|
+
"aria-hidden": "true",
|
|
1500
|
+
"data-for-engineers": "",
|
|
1501
|
+
...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
|
|
1502
|
+
children: plain.forEngineers
|
|
1503
|
+
}
|
|
1504
|
+
) : null,
|
|
1493
1505
|
actions
|
|
1494
1506
|
] })
|
|
1495
1507
|
]
|
|
@@ -5021,7 +5033,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5021
5033
|
size: "sm",
|
|
5022
5034
|
disabled: mutation.saving || missing !== null,
|
|
5023
5035
|
onClick: () => void save(),
|
|
5024
|
-
children: mutation.saving ? "Saving\u2026" : field ?
|
|
5036
|
+
children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
|
|
5025
5037
|
}
|
|
5026
5038
|
),
|
|
5027
5039
|
onCancel ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_design_system14.Button, { size: "sm", variant: "ghost", onClick: onCancel, children: "Cancel" }) : null,
|
|
@@ -7426,7 +7438,9 @@ function FieldPicker({
|
|
|
7426
7438
|
}) {
|
|
7427
7439
|
const want = LAYOUT_FIELD_KIND[layout];
|
|
7428
7440
|
const offerable = want ? offerableFields(all, want) : [];
|
|
7429
|
-
|
|
7441
|
+
const chosenField = chosen ? all.find((field) => field.key === chosen) : void 0;
|
|
7442
|
+
const unusual = chosenField && !offerable.some((field) => field.key === chosenField.key) ? chosenField : void 0;
|
|
7443
|
+
if (offerable.length === 0 && !unusual) {
|
|
7430
7444
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-xs text-muted-foreground", children: LAYOUT_NO_FIELD[layout] });
|
|
7431
7445
|
}
|
|
7432
7446
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center gap-1.5 text-xs", children: [
|
|
@@ -7441,7 +7455,11 @@ function FieldPicker({
|
|
|
7441
7455
|
onChange: (event) => onPick(event.target.value === "" ? null : event.target.value),
|
|
7442
7456
|
children: [
|
|
7443
7457
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("option", { value: "", children: "Choose a field" }),
|
|
7444
|
-
offerable.map((field) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("option", { value: field.key, children: fieldName(field) }, field.key))
|
|
7458
|
+
offerable.map((field) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("option", { value: field.key, children: fieldName(field) }, field.key)),
|
|
7459
|
+
unusual ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("option", { value: unusual.key, children: [
|
|
7460
|
+
fieldName(unusual),
|
|
7461
|
+
" \u2014 an unusual column to use here"
|
|
7462
|
+
] }, unusual.key) : null
|
|
7445
7463
|
]
|
|
7446
7464
|
}
|
|
7447
7465
|
),
|
|
@@ -9973,6 +9991,7 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9973
9991
|
{
|
|
9974
9992
|
portalId: portal.portal_id,
|
|
9975
9993
|
...tableId ? { tableId } : {},
|
|
9994
|
+
origin,
|
|
9976
9995
|
onChanged: () => void load()
|
|
9977
9996
|
}
|
|
9978
9997
|
) : null
|
|
@@ -9980,7 +9999,7 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9980
9999
|
}) })
|
|
9981
10000
|
] });
|
|
9982
10001
|
}
|
|
9983
|
-
function CopyLink({ url }) {
|
|
10002
|
+
function CopyLink({ url, what }) {
|
|
9984
10003
|
const [copied, setCopied] = (0, import_react68.useState)(false);
|
|
9985
10004
|
const [shown, setShown] = (0, import_react68.useState)(false);
|
|
9986
10005
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
@@ -10001,7 +10020,7 @@ function CopyLink({ url }) {
|
|
|
10001
10020
|
}
|
|
10002
10021
|
})();
|
|
10003
10022
|
},
|
|
10004
|
-
children: copied ? "Copied" : "Copy link"
|
|
10023
|
+
children: copied ? "Copied" : what ? `Copy ${what}` : "Copy link"
|
|
10005
10024
|
}
|
|
10006
10025
|
),
|
|
10007
10026
|
shown ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { className: "mt-1.5 w-full break-all rounded border border-dashed border-border px-2 py-1 text-xs", children: [
|
|
@@ -10013,6 +10032,7 @@ function CopyLink({ url }) {
|
|
|
10013
10032
|
function PortalDetail({
|
|
10014
10033
|
portalId,
|
|
10015
10034
|
tableId,
|
|
10035
|
+
origin,
|
|
10016
10036
|
onChanged
|
|
10017
10037
|
}) {
|
|
10018
10038
|
const client = (0, import_react69.useRecordsClient)();
|
|
@@ -10062,6 +10082,7 @@ function PortalDetail({
|
|
|
10062
10082
|
PortalCardView,
|
|
10063
10083
|
{
|
|
10064
10084
|
card,
|
|
10085
|
+
origin,
|
|
10065
10086
|
...tableId ? { tableId } : {},
|
|
10066
10087
|
onRevoke: revoke,
|
|
10067
10088
|
onPreview: preview,
|
|
@@ -10084,6 +10105,7 @@ function PortalCardView({
|
|
|
10084
10105
|
onRevoke,
|
|
10085
10106
|
onPreview,
|
|
10086
10107
|
invite,
|
|
10108
|
+
origin,
|
|
10087
10109
|
className
|
|
10088
10110
|
}) {
|
|
10089
10111
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: (0, import_design_system33.cn)("mt-2.5 flex flex-col gap-2.5 border-t border-border pt-2.5", className), children: [
|
|
@@ -10114,7 +10136,7 @@ function PortalCardView({
|
|
|
10114
10136
|
] }, exposed.table_id))
|
|
10115
10137
|
] }),
|
|
10116
10138
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Separator, {}),
|
|
10117
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(People, { card, onRevoke, onPreview }),
|
|
10139
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(People, { card, onRevoke, onPreview, origin }),
|
|
10118
10140
|
invite ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
10119
10141
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Separator, {}),
|
|
10120
10142
|
invite
|
|
@@ -10124,8 +10146,10 @@ function PortalCardView({
|
|
|
10124
10146
|
function People({
|
|
10125
10147
|
card,
|
|
10126
10148
|
onRevoke,
|
|
10127
|
-
onPreview
|
|
10149
|
+
onPreview,
|
|
10150
|
+
origin
|
|
10128
10151
|
}) {
|
|
10152
|
+
const here = origin ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
10129
10153
|
const [askingToRevoke, setAskingToRevoke] = (0, import_react68.useState)(null);
|
|
10130
10154
|
const [busy, setBusy] = (0, import_react68.useState)(false);
|
|
10131
10155
|
const [said, setSaid] = (0, import_react68.useState)(null);
|
|
@@ -10167,6 +10191,7 @@ function People({
|
|
|
10167
10191
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "mt-0.5 break-all text-muted-foreground", children: person.email }),
|
|
10168
10192
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: state.why }),
|
|
10169
10193
|
person.is_active ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "mt-1.5 flex flex-wrap gap-1.5", children: [
|
|
10194
|
+
person.accept_path ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(CopyLink, { url: `${here}${person.accept_path}`, what: "their link" }) : null,
|
|
10170
10195
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
10171
10196
|
import_design_system33.Button,
|
|
10172
10197
|
{
|
|
@@ -10178,6 +10203,11 @@ function People({
|
|
|
10178
10203
|
),
|
|
10179
10204
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRevoke(person), children: "Remove their access" })
|
|
10180
10205
|
] }) : null,
|
|
10206
|
+
person.is_active && !person.signed_in && !person.accept_path ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { className: "mt-1.5 text-muted-foreground", children: [
|
|
10207
|
+
"There is no link to copy for ",
|
|
10208
|
+
person.client || person.email,
|
|
10209
|
+
": they were asked some other way, or the link they were sent has expired. Invite them again and a fresh one is made."
|
|
10210
|
+
] }) : null,
|
|
10181
10211
|
asking ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "mt-1.5 flex flex-col gap-1.5 rounded border border-destructive/40 px-2 py-1.5", children: [
|
|
10182
10212
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-destructive", children: revokeConsequence(person, card.title) }),
|
|
10183
10213
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
@@ -14999,7 +15029,7 @@ function TablePage({
|
|
|
14999
15029
|
variant: surfaceChosen(surface, { rail: "field" }) ? "secondary" : "outline",
|
|
15000
15030
|
"aria-pressed": surfaceChosen(surface, { rail: "field" }),
|
|
15001
15031
|
onClick: () => press({ rail: "field" }),
|
|
15002
|
-
children:
|
|
15032
|
+
children: ADD_FIELD_OPEN_LABEL
|
|
15003
15033
|
}
|
|
15004
15034
|
),
|
|
15005
15035
|
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|