@object-ui/plugin-form 17.3.0 → 17.6.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 +1159 -0
- package/README.md +494 -77
- package/dist/DrawerForm.d.ts.map +1 -1
- package/dist/EmbeddableForm.d.ts.map +1 -1
- package/dist/LineItemsPanel.d.ts +33 -0
- package/dist/LineItemsPanel.d.ts.map +1 -1
- package/dist/MasterDetailForm.d.ts.map +1 -1
- package/dist/ModalForm.d.ts.map +1 -1
- package/dist/ObjectForm.d.ts +18 -2
- package/dist/ObjectForm.d.ts.map +1 -1
- package/dist/SplitForm.d.ts.map +1 -1
- package/dist/TabbedForm.d.ts.map +1 -1
- package/dist/WizardForm.d.ts +33 -0
- package/dist/WizardForm.d.ts.map +1 -1
- package/dist/autoLayout.d.ts.map +1 -1
- package/dist/deriveMasterDetail.d.ts +1 -1
- package/dist/deriveMasterDetail.d.ts.map +1 -1
- package/dist/flatFields.d.ts +21 -0
- package/dist/flatFields.d.ts.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1570 -1112
- package/dist/index.umd.cjs +1 -1
- package/dist/schemaDefaults.d.ts +142 -0
- package/dist/schemaDefaults.d.ts.map +1 -0
- package/dist/sectionFields.d.ts +18 -0
- package/dist/sectionFields.d.ts.map +1 -1
- package/dist/submitRedirect.d.ts +148 -0
- package/dist/submitRedirect.d.ts.map +1 -0
- package/dist/submitRedirectNavigation.d.ts +82 -0
- package/dist/submitRedirectNavigation.d.ts.map +1 -0
- package/dist/successBehavior.d.ts +18 -1
- package/dist/successBehavior.d.ts.map +1 -1
- package/dist/thankYouRedirectCountdown.d.ts +17 -0
- package/dist/thankYouRedirectCountdown.d.ts.map +1 -0
- package/dist/thankYouRedirectNavigation.d.ts +111 -0
- package/dist/thankYouRedirectNavigation.d.ts.map +1 -0
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1158 @@
|
|
|
1
1
|
# @object-ui/plugin-form
|
|
2
2
|
|
|
3
|
+
## 17.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8c0d52e: A form's ruled `submitBehavior.url` redirect can now be performed by the HOST, so a destination stays inside a console mounted at a sub-path (objectui#4989 defect 4).
|
|
8
|
+
|
|
9
|
+
`ObjectForm` and `WizardForm` accept a relative in-app path and used to travel to it with `window.location.assign`. A rooted path resolves against the ORIGIN there, so under a mounted host — `<BrowserRouter basename="/_console">`, which the framework CLI configures for every embedded deployment — an authored `/thanks` left the application. The destination was correct and the navigation was wrong, and a published renderer cannot fix that alone: only the host knows its mount.
|
|
10
|
+
|
|
11
|
+
**New: `HostNavigationContext` (`@object-ui/react`).** A host offers its own navigate; a renderer uses it when it is there:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { HostNavigationProvider } from '@object-ui/react';
|
|
15
|
+
import { useNavigate } from 'react-router-dom';
|
|
16
|
+
|
|
17
|
+
function Bridge({ children }) {
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
return (
|
|
20
|
+
<HostNavigationProvider value={{ navigate: (to, o) => navigate(to, { replace: o?.replace ?? false }) }}>
|
|
21
|
+
{children}
|
|
22
|
+
</HostNavigationProvider>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`@object-ui/app-shell`'s `ConsoleShell` now mounts that bridge above every console route, so the console's own routes, and its basename, are what a post-submit redirect resolves against — and the redirect becomes an SPA transition instead of a full page load.
|
|
28
|
+
|
|
29
|
+
**Nothing changes for a host that wires nothing.** With no provider the behaviour is byte-for-byte what it was: one `window.location.assign` of the resolved path after the declared `delayMs`. A host with no router has no basename, so origin-rooted resolution is already right there — the seam changes what a MOUNTED host gets and nothing else. `useHostNavigation()` outside a provider answers `{ navigate: undefined }` and never throws.
|
|
30
|
+
|
|
31
|
+
Two mechanisms were weighed and rejected by the ruling, recorded so they are not re-proposed. **Reading React Router's context when a router happens to be present** is implicit, and unavailable anyway: a React context is a module-instance object, so reading the host's means importing `react-router` into the renderer — which `@object-ui/plugin-form` declares in none of its dependency fields, whose published build externalises every bare specifier, and which two real consumers (`apps/site`, `packages/plugin-view`) do not install. **Requiring `react-router` as a peer** is the honest version of the same thing and costs the package its property of dropping into any React application.
|
|
32
|
+
|
|
33
|
+
What the host takes on by supplying a navigate: the destination becomes a client-side transition, so an in-app path with no matching route renders the host's not-found instead of a full page load. That is the host's routing table to answer for, which is why the choice is the host's. The contract verdict is unchanged either way — a destination `@objectstack/spec` refuses is refused identically with or without a seam, so an injected navigate can never launder a value the authoring door rejects.
|
|
34
|
+
|
|
35
|
+
`navigateOnSuccess` is a different declared key with its own open contract question (objectui#5034) and is deliberately untouched here.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 9b20dea: Fix: a `drawer` form with no `sections` now renders the object's declared
|
|
40
|
+
`fieldGroups` as sections, matching `ObjectForm` and `ModalForm`.
|
|
41
|
+
|
|
42
|
+
`deriveFieldGroupSections` had exactly two call sites in the repo —
|
|
43
|
+
`ObjectForm` and `ModalForm` — so the same object, with the same metadata,
|
|
44
|
+
rendered one section per declared group in the modal create dialog and one
|
|
45
|
+
ungrouped flat list in the drawer. The author who laid the groups out in the
|
|
46
|
+
object designer saw them honoured on two surfaces out of three.
|
|
47
|
+
|
|
48
|
+
`DrawerForm` now runs the same fallback the modal does: gated on "no explicit
|
|
49
|
+
`sections`, no `customFields`", over the same auto-layout-filtered field list
|
|
50
|
+
(system fields dropped, auto-generated fields dropped in create mode), with the
|
|
51
|
+
flat path's inferred column count carried onto the grouped layout. A curated
|
|
52
|
+
`sections` list from a form view still wins, and an object whose fields join no
|
|
53
|
+
declared group keeps its flat layout untouched. A derived group declaring
|
|
54
|
+
ADR-0085 `collapse` renders as a collapsible header, like an authored one.
|
|
55
|
+
- 469b604: Fix: a `drawer` form with no `sections` now honours the object's field-level
|
|
56
|
+
conditional rules (`visibleWhen` / `readonlyWhen` / `requiredWhen`) and field
|
|
57
|
+
`group`.
|
|
58
|
+
|
|
59
|
+
`ModalForm` and `DrawerForm` each carried their own copy of the "object-schema
|
|
60
|
+
field to runtime FormField" loop for the no-sections case, and the drawer's
|
|
61
|
+
copy had fallen behind: it stopped at `multiple`, so the ADR-0036 predicates
|
|
62
|
+
never reached the runtime field and `resolveFieldRuleState` had nothing to
|
|
63
|
+
resolve. A hidden field rendered anyway, a frozen field stayed editable (with
|
|
64
|
+
the server then dropping the write), and a conditionally-required field never
|
|
65
|
+
blocked the submit.
|
|
66
|
+
|
|
67
|
+
Both containers now build that list through one shared `buildFlatFields`, which
|
|
68
|
+
resolves each field through the same `fromObjectSchema` the sectioned path uses
|
|
69
|
+
— so the next field-mapping fix lands once and reaches every container.
|
|
70
|
+
- d7be3bd: `EmbeddableForm`'s thank-you redirect stops being mount-blind: an in-app destination now goes through the host's injected navigate.
|
|
71
|
+
|
|
72
|
+
The redirect ended in one unconditional `window.location.href = url`. That is
|
|
73
|
+
right for the external destination this key deliberately admits, and wrong for
|
|
74
|
+
the in-app one it equally admits: a rooted path such as `/thanks` assigned to
|
|
75
|
+
`location.href` resolves against the ORIGIN root, so under a host mounted at a
|
|
76
|
+
sub-path — the framework CLI configures one for every embedded deployment, and
|
|
77
|
+
the console runs at basename `/_console` — the submitter landed outside the
|
|
78
|
+
application, usually on the host's own 404. Nothing refused either half of that
|
|
79
|
+
authoring, so the failure was silent. This is objectui#4989 defect 4 on the key
|
|
80
|
+
that card explicitly did not cover, and it is fixed here through the seam
|
|
81
|
+
objectui#5111 landed (`HostNavigationContext`, `@object-ui/react`).
|
|
82
|
+
|
|
83
|
+
The destinations are now split by who can travel to them:
|
|
84
|
+
|
|
85
|
+
- an **app-relative** destination (`/thanks`, `thanks`, `?ok=1`, `#done`) is
|
|
86
|
+
handed to the host's navigate when a host supplied one, so a mounted host
|
|
87
|
+
places it inside its mount; with no provider the behaviour is byte-for-byte
|
|
88
|
+
what it was — a host with no router has no basename, so origin-rooted
|
|
89
|
+
resolution is already correct there;
|
|
90
|
+
- an **external** destination admitted by `allowedRedirectHosts` keeps
|
|
91
|
+
browser-level navigation **unconditionally**. This is the seam's own declared
|
|
92
|
+
input contract, not a conservatism: `HostNavigationValue.navigate` documents
|
|
93
|
+
`to` as an application-relative path, "never an absolute URL", because a host
|
|
94
|
+
navigate is a client-side router transition. Since a relative reference cannot
|
|
95
|
+
carry an authority, the seam is now structurally incapable of being handed a
|
|
96
|
+
cross-origin URL.
|
|
97
|
+
|
|
98
|
+
A same-origin **absolute** URL — the one shape those two arms do not name — also
|
|
99
|
+
keeps browser-level navigation. Routing it through the seam would mean rewriting
|
|
100
|
+
the author's full address into a path a mounted router then places at a
|
|
101
|
+
different address; an author who spelled out the whole address asked for that
|
|
102
|
+
address.
|
|
103
|
+
|
|
104
|
+
Not changed, deliberately: `isRedirectUrlSafe` and `allowedRedirectHosts` —
|
|
105
|
+
WHICH destinations are followed. That acceptance set (same-origin OR the
|
|
106
|
+
author's allowlist) is this key's own contract, a refused destination reaches
|
|
107
|
+
neither the seam nor the browser, and objectstack#7496's relative-only ruling
|
|
108
|
+
belongs to `submitBehavior.url` and is not imported onto this key. The wait's
|
|
109
|
+
ownership (objectui#5049) and the thank-you panel's copy (objectui#5073) are
|
|
110
|
+
carried over unchanged: unmounting or pressing "Submit Another Response" still
|
|
111
|
+
cancels a pending redirect, seam or no seam.
|
|
112
|
+
- a954b48: A public form's thank-you countdown ("Redirecting in {{seconds}} seconds…") now
|
|
113
|
+
actually counts down, instead of rendering a number once and leaving it frozen
|
|
114
|
+
for the whole wait.
|
|
115
|
+
|
|
116
|
+
All ten locale packs document `publicForm.redirecting`'s `{{seconds}}` as "the
|
|
117
|
+
remaining seconds", but `EmbeddableForm` computed it exactly once — at the render
|
|
118
|
+
that first shows the thank-you panel — from `pendingRedirect.delayMs`, and never
|
|
119
|
+
touched it again. On the 3 second default delay, a submitter saw a fully static
|
|
120
|
+
"Redirecting in 3 seconds…" for the entire wait (objectui#5083).
|
|
121
|
+
|
|
122
|
+
The number is now owned by a per-second `setInterval`, on the same ownership
|
|
123
|
+
model PR #5070 established for the redirect wait itself: an effect keyed on the
|
|
124
|
+
accepted destination (`pendingRedirect`), cancelled on unmount and on
|
|
125
|
+
`handleReset`'s `Submit Another Response` — the exact regression surface
|
|
126
|
+
objectui#5049 fixed for the navigation timer, restated here rather than
|
|
127
|
+
reintroduced. The interval also stops itself once it reaches 0, rather than
|
|
128
|
+
ticking indefinitely past a wait that has already ended.
|
|
129
|
+
|
|
130
|
+
Nothing about WHICH destinations are followed or refused changes
|
|
131
|
+
(`isRedirectUrlSafe` / `allowedRedirectHosts`, objectui#4989), and neither does
|
|
132
|
+
the navigation wait's own ownership (objectui#5049 / PR #5070) — this is the
|
|
133
|
+
display only.
|
|
134
|
+
- bda9b12: A public form's thank-you panel no longer promises a redirect its own guard just
|
|
135
|
+
refused, and the `texts.redirectBlocked` string can finally reach a screen.
|
|
136
|
+
|
|
137
|
+
`EmbeddableForm` decided whether to redirect from `isRedirectUrlSafe` /
|
|
138
|
+
`allowedRedirectHosts`, but the panel's copy was keyed on something else: whether
|
|
139
|
+
a `thankYouPage.redirectUrl` had been *authored* (objectui#5073). An author who
|
|
140
|
+
declared a cross-origin destination without allowlisting its host therefore got a
|
|
141
|
+
submitter who was told `Redirecting in 3 seconds…` and was then never redirected.
|
|
142
|
+
The guard did its job; the screen contradicted it. That screen is the terminal
|
|
143
|
+
state of a public form, so nothing came after to correct the impression.
|
|
144
|
+
|
|
145
|
+
On the same path, the `texts.redirectBlocked` string the refusal set was
|
|
146
|
+
unreachable in every locale. It was recorded with `setError(...)`, whose banner
|
|
147
|
+
lives in the form branch — and `setSubmitted(true)` has already run one statement
|
|
148
|
+
earlier, so the component is showing the thank-you branch, which renders no error
|
|
149
|
+
at all. That was the only assignment of the key anywhere; pressing
|
|
150
|
+
`Submit Another Response` cleared it rather than showing it.
|
|
151
|
+
|
|
152
|
+
Both now follow the verdict:
|
|
153
|
+
|
|
154
|
+
- The countdown renders on `pendingRedirect` — the destination that was actually
|
|
155
|
+
accepted — and reads its seconds from the delay captured with it, so the
|
|
156
|
+
displayed wait is the wait being served. A refused destination, and the
|
|
157
|
+
honeypot's silent fake-success (which accepts no destination either), simply
|
|
158
|
+
omit the line.
|
|
159
|
+
- A refused destination renders `texts.redirectBlocked` in the thank-you panel
|
|
160
|
+
when the author declared it — the case the key exists for, in the author's own
|
|
161
|
+
words to the public. Undeclared means silence; the author keeps the existing
|
|
162
|
+
`console.warn`, which is the channel for the person who can fix the
|
|
163
|
+
declaration.
|
|
164
|
+
|
|
165
|
+
Which destinations are refused is unchanged: `isRedirectUrlSafe` and
|
|
166
|
+
`allowedRedirectHosts` are untouched, as is the timer ownership introduced for
|
|
167
|
+
objectui#5049. Nothing was ever at risk in the data — the write succeeds before
|
|
168
|
+
any of this — the harm was a false statement on the confirmation screen and a
|
|
169
|
+
shipped, translated string no user could see.
|
|
170
|
+
- e354dd0: A public form's thank-you redirect no longer outlives the form that armed it, and
|
|
171
|
+
"Submit Another Response" now cancels it.
|
|
172
|
+
|
|
173
|
+
`EmbeddableForm` armed the `thankYouPage.redirectUrl` wait with a bare
|
|
174
|
+
`setTimeout` inside the submit handler: the handle was not stored, nothing cleared
|
|
175
|
+
it, and no part of the component owned it (objectui#5049). Two consequences, and
|
|
176
|
+
the second needs no unmount at all:
|
|
177
|
+
|
|
178
|
+
- For the whole of the delay a full-page navigation was pending that survived the
|
|
179
|
+
form being taken off screen — an embed removed by the host page, a route change,
|
|
180
|
+
a re-keyed subtree. With `redirectDelay` unset that window is the 3000 ms
|
|
181
|
+
default, so this was the normal state of every submit on this surface rather
|
|
182
|
+
than an edge authoring; the thank-you panel says as much out loud with
|
|
183
|
+
`Redirecting in {{seconds}} seconds…`.
|
|
184
|
+
- Under `allowMultiple`, `Submit Another Response` only flipped `submitted` back
|
|
185
|
+
to false while the pending navigation kept ticking. The component invited the
|
|
186
|
+
submitter into a fresh form and then, about three seconds later, threw the whole
|
|
187
|
+
page away while they were typing the next response.
|
|
188
|
+
|
|
189
|
+
The wait now lives in an effect keyed on the accepted destination, with a
|
|
190
|
+
`clearTimeout` cleanup, so unmounting cancels it; and `handleReset` drops the
|
|
191
|
+
destination, so pressing `Submit Another Response` cancels it too. The button
|
|
192
|
+
offers the submitter a fresh form, and that offer cannot be honoured alongside
|
|
193
|
+
discarding the page a moment later. This is the same move `ObjectForm` /
|
|
194
|
+
`WizardForm` (objectui#5033) and `apps/console`'s `FormPage` already made for
|
|
195
|
+
their own copies of this defect.
|
|
196
|
+
|
|
197
|
+
Nothing else changed. Which destinations are followed and which are refused is
|
|
198
|
+
still decided by the same `isRedirectUrlSafe` / `allowedRedirectHosts` guard, on
|
|
199
|
+
the same line as before — only who owns the wait changed. The delay is captured
|
|
200
|
+
together with the destination at the moment the write is accepted, so a host
|
|
201
|
+
re-rendering with a different `redirectDelay` mid-wait cannot restart the pause
|
|
202
|
+
under the submitter. The countdown copy is untouched. No data was ever at risk:
|
|
203
|
+
the write has already succeeded before the wait begins, so the harm was a
|
|
204
|
+
surprising navigation — and, on the `allowMultiple` path, the loss of what the
|
|
205
|
+
submitter had just re-typed.
|
|
206
|
+
- f68018d: A form's declared redirect delay no longer outlives the form that armed it.
|
|
207
|
+
|
|
208
|
+
`ObjectForm` and `WizardForm` consumed `submitBehavior: { kind: 'redirect' }` by
|
|
209
|
+
arming the `delayMs` wait with a bare `setTimeout` inside the submit handler. The
|
|
210
|
+
handle was not stored, nothing cleared it on unmount, and no part of the component
|
|
211
|
+
owned it — so for the whole of the declared delay there was a pending full-page
|
|
212
|
+
navigation that survived the form being taken off screen (objectui#5033). A
|
|
213
|
+
submitter who dismissed the modal or drawer variant after the confirmation, who
|
|
214
|
+
clicked an in-app link, or whose host re-keyed the subtree for its own reasons was
|
|
215
|
+
pulled away from wherever they had gone by a timer belonging to a form that no
|
|
216
|
+
longer existed. The longer the authored delay, the wider that window — and a
|
|
217
|
+
non-trivial delay is the intended authoring, since `delayMs` exists so the
|
|
218
|
+
confirmation is readable before the redirect.
|
|
219
|
+
|
|
220
|
+
The wait now lives in an effect keyed on the accepted destination, with a
|
|
221
|
+
`clearTimeout` cleanup, so unmounting cancels it. This is the same move
|
|
222
|
+
`apps/console`'s `FormPage` already made for its own copy of this defect.
|
|
223
|
+
|
|
224
|
+
`delayMs` semantics are unchanged: the pause is still a pause, an unset value is
|
|
225
|
+
still "go now" (a zero timer, exactly as the in-handler version scheduled it), and
|
|
226
|
+
which destinations are followed or refused is untouched — the contract verdict
|
|
227
|
+
that decides WHETHER to navigate is the same one, only WHEN it happens is now
|
|
228
|
+
owned by the component. The delay is captured together with the destination at the
|
|
229
|
+
moment the write is accepted, so a host re-rendering with a different `delayMs`
|
|
230
|
+
mid-wait cannot restart the pause under the submitter. Nothing was ever at risk of
|
|
231
|
+
being lost: the write has already succeeded before the wait begins, so the harm
|
|
232
|
+
was a surprising navigation, not a corrupted record.
|
|
233
|
+
- 375efb4: Publish the authoring surfaces of the four GA `object-*` blocks
|
|
234
|
+
|
|
235
|
+
`object-form`, `object-grid`, `object-master-detail-form` and `object-metric`
|
|
236
|
+
each honoured far more keys than they declared as registry `inputs`. An author —
|
|
237
|
+
very often an AI author — who wrote one of the undeclared keys got an
|
|
238
|
+
`unknown-prop` report from `sdui-parser` on a key that works, while the designer
|
|
239
|
+
panel and the generated `sdui-intrinsics.d.ts` denied it existed.
|
|
240
|
+
|
|
241
|
+
68 keys are now declared with descriptions written to teach correct authoring:
|
|
242
|
+
`object-form` +20 (record binding, button labels, post-submit behaviour, mobile
|
|
243
|
+
overrides), `object-grid` +21 (sorting, pagination, grouping, selection, row and
|
|
244
|
+
bulk actions, navigation, export), `object-master-detail-form` +10, and
|
|
245
|
+
`object-metric` +14 (formatting, comparison, drill-down). No renderer behaviour
|
|
246
|
+
changes — this documents what already shipped, so the manifest, the generated
|
|
247
|
+
`.d.ts`, the designer panel and the renderers finally agree.
|
|
248
|
+
|
|
249
|
+
Ten of `object-grid`'s spec-declared keys are deliberately NOT published:
|
|
250
|
+
its own `@deprecated` legacy spellings (`fields`, `staticData`, `selectable`,
|
|
251
|
+
`pageSize`, `showSearch`, `showPagination`, `defaultSort`, `defaultFilters`,
|
|
252
|
+
`resizableColumns`, `title`). The renderer keeps reading them so existing
|
|
253
|
+
documents render, but recommending a deprecated alias as new authoring surface
|
|
254
|
+
would harden it into a second dialect. Each canonical replacement — `columns`,
|
|
255
|
+
`data`, `selection`, `pagination`, `searchableFields`, `sort`, `filter`,
|
|
256
|
+
`resizable`, `label` — is declared, and each carries a description naming the
|
|
257
|
+
legacy spelling it supersedes.
|
|
258
|
+
- 800f455: fix(fields): grid columns are keyed by the declared `name`, so spec-compliant grid metadata renders populated cells
|
|
259
|
+
|
|
260
|
+
`GridField` declared its own column interface keyed by `field` and read
|
|
261
|
+
`c.field` at every site (`key=`, `row[…]`, the blank row, cell writes, the
|
|
262
|
+
column chooser, the running-total lookup), while the published
|
|
263
|
+
`GridColumnDefinition` in `@object-ui/types` — and the grid documentation, and
|
|
264
|
+
the `fields-grid` catalog examples — declare the key as `name`. Metadata
|
|
265
|
+
authored against the published type therefore rendered a grid with the correct
|
|
266
|
+
row count and every cell empty, plus a React "unique key" warning per column.
|
|
267
|
+
|
|
268
|
+
The renderer now reads the declared `name`, and the master-detail derivation
|
|
269
|
+
(`deriveColumns` / `hydrateColumns` / `pickAmountField`) produces and consumes
|
|
270
|
+
the same key. There is deliberately **no** `col.field ?? col.name` alias: one
|
|
271
|
+
spelling at the producer (AGENTS.md #0.1).
|
|
272
|
+
|
|
273
|
+
**Breaking for `field`-keyed columns.** Grid / line-item / master-detail
|
|
274
|
+
subform columns spelled `{ field: 'amount' }` must be re-spelled
|
|
275
|
+
`{ name: 'amount' }`. This affects author-supplied `columns` on the `grid`
|
|
276
|
+
field, `record:line_items`, `object-master-detail-form` details and a
|
|
277
|
+
relationship field's `inlineColumns`. Auto-derived columns (no explicit
|
|
278
|
+
`columns` block) need no change. List-view and `object-grid` columns are a
|
|
279
|
+
different contract (`ListColumn`) and keep their own `field` key.
|
|
280
|
+
- 3b03704: `mobile.fullscreenLongText` now reaches fields the spec spells `richtext` (objectui#4831).
|
|
281
|
+
|
|
282
|
+
`ObjectForm` is the one and only producer of the `mobile_fullscreen` flag: when a form
|
|
283
|
+
sets `mobile: { fullscreenLongText: true }` it stamps that flag onto the metadata of
|
|
284
|
+
every long-text field, and the widget renders an expand affordance plus a full-height
|
|
285
|
+
editing dialog from it. The list of types it stamped was four hand-written literals —
|
|
286
|
+
`textarea`, `field:textarea`, `field:markdown`, `field:html` — and `field:richtext` was
|
|
287
|
+
not among them.
|
|
288
|
+
|
|
289
|
+
`richtext` is the name `@objectstack/spec`'s `FieldType` gives this type; `markdown` and
|
|
290
|
+
`html` are the other two, and all three are registry keys on ONE widget, `RichTextField`,
|
|
291
|
+
which has read the flag since objectui#3301. So the consumer side was complete and two of
|
|
292
|
+
the widget's three keys were stamped: a field authored exactly as the spec prescribes
|
|
293
|
+
(`type: richtext`) rendered the rich-text editor with no expand button, on a form whose
|
|
294
|
+
`mobile` documentation promises "textarea/rich-text get an expand button". `markdown` and
|
|
295
|
+
`html` beside it worked. This is the same hole objectui#4250 found in this package's
|
|
296
|
+
`WIDE_FIELD_TYPES`, which is why the twin set already lists `richtext` and this one did
|
|
297
|
+
not.
|
|
298
|
+
|
|
299
|
+
Adding the missing key is the whole change; no other type's behaviour moves, and a form
|
|
300
|
+
that has not opted in still stamps nothing.
|
|
301
|
+
- 958d757: The plugin-form documentation-site page now teaches the `validation` shape the
|
|
302
|
+
form renderer actually reads, so a copied example validates instead of only
|
|
303
|
+
looking as though it does.
|
|
304
|
+
|
|
305
|
+
`content/docs/plugins/plugin-form.mdx` carried the same two defects
|
|
306
|
+
`packages/plugin-form/README.md` did before it was rewritten (objectui#5075 /
|
|
307
|
+
objectui#5118): the README half was fixed and the documentation-site mirror was
|
|
308
|
+
not touched.
|
|
309
|
+
|
|
310
|
+
`### Form Field` redeclared a local `interface FormField` whose `validation` was
|
|
311
|
+
`ValidationRule[]`. No `ValidationRule` type exists in this repository under any
|
|
312
|
+
spelling, and `validation` is not an array — it is `FieldValidationRules`
|
|
313
|
+
(`packages/types/src/form.ts`), an object keyed by rule name. The block also
|
|
314
|
+
listed `defaultValue` and `className`, neither of which is a declared member of
|
|
315
|
+
`FormField`, and marked `type` and `label` required when `name` is the only
|
|
316
|
+
required key of the 23. The section no longer declares a local interface at all
|
|
317
|
+
— a hand-written `interface` in a documentation snippet compiles nowhere, which
|
|
318
|
+
is how it drifted this far — and references the declared keys instead, each one
|
|
319
|
+
measured against the renderer's read points.
|
|
320
|
+
|
|
321
|
+
`### Form with Validation` authored the array to match, and that spelling fails
|
|
322
|
+
silently rather than loudly. The only reader of the key spreads it into the rule
|
|
323
|
+
object handed to react-hook-form (`const rules: any = { ...validation }`,
|
|
324
|
+
`packages/components/src/renderers/form/form.tsx:1652`); spreading an array
|
|
325
|
+
produces numeric keys, react-hook-form recognises none of them, and every rule
|
|
326
|
+
is dropped without an error. Measured against the real renderer: the old snippet
|
|
327
|
+
submits a two-character username under `minLength: 3` with no message shown,
|
|
328
|
+
while the rewritten one blocks it. The example is now annotated `FormSchema`, so
|
|
329
|
+
the array spelling is a compile error (TS2559) rather than a runtime surprise,
|
|
330
|
+
and a JSON variant is given alongside it for metadata authoring.
|
|
331
|
+
|
|
332
|
+
Three facts a reader could previously only discover by experiment are now
|
|
333
|
+
stated: `validation.required` supplies the required *message* while `required` /
|
|
334
|
+
`requiredWhen` on the field decide whether it is required; there is no `email`
|
|
335
|
+
rule name, an email check is a `pattern`; and a hand-authored `pattern` has to
|
|
336
|
+
carry a RegExp, because react-hook-form applies a pattern only when its value is
|
|
337
|
+
`instanceof RegExp` — it is the object-metadata path (`buildValidationRules`)
|
|
338
|
+
that compiles a declared string into one.
|
|
339
|
+
|
|
340
|
+
The two `DOC_TYPE_EXEMPTIONS` entries this page held in
|
|
341
|
+
`scripts/check-doc-component-types.mjs` are deleted with it. They exempted
|
|
342
|
+
`minLength` / `maxLength` as "ValidationRule discriminants under a field's
|
|
343
|
+
`validation[]`" — a reason whose every clause was the fiction being removed —
|
|
344
|
+
and without them the gate now fails if the array spelling returns.
|
|
345
|
+
- bfb64ee: `plugin-form` README: the "Integration with Data Sources" section now teaches the adapter's real path instead of two keys no form renderer reads.
|
|
346
|
+
|
|
347
|
+
The section taught backend wiring as two keys on a form schema — `dataSource`
|
|
348
|
+
(the adapter itself) and `resource: 'users'` — on an un-annotated
|
|
349
|
+
`const schema = { … }`. Neither key is read anywhere on either form route:
|
|
350
|
+
|
|
351
|
+
- **`dataSource`** is *discarded* by the basic form. The renderer reads its
|
|
352
|
+
adapter off `SchemaRendererContext`
|
|
353
|
+
(`packages/components/src/renderers/form/form.tsx:1004`) and passes it down per
|
|
354
|
+
field (`:2061`); a same-named key arriving on the schema or props is dropped by
|
|
355
|
+
the discard destructures at `form.tsx:304` and `:2168`, so it reaches neither a
|
|
356
|
+
widget nor the DOM.
|
|
357
|
+
- **`resource`** is declared on neither `FormSchema` nor `ObjectFormSchema`. The
|
|
358
|
+
key exists in the protocol, but on `CRUDSchema` (`packages/types/src/crud.ts`,
|
|
359
|
+
`type: 'crud'`); no form renderer reads it under any spelling.
|
|
360
|
+
|
|
361
|
+
Both survived compilation because `FormSchema` and `ObjectFormSchema` extend
|
|
362
|
+
`BaseSchema`, which declares `[key: string]: any` — so an invented key is never a
|
|
363
|
+
type error, merely never read. A reader who copied the block got a form that did
|
|
364
|
+
not connect to a backend, with nothing reported: what appeared to work was the
|
|
365
|
+
hand-written `onSubmit` closure, which genuinely runs (the renderer awaits it at
|
|
366
|
+
`form.tsx:1428`) using the adapter its *closure* captured, entirely independently
|
|
367
|
+
of the two keys beside it.
|
|
368
|
+
|
|
369
|
+
The section is rewritten around the real mechanism: the adapter is injected once
|
|
370
|
+
by `SchemaRendererProvider` and travels on context, the metadata route uses
|
|
371
|
+
`object-form` with its required `objectName` + `mode`, and the TypeScript route
|
|
372
|
+
is a bare `form` whose `onSubmit` owns persistence. Both examples now carry real
|
|
373
|
+
type annotations (`ObjectFormSchema` / `FormSchema`) in line with the rest of the
|
|
374
|
+
file — an un-annotated object literal type-checks whatever is written in it. A
|
|
375
|
+
closing note records the one thing a top-level `dataSource` *does* mean on a
|
|
376
|
+
schema node: the spec's element binding (`{ object }`, objectstack#6953), which
|
|
377
|
+
explicitly rejects a live adapter (`element-data-source.ts:131` refuses any value
|
|
378
|
+
carrying a `find` method).
|
|
379
|
+
|
|
380
|
+
Documentation only — no source, type or behavior change. This also removes a
|
|
381
|
+
self-contradiction inside the same README, whose "Registering a component under
|
|
382
|
+
your own key" section already stated the rule correctly ("never a `dataSource` —
|
|
383
|
+
that travels on `SchemaRendererContext`").
|
|
384
|
+
- e09f9e8: Docs only: `packages/plugin-form/README.md`'s Schema API and Examples now spell
|
|
385
|
+
the keys the form renderers actually read (objectui#5075). Three connected
|
|
386
|
+
drifts, judged against the build product's `dist/index.d.ts` under `strict`:
|
|
387
|
+
|
|
388
|
+
- **`validation` was written as an ARRAY** of `{ type, value, message }` entries
|
|
389
|
+
in three places. The real key is `FormField.validation?: FieldValidationRules`
|
|
390
|
+
— an OBJECT keyed by rule name (`required`, `minLength`, `maxLength`, `min`,
|
|
391
|
+
`max`, `pattern`, `validate`). The array form is worse than a type error,
|
|
392
|
+
because its runtime failure is SILENT: the only reader spreads the value into
|
|
393
|
+
the rule object handed to react-hook-form (`const rules: any = { ...validation }`,
|
|
394
|
+
`packages/components/src/renderers/form/form.tsx:1652`), and spreading an array
|
|
395
|
+
into an object literal yields numeric keys (`{ '0': …, '1': … }`).
|
|
396
|
+
react-hook-form's field validator reads exactly `required`, `maxLength`,
|
|
397
|
+
`minLength`, `min`, `max`, `pattern`, `validate`, `valueAsNumber` off its
|
|
398
|
+
descriptor, so every documented rule was dropped with nothing thrown — a form
|
|
399
|
+
copied from this README looked validated while validating nothing. The rewrite
|
|
400
|
+
also records two facts a reader could not have guessed: `validation.required`
|
|
401
|
+
supplies the required MESSAGE only (presence is decided by the field's own
|
|
402
|
+
`required` / `requiredWhen`), and a hand-authored `pattern.value` must be a
|
|
403
|
+
RegExp, since react-hook-form only applies a pattern whose value
|
|
404
|
+
`instanceof RegExp` and it is the object-metadata path (`buildValidationRules`)
|
|
405
|
+
that compiles a declared string into one.
|
|
406
|
+
|
|
407
|
+
- **`type: 'multi-step-form'` is registered nowhere**, and `steps` is not a key
|
|
408
|
+
on any form schema — so the whole "Multi-Step Form" example rendered the
|
|
409
|
+
unknown-component placeholder, with the fields inside `steps` never read. The
|
|
410
|
+
example is replaced by the two real entry points: an `object-form` with
|
|
411
|
+
`formType: 'wizard'`, whose steps are its `sections` (this is what
|
|
412
|
+
`ObjectForm` routes to `WizardForm`), and the exported `WizardForm` itself
|
|
413
|
+
with inline section fields and no data source — the shape closest to what the
|
|
414
|
+
old snippet was reaching for. No new schema type was registered to make the
|
|
415
|
+
old spelling true.
|
|
416
|
+
|
|
417
|
+
- **The `FormField` reference block declared a local `interface FormField`**,
|
|
418
|
+
which type-checks whatever it says because it is unrelated to the real type.
|
|
419
|
+
Five of its rows were wrong (`type` and `label` are OPTIONAL; `validation` is
|
|
420
|
+
the object above; `defaultValue` and `className` are not declared keys — the
|
|
421
|
+
form-level `defaultValues` and `span` / `colSpan` / `fieldContainerClass` are),
|
|
422
|
+
it named a `ValidationRule` type that exists nowhere in the repo, and it listed
|
|
423
|
+
7 of the real 23 keys. The block is now a key table over the real declaration,
|
|
424
|
+
with `FormSchema`'s own keys beside it, and both examples are annotated with
|
|
425
|
+
their real types — the annotation is the point: `FormField` and `BaseSchema`
|
|
426
|
+
both declare `[key: string]: any`, so an un-annotated `const schema = { … }`
|
|
427
|
+
accepts any invented key and a nonexistent key is never a compile error.
|
|
428
|
+
|
|
429
|
+
No renderer behaviour changes, and no capability, export or type was added to
|
|
430
|
+
make an example true.
|
|
431
|
+
- 03e5f97: `packages/plugin-form/README.md`: three assertions about this package's export
|
|
432
|
+
surface were false, and the export names are now taken from the built
|
|
433
|
+
`dist/index.d.ts` (TS compiler API `checker.getExportsOfModule`) with every
|
|
434
|
+
TypeScript block compiled against those same declarations under `strict`.
|
|
435
|
+
|
|
436
|
+
- **`formComponents`** — fiction, and not a name that could be corrected: there
|
|
437
|
+
is no aggregate component map on the surface at all, so the "Manual
|
|
438
|
+
Registration" section described a mechanism that does not exist. Copying it got
|
|
439
|
+
`undefined` and threw on `Object.entries(undefined)`. It is replaced by what
|
|
440
|
+
actually happens: registration is a side effect of importing the entry, whose
|
|
441
|
+
six `ComponentRegistry.register(...)` calls claim
|
|
442
|
+
`plugin-form:object-form`, `view:form`, `plugin-form:embeddable-form`,
|
|
443
|
+
`plugin-form:form-analytics`, `plugin-form:object-master-detail-form` and
|
|
444
|
+
`record:line_items` — the two `skipFallback: true` calls being why bare `form`
|
|
445
|
+
and bare `line_items` are *not* taken over. The section also lists the real
|
|
446
|
+
export surface, and shows the thing the old snippet was reaching for: putting
|
|
447
|
+
an exported component on a schema type of your own, with the caveat that the
|
|
448
|
+
package's own registered renderers are internal wrappers that first resolve
|
|
449
|
+
`dataSource` from `SchemaRendererContext`.
|
|
450
|
+
- **`FormSchema` / `FormField`** — real types imported from the wrong package.
|
|
451
|
+
Both are protocol types declared in `@object-ui/types` (`src/form.ts`); this
|
|
452
|
+
package imports them and does not re-export them, so the documented import was
|
|
453
|
+
a `TS2305` pair. Only the import path changed — no re-export was added to make
|
|
454
|
+
the old path true, since widening a package's public surface is a contract
|
|
455
|
+
change and not a documentation fix. The section now also points at the form
|
|
456
|
+
types that *are* on this entry (`TabbedFormSchema`, `WizardFormSchema`,
|
|
457
|
+
`ModalFormSchema`, …).
|
|
458
|
+
- **`isRuntimeDefault` "(re-exported here)"** — the create-defaults section
|
|
459
|
+
claimed the predicate is re-exported by this package. It is re-exported by
|
|
460
|
+
`src/schemaDefaults.ts` for internal use only, never from the entry, and the
|
|
461
|
+
package publishes just the `"."` export — so `import { isRuntimeDefault } from
|
|
462
|
+
'@object-ui/plugin-form'` is another `TS2305`. The parenthetical now says where
|
|
463
|
+
the re-export actually lives.
|
|
464
|
+
|
|
465
|
+
No code, types or runtime behaviour change — the diff is one README plus this
|
|
466
|
+
changeset. It declares a patch because `README.md` is in the package's published
|
|
467
|
+
`files`, so the correction reaches npm with the next release.
|
|
468
|
+
- ae804ec: `ObjectForm` and `WizardForm` now consume a declared `submitBehavior: { kind: 'redirect' }` the way objectstack#7496 ruled it (objectui#4989): the destination is a **relative** path, `{{record.field_name}}` tokens are substituted from the record the submit just wrote and URL-escaped as the redirect is built, and a destination outside the contract is **refused on screen** instead of being dropped in silence.
|
|
469
|
+
|
|
470
|
+
Both call sites previously read the value through `isSameOriginUrl` — resolve against `window.location.href`, compare origins — and navigated when that answered yes. That is not an open redirect (a cross-origin destination never reached the navigation) but it diverged from the ruled contract three ways, all fixed here:
|
|
471
|
+
|
|
472
|
+
- **An out-of-contract destination was dropped in silence.** When the guard answered no the `if` simply did not fire: no toast, no error, no confirmation. The write had already succeeded, so the submitter was left facing a still-filled form with no feedback about what happened — and the obvious next move, submitting again, wrote a second record. A refusal now shows the spec's own author-facing prescription in an alert beside the confirmation that the record WAS written, toasts it, and replaces the filled form so there is nothing left to resubmit. Silence is the one outcome the ruling's consumer half rules out.
|
|
473
|
+
- **A same-origin ABSOLUTE url was followed**, where the contract is relative-only — so this renderer accepted a spelling the authoring door refuses, which is how a rejected spelling stays alive in a corpus. The verdict is no longer restated here at all: `resolveSubmitRedirect` asks `@objectstack/spec`'s own `FormViewSchema` at the moment of use, so an absolute URL, a protocol-relative `//host`, a backslash, a whitespace or control-character smuggle, a malformed token or a document-relative path is refused with the spec's own wording — and a later widening of the ruling is followed by the version pin rather than by an edit here.
|
|
474
|
+
- **`{{record.field_name}}` tokens were never substituted**, so an authored `/thanks?ref={{record.id}}` navigated with the literal braces in the query. Substitution now happens where the ruling assigns it — when the redirect is built — from the values as submitted with whatever the DataSource answered layered on top, and every interpolated value goes through `encodeURIComponent`, so a token is a value in the path and never a way to add path structure.
|
|
475
|
+
|
|
476
|
+
`delayMs` semantics are unchanged. `navigateOnSuccess` is a different declared key with its own dialect and its own open contract question, and is deliberately untouched; `isSameOriginUrl` survives because that key still needs it.
|
|
477
|
+
|
|
478
|
+
**Escalated rather than guessed at, and since ruled:** this change left a ruled in-app path handed to a browser-level navigation, which resolves it against the origin root, so under a host mounted at a sub-path the destination still left the app (objectui#4989 defect 4). Applying the mount means learning it, and every mechanism available to a published renderer package changes its contract. The maintainer ruled the mechanism on 2026-08-17 — an optional injected navigation seam — and it ships in the same release; see the `plugin-form-injected-navigation-4989` changeset for what a host now supplies and what a host that supplies nothing still gets.
|
|
479
|
+
- d971e51: A create form no longer deadlocks on a `requiredWhen` field that also declares a runtime `defaultValue`.
|
|
480
|
+
|
|
481
|
+
`#4069` ruled that in **create** mode a field whose `defaultValue` is a runtime
|
|
482
|
+
instruction the server resolves per insert (`NOW()` / `current_user`, or a CEL
|
|
483
|
+
Expression envelope) is producer-owned: the control is deliberately left empty
|
|
484
|
+
and the key is omitted from the payload, because `ObjectQL.applyFieldDefaults`
|
|
485
|
+
resolves the declaration only for a field that arrives absent or null. That was
|
|
486
|
+
implemented on the STATIC `required` flag.
|
|
487
|
+
|
|
488
|
+
The conditional spelling was not covered. `requiredWhen` is resolved one layer
|
|
489
|
+
downstream, in the form renderer, against the live record — so a predicate
|
|
490
|
+
resolving TRUE on a create form put the requirement straight back: the control
|
|
491
|
+
was still empty by design, the submit was refused, and the user had nothing
|
|
492
|
+
sensible to type.
|
|
493
|
+
|
|
494
|
+
Both spellings now behave identically on a producer-owned field. A
|
|
495
|
+
`requiredWhen` predicate is a claim about the value at rest in a given state,
|
|
496
|
+
and `NOW()` / `current_user` resolve at insert regardless of state, so the
|
|
497
|
+
producer's guarantee covers the conditional claim by the same argument that
|
|
498
|
+
covers the unconditional one. An author who really means "the user must supply
|
|
499
|
+
this in this state" has a natural spelling for it: do not declare the default.
|
|
500
|
+
|
|
501
|
+
The suppression lands in the single evaluator both layers read,
|
|
502
|
+
`resolveFieldRuleState` — the same verdict that draws the required marker and
|
|
503
|
+
the one the submit-time check consults — so a field can never lose its asterisk
|
|
504
|
+
while still refusing the write. The classifier that answers "is this value the
|
|
505
|
+
producer's to supply" moved down to `@object-ui/core`
|
|
506
|
+
(`isRuntimeDefault` / `isServerOwnedValue`, re-exported from
|
|
507
|
+
`@object-ui/plugin-form`) so the renderer, the wizard's cross-step gate and the
|
|
508
|
+
create-form field builders all read one implementation rather than three.
|
|
509
|
+
|
|
510
|
+
**Edit mode is unchanged.** Defaults do not re-apply to an existing record, so
|
|
511
|
+
on a persisted row the token was already resolved at insert and blanking the
|
|
512
|
+
column is a real removal: `requiredWhen` enforces there exactly as authored.
|
|
513
|
+
Fields with no declared default, and fields whose default is a static literal
|
|
514
|
+
(which IS seeded into the control), are also unaffected in both modes.
|
|
515
|
+
- 2165d88: Rename four component-props types off the names `@objectstack/spec` starts owning in
|
|
516
|
+
17.0.0, keeping the old spellings as deprecated aliases. No behaviour changes and no
|
|
517
|
+
importer breaks.
|
|
518
|
+
|
|
519
|
+
`@objectstack/spec/ui` exports `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGridProps`
|
|
520
|
+
and `ObjectKanbanProps` from 17.0.0, where each is the AUTHORED props document of the
|
|
521
|
+
matching element — a serialisable authoring surface (`z.input< typeof
|
|
522
|
+
ObjectGridPropsSchema >`). The same-named interfaces here are the RENDERERS' props: a live
|
|
523
|
+
`dataSource`, records pre-fetched by a parent, and the host callbacks. Two different things
|
|
524
|
+
under one word, so the local ones are renamed rather than derived, following the split this
|
|
525
|
+
repo already made for `PageHeaderProps` -> `PageHeaderComponentProps` and the
|
|
526
|
+
`Record*ComponentProps` family in `@object-ui/types`:
|
|
527
|
+
|
|
528
|
+
| package | new name | old name |
|
|
529
|
+
|---|---|---|
|
|
530
|
+
| `@object-ui/plugin-calendar` | `ObjectCalendarComponentProps` | `ObjectCalendarProps` |
|
|
531
|
+
| `@object-ui/plugin-form` | `ObjectFormComponentProps` | `ObjectFormProps` |
|
|
532
|
+
| `@object-ui/plugin-grid` | `ObjectGridComponentProps` | `ObjectGridProps` |
|
|
533
|
+
| `@object-ui/plugin-kanban` | `ObjectKanbanComponentProps` | `ObjectKanbanProps` |
|
|
534
|
+
|
|
535
|
+
Every old name is still exported from its package barrel as a `@deprecated` alias denoting
|
|
536
|
+
the SAME type, pinned per package by `spec-symbol-4650.test.ts`, so existing imports keep
|
|
537
|
+
compiling. New code should use the `ComponentProps` spelling.
|
|
538
|
+
|
|
539
|
+
`@object-ui/app-shell` carries no API change: its `SECRET_MASK` — the ADR-0100 credential
|
|
540
|
+
read mask, which 17.0.0 moves into `@objectstack/spec/data` — is renamed to
|
|
541
|
+
`OBJECTUI_SECRET_MASK` at its declaration in `views/metadata-admin/widgets.tsx`. That
|
|
542
|
+
constant is package-internal and is not re-exported from the barrel, so nothing published
|
|
543
|
+
changes; the rename exists so the local copy cannot be read as the spec's own definition
|
|
544
|
+
while this repo is still pinned below the release that exports it.
|
|
545
|
+
- Updated dependencies [88085e3]
|
|
546
|
+
- Updated dependencies [69251bf]
|
|
547
|
+
- Updated dependencies [57e668f]
|
|
548
|
+
- Updated dependencies [516663d]
|
|
549
|
+
- Updated dependencies [41ac1b7]
|
|
550
|
+
- Updated dependencies [1eaf0a1]
|
|
551
|
+
- Updated dependencies [a09bc33]
|
|
552
|
+
- Updated dependencies [460c4d0]
|
|
553
|
+
- Updated dependencies [0ae27f7]
|
|
554
|
+
- Updated dependencies [2533ec5]
|
|
555
|
+
- Updated dependencies [78c0f9a]
|
|
556
|
+
- Updated dependencies [bbe8b86]
|
|
557
|
+
- Updated dependencies [8477be5]
|
|
558
|
+
- Updated dependencies [279fb13]
|
|
559
|
+
- Updated dependencies [2e82ab2]
|
|
560
|
+
- Updated dependencies [ad07b65]
|
|
561
|
+
- Updated dependencies [41f498b]
|
|
562
|
+
- Updated dependencies [ef0d150]
|
|
563
|
+
- Updated dependencies [f34226e]
|
|
564
|
+
- Updated dependencies [564b605]
|
|
565
|
+
- Updated dependencies [e1d4251]
|
|
566
|
+
- Updated dependencies [40d3a33]
|
|
567
|
+
- Updated dependencies [8b9dc62]
|
|
568
|
+
- Updated dependencies [1184192]
|
|
569
|
+
- Updated dependencies [a2a9747]
|
|
570
|
+
- Updated dependencies [65e88e6]
|
|
571
|
+
- Updated dependencies [a1609a6]
|
|
572
|
+
- Updated dependencies [53f23bc]
|
|
573
|
+
- Updated dependencies [c4533dc]
|
|
574
|
+
- Updated dependencies [be60815]
|
|
575
|
+
- Updated dependencies [37f6844]
|
|
576
|
+
- Updated dependencies [93de4f6]
|
|
577
|
+
- Updated dependencies [2b50261]
|
|
578
|
+
- Updated dependencies [384f30d]
|
|
579
|
+
- Updated dependencies [ac600e5]
|
|
580
|
+
- Updated dependencies [97fba31]
|
|
581
|
+
- Updated dependencies [232f61a]
|
|
582
|
+
- Updated dependencies [d374caf]
|
|
583
|
+
- Updated dependencies [5673576]
|
|
584
|
+
- Updated dependencies [c1ef923]
|
|
585
|
+
- Updated dependencies [911ceaa]
|
|
586
|
+
- Updated dependencies [98eab36]
|
|
587
|
+
- Updated dependencies [af5e292]
|
|
588
|
+
- Updated dependencies [3fbbea1]
|
|
589
|
+
- Updated dependencies [0bffb18]
|
|
590
|
+
- Updated dependencies [800f455]
|
|
591
|
+
- Updated dependencies [5458414]
|
|
592
|
+
- Updated dependencies [3241559]
|
|
593
|
+
- Updated dependencies [7f96b10]
|
|
594
|
+
- Updated dependencies [167ec42]
|
|
595
|
+
- Updated dependencies [616a2a5]
|
|
596
|
+
- Updated dependencies [6c68b13]
|
|
597
|
+
- Updated dependencies [0046d8f]
|
|
598
|
+
- Updated dependencies [f1d4748]
|
|
599
|
+
- Updated dependencies [bea374e]
|
|
600
|
+
- Updated dependencies [b1119ec]
|
|
601
|
+
- Updated dependencies [5607092]
|
|
602
|
+
- Updated dependencies [9f23d2b]
|
|
603
|
+
- Updated dependencies [578e025]
|
|
604
|
+
- Updated dependencies [af025ee]
|
|
605
|
+
- Updated dependencies [d109a4d]
|
|
606
|
+
- Updated dependencies [598c89a]
|
|
607
|
+
- Updated dependencies [4a0bd17]
|
|
608
|
+
- Updated dependencies [b8b9af4]
|
|
609
|
+
- Updated dependencies [d8b9259]
|
|
610
|
+
- Updated dependencies [31676be]
|
|
611
|
+
- Updated dependencies [8c0d52e]
|
|
612
|
+
- Updated dependencies [aff10e2]
|
|
613
|
+
- Updated dependencies [70a774b]
|
|
614
|
+
- Updated dependencies [9ce096f]
|
|
615
|
+
- Updated dependencies [e05db88]
|
|
616
|
+
- Updated dependencies [7458a41]
|
|
617
|
+
- Updated dependencies [ad13d63]
|
|
618
|
+
- Updated dependencies [5ffcc14]
|
|
619
|
+
- Updated dependencies [d971e51]
|
|
620
|
+
- Updated dependencies [97abb24]
|
|
621
|
+
- Updated dependencies [deb157a]
|
|
622
|
+
- Updated dependencies [9c60144]
|
|
623
|
+
- Updated dependencies [e7747f1]
|
|
624
|
+
- Updated dependencies [d2ce342]
|
|
625
|
+
- Updated dependencies [9695da7]
|
|
626
|
+
- Updated dependencies [ac2f332]
|
|
627
|
+
- Updated dependencies [a777058]
|
|
628
|
+
- Updated dependencies [75444e3]
|
|
629
|
+
- Updated dependencies [58b8346]
|
|
630
|
+
- Updated dependencies [2d0bd16]
|
|
631
|
+
- Updated dependencies [a9e17b4]
|
|
632
|
+
- Updated dependencies [b8ce7dc]
|
|
633
|
+
- Updated dependencies [dad51e5]
|
|
634
|
+
- Updated dependencies [1c9c342]
|
|
635
|
+
- Updated dependencies [787c738]
|
|
636
|
+
- Updated dependencies [8396656]
|
|
637
|
+
- Updated dependencies [dbbd38a]
|
|
638
|
+
- Updated dependencies [61556dc]
|
|
639
|
+
- Updated dependencies [8871c14]
|
|
640
|
+
- Updated dependencies [93fe362]
|
|
641
|
+
- Updated dependencies [dfc6975]
|
|
642
|
+
- Updated dependencies [3cf4de0]
|
|
643
|
+
- Updated dependencies [c9dc811]
|
|
644
|
+
- Updated dependencies [144ef9b]
|
|
645
|
+
- Updated dependencies [138ab04]
|
|
646
|
+
- Updated dependencies [a0b9e91]
|
|
647
|
+
- Updated dependencies [99bd015]
|
|
648
|
+
- Updated dependencies [21e4585]
|
|
649
|
+
- @object-ui/types@17.6.0
|
|
650
|
+
- @object-ui/fields@17.6.0
|
|
651
|
+
- @object-ui/i18n@17.6.0
|
|
652
|
+
- @object-ui/react@17.6.0
|
|
653
|
+
- @object-ui/components@17.6.0
|
|
654
|
+
- @object-ui/core@17.6.0
|
|
655
|
+
- @object-ui/permissions@17.6.0
|
|
656
|
+
|
|
657
|
+
## 17.5.0
|
|
658
|
+
|
|
659
|
+
### Patch Changes
|
|
660
|
+
|
|
661
|
+
- ae10a01: Console chrome reaches the bundle — the list switcher, the aggregate footer, the dialog a11y fallbacks and the whole Settings namespace screen stop being English on non-English consoles
|
|
662
|
+
|
|
663
|
+
Six strings on the two screens a user looks at most were hardcoded English literals rather than bundle lookups, so they stayed English on every non-English console with nothing an app could author to change them. They are not object, field, view or action labels — no key in `TranslationData` reaches them — while the console's own bundle already ships zh-CN, ja-JP, es-ES, de, fr, pt, ru, ko and ar and translates hundreds of neighbouring strings. Omissions from an otherwise complete bundle, not a missing capability.
|
|
664
|
+
|
|
665
|
+
**Two of the six needed no new keys at all, which is the more interesting half.** The list-view mode switcher named its nine visualizations from a private `VIEW_LABELS` table while `console.objectView.viewType*` — the same nine words — had been resolved through the bundle by the create-view picker for months; the switcher now reads those keys, so the picker's 「画廊」 and the switcher's 「画廊」 cannot drift apart in nine languages. The create/edit dialog's close button is the remainder of a fix that already landed: objectstack#5505 routed the `sr-only` close label through `common.close` for the two Shadcn-synced primitives, but `MobileDialogContent` is a hand-written wrapper outside that regeneration zone with its own close button, and it is exactly what `ModalForm` renders — so the dialog the report measured was the one place still announcing "Close" in English.
|
|
666
|
+
|
|
667
|
+
The aggregate footer is the one the original report singled out: the **number** was already locale-formatted and the **prefix** was a hardcoded `Avg: ` / `Sum: `. All eleven aggregation kinds now take their prefix from `grid.summary.*`, and the label/value join is its own key rather than a `': '` baked into the renderer — the separator is translatable content, so zh sets a fullwidth colon and fr the French space-before-colon. The numbers are untouched. The form dialog's `sr-only` description fallback joins the packs too; it is clipped, not visible, so the only way an app could displace it was to author a `description` and thereby put a visible subtitle on every dialog.
|
|
668
|
+
|
|
669
|
+
**The Settings namespace screen converts as one unit.** `SettingsView` routed zero framing copy through i18n — save/failure toasts, the env-lock and crypto refusals, the load-error card, the empty-route state, the navigation buttons, the unsaved-changes save bar — while its immediate sibling `SettingsHub`, in the same directory, resolved everything through `t('console.settingsHub.*')`. A zh-CN admin read correctly translated field labels sitting inside an English save bar, because `useSettingsLabel` translates a namespace's authored content but reaches none of the chrome around it. All of it now resolves through a `console.settingsView.*` namespace placed beside the hub's, including the crypto-refusal strings that objectui#4579 deliberately left in English rather than leave one translated string among a dozen literals.
|
|
670
|
+
|
|
671
|
+
The save-bar counter was an English plural rule executing in every locale (`change` plus an `s` when the count exceeds one). It is now a real i18next plural family — base key plus `_one` and `_other` in all ten packs — not the `(s)` spelling translated nine ways. The base key is the load-bearing part: i18next asks `Intl.PluralRules` for the one suffix a language needs and, finding no such slot, falls back to English, so without it Russian would read English at counts 2-20 and Arabic at 2-99. Russian and Arabic take the "noun: {count}" form their packs already use for this exact reason, and the counter is verified rendering in-language at 1, 2 and 5.
|
|
672
|
+
|
|
673
|
+
The Beta badge reuses the hub's existing key rather than minting a twin, and the refusal messages interpolate their subject through the bundle instead of concatenating a translated word onto an English prefix.
|
|
674
|
+
|
|
675
|
+
- c32a8a1: `richtext` fields are placed like the long-form fields they are — four layout sets stopped spelling the type three ways the spec rejects
|
|
676
|
+
|
|
677
|
+
`@objectstack/spec` spells the WYSIWYG type `richtext`, one word, and **rejects** `rich_text` and `rich-text`: both exist only as typo keys in the spec's own `suggestFieldType` table, so `FieldSchema` refuses a field declared with either. Four sets that place fields by matching the RAW type string carried nothing else — `SKIP_TYPES` in the related list spelled it `rich_text`, both `WIDE_FIELD_TYPES` and `SECONDARY_FIELD_TYPES` spelled it `rich-text` — so each set was inert for the only spelling a producer can emit, and every one of them named the type it was failing to handle.
|
|
678
|
+
|
|
679
|
+
For a real `richtext` field that meant: it was auto-derived into a related-list column, it never spanned the full row in a multi-column detail section or form (unlike `markdown` and `html` sitting right beside it in the same sets), and it stayed in the dense primary section of the record page instead of dropping into "More details". All four move together — half of them would have left the detail page and the form disagreeing about the same field, which is worse than the uniform gap.
|
|
680
|
+
|
|
681
|
+
The dead spellings are dropped rather than kept alongside the live one: the alias table is the single place aliases belong, and a set that carries both invites the next drift. The pins are derived from the spec's own `FieldType` vocabulary instead of enumerated, so a member that stops being a real type name fails by name — replacing an assertion that was green only because the set contained the string it asked about.
|
|
682
|
+
|
|
683
|
+
`markdown` joins `richtext` and `html` in the related list's `SKIP_TYPES`, on a measurement rather than on the assumption that it renders raw. It does not: markdown and richtext both render through `MarkdownCellRenderer`, formatted and sanitized. The reason none of the three works in a table is that the formatted output is block-level — a heading, paragraphs, a list — inside a single-line truncating cell, so a document shows as one clipped heading with the rest invisible. `textarea` stays derived for the same reason read the other way: it renders as plain truncated text, which is a useful column. Author-declared columns are untouched — this set only filters the zero-config auto-derive walk.
|
|
684
|
+
|
|
685
|
+
- Updated dependencies [0e67b53]
|
|
686
|
+
- Updated dependencies [ceccdcf]
|
|
687
|
+
- Updated dependencies [d6e5124]
|
|
688
|
+
- Updated dependencies [debad27]
|
|
689
|
+
- Updated dependencies [dc2aa3e]
|
|
690
|
+
- Updated dependencies [ee66e2e]
|
|
691
|
+
- Updated dependencies [e2e6360]
|
|
692
|
+
- Updated dependencies [ee26e65]
|
|
693
|
+
- Updated dependencies [5900ac5]
|
|
694
|
+
- Updated dependencies [932cbcd]
|
|
695
|
+
- Updated dependencies [734d186]
|
|
696
|
+
- Updated dependencies [f650253]
|
|
697
|
+
- Updated dependencies [3d9769a]
|
|
698
|
+
- Updated dependencies [8f85f8b]
|
|
699
|
+
- Updated dependencies [d0c3b26]
|
|
700
|
+
- Updated dependencies [3fc2971]
|
|
701
|
+
- Updated dependencies [aca27fa]
|
|
702
|
+
- Updated dependencies [dde7283]
|
|
703
|
+
- Updated dependencies [f7c6430]
|
|
704
|
+
- Updated dependencies [4dadf0d]
|
|
705
|
+
- Updated dependencies [ae10a01]
|
|
706
|
+
- Updated dependencies [0f21348]
|
|
707
|
+
- Updated dependencies [d2e2caf]
|
|
708
|
+
- Updated dependencies [92876f0]
|
|
709
|
+
- Updated dependencies [f279deb]
|
|
710
|
+
- Updated dependencies [4b70d28]
|
|
711
|
+
- Updated dependencies [eb7f586]
|
|
712
|
+
- Updated dependencies [e901131]
|
|
713
|
+
- Updated dependencies [ebb4e0e]
|
|
714
|
+
- Updated dependencies [3a9021e]
|
|
715
|
+
- Updated dependencies [d9d3463]
|
|
716
|
+
- Updated dependencies [2a40f69]
|
|
717
|
+
- Updated dependencies [bec3e14]
|
|
718
|
+
- Updated dependencies [613b167]
|
|
719
|
+
- Updated dependencies [b4d3c22]
|
|
720
|
+
- Updated dependencies [1f9b905]
|
|
721
|
+
- Updated dependencies [8f60d73]
|
|
722
|
+
- Updated dependencies [cb13400]
|
|
723
|
+
- Updated dependencies [828549a]
|
|
724
|
+
- Updated dependencies [e1ade8f]
|
|
725
|
+
- Updated dependencies [bc64bfe]
|
|
726
|
+
- Updated dependencies [abb0f81]
|
|
727
|
+
- Updated dependencies [38ab505]
|
|
728
|
+
- Updated dependencies [3e19fe7]
|
|
729
|
+
- Updated dependencies [bb58d1d]
|
|
730
|
+
- Updated dependencies [433ff9f]
|
|
731
|
+
- Updated dependencies [5cc847c]
|
|
732
|
+
- Updated dependencies [e7663f2]
|
|
733
|
+
- Updated dependencies [fa21254]
|
|
734
|
+
- Updated dependencies [33c32bf]
|
|
735
|
+
- Updated dependencies [66fb4fa]
|
|
736
|
+
- Updated dependencies [b953a97]
|
|
737
|
+
- Updated dependencies [d7f3e30]
|
|
738
|
+
- Updated dependencies [6d641c9]
|
|
739
|
+
- Updated dependencies [7e4f0e5]
|
|
740
|
+
- Updated dependencies [a84385b]
|
|
741
|
+
- Updated dependencies [45e1949]
|
|
742
|
+
- Updated dependencies [92250d6]
|
|
743
|
+
- Updated dependencies [c1d939f]
|
|
744
|
+
- Updated dependencies [58bebf6]
|
|
745
|
+
- Updated dependencies [36310dc]
|
|
746
|
+
- Updated dependencies [52d878a]
|
|
747
|
+
- Updated dependencies [405e808]
|
|
748
|
+
- Updated dependencies [49ae9f4]
|
|
749
|
+
- Updated dependencies [a3ae404]
|
|
750
|
+
- Updated dependencies [bfdf3d4]
|
|
751
|
+
- Updated dependencies [bb68488]
|
|
752
|
+
- Updated dependencies [c0f9a4b]
|
|
753
|
+
- Updated dependencies [b1e42d0]
|
|
754
|
+
- Updated dependencies [2459a3e]
|
|
755
|
+
- Updated dependencies [ac853ce]
|
|
756
|
+
- Updated dependencies [fa51109]
|
|
757
|
+
- Updated dependencies [d6aa172]
|
|
758
|
+
- Updated dependencies [fe52a04]
|
|
759
|
+
- Updated dependencies [d46f9b8]
|
|
760
|
+
- Updated dependencies [3f5f87c]
|
|
761
|
+
- Updated dependencies [2fea4d2]
|
|
762
|
+
- Updated dependencies [f5e1143]
|
|
763
|
+
- Updated dependencies [7f1cb33]
|
|
764
|
+
- Updated dependencies [f148a64]
|
|
765
|
+
- Updated dependencies [bb68488]
|
|
766
|
+
- Updated dependencies [2e3b0c0]
|
|
767
|
+
- Updated dependencies [9461dd3]
|
|
768
|
+
- Updated dependencies [78fa331]
|
|
769
|
+
- Updated dependencies [47f551b]
|
|
770
|
+
- Updated dependencies [31ab1ac]
|
|
771
|
+
- Updated dependencies [0082db8]
|
|
772
|
+
- Updated dependencies [ab04728]
|
|
773
|
+
- Updated dependencies [5bf09fd]
|
|
774
|
+
- Updated dependencies [06915b0]
|
|
775
|
+
- Updated dependencies [ff84b05]
|
|
776
|
+
- @object-ui/i18n@17.5.0
|
|
777
|
+
- @object-ui/react@17.5.0
|
|
778
|
+
- @object-ui/components@17.5.0
|
|
779
|
+
- @object-ui/core@17.5.0
|
|
780
|
+
- @object-ui/fields@17.5.0
|
|
781
|
+
- @object-ui/types@17.5.0
|
|
782
|
+
- @object-ui/permissions@17.5.0
|
|
783
|
+
|
|
784
|
+
## 17.4.0
|
|
785
|
+
|
|
786
|
+
### Minor Changes
|
|
787
|
+
|
|
788
|
+
- ecae400: Retire the `capability-multiselect` field widget name, which existed only on the docs-site registration path and which nothing ever stamped (objectui#3308, ADR-0049 enforce-or-remove)
|
|
789
|
+
|
|
790
|
+
`field:capability-multiselect` was registered by `registerFields()` and only there. That function's sole caller is the docs site, so the key never existed on the live path (`registerAllFields()`, run at module import, iterates `fieldWidgetMap` — which never listed it). A field authored with `widget: 'capability-multiselect'` therefore resolved to nothing in every real application, while the comment above the registration described it as usable from a record form: a code comment promising a capability that does not exist, which is the worst direction for a metadata renderer AI-authored apps read as authority.
|
|
791
|
+
|
|
792
|
+
Nothing stamped the hint either. ADR-0056 P1 stamps `permission-facet-link` on all six `sys_permission_set` facets — `system_permissions` included — through the single `ObjectStackAdapter.getObjectSchema` choke point, and P2 put the capability editor in Studio. The widget name was a leftover from an intermediate iteration of that rollout.
|
|
793
|
+
|
|
794
|
+
Removed, with a tombstone at each site:
|
|
795
|
+
|
|
796
|
+
- `@object-ui/fields` — the `field:capability-multiselect` registration and the comment that advertised it. **Breaking in name only**: the key was unreachable outside the docs site, so no application could have resolved it. A field still carrying the hint now degrades to its declared `type` renderer, the defined behavior for an unregistered widget.
|
|
797
|
+
- `@object-ui/plugin-detail` — `InlineFieldInput`'s `widget === 'capability-multiselect'` branch, the hint's last honoring surface. Leaving one consumer for a name no producer emits and no form resolves is the same declared-vs-enforced split, inverted. The sibling `permission-facet-link` branch is untouched and pinned.
|
|
798
|
+
- `@object-ui/components` — the dead `capability-multiselect` entry in the form renderer's `DATA_SOURCE_FIELD_TYPES` set, which could never match a resolvable widget.
|
|
799
|
+
- `@object-ui/plugin-form` — a comment naming `capability-multiselect` as the widget stamped onto `sys_permission_set.system_permissions`; it names `permission-facet-link` now, which is what is actually stamped.
|
|
800
|
+
|
|
801
|
+
`CapabilityMultiSelectField` itself is **unchanged and still exported**: Studio's `PermissionMatrixEditor` imports and renders it directly, which is ADR-0056 P2's design. Only the widget name is retired — the component is not a registry field widget and its doc comment now says so.
|
|
802
|
+
|
|
803
|
+
`registerFields()` is also **kept**, with its `@deprecated Use registerAllFields() instead` note corrected. The two are not interchangeable: it registers `createFieldRenderer(widget)`, which synthesizes the label, description and the local `value`/`onChange` state that lets a bare field node (`{ type: 'currency', label: 'Amount' }`) render standalone in the docs demos. Retiring it needs a decision about where that demo chrome goes; the note now records that instead of implying a drop-in replacement.
|
|
804
|
+
|
|
805
|
+
- 1bd6faa: fix(fields,plugin-form): stop the inline child grid from collapsing `datetime`/`time` columns onto the `date` control
|
|
806
|
+
|
|
807
|
+
`deriveMasterDetail`'s `fieldTypeToColumnType` mapped `date`, `datetime` and `time` onto the single `date` grid column type, and `GridField` renders that as `<input type="date">`. The consequence was not cosmetic: that control emits a bare `YYYY-MM-DD` on change, so a user who merely re-picked the **day** of a `datetime` cell silently wrote the record's time component out of existence — a `14:30` became midnight with no warning and no undo.
|
|
808
|
+
|
|
809
|
+
`GridColumn['type']` now carries `'datetime'` and `'time'` alongside `'date'`, and each renders its own control with its own read/write adapter:
|
|
810
|
+
|
|
811
|
+
- `datetime` → `<input type="datetime-local">`, read through `toDateTimeInputValue` and written back through `fromDateTimeInputValue`, so the stored shape stays ISO-8601 and read and write share one basis (the contract `DateTimeField` already follows, objectui#3127).
|
|
812
|
+
- `time` → `<input type="time">`, round-tripping the stored zone-less `HH:mm[:ss]` verbatim.
|
|
813
|
+
- `date` → unchanged.
|
|
814
|
+
|
|
815
|
+
The read-only surfaces are fixed with it. `displayText()` and the read-only table both fell through to `String(value)` and printed the raw stored ISO on screen (`2026-06-17T00:00:00.000Z`); each temporal type now formats as itself — a day for `date`, day + local time for `datetime`, the wall clock for `time`. That could not be fixed before the type collapse was undone, because with one column type the renderer had no way to know which of the two to show.
|
|
816
|
+
|
|
817
|
+
Authors writing explicit grid `columns` can now declare `type: 'datetime'` / `type: 'time'`; previously those spellings were not part of the exported union.
|
|
818
|
+
|
|
819
|
+
### Patch Changes
|
|
820
|
+
|
|
821
|
+
- 8497579: A required field whose `defaultValue` is a runtime token is submittable from a create form
|
|
822
|
+
|
|
823
|
+
`@objectstack/spec` lets a field's `defaultValue` be a runtime _instruction_
|
|
824
|
+
rather than a value — the `DEFAULT_VALUE_TOKENS` family (`'NOW()'`,
|
|
825
|
+
`'current_user'`) or a CEL Expression envelope. The server resolves those per
|
|
826
|
+
insert, in `ObjectQL.applyFieldDefaults`, for any field that arrives absent or
|
|
827
|
+
null, which is why a create form must leave them empty: seeding the literal text
|
|
828
|
+
`NOW()` into a datetime input and submitting it suppresses the very resolution
|
|
829
|
+
the declaration asked for.
|
|
830
|
+
|
|
831
|
+
Correct for an optional field. Combined with `required: true` it deadlocked:
|
|
832
|
+
|
|
833
|
+
```ts
|
|
834
|
+
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
the control opened empty, the client-side required rule refused the submit, and
|
|
838
|
+
there was nothing sensible for the user to type — the declaration had already
|
|
839
|
+
said what the value is, and omitting the field is exactly what makes the server
|
|
840
|
+
supply it. Same shape as the `required` + static-default case, one layer down.
|
|
841
|
+
|
|
842
|
+
In **create** mode a runtime `defaultValue` now suppresses the client-side
|
|
843
|
+
`required` rule, and the field is omitted from the payload. The producer
|
|
844
|
+
guarantees the value at insert, so the field is not "missing" — it is
|
|
845
|
+
server-owned. `required: true` alongside a runtime default is coherent authoring
|
|
846
|
+
(storage-level required, producer-guaranteed), not an authoring error.
|
|
847
|
+
|
|
848
|
+
Both halves matter. Suppressing the rule alone would have been half an answer: a
|
|
849
|
+
rendered control registers with the form whether or not anything seeded it, so
|
|
850
|
+
an untouched runtime-default field still reached the payload as `undefined` — or
|
|
851
|
+
as `''` once anything focused it. `undefined` is invisible to a
|
|
852
|
+
`JSON.stringify` inspection while remaining a KEY a data source may translate
|
|
853
|
+
into an explicit column write, and `''` is neither absent nor null, so it stores
|
|
854
|
+
a blank and defeats the declaration outright.
|
|
855
|
+
|
|
856
|
+
Three boundaries came with it, each pinned in both directions:
|
|
857
|
+
|
|
858
|
+
- **Create only.** An edit form shows a persisted row, where the token was
|
|
859
|
+
resolved at insert; blanking a required column there is a real removal and is
|
|
860
|
+
still refused.
|
|
861
|
+
- **Runtime defaults only.** A static literal default _is_ seeded into the
|
|
862
|
+
control, so if the user clears it they have removed a value that was really
|
|
863
|
+
there — `required` still fires.
|
|
864
|
+
- **The rule, not the field.** A value the user does type is submitted normally
|
|
865
|
+
and outranks the declared default. Only the "must not be empty" check is
|
|
866
|
+
suppressed.
|
|
867
|
+
|
|
868
|
+
Seeding and this rule read ONE predicate (`isRuntimeDefault`), so a form can
|
|
869
|
+
never seed a field it also refuses to submit. The suppression also drops the
|
|
870
|
+
required marker and `aria-required` for that field in create mode, since both
|
|
871
|
+
are driven by the same boolean — the honest reading, as the user really is not
|
|
872
|
+
required to provide the value. Surfacing what the server _will_ supply, as a
|
|
873
|
+
non-authoritative preview, is a separate follow-up.
|
|
874
|
+
|
|
875
|
+
Not extended to `requiredWhen` (the conditional-required CEL rule), which is
|
|
876
|
+
resolved downstream in the form renderer against the live record.
|
|
877
|
+
|
|
878
|
+
- f0c9a90: Create forms now open with the object schema's declared `defaultValue`s
|
|
879
|
+
|
|
880
|
+
A field declared `required: true, defaultValue: 'draft'` opened the console's
|
|
881
|
+
create dialog with an empty select and a required marker: the user had to pick a
|
|
882
|
+
value the system already knew, with every neighbouring option — some with side
|
|
883
|
+
effects — one click away. `defaultValue` + `required` produced the worst create
|
|
884
|
+
experience of any modelling choice, strictly worse than declaring no default.
|
|
885
|
+
|
|
886
|
+
The server was never the problem. Omitting the field from a create request
|
|
887
|
+
stores the declared default, because `ObjectQL.applyFieldDefaults` resolves it on
|
|
888
|
+
insert. The gap was container-side: `ObjectForm` seeded its opening values from
|
|
889
|
+
the object schema, and the five other object-form containers did not — their
|
|
890
|
+
create branch set the form data to `initialData || initialValues || {}` and never
|
|
891
|
+
looked at the schema. The console's create dialog is the global `<ModalForm>`,
|
|
892
|
+
one of those five. Modal, Drawer, Tabbed, Split and Wizard now seed through one
|
|
893
|
+
shared module (`schemaDefaults`), so a create form opens preselected and
|
|
894
|
+
submittable.
|
|
895
|
+
|
|
896
|
+
Three boundaries came with it, each pinned in both directions:
|
|
897
|
+
|
|
898
|
+
- **Create only.** An edit form shows a persisted row as the server holds it.
|
|
899
|
+
`ObjectForm`'s pass had been running in every mode, so a column the record
|
|
900
|
+
leaves unset showed the default — arming a silent write of a value the user
|
|
901
|
+
never chose on the next save of any other field. It is now gated on the same
|
|
902
|
+
"no persisted record" test the data-fetch effect uses.
|
|
903
|
+
- **Static defaults only.** A `defaultValue` may be an instruction the server
|
|
904
|
+
resolves per insert — the `NOW()` / `current_user` runtime tokens
|
|
905
|
+
(`DEFAULT_VALUE_TOKENS`) or a CEL Expression envelope. `ObjectForm` had been
|
|
906
|
+
seeding those verbatim, which put the literal text `NOW()` into a datetime
|
|
907
|
+
input and then submitted it as the field's value, suppressing the very
|
|
908
|
+
resolution the declaration asked for (`applyFieldDefaults` only fills fields
|
|
909
|
+
that arrive empty). Those are now left empty for the server.
|
|
910
|
+
- **Callers still win.** `initialData` / `initialValues` outrank a schema
|
|
911
|
+
default — a lookup prefill or a duplicate-record seed is the more specific
|
|
912
|
+
instruction.
|
|
913
|
+
|
|
914
|
+
Only the field-level `defaultValue` is honoured, not a select option's
|
|
915
|
+
`default: true`, even though `@objectstack/spec`'s `SelectOptionSchema` declares
|
|
916
|
+
that key: the insert path resolves `defaultValue` and nothing else, so seeding
|
|
917
|
+
from option-level `default` would preselect values the server would never have
|
|
918
|
+
applied — a UI-only second default contract.
|
|
919
|
+
|
|
920
|
+
- 5bfaabd: `PageComponentSchema.dataSource` now reaches every object-bound block, not just
|
|
921
|
+
`list-view` — and `element:record_picker` stops discarding `view`
|
|
922
|
+
(objectstack#6953).
|
|
923
|
+
|
|
924
|
+
objectstack#5576 wired the spec's per-element data binding
|
|
925
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to `list-view` and left
|
|
926
|
+
the same declaration inert on every other page component. Two gaps remained, and
|
|
927
|
+
both were silent:
|
|
928
|
+
|
|
929
|
+
- **`element:record_picker` read four of the five keys and dropped `view`.** So
|
|
930
|
+
`dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
931
|
+
built a picker over EVERY account instead of the rows the saved view selects.
|
|
932
|
+
Nothing threw and nothing rendered an error; the option list was simply wider
|
|
933
|
+
than what was authored, which also means a user could select a record the page
|
|
934
|
+
said was out of scope.
|
|
935
|
+
- **`object-grid` / `object-form` / `object-kanban` / `object-calendar` /
|
|
936
|
+
`object-chart` / `object-metric` / `record:related_list` read none of it.**
|
|
937
|
+
Each gates its fetch on its own `objectName`, and nothing mapped
|
|
938
|
+
`dataSource.object` onto it, so a page written the way the spec documents
|
|
939
|
+
rendered an empty grid / a field-less form / a board with no cards / an empty
|
|
940
|
+
month / an empty chart / a static metric number — with no request and no
|
|
941
|
+
diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
942
|
+
objectstack#4413 shape.
|
|
943
|
+
|
|
944
|
+
Composition follows objectstack#5576's landed semantics unchanged on every block:
|
|
945
|
+
a named saved view supplies the baseline, a key written on the component itself
|
|
946
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
947
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
948
|
+
a `view` name that does not resolve renders a configuration error instead of
|
|
949
|
+
degrading to the object's full scope.
|
|
950
|
+
|
|
951
|
+
- `@object-ui/react` — new `useElementDataSourceSchema(schema, mapping, dataSource?)`
|
|
952
|
+
and `ElementDataSourceGate` apply a resolved binding to the schema keys a given
|
|
953
|
+
block reads, plus `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`
|
|
954
|
+
for the two non-final states. One precedence table for all blocks rather than
|
|
955
|
+
one copy per block — that copy is how "additional filter criteria" would have
|
|
956
|
+
become two dialects.
|
|
957
|
+
- A mapping names **only** keys its block genuinely reads. A composed value
|
|
958
|
+
written onto a key the block ignores would be accepted and dropped, which is
|
|
959
|
+
the defect being removed, one layer deeper — so a kanban's swimlane `columns`
|
|
960
|
+
never receive a view's field list, and a block with no row cap leaves `limit`
|
|
961
|
+
unmapped. The per-block coverage table, including two residual gaps that are
|
|
962
|
+
named rather than papered over, is in `content/docs/guide/data-source.md`.
|
|
963
|
+
|
|
964
|
+
No behaviour changes for a block that carries no `dataSource`: the binding-free
|
|
965
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
966
|
+
|
|
967
|
+
- 022002a: `PageComponentSchema.dataSource` now reaches the remaining object-bound public
|
|
968
|
+
blocks: `object-gantt` / `object-timeline` / `object-map` / `object-pivot` /
|
|
969
|
+
`object-master-detail-form` / `embeddable-form` / `record:line_items`
|
|
970
|
+
(objectstack#7121).
|
|
971
|
+
|
|
972
|
+
objectstack#6953 wired the spec's per-element data binding
|
|
973
|
+
(`dataSource: { object, view?, filter?, sort?, limit? }`) to the eight blocks it
|
|
974
|
+
named and left the same declaration inert on these seven. Each gates its fetch on
|
|
975
|
+
its own object key and nothing mapped `dataSource.object` onto it, so a page
|
|
976
|
+
written the way the spec documents rendered an empty gantt / an empty timeline
|
|
977
|
+
rail / a map with no markers / an empty cross-tab / a field-less form — with no
|
|
978
|
+
request and no diagnostic anywhere. Spec-valid metadata rendering nothing is the
|
|
979
|
+
objectstack#4413 shape.
|
|
980
|
+
|
|
981
|
+
Composition follows objectstack#5576's landed semantics unchanged, through the
|
|
982
|
+
shared `ElementDataSourceGate` (no change to it or to the resolution layer): a
|
|
983
|
+
named saved view supplies the baseline, a key written on the component itself
|
|
984
|
+
overrides it, an explicit binding key overrides both, `filter` AND-combines
|
|
985
|
+
("additional filter criteria" — a binding can narrow a view, never widen it), and
|
|
986
|
+
a `view` name that does not resolve renders a configuration error on every one of
|
|
987
|
+
these blocks instead of degrading to the object's full scope.
|
|
988
|
+
|
|
989
|
+
Each block maps **only** the keys it genuinely reads, which for this batch means
|
|
990
|
+
several keys stay deliberately unmapped rather than being parked somewhere
|
|
991
|
+
plausible:
|
|
992
|
+
|
|
993
|
+
- `object-gantt` and `object-map` take `object` / `filter` / `sort`; neither has a
|
|
994
|
+
row cap or a field-list read site.
|
|
995
|
+
- `object-pivot` takes `object` / `filter`; a cross-tab orders itself by its own
|
|
996
|
+
row/column grouping and cannot be computed over a truncated page.
|
|
997
|
+
- `object-timeline` takes `object` only — its fetch is
|
|
998
|
+
`find(objectName, { options: { $top: 100 } })`, with no filter/sort read site
|
|
999
|
+
at all, so a named view is error-checked and then contributes nothing.
|
|
1000
|
+
- `embeddable-form` and `object-master-detail-form` take `object` only (the
|
|
1001
|
+
parent object, in the master-detail case); a form that writes one record has no
|
|
1002
|
+
collection query for `filter` / `sort` / `limit` to narrow.
|
|
1003
|
+
- `record:line_items` takes `object` onto **`childObject`** — the collection it
|
|
1004
|
+
actually lists — and nothing else: its query is the parent FK plus a fixed
|
|
1005
|
+
`$top: 500`, and its `columns` are editable `GridColumn` objects rather than a
|
|
1006
|
+
field-name projection a view could supply.
|
|
1007
|
+
|
|
1008
|
+
The per-block coverage table, including every residual gap named above, is in
|
|
1009
|
+
`content/docs/guide/data-source.md`.
|
|
1010
|
+
|
|
1011
|
+
No behaviour change for a block that carries no `dataSource`: the binding-free
|
|
1012
|
+
path returns the schema by reference, so nothing remounts and nothing refetches.
|
|
1013
|
+
|
|
1014
|
+
- 6d762da: The five locale keys behind #3546's eight no-fallback `t()` call sites are now defined in all ten packs, so the built-in-view toasts, the activity-timeline source link, the wizard's required-field toast and the Gantt refresh button's accessible name are translated instead of falling back to English — or, on two surfaces, to the key itself (part of #3546).
|
|
1015
|
+
|
|
1016
|
+
`scripts/check-i18n-call-site-keys.mjs` measured 258 keys that a `t()` call site asks for and no pack defines. These five were the subset with no working inline default: `console.objectView.cannotEditMetaView`, `console.objectView.cannotDeleteMetaView`, `detail.viewSource`, `gantt.toolbar.refresh` and `wizard.missingRequired`. Adding a `defaultValue` is deliberately not the fix — that mechanism is what kept all 258 invisible for months.
|
|
1017
|
+
|
|
1018
|
+
**Two of the eight sites really did render the raw key**, and both go through a binding with nothing in front of i18next. `ObjectView.tsx` calls `useObjectTranslation()` directly, so five toasts read `console.objectView.cannotEditMetaView` / `cannotDeleteMetaView` on screen; the `|| 'Built-in views cannot be renamed.'` guards next to them were dead on every path, because i18next answers a miss with the key itself and a non-empty string never falls through `||`. Those four unreachable English strings are removed rather than repaired: one key served four call sites (rename / pin / set-as-default / configure), so the pack copy covers any change to a built-in view instead of naming one operation. `RecordActivityTimeline.tsx` fails the same way for a subtler reason — `useDetailTranslation` is `createSafeTranslation(..., 'detail.back')`, and because `detail.back` does resolve, the probe hands back i18next's `t` for every key and bypasses the defaults map wholesale, so `detail.viewSource` reached the user verbatim.
|
|
1019
|
+
|
|
1020
|
+
**The other two sites were not rendering a raw key**, contrary to the issue's description, and are fixed here as the milder "English in all ten languages" class. `wizard.missingRequired` is its own hook's probe key, so the probe failed and `createSafeTranslation` correctly served its English default. `gantt.toolbar.refresh` goes through `useGanttTranslation`, which deliberately does not use `createSafeTranslation` and falls back per key — so the refresh button's `aria-label` was "Refresh", in English, never the key. Screen-reader users heard an English word rather than an identifier; a `zh` session now hears 刷新.
|
|
1021
|
+
|
|
1022
|
+
Regression cover is provider-mounted on purpose: with no `I18nProvider` the defaults maps answer every one of these keys and the assertions pass while the console is broken, which is precisely the false-green the issue documents. For the two sites whose English output was already correct, `en` cannot discriminate before from after — the `zh` assertions are the ones that pin the fix.
|
|
1023
|
+
|
|
1024
|
+
- 11c1e71: Resolve a `select` field declared `multiple: true` to the `field:multiselect` widget, so the object form's visible label actually names the chip picker it renders (objectui#3986).
|
|
1025
|
+
|
|
1026
|
+
`mapFieldTypeToFormType` keyed the widget id on the field's `type` string alone, so an object-schema `{ type: 'select', multiple: true }` picklist — a spec-legal, entirely ordinary shape — became `field:select`. `SelectField` then delegated to `MultiSelectField` on `config.multiple`, so the component that RENDERED was the chip picker while everything keyed on the widget id still answered for the single-value combobox. Above all the label-association declaration (`ComponentMeta.labelling`, objectui#3961), which the form renderer resolves per widget id: the host emitted `<label for>` at the chip row's wrapper `div`, where a `for` is inert — `HTMLLabelElement.control` returns `null`. Visually the field had a label; in the accessibility tree that label named nothing. Measured on the object-form path, `role=group` + accessible name went from 1 for a `multiselect`-typed field (fixed in objectui#3975) to 0 for this one.
|
|
1027
|
+
|
|
1028
|
+
Declaring `select` itself `labelling: 'group'` was not available: a single-value select's trigger is a labelable `button[role=combobox]` whose `for` association works, and a bare `select` is a builtin the renderer resolves without consulting the registry at all. The fix is therefore at the producer — the widget id now carries the arity, so one place decides which widget renders and the declaration can no longer be addressed to a widget that is not rendering.
|
|
1029
|
+
|
|
1030
|
+
- `mapFieldTypeToFormType(fieldType, config?)` takes an optional second argument — the rest of the field definition, of which only `multiple` is read. Existing single-argument calls are unchanged, and so is every type outside the new table: `select` is the only one whose `multiple` form is a different WIDGET. The spec's multi-capable set is larger (select / lookup / file / image, with `radio` on the select branch and `user` storing like `lookup`), but `LookupField`, `FileField` and `ImageField` each render both arities themselves, so their id — and their labelling declaration — is already right for either.
|
|
1031
|
+
- The four object-form producers pass the pair: `ObjectForm`, `DrawerForm`, `ModalForm`, and `sectionFields` (Tabbed / Wizard / Split / Drawer / Modal). In `sectionFields` the id is now computed once from the EFFECTIVE pair, after view-level overrides have merged, because `multiple` is itself a spec `FormField` key: a view restating only `multiple: true` over a single-value object field moves the widget too, and `multiple: false` moves it back.
|
|
1032
|
+
- `SelectField`'s `multiple` delegation is KEPT, not retired. Measured, it stays reachable from three entrances that never consult the alias map: the inline grid editor (`FieldEditWidget` finds `select` in its own table first), `ActionParamDialog` (`resolveFormWidgetType` returns `select` from `fieldWidgetMap` first), and hand-written SDUI addressing `field:select` by name with `multiple` on its metadata.
|
|
1033
|
+
|
|
1034
|
+
Read-only rendering of these widgets is untouched (objectui#4005), as is the built-in `Select` branch (objectui#3976).
|
|
1035
|
+
|
|
1036
|
+
- 523be48: `object-timeline` and `record:line_items` now apply the filter / sort / row cap they are given, so a named `dataSource.view` narrows them instead of contributing nothing
|
|
1037
|
+
|
|
1038
|
+
These were the two residual gaps in objectstack#7121's per-block coverage table
|
|
1039
|
+
(objectstack#7137). Both blocks are object-bound lists, both accepted the spec's
|
|
1040
|
+
per-element `dataSource` binding, and neither had a read site for `filter` or
|
|
1041
|
+
`sort` anywhere in its fetch:
|
|
1042
|
+
|
|
1043
|
+
- `object-timeline`'s entire query was
|
|
1044
|
+
`find(objectName, { options: { $top: 100 } })`.
|
|
1045
|
+
- `record:line_items`' was the parent FK plus a fixed `$top: 500`.
|
|
1046
|
+
|
|
1047
|
+
So `dataSource: { object, view: 'hot' }` resolved the view — a typo still reported
|
|
1048
|
+
a configuration error, it never degraded into an unfiltered query — and then
|
|
1049
|
+
dropped everything the view said. The rendered rows could be **wider than the view
|
|
1050
|
+
they named**, silently, which is exactly the class of mistake AI-authored metadata
|
|
1051
|
+
hides best: the page looks like it works. objectstack#7121 deliberately left the
|
|
1052
|
+
keys unmapped and recorded the gap rather than writing composed values onto schema
|
|
1053
|
+
keys nobody read; this closes it at the fetch instead.
|
|
1054
|
+
|
|
1055
|
+
What each block now reads:
|
|
1056
|
+
|
|
1057
|
+
- **`object-timeline`** — `$filter: schema.filter`,
|
|
1058
|
+
`$orderby: convertSortToQueryParams(schema.sort)`, and
|
|
1059
|
+
`$top: schema.limit ?? 100`, matching the form `object-gantt` / `object-map` /
|
|
1060
|
+
`object-calendar` already use. Its registry mapping gains
|
|
1061
|
+
`filter` / `sort` / `limit`; `columns` stays unmapped, because a timeline
|
|
1062
|
+
projects the fields its `timeline` config names.
|
|
1063
|
+
- **`record:line_items`** — the composed filter is **AND-combined** with the parent
|
|
1064
|
+
relationship condition through `mergeFilterNodes`, never substituted for it, the
|
|
1065
|
+
same way `record:related_list` composes its own since objectstack#7118: a
|
|
1066
|
+
line-items panel is always scoped to the record it sits on, so an _additional_
|
|
1067
|
+
criterion can only narrow this parent's children and can never surface another
|
|
1068
|
+
parent's rows. `sort` becomes the load order and `limit` the row cap (default
|
|
1069
|
+
500). `columns` stays unmapped — here they are `GridColumn[]` driving an editable
|
|
1070
|
+
grid, not a field-name projection, so a view's column list would be the wrong
|
|
1071
|
+
_shape_ rather than merely a wider answer.
|
|
1072
|
+
|
|
1073
|
+
**Behaviour change worth knowing about:** the timeline's default window is now a
|
|
1074
|
+
real cap. `{ options: { $top: 100 } }` nested the limit under a key that is not a
|
|
1075
|
+
`QueryParams` field and that no adapter in this repo reads (`convertQueryParams`
|
|
1076
|
+
maps `params.$top`), so the intended window never reached the wire and a timeline
|
|
1077
|
+
over a large object fetched whatever the server chose to return. It is now sent as
|
|
1078
|
+
`$top`, and authorable via `limit` or a view's `pagination.pageSize`.
|
|
1079
|
+
|
|
1080
|
+
`@object-ui/core` gains `convertSortToQueryParams`, the sort→`$orderby` lowering
|
|
1081
|
+
the three sibling blocks each inline privately. It is shared rather than copied
|
|
1082
|
+
twice more, and is slightly more faithful to the declared contract than those
|
|
1083
|
+
copies: a sort entry that omits `order` means ascending instead of being dropped
|
|
1084
|
+
(the string spelling `"amount"` already meant ascending in the same copies), and
|
|
1085
|
+
nothing orderable yields `undefined` rather than a truthy empty `{}`. Migrating
|
|
1086
|
+
the three existing copies onto it is objectstack#7148 and is not done here.
|
|
1087
|
+
|
|
1088
|
+
- Updated dependencies [794c497]
|
|
1089
|
+
- Updated dependencies [993336f]
|
|
1090
|
+
- Updated dependencies [f0a625a]
|
|
1091
|
+
- Updated dependencies [b5980f4]
|
|
1092
|
+
- Updated dependencies [8aad9fd]
|
|
1093
|
+
- Updated dependencies [6719877]
|
|
1094
|
+
- Updated dependencies [56ff091]
|
|
1095
|
+
- Updated dependencies [0186cdc]
|
|
1096
|
+
- Updated dependencies [7864f03]
|
|
1097
|
+
- Updated dependencies [ea41a59]
|
|
1098
|
+
- Updated dependencies [0cbdca8]
|
|
1099
|
+
- Updated dependencies [d229dfa]
|
|
1100
|
+
- Updated dependencies [ecae400]
|
|
1101
|
+
- Updated dependencies [4bc6c23]
|
|
1102
|
+
- Updated dependencies [d3e738a]
|
|
1103
|
+
- Updated dependencies [c3b01a7]
|
|
1104
|
+
- Updated dependencies [f5f8744]
|
|
1105
|
+
- Updated dependencies [7ed3360]
|
|
1106
|
+
- Updated dependencies [69becd2]
|
|
1107
|
+
- Updated dependencies [5e52495]
|
|
1108
|
+
- Updated dependencies [0fa5e4d]
|
|
1109
|
+
- Updated dependencies [b750823]
|
|
1110
|
+
- Updated dependencies [5bfaabd]
|
|
1111
|
+
- Updated dependencies [e06810e]
|
|
1112
|
+
- Updated dependencies [ab3ad4f]
|
|
1113
|
+
- Updated dependencies [65bb513]
|
|
1114
|
+
- Updated dependencies [c97a45e]
|
|
1115
|
+
- Updated dependencies [b19162d]
|
|
1116
|
+
- Updated dependencies [c2fd122]
|
|
1117
|
+
- Updated dependencies [1bd6faa]
|
|
1118
|
+
- Updated dependencies [ac2139c]
|
|
1119
|
+
- Updated dependencies [b14ab3a]
|
|
1120
|
+
- Updated dependencies [e24d767]
|
|
1121
|
+
- Updated dependencies [8c60819]
|
|
1122
|
+
- Updated dependencies [aca561a]
|
|
1123
|
+
- Updated dependencies [e64a52e]
|
|
1124
|
+
- Updated dependencies [844d17f]
|
|
1125
|
+
- Updated dependencies [d8a0be4]
|
|
1126
|
+
- Updated dependencies [48132f7]
|
|
1127
|
+
- Updated dependencies [4dcd52a]
|
|
1128
|
+
- Updated dependencies [42ae5c6]
|
|
1129
|
+
- Updated dependencies [0ef9dfd]
|
|
1130
|
+
- Updated dependencies [f4b97c8]
|
|
1131
|
+
- Updated dependencies [1d723e3]
|
|
1132
|
+
- Updated dependencies [0109f54]
|
|
1133
|
+
- Updated dependencies [7e5bb5d]
|
|
1134
|
+
- Updated dependencies [fbc23e0]
|
|
1135
|
+
- Updated dependencies [6d762da]
|
|
1136
|
+
- Updated dependencies [e6fdbdc]
|
|
1137
|
+
- Updated dependencies [54233b1]
|
|
1138
|
+
- Updated dependencies [c2ecbae]
|
|
1139
|
+
- Updated dependencies [f9faa7d]
|
|
1140
|
+
- Updated dependencies [97b63d7]
|
|
1141
|
+
- Updated dependencies [6bb454a]
|
|
1142
|
+
- Updated dependencies [11c1e71]
|
|
1143
|
+
- Updated dependencies [523be48]
|
|
1144
|
+
- Updated dependencies [7e2b7e9]
|
|
1145
|
+
- Updated dependencies [33526fd]
|
|
1146
|
+
- Updated dependencies [32413ec]
|
|
1147
|
+
- Updated dependencies [c1e1e6b]
|
|
1148
|
+
- @object-ui/components@17.4.0
|
|
1149
|
+
- @object-ui/react@17.4.0
|
|
1150
|
+
- @object-ui/core@17.4.0
|
|
1151
|
+
- @object-ui/fields@17.4.0
|
|
1152
|
+
- @object-ui/i18n@17.4.0
|
|
1153
|
+
- @object-ui/types@17.4.0
|
|
1154
|
+
- @object-ui/permissions@17.4.0
|
|
1155
|
+
|
|
3
1156
|
## 17.3.0
|
|
4
1157
|
|
|
5
1158
|
### Minor Changes
|
|
@@ -1268,6 +2421,7 @@ undefined` — swallowing the very errors the command exists to print. Now reads
|
|
|
1268
2421
|
objects+fields were editable in Studio; this reworks both surfaces.
|
|
1269
2422
|
|
|
1270
2423
|
**Setup (assign + read-only):**
|
|
2424
|
+
|
|
1271
2425
|
- The six facets (`object_permissions`, `field_permissions`, `system_permissions`,
|
|
1272
2426
|
`row_level_security`, `tab_permissions`, `admin_scope`) now render read-only on
|
|
1273
2427
|
the `sys_permission_set` record page as a compact summary (counts, or capability
|
|
@@ -1282,6 +2436,7 @@ undefined` — swallowing the very errors the command exists to print. Now reads
|
|
|
1282
2436
|
|
|
1283
2437
|
**Studio (design every facet):** the permission matrix editor gains structured
|
|
1284
2438
|
editors for the facets that were JSON-only —
|
|
2439
|
+
|
|
1285
2440
|
- **System Capabilities**: a multi-select over the live `sys_capability` registry
|
|
1286
2441
|
(scope-grouped, labelled chips).
|
|
1287
2442
|
- **Row-Level Security**: per-policy rows (object · operation · enabled) with CEL
|
|
@@ -2459,6 +3614,7 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2459
3614
|
### Patch Changes
|
|
2460
3615
|
|
|
2461
3616
|
- 89ae109: Fix click navigation and required-FK form rendering
|
|
3617
|
+
|
|
2462
3618
|
- **plugin-grid**: ObjectGrid's `getSelectFields()` now always includes `id` in
|
|
2463
3619
|
the SELECT projection. Previously, when a view configured `columns` without
|
|
2464
3620
|
`id`, the SQL driver stripped it from results, and row-click handlers silently
|
|
@@ -2521,6 +3677,7 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2521
3677
|
Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined `require("react")` / `require("react-dom")` calls that cause `Calling \`require\` for "react" in an environment that doesn't expose the \`require\` function` runtime errors when consumer apps re-bundle the published dist.
|
|
2522
3678
|
|
|
2523
3679
|
Specifically fixes:
|
|
3680
|
+
|
|
2524
3681
|
- `@object-ui/plugin-dashboard` no longer inlines `react-grid-layout` (and its transitive `react-draggable` / `react-resizable` CJS bundles). `react-grid-layout` is now declared as a peer dependency so consumers install a single ESM-friendly copy.
|
|
2525
3682
|
- `@object-ui/components`, `@object-ui/plugin-calendar`, `@object-ui/plugin-charts`, `@object-ui/plugin-designer` no longer inline `react-i18next` / `i18next` / `use-sync-external-store` CJS shims.
|
|
2526
3683
|
- All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.
|
|
@@ -2598,11 +3755,13 @@ parentData, details)` — emits a parent `update` op (index 0) then diffs each
|
|
|
2598
3755
|
`FormSchema.mobileStickyActions` (new) is the lower-level escape hatch — applied automatically when `mobile.stickyActions` is set on `ObjectFormSchema`.
|
|
2599
3756
|
|
|
2600
3757
|
**`@object-ui/plugin-form`** — `ObjectForm` now:
|
|
3758
|
+
|
|
2601
3759
|
- propagates `mobile.fullscreenLongText` to every textarea/markdown/html field as `mobile_fullscreen: true`,
|
|
2602
3760
|
- sets `mobileStickyActions` on the inner form schema and adds `pb-20` padding so content isn't covered by the fixed bar,
|
|
2603
3761
|
- when `mobile.stepper === true` (or `'auto'` + `useIsMobile()` + > `stepperMinFields` fields), routes the flat field list through the existing `WizardForm` with synthetic single-field "steps" — keeping per-step validation and the existing `Next`/`Back`/`Submit` flow.
|
|
2604
3762
|
|
|
2605
3763
|
**`@object-ui/components`** — the registered `form` renderer adds:
|
|
3764
|
+
|
|
2606
3765
|
- a `mobileStickyActions` opt-in that turns the action row into a `position: sticky; bottom: 0` bar on small viewports, and
|
|
2607
3766
|
- an inline `FullscreenTextarea` wrapper used when no field-package widget is registered, providing the same expand-button + edit-dialog UX so the feature works even in lighter setups.
|
|
2608
3767
|
|