@object-ui/data-objectstack 17.6.0 → 18.0.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 +2460 -0
- package/README.md +727 -11
- package/dist/index.cjs +2213 -405
- package/dist/index.d.cts +1971 -211
- package/dist/index.d.ts +1971 -211
- package/dist/index.js +2199 -406
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,2465 @@
|
|
|
1
1
|
# @object-ui/data-objectstack
|
|
2
2
|
|
|
3
|
+
## 18.0.0
|
|
4
|
+
|
|
5
|
+
### Dependency floors
|
|
6
|
+
|
|
7
|
+
Computed from `.objectstack-sha` `06cab4849a8a`: the earliest published `@objectstack/spec` that contains it is `18.0.0`.
|
|
8
|
+
|
|
9
|
+
- `@objectstack/client` (`dependencies`): `^18.0.0-next.1` → `^18.0.0`
|
|
10
|
+
- `@objectstack/spec` (`dependencies`): `^18.0.0-next.1` → `^18.0.0`
|
|
11
|
+
|
|
12
|
+
### Major Changes
|
|
13
|
+
|
|
14
|
+
- 6f3299f: objectui moves to major 18, following `@objectstack/*` from 17 to 18 (objectui#12334). objectui's major is not a count of its own breaking changes: it tracks the major of the `@objectstack/spec` its packages depend on, so that the same major means compatible across the two projects. These packages already declare `@objectstack/*` on the 18 line (objectui#12030), so every package in the fixed release group crosses to 18 together. It is the one `major` that rule admits: objectui's own breaking changes ship as `minor`, described in their entries.
|
|
15
|
+
|
|
16
|
+
The 18 line is released from Changesets pre mode on the `next` tag (`.changeset/pre.json`), as `@objectstack/*` 18 is: these releases are `18.0.0-next.N` on the npm `next` dist-tag, and they do not move `latest`. The first `18.0.0` on `latest` comes when `@objectstack` 18 is generally available and this repository leaves pre mode.
|
|
17
|
+
|
|
18
|
+
What a consumer must do to take it is in the `@objectstack/*` 18 entry of this release (objectui#12030): move its own `@objectstack/*` packages to the 18 line at the same time.
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- da35453: Objects with a picklist-bound field save again from the OWD overview, the Setup fields and objects pages, the metadata-admin embedded-item editor, and `MetadataService.saveFields` (objectui#11692).
|
|
23
|
+
|
|
24
|
+
The runtime serves a field that names a shared picklist (`picklist: 'NAME'`) with the options it resolved from the list. The authoring door refuses `options` beside `picklist`, with `422 INVALID_METADATA` at `fields.FIELD.options`, and the refusal covers the whole object. objectui#10202 fixed the two designers that host the select-field editor. These writers also seed their save from a served object read, so they sent the resolved options back and were refused, whatever had been edited:
|
|
25
|
+
|
|
26
|
+
- **Studio → Access → OWD overview.** Changing an object's sharing model and saving was refused.
|
|
27
|
+
- **The Setup fields page** (`MetadataFieldsPage`). Editing any field of such an object was refused.
|
|
28
|
+
- **The Setup objects page** (`MetadataObjectsPage`). Relabelling such an object was refused.
|
|
29
|
+
- **The metadata-admin embedded-item editor** (`EmbeddedItemEditor`, opened from an object's fields, indexes or validations). Saving any item of such an object was refused.
|
|
30
|
+
- **`MetadataService.saveFields`.** A field list built from the served object was refused.
|
|
31
|
+
|
|
32
|
+
Each now leaves `options` out of every field that names a picklist, before the PUT. A field without `picklist` keeps its inline `options` unchanged, and no other key is touched.
|
|
33
|
+
|
|
34
|
+
`dropServedPicklistOptions` is now exported from `@object-ui/data-objectstack`. It is the served-to-authored conversion these writers apply, and it moves here from `@object-ui/app-shell` (it was not exported there), because `@object-ui/plugin-designer` does not depend on `app-shell`. It is for code that builds an object PUT from a served read. `MetadataClient.save` does not apply it, and neither do `MetadataService.saveObject` and `MetadataService.saveMetadataItem`, which read nothing: a body that pairs `picklist` with `options` without a served read behind it is still refused by the server, with its prescription.
|
|
35
|
+
- b9814b3: Studio's first draft save after opening an item no longer replaces a draft that was saved elsewhere in the meantime (objectui#11773).
|
|
36
|
+
|
|
37
|
+
Until now an editor's draft saves were pinned only from its second save on: the `/meta` draft read served no version, so two editors who each opened an item and saved it once overwrote each other's draft in silence. The `@objectstack/*` 18 draft read serves the version, and each editor's load now pins its first save to what it read:
|
|
38
|
+
|
|
39
|
+
- an item with a pending draft is saved with `If-Match` and that draft's version;
|
|
40
|
+
- an item with no pending draft is saved with `If-None-Match: *`, so a draft another editor created in the meantime is refused, not overwritten.
|
|
41
|
+
|
|
42
|
+
The editors read the draft at the package their saves write to (`?package=`), since a read's version describes the row a save to the same address compares against. This covers the metadata editor, the permission matrix's package door, the Studio pillars (objects, flows, interfaces and app navigation), the list-view panel and the hooks panel.
|
|
43
|
+
|
|
44
|
+
A create sends `If-None-Match: *`, so creating an item under a name that already holds a draft is refused instead of writing the new skeleton over it.
|
|
45
|
+
|
|
46
|
+
In the conflict dialog, **Overwrite** now re-sends the draft pinned to the version the refusal names (`currentVersion`), so a save that lands while the dialog is open is refused and asked about again rather than overwritten. A server whose refusal names no `currentVersion` is overwritten unpinned, as before.
|
|
47
|
+
|
|
48
|
+
`@object-ui/data-objectstack`: `MetadataClientSaveOptions` gains `ifNoneMatch?: '*'`, sent as `If-None-Match: *`. It never travels with `ifMatch`: `MetadataClient.save` throws before sending a request that sets both. The `ifMatch` docblock now names the token's real sources (the item read's `version`, a save receipt's `version`, a conflict's `currentVersion`) instead of "the `checksum` returned by the last read".
|
|
49
|
+
- c5406c2: Depend on the `@objectstack/*` 18 prerelease line (objectui#12030).
|
|
50
|
+
|
|
51
|
+
Every `@objectstack/*` range these packages declare in `dependencies` or `peerDependencies` moves from `^17.x` to `^18.0.0-next.0`, the `next` prerelease of `@objectstack/*` 18 (raised to `^18.0.0-next.1` by objectui#12370 before release).
|
|
52
|
+
|
|
53
|
+
**Breaking for consumers:** an install that resolves `@objectstack/spec` (or `@objectstack/client`, `@objectstack/formula`, `@objectstack/lint`) 17.x no longer satisfies these ranges. A consumer must move its own `@objectstack/*` packages to the 18 line, starting with `18.0.0-next.1` (objectui#12370), at the same time as it takes this release.
|
|
54
|
+
|
|
55
|
+
**objectui's own surface moves with it:**
|
|
56
|
+
|
|
57
|
+
- `element:record_picker`'s registration stops publishing the `object`, `filter`, `sort` and `limit` inputs, which spec 18 retires; a JSX page that writes one now gets `unknown-prop`.
|
|
58
|
+
- The `CapabilityMultiSelectField` widget's curated capability set (`@object-ui/fields`) follows spec 18: `manage_mailboxes` in, `manage_org_presentation` out.
|
|
59
|
+
- `element:number` and `element:repeater` nodes that bind no object are accepted, as spec 18's row and `PageComponentSchema` accept them; the two `ELEMENT_*_OBJECT_REQUIRED` refusals are gone.
|
|
60
|
+
|
|
61
|
+
**Retired with the spec:** `@objectstack/spec` 18 tombstones `object-grid`'s `defaultFilters` and refuses it by name, and `ObjectGrid` and `ObjectView` stop reading it (`table.defaultFilters` on an `object-view` included). A view that still carries it renders unfiltered: rename the key to `filter`, whose value is the same rule array.
|
|
62
|
+
- 6757a93: Show a write answer's advisory `warnings` to the user after save (objectui#12107).
|
|
63
|
+
|
|
64
|
+
- `@object-ui/data-objectstack`: `WriteWarningEvent` gains an optional `warnings` member, typed by the spec's `ValidateDataIssue` (the element of `CreateDataResponseSchema` / `UpdateDataResponseSchema`'s `warnings`). A single-record `create` / `update` parses the answer's `warnings` through `ValidateDataIssueSchema` and emits the write-warning event when either `droppedFields` or `warnings` is non-empty; `droppedFields` stays required and is `[]` when the event exists only for warnings. An answer without the key emits nothing new, including from a runtime that predates it.
|
|
65
|
+
- `@object-ui/app-shell`: the write-warning toast shows each distinct warning message as the server wrote it, in its own toast titled by the new `detail.writeWarningsTitle` key. The stripped-fields toast is unchanged.
|
|
66
|
+
- `@object-ui/i18n`: adds `detail.writeWarningsTitle` to all ten packs.
|
|
67
|
+
- 43e1505: **A dashboard or a list view the server refuses you, opened by its URL, now says it was refused** (objectui#12112, the console half of objectstack#22611's ruling A).
|
|
68
|
+
|
|
69
|
+
Since objectstack#22731 the server applies a dashboard's and a list view's `requiredPermissions`: its dashboard and view lists carry only what you may open, and a by-name read of one you may not answers `403 PERMISSION_DENIED`. The console read only the lists, so a refused dashboard's URL showed "Dashboard Not Found", and a refused list view's URL silently showed the object's default view instead.
|
|
70
|
+
|
|
71
|
+
- **The dashboard page and the object's list page** ask a missed name by name. A `PERMISSION_DENIED` answer shows "You don't have access" with the server's own sentence, which names the item: `You do not have permission to open the 'NAME' dashboard.` (or `view`). Any other answer keeps today's face: "Dashboard Not Found", or the fall-back to the default view. Nothing is asked in draft preview, or by a host whose data source has no probe. The title reuses existing translations; no translation key is added.
|
|
72
|
+
- **`@object-ui/data-objectstack`: `ObjectStackAdapter.probeMetaItemAccess(type, name)`**, and its result type `MetaItemAccess`. It is `probeAppAccess` for any type the by-name meta route gates per caller, and it also returns the refusal's `message` with a `denied` verdict. `probeAppAccess` now delegates to it and answers as before.
|
|
73
|
+
- An object's own list view, served whole inside the object's metadata to a user who may edit the object, is still listed and opens. The server serves it to that user, and the console adds no rule of its own.
|
|
74
|
+
- dbc8c11: Studio's Automations rail and the open flow's Status pill read the automation engine's registration verdict, which the flow list now serves beside its items (`registrations`, `FlowRegistrationSchema` in `@objectstack/spec`). A flow the engine refused at boot reads "Refused by the engine", titled with the engine's message as sent, instead of "Unpublished draft" or "Enabled"; a flow the engine holds no registration for reads "Not running here". A registered flow, and a flow with no verdict, read as before, and the flow's stored `status` is never rewritten. `MetadataClient.listResponse` returns a list read with what the server serves beside the items; `list` is unchanged.
|
|
75
|
+
|
|
76
|
+
**Clause-②: yes (widening).** `@object-ui/data-objectstack` gains one method, `MetadataClient.listResponse`, and one exported type, `MetadataListAnswer` (`items` plus the optional `registrations` of the spec's `GetMetaItemsResponse`). No export, prop or member is removed or changes type; `@object-ui/app-shell`'s exports are unchanged.
|
|
77
|
+
- ff9df88: Depend on `@objectstack/*` `18.0.0-next.1` (objectui#12370).
|
|
78
|
+
|
|
79
|
+
Every `@objectstack/*` range these packages declare moves from `^18.0.0-next.0` to `^18.0.0-next.1`; the console and the runner bundle `18.0.0-next.1`. A consumer moves its own `@objectstack/*` packages to `18.0.0-next.1` with this release.
|
|
80
|
+
|
|
81
|
+
**Accept sets that narrow with it.** `@object-ui/types` validates page action rows and view data bindings through the spec's own rows, so its faces (`safeValidateSchema` and `StrictAnyComponentSchema`) now refuse what `18.0.0-next.1` refuses. Each was accepted on `18.0.0-next.0`:
|
|
82
|
+
|
|
83
|
+
- `onSuccess` on an `action:button` / `action:icon` row, or an `action:group` / `action:menu` member, whose executor is the `url`, `navigation`, `modal`, `flow` or `form` built-in, or that has no executor at all. Write the hop on an `api` or `script` action (or a host handler name), or drop it.
|
|
84
|
+
- `onSuccess` beside `operation: 'update'`, and an `onSuccess` block carrying a key outside `{ navigate, openIn }`.
|
|
85
|
+
- `actionType: 'navigation'` on a row (and `type: 'navigation'` on a container member): write `'url'`.
|
|
86
|
+
- A `url` executor's `target` that is not openable, judged on the executor the runner runs, including a row whose `name` selects it.
|
|
87
|
+
- A view `data` binding with `provider: 'api'`: bind the object or static rows; an API call is the page element's binding.
|
|
88
|
+
- 919d5cd: "Publish all" publishes each language of a package-less email template by its own bundle member (objectui#12402, the publish half of objectui#12271).
|
|
89
|
+
|
|
90
|
+
Since `@objectstack/spec` 18.0.0-next.1 each `GET /meta/_drafts` row carries `member` for a type whose identity is a pair (today only `email_template`, `(name, locale)`), and the by-name publish door reads it back as `?member=`. A publish that names no member promotes the canonical member's draft, so two languages of one template drafted outside a package were published as the canonical one twice.
|
|
91
|
+
|
|
92
|
+
- **`MetadataClient.publishDraft(type, name, options?)`** takes an optional `{ member }` and sends `?member=` when it is set. Without one the parameter is omitted, never sent empty. This widens the method's accepted arguments; a two-argument call sends exactly the request it sent before.
|
|
93
|
+
- **`usePublishAllDrafts`** (Home's "Publish all" and the draft-preview bar) keeps each draft row's `member` and passes it on the package-less path. Rows without a member send no selector.
|
|
94
|
+
- **Unchanged:** package-bound drafts still publish through `publishPackageDrafts(packageId)`.
|
|
95
|
+
- 431cece: `MetadataDraftHeader`, the row type `MetadataClient.listDrafts()` declares, now declares `member` (objectui#12413).
|
|
96
|
+
|
|
97
|
+
`@objectstack/spec` declares `ListDraftsResponseSchema.drafts[].member`, an optional string, and `GET /meta/_drafts` serves it on every draft of a type whose identity is a pair (today `email_template`, `(name, locale)`). The published row type had no `member`, so a reader had to cast the row to reach it.
|
|
98
|
+
|
|
99
|
+
- **`@object-ui/data-objectstack`, additive widening.** `MetadataDraftHeader` is now derived from the spec row by `Pick` over `type`, `name`, `member`, `packageId`, `updatedAt` and `updatedBy`. The five keys it declared before keep their exact types; `member?: string` is the one key added. Nothing is removed or narrowed. `MetadataSaveAdvisoryEvent` gains an optional `member`, set by `publishDraft()` when the publish named one, so an advisory raised on one language's publish is attributed to that member. Every other door emits the event as before, without the key.
|
|
100
|
+
- **`@object-ui/app-shell`, no behaviour change.** The publish-all hook reads each draft row's `member` through the declared type instead of an `any[]` cast. The URLs it sends are unchanged.
|
|
101
|
+
- caac3b2: A list's sort, column layout, Filter panel and tab order are now each user's own, and follow them to another browser (objectui#12418).
|
|
102
|
+
|
|
103
|
+
**What changes for users.** Sorting a list, resizing or moving its columns, and dragging the tabs in Manage Views is now personal: it no longer changes the view for everyone. Before this change, a header sort or a column drag was saved into the shared view, and a Manage Views drag saved a new order onto every saved view, so one user's click re-sorted or reordered the list for every other user. Under a walled tenancy posture (`group` or `isolated`), where only the platform operator may write environment metadata, the same click ended in an error. The view's own sort, columns, filter and tab order stay the default for every user who has not changed them, and the view editor (the view settings panel and Studio) is still how an author changes them for everyone. Density and hidden fields are not part of this change: they are still saved to the view.
|
|
104
|
+
|
|
105
|
+
- **Where it is kept.** One `sys_user_preference` row per user and object, key `ui.list.<object>`, holding `{ viewOrder, perView: { <view id>: { sort, filters, columnState } } }`. The console reads every such row in its one preference read at load (objectui#12187), so this adds no request. localStorage (`list-state:<user>:<object>`) is the write-through cache, and the only store when no preference backend is attached. The first read of an object with nothing cached takes that browser's older tab order (`viewOrder:<object>`) and Filter panel conditions (`list-filters:…`). The search term stays where it was.
|
|
106
|
+
- **Opening a list.** A list now opens on the user's stored sort and Filter panel conditions, and the page waits until the preference read has answered (2.5 s at most) before drawing it. A link that carries list state still wins over the stored state, as before (objectui#11860).
|
|
107
|
+
- **Resetting.** The Filter panel's "Clear all" clears the user's stored filters for the view. A new "Reset view" entry in the page header's ⋯ menu, beside the favorites entry, appears when the user has stored anything for the active view. It drops that view's sort, filters and column layout and opens the list as the view declares it. An administrator can delete a user's row under Setup → User Preferences.
|
|
108
|
+
|
|
109
|
+
`@object-ui/data-objectstack` adds `createObjectStackUserPreferenceFamily` (with the `UserPreferenceFamily` and `ObjectStackUserPreferenceFamilyOptions` types and the `PREFERENCE_FAMILY_ROW_LIMIT` constant). It loads every row whose key starts with a prefix, through the same shared read as the per-key adapters. A read that includes a prefix asks for its keys or that prefix, with a `$top` of the key count plus 200 rows per prefix. When an answer fills that `$top`, a key whose row is missing from it reads its own row. `createObjectStackUserStateAdapter` keeps its signature and its behaviour.
|
|
110
|
+
|
|
111
|
+
`@object-ui/i18n` adds `console.objectView.resetView` and `console.objectView.resetViewTitle` in all ten packs.
|
|
112
|
+
- 3eb3ec1: The console's list toolbar no longer has a write into the shared view, and the data-source method that write used is retired (objectui#12504).
|
|
113
|
+
|
|
114
|
+
**BREAKING:** `DataSource.updateViewConfig` is removed from `@object-ui/types`, and `ObjectStackAdapter.updateViewConfig` is removed from `@object-ui/data-objectstack`. The capability gate that only that method read goes with it: `ObjectStackAdapter.setSystemCapabilities`, the `systemCapabilities` option of the adapter constructor and of `createObjectStackAdapter`, `VIEW_CONFIG_CAPABILITY`, `ViewConfigPermissionDeniedError` and `isViewConfigPermissionDeniedError`. A class that implements `DataSource` and keeps an `updateViewConfig` method still compiles, but nothing calls the method any more; an object literal annotated as `DataSource` that carries it is refused as an unknown property. Code that calls `updateViewConfig` through a `DataSource` or an `ObjectStackAdapter`, or imports one of the gate names, no longer compiles. To change a view for everyone, save the view: `createView` / `updateView` on the data source, or the console's view editor, which writes through the runtime metadata seam.
|
|
115
|
+
|
|
116
|
+
Since objectui#12418 and objectui#12474, the toolbar's sort, column layout, density and Hide Fields are each user's own list state, so the toolbar write had no caller. The view editor's explicit save is now the one write path to the shared view, and it is unchanged.
|
|
117
|
+
|
|
118
|
+
Personalization rows that earlier consoles wrote into `/meta/view` stay readable: the overlay marker, `narrowPersonalizationOverlay` and `listViews()` exclusion still apply to them. Nothing new writes such a row.
|
|
119
|
+
|
|
120
|
+
`@object-ui/i18n` drops `console.objectView.viewConfigPermissionDenied` from all ten packs. It was the toast the retired write raised when the gate refused it. The other packages change only in comments and tests that named the retired path.
|
|
121
|
+
- 3bcf314: Studio marks the settings the AI copilot drafted: a Draft chip and a Source line ("Added as a draft by the AI copilot 2 min ago") on a field or a list-view setting the copilot changed, read from the platform's per-key draft provenance (objectui#12725, item 3; objectstack#24298 and #24395).
|
|
122
|
+
|
|
123
|
+
- **`@object-ui/data-objectstack` reads and writes the provenance.** The strict draft read (`getDraft`) serves `draftChanges` beside `item`: one `{ path, by, actor, at }` per changed key path. New exports beside `extractDraftBody`: `readDraftChanges(resp)` (the envelope's entries, or `null` when the read recorded none, never `[]`), `governingDraftChange(changes, path)` (the entry that answers for a key: the longest prefix), `draftChangesUnder(changes, path)`, and the `DraftChange` type, derived from `@objectstack/spec`'s `GetMetaItemResponse`. `MetadataClientSaveOptions` gains `surface?: 'studio' | 'api'`, sent once as `?surface=`, derived from the spec's `MetadataAuthoringSurface` minus `copilot` (the HTTP doors refuse it). `MetadataDeleteOptions` no longer inherits it: the delete door reads no such parameter.
|
|
124
|
+
- **Studio's own draft saves declare `surface: 'studio'`,** so a person's edit there is recorded as `studio`, not `api`.
|
|
125
|
+
- **The marks.** The Data pillar's field properties show the chip and the Source line when the field's entry, or an entry under it, is the copilot's. An `object` leaf's list-view properties show the chip on each setting the copilot changed (card fields, title, click to open, group by, columns, filter, sort), mark the elements it added to an array setting by comparing the draft's array with the published one, and end with a Source line naming them. A `studio` or `api` entry draws nothing, and so does a draft whose read recorded no provenance. To compare arrays, the list-view panel reads the published layer beside a draft when, and only when, the draft carries a copilot entry.
|
|
126
|
+
|
|
127
|
+
The new copy lives in the metadata-admin designer's own string tables (en and zh).
|
|
128
|
+
- 6058bf8: Studio's Changes drawer can now fill a new field's default into the records an object already holds, when you publish (objectui#12929).
|
|
129
|
+
|
|
130
|
+
- **The choice.** In the drawer, under a field that a pending draft adds to an already-published object, an unchecked "Backfill default (N existing records)" choice appears when the field declares a literal `defaultValue`. N is the object's record count, read as you. If the count read gives no total, the choice shows no number. A field whose default is a runtime token (`NOW()`, `current_user`) or an expression, a field with no default, and the fields of an object that has never been published get no choice. The publish door refuses those with a 400, and there are no existing records to fill.
|
|
131
|
+
- **The publish.** Publish all sends the checked fields as the protocol's opt-in `backfill` (objectstack#24253). The server writes each field's default into the existing records that have no value, as you, with permissions and record hooks applied. The publish toast then shows the rows the server says it wrote for each field. If a field stopped short, the toast is a warning that gives the server's reason, and the publish still stands. If nothing is checked, the publish request is `{}`, the same as before, and no existing record is written.
|
|
132
|
+
- **`MetadataClient.publishPackageDrafts(packageId, options?)`** (`@object-ui/data-objectstack`) takes `options.backfill`, an array of `{ object, field }`. Its type is picked from `@objectstack/client`'s `packages.publishDrafts`. The method sends `backfill` only when it is given. The receipt is `backfillApplied` on the result. A call without options sends the `{}` body it always sent.
|
|
133
|
+
- **New strings:** `preview.changes.backfillOption` (a count family) and `preview.changes.backfillOptionNoCount` in all ten `@object-ui/i18n` packs. The Studio table gains `engine.studio.backfillWritten` and `engine.studio.backfillStopped` (en and zh).
|
|
134
|
+
- 088e0f7: A record-form field can name one item of a metadata registry by its machine name: a `text` field that declares `widget: 'position-ref'` lists the position registry, and one that declares `widget: 'permission-ref'` lists the permission-set registry. It stores the name only (objectui#7611, ruling B2 on objectstack#22865). This is the mechanism the metadata forms' `ref-multi:permission` picker brought in (objectui#12126), now reachable from an ObjectQL object field. A grant table's name field (`sys_user_position`'s position, `sys_user_permission_set.permission_set`) can now offer the declared items instead of a box for a typed name.
|
|
135
|
+
|
|
136
|
+
- **The widget (`@object-ui/fields`).** `field:position-ref` and `field:permission-ref`, one key per registry type, with the type in the key, as `object-ref` carries `object`. Like every `widget` value, the name is looked up as `field:<widget>`. Each item reads as its label beside its name, and a pick writes the name: no label, no id. A stored name the loaded registry does not list stays on screen as its own option, flagged *not found*. The four catalog arms follow the metadata forms' `ref:dataset` / `ref-multi:<type>` pickers. With no data source that can list a registry, the field is a text box for the typed name. While the list loads, the stored name sits in a disabled box. When the load fails, a notice names the failure and its cause, and the text box stays enabled. A loaded but empty registry gives the same text box. Neither key is a declarable field type: both are widget hints, like `object-ref`.
|
|
137
|
+
- **The catalog (`@object-ui/types`, `@object-ui/data-objectstack`).** `DataSource` gains an optional `listMetadataItems(type)`, answering `{ name, label }` headers for one registry type. An empty array is authoritative ("this registry has no item of this type"), so a failed load must reject, never resolve `[]`. `ObjectStackAdapter` implements it over `GET /api/v1/meta/:type` (the client's `meta.getItems`). It rejects with the transport's error, and refuses an answer that carries no `items` list. It is uncached, so an item authored in Studio shows on the next form.
|
|
138
|
+
- **Locale keys (`@object-ui/i18n`).** `fields.registryRef.loadFailed` and `fields.registryRef.notFound`, in all ten packs.
|
|
139
|
+
- **Wiring.** The form renderer threads `dataSource` to both keys, as it does for the other widget-hint pickers (`@object-ui/components`). The inline-grid exclusion set and the action-param value-shape table list both keys as widget-hint-only (`@object-ui/fields`, `@object-ui/app-shell`). The CLI's generated `KNOWN_SCHEMA_TYPES` gains `field:position-ref` and `field:permission-ref` (`@object-ui/cli`). A comment in `@object-ui/core` stops counting the picker names.
|
|
140
|
+
|
|
141
|
+
**Clause-②: yes (widening)**: one optional `DataSource` member, two registered `field:` keys (the `RegisteredFieldWidgetType` union and `FORM_FIELD_TYPES` gain two members), and two locale keys. Nothing narrows: a field without the hint renders exactly as before.
|
|
142
|
+
- 78b07f4: The object-page special cases for the retired permission-set catalog object are removed (objectui#7611, after objectstack's ADR-0131 stage 8 retired `sys_position`, `sys_permission_set`, `sys_position_permission_set` and `sys_capability`). A permission set is edited on its Setup metadata page, which reads the registry; none of the removed code ran anywhere else.
|
|
143
|
+
|
|
144
|
+
**Breaking (narrowing; scored `minor` per the fixed group's version policy).** Each removal below shrinks a published face:
|
|
145
|
+
|
|
146
|
+
- **`@object-ui/core`: `recordDelete.confirmText`.** FROM `recordDelete.confirmText({ objectName, t }, record)` TO `recordDelete.confirmText({ t })`. A package-owned `sys_permission_set` row no longer gets the reset question or the "reset" toast: every record gets the delete question and the delete toast. Fix: drop `objectName` and the second argument.
|
|
147
|
+
- **`@object-ui/i18n`: pack keys.** FROM `perm.facet.*` (`none`, `more`, `objects`, `fields`, `rls`, `tabs` with their plural slots, `adminScope`, `designInStudio`, `designInStudioHint`) and `objectActions.resetPackageSetConfirm` / `objectActions.resetPackageSetSuccess` in all ten packs TO absent. Fix: a consumer that still asks for one keeps its own copy (inline `defaultValue` or its own bundle).
|
|
148
|
+
- **`@object-ui/plugin-detail`: the `field:permission-facet-link` registry key.** FROM a registered field widget (the read-only facet summary with a "Design in Studio" link, also special-cased in the detail section and inline edit) TO unregistered. A field still carrying `widget: 'permission-facet-link'` renders with its declared type, like any unregistered widget. Fix: drop the `widget` hint, or register an own widget under that key.
|
|
149
|
+
- **`@object-ui/cli`: `objectui check`'s known schema types.** FROM `field:permission-facet-link` known TO unknown, so `objectui check` reports a node of that type. Fix: as above.
|
|
150
|
+
|
|
151
|
+
**Behaviour changes, not narrowings:**
|
|
152
|
+
|
|
153
|
+
- **`@object-ui/data-objectstack`:** `getObjectSchema` no longer stamps `widget: 'permission-facet-link'` onto the six `sys_permission_set` facet fields (that object is no longer served).
|
|
154
|
+
- **`@object-ui/app-shell`:** the `sys_permission_set` record page no longer appends the assigned-users panel. The panel itself, `AssignedUsersSection`, stays on the permission-set metadata page.
|
|
155
|
+
- **`@object-ui/app-shell`: a permission set created from the Setup catalog is created live.** "New" on the permission-set page used to save a draft, while the set's editor edits the live set and reads no draft: the editor opened an empty set without the label, its Save wrote the grants live, and the draft stayed pending, so publishing it from Home replaced the grants with the empty create body. Outside a package binding the create now writes live (with the same first-write pin); under `?package=` it stays a package draft.
|
|
156
|
+
|
|
157
|
+
**Widenings:**
|
|
158
|
+
|
|
159
|
+
- **`@object-ui/core`:** the data source `recordDelete.run` is injected with needs only `delete` (it no longer calls `findOne`).
|
|
160
|
+
- **`@object-ui/app-shell`:** the metadata-resource registry entry gains an optional `createLive`, which a type sets when its edit page writes the live item.
|
|
161
|
+
|
|
162
|
+
Internal, not reachable from a published entry: the `PermissionFacetLink` component (never exported from `@object-ui/plugin-detail`), `RecordPermissionAssignmentsRenderer` (never exported from `@object-ui/app-shell`), and `ObjectStackAdapter`'s private `applyFieldWidgetOverrides`.
|
|
163
|
+
|
|
164
|
+
### Patch Changes
|
|
165
|
+
|
|
166
|
+
- 4590363: A record open no longer sends the same record read twice at once, and asks each explain question once, with concurrent callers sharing the request (objectui#11699).
|
|
167
|
+
|
|
168
|
+
A record page open sent `GET /api/v1/data/OBJ/ID` twice at the same moment and `POST /api/v1/security/explain` four times: the `update` and the `delete` question, each asked twice. Both reads came from the details block, whose load effect runs again while its first read is still on the wire. Each question was asked by the page header and by the details block, which mounts while the header's answer is still pending. The record page's own `$expand` read of the record, which it sends again when the object definition changes identity, is not changed here.
|
|
169
|
+
|
|
170
|
+
- **`ObjectStackAdapter.findOne` shares an in-flight read**, the way `find` already did. Calls for the same resource, id and params that arrive while a read is pending get that read's answer. The entry is dropped when the read settles, so a later call reads again: this is not a response cache. A failed read reaches every caller that shared it and is not remembered. A write through the adapter (`create`, `update`, `delete`, the bulk and batch writes) drops the pending `findOne` reads of the resource it wrote, so a record read asked after a save never gets an answer sent before it.
|
|
171
|
+
- **The record-level edit and delete probe (`useRecordEditable`) shares an unanswered question.** Mounts asking the same question (same principal, object, record and operation) while it is pending wait on one request. A question whose record changes while it is pending is not shared, so the re-ask is a fresh request. No pending question is shared across a change of the signed-in principal.
|
|
172
|
+
|
|
173
|
+
Nothing is added to either package entry: no export, prop, type member or language-pack key. `findOne` keeps its signature.
|
|
174
|
+
- 4a9fe31: Studio shows a refused save as a sentence the author can act on, with the technical detail behind a "Details" disclosure (objectui#11785).
|
|
175
|
+
|
|
176
|
+
- **Studio's save strips.** The Data, Automations and Interfaces pages used to print the refusal verbatim: the write guard's developer prose, or the server's raw issue paths such as `nodes.2.config.title — …`. Each strip now shows one sentence that names the input the refusal is about, in the terms the editor shows. On the Data page that is the field's label. On the Automations page it is the step's label and the inspector input, so a refusal at `nodes.2.config.title` reads "Check Title on the step “Notify approver” — …". On the Interfaces page it is the navigation item and its input. A "Show me" button opens that field, step or navigation item. The raw text stays under a closed "Details" disclosure, unchanged. A path Studio cannot place on the open page keeps its line there, and the sentence says the draft was refused.
|
|
177
|
+
- **A Picklist with no options.** Switching a field to Picklist before it has an option now says "Changes not saved: the field “Status” needs at least one option…", instead of the guard's message.
|
|
178
|
+
- **The object write guard's message names no code.** `assertObjectMetadataWritable` no longer prints the door's class name, a package name or tracker ids. Its message is the same at every door, so the other surfaces that show it as their banner read the field, its type and the remedy. Its signature, error type and exports are unchanged.
|
|
179
|
+
|
|
180
|
+
`formatMetadataError` is unchanged, so every other surface that calls it prints what it printed before. Nothing is added to either package entry: no export, prop, type member or language-pack key. The new sentences are rows in the metadata-admin designer's own string tables (en and zh).
|
|
181
|
+
- 6d8bf0c: Opening a Studio package no longer sends the same read several times at once (objectui#11797).
|
|
182
|
+
|
|
183
|
+
Several readers mount together on Studio entry and each one used to send its own request for the same answer. A caller that arrives while an identical request is still pending now waits for that request instead of sending another:
|
|
184
|
+
|
|
185
|
+
- **`@object-ui/app-shell`:** the package list (`GET /api/v1/packages`, read by the Studio package switcher, the read-only gate and the object-name namespace lookup) and the pending-drafts count (`GET /api/v1/meta/_drafts`, read by the Studio top bar and the chat bar).
|
|
186
|
+
- **`@object-ui/data-objectstack`:** `MetadataClient.listTypes`, `list`, `listDrafts`, `get` and `getDraft`. Requests are shared between clients built on the same `fetch`, so the per-component clients the console creates share them too. Each caller, the first one included, still gets an answer object that no other caller holds.
|
|
187
|
+
|
|
188
|
+
This is not a response cache. Once a request settles, the next call goes to the server again. A failed request is shared only by the callers already waiting, and the next call retries. A write the reader can see drops any read still pending, so a read made after the write never gets the answer of a request sent before it. For the metadata client that is any save, publish or reset through a client on the same `fetch`. For the app-shell reads it is a package duplicate, the `objectui:packages-changed` announcement and the publish refresh pulse.
|
|
189
|
+
|
|
190
|
+
No exported function, method signature, option or type changes.
|
|
191
|
+
- 5dff027: Studio stops asking questions whose expected answer is an error, so the browser console no longer fills with red lines that hide real failures (objectui#11799).
|
|
192
|
+
|
|
193
|
+
- **`MetadataClient.getDraft()` reads the drafts ledger first.** It reads `GET /meta/_drafts` through the client's shared read, and sends `GET /meta/:type/:name?state=draft` only when the ledger lists the name. An item with no draft no longer logs a 404. The method still resolves `null` when there is no draft and the draft envelope when there is one. A draft saved a moment ago is found, because a write drops the ledger read that was pending when it landed. A ledger the caller cannot read (403, 501, a network fault) counts as unknown, and the item read is sent as before.
|
|
194
|
+
- **Studio reads an item's published baseline only when it uses it.** The Interfaces pillar's leaves and the Automations pillar's flows read `GET /meta/:type/:name/layers` only for an item with no pending draft, and the Interfaces pillar reads none for an app found only in the drafts ledger. An item that was never saved always has a draft, so it no longer logs a 404 for `/layers`. Opening a leaf or a flow now waits for the ledger before it reads the item, one round trip more than the parallel reads it replaces. The Data pillar still reads `/layers` for an object that was never published: that answer is how it tells it has no records table yet.
|
|
195
|
+
- **The AI usage indicator asks nothing while AI is off.** It reads `GET /ai/usage` only while the agent catalog at its base lists an agent, the signal the console gates every AI entry point on. On an open-edition server, Studio's copilot dock no longer logs a 501 for it each time it mounts.
|
|
196
|
+
|
|
197
|
+
No REST answer, export, prop or type member changes.
|
|
198
|
+
- d48071e: A console page load reads `sys_user_preference` once, not once per preference key (objectui#12187).
|
|
199
|
+
|
|
200
|
+
- **The user-state adapters share one read per load.** `createObjectStackUserStateAdapter`'s `load()` now joins an open read for its data source, resource and user. Every load of that scope issued before the next microtask adds its key, the read is sent once, and each adapter takes its own row from the answer. The console's favorites, recent items and flow palette recents used to make three requests, one per key; they now make one, `user_id = USER and key in (the three keys)`, with `$top` set to the number of keys. The object's unique index on `(user_id, key)` bounds the answer to one row per key.
|
|
201
|
+
- **A read that collects one key is unchanged.** It sends the same per-key query, `$top: 1`, as before.
|
|
202
|
+
- **Nothing is kept past the request.** The read closes when it is sent. A later `load()`, such as a new adapter after a language or account switch, asks the server again. A different user or data source never joins another's read.
|
|
203
|
+
- **Writes are unchanged.** `save()` still finds and writes its own row, and the stored rows and their shape are the same.
|
|
204
|
+
|
|
205
|
+
No export, option or type changes.
|
|
206
|
+
- 40cb13b: The connection state follows the requests the console already makes, so the header chip now shows a mid-session outage (objectui#12295).
|
|
207
|
+
|
|
208
|
+
Until now the adapter set its connection state only while making the first connection. Once that had succeeded, no failed request could change it, so a backend that stopped mid-session left stale rows on screen and the header chip hidden.
|
|
209
|
+
|
|
210
|
+
- **A request with no HTTP answer** (refused, reset, unreachable) moves the state to `reconnecting`. Once the adapter's own retry window is spent, it moves to `error`. The window is the same `autoReconnect` / `maxReconnectAttempts` budget a failed first connection gets, counted in unanswered requests. The state stays there until a request is answered, and the header chip stays visible the whole time.
|
|
211
|
+
- **The next answered request** moves it back to `connected`, and the chip hides again after its brief "Connected" flash.
|
|
212
|
+
- **An answered HTTP error does not move it.** A 4xx or 5xx means the server is reachable. A request its caller aborted does not move it either.
|
|
213
|
+
|
|
214
|
+
The adapter adds no timer, no heartbeat and no request of its own: the state changes when a request the host makes settles. Every request goes through the adapter's one fetch, including those made through `getClient()`, except `uploadFile` / `uploadFiles`: those two call the global `fetch`, so an upload's outcome does not move the state. The fetch hands its response or rejection to the caller unchanged. Nothing is added to the package entry: no export, option, type member or language-pack key.
|
|
215
|
+
- 068a540: The console's Setup › Apps page reports what the server did with a write (objectui#12300). Before, a Delete and a Disable could report success for a write that changed nothing.
|
|
216
|
+
|
|
217
|
+
- **Delete.** When the reset door answers `reset: false` (there was no customization row to remove, so the app already is what its package ships), the page shows a neutral notice with the server's own sentence and never says "deleted". The row stays, because the server still serves it.
|
|
218
|
+
- **Disable, Set as default and the bulk Enable / Disable.** A save of an app that a package owns now names that package (`?package=`, read off the served `_packageId`), so it lands on the row the server serves. A package-less app is saved with no scope, as before.
|
|
219
|
+
- After every write, whatever it answered, the page re-reads the app list and shows the served state.
|
|
220
|
+
|
|
221
|
+
`ObjectStackAdapter` forwards every argument of `getClient().meta.saveItem`. Its save-advisory wrapper used to pass on only `type`, `name` and `item`, so the SDK's options bag (`packageId`, `force`, `mode`, `ifMatch`) was dropped before it reached the wire, for every caller of the adapter's client.
|
|
222
|
+
|
|
223
|
+
Two new `appManagement.toast` keys, `nothingToDelete` and `alreadyAtDefault`, ship in all ten language packs.
|
|
224
|
+
- 866763b: A failed first `connect()` with `autoReconnect` now retries discovery and settles (objectui#12318). The retry used to call `connect()` from inside the attempt it was retrying. `connect()` hands every caller the attempt in flight, so the retry waited on itself: one discovery read, a state stuck at `reconnecting`, and a promise that never settled. A console opened while its backend was down never finished starting, even after the backend came back.
|
|
225
|
+
|
|
226
|
+
- **Each retry is a new discovery read**, after the existing backoff (`reconnectDelay` × 2^(n-1)), up to `maxReconnectAttempts` times.
|
|
227
|
+
- **`connect()` settles.** It resolves once discovery answers, and the state ends `connected`. Once the retries are spent it rejects with the last attempt's `ConnectionError`, and the state ends `error`.
|
|
228
|
+
- **Concurrent callers still share one attempt**, including a caller that arrives during the backoff.
|
|
229
|
+
|
|
230
|
+
Nothing is added to the package entry: no export, option or type member. The state sequence of a failed attempt is unchanged: `error`, then `reconnecting` while retries remain.
|
|
231
|
+
- d12bc5f: A console reset, delete or draft discard names the package whose row it removes (objectui#12335, the consumer half of objectstack#23165).
|
|
232
|
+
|
|
233
|
+
Since `@objectstack/rest` 18.0.0-next.1 the metadata delete door reads `?package=`. A delete that names no package addresses the one package that holds the item; when two or more packages hold it the door refuses `409 RESOURCE_CONFLICT`, deletes nothing, and names `?package=` as the remedy. The console could not send that remedy.
|
|
234
|
+
|
|
235
|
+
- **`MetadataClient.reset()`** now sends `?package=` when `options.packageId` is set, beside `?state=draft` for a draft discard, with the spelling `save()` and `publish()` use. Without a binding the parameter is omitted, never sent empty. Its options type already declared `packageId` (inherited by `MetadataDeleteOptions`); `reset()` used to drop it. No exported type changes.
|
|
236
|
+
- **The metadata editor** (`MetadataResourceEditPage`): Reset/Delete and "Discard draft" state the package the editor's save and publish already state, read from `?package=` on the editor URL (the `all` scope names none). An editor with no package sends none, and a refusal is shown on the page's error line in the server's words.
|
|
237
|
+
- **The Objects page** (`MetadataObjectsPage`, `@object-ui/plugin-designer`): deleting an object states the package the listed row is served from (its `_packageId`). A row no package owns, including one served with the `sys_metadata` sentinel, sends none, and a refusal is shown in the page's error panel.
|
|
238
|
+
- **Unchanged:** the runtime draft bar's Discard and the adapter's `deleteView` hold no package binding, so they keep the package-less delete. Their refusals already reach the caller: the draft bar shows the server's message in its error toast, and `deleteView` throws.
|
|
239
|
+
- cfbca7f: `deleteView` now shows the server's reason when the published half of a view delete is refused (objectui#12416).
|
|
240
|
+
|
|
241
|
+
`ObjectStackAdapter.deleteView` deletes a view's draft home, then its published home. When the published delete failed, the adapter threw its own sentence ("… is NOT fully removed … retry the delete") and kept the server's error only on `cause`. `formatMetadataError(err)` reads the error's issues or its own message, so the console's Delete view toast showed the adapter's sentence and hid the server's reason. For the `409 RESOURCE_CONFLICT` a package-less delete of a view two packages hold gets since `@objectstack/rest` 18.0.0-next.1, that reason is the `?package=` remedy, and the toast told the user to retry a delete that cannot succeed.
|
|
242
|
+
|
|
243
|
+
The thrown error's message is now the adapter's sentence followed, on the next line, by the server's reason as `formatMetadataError` renders it (its issue lines, or else the served message unchanged). The sentence still says the view is not fully removed and the published row still serves it. It says "retry the delete" only when the failure is not a 4xx refusal. `cause` and `outcome` are unchanged. A refusal of the draft half is thrown as served, as before.
|
|
244
|
+
- 35ef750: `aggregate()` no longer reads a served `null` measure as a missing one. The analytics answer's `null` (the max or min over rows whose field is all NULL) now reaches the caller as `null` from the single analytics request; only a measure key that is absent from the answer falls back to re-reading the rows through `find()`. This removes a second, row-reading request from every widget bound to such an aggregate.
|
|
245
|
+
- 65fe4c2: One page open asks the server for one object document once (objectui#13044). The metadata provider's by-name object read now goes through the adapter's `getObjectSchema`, so the page's object scope and the record form, `useSettledSchema` and the field widgets share one `GET /meta/object/NAME`. The published view list (`GET /meta/view`) is shared while a request is on the wire: the provider's eager view read, `listViews` and `listViewOverrides` join it instead of each sending its own, and nothing is stored, so a later read, a refresh or a view write still asks the server. An organization or session switch clears the adapter's metadata cache. Each reader still gets its own copy, in the shape it got before; the draft-preview path is unchanged.
|
|
246
|
+
- cd4623d: One object-page open reads the published view list (`GET /meta/view`) once (objectui#13057). The adapter now stores the list its client read and answers `listViews` and `listViewOverrides` from it, so the provider's eager view read serves the page's two readers. The client read itself still always asks the server. Every path that should see a fresh list drops it first: a refresh of the object page, every view write (through the adapter, its client, or the console's `MetadataClient`, whose view saves, publishes, resets, rollbacks and package publishes now drop it), and an organization or session switch. The package sheet's *Publish app* now announces the publish like every other publish path, so open views, forms and navigation re-read what went live. The draft-preview list read is unchanged and never fills the stored list.
|
|
247
|
+
- 12ff256: `ConnectionState` is re-exported from `@object-ui/types` instead of being declared a second time here (objectui#6349, batch 10). `@object-ui/collaboration` declared the same five states for `useRealtimeSubscription`, so both packages now publish the one declaration under the same name. The union is unchanged, and so are `getConnectionState()`, `ConnectionStateEvent` and every import of `ConnectionState` from this package.
|
|
248
|
+
|
|
249
|
+
No runtime behaviour changes.
|
|
250
|
+
- 1b2d016: Doc comments only. `UserDataAdapter` now carries the contract notes `@object-ui/app-shell` kept on its own copy of the interface: implementations must be safe to call concurrently and should never throw, so the hosting provider can degrade to localStorage when the backend fails. `@object-ui/app-shell` now re-exports this declaration instead of declaring a second one (objectui#6349, batch 9). The type and its members are unchanged.
|
|
251
|
+
|
|
252
|
+
No runtime behaviour changes.
|
|
253
|
+
- Updated dependencies [18d7b48]
|
|
254
|
+
- Updated dependencies [172acc3]
|
|
255
|
+
- Updated dependencies [f0496bd]
|
|
256
|
+
- Updated dependencies [c4c506b]
|
|
257
|
+
- Updated dependencies [9db9ff3]
|
|
258
|
+
- Updated dependencies [b10c68e]
|
|
259
|
+
- Updated dependencies [bdc9049]
|
|
260
|
+
- Updated dependencies [2e818d0]
|
|
261
|
+
- Updated dependencies [8b14aec]
|
|
262
|
+
- Updated dependencies [2abec3a]
|
|
263
|
+
- Updated dependencies [9dfaca6]
|
|
264
|
+
- Updated dependencies [e6dcd85]
|
|
265
|
+
- Updated dependencies [96fcf30]
|
|
266
|
+
- Updated dependencies [d50f724]
|
|
267
|
+
- Updated dependencies [17acfbb]
|
|
268
|
+
- Updated dependencies [89cc738]
|
|
269
|
+
- Updated dependencies [c0862c1]
|
|
270
|
+
- Updated dependencies [c0862c1]
|
|
271
|
+
- Updated dependencies [c0862c1]
|
|
272
|
+
- Updated dependencies [c0862c1]
|
|
273
|
+
- Updated dependencies [4c0de52]
|
|
274
|
+
- Updated dependencies [a80fef7]
|
|
275
|
+
- Updated dependencies [f34e877]
|
|
276
|
+
- Updated dependencies [f34e877]
|
|
277
|
+
- Updated dependencies [aaba865]
|
|
278
|
+
- Updated dependencies [45d5853]
|
|
279
|
+
- Updated dependencies [2312bd9]
|
|
280
|
+
- Updated dependencies [c5406c2]
|
|
281
|
+
- Updated dependencies [d99b731]
|
|
282
|
+
- Updated dependencies [2a48bd4]
|
|
283
|
+
- Updated dependencies [c7b30bd]
|
|
284
|
+
- Updated dependencies [69bca0a]
|
|
285
|
+
- Updated dependencies [023f00d]
|
|
286
|
+
- Updated dependencies [158c75d]
|
|
287
|
+
- Updated dependencies [7525045]
|
|
288
|
+
- Updated dependencies [5af42db]
|
|
289
|
+
- Updated dependencies [5f75cfa]
|
|
290
|
+
- Updated dependencies [c0c2f79]
|
|
291
|
+
- Updated dependencies [0665c3b]
|
|
292
|
+
- Updated dependencies [d2500d8]
|
|
293
|
+
- Updated dependencies [b231607]
|
|
294
|
+
- Updated dependencies [466a219]
|
|
295
|
+
- Updated dependencies [6fb281d]
|
|
296
|
+
- Updated dependencies [334f0cf]
|
|
297
|
+
- Updated dependencies [c08c933]
|
|
298
|
+
- Updated dependencies [c4dd707]
|
|
299
|
+
- Updated dependencies [923504f]
|
|
300
|
+
- Updated dependencies [10aba37]
|
|
301
|
+
- Updated dependencies [5d3f437]
|
|
302
|
+
- Updated dependencies [7af0ab3]
|
|
303
|
+
- Updated dependencies [fd4872f]
|
|
304
|
+
- Updated dependencies [f0532d8]
|
|
305
|
+
- Updated dependencies [3092aca]
|
|
306
|
+
- Updated dependencies [82e5f53]
|
|
307
|
+
- Updated dependencies [d81e460]
|
|
308
|
+
- Updated dependencies [8106db1]
|
|
309
|
+
- Updated dependencies [093d55b]
|
|
310
|
+
- Updated dependencies [a8051d6]
|
|
311
|
+
- Updated dependencies [524136e]
|
|
312
|
+
- Updated dependencies [6f3299f]
|
|
313
|
+
- Updated dependencies [f2b17c6]
|
|
314
|
+
- Updated dependencies [52d6194]
|
|
315
|
+
- Updated dependencies [23bf859]
|
|
316
|
+
- Updated dependencies [1c72d77]
|
|
317
|
+
- Updated dependencies [2b4d3f1]
|
|
318
|
+
- Updated dependencies [d75f212]
|
|
319
|
+
- Updated dependencies [ff9df88]
|
|
320
|
+
- Updated dependencies [a2225f9]
|
|
321
|
+
- Updated dependencies [21f9577]
|
|
322
|
+
- Updated dependencies [3eb3ec1]
|
|
323
|
+
- Updated dependencies [619e3f7]
|
|
324
|
+
- Updated dependencies [f62b6ff]
|
|
325
|
+
- Updated dependencies [d66d7e8]
|
|
326
|
+
- Updated dependencies [15d8ca6]
|
|
327
|
+
- Updated dependencies [378896d]
|
|
328
|
+
- Updated dependencies [4839f5c]
|
|
329
|
+
- Updated dependencies [c999d4c]
|
|
330
|
+
- Updated dependencies [260ef71]
|
|
331
|
+
- Updated dependencies [26323c4]
|
|
332
|
+
- Updated dependencies [c219165]
|
|
333
|
+
- Updated dependencies [fb501bc]
|
|
334
|
+
- Updated dependencies [30083da]
|
|
335
|
+
- Updated dependencies [025130c]
|
|
336
|
+
- Updated dependencies [8aa8e3e]
|
|
337
|
+
- Updated dependencies [2493e85]
|
|
338
|
+
- Updated dependencies [a928aaa]
|
|
339
|
+
- Updated dependencies [61ed5ad]
|
|
340
|
+
- Updated dependencies [1b94d50]
|
|
341
|
+
- Updated dependencies [fb85637]
|
|
342
|
+
- Updated dependencies [9f1dd79]
|
|
343
|
+
- Updated dependencies [df71568]
|
|
344
|
+
- Updated dependencies [cbac9d7]
|
|
345
|
+
- Updated dependencies [616df65]
|
|
346
|
+
- Updated dependencies [daeb77f]
|
|
347
|
+
- Updated dependencies [eb4552e]
|
|
348
|
+
- Updated dependencies [3c3115e]
|
|
349
|
+
- Updated dependencies [55e90fd]
|
|
350
|
+
- Updated dependencies [9b75509]
|
|
351
|
+
- Updated dependencies [b13ea3c]
|
|
352
|
+
- Updated dependencies [d7e9e9a]
|
|
353
|
+
- Updated dependencies [cf62edf]
|
|
354
|
+
- Updated dependencies [0253416]
|
|
355
|
+
- Updated dependencies [5d77c09]
|
|
356
|
+
- Updated dependencies [3fd8625]
|
|
357
|
+
- Updated dependencies [1473757]
|
|
358
|
+
- Updated dependencies [d73d987]
|
|
359
|
+
- Updated dependencies [12ff256]
|
|
360
|
+
- Updated dependencies [26b0bd2]
|
|
361
|
+
- Updated dependencies [d328698]
|
|
362
|
+
- Updated dependencies [d328698]
|
|
363
|
+
- Updated dependencies [b4e0787]
|
|
364
|
+
- Updated dependencies [088e0f7]
|
|
365
|
+
- Updated dependencies [78b07f4]
|
|
366
|
+
- Updated dependencies [b403bb3]
|
|
367
|
+
- Updated dependencies [12dc7b4]
|
|
368
|
+
- @object-ui/types@18.0.0
|
|
369
|
+
- @object-ui/core@18.0.0
|
|
370
|
+
|
|
371
|
+
## 17.7.0
|
|
372
|
+
|
|
373
|
+
### Minor Changes
|
|
374
|
+
|
|
375
|
+
- 7b395d8: **BREAKING** — three more record ids that the objectui#9511 ruling's enumeration left out are
|
|
376
|
+
strings now (objectui#10078). `CommentSearchResult.recordId`, `RecordSubscription.recordId`
|
|
377
|
+
and `DataSourceMutationEvent.id` narrow **FROM** `string | number` **TO** `string`. ⚠️ Other
|
|
378
|
+
record ids in `@object-ui/types` still admit a number and are not touched here, among them
|
|
379
|
+
`DetailViewSchema.recordNavigation` (`recordIds`, `onNavigate`), `FeedItem.sourceId`, and the
|
|
380
|
+
`onNavigate` slots on `ObjectGridSchema`, `ObjectViewSchema` and `ListViewRuntimeProps`.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
// before — compiled
|
|
384
|
+
const bell: RecordSubscription = { recordId: 42, subscribed: true };
|
|
385
|
+
// after — refused by the compiler; write the id the protocol carries
|
|
386
|
+
const bell: RecordSubscription = { recordId: '42', subscribed: true };
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Why.** objectui#9511 (director batch #195 item 1, letter A) ruled that a record id is a
|
|
390
|
+
string wherever **metadata** names one, and extended that, as 「the same principle」, to
|
|
391
|
+
`CommentEntry.recordId` and `MentionNotification.recordId`. It did not name these three, which
|
|
392
|
+
are runtime and API shapes rather than metadata. Carrying the rule to them is the `domain:spec`
|
|
393
|
+
seat's inheritance decision, recorded in the claim on objectui#10078: they hold the same id and
|
|
394
|
+
were left with the old spelling. `CommentSearchResult.recordId` is copied straight from
|
|
395
|
+
`CommentEntry.recordId`, `RecordSubscription.recordId` names the record a notification bell is
|
|
396
|
+
for, and `DataSourceMutationEvent.id` is the id the `update` / `delete` doors already take as a
|
|
397
|
+
string.
|
|
398
|
+
|
|
399
|
+
**What changes at runtime.** `ValueDataSource` and `ObjectStackAdapter` now emit
|
|
400
|
+
`onMutation` events whose `id` is always a string. Both methods still accept a numeric id
|
|
401
|
+
through their own wider parameter, and each converts it at its own boundary before emitting —
|
|
402
|
+
the one place the rule says the conversion lives. An in-memory `value` source whose items
|
|
403
|
+
carry numeric keys therefore announces `'42'`, not `42`. A subscriber that compared
|
|
404
|
+
`event.id` against a number must compare against the string. The two in-repo subscribers
|
|
405
|
+
that read `event.id` already pass it through `String(...)`, so they see no difference.
|
|
406
|
+
|
|
407
|
+
**Packages that narrow without a source change.** `@object-ui/collaboration` (`useCommentSearch`
|
|
408
|
+
returns `CommentSearchResult[]`) and `@object-ui/plugin-detail` (`SubscriptionToggleProps`
|
|
409
|
+
carries a `RecordSubscription`) publish the narrower type through their own `.d.ts`.
|
|
410
|
+
|
|
411
|
+
**What a host has to do.** Code that builds a `RecordSubscription`, reads
|
|
412
|
+
`CommentSearchResult.recordId`, or implements `DataSource.onMutation` with numeric keys
|
|
413
|
+
converts once, where the number enters (its own adapter or data boundary), and passes the
|
|
414
|
+
string from there on. ⛔ Nothing converts silently on the reading side.
|
|
415
|
+
|
|
416
|
+
**Disposition: ADR-0087 D7 — a published runtime TypeScript interface with no metadata
|
|
417
|
+
surface is carried by the compiler and needs no ledger entry; ⛔ no conversion entry, ⛔ not
|
|
418
|
+
a tombstone, ⛔ not an npm `major`.** None of the three has a zod mirror, none is authored into JSON, and no schema
|
|
419
|
+
references them, so there is no parse-time door and nothing for a conversion table to
|
|
420
|
+
rewrite: the compiler error at the numeric site is the delivery channel. The level is `minor`
|
|
421
|
+
because objectui marks its own breaking changes `minor` with this banner (AGENTS.md, version
|
|
422
|
+
alignment); `major` is refused outright because every publishable package sits in one `fixed`
|
|
423
|
+
group pinned to the `@objectstack` major (`scripts/check-changeset-no-major.mjs`).
|
|
424
|
+
|
|
425
|
+
**Pin.** `record-id-string-survivors-10078.test.ts` in `@object-ui/types` asserts all three
|
|
426
|
+
members read `string` (not `any`), refuses a numeric literal at each, and keeps a
|
|
427
|
+
source-text census of the three declarations with controls that prove the census can fire.
|
|
428
|
+
- 1563d3e: The object-metadata write guard now holds a `select` / `radio` field that has no option source.
|
|
429
|
+
|
|
430
|
+
This is the objectui half of the maintainer's ruling A on objectstack#20827, which refuses a
|
|
431
|
+
`select` / `radio` with neither `options` nor `picklist` at the `FieldSchema` door and has objectui
|
|
432
|
+
stop sending one first. The rule lives in the existing door guard, so `MetadataClient.save`,
|
|
433
|
+
`importObjectDraft` and `MetadataService` all apply it, for every writer behind them.
|
|
434
|
+
|
|
435
|
+
BREAKING (behaviour, released as `minor` per this repository's version policy):
|
|
436
|
+
`assertObjectMetadataWritable('object', …)`, and so `MetadataClient.save('object', …)`, now throws
|
|
437
|
+
BEFORE issuing the request when a `select` or `radio` field carries neither a non-empty `options`
|
|
438
|
+
list nor a `picklist` string. `options: []` counts as none, as the contract's own completeness rule
|
|
439
|
+
reads it. Unlike the relationship rule, this refuses a document the installed server still accepts:
|
|
440
|
+
the ruling orders the client first on purpose, and the door follows. The message names the field
|
|
441
|
+
and asks for an option, a shared `picklist`, or a non-choice type. A field that names a `picklist`
|
|
442
|
+
is never refused by this rule.
|
|
443
|
+
|
|
444
|
+
The Studio data page and the metadata-admin object editor already surface the refusal in their
|
|
445
|
+
error banner and send the next autosave once an option exists, including for an object that already
|
|
446
|
+
stores such a field.
|
|
447
|
+
|
|
448
|
+
New export: `CHOICE_TYPES_REQUIRING_OPTIONS`, derived by this package's pin from the installed
|
|
449
|
+
`@objectstack/spec`'s `field/choice-without-options` completeness rule.
|
|
450
|
+
- d893290: `formatMetadataError` and `formatMetadataIssue` are exported from `@object-ui/data-objectstack`: the one reader of a failed metadata save (objectui#11302).
|
|
451
|
+
|
|
452
|
+
A save the spec refuses answers `422 INVALID_METADATA` with a headline message (a count plus `path [code]` locators) and the author's prescription in structured issues, which `MetadataClient` puts on `MetadataError.issues`. `formatMetadataError(err)` lists those issues one field per line (`• fields.amount.type — Required`) and falls back to the error's message when there are none; `formatMetadataIssue(issue)` is that one-line grammar, for callers that format a list of failures. Render the result with a whitespace class that keeps newlines.
|
|
453
|
+
|
|
454
|
+
The reader moved here from `@object-ui/app-shell`, where it was internal, so every surface that saves through `MetadataClient` can render a refusal the same way. Additive: nothing existing changed.
|
|
455
|
+
- 993f312: Parse a write-strip's `reason` against the spec enum at the boundary
|
|
456
|
+
(objectui#4934).
|
|
457
|
+
|
|
458
|
+
`notifyDroppedFields` filtered a create/update response's `droppedFields` on
|
|
459
|
+
SHAPE alone — a hand-written `e is DroppedFieldsEvent` guard that checked
|
|
460
|
+
`Array.isArray(fields)` and nothing else — so a `reason` outside
|
|
461
|
+
`'readonly' | 'readonly_when' | 'primary_key'` reached every subscriber typed as
|
|
462
|
+
though it were inside the union. A deployed client normally runs BEHIND the
|
|
463
|
+
server it talks to, so a reason from the future is the expected skew direction,
|
|
464
|
+
not a corrupt payload; the interior was typed to trust a union no one had
|
|
465
|
+
checked, and nothing in the repo could say so. `notifyBatchDroppedFields` did
|
|
466
|
+
the same through its `entry as DroppedFieldsEvent & { index?: number }` cast.
|
|
467
|
+
|
|
468
|
+
Both paths now read `reason` against `DroppedFieldsEventSchema.shape.reason` —
|
|
469
|
+
the enum the installed pin declares, derived rather than restated, so a pin bump
|
|
470
|
+
that adds an arm widens the accept set on its own:
|
|
471
|
+
|
|
472
|
+
- **Every entry is kept.** Dropping the unparsable ones would tell the user
|
|
473
|
+
nothing about fields the server really did strip, which is exactly the silence
|
|
474
|
+
objectui#3484 removed.
|
|
475
|
+
- An unrecognized `reason` arrives on a named skew arm,
|
|
476
|
+
`UnrecognizedDropReasonEvent`, carrying `UNRECOGNIZED_DROP_REASON` plus the
|
|
477
|
+
wire value **verbatim** in `unrecognizedReason` — never coerced onto a known
|
|
478
|
+
arm, because claiming `readonly` for a reason we cannot name is a false
|
|
479
|
+
statement about the user's data.
|
|
480
|
+
- `WriteWarningEvent['droppedFields']` is therefore the two-arm
|
|
481
|
+
`DroppedFieldsNotice`. The spec type stays the canonical arm and is not
|
|
482
|
+
widened to `string` (objectui#3160): the skew arm is not assignable to
|
|
483
|
+
`DroppedFieldsEvent`, so a consumer branching on `reason` now hears about
|
|
484
|
+
server skew from `tsc` instead of from a per-consumer discipline.
|
|
485
|
+
|
|
486
|
+
Runtime wording is unchanged: the one reader, the app shell's write-warning
|
|
487
|
+
toast, already answered an unrecognized reason with its cause-free line.
|
|
488
|
+
|
|
489
|
+
**Blast radius — the compile error IS the intended signal, not a regression.** A
|
|
490
|
+
consumer that branches exhaustively on `reason` — a parameter, a `Map` key or a
|
|
491
|
+
`Record` annotated `DroppedFieldsEvent['reason']` — stops compiling against this
|
|
492
|
+
release, with a `TS2345` at each such site. That error is the notification, and
|
|
493
|
+
the only one: the skew arm is deliberately NOT assignable to the spec union, so
|
|
494
|
+
`tsc` reports server skew at the one place the wire is read rather than leaving
|
|
495
|
+
it to a per-consumer discipline. Do not cast it away. Widen the annotation to
|
|
496
|
+
`DroppedFieldsNotice['reason']`, and where the two arms have to be told apart,
|
|
497
|
+
narrow with `entry.reason === UNRECOGNIZED_DROP_REASON` and read the wire value
|
|
498
|
+
verbatim from `unrecognizedReason`.
|
|
499
|
+
|
|
500
|
+
Widen the LOOKUPS, not the table. A `Record` that must stay exhaustive over the
|
|
501
|
+
SPEC arms keeps its `DroppedFieldsEvent['reason']` key: widening that one would
|
|
502
|
+
trade away the guarantee that a newly pinned spec reason fails `type-check`
|
|
503
|
+
unworded (objectui#3935).
|
|
504
|
+
|
|
505
|
+
In this repo the entire blast radius is the app shell's write-warning toast —
|
|
506
|
+
two type annotations, no runtime change. Its executable JavaScript is byte-identical
|
|
507
|
+
and its wording tests pass unchanged, because the file was already written for
|
|
508
|
+
this value: its own docstring says the runtime `reason` may sit outside the spec
|
|
509
|
+
union and that the cause-free fallback is reachable, not dead. Only the parameter
|
|
510
|
+
and the `Map` key had been left narrower than that documented contract.
|
|
511
|
+
- 41b7ce3: **View configuration is explicitly org-wide, and its write path is now gated (objectstack#7494's
|
|
512
|
+
ruling, maintainer 2026-08-12).** The `sort` / `hiddenFields` / `columnState` / `rowHeight` that a
|
|
513
|
+
list toolbar persists were never per-user: they are one shared row on the view, so an ordinary user
|
|
514
|
+
dragging a column or cycling density was re-styling that view for the entire organization. Nothing
|
|
515
|
+
in the console said so, and nothing stopped it. A per-user scope stays parked (objectstack#7611,
|
|
516
|
+
v18) and is deliberately not built here — which is precisely why the write has to be gated rather
|
|
517
|
+
than narrowed: there is no second, private store for it to fall back to.
|
|
518
|
+
|
|
519
|
+
`ObjectStackAdapter.updateViewConfig` now refuses when the session's **reported** ADR-0066 capability
|
|
520
|
+
set does not contain `manage_metadata`, throwing the new `ViewConfigPermissionDeniedError`
|
|
521
|
+
(`VIEW_CONFIG_PERMISSION_DENIED`, with `isViewConfigPermissionDeniedError` and the
|
|
522
|
+
`VIEW_CONFIG_CAPABILITY` constant alongside it). The gate is the **first** statement in the method —
|
|
523
|
+
before `connect()`, before the payload is assembled — so a refused call puts nothing on the wire.
|
|
524
|
+
It is on the write rather than on the toolbar button on purpose: withholding the affordance would
|
|
525
|
+
leave the method still accepting the call from anything else holding the adapter, whereas a gate on
|
|
526
|
+
the write is inherited by every caller, present and future.
|
|
527
|
+
|
|
528
|
+
`manage_metadata` is not a newly minted name. It is the capability this repo already treats as
|
|
529
|
+
metadata-authoring authority — `HomePage`'s `AUTHORING_CAPABILITY`, the one the server itself
|
|
530
|
+
refuses metadata writes without — and the gated write goes through `client.meta.saveItem`, the very
|
|
531
|
+
same ADR-0005 metadata door, so this applies the authority the server is already applying instead of
|
|
532
|
+
inventing a parallel one.
|
|
533
|
+
|
|
534
|
+
**Unknown fails open, by doctrine.** A capability set that was never reported (a backend predating
|
|
535
|
+
ADR-0066, or no permission provider mounted) is not a denial: the server enforces regardless, so a
|
|
536
|
+
client-side refusal on missing data cannot protect anything and can only break a permitted user. A
|
|
537
|
+
*reported* empty grant gates strictly. Hosts push the session's capabilities in with the new
|
|
538
|
+
`setSystemCapabilities`; `ObjectView` wires it from `usePermissions()`.
|
|
539
|
+
|
|
540
|
+
The refusal is also **said out loud**. `ObjectView`'s persist path previously swallowed every failure
|
|
541
|
+
into `console.error`, which for a debounced toggle whose UI has already moved would have left the
|
|
542
|
+
operator looking at a density they did not get; a denied write now raises a toast. And the "View
|
|
543
|
+
settings" popover — where density and field visibility are actually changed — now states the scope
|
|
544
|
+
before the operator acts: *"Grouping, color, density, and visible fields. Applies to everyone who
|
|
545
|
+
uses this view."*, translated in all ten packs.
|
|
546
|
+
- 94e2fa7: `MetadataClient.layered()` validates the ADR-0010 protection envelope against the
|
|
547
|
+
producer's own schema at the boundary, instead of casting ten wire fields through
|
|
548
|
+
unchecked (objectui#5676, triage adjudication 2026-08-22).
|
|
549
|
+
|
|
550
|
+
The envelope arrived by ten `as` assertions over a raw `res.json()` body — no parse, no
|
|
551
|
+
allowlist, no default. The consumer that reads it opens the metadata lock banner on
|
|
552
|
+
`layered?.lock && layered.lock !== 'none'`, true for **any** non-`none` value, so a server
|
|
553
|
+
sending a lock state this console had never heard of opened the amber box, drew the padlock
|
|
554
|
+
and the border, and rendered an empty title. No fifth state ever had to be added to this
|
|
555
|
+
repo for that to happen: a union types what this repo writes and constrains nothing about
|
|
556
|
+
what a server sends.
|
|
557
|
+
|
|
558
|
+
The boundary now runs `GetMetaItemLayeredResponseSchema.safeParse`. On the conforming path
|
|
559
|
+
every value is the producer's schema output and the ten assertions are gone. `safeParse`
|
|
560
|
+
and never `parse`: a metadata console that rejected every dialect it had not been compiled
|
|
561
|
+
against would answer a newer server with a blank page, which is strictly worse than the
|
|
562
|
+
wrong render being fixed. Values the schema rejects are still **forwarded** — dropping them
|
|
563
|
+
would be that same refused rejection wearing different clothes — and are named in a new
|
|
564
|
+
optional `MetadataLayered._unrecognized`, absent whenever everything parsed. This extends
|
|
565
|
+
to the whole envelope the "pass through and label" treatment objectui#5672 chose for `lock`
|
|
566
|
+
alone; the banner's existing unrecognised-token title is unchanged and needed no edit.
|
|
567
|
+
|
|
568
|
+
The labelling is per field, which is the part that makes it a degrade rather than a subtler
|
|
569
|
+
version of the same bug. Measured on the installed spec (17.2.0):
|
|
570
|
+
`GetMetaItemLayeredResponseSchema.safeParse(body)` is all-or-nothing — one unknown `lock`
|
|
571
|
+
returns `success: false` with `data` undefined — so the failure branch re-checks each key
|
|
572
|
+
against that schema's own `shape[key]`, where only the offending field fails and the other
|
|
573
|
+
six still arrive typed. Absence is never "unrecognised": the four resolved verdicts are
|
|
574
|
+
required upstream on this path, so a pre-ADR-0010 backend takes the failure branch with
|
|
575
|
+
nothing flagged and behaves exactly as before.
|
|
576
|
+
|
|
577
|
+
One consequence of the same ruling, fixed alongside because it defeats it: a 200 answer
|
|
578
|
+
whose body was a bare JSON string or number **rejected** the promise with a
|
|
579
|
+
`TypeError: Cannot use 'in' operator`, from the envelope-detection guard's bare truthiness
|
|
580
|
+
check. A malformed body must degrade, never throw.
|
|
581
|
+
- f07b976: `aggregate()`'s spec-shape branch now REFUSES an unlowered `where` instead of
|
|
582
|
+
posting it (objectui#6825, maintainer ruling 2026-08-30 — option A).
|
|
583
|
+
|
|
584
|
+
**Breaking for callers that were already broken, so read this if you call
|
|
585
|
+
`aggregate()` with a `where`.** `aggregate()` has two branches. The analytics
|
|
586
|
+
branch takes `filter` and lowers a rule-shaped array before the wire (#6302).
|
|
587
|
+
The spec-shape branch — entered when `params` carries an array `groupBy`, an
|
|
588
|
+
array `aggregations`, or ANY `where` key — takes `where` and posts it to
|
|
589
|
+
`POST /data/:object/query` verbatim. It never lowered, and it still does not:
|
|
590
|
+
it now says so.
|
|
591
|
+
|
|
592
|
+
**What now throws that previously went through.** A `where` that is an ARRAY
|
|
593
|
+
the spec's own `isFilterAST` gate rejects — an unlowered
|
|
594
|
+
`[{ field, operator, value }, ...]` above all, plus the infix join dialect
|
|
595
|
+
(`[condA, 'or', condB]`), a tuple whose operator is outside the AST vocabulary,
|
|
596
|
+
`['and']` with nothing to join, and an element that is not a condition. The
|
|
597
|
+
throw is an `UnloweredAggregateWhereError` (exported), carrying the same
|
|
598
|
+
`code: 'INVALID_FILTER'` / `httpStatus: 400` pair as `MalformedFilterError`, so
|
|
599
|
+
`isMalformedFilterError()` recognises it and a failed widget renders "this
|
|
600
|
+
filter is malformed" rather than "check your connection".
|
|
601
|
+
|
|
602
|
+
**This adds no new failure — it relocates one.** Every shape now refused is one
|
|
603
|
+
the receiving engine already refused (`is not a filter`, 400 `INVALID_FILTER`,
|
|
604
|
+
before the store is touched). What changed is WHERE you find out and whether you
|
|
605
|
+
can act on it: previously the predicate was lost on the wire — or dropped
|
|
606
|
+
outright, leaving a chart rendering confident, wrong numbers with no signal to
|
|
607
|
+
its author. The refusal is now raised at the producer, names the value it
|
|
608
|
+
received, names the shape expected and where the spec declares it, and says
|
|
609
|
+
nothing was sent.
|
|
610
|
+
|
|
611
|
+
**What an affected caller should send instead.** Lower the rules to a filter AST
|
|
612
|
+
BEFORE calling `aggregate()`:
|
|
613
|
+
|
|
614
|
+
```diff
|
|
615
|
+
- adapter.aggregate('opportunity', {
|
|
616
|
+
- groupBy: ['stage'], aggregations: [...],
|
|
617
|
+
- where: [{ field: 'stage', operator: 'equals', value: 'won' }],
|
|
618
|
+
- })
|
|
619
|
+
+ adapter.aggregate('opportunity', {
|
|
620
|
+
+ groupBy: ['stage'], aggregations: [...],
|
|
621
|
+
+ where: ['stage', '=', 'won'],
|
|
622
|
+
+ })
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Or keep using the analytics branch, which lowers for you: pass the rules under
|
|
626
|
+
`filter` with the legacy `field` / `function` / `groupBy` params.
|
|
627
|
+
|
|
628
|
+
**Unchanged, deliberately.** The analytics branch still lowers `filter` exactly
|
|
629
|
+
as #6302 left it. On the spec-shape branch a `FilterCondition` object
|
|
630
|
+
(`{ stage: 'won' }` — what `QuerySchema.where` actually declares), an empty
|
|
631
|
+
array (`[]` is "no filter", and the engine agrees), and every already-valid
|
|
632
|
+
filter AST all reach `client.data.query` byte-unchanged.
|
|
633
|
+
- 503cd8b: `aggregate()`'s spec-shape branch now REFUSES the analytics branch's `filter` /
|
|
634
|
+
`field` / `function` instead of dropping them (extending the
|
|
635
|
+
maintainer ruling of 2026-08-30 on objectui#6825 — option A, refuse at the
|
|
636
|
+
producer).
|
|
637
|
+
|
|
638
|
+
**Breaking for callers that were already broken, so read this if you call
|
|
639
|
+
`aggregate()` with an ARRAY `groupBy`.** The spec-shape branch — entered when
|
|
640
|
+
`params` carries an array `groupBy`, an array `aggregations`, or any `where`
|
|
641
|
+
key — builds its request from exactly four keys: `groupBy`, `aggregations`,
|
|
642
|
+
`where`, `limit`. `filter`, `field` and `function` are the OTHER branch's
|
|
643
|
+
parameters, and they were neither read, nor refused, nor warned about: they
|
|
644
|
+
were simply absent from the body that went to `POST /data/:object/query`.
|
|
645
|
+
|
|
646
|
+
**Why that was worse than the `where` half #6825 fixed.** `field` + `function`
|
|
647
|
+
are the analytics branch's whole measure, and this branch takes a measure only
|
|
648
|
+
out of `aggregations`. So the legacy shape `{ field, function, groupBy, filter }`
|
|
649
|
+
whose `groupBy` happened to be an ARRAY produced a query carrying a `groupBy`
|
|
650
|
+
and **no aggregations at all** — a grouping with no measure — with the author's
|
|
651
|
+
filter gone as well. The chart rendered, the numbers were wrong, and there was
|
|
652
|
+
nothing on screen or on the wire to look at.
|
|
653
|
+
|
|
654
|
+
**What now throws that previously went through.** A spec-shape call carrying a
|
|
655
|
+
non-nullish `filter`, `field` or `function` throws the new
|
|
656
|
+
`AnalyticsKeysOnSpecShapeError`. It carries the `INVALID_FILTER` / 400 pair its
|
|
657
|
+
siblings carry (so `isMalformedFilterError()` recognises it and a failed widget
|
|
658
|
+
renders "this filter is malformed" rather than "check your connection"), plus
|
|
659
|
+
`keys` — the offending key names — and `received`, what each one carried. The
|
|
660
|
+
message names each key, says what its spec-shape equivalent is, states which
|
|
661
|
+
`looksLikeSpecShape` disjunct put the call on this branch, and says outright
|
|
662
|
+
when the resulting query would have had no measure. Nothing is sent to the
|
|
663
|
+
server, so no unfiltered numbers come back.
|
|
664
|
+
|
|
665
|
+
**What is deliberately NOT refused.** The legacy analytics shape (a STRING
|
|
666
|
+
`groupBy`) is untouched and still lowers `filter` and still fuses
|
|
667
|
+
`field` + `function` into its measure — the refusal lives inside the spec-shape
|
|
668
|
+
branch only. A key that is present but nullish (`filter: undefined`) carries
|
|
669
|
+
nothing to drop and passes, so params built by spreading possibly-absent
|
|
670
|
+
authored values keep working. And keys outside those three (`orderBy`, a future
|
|
671
|
+
spec key, any host extra) are not refused: the gate names three keys, and only
|
|
672
|
+
those.
|
|
673
|
+
|
|
674
|
+
**Migration.** Pick one shape per call. Spec-shape: `{ groupBy: GroupByNode[],
|
|
675
|
+
aggregations: AggregationNode[], where?, limit? }`, with `where` already lowered.
|
|
676
|
+
Analytics: `{ field, function, groupBy: string, filter? }`, which lowers `filter`
|
|
677
|
+
for you. `AnalyticsKeysOnSpecShapeError` is exported from
|
|
678
|
+
`@object-ui/data-objectstack`.
|
|
679
|
+
- ce986aa: Studio's "publish whole app" reports the runtime authoring gate's per-draft
|
|
680
|
+
advisories (server half objectstack#9343).
|
|
681
|
+
|
|
682
|
+
`POST /packages/:id/publish-drafts` began answering `advisories` on each
|
|
683
|
+
`published[]` element when objectstack#9343 landed, but the author publishing a
|
|
684
|
+
whole app was still told nothing: both client call sites bypassed the data-layer
|
|
685
|
+
seam — a bare `fetch` in `usePublishAllDrafts` and the page-private `apiJson` in
|
|
686
|
+
`PackagesPage`, whose declared response type held two counts and `failed[]`, with
|
|
687
|
+
no `published[]` at all. The same button's own client-side capability lint was
|
|
688
|
+
raising a toast the whole time, so a finding from the server was the one thing
|
|
689
|
+
that could not reach the person pressing it.
|
|
690
|
+
|
|
691
|
+
- `MetadataClient.publishPackageDrafts(packageId)` expresses the route and emits
|
|
692
|
+
one advisory event per advised `published[]` element — each naming that
|
|
693
|
+
element's own `type` / `name` — into the sink, event and renderer the save and
|
|
694
|
+
single-item publish doors already use. Both call sites go through it.
|
|
695
|
+
- The batch door reports `door: 'publish'` rather than a third discriminator
|
|
696
|
+
value: every item the event names really was published, and the renderer's
|
|
697
|
+
only door-dependent output is that verb. The per-item identity the author
|
|
698
|
+
needs rides `type` / `name`, one event per item.
|
|
699
|
+
- It renders only what the server sent where `PublishPackageDraftsResponseSchema`
|
|
700
|
+
declares it. A half-shaped finding, an element that cannot name its item, and a
|
|
701
|
+
top-level `advisories` the ruled shape does not put there all report nothing —
|
|
702
|
+
pinned, alongside the presence, in `metadata-client.publishAdvisories.test.ts`,
|
|
703
|
+
whose absence pin this flips.
|
|
704
|
+
- `publishPackageDrafts` returns the batch body derived from the spec schema, so
|
|
705
|
+
a caller reading `failed[]` / `publishedCount` reads a declared shape. Non-2xx
|
|
706
|
+
raises the usual `MetadataError`; the 2xx batch verdict stays the caller's to
|
|
707
|
+
judge, because `success: false` is not a refusal on this route.
|
|
708
|
+
- 512bc90: `aggregate()` reads the analytics answer in ONE spelling: `rows` on the `AnalyticsResult` that `client.analytics.query` resolves to (objectui#7028). The `{ success, data: { rows } }` envelope is no longer accepted at this site.
|
|
709
|
+
|
|
710
|
+
`@objectstack/client` 17.3.0 converged `analytics.query` on `unwrapResponse` (objectstack#13079): the method resolves to the payload, and the caller reads `result.rows`. The maintainer's ruling on that card ordered this adapter's tolerant row ladder tightened in the same wave. The ladder read five spellings (a bare array, `rows`, `data` as an array, `data.data.rows` and `results`) and answered any other shape with `[]`, which a chart renders as "no data" and a KPI as a confident zero.
|
|
711
|
+
|
|
712
|
+
What changes:
|
|
713
|
+
|
|
714
|
+
- **Only `rows` is read.** Any other value now throws the new `AnalyticsResultShapeError` (`code: 'ANALYTICS_RESULT_SHAPE_INVALID'`, with `envelope: true` when the value is the pre-17.3.0 envelope). It is not answered by the client-side `find()` fallback, because analytics did answer: degrading would put plausible numbers from a different code path over a contract violation.
|
|
715
|
+
- **Minimum version: `@objectstack/client` 17.3.0.** The dependency range moves from `^17.0.0` to `^17.3.0`, the first release that unwraps this method. A client older than that resolved `analytics.query` to the whole envelope, which this site no longer reads. The console's own `@objectstack/client` range moves with it, so the workspace declares one client floor.
|
|
716
|
+
- **No server is dropped.** The envelope has only ever been a client-side question: every `@objectstack` 17.x server answers `POST /analytics/query` with the same `{ success, data }` envelope, and the client decides whether it is unwrapped. The server version is not the variable here.
|
|
717
|
+
- The measure-missing fallback is unchanged: rows that come back without the requested measure are still re-aggregated client-side from a scoped `find()`.
|
|
718
|
+
|
|
719
|
+
The objectui#7122 changeset recorded these branches as kept "rather than deleted" and left the compatibility question open. The objectstack#13079 ruling answers it, and this change removes them.
|
|
720
|
+
- e2b3826: Grid grouping is now **server-side** (objectui#7189, maintainer ruling A): the
|
|
721
|
+
set of groups and every number in a group header — the count and any per-group
|
|
722
|
+
aggregation — come from the query, and the rows inside a group are paged by the
|
|
723
|
+
server.
|
|
724
|
+
|
|
725
|
+
Before, a grouped grid fetched one window and bucketed it in the browser, so
|
|
726
|
+
both the group set and every header number were properties of the page. On a
|
|
727
|
+
store of 186 records over five business units sized 86 / 61 / 31 / 7 / 1 with a
|
|
728
|
+
100-row page it rendered **two** headers (86, 14) when the rows were stored
|
|
729
|
+
contiguously and five page slices (31 / 31 / 30 / 7 / 1) when interleaved — and
|
|
730
|
+
the records past the window were not on a second page, they were unreachable.
|
|
731
|
+
The same store now renders five headers reading 86 / 61 / 31 / 7 / 1 in either
|
|
732
|
+
order, and every record is reachable through its group's own pager.
|
|
733
|
+
|
|
734
|
+
- **`@object-ui/types`** — `DataSource` gains an optional
|
|
735
|
+
`queryGroupHeaders(resource, query)`: it answers the group header query
|
|
736
|
+
`@objectstack/spec/ui`'s `compileListViewGroupQuery` compiles (an
|
|
737
|
+
`EngineAggregateOptions`) with one header row per group. Presence is the
|
|
738
|
+
capability; a source that cannot answer it leaves it undeclared.
|
|
739
|
+
- **`@object-ui/data-objectstack`** — `ObjectStackAdapter.queryGroupHeaders`
|
|
740
|
+
posts the compiled query verbatim to the existing `POST /data/:object/query`
|
|
741
|
+
door and answers its `records`. It never degrades: a refusal throws, and a
|
|
742
|
+
body without `records` is refused rather than read as "no groups".
|
|
743
|
+
- **`@object-ui/plugin-grid`** — a grouped grid that fetches its own rows from a
|
|
744
|
+
source declaring `queryGroupHeaders` asks for its headers (one compiled query
|
|
745
|
+
per grouping level) and pages each **open** group's rows with the group's
|
|
746
|
+
own compiled row query (`compileListViewGroupRowsQuery`: the view's filter AND
|
|
747
|
+
the group key, `limit` / `offset` per group); a collapsed group costs no row
|
|
748
|
+
query, `aggregations` are the header query's numbers, and a reference-typed
|
|
749
|
+
grouping key is labelled from the referenced record. Such a grid's column
|
|
750
|
+
sort is the server's, and it no longer shows the `Partial` marker: its counts
|
|
751
|
+
are the query's own. Rows handed in whole are still grouped in the browser
|
|
752
|
+
(exact), and a source with no header query still groups the page it fetched
|
|
753
|
+
and still marks it partial.
|
|
754
|
+
- **`@object-ui/plugin-list`** — `ListView` hands a grouped grid its own fetch,
|
|
755
|
+
with the view's effective filter, when the data source can answer the header
|
|
756
|
+
query, and draws no record-count bar over it. While a toolbar search is
|
|
757
|
+
active it keeps hosting the rows as before: `$search` has no counterpart on
|
|
758
|
+
the header query.
|
|
759
|
+
|
|
760
|
+
**Breaking semantics (declared `minor` per this repo's version policy):**
|
|
761
|
+
|
|
762
|
+
- A grouped grid over a capable source sends different requests: one header
|
|
763
|
+
query per grouping level plus one row query per open group, instead of one
|
|
764
|
+
window. A host that counted or mocked the single window sees the new shape.
|
|
765
|
+
- `useGroupedData` takes an optional fifth argument (the server's header rows).
|
|
766
|
+
- `GroupEntry` gains two REQUIRED members: `count` (the group's size — use it
|
|
767
|
+
instead of `rows.length`, which is empty on a server-grouped grid) and
|
|
768
|
+
`keyValues`. Code that only READS a `GroupEntry` is unaffected; code that
|
|
769
|
+
CONSTRUCTS a `GroupEntry` literal no longer type-checks until it supplies
|
|
770
|
+
both.
|
|
771
|
+
- `AggregationResult.value` is `number | null`, `null` being what the server's
|
|
772
|
+
aggregate answered over no values (rendered as a dash, never invented as 0).
|
|
773
|
+
- `@object-ui/plugin-grid` and `@object-ui/data-objectstack` now require
|
|
774
|
+
`@objectstack/spec` `^17.4.0`, the first release that ships the group-query
|
|
775
|
+
compilers.
|
|
776
|
+
|
|
777
|
+
This supersedes the last paragraph of the pending
|
|
778
|
+
`7189-grouped-grid-partial-disclosure` changeset, which said server-side
|
|
779
|
+
grouping was not built: it now is, and the `Partial` marker described there
|
|
780
|
+
remains only for the data sources that cannot answer the header query.
|
|
781
|
+
|
|
782
|
+
⚠️ **Dated note, 2026-09-28 — a source with no header query now refuses
|
|
783
|
+
grouping — objectui#10881.** Later in this same release two sentences above
|
|
784
|
+
stopped describing the grid: that a source with no header query still groups
|
|
785
|
+
the page it fetched and still marks it partial, and that the `Partial` marker
|
|
786
|
+
remains for the data sources that cannot answer the header query. Maintainer
|
|
787
|
+
ruling F retired both. Over such a source a grouped grid that fetches its own
|
|
788
|
+
rows, and a `ListView` hosting a grouped grid, refuse grouping with an error
|
|
789
|
+
naming `queryGroupHeaders`, and the marker is gone everywhere. Rows handed in
|
|
790
|
+
whole are still grouped in the browser. The rest of this entry is kept as the
|
|
791
|
+
reading of this change; the `10881-grouping-needs-header-query` entry states
|
|
792
|
+
what ships.
|
|
793
|
+
- 6a99bb2: `createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
|
|
794
|
+
interface (objectui#7323).
|
|
795
|
+
|
|
796
|
+
The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
|
|
797
|
+
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
|
|
798
|
+
— the loss was entirely on the reading side. Measured against the shipped
|
|
799
|
+
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
|
|
800
|
+
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
|
|
801
|
+
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
|
|
802
|
+
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
|
|
803
|
+
nine reads are on this package's README API Reference list, and four whole README
|
|
804
|
+
sections are built on them; the ninth measured read is the one the factory's own JSDoc
|
|
805
|
+
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
|
|
806
|
+
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
|
|
807
|
+
too and was documented all along; it simply was not one of the reads the card's
|
|
808
|
+
reproduction measured. So the file's own doc comment pointed the reader at a method its
|
|
809
|
+
declared return hid, and the two documented ways to obtain the same object — the factory
|
|
810
|
+
and `new ObjectStackAdapter(…)` — handed back different type surfaces.
|
|
811
|
+
|
|
812
|
+
**What the declared return now is: the whole class, not those nine reads.** The nine
|
|
813
|
+
above are what the reproduction measured, not the size of this change. The factory's
|
|
814
|
+
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
|
|
815
|
+
class** is part of what the factory promises. Against `DataSource` that is **20**
|
|
816
|
+
members, not nine — `tsc`-computed as
|
|
817
|
+
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
|
|
818
|
+
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
|
|
819
|
+
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
|
|
820
|
+
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
|
|
821
|
+
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
|
|
822
|
+
`updateDashboard`. The eleven past the documented nine were already in the shipped class
|
|
823
|
+
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
|
|
824
|
+
already reachable through `new ObjectStackAdapter(…)` and through every
|
|
825
|
+
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
|
|
826
|
+
here is what the **factory declares**, not what the package ships. Two are escape-hatch
|
|
827
|
+
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
|
|
828
|
+
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.
|
|
829
|
+
|
|
830
|
+
**Branch taken: A (widen the factory's declared return), and why.** The card offered
|
|
831
|
+
three. B — moving caching, connection state and batch progress onto `DataSource` — was
|
|
832
|
+
rejected because those are this adapter's concerns, not every data source's; every other
|
|
833
|
+
`DataSource` implementation would then declare members it does not have. C — documenting
|
|
834
|
+
a cast — teaches a cast around a declaration that is merely narrower than the value,
|
|
835
|
+
which is the opposite of `declared = enforced`. A is one line and makes declared match
|
|
836
|
+
shipped for every documented member at once.
|
|
837
|
+
|
|
838
|
+
Two questions decided the shape and both were answered from the code before the diff.
|
|
839
|
+
`ObjectStackAdapter` was **already** exported from the package's only entry
|
|
840
|
+
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
|
|
841
|
+
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
|
|
842
|
+
by name) — so widening the return exports nothing by implication. And the narrow return
|
|
843
|
+
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
|
|
844
|
+
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
|
|
845
|
+
factory's own config bag left the members that observe those features off the factory's
|
|
846
|
+
declared return in the same change.
|
|
847
|
+
|
|
848
|
+
**One caller shape breaks: a structural stand-in for the factory's return.** A
|
|
849
|
+
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
|
|
850
|
+
longer satisfies that type, because it is now a class with private members (TS2740) —
|
|
851
|
+
annotate such a fake as `DataSource` instead, which is what it was standing in for.
|
|
852
|
+
Nothing else moves: a wider return is assignable to the narrower annotation, so
|
|
853
|
+
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
|
|
854
|
+
the narrow surface to anyone who wants it.
|
|
855
|
+
|
|
856
|
+
The README's note saying the page could not yet teach the factory's shape is removed, and
|
|
857
|
+
the four sections built on the adapter-only members (Metadata Caching, Connection State
|
|
858
|
+
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
|
|
859
|
+
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
|
|
860
|
+
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
|
|
861
|
+
way: its prose, its factory signature fragment and its "hold the class type to reach
|
|
862
|
+
these" section described the old narrow return, and its Mutations and Troubleshooting
|
|
863
|
+
examples told the reader to construct the class by hand to reach members the factory now
|
|
864
|
+
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
|
|
865
|
+
inverted,
|
|
866
|
+
with two controls: the adapter-only members stay absent from `DataSource` (fires on
|
|
867
|
+
option B), and the widened return stays assignable to `DataSource` (swappability kept).
|
|
868
|
+
- a915064: `listImportMappings` no longer renders a refused door as "no mapping is registered"
|
|
869
|
+
(objectui#7741).
|
|
870
|
+
|
|
871
|
+
`ObjectStackAdapter.listImportMappings` degrades every failure to an empty list, and the
|
|
872
|
+
import wizard hides its saved-mapping selector on an empty list. So "the server served
|
|
873
|
+
zero mappings" and "the server refused, or broke" produced the identical UI on every
|
|
874
|
+
deployment — the feature simply absent — with a `console.warn` as the only
|
|
875
|
+
discriminator, in the browser console, with nothing pointing at it. That silence did not
|
|
876
|
+
merely hide a fault: it produced a confident WRONG diagnosis in a careful reporter
|
|
877
|
+
(objectstack#14026 was filed, routed and worked by two seats against a wizard that had
|
|
878
|
+
been correct since `@object-ui/data-objectstack@17.1.0`).
|
|
879
|
+
|
|
880
|
+
**The empty-list return is unchanged.** `listImportMappings` still answers
|
|
881
|
+
`Promise<any[]>` and still never throws, on every arm including the loud ones — this is
|
|
882
|
+
a channel added ALONGSIDE that contract, not a change to it.
|
|
883
|
+
|
|
884
|
+
- **New: `ObjectStackAdapter.onMetadataReadWarning(cb)`** — a subscribe/unsubscribe
|
|
885
|
+
channel, sibling in shape to `onWriteWarning` and `onSaveAdvisory`. It fires when a
|
|
886
|
+
metadata read failed in a way that is NOT the supported "this deployment does not
|
|
887
|
+
serve that kind" shape, carrying `MetadataReadWarningEvent`: which read it was, the
|
|
888
|
+
object, whether the server `refused` this caller or the answer was `unreadable`, and
|
|
889
|
+
the server's own ADR-0112 code, HTTP status and message.
|
|
890
|
+
- **New: `classifyImportMappingsFailure(err)`** and `ImportMappingsFailureKind`, exported
|
|
891
|
+
so a consumer can apply the same verdict. It reads the ERROR — the ADR-0112 `code`
|
|
892
|
+
first, the status only where no code was declared — and never "is the result an empty
|
|
893
|
+
array", which is what both conditions produce and so can never tell them apart.
|
|
894
|
+
- **The older-server case stays quiet.** A deployment that does not serve the `mapping`
|
|
895
|
+
kind (404/501 with no route, `ROUTE_NOT_FOUND`, `NOT_IMPLEMENTED`, or the metadata list
|
|
896
|
+
door's 400 `INVALID_REQUEST`) still degrades to an empty list with no selector and no
|
|
897
|
+
event. That is a real, supported deployment shape and it must not become a visible
|
|
898
|
+
fault.
|
|
899
|
+
- **The console now says so.** `AdapterProvider` subscribes to the new channel and
|
|
900
|
+
renders a warning toast naming the object, the remedy and the server's own words, so a
|
|
901
|
+
user without devtools open can tell "there are none" from "we could not find out".
|
|
902
|
+
Three new `console.importMappings*` keys ship in all ten locale packs.
|
|
903
|
+
|
|
904
|
+
This applies framework objectstack#13906 decision 1 option A — *a thing that could not be READ is
|
|
905
|
+
not a thing that is ABSENT* — at this seam. It is an already-adopted discrimination, not
|
|
906
|
+
a new principle.
|
|
907
|
+
- 53ded82: Array filters on analytics aggregates were posted un-lowered and refused by the runtime with 400; they are now lowered to the canonical `FilterCondition` before the wire.
|
|
908
|
+
|
|
909
|
+
An `element:number` or an `object-metric` whose filter is authored as an array (`[{ field, operator, value }, ...]`, a comparison tuple, or an `and`/`or` group) reached `POST /analytics/query` as an array. That route parses the body with `AnalyticsQueryRequestSchema` first, and its `where` is a `FilterCondition`, so the widget answered `400 Invalid AnalyticsQuery body: where: ...` instead of its number — leaving the MongoDB-style record as the only authoring form that still worked.
|
|
910
|
+
|
|
911
|
+
`aggregate()` now lowers the array through `parseFilterAST`, the single sink `@objectstack/spec` names for turning a `FilterArray` into a `FilterCondition`, so the posted `where` is the shape the wire declares. An empty array posts no `where` at all, a record-shaped filter is unchanged, and an array the sink cannot lower — an infix join such as `[condA, 'or', condB]` — is refused with this adapter's `INVALID_FILTER` / 400 error rather than posted or silently dropped into an unfiltered aggregate.
|
|
912
|
+
- 9662aca: Apply the object-metadata write invariant at the write DOORS instead of at the writers.
|
|
913
|
+
|
|
914
|
+
objectui#7714 ruled that a half-filled relationship stays client-side and the PUT body never
|
|
915
|
+
carries one without a non-empty `reference`, and implemented that ruling by naming the two
|
|
916
|
+
writers it knew of. The identical defect was then reproduced on a third (fixed in `9073cf018`); a
|
|
917
|
+
sweep found nine more. The doors — the three places in this repo that actually PUT `/meta/:type/:name` —
|
|
918
|
+
now apply the invariant themselves, so every writer is covered without any list of writers
|
|
919
|
+
existing anywhere, and a new door is caught by a gate that derives the door set from the
|
|
920
|
+
tree rather than restating it.
|
|
921
|
+
|
|
922
|
+
Behaviour change for consumers: `MetadataClient.save('object', …)` now throws BEFORE issuing
|
|
923
|
+
the request when the body carries a relationship field with a missing, empty or whitespace-only
|
|
924
|
+
`reference`. The same document is refused by the server with a 422 on `fields.NAME.reference`,
|
|
925
|
+
so nothing that previously succeeded now fails — the refusal moves earlier, names the field,
|
|
926
|
+
and leaves the draft in the client instead of wedging every later save of that object. Writes
|
|
927
|
+
of every other metadata type are untouched.
|
|
928
|
+
|
|
929
|
+
New export from `@object-ui/data-objectstack`: `assertObjectMetadataWritable`,
|
|
930
|
+
`RELATIONSHIP_TYPES_REQUIRING_REFERENCE` and `OBJECT_METADATA_TYPE`.
|
|
931
|
+
- abf710d: Lower the TRUE-identity filter combinators to "no constraint" instead of handing the
|
|
932
|
+
caller's own object back (objectui#8770).
|
|
933
|
+
|
|
934
|
+
`convertFiltersToAST({ $and: [] })`, `{ $or: [{}] }` and `{ $and: [{}] }` returned the
|
|
935
|
+
INPUT OBJECT unchanged. `lowerLogicalGroup` correctly answers `undefined` for a group
|
|
936
|
+
that reduces to the TRUE identity — objectstack#5322 rules all three "every row", and a
|
|
937
|
+
childless `['and']` would be `isFilterAST` FALSE — but when such a group was the only
|
|
938
|
+
thing in the filter, that `undefined` fell through to the general tail
|
|
939
|
+
(`if (conditions.length === 0) return filter`) and the group reappeared one level up, in
|
|
940
|
+
the `$` dialect, in the slot the AST occupies. The same function already lowered the
|
|
941
|
+
fourth identity, `{ $or: [] }`, correctly, so this was an internal inconsistency rather
|
|
942
|
+
than an open question; the consumer half was settled by objectui#8513.
|
|
943
|
+
|
|
944
|
+
**This widens what those three filters return, and that is the point.** Measured against
|
|
945
|
+
`@objectstack/spec` 17.4.0 and `@objectstack/client` 17.4.0 before the change: the
|
|
946
|
+
returned object is not sent as a filter and refused — `client.data.find()` tests the
|
|
947
|
+
value with `isFilterAST` and its else branch spreads a plain object's entries as query
|
|
948
|
+
parameters, so `{ $and: [] }` left as `?$and=` with **no `filter` parameter at all**, and
|
|
949
|
+
the server answered `400 UNSUPPORTED_QUERY_PARAM` for the unknown `$`-prefixed
|
|
950
|
+
parameter. A filter whose ruled answer is EVERY ROW was a **failed list**, not a narrowed
|
|
951
|
+
one — so nothing could have been relying on it to scope data. On the sibling
|
|
952
|
+
`$expand` / `$search` route the same object travelled as `filter={"$and":[]}`, which the
|
|
953
|
+
server accepts as a `FilterCondition` and already answers with every row; the two routes
|
|
954
|
+
disagreed about one filter and now agree.
|
|
955
|
+
|
|
956
|
+
**`@object-ui/core`.** `convertFiltersToAST`'s declared return type gains `undefined`,
|
|
957
|
+
which is what `toFilterNode`, `mergeFilterNodes` and `data-objectstack`'s
|
|
958
|
+
`translateFilterToAST` already mean by "no filter, skip the slot". Every call site
|
|
959
|
+
already acted on it. The fold is scoped to a filter whose EVERY key is such a group:
|
|
960
|
+
the same tail also serves `{}`, an all-null filter and an empty operator map, and those
|
|
961
|
+
keep the object they always returned — a null-valued key is this converter's own
|
|
962
|
+
tolerance rather than a ruled identity, and the object it hands back reaches the server
|
|
963
|
+
as a REAL `a IS NULL` predicate on the `$expand` route, so folding it in would return
|
|
964
|
+
more rows on a path the ruling said nothing about.
|
|
965
|
+
|
|
966
|
+
`{ $or: [] }` is untouched: FALSE is not "no constraint", the AST has no contradiction
|
|
967
|
+
literal, and its `['$or', '=', []]` leaf answers FALSE at both consumers.
|
|
968
|
+
|
|
969
|
+
**`@object-ui/data-objectstack`.** `convertQueryParams` skips the `filters` slot when the
|
|
970
|
+
lowering answers `undefined`, the same answer the raw-GET route's
|
|
971
|
+
`if (translated !== undefined)` already gave, so the two `find()` routes cannot disagree
|
|
972
|
+
about one filter.
|
|
973
|
+
|
|
974
|
+
**Migration.** A TypeScript caller that stored `convertFiltersToAST(...)` in a
|
|
975
|
+
`FilterNode | Record<string, any>` slot must widen it with `| undefined` and skip the
|
|
976
|
+
filter when it is absent — the same handling `toFilterNode` has always needed. At
|
|
977
|
+
runtime, a filter that is nothing but TRUE-identity combinators now returns every row
|
|
978
|
+
(what objectstack#5322 rules) instead of failing the request.
|
|
979
|
+
|
|
980
|
+
⚠️ **Dated note, 2026-09-27 — an empty operator map no longer keeps the object — objectui#9164.**
|
|
981
|
+
Later in this same release `convertFiltersToAST` refuses an empty operator map that is all
|
|
982
|
+
a filter says (`{ a: {} }`, `{ a: {}, b: undefined }`, `{ $and: [], a: {} }`) with a
|
|
983
|
+
`FilterOperatorError` instead of returning the object, so the sentence above that it
|
|
984
|
+
"keeps the object" no longer holds for it; an all-null filter had already moved to
|
|
985
|
+
`undefined` with objectui#9020. Only `{}` still comes back as itself. The rest of this
|
|
986
|
+
entry is kept as the reading of this change; the objectui#9164 entry states what an empty
|
|
987
|
+
operator map now answers.
|
|
988
|
+
- 2bf34f7: The console's "app not available" screen now says what it measured, and the by-name app
|
|
989
|
+
probe stopped folding four answers into one (objectui#9262).
|
|
990
|
+
|
|
991
|
+
**BREAKING for consumers of the published `AppAccessVerdict` type.** `probeAppAccess`
|
|
992
|
+
widens by two members ruled by the maintainer: `granted | denied | unknown` becomes
|
|
993
|
+
`granted | denied | not_found | unreachable | unknown`. A consumer that exhaustively
|
|
994
|
+
switches on the verdict gains two cases. The meaning of `unknown` also narrows — it no
|
|
995
|
+
longer means "an absent app, an unreachable server, or an adapter that cannot ask", it
|
|
996
|
+
means only that nothing was asked (a host DataSource without this probe). Code that read
|
|
997
|
+
`unknown` as a positive absence must move to `not_found`.
|
|
998
|
+
|
|
999
|
+
**What users see.** The sentence `This app is not available yet — it may still be
|
|
1000
|
+
publishing. Try again in a moment.` is retired from all ten locale packs. It was asserted
|
|
1001
|
+
for seven distinct causes, only one of which is a publish, and it is the screen an author
|
|
1002
|
+
lands on after any of them — the same defect objectui#4252 split `denied` off this screen
|
|
1003
|
+
for. In its place: `This app can't be opened` for a measured absence, `Couldn't reach the
|
|
1004
|
+
server` for a probe that got no answer, the unchanged denial screen for a permission
|
|
1005
|
+
refusal, and a neutral `App not available` when nothing was measured. All but the denial
|
|
1006
|
+
keep the Retry button, because all but the denial can change on their own. A genuine
|
|
1007
|
+
post-publish lag reads as an absence, and the one forced metadata refresh that gives that
|
|
1008
|
+
lag its chance to resolve is unchanged.
|
|
1009
|
+
|
|
1010
|
+
**Measured before the branches were written.** On a real server the by-name route answers
|
|
1011
|
+
`200` with the declared envelope MINUS its `item` for a name that does not exist — not the
|
|
1012
|
+
`404` this code assumed — so `probeAppAccess` returned `granted` for every typo, every
|
|
1013
|
+
never-created app and every unpublished draft. `granted` fell through to the same screen as
|
|
1014
|
+
`unknown`, which is why nothing showed. Absence is therefore read from the envelope as well
|
|
1015
|
+
as from the `404` code; a `not_found` branch keyed on the status alone would have been dead
|
|
1016
|
+
for the commonest case.
|
|
1017
|
+
- b253c4e: The import wizard's "Download template" button now downloads the server's import template, an Excel workbook, instead of building a CSV of every field (objectui#9600).
|
|
1018
|
+
|
|
1019
|
+
The old CSV listed every field the wizard was handed, including the system and read-only columns every object carries (created by, owner, and so on). The server strips those columns on import without an error, so a user who filled them in lost that data silently. The server's template (`GET /api/v1/data/:object/export?template=true`, objectstack 17.6.0) lists only the columns this caller can import. It marks required ones with `*`, adds dropdowns for select and boolean columns, and includes an instructions sheet.
|
|
1020
|
+
|
|
1021
|
+
- **`@object-ui/types`: new optional `DataSource.downloadImportTemplate(resource)`**, resolving to the template file as a `Blob`. Additive. When a data source lacks it, no template is offered: there is no client-side fallback.
|
|
1022
|
+
- **`@object-ui/data-objectstack`: `ObjectStackAdapter.downloadImportTemplate(resource)`** sends `template=true` on the export route, on the same request path as `exportDownload`. When the client has a locale set (`getClient().setLocale`), the request carries it as `Accept-Language`, the way the import request does. The template's labels are written in the request's locale, and the import accepts a translated label only in its own request's locale, so the two requests must match.
|
|
1023
|
+
- **`@object-ui/plugin-grid`: `ImportWizard` offers the template** when its data source has `downloadImportTemplate` and the user can create records of the object (`usePermissions().can(object, 'create')`, the server's own gate). A 405 (object not open for import), a 403 (no create permission) and any other failure each show a message on the upload step, and no file is saved. The client-built CSV is deleted, with its helpers (`buildImportTemplateCsv`, `exampleForField`, `firstOptionValue`, `downloadTextFile`). The column mapping step is unchanged.
|
|
1024
|
+
- **`@object-ui/app-shell`**: the identity-import data source (`sys_user`) withholds `downloadImportTemplate`, because the server's template describes the generic import door, not the identity pipeline.
|
|
1025
|
+
- **`@object-ui/i18n`**: `grid.import.downloadTemplateHint` describes the Excel template in all ten packs, and `grid.import.templateDownloadFailed` and `grid.import.templateNotPermitted` are new.
|
|
1026
|
+
- d5c1f52: `classifyAnalyticsFailure` now reads a 400 as a refusal of the query body we
|
|
1027
|
+
sent regardless of which ADR-0112 `code` it carries, so `aggregate()` no
|
|
1028
|
+
longer answers a rejected filter with client-side numbers from a different
|
|
1029
|
+
door (objectui#7755).
|
|
1030
|
+
|
|
1031
|
+
Before this fix, only 400 `VALIDATION_FAILED` (and a code-less 400) threw
|
|
1032
|
+
`AnalyticsQueryRejectedError`. Any OTHER coded 400 — `service-analytics` ships
|
|
1033
|
+
its own 400 `INVALID_FILTER` on a filter shape it refuses — matched none of
|
|
1034
|
+
`classifyAnalyticsFailure`'s branches and fell through to `unknown`, which
|
|
1035
|
+
`aggregate()`'s catch has no arm for, so it silently degraded to
|
|
1036
|
+
`aggregateViaFind`: a re-read through `find()`'s `$filter` query-string
|
|
1037
|
+
contract, which accepts array shapes the analytics request body does not. A
|
|
1038
|
+
filter the analytics route refused could still be answered — with a
|
|
1039
|
+
plausible, wrong number, and no sign the request had a defect.
|
|
1040
|
+
|
|
1041
|
+
The fix is a floor UNDER the existing code branches, not a replacement for
|
|
1042
|
+
them: `NOT_IMPLEMENTED` / `ROUTE_NOT_FOUND` still win `not-installed`,
|
|
1043
|
+
`VALIDATION_FAILED` / `UNAUTHENTICATED` / `CUBE_NOT_FOUND` still win their own
|
|
1044
|
+
outcomes first (objectui#5721). Only a 400 that none of those four already
|
|
1045
|
+
claimed now falls to the new floor instead of past it. An unmatched NON-400
|
|
1046
|
+
coded error (e.g. a coded 5xx) is unaffected and keeps degrading exactly as
|
|
1047
|
+
before — this fix is scoped to the 400 case only.
|
|
1048
|
+
- b2065e7: Classify `/analytics/query` failures by their ADR-0112 `code` rather than their HTTP status, so a chart is no longer answered from a different code path behind the wrong explanation.
|
|
1049
|
+
|
|
1050
|
+
`classifyAnalyticsFailure` tested `status === 404 || status === 501` before the code operands on the same line, so the status short-circuited every one of them: any 404 on this face was classified "the analytics capability is not installed" whatever code it carried, and `NOT_IMPLEMENTED` / `ROUTE_NOT_FOUND` were unreachable for the conditions they name. Three unrelated conditions answer 404 on this url, so the status cannot tell them apart — the `code` is the contract.
|
|
1051
|
+
|
|
1052
|
+
Two conditions change behaviour:
|
|
1053
|
+
|
|
1054
|
+
- **404 `CUBE_NOT_FOUND`** (a misspelled or unregistered cube — an authoring mistake) now **throws** the server's own error verbatim, keeping `code` and the producer's repair instructions. It previously warned "install `@objectstack/service-analytics`" and silently degraded to `find()` + client-side aggregation — which cannot answer it anyway, because the fallback re-reads the same name through `/data`, where an unregistered object is a 404 `OBJECT_NOT_FOUND`.
|
|
1055
|
+
- **401 `UNAUTHENTICATED`** (an anonymous or lapsed session) now **throws** `AnalyticsUnauthenticatedError` instead of degrading silently behind a `find()` that is about to be refused the same way.
|
|
1056
|
+
|
|
1057
|
+
Unchanged: `NOT_IMPLEMENTED` / `ROUTE_NOT_FOUND` and code-less 404/501 answers still degrade loudly to the client-side fallback, 400 `VALIDATION_FAILED` still throws, and 5xx / network failures still degrade silently.
|
|
1058
|
+
- b470e91: The metadata designer states its package on the publish step, not only on the save
|
|
1059
|
+
|
|
1060
|
+
Studio's designer save→publish loop bound the draft to a software package on the
|
|
1061
|
+
save (`PUT ?mode=draft&package=<id>`) and then sealed it with a publish that named
|
|
1062
|
+
no package at all. objectstack `9e04c3e35` (shipped in `@objectstack/rest` 17.2.0) taught
|
|
1063
|
+
`POST /meta/:type/:name/publish` to accept `?package=<id>`, so the second call can now
|
|
1064
|
+
state the same binding the first one already states.
|
|
1065
|
+
|
|
1066
|
+
- `MetadataClient.publish()` accepts `packageId` and sends `?package=<id>`, the same
|
|
1067
|
+
wire spelling and the same `encodeURIComponent` treatment `save()` gives it.
|
|
1068
|
+
- `MetadataResourceEditPage` reads the binding for BOTH steps from one derivation
|
|
1069
|
+
(`readActivePackageBinding`), so the two calls of one loop cannot drift apart. The
|
|
1070
|
+
`?package=all` "show everything" scope keeps folding to "no package".
|
|
1071
|
+
|
|
1072
|
+
The parameter is **omitted**, never sent empty, when the designer holds no binding.
|
|
1073
|
+
Empty and absent are the same to the framework's normaliser today, but absent is the
|
|
1074
|
+
shape the save door already followed, and the framework's promotion path branches on
|
|
1075
|
+
the key being present downstream.
|
|
1076
|
+
|
|
1077
|
+
What this buys is **reachability**, not speed: it lets `#9612`'s package-closure
|
|
1078
|
+
narrowing at the runtime publish gate fire on an HTTP-driven promotion at all. That
|
|
1079
|
+
narrowing has a second, independent gate this does not touch — objects carrying no
|
|
1080
|
+
`_packageId` provenance are kept unconditionally — so on a tenant-authored overlay
|
|
1081
|
+
corpus stating the package still narrows nothing.
|
|
1082
|
+
|
|
1083
|
+
⚠️ **Dated note, 2026-09-29 — the publish-gate narrowing is an objectstack card — objectui#11016.** The last paragraph
|
|
1084
|
+
names the package-closure narrowing by a bare number, which in this repository
|
|
1085
|
+
resolves to an unrelated objectui pull request. It is
|
|
1086
|
+
objectstack-ai/objectstack#9612, the runtime publish gate's rule snapshot. The text above is kept as the reading of this change.
|
|
1087
|
+
- fffa30d: `listViews` no longer renders a refused metadata read as "this object has no saved views"
|
|
1088
|
+
(objectui#8151).
|
|
1089
|
+
|
|
1090
|
+
`ObjectStackAdapter.listViews` degrades every failure to an empty list, and every consumer
|
|
1091
|
+
reads only the return. So "the server served zero saved views" and "the server refused, or
|
|
1092
|
+
broke" produced the identical UI — with a `console.warn` as the only discriminator, in the
|
|
1093
|
+
browser console, with nothing pointing at it. It is the same defect objectui#7741 removed
|
|
1094
|
+
from `listImportMappings` one method over, and the user-visible cost is the higher one: an
|
|
1095
|
+
empty `listViews` is an object's **view switcher**, so a user whose token lapsed
|
|
1096
|
+
mid-session could be shown an object that appears to have no saved views at all —
|
|
1097
|
+
including views they created themselves.
|
|
1098
|
+
|
|
1099
|
+
**The empty-list return is unchanged.** `listViews` still answers `Promise<any[]>` and
|
|
1100
|
+
still never throws, on every arm including the loud ones — this is a channel added
|
|
1101
|
+
ALONGSIDE that contract, not a change to it. `listImportMappings` is likewise unchanged,
|
|
1102
|
+
down to its wording.
|
|
1103
|
+
|
|
1104
|
+
- **`ObjectStackAdapter.listViews` now emits on `onMetadataReadWarning`** — the channel
|
|
1105
|
+
objectui#7741 added — when the read failed in a way that is not the supported "this host
|
|
1106
|
+
mounted no metadata door" shape. The event carries the object, whether the server
|
|
1107
|
+
`refused` this caller or the answer was `unreadable`, and the server's own ADR-0112
|
|
1108
|
+
code, HTTP status and message.
|
|
1109
|
+
- **New: `classifyViewsFailure(err)`.** A SEPARATE reading, deliberately not a second
|
|
1110
|
+
caller of `classifyImportMappingsFailure`: `view`'s quiet set is strictly smaller. The
|
|
1111
|
+
arm the mapping classifier is built around — 400 `INVALID_REQUEST`, the metadata list
|
|
1112
|
+
door's "this deployment carries no such kind" — is unreachable for `view`, which is in
|
|
1113
|
+
the platform's static spelling contract, so reading it as kind-absence would swallow a
|
|
1114
|
+
real refusal. On `view`, only a host with no `/meta` door at all stays quiet.
|
|
1115
|
+
- **`MetadataReadWarningEvent`'s `operation` and `kind` gain their second members**
|
|
1116
|
+
(`'listViews'` / `'view'`). This is the additive, reviewed widening the single-member
|
|
1117
|
+
unions were designed for, and it worked as designed: the consumer that renders these
|
|
1118
|
+
events had a `switch` naming one operation, so the widening turned "a views failure is
|
|
1119
|
+
toasted as an import-mapping failure" into a compile error rather than a runtime lie.
|
|
1120
|
+
- **New: `MetadataReadFailureKind`**, the neutral spelling of the three verdicts.
|
|
1121
|
+
`ImportMappingsFailureKind` is now an alias of it — identical members, so existing
|
|
1122
|
+
consumers are unaffected in both directions.
|
|
1123
|
+
- **The console says which list it was.** `metadataReadWarningToast` picks its title and
|
|
1124
|
+
its remedy by `operation`, so a failed view read reads *"Saved views for {{object}}
|
|
1125
|
+
could not be loaded … not because this object has no saved views"*. Three new
|
|
1126
|
+
`console.savedViews*` keys ship in all ten locale packs; the `console.importMappings*`
|
|
1127
|
+
copy is untouched.
|
|
1128
|
+
|
|
1129
|
+
This applies framework objectstack#13906 decision 1 option A — *a thing that could not be READ is not
|
|
1130
|
+
a thing that is ABSENT* — at the second seam that needed it.
|
|
1131
|
+
- 8e00bfd: **Breaking (published surface):** remove `options.actor` from `MetadataClient`'s
|
|
1132
|
+
`save`, `reset`, `publish` and `rollback`, and stop emitting the `X-Actor`
|
|
1133
|
+
request header.
|
|
1134
|
+
|
|
1135
|
+
The server stopped honouring that header. objectstack#7941 ruled that the
|
|
1136
|
+
recorded actor is the identity the request was authorized as, and removed the
|
|
1137
|
+
header limb from the `/meta` write resolver — attribution cannot drift from
|
|
1138
|
+
authorization. The option therefore typed cleanly, sent a header, and could not
|
|
1139
|
+
influence the audit or history row it appeared to address: a false affordance
|
|
1140
|
+
that promised attribution and silently failed to deliver it.
|
|
1141
|
+
|
|
1142
|
+
Three declarations go: `MetadataClientSaveOptions.actor` (inherited by
|
|
1143
|
+
`MetadataDeleteOptions` via `extends`, so it served both `save` and `reset`),
|
|
1144
|
+
and the inline `{ actor?: string }` on each of `publish` and `rollback`.
|
|
1145
|
+
`MetadataAuditEntry.actor` is unaffected — that is the server's read-back of
|
|
1146
|
+
who acted, and it remains the way to see attribution.
|
|
1147
|
+
|
|
1148
|
+
Marked `minor` rather than `major` per this repo's version-alignment policy
|
|
1149
|
+
(the fixed group's major tracks `@objectstack`, and `major` in a changeset
|
|
1150
|
+
would drag all 39 packages off that cadence).
|
|
1151
|
+
|
|
1152
|
+
No caller in this repo passed `actor`; the census found the only in-repo
|
|
1153
|
+
occurrence was the client's own unit test. Callers outside this repo that still
|
|
1154
|
+
pass it are unaffected at runtime beyond losing a header the server already
|
|
1155
|
+
ignored — the property is dropped rather than forwarded, pinned by
|
|
1156
|
+
`metadata-actor-retired-4834.pin.test.ts`.
|
|
1157
|
+
- fd8dace: Studio surfaces the runtime authoring gate's advisory findings after a **publish**, not only after a save
|
|
1158
|
+
|
|
1159
|
+
objectui#4133 / PR #4236 wired the gate's advisories to the save door and recorded, honestly, what that left unsurfaced: Studio's designer stages every edit as a `mode: 'draft'` save, drafts are never gated (the framework returns at its D1 early-return before a single rule runs), and the publish step that *is* gated returned no `advisories` field at all. So on the flow most tenants actually use, the author was told nothing at either door — for two different reasons, only one of which was objectui's.
|
|
1160
|
+
|
|
1161
|
+
The second reason has expired. `PublishMetaItemResponseSchema` now declares the same optional, omitted-when-empty `advisories` key that `SaveMetaItemResponseSchema` has carried since #4717, and `publishMetaItem` populates it. Measured against the installed `@objectstack/spec` (17.2.0) rather than inferred from the version number: the key survives a `safeParse`, a half-shaped finding is rejected, and a clean publish omits the key entirely. That reading is now a test rather than a note, so a spec drift fails CI instead of silently re-muting the door.
|
|
1162
|
+
|
|
1163
|
+
⚠️ **Dated note, 2026-09-29 — the save response's advisories key came from an objectstack card — objectui#11016.** The
|
|
1164
|
+
paragraph above dates the save response's `advisories` key by a bare number,
|
|
1165
|
+
which in this repository resolves to an unrelated objectui item. It is
|
|
1166
|
+
objectstack-ai/objectstack#4717, the card that put the runtime authoring gate's
|
|
1167
|
+
advisory findings on the response. The text above is kept as the reading of this change.
|
|
1168
|
+
|
|
1169
|
+
`MetadataClient.publish` and `MetadataClient.publishDraft` — the two methods over the single-item publish route `POST /meta/:type/:name/publish` — now report through the **same** sink, the same event and the same renderer the save door already used. No new UI shape: same warning tier, same 10s duration, same per-finding `rule` + `message` + `hint` formatting, findings still rendered verbatim as server prose. The wiring lands in the data layer rather than at the call sites, so `ResourceEditPage`'s Publish button and the runtime `RuntimeDraftBar` promotion (ObjectView / ReportView / DashboardView) are covered by one change, as are future ones.
|
|
1170
|
+
|
|
1171
|
+
One thing had to differ, and it is the frame's verb. Save and Publish are two different buttons in this product, so a toast that says "Saved" after a Publish tells the author their change is still a draft — the opposite of what happened. `MetadataSaveAdvisoryEvent` therefore gains a required `door: 'save' | 'publish'` and the renderer picks `console.publishAdvisoryTitle` (added to all ten locale packs) accordingly. `door` exists because `mode` cannot answer this: a direct active save and a draft promotion both report `mode: 'publish'`, since both land the body in the active overlay. It is required rather than optional so a future third door cannot be wired without saying which one it is, and the renderer branches on it through an exhaustive switch with a `never` check, so adding a third member is a compile error rather than a silently wrong verb.
|
|
1172
|
+
|
|
1173
|
+
**BREAKING for event constructors — `MetadataSaveAdvisoryEvent.door` is required.** Reading the event is unaffected: a listener that ignores `door` behaves exactly as before, and every other member is unchanged. Constructing one is a compile break — a door-less event literal that type-checked before now fails with TS2741, `Property 'door' is missing`. Measured on the emitted `dist/index.d.ts` of `@object-ui/data-objectstack` on both sides: that single required member is the entire non-comment delta of the package's published surface. **Migration:** add `door: 'save'` or `door: 'publish'` to the literal, whichever write it models — `'save'` for `PUT /meta/:type/:name`, `'publish'` for `POST /meta/:type/:name/publish`. Scored `minor` rather than `major` per the repo's version policy: objectui's major is pinned to `@objectstack`'s so that "same major means compatible" holds across the two repos, so objectui's own breaking changes ship as `minor` with the break named here (`scripts/check-changeset-no-major.mjs`). Every publishable package sits in one `fixed` group, so this entry carries the group.
|
|
1174
|
+
|
|
1175
|
+
Unchanged, deliberately: the **batch** door. "Publish whole app" (`POST /packages/:id/publish-drafts`) discarded per-draft advisories server-side when this change was written — objectstack#9343, open and unruled at the time — and nothing here compensated for that from the client side. A test pinned the absence, so a later traversal of a batch-shaped `published[]` could not be added without turning it red.
|
|
1176
|
+
|
|
1177
|
+
*Corrected before release (`ce986aafc`): both present-tense claims in the paragraph above went false after it was written — objectstack#9343 landed, the batch response now carries per-draft advisories, and `ce986aafc` routes that door through the same seam and flips the absence pin to a presence pin. The paragraph is kept in the past tense as the record of what this change did and did not do; this note is what the CHANGELOG publishes instead of a sentence that was true only while it sat here.*
|
|
1178
|
+
- 2d36552: Pins `@objectstack/spec`, `@objectstack/client`, `@objectstack/formula` and `@objectstack/lint` to `17.1.0`, and adapts the two consumer surfaces the new build moves.
|
|
1179
|
+
|
|
1180
|
+
The pin itself is a lockfile refresh — every manifest already declared `^17.0.0`, which admits `17.1.0`, so no dependency range changed. All four move together: a split resolution is what produced the dual-version spec graph that reddened `check:spec-symbols` in this repo's history.
|
|
1181
|
+
|
|
1182
|
+
**A `icontains` filter now reaches the driver as a filter.** `icontains` is a canonical `VIEW_FILTER_OPERATORS` member as of `17.1.0`, so an author can declare it on a `ViewFilterRule` and the spec validates it — but `@object-ui/data-objectstack`'s alias table had no row for it, and an unmapped operator is how this adapter shipped an unfiltered query before (objectstack#3948). It is an identity row like `contains`: `icontains` is itself a member of `VALID_AST_OPERATORS`, so the spelling the author writes is the spelling the AST takes, and no case-sensitivity is translated away. Declared rather than left to the table's `?? op` fall-through, on the rule its own parity test states — the AST gate accepting a spelling is not the driver compiling it into a `WHERE` clause.
|
|
1183
|
+
|
|
1184
|
+
The same operator reaches the list view's own bridge: `@object-ui/plugin-list`'s `mapOperator` gains an explicit `icontains` arm. The emitted spelling is identical to the input, but the arm is written out rather than left to the `default` passthrough — `icontains` is its own member of `VALID_AST_OPERATORS`, so a raw passthrough is accepted *today*, and depending on that coincidence is what the bridge's own parity test records as how it once stopped discriminating.
|
|
1185
|
+
|
|
1186
|
+
`@object-ui/core` adds `onSuccess` to its spec key inventory, so an author writing the key `17.1.0` now declares is no longer warned that it is unknown. That is a diagnostic statement only — the four declared action surfaces still drop the key before it reaches the runner, which is tracked separately.
|
|
1187
|
+
|
|
1188
|
+
**A stored view filtering case-insensitively still shows that operator when it is reopened.** `@object-ui/plugin-view`'s canonical-to-builder table is keyed by `ViewFilterOperator`, so `17.1.0` adding `icontains` failed to compile rather than letting the operator reach the FilterBuilder as a raw spelling its dropdown cannot select. It maps to the builder's `containsCaseInsensitive` — the id that authors the spec's `$icontains` — and deliberately not to `contains`, which would quietly rewrite a case-insensitive filter into a case-sensitive one the next time the view was saved.
|
|
1189
|
+
|
|
1190
|
+
**The page-editor palette keeps one entry per renderer.** `17.1.0` retires `element:filter` from `PageComponentType` and adds `record:discussion`, leaving the member count at 34 either side — so the swap is invisible to any count-based reading. The stale `element:filter` exclusion is dropped, and `record:discussion` is excluded because it is the *same renderer* as the already-offered `record:chatter`, not because it is unauthorable. Nothing the palette offers changes.
|
|
1191
|
+
|
|
1192
|
+
**The console eager-closure ceiling is re-baselined, by maintainer ruling.** The release is roughly 930 KB larger uncompressed and nearly all of it lands in `vendor-objectstack-*.js`, which put the closure past a ceiling that was deliberately sized to catch a 89 KiB regression — the gate refused the bump, correctly. Raising it was escalated rather than taken locally, because gate-strength policy had been ruled the maintainer's; the ruling on objectui#5531 authorised the raise. `MAX_EAGER_CLOSURE_GZIP_BYTES` and the `BASELINE` it is derived from move together in one commit, keeping headroom at 2.00% and below the 91,136-byte regression size the gate must still catch. The gate's *sensitivity* is untouched: a repeat of that regression from the new baseline still fails. No behaviour ships from this file — it is CI policy, recorded here because the version it governs is the one this changeset publishes.
|
|
1193
|
+
|
|
1194
|
+
### Patch Changes
|
|
1195
|
+
|
|
1196
|
+
- 9ec8ceb: Views that an earlier "Edit view config → Save" made read-only recover on read, and keep
|
|
1197
|
+
their edits (objectui#10210).
|
|
1198
|
+
|
|
1199
|
+
A view row now counts as a personalization overlay only when it carries the `_isOverride`
|
|
1200
|
+
marker. The view list used to treat a flat row carrying `viewKind: 'list'` as an overlay
|
|
1201
|
+
too, a shape guess kept for toolbar rows written before the marker existed. A config save
|
|
1202
|
+
on a code-defined view stored exactly that shape, so the view dropped out of the saved
|
|
1203
|
+
views and its tab turned read-only, for good once the draft was published. With the guess
|
|
1204
|
+
retired, such a row reads back as the saved view it is: its tab is editable again and shows
|
|
1205
|
+
what the save stored. Nothing is rewritten at rest; the next read is enough.
|
|
1206
|
+
|
|
1207
|
+
⚠️ This exposes one class of row: overlay rows written before the marker
|
|
1208
|
+
(objectui#4227, closed 2026-08-15) and never touched since. They have the same shape, so
|
|
1209
|
+
they now also read as plain rows, and their frozen label, columns and filter copy covers
|
|
1210
|
+
the code definition again. No deployment is named as holding one.
|
|
1211
|
+
- f812f37: fix(data-objectstack): the object-metadata write refusal no longer points at an issue that answers 404
|
|
1212
|
+
|
|
1213
|
+
`assertObjectMetadataWritable` throws when an object-metadata write carries a relationship
|
|
1214
|
+
field with no usable target, and a host surfaces that message on screen or in a log. Its
|
|
1215
|
+
reason ended with two issue pointers, and one of them answers 404, so a reader who followed
|
|
1216
|
+
it found nothing. The message now cites only objectui#7714, the ruling the refusal enforces.
|
|
1217
|
+
|
|
1218
|
+
Nothing else in the message moves: the door name it opens with, the field it names, the
|
|
1219
|
+
target state it describes and the advice it closes with are unchanged, and the guard refuses
|
|
1220
|
+
exactly the writes it refused before.
|
|
1221
|
+
- ac15833: fix(data-objectstack, plugin-dashboard): a dataset tile the viewer may not read shows a localized "no access" state (objectui#10899)
|
|
1222
|
+
|
|
1223
|
+
When the analytics read admission refused a dashboard dataset query — `403`
|
|
1224
|
+
with ADR-0112 `PERMISSION_DENIED` — `queryDataset` had no branch for that code,
|
|
1225
|
+
so it threw the generic `Dataset query failed: 403 Forbidden — [Analytics]
|
|
1226
|
+
Access denied: …` string, and `DatasetWidget` printed it verbatim in a red
|
|
1227
|
+
alert. The list view over the same object already says 「无权访问」.
|
|
1228
|
+
|
|
1229
|
+
- `queryDataset` now throws a typed `AnalyticsForbiddenError` for that code,
|
|
1230
|
+
carrying `httpStatus: 403` and `code: 'PERMISSION_DENIED'` (plus the server's
|
|
1231
|
+
code and message for diagnostics). A code-less 403 — no ObjectStack route
|
|
1232
|
+
wrote it — keeps the generic error.
|
|
1233
|
+
- `DatasetWidget` classifies a failed query with the shared `classifyLoadError`
|
|
1234
|
+
and, for `forbidden`, renders a localized no-access state
|
|
1235
|
+
(`dashboard.widgetForbiddenTitle` / `dashboard.widgetForbiddenMessage`, new in
|
|
1236
|
+
all ten packs) instead of the exception text. Every other failure keeps the
|
|
1237
|
+
detailed alert.
|
|
1238
|
+
- 3c13675: **The console reads a saved view by the spellings `@objectstack/spec` declares, and stops reading the keys nothing writes (objectui#11013).** This is the console end of the ruling on objectstack#20051: the spec now declares the keys the console writes onto a stored view and reads back (`VIEW_CONSOLE_ROUND_TRIP_KEYS`), and the console reads a stored view under those spellings.
|
|
1239
|
+
|
|
1240
|
+
**A saved view keeps its switcher state across a reload.** A view saved as a record (the "+" tab's create, or "Edit view config → Save") used to come back from `listViews()` with only its configuration, name, label and default flag. It now also comes back with its pin (`isPinned`), its position among saved views (`sortOrder`), its switcher group (`visibility`), its column layout (`columnState`) and its bound `object`. Visible effect: a saved view you dragged to a new position keeps that position after a reload, including in a browser that never saw the drag. The carried keys are read off the spec's record, not hand-listed.
|
|
1241
|
+
|
|
1242
|
+
**Keys a saved view no longer steers.** No console surface writes any of these onto a view, no view in this repository authors one, and the spec's view schema refuses each by name. A stored view that still carries one now behaves as if it did not:
|
|
1243
|
+
|
|
1244
|
+
| key on the stored view | what changes for the user |
|
|
1245
|
+
| --- | --- |
|
|
1246
|
+
| `allowExport` | A view carrying `allowExport: false` no longer hides the list's export control or drops its `exportOptions`. Whether a list offers export is the page's setting (the `list-view` / `object-view` node's `allowExport`); which formats it offers is the view's `exportOptions.formats`. |
|
|
1247
|
+
| `wrapHeaders` | Column-header wrapping follows the page's setting, not the view's. |
|
|
1248
|
+
| `editRecordsInline` | On the object page this was read as a second spelling of `inlineEdit`. A view carrying only it no longer turns inline editing on; write `inlineEdit`. |
|
|
1249
|
+
| `clickIntoRecordDetails`, `addRecordViaForm`, `addDeleteRecordsInline`, `collapseAllByDefault`, `fieldTextColor`, `prefixField` | Relayed into the list, which drew nothing from them. No visible change. |
|
|
1250
|
+
|
|
1251
|
+
The same keys authored on the `object-view` node itself are unchanged: that node's own values still reach the list.
|
|
1252
|
+
|
|
1253
|
+
**The toolbar policy is read as `userActions`.** The `object-view` node the object page builds, and `@object-ui/plugin-view`'s own kanban / calendar / gallery / timeline / gantt / map route (a host that renders the `object-view` node without `renderListView`), read a view's search, sort and filter toggles from `userActions.search`, `.sort` and `.filter`, where they used to read the bare `showSearch`, `showSort` and `showFilters` flags. On that plugin route, a view that declared `userActions: { search: false }` now hides the search control; the object page's list already honoured it. A stored view that still carries a bare flag keeps its answer: `normalizeListViewSchema` folds it onto `userActions`.
|
|
1254
|
+
|
|
1255
|
+
**A stored view is bound and identified by its declared keys only.** A view row is matched to its object by `object` (or its configuration's `data.object`), and no longer by `objectName`; it is identified by `name`, and no longer by a top-level `id` or `_id`. This holds in the view switcher (`listViews()` / `listViewOverrides()`), in Studio's view picker for `interfaceConfig.sourceView`, and in Studio's view preview. Every console write stamps both `object` and `name`, and the metadata door refuses a view record or overlay row bound by `objectName` alone, so a row the console wrote is unaffected. The console also no longer stamps an undeclared `objectName` onto the rows it reads, which a saved view's toolbar save used to write back into the stored row.
|
|
1256
|
+
|
|
1257
|
+
**BREAKING (TypeScript only) — `NamedListView.allowExport` is retired.** It is now a `?: never` tombstone, like the seventeen members objectui#7924 retired: a TypeScript author who writes it gets a compile error there. Ruling A on objectui#7924 had kept it declared only because both relays read it off a view, and those reads are gone.
|
|
1258
|
+
- f61dab1: `reference` is now the only spelling ObjectUI writes or reads for a relational field's target object (objectui#11070, round 4, under the objectui#6837 ruling: 「objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀」).
|
|
1259
|
+
|
|
1260
|
+
- **Types.** `LookupFieldMetadata`, `MasterDetailFieldMetadata` and `DetailViewField` (with its zod mirror `DetailViewFieldSchema`) declare `reference` and no longer declare `reference_to`. On the two field metadata types the member is typed by reference to `@objectstack/spec`'s `FieldSchema.reference`. `@object-ui/plugin-form`'s `FieldDefaultsSchemaLike` drops its `reference_to` member the same way.
|
|
1261
|
+
- **Readers.** `LookupField`, `UserField`, `LookupCellRenderer`, `UserCellRenderer`, the inline editor's reference fallback, the form's `current_user` seeding and the inline-subform parent lookup read `reference` alone.
|
|
1262
|
+
- **Emitters.** Every in-repo producer that builds a field definition or a widget `field` prop writes `reference`: the action-param dialog (`paramToField`), the bulk-action dialog, the record detail page, drawer, footer, related list and synthesised page, the gallery card, the form's section-field override and the flow designer's reference picker. The grid's and the dashboard's relational copy sets carry `reference` and no longer copy `reference_to`.
|
|
1263
|
+
- **Ingestion.** `normalizeFieldReferenceKeys` (behind `ObjectStackAdapter.getObjectSchema` and `MetadataProvider`) still folds a legacy `reference_to` / `referenceTo` onto `reference` when `reference` is absent, and still warns in dev. It no longer stamps `reference_to` onto every relational definition, and it still never drops or overwrites a key.
|
|
1264
|
+
|
|
1265
|
+
`@objectstack/spec`'s `FieldSchema` refuses `reference_to` by name, and objectstack#13847 rewrites stored ones on the serve path and in `os migrate meta`, so a definition served by an ObjectStack backend is unaffected.
|
|
1266
|
+
|
|
1267
|
+
## ⚠️ BREAKING for a host that hands `reference_to` to the widgets directly
|
|
1268
|
+
|
|
1269
|
+
The type change is a compile error for TypeScript that writes `reference_to` on `LookupFieldMetadata`, `MasterDetailFieldMetadata` or `DetailViewField`: rename it to `reference`.
|
|
1270
|
+
|
|
1271
|
+
At runtime the break reaches exactly one kind of host: one that serves object definitions spelling the target only as `reference_to` through a `DataSource` other than `ObjectStackAdapter`, or that passes such a definition straight into `LookupField`, `UserField` or a cell renderer. Those definitions never pass the ingestion fold. Measured with an object-bound lookup field on `ObjectForm` and a lookup column on `ObjectGrid`, both fed by a hand-written `DataSource` whose `getObjectSchema` returns `{ type: 'lookup', reference_to: 'account' }`:
|
|
1272
|
+
|
|
1273
|
+
- before this change, opening the picker queried `account` and the cell resolved the record's name;
|
|
1274
|
+
- after it, the picker has no object to query (no `find` call is made) and the cell shows the raw id beside the unresolved-reference marker.
|
|
1275
|
+
|
|
1276
|
+
The same definition served through `ObjectStackAdapter.getObjectSchema` still works, before and after: the fold adds `reference`, the picker queries `account`, the cell resolves the name, and the dev warning names the field. **Fix:** spell the target `reference` in the definition your `DataSource` serves.
|
|
1277
|
+
|
|
1278
|
+
⚠️ **Dated note, 2026-09-30 — the break reaches more than the paragraph above states — objectui#11070.** Two corrections from the contract review of this change, appended rather than edited in; neither changes what ships.
|
|
1279
|
+
|
|
1280
|
+
- "The break reaches exactly one kind of host" above is too narrow. Host code that read `reference_to` off a definition after ingestion — from `ObjectStackAdapter.getObjectSchema()` or `useMetadata().objects` — also loses that key, because the ingestion pass no longer stamps it (the **Ingestion** bullet above states the fact, and `reference_to` was never a declared member of those definitions). **Fix:** read `reference`.
|
|
1281
|
+
- The measured break above is a `lookup` field's. A `user` field handed a `reference_to`-only definition directly does not end at "no query": `UserField` falls back to `sys_user`, so its picker queries `sys_user` rather than the object `reference_to` named. **Fix:** spell the target `reference` there too.
|
|
1282
|
+
|
|
1283
|
+
The text above is kept as the reading of this change.
|
|
1284
|
+
- 81f8498: objectui now resolves `@objectstack/*` 17.5.0 and `zod` 4.6.5, and follows every contract move that release makes (objectui#11073). `@objectstack/spec` 17.5.0 and `@objectstack/core` 17.5.0 require `zod ^4.6.1`, so objectui's own `zod` resolves to the same 4.6.5: two zod minors do not type-check against each other.
|
|
1285
|
+
|
|
1286
|
+
⚠️ Breaking in places, marked `minor` under this repo's version-alignment rule (a `major` in the fixed group would move all of it off the `@objectstack` major). Every narrowing below is the spec's own, and already reaches any consumer that resolves `@objectstack/spec ^17.5`.
|
|
1287
|
+
|
|
1288
|
+
- `@object-ui/types`: declares `@objectstack/spec ^17.5.0` and `zod ^4.6.1`. A named list view's `pageName` and `tabs` are retired, as the spec retired them: typed `never` and refused by name at parse. `DashboardWidgetSchema` attaches the spec's two new checks (`checkDashboardWidgetStageOrder`, `checkDashboardWidgetMetricMeasureArity`), so a widget the spec refuses is refused here too. Where a strict object is an arm of a plain union, its unknown-key refusal is terminal again (the spec's own mechanism, needed under zod 4.6): the union answers with every arm instead of the one that failed only on unknown keys. That changes the error shape, never the verdict (measured on every objectui union). Two by-name named-view checks that can no longer run are retired; the spec's refusal answers those documents.
|
|
1289
|
+
- `@object-ui/core`: declares `@objectstack/spec ^17.5.0`. `page` leaves the list-view kinds the normalizer knows, as the spec removed `type: 'page'`. `isRefusedTextComparand` and `textComparandRefusalReason` are re-exported from `@objectstack/spec/data`, whose copies are byte-identical. `SPEC_ACTION_KEYS` lists `execution`, which `ActionSchema` now declares.
|
|
1290
|
+
- `@object-ui/data-objectstack`: declares `@objectstack/spec ^17.5.0`, up from `^17.4.0`. Its bundle carries `@object-ui/core`'s filter converter, which now takes `isRefusedTextComparand` and `textComparandRefusalReason` from `@objectstack/spec/data`. `@objectstack/spec` 17.4.0 exports neither, so the old range admitted a spec under which the shipped module names exports that are not there (objectui#5793's floor gate). Nothing moves at runtime in this repository, because the lockfile already resolves 17.5.0. `patch`, as the floor raise of objectui#10864 was.
|
|
1291
|
+
- `@object-ui/fields`: the percent and number `scale` clamp is gone, at the sunset its own test named. The spec now refuses a `scale` above 100 at the declaration, so the width a formatter cannot render no longer arrives, and every face passes the declared width through as it did before the clamp.
|
|
1292
|
+
- `@object-ui/components`: two module-local prop interfaces of the action renderers are renamed (`ActionButtonRendererProps`, `ActionIconRendererProps`), because the spec now exports the old names for its authored props. Neither is reachable through the package's exports map; only the shipped per-file declarations change.
|
|
1293
|
+
- `@object-ui/plugin-list`: the README's view-type example no longer lists the retired `page` kind.
|
|
1294
|
+
- `@object-ui/app-shell`: the flow designer follows 17.5.0 (maintainer ruling on objectui#11088, decision 2). A new `wait` node is seeded `waitEventConfig: { eventType: 'timer', timerDuration: 'PT1H' }`, because the spec now refuses a timer wait with no duration; the inspector shows the Duration at once and it can be changed. The decision form offers the spec's new `mode` (exclusive or inclusive), with no default declared because the spec applies none. The screen form's `mode` states `create` again, because the spec now applies that default. `minor` because the decision form gains a control.
|
|
1295
|
+
- `@object-ui/app-shell`: declares `@objectstack/spec ^17.5.0`, up from `^17.4.0`, and `zod ^4.6.1`, up from `^4.4.3`. The designer now states 17.5.0 behaviour. The screen form's declared `create` default equals a default the spec applies only since 17.5.0, and objectui#9109 requires the two to be equal, so a range that admitted 17.4.0 claimed more than the designer holds to. The resolved versions do not move.
|
|
1296
|
+
- `@object-ui/console`: the bundle inlines the 17.5.0 packages, so its client-side validation answers as the published 17.5.0 contract does. The flow preview sample's script nodes use the 17.5.0 script contract. The eager-closure budget is re-baselined for the release's growth, under the maintainer ruling on objectui#11088 (decision 1).
|
|
1297
|
+
- 490d9a9: Grid headers offer a sort click only on columns the PLATFORM says it will order by
|
|
1298
|
+
(objectui#5729 — the consumer leg of objectstack#10235, maintainer ruling A, 2026-08-23:
|
|
1299
|
+
the platform serves an explicit per-column sortability signal and the grid reads it,
|
|
1300
|
+
rather than re-deriving "virtual ⇒ unsortable" from field type).
|
|
1301
|
+
|
|
1302
|
+
`GET /api/v1/meta/object/:name` now answers with a `sortability` projection on its
|
|
1303
|
+
ENVELOPE — `{ fields: { [name]: { sortable, reason?, caveat? } } }`, computed at serve
|
|
1304
|
+
time from the platform's own storage predicates, deliberately beside `item` rather than
|
|
1305
|
+
inside it so the key stays un-authorable. The signal was reaching the browser and being
|
|
1306
|
+
discarded one line before its only consumer: `ObjectStackAdapter.getObjectSchema` unwraps
|
|
1307
|
+
the envelope to `item`, so every UI reader saw a document with no signal on it. It now
|
|
1308
|
+
survives that unwrap, carried on the schema under a symbol key — invisible to
|
|
1309
|
+
`JSON.stringify`, to `Object.keys` and to a spread, so a schema handed back at a metadata
|
|
1310
|
+
write endpoint can never take it into a body the server parses strictly.
|
|
1311
|
+
|
|
1312
|
+
`@object-ui/core` gains the one spelling of the consumer contract:
|
|
1313
|
+
`isPlatformSortableField(projection, name)` is `true` iff an entry EXISTS for the name and
|
|
1314
|
+
says `sortable: true`. Absence is a refusal — it is how the platform encodes an unknown
|
|
1315
|
+
name, a dotted path and an unprovisioned audit column, all three of which the runtime
|
|
1316
|
+
doors reject — so the `!== false` spelling every other optional flag in this repo uses
|
|
1317
|
+
would get exactly that family backwards. A projection that is absent ALTOGETHER is a
|
|
1318
|
+
different question with a different answer (`undefined`: no signal was served) and is
|
|
1319
|
+
typed apart from an empty one, so a deployment older than the upstream change keeps the
|
|
1320
|
+
behaviour it had rather than being told, falsely, that nothing on the object is sortable.
|
|
1321
|
+
|
|
1322
|
+
Three things follow in the grid. The header click on a refused column ceases to exist, so
|
|
1323
|
+
neither the old silent-unordered result nor the `400 INVALID_SORT` that replaced it is
|
|
1324
|
+
reachable from it. A sort PERSISTED before the signal existed is filtered out of both what
|
|
1325
|
+
the grid renders and what it emits, so a restored personalization cannot ride back into
|
|
1326
|
+
the next `persistViewPatch({ sort })` — the half-fix where the affordance is gone and the
|
|
1327
|
+
PUT still fires. And the relational carve-out is untouched and deliberately not delegated
|
|
1328
|
+
to this signal: the platform answers `sortable: true` for a `lookup` (it has a stored
|
|
1329
|
+
foreign key and both runtime doors accept ordering by it), while the grid withholds that
|
|
1330
|
+
header for a different reason — a column of names ordered by an invisible id.
|
|
1331
|
+
|
|
1332
|
+
Columns carrying `caveat: 'unprovisioned-anchor'` keep their click. The runtime accepts
|
|
1333
|
+
those sorts; refusing what the platform does not refuse would recreate declared-≠-enforced
|
|
1334
|
+
drift in mirror image.
|
|
1335
|
+
- a26b9e4: `packages/core/src/adapters/README.md` now documents the adapters that are actually in that
|
|
1336
|
+
directory, and the ObjectStack material it carried moved to the package that owns the behaviour
|
|
1337
|
+
(objectui#6213). Both files ship to consumers — `@object-ui/core` publishes its `src/`, and a
|
|
1338
|
+
README rides every tarball — so this was published documentation describing the wrong package.
|
|
1339
|
+
|
|
1340
|
+
The page had been left behind when the ObjectStack adapter moved out to
|
|
1341
|
+
`@object-ui/data-objectstack`: its headings, feature list, filter-operator table and
|
|
1342
|
+
query-parameter table were all about that adapter, and its one-entry "Available Adapters" list
|
|
1343
|
+
told a reader Object UI has exactly one adapter and that it comes from `@object-ui/core`.
|
|
1344
|
+
`ApiDataSource`, `ValueDataSource`, `resolveDataSource`, `runBatchTransaction` and
|
|
1345
|
+
`emulateBatchTransaction` — the five exports that directory really ships — were named nowhere.
|
|
1346
|
+
|
|
1347
|
+
- **`@object-ui/core`**: the page now opens with what the directory holds, gives each export a
|
|
1348
|
+
usage snippet and a `provider` mapping, and points at `@object-ui/data-objectstack` for the
|
|
1349
|
+
ObjectStack adapter. `## Creating Custom Adapters` is unchanged — it is the one section that was
|
|
1350
|
+
always about this directory.
|
|
1351
|
+
- **`@object-ui/data-objectstack`**: gains a `## Query Translation` section carrying the
|
|
1352
|
+
filter-operator and query-parameter mapping tables, the AST conversion example and the sorting
|
|
1353
|
+
example. That material existed **only** in the `core` copy — this package's README documented
|
|
1354
|
+
query translation as a single feature bullet — so it is ported, not dropped.
|
|
1355
|
+
|
|
1356
|
+
No runtime behaviour changes; the duplicate copy of one package's documentation living under
|
|
1357
|
+
another package is what goes away.
|
|
1358
|
+
- 5127378: `ObjectStackAdapter.aggregate()` lowers rule-shaped filter arrays before the
|
|
1359
|
+
analytics wire, reusing the lowering `find()` already runs (objectui#6302).
|
|
1360
|
+
|
|
1361
|
+
`find()` has translated `[{ field, operator, value }, ...]` into the server's
|
|
1362
|
+
filter AST for as long as `convertQueryParams` has existed. The analytics path
|
|
1363
|
+
did not: `aggregate()` assigned `payload.where = params.filter` verbatim and
|
|
1364
|
+
posted it to `/analytics/query`.
|
|
1365
|
+
|
|
1366
|
+
The two doors are not equally forgiving, so the gap had a user-visible end.
|
|
1367
|
+
`lowerAnalyticsWhere` in `@objectstack/service-analytics` — shared by both
|
|
1368
|
+
aggregation strategies — accepts AST tuples and throws on an array of rule
|
|
1369
|
+
objects. A stored `ViewFilterRule[]` that a LIST renders correctly therefore
|
|
1370
|
+
rendered `element:number` into its error state on every analytics-capable
|
|
1371
|
+
deployment, which is the default one because the CLI always loads analytics.
|
|
1372
|
+
|
|
1373
|
+
An array filter now goes through the same `translateFilterArray` the `find()`
|
|
1374
|
+
path uses — one lowering, so the two paths cannot disagree about one stored
|
|
1375
|
+
filter. Rules spread into a logical node (`['and', ...rules, ...tuples]`, the
|
|
1376
|
+
commonest composite there is) are lowered at depth, as they already were on
|
|
1377
|
+
`find()`. Non-array filters are untouched: the MongoDB-style object this branch
|
|
1378
|
+
was written for is what `/analytics/query` already accepts, and translating it
|
|
1379
|
+
would be a semantic change this fix does not make. Already-AST arrays,
|
|
1380
|
+
record-shaped filters, and the no-filter case are byte-unchanged.
|
|
1381
|
+
- 5961030: `@object-ui/core` and `@object-ui/data-objectstack` now declare
|
|
1382
|
+
`"@objectstack/spec": "^17.2.0"` rather than `^17.0.0`, which is the lowest published
|
|
1383
|
+
spec that carries every symbol each package's own build output references
|
|
1384
|
+
(objectui#6361).
|
|
1385
|
+
|
|
1386
|
+
`packages/core/dist/utils/column-sortability.d.ts` references
|
|
1387
|
+
`FIELD_SORTABLE_UNPROVISIONED_ANCHOR`, `FIELD_UNSORTABLE_VIRTUAL_TYPE`,
|
|
1388
|
+
`FieldSortability` and `ObjectSortability` from `@objectstack/spec/api`, and
|
|
1389
|
+
`packages/data-objectstack/dist/index.js` references the first two — none of which
|
|
1390
|
+
`@objectstack/spec@17.0.0` exports. Measured against the published tarballs rather than
|
|
1391
|
+
the installed tree, by `scripts/check-spec-range-floors.mjs`: six `floor-too-low`
|
|
1392
|
+
findings across the two packages, and `^17.2.0` is that gate's own computed answer for
|
|
1393
|
+
both. So the old range was a claim neither package could honour: any consumer
|
|
1394
|
+
resolution that lands 17.0.0 — a sibling pinning it exactly, an `overrides` entry, a
|
|
1395
|
+
mirror two minors behind — satisfied `^17.0.0` and got a dangling reference.
|
|
1396
|
+
|
|
1397
|
+
Nothing a consumer installs today changes: normal resolution already picks the newest
|
|
1398
|
+
17.x, and `pnpm-lock.yaml` still resolves `17.2.0` on both edges after the bump — only
|
|
1399
|
+
the recorded `specifier:` moves. No source and no behaviour changes, which is why this
|
|
1400
|
+
is scored `patch`, on the reasoning `111741454` used for the same remediation on
|
|
1401
|
+
`@object-ui/plugin-detail`.
|
|
1402
|
+
|
|
1403
|
+
The bump is release-blocking rather than cosmetic. `check:spec-floors` is deliberately
|
|
1404
|
+
not a `pull_request` job, so every PR stayed green while its blocking copy on the
|
|
1405
|
+
publish path — `pnpm changeset:publish` runs it before a single tarball reaches npm —
|
|
1406
|
+
would have cancelled the next release.
|
|
1407
|
+
- 639114c: Reconcile `@object-ui/data-objectstack` with the `@objectstack/*` family at
|
|
1408
|
+
17.3.0 (objectui#7122).
|
|
1409
|
+
|
|
1410
|
+
`@objectstack/client`, `core`, `formula` and `lint` each pin `@objectstack/spec`
|
|
1411
|
+
EXACTLY, so resolving the spec alone to 17.3.0 left the console bundling TWO
|
|
1412
|
+
copies of it. Moving the family with it in `pnpm-lock.yaml` collapses the
|
|
1413
|
+
duplicate; every declared range already admitted 17.3.0, so no manifest moved.
|
|
1414
|
+
|
|
1415
|
+
The one source change the family bump forces is a type reconciliation, not a
|
|
1416
|
+
behaviour change. `client.analytics.query` resolved to `Promise<any>` at 17.2.0
|
|
1417
|
+
and resolves to `Promise<AnalyticsResult>` at 17.3.0, so the pre-envelope
|
|
1418
|
+
branches of `aggregate`'s row-shape fallback stopped type-checking. Those
|
|
1419
|
+
branches are read through a widened alias rather than deleted: the client's own
|
|
1420
|
+
docblock records the runtime change behind the narrower type ("BREAKING since
|
|
1421
|
+
objectstack#13079 — read `result.rows`, not `result.data.rows`"), and deleting
|
|
1422
|
+
them is a runtime compatibility decision about servers older than that, not a
|
|
1423
|
+
type repair. The alias restores exactly the compile-time latitude 17.2.0 gave
|
|
1424
|
+
the same expression and changes no runtime byte of it.
|
|
1425
|
+
- f57ca75: fix(studio): one draft-envelope reader, and it strips the framework's read decorations
|
|
1426
|
+
|
|
1427
|
+
`client.getDraft()` serves a DECORATED body — the draft branch stamps
|
|
1428
|
+
`_draft: true` and then `decorateMetadataItem` attaches `_diagnostics` for any
|
|
1429
|
+
type with a registered Zod schema. The spec names both READ-TIME decorations
|
|
1430
|
+
precisely because a served body "is NOT a valid input to the schema that
|
|
1431
|
+
produced it until these are removed" (`METADATA_READ_DECORATIONS`).
|
|
1432
|
+
|
|
1433
|
+
objectui#7603 taught `ResourceEditPage` to strip them. It could only teach one
|
|
1434
|
+
site, because `extractDraftBody` existed **four times** — three verbatim copies
|
|
1435
|
+
plus a hand-rolled one in `ObjectHooksPanel` — and six more consumers unwrapped
|
|
1436
|
+
the envelope inline. Ten readers, one of which knew the rule.
|
|
1437
|
+
|
|
1438
|
+
**The user-visible half.** The pending-changes sheet's per-entry diff compares
|
|
1439
|
+
the published body against the draft body key by key. Those two reads are
|
|
1440
|
+
decorated ASYMMETRICALLY — only the draft branch stamps `_draft` — so the sheet
|
|
1441
|
+
listed `_draft` under "Also changed:" on every entry that has a published
|
|
1442
|
+
counterpart, and `_diagnostics` alongside it whenever the two read-time verdicts
|
|
1443
|
+
differed. Framework-internal keys were being presented to the author as their
|
|
1444
|
+
own edits, on the screen where they decide whether to publish.
|
|
1445
|
+
|
|
1446
|
+
**The rest.** Six sites merged a decorated body into a document they then wrote
|
|
1447
|
+
back through `save(..., { mode: 'draft' })` — the Studio app / page / object /
|
|
1448
|
+
flow surfaces, the package OWD panel, the object hooks panel, and the
|
|
1449
|
+
adapter's `updateView`. Today's server absorbs that (it strips read decorations
|
|
1450
|
+
on ingress, before its own schema gate), so nothing 400s; this is still a client
|
|
1451
|
+
emitting a body its own spec calls invalid, and the fix belongs at the producer.
|
|
1452
|
+
|
|
1453
|
+
The cure is one function rather than ten strips: `extractDraftBody` is now
|
|
1454
|
+
exported from `@object-ui/data-objectstack`, beside the `getDraft` whose
|
|
1455
|
+
envelope it decodes. The key list is the spec's exported
|
|
1456
|
+
`stripReadDecorations` — never a second hand-maintained copy in this repo. The
|
|
1457
|
+
presence verdict still runs BEFORE the strip, so removing our own annotations
|
|
1458
|
+
can never turn a served draft into "nothing pending", and the ADR-0010
|
|
1459
|
+
protection envelope (`_lock`, `_provenance`, `_packageId`, `_packageVersion`)
|
|
1460
|
+
is deliberately untouched: those keys are declared by the closed schemas.
|
|
1461
|
+
|
|
1462
|
+
No schema was loosened, and no gate was taught to tolerate `_diagnostics`.
|
|
1463
|
+
- 0b8c638: fix(data-objectstack): the rule-entry filter form refuses an empty or non-string `icontains` comparand
|
|
1464
|
+
|
|
1465
|
+
`@objectstack/spec`'s `FILTER_TEXT_CASES` declares two shapes REFUSED for the
|
|
1466
|
+
case-insensitive contains operator: an empty comparand, and one that is not a
|
|
1467
|
+
string. This adapter already refused both when a filter arrived as an object
|
|
1468
|
+
(`{ name: { $icontains: '' } }`), but a filter in the rule-entry form
|
|
1469
|
+
(`[{ field: 'name', operator: 'icontains', value: '' }]`) was lowered to
|
|
1470
|
+
`['name', 'icontains', '']` and sent. So the same condition was refused or sent
|
|
1471
|
+
depending on which form it was written in. An empty comparand matches every
|
|
1472
|
+
row, so the entry form returned the whole table where the author asked for a
|
|
1473
|
+
subset.
|
|
1474
|
+
|
|
1475
|
+
The entry form now asks the spec's own `isRefusedTextComparand` after the
|
|
1476
|
+
operator alias fold, so a case variant such as `ICONTAINS`, or the operator
|
|
1477
|
+
given under the `op` key, is judged too. It throws `MalformedFilterError`
|
|
1478
|
+
(`INVALID_FILTER` / 400) carrying the spec's `textComparandRefusalReason`
|
|
1479
|
+
unchanged, before any request is sent, on `find()` (both routes) and on
|
|
1480
|
+
`aggregate()`, and for a rule nested under a logical node as well as at the top
|
|
1481
|
+
level. `convertFiltersToAST` and `ValueDataSource` read the same two functions,
|
|
1482
|
+
so all three now refuse the same set: an empty string and any non-string,
|
|
1483
|
+
including a missing `value`. A missing value used to go out as JSON `null`.
|
|
1484
|
+
|
|
1485
|
+
`MalformedFilterError`'s public signature is unchanged: the refusal sentence is
|
|
1486
|
+
seated inside this module. Building its message no longer throws for an entry
|
|
1487
|
+
that carries a BigInt (such a value is shown as its literal, e.g. `3n`), and its
|
|
1488
|
+
text is otherwise the same as before. The README's "Rule-shaped arrays" section
|
|
1489
|
+
documents the refusal. The case-sensitive `contains` family is untouched: the
|
|
1490
|
+
table declares no such row for it.
|
|
1491
|
+
- 72d6587: A record id is a `string` everywhere in the published types, as
|
|
1492
|
+
`@objectstack/spec` has always declared it. Three published declarations that
|
|
1493
|
+
admitted `number` no longer do.
|
|
1494
|
+
|
|
1495
|
+
⚠️ **BREAKING if your code hands a numeric primary key to any of these three:**
|
|
1496
|
+
|
|
1497
|
+
- **`RecordContextValue.recordId`** (`@object-ui/react`) — the value
|
|
1498
|
+
`useRecordContext()` gives you is now a `string`, never a `number`.
|
|
1499
|
+
- **`DataSource.update`'s `id` parameter** (`@object-ui/types`) — a call that
|
|
1500
|
+
passes a `string | number` is now a type error. Adapters that *implement*
|
|
1501
|
+
`DataSource` are unaffected (see Migration).
|
|
1502
|
+
- **`TransactionOperation.id`** (`@object-ui/core`) — the operation record you
|
|
1503
|
+
hand to `TransactionManager.recordOperation()` must carry a `string` id. If
|
|
1504
|
+
you build that object from a numeric key, convert it where you build it. This
|
|
1505
|
+
type is exported from the package root, so this is a breaking change for
|
|
1506
|
+
`@object-ui/core` consumers in its own right, not just a knock-on.
|
|
1507
|
+
|
|
1508
|
+
Ships as `minor` per the launch-window convention: objectui's
|
|
1509
|
+
`major` is a cross-repo pin to `@objectstack`'s so that "same major means
|
|
1510
|
+
compatible" holds across the two repos
|
|
1511
|
+
(`scripts/check-changeset-no-major.mjs`), and objectui's own breaking changes
|
|
1512
|
+
ship as `minor` with the break named where it lands — this entry is the channel
|
|
1513
|
+
that carries it.
|
|
1514
|
+
|
|
1515
|
+
## What changed
|
|
1516
|
+
|
|
1517
|
+
- `RecordContextValue.recordId` (`@object-ui/react`) was
|
|
1518
|
+
`string | number | null | undefined`; it is now `string | null | undefined`.
|
|
1519
|
+
- `DataSource.update`'s `id` parameter (`@object-ui/types`) was
|
|
1520
|
+
`string | number`; it is now `string`.
|
|
1521
|
+
- `TransactionOperation.id` (`@object-ui/core`) was `string | number`; it is now
|
|
1522
|
+
`string`. Its sibling `BatchTransactionOperation.id` was already a `string`,
|
|
1523
|
+
so the two operation records finally agree.
|
|
1524
|
+
- `LineItemsPanel` (`@object-ui/plugin-form`) drops the type assertion
|
|
1525
|
+
objectui#9304 left on its parent id. That assertion was the only thing making
|
|
1526
|
+
the context declaration and `buildMasterDetailEditBatch(parentId: string)`
|
|
1527
|
+
meet; the declaration now does it, so the evidence is discharged.
|
|
1528
|
+
|
|
1529
|
+
## Why the protocol, and not a wider consumer type
|
|
1530
|
+
|
|
1531
|
+
`@objectstack/spec` declares a record id as `z.string()` on every record door —
|
|
1532
|
+
get, update, delete and the batch operation. A consumer type may not be wider
|
|
1533
|
+
than the protocol: a declaration that admits `number` promises callers something
|
|
1534
|
+
the wire never carries, and the promise is kept only by an assertion at the far
|
|
1535
|
+
end, which is what this card was filed about.
|
|
1536
|
+
|
|
1537
|
+
## Internal consumers repaired at the same time (no public contract moves)
|
|
1538
|
+
|
|
1539
|
+
Narrowing an interface **parameter** never reaches implementors — TypeScript
|
|
1540
|
+
compares method parameters bivariantly, so an adapter that still declares
|
|
1541
|
+
`id: string | number` keeps satisfying `DataSource`. It reaches **callers**. A
|
|
1542
|
+
full local type-check of every workspace type-check program found exactly six,
|
|
1543
|
+
in three packages, and each was red because a further declaration one layer in
|
|
1544
|
+
was itself wider than the protocol. All three are narrowed here, types only, with
|
|
1545
|
+
no runtime change and no coercion added at any call site:
|
|
1546
|
+
|
|
1547
|
+
- `UserPreferenceRecord.id` and the `cachedRowId` it feeds
|
|
1548
|
+
(`@object-ui/data-objectstack`) are `string`. Module-local, not published —
|
|
1549
|
+
these rows are read back off the protocol, so the union was a claim the wire
|
|
1550
|
+
never makes.
|
|
1551
|
+
- `resolveRecordId`'s return type (`@object-ui/plugin-grid`) is
|
|
1552
|
+
`string | undefined`. Module-local, not published — it annotates `any`-typed
|
|
1553
|
+
row data, so the union was an assertion rather than a measurement.
|
|
1554
|
+
|
|
1555
|
+
## Migration — no `String(...)` at your call sites
|
|
1556
|
+
|
|
1557
|
+
`RecordContextProvider` still **accepts** `string | number | null | undefined`
|
|
1558
|
+
and narrows it once, itself. A host that mounts a record with a numeric primary
|
|
1559
|
+
key therefore changes nothing: the conversion is paid at that injection
|
|
1560
|
+
boundary, typed, in one place. Consumers of `useRecordContext()` read a
|
|
1561
|
+
`string`.
|
|
1562
|
+
|
|
1563
|
+
`DataSource` implementors are unaffected — TypeScript compares method parameters
|
|
1564
|
+
bivariantly, so an adapter that still declares `id: string | number` continues
|
|
1565
|
+
to satisfy the interface. What changes is the **caller** side: a call that passes
|
|
1566
|
+
a `string | number` to `dataSource.update` is now a type error. A backend whose
|
|
1567
|
+
primary keys are numeric maps them at its own adapter boundary rather than
|
|
1568
|
+
pushing the union through every caller.
|
|
1569
|
+
|
|
1570
|
+
For `TransactionOperation`, the same rule applies one level up: build the
|
|
1571
|
+
operation record with a `string` id. If the id arrives from a numeric-keyed
|
|
1572
|
+
backend, convert it in your adapter — the one place that knows the backend's key
|
|
1573
|
+
type — rather than at each `recordOperation()` call. Nothing about this change
|
|
1574
|
+
alters what is sent over the wire; only the declarations moved.
|
|
1575
|
+
- 269880a: Read the flat REST error envelope in `ObjectStackAdapter`, so a denial that names the
|
|
1576
|
+
missing grant stops arriving as a bare HTTP status word (objectui#9594).
|
|
1577
|
+
|
|
1578
|
+
A console operator who clicked "export to CSV" without the `allowExport` grant was
|
|
1579
|
+
shown one word: `Forbidden`. The server had answered a full sentence — which object,
|
|
1580
|
+
which user, which axis — and the adapter threw it away.
|
|
1581
|
+
|
|
1582
|
+
**Why it was thrown away.** `exportDownload` read its failure through a ladder that
|
|
1583
|
+
knew two envelope dialects: the ADR-0112 nested `error: { message }`, and the flat shape
|
|
1584
|
+
that carries a `message` key. The REST export gate writes a third one — flat, with the
|
|
1585
|
+
sentence in a bare string `error` and no `message` key anywhere — so every rung missed
|
|
1586
|
+
and the ladder fell through to `res.statusText`. The same ladder had been hand-copied at
|
|
1587
|
+
four sites in the module (`searchAll`, `rawFindWithPopulate`, `exportDownload`,
|
|
1588
|
+
`fetchObjectSchemaFresh`), and the two upload paths (`uploadFile`, `uploadFiles`) read a
|
|
1589
|
+
narrower version of it, so all six were blind to the same dialect.
|
|
1590
|
+
|
|
1591
|
+
**The fix is one reader, not six patches.** A single internal `readErrorEnvelope` now
|
|
1592
|
+
holds the dialect knowledge and every one of those six sites routes through it. It is
|
|
1593
|
+
deliberately **not** exported from the package entry point — the published face of
|
|
1594
|
+
`@object-ui/data-objectstack` is unchanged by this release.
|
|
1595
|
+
|
|
1596
|
+
**The rung order is load-bearing.** `message` is consulted before a string-valued
|
|
1597
|
+
`error`, because the REST 401 body is flat *and* carries both keys, with the code word in
|
|
1598
|
+
`error` and the sentence in `message`. Reading `error` first would make an
|
|
1599
|
+
unauthenticated response render the word `UNAUTHENTICATED` instead of its sentence. The
|
|
1600
|
+
401/403 pair is pinned as a control for exactly that inversion.
|
|
1601
|
+
|
|
1602
|
+
**The machine code now reaches the caller too.** The four `res.ok` ladders attach the
|
|
1603
|
+
server's `code` to the thrown error (`EXPORT_NOT_PERMITTED`, `PERMISSION_DENIED`, …), so
|
|
1604
|
+
a surface has something to discriminate on and an operator has something to search for.
|
|
1605
|
+
`rawFindWithPopulate` already did this; the other three now match it rather than drifting
|
|
1606
|
+
from it. The code is read from `code` or a nested `error.code` and **never** promoted out
|
|
1607
|
+
of a string `error` — a sentence is not a code, and `@objectstack/core`'s own guidance
|
|
1608
|
+
names that chain as where an envelope regression hides. The upload paths keep their
|
|
1609
|
+
declared `UPLOAD_ERROR` code and their own fallback text; only the dialect reading is
|
|
1610
|
+
shared, because their envelope is genuinely a different shape.
|
|
1611
|
+
|
|
1612
|
+
**Not a lenient fallback.** ADR-0112's 2026-07-30 amendment records the flat and the
|
|
1613
|
+
wrapped envelopes as the two live, sanctioned shapes, and D5 leaves the flat one's
|
|
1614
|
+
permanent position an open maintainer question. Every site routed through the reader calls
|
|
1615
|
+
a `/data` route, whose declared answer is the flat family, so reading it is conformance
|
|
1616
|
+
rather than tolerance. The server envelope is untouched — that belongs to the producer's
|
|
1617
|
+
ratchet card in the `objectstack` repo.
|
|
1618
|
+
|
|
1619
|
+
**Blast radius.** Not one route: the REST package answers a wide family of these
|
|
1620
|
+
`{ code, error }` bodies, and `VALIDATION_ERROR` / `PERMISSION_DENIED` / `INVALID_FILTER`
|
|
1621
|
+
arriving as `Bad Request` / `Forbidden` is the same loss with a much wider audience than
|
|
1622
|
+
the export button. The affected-code count quoted on objectui#9594 is a reading taken
|
|
1623
|
+
against one published `@objectstack/rest` version by the scan that card records; nothing
|
|
1624
|
+
in this repo re-derives it, so it is not restated here as a live figure.
|
|
1625
|
+
- b2e85a9: `ObjectStackAdapter.getApp` and `getPage` now address the `app` / `page` metadata
|
|
1626
|
+
types in the singular, matching the other twelve `client.meta.*` call sites in this
|
|
1627
|
+
file (objectui#4940).
|
|
1628
|
+
|
|
1629
|
+
`getApp` (`getItem('apps', …)`) and `probeAppAccess` (`getItem('app', …)`) addressed
|
|
1630
|
+
the same metadata type sixty lines apart, and only `probeAppAccess`'s comment argued
|
|
1631
|
+
its singular spelling was deliberate — the plural site was silent. Both plural sites
|
|
1632
|
+
resolved today only because the server folds plural → singular
|
|
1633
|
+
(`RestServer.metaTypeSingular` via `PLURAL_TO_SINGULAR` from `@objectstack/spec/shared`,
|
|
1634
|
+
confirmed by reading both the mapping and the by-name route handler that calls it), so
|
|
1635
|
+
this is consistency restoration rather than a behavior change — nothing a user hits was
|
|
1636
|
+
broken, and nothing a user hits changes.
|
|
1637
|
+
|
|
1638
|
+
`appAccessProbe.test.ts` (objectui#4252's local pin for this same spelling) is extended
|
|
1639
|
+
with two new cases asserting `getApp`/`getPage` pass the singular type to
|
|
1640
|
+
`client.meta.getItem`, so a future revert to the plural spelling fails a test instead of
|
|
1641
|
+
depending on the server-side fold staying in place.
|
|
1642
|
+
- c7cd2b6: `ObjectStackAdapter.queryDataset` now maps a failed dataset query by the server's
|
|
1643
|
+
ADR-0112 error `code`, not by the HTTP status, so an unknown dataset and an
|
|
1644
|
+
unauthenticated session stop being reported as a missing analytics capability
|
|
1645
|
+
(objectui#5663).
|
|
1646
|
+
|
|
1647
|
+
Two unrelated conditions answer **404** on `POST /api/v1/analytics/dataset/query`:
|
|
1648
|
+
the runtime dispatcher's `ROUTE_NOT_FOUND` when the route was never mounted, and
|
|
1649
|
+
the route's own `NOT_FOUND` when `body.datasetName` matches no saved dataset. The
|
|
1650
|
+
mapping tested `res.status === 501 || res.status === 404` and called all of it
|
|
1651
|
+
"the analytics capability is not installed", so every unknown dataset produced a
|
|
1652
|
+
banner telling the operator to install `@objectstack/service-analytics` and mount
|
|
1653
|
+
`AnalyticsServicePlugin`. Measured live on a prod tenant, that banner was shown on
|
|
1654
|
+
four HotCRM Executive Overview widgets while the analytics service was installed
|
|
1655
|
+
and answering — the real condition was an installed `app.objectstack.hotcrm` at
|
|
1656
|
+
1.3.0 whose datasets ship in 2.2.2, i.e. a package upgrade, the opposite corner of
|
|
1657
|
+
the system from the remedy the banner named.
|
|
1658
|
+
|
|
1659
|
+
Three conditions now get three answers, each keyed on the code the framework
|
|
1660
|
+
declares for it:
|
|
1661
|
+
|
|
1662
|
+
- `NOT_IMPLEMENTED` (501, route mounted with no analytics service) and
|
|
1663
|
+
`ROUTE_NOT_FOUND` (404, route not mounted) keep the existing
|
|
1664
|
+
`AnalyticsNotInstalledError` and its copy — one remedy, one message.
|
|
1665
|
+
- `NOT_FOUND` (404, unknown `datasetName`) throws the new
|
|
1666
|
+
`AnalyticsDatasetNotFoundError` (`ANALYTICS_DATASET_NOT_FOUND`), naming the
|
|
1667
|
+
dataset and pointing at the installed app's version rather than at the server.
|
|
1668
|
+
- `UNAUTHENTICATED` (401, `enforceAuth`) throws the new
|
|
1669
|
+
`AnalyticsUnauthenticatedError` (`ANALYTICS_UNAUTHENTICATED`), which says the
|
|
1670
|
+
request was refused before it ran and therefore says nothing about the
|
|
1671
|
+
capability.
|
|
1672
|
+
|
|
1673
|
+
The banner also used to print the server's own message in a parenthetical while
|
|
1674
|
+
contradicting it in the headline — it quoted `Dataset "opportunity_metrics" not
|
|
1675
|
+
found.` under a headline claiming a missing capability. That is now structurally
|
|
1676
|
+
impossible rather than merely fixed: the headline is a pure function of `code` and
|
|
1677
|
+
the parenthetical is a verbatim quote of `message`, both read off the same
|
|
1678
|
+
response, and a test walks every branch asserting each message carries its own
|
|
1679
|
+
headline and none of the others'.
|
|
1680
|
+
|
|
1681
|
+
Additive only. `AnalyticsNotInstalledError` keeps its `code`, its copy and its
|
|
1682
|
+
constructor signature (it gains an optional third `serverCode` argument and a
|
|
1683
|
+
`serverCode` field), so consumers matching `ANALYTICS_NOT_INSTALLED` — including
|
|
1684
|
+
the metadata-admin dataset preview — are unaffected. A 404 carrying a code this
|
|
1685
|
+
client does not recognise, such as the analytics cube gate's `CUBE_NOT_FOUND`, now
|
|
1686
|
+
keeps its server detail instead of being relabelled as a missing capability; a 404
|
|
1687
|
+
or 501 carrying no code at all is still read as the capability being absent, since
|
|
1688
|
+
the route's own `NOT_FOUND` always ships a code.
|
|
1689
|
+
- 617707a: `convertFiltersToAST` lowers the `$and` / `$or` combinators to real ObjectQL AST
|
|
1690
|
+
group nodes (objectui#6948).
|
|
1691
|
+
|
|
1692
|
+
`FilterCondition` declares `$and` / `$or` / `$not`, and this repo's one lowering
|
|
1693
|
+
had no branch for any of them. `$and` / `$or` fell through to the
|
|
1694
|
+
simple-equality branch and became a leaf naming a field literally called `$and`
|
|
1695
|
+
/ `$or`. That leaf reached the server intact — `parseFilterAST` reads
|
|
1696
|
+
`['$or', '=', [...]]` back as a real `$or`, so the wire condition was correct
|
|
1697
|
+
and is unchanged by this release — but it is a well-formed *comparison* node, so
|
|
1698
|
+
every AST evaluator in this repo read `$or` as a field name, found no such key
|
|
1699
|
+
on any record, and returned an EMPTY list with no error. Producers that reach
|
|
1700
|
+
this today include `mergeFilters` (dashboard scope broadcast, dataset report
|
|
1701
|
+
blocks), `FilterConditionField`, and `Field.relatedListFilter`.
|
|
1702
|
+
|
|
1703
|
+
`minor` rather than `patch`: shipped results move. A list filtered by a
|
|
1704
|
+
combinator through any in-process data source went from zero rows to the rows
|
|
1705
|
+
the author asked for, and an unknown or refused operator *inside* a combinator
|
|
1706
|
+
branch — which used to travel to the wire unchecked inside the leaf's value slot
|
|
1707
|
+
— is now refused at the same door as every other operator.
|
|
1708
|
+
|
|
1709
|
+
`$not` is refused with an accurate message instead of translated: the AST has no
|
|
1710
|
+
negation keyword (`FILTER_ARRAY_LOGIC_KEYWORDS` is `['and', 'or']`) and several
|
|
1711
|
+
operators it carries have no negated counterpart, so a rewrite would be silently
|
|
1712
|
+
partial. It threw before this change too, naming the author's own nested field
|
|
1713
|
+
as a bogus operator; the verdict is unchanged, only the diagnostic.
|
|
1714
|
+
- 8d37efb: The metadata lock banner can no longer render an amber, padlocked box with no
|
|
1715
|
+
title, and the ADR-0010 §3.6 lock vocabulary is declared once instead of three
|
|
1716
|
+
times (objectui#5024).
|
|
1717
|
+
|
|
1718
|
+
`MetadataLayered.lock` and `MetadataAuditEntry.lockState` each spelled the four
|
|
1719
|
+
states out by hand, 42 lines apart in one file, compared by no gate. They are now
|
|
1720
|
+
one exported `MetadataLockState` — derived from `GetMetaItemLayeredResponseSchema`'s
|
|
1721
|
+
`z.enum` in `@objectstack/spec`, which already owns this vocabulary, so the copies
|
|
1722
|
+
were restating a schema rather than filling a gap.
|
|
1723
|
+
|
|
1724
|
+
The user-visible half is the banner. Its title was three independent `&&` branches
|
|
1725
|
+
with no fallback, while the switch that opens the banner is true for any non-`none`
|
|
1726
|
+
value — so a lock state outside the four opened the box and left the headline
|
|
1727
|
+
empty. That is reachable without a fifth state ever being added here:
|
|
1728
|
+
`MetadataClient.layered()` casts the wire value through unchecked, so a newer
|
|
1729
|
+
server reaches this banner as-is. Measured, not assumed — feeding `no-publish`
|
|
1730
|
+
through the page rendered the padlock, the border and an empty title. The title is
|
|
1731
|
+
now a keyed lookup with a loud fallback that names the unrecognised token, so a
|
|
1732
|
+
fifth state fails `type-check` here and, if one arrives from a server anyway, the
|
|
1733
|
+
operator reads a sentence instead of a blank box.
|
|
1734
|
+
- 9118a31: Preserve the producer's `userMessage` marking when `normaliseClientError` re-wraps a refusal.
|
|
1735
|
+
|
|
1736
|
+
`ApiErrorSchema.userMessage` (objectstack `79c46da90`) is the opt-in channel an application author
|
|
1737
|
+
sets at throw time to say "this text is for the end user", and the contract states it
|
|
1738
|
+
status-agnostic — any refusal status may carry it. Both of the shapes this adapter re-wraps
|
|
1739
|
+
into typed errors dropped the marking: a hook that refused a write with `VALIDATION_FAILED`
|
|
1740
|
+
or `CONCURRENT_UPDATE` and marked its own sentence had that sentence discarded at the
|
|
1741
|
+
adapter boundary, before any surface could render it. Nothing threw and the typed error was
|
|
1742
|
+
otherwise correct, so the only symptom was the user reading a generic string instead of the
|
|
1743
|
+
sentence their administrator wrote.
|
|
1744
|
+
|
|
1745
|
+
The marking now rides both re-wraps, in the form the shared reader (`declaredUserMessage`)
|
|
1746
|
+
already looks for: on the details bag for `DataApiValidationError`, exactly the way `fields`
|
|
1747
|
+
already survives, and on a new readonly `userMessage` member for `ConcurrentUpdateError`,
|
|
1748
|
+
which has no details bag. Unmarked refusals are untouched — they carry no key and still read
|
|
1749
|
+
as `null`, so nothing a producer did not opt into can reach a user.
|
|
1750
|
+
- 7b43319: **Fix:** `MetadataClient.publishDraft` no longer unwraps a `{ success, data }`
|
|
1751
|
+
envelope, so it and `MetadataClient.publish` hold ONE belief about the route
|
|
1752
|
+
they share.
|
|
1753
|
+
|
|
1754
|
+
The two methods sit ~250 lines apart in `metadata-client.ts` and both POST
|
|
1755
|
+
`/api/v1/meta/:type/:name/publish`. `publishDraft` tolerated a dispatcher-shaped
|
|
1756
|
+
envelope and returned the inner object; `publish` returned the body as parsed.
|
|
1757
|
+
Nothing said which was right, and the card explicitly refused to settle it from
|
|
1758
|
+
`PublishMetaItemResponseSchema` alone — an inference from a declaration is not a
|
|
1759
|
+
measurement of the server.
|
|
1760
|
+
|
|
1761
|
+
So the producer was read instead, in the framework checkout:
|
|
1762
|
+
|
|
1763
|
+
- `POST /api/v1/meta/:type/:name/publish` has exactly ONE mount,
|
|
1764
|
+
`packages/rest/src/rest-server.ts`, whose handler ends
|
|
1765
|
+
`res.json(await p.publishMetaItem(publishRequest))` — the protocol object
|
|
1766
|
+
verbatim, no envelope branch on any arm. The ADR-0006 project-scoped base
|
|
1767
|
+
re-mounts that same handler.
|
|
1768
|
+
- The `{ success, data }` envelope has ONE producer, `HttpDispatcher.success()`,
|
|
1769
|
+
and it does not serve this route: no publish branch in `runtime`'s `/meta`
|
|
1770
|
+
domain (its three-segment arm is `/published`, GET only), no row in the
|
|
1771
|
+
dispatcher's route ledger, and a three-segment `/meta` path that is not
|
|
1772
|
+
`/published` terminates in a located `routeNotFound`. That holds for the
|
|
1773
|
+
dispatcher-only Hono adapter as much as for a full `rest` +
|
|
1774
|
+
`plugin-hono-server` boot, where the REST mount shadows the catch-all.
|
|
1775
|
+
- `packages/spec` declares the split in so many words:
|
|
1776
|
+
`PublishMetaItemResponseSchema` documents "the FULL body" of this route and
|
|
1777
|
+
records that the REST route hands the producer's object to `res.json()`
|
|
1778
|
+
verbatim, while the batch sibling `PublishPackageDraftsResponseSchema`
|
|
1779
|
+
documents a body answered "inside the dispatcher's `{ success, data }`
|
|
1780
|
+
envelope".
|
|
1781
|
+
|
|
1782
|
+
The route never envelopes, so the tolerance was a dialect with no producer —
|
|
1783
|
+
Commandment #0.1's lenient fallback, and the kind that fails in the direction
|
|
1784
|
+
that hides the problem: a body arriving enveloped is one this door did not
|
|
1785
|
+
serve, and unwrapping it presents that as a successful promotion.
|
|
1786
|
+
|
|
1787
|
+
**Behaviour delta, stated plainly.** Handed an enveloped body, `publishDraft`
|
|
1788
|
+
used to return the inner object and now returns the body as-is. Graded `patch`
|
|
1789
|
+
because that input is not one any measured configuration emits: on a conformant
|
|
1790
|
+
response — which has no `data` member at all — the removed branch was already a
|
|
1791
|
+
no-op, so `seedApplied`, `version` and `seq` read off both methods exactly as
|
|
1792
|
+
before. Sibling tolerances in the same file are untouched and still correct:
|
|
1793
|
+
`listDrafts` reads `data?.data?.drafts` because `GET /meta/_drafts` really is a
|
|
1794
|
+
dispatcher route, and `publishHealthFromResponse` unwraps because the batch
|
|
1795
|
+
publish door really is enveloped. The rule is per-route, not per-file.
|
|
1796
|
+
- f75810e: Parse a `droppedFields` wire entry's `fields` elements and its `object` at the
|
|
1797
|
+
write-warning boundary instead of asserting them.
|
|
1798
|
+
|
|
1799
|
+
The structural gate checked `Array.isArray(fields) && fields.length > 0` and then
|
|
1800
|
+
asserted the entry into a type declaring `fields: string[]` and a required
|
|
1801
|
+
`object: string` — reading neither. A response carrying `fields: [42]` reached
|
|
1802
|
+
`onWriteWarning` subscribers typed as a field name (the shell rendered it as the
|
|
1803
|
+
label `42`), and an entry that omitted `object` arrived claiming a string that was
|
|
1804
|
+
not there.
|
|
1805
|
+
|
|
1806
|
+
Now the wire type declares only what the gate establishes, and the notice is
|
|
1807
|
+
parsed: non-string `fields` elements are refused, an entry naming no field at all
|
|
1808
|
+
is dropped as `fields: []` already was, and a missing or non-string `object` is
|
|
1809
|
+
healed from the object the write targeted. Warnings are never silenced for a
|
|
1810
|
+
field the server really did name. No published type changes — `WriteWarningEvent`
|
|
1811
|
+
and `DroppedFieldsNotice` keep their shapes, and a subscriber's `fields: string[]`
|
|
1812
|
+
is now true rather than asserted.
|
|
1813
|
+
- Updated dependencies [97abedc]
|
|
1814
|
+
- Updated dependencies [b46c58f]
|
|
1815
|
+
- Updated dependencies [ad694ac]
|
|
1816
|
+
- Updated dependencies [6f96fca]
|
|
1817
|
+
- Updated dependencies [c131d9e]
|
|
1818
|
+
- Updated dependencies [5f00ff4]
|
|
1819
|
+
- Updated dependencies [c9e073a]
|
|
1820
|
+
- Updated dependencies [7b395d8]
|
|
1821
|
+
- Updated dependencies [0879812]
|
|
1822
|
+
- Updated dependencies [8cedb0d]
|
|
1823
|
+
- Updated dependencies [6cc910b]
|
|
1824
|
+
- Updated dependencies [061f5e8]
|
|
1825
|
+
- Updated dependencies [2dd4d3f]
|
|
1826
|
+
- Updated dependencies [4ab4f1b]
|
|
1827
|
+
- Updated dependencies [e3ea4f9]
|
|
1828
|
+
- Updated dependencies [8b1f066]
|
|
1829
|
+
- Updated dependencies [af243c1]
|
|
1830
|
+
- Updated dependencies [961ceaa]
|
|
1831
|
+
- Updated dependencies [f3f4e4c]
|
|
1832
|
+
- Updated dependencies [a05c350]
|
|
1833
|
+
- Updated dependencies [8c10f4f]
|
|
1834
|
+
- Updated dependencies [90dac98]
|
|
1835
|
+
- Updated dependencies [6096f20]
|
|
1836
|
+
- Updated dependencies [544aca2]
|
|
1837
|
+
- Updated dependencies [ea02938]
|
|
1838
|
+
- Updated dependencies [a14fb23]
|
|
1839
|
+
- Updated dependencies [ae98f1d]
|
|
1840
|
+
- Updated dependencies [f98eddf]
|
|
1841
|
+
- Updated dependencies [ce6bd99]
|
|
1842
|
+
- Updated dependencies [a5b08c9]
|
|
1843
|
+
- Updated dependencies [86982ac]
|
|
1844
|
+
- Updated dependencies [ff14e29]
|
|
1845
|
+
- Updated dependencies [9b28151]
|
|
1846
|
+
- Updated dependencies [64563a9]
|
|
1847
|
+
- Updated dependencies [1a5003f]
|
|
1848
|
+
- Updated dependencies [8acc51b]
|
|
1849
|
+
- Updated dependencies [3261e64]
|
|
1850
|
+
- Updated dependencies [d22b37b]
|
|
1851
|
+
- Updated dependencies [1daf477]
|
|
1852
|
+
- Updated dependencies [6c2f3c5]
|
|
1853
|
+
- Updated dependencies [fb13e85]
|
|
1854
|
+
- Updated dependencies [c2d8659]
|
|
1855
|
+
- Updated dependencies [e0f8202]
|
|
1856
|
+
- Updated dependencies [c3a26cc]
|
|
1857
|
+
- Updated dependencies [a66e58e]
|
|
1858
|
+
- Updated dependencies [d89492c]
|
|
1859
|
+
- Updated dependencies [9a5f998]
|
|
1860
|
+
- Updated dependencies [9327397]
|
|
1861
|
+
- Updated dependencies [17cc3a3]
|
|
1862
|
+
- Updated dependencies [4758b33]
|
|
1863
|
+
- Updated dependencies [9c78ebe]
|
|
1864
|
+
- Updated dependencies [12809a5]
|
|
1865
|
+
- Updated dependencies [f9c06ef]
|
|
1866
|
+
- Updated dependencies [5ad3b88]
|
|
1867
|
+
- Updated dependencies [f9d772b]
|
|
1868
|
+
- Updated dependencies [97b6c21]
|
|
1869
|
+
- Updated dependencies [baac95a]
|
|
1870
|
+
- Updated dependencies [29b45f6]
|
|
1871
|
+
- Updated dependencies [39b8d51]
|
|
1872
|
+
- Updated dependencies [17b323e]
|
|
1873
|
+
- Updated dependencies [b956e69]
|
|
1874
|
+
- Updated dependencies [33e58d8]
|
|
1875
|
+
- Updated dependencies [256b4c9]
|
|
1876
|
+
- Updated dependencies [fec3b1a]
|
|
1877
|
+
- Updated dependencies [b8e0941]
|
|
1878
|
+
- Updated dependencies [0c50f18]
|
|
1879
|
+
- Updated dependencies [1dae95a]
|
|
1880
|
+
- Updated dependencies [e32dae1]
|
|
1881
|
+
- Updated dependencies [30b11ad]
|
|
1882
|
+
- Updated dependencies [4aebea0]
|
|
1883
|
+
- Updated dependencies [f976774]
|
|
1884
|
+
- Updated dependencies [3d6badf]
|
|
1885
|
+
- Updated dependencies [25cb364]
|
|
1886
|
+
- Updated dependencies [c6678b1]
|
|
1887
|
+
- Updated dependencies [0638322]
|
|
1888
|
+
- Updated dependencies [e3782d2]
|
|
1889
|
+
- Updated dependencies [db0beb2]
|
|
1890
|
+
- Updated dependencies [997ce38]
|
|
1891
|
+
- Updated dependencies [ae0b9d3]
|
|
1892
|
+
- Updated dependencies [3b469c8]
|
|
1893
|
+
- Updated dependencies [990a2d6]
|
|
1894
|
+
- Updated dependencies [6650259]
|
|
1895
|
+
- Updated dependencies [4f8b7f8]
|
|
1896
|
+
- Updated dependencies [9e6619f]
|
|
1897
|
+
- Updated dependencies [f6ae5e2]
|
|
1898
|
+
- Updated dependencies [7343376]
|
|
1899
|
+
- Updated dependencies [b2683a2]
|
|
1900
|
+
- Updated dependencies [dded788]
|
|
1901
|
+
- Updated dependencies [b45d463]
|
|
1902
|
+
- Updated dependencies [54a7830]
|
|
1903
|
+
- Updated dependencies [f3135a4]
|
|
1904
|
+
- Updated dependencies [b5696d3]
|
|
1905
|
+
- Updated dependencies [3f9d926]
|
|
1906
|
+
- Updated dependencies [e978ed5]
|
|
1907
|
+
- Updated dependencies [6a7f24e]
|
|
1908
|
+
- Updated dependencies [b3c96d6]
|
|
1909
|
+
- Updated dependencies [8d0ca91]
|
|
1910
|
+
- Updated dependencies [c30c8dd]
|
|
1911
|
+
- Updated dependencies [328abeb]
|
|
1912
|
+
- Updated dependencies [24d3e65]
|
|
1913
|
+
- Updated dependencies [95a7c8d]
|
|
1914
|
+
- Updated dependencies [e227156]
|
|
1915
|
+
- Updated dependencies [cc4e476]
|
|
1916
|
+
- Updated dependencies [92970c4]
|
|
1917
|
+
- Updated dependencies [d570eaa]
|
|
1918
|
+
- Updated dependencies [42687ba]
|
|
1919
|
+
- Updated dependencies [6cd8f66]
|
|
1920
|
+
- Updated dependencies [24a0f14]
|
|
1921
|
+
- Updated dependencies [797a30f]
|
|
1922
|
+
- Updated dependencies [b4075c0]
|
|
1923
|
+
- Updated dependencies [9b85600]
|
|
1924
|
+
- Updated dependencies [99878d8]
|
|
1925
|
+
- Updated dependencies [3c13675]
|
|
1926
|
+
- Updated dependencies [63ab761]
|
|
1927
|
+
- Updated dependencies [0eb9f36]
|
|
1928
|
+
- Updated dependencies [ae582b7]
|
|
1929
|
+
- Updated dependencies [db11afd]
|
|
1930
|
+
- Updated dependencies [154075a]
|
|
1931
|
+
- Updated dependencies [582edef]
|
|
1932
|
+
- Updated dependencies [19f484f]
|
|
1933
|
+
- Updated dependencies [0a78a20]
|
|
1934
|
+
- Updated dependencies [615346d]
|
|
1935
|
+
- Updated dependencies [75dcc81]
|
|
1936
|
+
- Updated dependencies [55a12a8]
|
|
1937
|
+
- Updated dependencies [edfcf5a]
|
|
1938
|
+
- Updated dependencies [0a3e540]
|
|
1939
|
+
- Updated dependencies [f61dab1]
|
|
1940
|
+
- Updated dependencies [b0a05dd]
|
|
1941
|
+
- Updated dependencies [dd5ff19]
|
|
1942
|
+
- Updated dependencies [81f8498]
|
|
1943
|
+
- Updated dependencies [a782fa7]
|
|
1944
|
+
- Updated dependencies [76e9df0]
|
|
1945
|
+
- Updated dependencies [6158e4c]
|
|
1946
|
+
- Updated dependencies [c27b575]
|
|
1947
|
+
- Updated dependencies [0e6e76b]
|
|
1948
|
+
- Updated dependencies [bf43afa]
|
|
1949
|
+
- Updated dependencies [858eafb]
|
|
1950
|
+
- Updated dependencies [cd5b19a]
|
|
1951
|
+
- Updated dependencies [17dc167]
|
|
1952
|
+
- Updated dependencies [20d23be]
|
|
1953
|
+
- Updated dependencies [20d23be]
|
|
1954
|
+
- Updated dependencies [e6bc087]
|
|
1955
|
+
- Updated dependencies [a7557a7]
|
|
1956
|
+
- Updated dependencies [7d074ba]
|
|
1957
|
+
- Updated dependencies [6158e4c]
|
|
1958
|
+
- Updated dependencies [6158e4c]
|
|
1959
|
+
- Updated dependencies [52aad5c]
|
|
1960
|
+
- Updated dependencies [58da8ae]
|
|
1961
|
+
- Updated dependencies [138ad45]
|
|
1962
|
+
- Updated dependencies [138ad45]
|
|
1963
|
+
- Updated dependencies [5262f7d]
|
|
1964
|
+
- Updated dependencies [6aa029b]
|
|
1965
|
+
- Updated dependencies [770cc5b]
|
|
1966
|
+
- Updated dependencies [1a88ce2]
|
|
1967
|
+
- Updated dependencies [a1a44d6]
|
|
1968
|
+
- Updated dependencies [e0a9c67]
|
|
1969
|
+
- Updated dependencies [5638529]
|
|
1970
|
+
- Updated dependencies [5638529]
|
|
1971
|
+
- Updated dependencies [c476be0]
|
|
1972
|
+
- Updated dependencies [c82ff39]
|
|
1973
|
+
- Updated dependencies [6c3da53]
|
|
1974
|
+
- Updated dependencies [31987bd]
|
|
1975
|
+
- Updated dependencies [3c3ce15]
|
|
1976
|
+
- Updated dependencies [063119f]
|
|
1977
|
+
- Updated dependencies [e100589]
|
|
1978
|
+
- Updated dependencies [304f611]
|
|
1979
|
+
- Updated dependencies [e46ee77]
|
|
1980
|
+
- Updated dependencies [f9c8c4e]
|
|
1981
|
+
- Updated dependencies [6e9c8d2]
|
|
1982
|
+
- Updated dependencies [9547063]
|
|
1983
|
+
- Updated dependencies [3f6efd6]
|
|
1984
|
+
- Updated dependencies [c4ab6d0]
|
|
1985
|
+
- Updated dependencies [0e9058b]
|
|
1986
|
+
- Updated dependencies [5988b6b]
|
|
1987
|
+
- Updated dependencies [00ccdf7]
|
|
1988
|
+
- Updated dependencies [9d9ed54]
|
|
1989
|
+
- Updated dependencies [ca3de72]
|
|
1990
|
+
- Updated dependencies [83e3f83]
|
|
1991
|
+
- Updated dependencies [401611b]
|
|
1992
|
+
- Updated dependencies [2c0ddf2]
|
|
1993
|
+
- Updated dependencies [4abc0aa]
|
|
1994
|
+
- Updated dependencies [f560ded]
|
|
1995
|
+
- Updated dependencies [2b188fa]
|
|
1996
|
+
- Updated dependencies [f68e0a0]
|
|
1997
|
+
- Updated dependencies [aea682a]
|
|
1998
|
+
- Updated dependencies [fcdc8ec]
|
|
1999
|
+
- Updated dependencies [2d576e4]
|
|
2000
|
+
- Updated dependencies [8366acc]
|
|
2001
|
+
- Updated dependencies [95e58a3]
|
|
2002
|
+
- Updated dependencies [9d7419b]
|
|
2003
|
+
- Updated dependencies [fc7db05]
|
|
2004
|
+
- Updated dependencies [9ed8d0f]
|
|
2005
|
+
- Updated dependencies [c73cdb5]
|
|
2006
|
+
- Updated dependencies [6f5719e]
|
|
2007
|
+
- Updated dependencies [64dae8e]
|
|
2008
|
+
- Updated dependencies [06a8af5]
|
|
2009
|
+
- Updated dependencies [6a91586]
|
|
2010
|
+
- Updated dependencies [a04d7c6]
|
|
2011
|
+
- Updated dependencies [f3c2bb0]
|
|
2012
|
+
- Updated dependencies [9801765]
|
|
2013
|
+
- Updated dependencies [9cebfca]
|
|
2014
|
+
- Updated dependencies [460575f]
|
|
2015
|
+
- Updated dependencies [d88e20f]
|
|
2016
|
+
- Updated dependencies [2d7304d]
|
|
2017
|
+
- Updated dependencies [636b236]
|
|
2018
|
+
- Updated dependencies [d6d8fb9]
|
|
2019
|
+
- Updated dependencies [64d624d]
|
|
2020
|
+
- Updated dependencies [053fdc8]
|
|
2021
|
+
- Updated dependencies [ae476b8]
|
|
2022
|
+
- Updated dependencies [95bad12]
|
|
2023
|
+
- Updated dependencies [d2fb6ef]
|
|
2024
|
+
- Updated dependencies [fda49e5]
|
|
2025
|
+
- Updated dependencies [490d9a9]
|
|
2026
|
+
- Updated dependencies [fc62bb4]
|
|
2027
|
+
- Updated dependencies [41df893]
|
|
2028
|
+
- Updated dependencies [0cba1b7]
|
|
2029
|
+
- Updated dependencies [00f3eb5]
|
|
2030
|
+
- Updated dependencies [1ec291c]
|
|
2031
|
+
- Updated dependencies [453dbaa]
|
|
2032
|
+
- Updated dependencies [69a2163]
|
|
2033
|
+
- Updated dependencies [24e027e]
|
|
2034
|
+
- Updated dependencies [2c3cd1b]
|
|
2035
|
+
- Updated dependencies [90665e0]
|
|
2036
|
+
- Updated dependencies [7e19d03]
|
|
2037
|
+
- Updated dependencies [1e946c9]
|
|
2038
|
+
- Updated dependencies [546ddf7]
|
|
2039
|
+
- Updated dependencies [864154e]
|
|
2040
|
+
- Updated dependencies [b023625]
|
|
2041
|
+
- Updated dependencies [75bd83d]
|
|
2042
|
+
- Updated dependencies [44d075b]
|
|
2043
|
+
- Updated dependencies [40c479a]
|
|
2044
|
+
- Updated dependencies [971d387]
|
|
2045
|
+
- Updated dependencies [ee851c3]
|
|
2046
|
+
- Updated dependencies [6414dfd]
|
|
2047
|
+
- Updated dependencies [a8d5c71]
|
|
2048
|
+
- Updated dependencies [905b21f]
|
|
2049
|
+
- Updated dependencies [88e9109]
|
|
2050
|
+
- Updated dependencies [2c45966]
|
|
2051
|
+
- Updated dependencies [db3a600]
|
|
2052
|
+
- Updated dependencies [3a3db76]
|
|
2053
|
+
- Updated dependencies [0d723a3]
|
|
2054
|
+
- Updated dependencies [0c95d3d]
|
|
2055
|
+
- Updated dependencies [3e4fa2c]
|
|
2056
|
+
- Updated dependencies [b5b928a]
|
|
2057
|
+
- Updated dependencies [52a43de]
|
|
2058
|
+
- Updated dependencies [195052f]
|
|
2059
|
+
- Updated dependencies [e4559d1]
|
|
2060
|
+
- Updated dependencies [2c71482]
|
|
2061
|
+
- Updated dependencies [a26b9e4]
|
|
2062
|
+
- Updated dependencies [5ef9c4f]
|
|
2063
|
+
- Updated dependencies [46f0bb4]
|
|
2064
|
+
- Updated dependencies [06b82b8]
|
|
2065
|
+
- Updated dependencies [6f81384]
|
|
2066
|
+
- Updated dependencies [8f1d995]
|
|
2067
|
+
- Updated dependencies [dddb942]
|
|
2068
|
+
- Updated dependencies [29754cf]
|
|
2069
|
+
- Updated dependencies [3c2b6f7]
|
|
2070
|
+
- Updated dependencies [b84dc18]
|
|
2071
|
+
- Updated dependencies [ac8abb0]
|
|
2072
|
+
- Updated dependencies [9d86e1d]
|
|
2073
|
+
- Updated dependencies [3a5817f]
|
|
2074
|
+
- Updated dependencies [99a3c2d]
|
|
2075
|
+
- Updated dependencies [5961030]
|
|
2076
|
+
- Updated dependencies [c8ea8af]
|
|
2077
|
+
- Updated dependencies [3190414]
|
|
2078
|
+
- Updated dependencies [4e480f5]
|
|
2079
|
+
- Updated dependencies [38a123c]
|
|
2080
|
+
- Updated dependencies [299102e]
|
|
2081
|
+
- Updated dependencies [d7acad6]
|
|
2082
|
+
- Updated dependencies [45a9aeb]
|
|
2083
|
+
- Updated dependencies [713db46]
|
|
2084
|
+
- Updated dependencies [bf3a03c]
|
|
2085
|
+
- Updated dependencies [cb55718]
|
|
2086
|
+
- Updated dependencies [831be72]
|
|
2087
|
+
- Updated dependencies [29cb85b]
|
|
2088
|
+
- Updated dependencies [3e028c8]
|
|
2089
|
+
- Updated dependencies [d0889e2]
|
|
2090
|
+
- Updated dependencies [ce503e5]
|
|
2091
|
+
- Updated dependencies [f20dcf0]
|
|
2092
|
+
- Updated dependencies [4ca30d0]
|
|
2093
|
+
- Updated dependencies [7a5da14]
|
|
2094
|
+
- Updated dependencies [2c1c967]
|
|
2095
|
+
- Updated dependencies [4d5f9b4]
|
|
2096
|
+
- Updated dependencies [d6ceb8d]
|
|
2097
|
+
- Updated dependencies [7977ff9]
|
|
2098
|
+
- Updated dependencies [3beef6d]
|
|
2099
|
+
- Updated dependencies [2acd8e1]
|
|
2100
|
+
- Updated dependencies [045d20b]
|
|
2101
|
+
- Updated dependencies [a2d2515]
|
|
2102
|
+
- Updated dependencies [adb2a86]
|
|
2103
|
+
- Updated dependencies [3619792]
|
|
2104
|
+
- Updated dependencies [3561bd2]
|
|
2105
|
+
- Updated dependencies [bf97b98]
|
|
2106
|
+
- Updated dependencies [b0d308d]
|
|
2107
|
+
- Updated dependencies [40f34b4]
|
|
2108
|
+
- Updated dependencies [8063bcb]
|
|
2109
|
+
- Updated dependencies [b74a859]
|
|
2110
|
+
- Updated dependencies [d4493fd]
|
|
2111
|
+
- Updated dependencies [240b80f]
|
|
2112
|
+
- Updated dependencies [77cb489]
|
|
2113
|
+
- Updated dependencies [bfaa158]
|
|
2114
|
+
- Updated dependencies [777e5c6]
|
|
2115
|
+
- Updated dependencies [0c386dd]
|
|
2116
|
+
- Updated dependencies [9e37d9b]
|
|
2117
|
+
- Updated dependencies [5ad86dd]
|
|
2118
|
+
- Updated dependencies [16a725f]
|
|
2119
|
+
- Updated dependencies [4dfdcc3]
|
|
2120
|
+
- Updated dependencies [446d93d]
|
|
2121
|
+
- Updated dependencies [ecd9cb2]
|
|
2122
|
+
- Updated dependencies [98d4108]
|
|
2123
|
+
- Updated dependencies [0e3b3be]
|
|
2124
|
+
- Updated dependencies [a29ae2d]
|
|
2125
|
+
- Updated dependencies [4388f71]
|
|
2126
|
+
- Updated dependencies [0b1ac58]
|
|
2127
|
+
- Updated dependencies [c93b4d5]
|
|
2128
|
+
- Updated dependencies [c1fe272]
|
|
2129
|
+
- Updated dependencies [8ad218d]
|
|
2130
|
+
- Updated dependencies [3e41187]
|
|
2131
|
+
- Updated dependencies [5f78953]
|
|
2132
|
+
- Updated dependencies [639114c]
|
|
2133
|
+
- Updated dependencies [1f31d3a]
|
|
2134
|
+
- Updated dependencies [351eb31]
|
|
2135
|
+
- Updated dependencies [20c04b2]
|
|
2136
|
+
- Updated dependencies [48c19bd]
|
|
2137
|
+
- Updated dependencies [a6d8b8d]
|
|
2138
|
+
- Updated dependencies [b652514]
|
|
2139
|
+
- Updated dependencies [adbda1b]
|
|
2140
|
+
- Updated dependencies [e2b3826]
|
|
2141
|
+
- Updated dependencies [2e32ed4]
|
|
2142
|
+
- Updated dependencies [e75f4c9]
|
|
2143
|
+
- Updated dependencies [19f1639]
|
|
2144
|
+
- Updated dependencies [47547d0]
|
|
2145
|
+
- Updated dependencies [1bee5d0]
|
|
2146
|
+
- Updated dependencies [858cd72]
|
|
2147
|
+
- Updated dependencies [cfc9b6d]
|
|
2148
|
+
- Updated dependencies [554f2b6]
|
|
2149
|
+
- Updated dependencies [669d71b]
|
|
2150
|
+
- Updated dependencies [ed27d7c]
|
|
2151
|
+
- Updated dependencies [52c8cf7]
|
|
2152
|
+
- Updated dependencies [2ceb43a]
|
|
2153
|
+
- Updated dependencies [7cdd2b9]
|
|
2154
|
+
- Updated dependencies [52c8cf7]
|
|
2155
|
+
- Updated dependencies [81a2eb1]
|
|
2156
|
+
- Updated dependencies [caa0cd3]
|
|
2157
|
+
- Updated dependencies [25c7d58]
|
|
2158
|
+
- Updated dependencies [00d2fa6]
|
|
2159
|
+
- Updated dependencies [c6198c2]
|
|
2160
|
+
- Updated dependencies [721d1e0]
|
|
2161
|
+
- Updated dependencies [1237ae4]
|
|
2162
|
+
- Updated dependencies [51eb515]
|
|
2163
|
+
- Updated dependencies [c354ce5]
|
|
2164
|
+
- Updated dependencies [8fe8e5c]
|
|
2165
|
+
- Updated dependencies [feac439]
|
|
2166
|
+
- Updated dependencies [efbd566]
|
|
2167
|
+
- Updated dependencies [9587fc9]
|
|
2168
|
+
- Updated dependencies [e62c44e]
|
|
2169
|
+
- Updated dependencies [5d0876c]
|
|
2170
|
+
- Updated dependencies [b041b9c]
|
|
2171
|
+
- Updated dependencies [ce2aaef]
|
|
2172
|
+
- Updated dependencies [544ecba]
|
|
2173
|
+
- Updated dependencies [bc640ec]
|
|
2174
|
+
- Updated dependencies [1e215c4]
|
|
2175
|
+
- Updated dependencies [da6e191]
|
|
2176
|
+
- Updated dependencies [3e377c9]
|
|
2177
|
+
- Updated dependencies [a3eb5d0]
|
|
2178
|
+
- Updated dependencies [4ce14f1]
|
|
2179
|
+
- Updated dependencies [aef97e5]
|
|
2180
|
+
- Updated dependencies [2af1fa7]
|
|
2181
|
+
- Updated dependencies [a137d0c]
|
|
2182
|
+
- Updated dependencies [caf477f]
|
|
2183
|
+
- Updated dependencies [f6375da]
|
|
2184
|
+
- Updated dependencies [967e5d8]
|
|
2185
|
+
- Updated dependencies [a4611b3]
|
|
2186
|
+
- Updated dependencies [20316ba]
|
|
2187
|
+
- Updated dependencies [d3499b3]
|
|
2188
|
+
- Updated dependencies [309c75e]
|
|
2189
|
+
- Updated dependencies [c9f9bae]
|
|
2190
|
+
- Updated dependencies [18897a4]
|
|
2191
|
+
- Updated dependencies [8b7ea39]
|
|
2192
|
+
- Updated dependencies [dcbf0b2]
|
|
2193
|
+
- Updated dependencies [52cac38]
|
|
2194
|
+
- Updated dependencies [93fea2e]
|
|
2195
|
+
- Updated dependencies [1422a92]
|
|
2196
|
+
- Updated dependencies [d05fe17]
|
|
2197
|
+
- Updated dependencies [a480f79]
|
|
2198
|
+
- Updated dependencies [f08d1a8]
|
|
2199
|
+
- Updated dependencies [64a252d]
|
|
2200
|
+
- Updated dependencies [786bc91]
|
|
2201
|
+
- Updated dependencies [75fca96]
|
|
2202
|
+
- Updated dependencies [7ca6ddd]
|
|
2203
|
+
- Updated dependencies [f1cd290]
|
|
2204
|
+
- Updated dependencies [5a41ce7]
|
|
2205
|
+
- Updated dependencies [8d50bc2]
|
|
2206
|
+
- Updated dependencies [604476d]
|
|
2207
|
+
- Updated dependencies [335abea]
|
|
2208
|
+
- Updated dependencies [0f5cadf]
|
|
2209
|
+
- Updated dependencies [4f9f1ee]
|
|
2210
|
+
- Updated dependencies [66e8b2a]
|
|
2211
|
+
- Updated dependencies [aa083cd]
|
|
2212
|
+
- Updated dependencies [12b5992]
|
|
2213
|
+
- Updated dependencies [b93e245]
|
|
2214
|
+
- Updated dependencies [c842594]
|
|
2215
|
+
- Updated dependencies [290de37]
|
|
2216
|
+
- Updated dependencies [8c8da45]
|
|
2217
|
+
- Updated dependencies [8cd8eb5]
|
|
2218
|
+
- Updated dependencies [cf1d29e]
|
|
2219
|
+
- Updated dependencies [1bd79c8]
|
|
2220
|
+
- Updated dependencies [af9e957]
|
|
2221
|
+
- Updated dependencies [c974edf]
|
|
2222
|
+
- Updated dependencies [ad852b6]
|
|
2223
|
+
- Updated dependencies [ee4d19f]
|
|
2224
|
+
- Updated dependencies [496d31d]
|
|
2225
|
+
- Updated dependencies [0ea7054]
|
|
2226
|
+
- Updated dependencies [9a853f2]
|
|
2227
|
+
- Updated dependencies [cb847fd]
|
|
2228
|
+
- Updated dependencies [ee70287]
|
|
2229
|
+
- Updated dependencies [3e98e13]
|
|
2230
|
+
- Updated dependencies [fc32921]
|
|
2231
|
+
- Updated dependencies [4eaa835]
|
|
2232
|
+
- Updated dependencies [8f9d87a]
|
|
2233
|
+
- Updated dependencies [b1777ae]
|
|
2234
|
+
- Updated dependencies [24845c4]
|
|
2235
|
+
- Updated dependencies [6f864cf]
|
|
2236
|
+
- Updated dependencies [24d1edd]
|
|
2237
|
+
- Updated dependencies [645087c]
|
|
2238
|
+
- Updated dependencies [33f4a19]
|
|
2239
|
+
- Updated dependencies [5323168]
|
|
2240
|
+
- Updated dependencies [841dd2b]
|
|
2241
|
+
- Updated dependencies [3014fc0]
|
|
2242
|
+
- Updated dependencies [dacb402]
|
|
2243
|
+
- Updated dependencies [846cec0]
|
|
2244
|
+
- Updated dependencies [91facae]
|
|
2245
|
+
- Updated dependencies [b38014e]
|
|
2246
|
+
- Updated dependencies [474797d]
|
|
2247
|
+
- Updated dependencies [704e695]
|
|
2248
|
+
- Updated dependencies [a407bd6]
|
|
2249
|
+
- Updated dependencies [317dbce]
|
|
2250
|
+
- Updated dependencies [3a43a15]
|
|
2251
|
+
- Updated dependencies [f76f436]
|
|
2252
|
+
- Updated dependencies [ce45a03]
|
|
2253
|
+
- Updated dependencies [421544b]
|
|
2254
|
+
- Updated dependencies [fb01022]
|
|
2255
|
+
- Updated dependencies [e9d9212]
|
|
2256
|
+
- Updated dependencies [ecfb693]
|
|
2257
|
+
- Updated dependencies [abc1b18]
|
|
2258
|
+
- Updated dependencies [81a51db]
|
|
2259
|
+
- Updated dependencies [67749c7]
|
|
2260
|
+
- Updated dependencies [507b61b]
|
|
2261
|
+
- Updated dependencies [512c84b]
|
|
2262
|
+
- Updated dependencies [fb3a101]
|
|
2263
|
+
- Updated dependencies [d4733f2]
|
|
2264
|
+
- Updated dependencies [f391ede]
|
|
2265
|
+
- Updated dependencies [f5cfbbd]
|
|
2266
|
+
- Updated dependencies [f5cfbbd]
|
|
2267
|
+
- Updated dependencies [8b532cb]
|
|
2268
|
+
- Updated dependencies [64c3cdd]
|
|
2269
|
+
- Updated dependencies [4d65991]
|
|
2270
|
+
- Updated dependencies [c42554e]
|
|
2271
|
+
- Updated dependencies [555b4ec]
|
|
2272
|
+
- Updated dependencies [1ccfc23]
|
|
2273
|
+
- Updated dependencies [542718f]
|
|
2274
|
+
- Updated dependencies [7f27bc5]
|
|
2275
|
+
- Updated dependencies [0a174f3]
|
|
2276
|
+
- Updated dependencies [f95b140]
|
|
2277
|
+
- Updated dependencies [541ce4e]
|
|
2278
|
+
- Updated dependencies [6479086]
|
|
2279
|
+
- Updated dependencies [d79f525]
|
|
2280
|
+
- Updated dependencies [d1865d2]
|
|
2281
|
+
- Updated dependencies [f1190b0]
|
|
2282
|
+
- Updated dependencies [561abef]
|
|
2283
|
+
- Updated dependencies [6a4680b]
|
|
2284
|
+
- Updated dependencies [c3a4273]
|
|
2285
|
+
- Updated dependencies [abf710d]
|
|
2286
|
+
- Updated dependencies [093af32]
|
|
2287
|
+
- Updated dependencies [1bd1be7]
|
|
2288
|
+
- Updated dependencies [d234fa9]
|
|
2289
|
+
- Updated dependencies [adf5812]
|
|
2290
|
+
- Updated dependencies [5058336]
|
|
2291
|
+
- Updated dependencies [2f6b2bf]
|
|
2292
|
+
- Updated dependencies [2028b31]
|
|
2293
|
+
- Updated dependencies [63601ab]
|
|
2294
|
+
- Updated dependencies [c372b29]
|
|
2295
|
+
- Updated dependencies [8693b85]
|
|
2296
|
+
- Updated dependencies [58b7b3d]
|
|
2297
|
+
- Updated dependencies [84defab]
|
|
2298
|
+
- Updated dependencies [681d3f1]
|
|
2299
|
+
- Updated dependencies [969d4f2]
|
|
2300
|
+
- Updated dependencies [f3bc481]
|
|
2301
|
+
- Updated dependencies [b79aac2]
|
|
2302
|
+
- Updated dependencies [93fc0e7]
|
|
2303
|
+
- Updated dependencies [a4b723f]
|
|
2304
|
+
- Updated dependencies [2b10ca0]
|
|
2305
|
+
- Updated dependencies [7db4a81]
|
|
2306
|
+
- Updated dependencies [19a0b0e]
|
|
2307
|
+
- Updated dependencies [526fc11]
|
|
2308
|
+
- Updated dependencies [f8e3e9a]
|
|
2309
|
+
- Updated dependencies [3b6bc69]
|
|
2310
|
+
- Updated dependencies [6732df4]
|
|
2311
|
+
- Updated dependencies [fe9e0d0]
|
|
2312
|
+
- Updated dependencies [63fb72c]
|
|
2313
|
+
- Updated dependencies [804831c]
|
|
2314
|
+
- Updated dependencies [279e48e]
|
|
2315
|
+
- Updated dependencies [8700d6d]
|
|
2316
|
+
- Updated dependencies [8db2a0f]
|
|
2317
|
+
- Updated dependencies [30443fb]
|
|
2318
|
+
- Updated dependencies [da45e6b]
|
|
2319
|
+
- Updated dependencies [835f0f3]
|
|
2320
|
+
- Updated dependencies [ed35b44]
|
|
2321
|
+
- Updated dependencies [729e851]
|
|
2322
|
+
- Updated dependencies [96919a4]
|
|
2323
|
+
- Updated dependencies [20b507a]
|
|
2324
|
+
- Updated dependencies [2e471dc]
|
|
2325
|
+
- Updated dependencies [be50942]
|
|
2326
|
+
- Updated dependencies [775e079]
|
|
2327
|
+
- Updated dependencies [7e8b3c0]
|
|
2328
|
+
- Updated dependencies [53374dc]
|
|
2329
|
+
- Updated dependencies [f6fb83f]
|
|
2330
|
+
- Updated dependencies [2049b03]
|
|
2331
|
+
- Updated dependencies [7cbc724]
|
|
2332
|
+
- Updated dependencies [fb91ac9]
|
|
2333
|
+
- Updated dependencies [8524372]
|
|
2334
|
+
- Updated dependencies [7cbefa5]
|
|
2335
|
+
- Updated dependencies [72d6587]
|
|
2336
|
+
- Updated dependencies [a272a4f]
|
|
2337
|
+
- Updated dependencies [55f39ee]
|
|
2338
|
+
- Updated dependencies [0970a0e]
|
|
2339
|
+
- Updated dependencies [e427e9c]
|
|
2340
|
+
- Updated dependencies [bbc9dc3]
|
|
2341
|
+
- Updated dependencies [02f1813]
|
|
2342
|
+
- Updated dependencies [ba0b61a]
|
|
2343
|
+
- Updated dependencies [ac716ff]
|
|
2344
|
+
- Updated dependencies [f0f3cd5]
|
|
2345
|
+
- Updated dependencies [ab856ed]
|
|
2346
|
+
- Updated dependencies [20f3e65]
|
|
2347
|
+
- Updated dependencies [bbba098]
|
|
2348
|
+
- Updated dependencies [87af769]
|
|
2349
|
+
- Updated dependencies [3be720e]
|
|
2350
|
+
- Updated dependencies [43c0d17]
|
|
2351
|
+
- Updated dependencies [c3df43a]
|
|
2352
|
+
- Updated dependencies [d16d0e9]
|
|
2353
|
+
- Updated dependencies [bbe57fd]
|
|
2354
|
+
- Updated dependencies [272a530]
|
|
2355
|
+
- Updated dependencies [1779e8d]
|
|
2356
|
+
- Updated dependencies [f0f4d6c]
|
|
2357
|
+
- Updated dependencies [4128188]
|
|
2358
|
+
- Updated dependencies [b253c4e]
|
|
2359
|
+
- Updated dependencies [78a9c67]
|
|
2360
|
+
- Updated dependencies [4a7ef0d]
|
|
2361
|
+
- Updated dependencies [dea17b4]
|
|
2362
|
+
- Updated dependencies [89bb77a]
|
|
2363
|
+
- Updated dependencies [06611e4]
|
|
2364
|
+
- Updated dependencies [dc3893d]
|
|
2365
|
+
- Updated dependencies [1bbaa16]
|
|
2366
|
+
- Updated dependencies [6ee259a]
|
|
2367
|
+
- Updated dependencies [e708426]
|
|
2368
|
+
- Updated dependencies [3b6d53b]
|
|
2369
|
+
- Updated dependencies [276d174]
|
|
2370
|
+
- Updated dependencies [2982ed9]
|
|
2371
|
+
- Updated dependencies [a8198de]
|
|
2372
|
+
- Updated dependencies [05a49f2]
|
|
2373
|
+
- Updated dependencies [a78cd37]
|
|
2374
|
+
- Updated dependencies [5ea623e]
|
|
2375
|
+
- Updated dependencies [5eabe86]
|
|
2376
|
+
- Updated dependencies [ca5d671]
|
|
2377
|
+
- Updated dependencies [32bf2d6]
|
|
2378
|
+
- Updated dependencies [af4fb29]
|
|
2379
|
+
- Updated dependencies [ff5ef1c]
|
|
2380
|
+
- Updated dependencies [9a97800]
|
|
2381
|
+
- Updated dependencies [6bca0e4]
|
|
2382
|
+
- Updated dependencies [81c0bc4]
|
|
2383
|
+
- Updated dependencies [60500cb]
|
|
2384
|
+
- Updated dependencies [2fcefb9]
|
|
2385
|
+
- Updated dependencies [b55a346]
|
|
2386
|
+
- Updated dependencies [065bba7]
|
|
2387
|
+
- Updated dependencies [6791717]
|
|
2388
|
+
- Updated dependencies [100547e]
|
|
2389
|
+
- Updated dependencies [6d1c155]
|
|
2390
|
+
- Updated dependencies [d7573b3]
|
|
2391
|
+
- Updated dependencies [bf3edfe]
|
|
2392
|
+
- Updated dependencies [0e05aac]
|
|
2393
|
+
- Updated dependencies [5aed9e4]
|
|
2394
|
+
- Updated dependencies [83c77dc]
|
|
2395
|
+
- Updated dependencies [18a8e7d]
|
|
2396
|
+
- Updated dependencies [e7957ab]
|
|
2397
|
+
- Updated dependencies [f7e34ca]
|
|
2398
|
+
- Updated dependencies [e719ebd]
|
|
2399
|
+
- Updated dependencies [516583b]
|
|
2400
|
+
- Updated dependencies [f9e4f91]
|
|
2401
|
+
- Updated dependencies [6ef48b1]
|
|
2402
|
+
- Updated dependencies [fa429cf]
|
|
2403
|
+
- Updated dependencies [ed8df3e]
|
|
2404
|
+
- Updated dependencies [8b446f5]
|
|
2405
|
+
- Updated dependencies [8e74b27]
|
|
2406
|
+
- Updated dependencies [8ebd57f]
|
|
2407
|
+
- Updated dependencies [617707a]
|
|
2408
|
+
- Updated dependencies [7357447]
|
|
2409
|
+
- Updated dependencies [199d31b]
|
|
2410
|
+
- Updated dependencies [3e01cb5]
|
|
2411
|
+
- Updated dependencies [7138bc1]
|
|
2412
|
+
- Updated dependencies [cef27e2]
|
|
2413
|
+
- Updated dependencies [4e8622b]
|
|
2414
|
+
- Updated dependencies [dffd752]
|
|
2415
|
+
- Updated dependencies [06973aa]
|
|
2416
|
+
- Updated dependencies [50798f3]
|
|
2417
|
+
- Updated dependencies [105f3c5]
|
|
2418
|
+
- Updated dependencies [3ccd9e8]
|
|
2419
|
+
- Updated dependencies [689b979]
|
|
2420
|
+
- Updated dependencies [e546222]
|
|
2421
|
+
- Updated dependencies [fd13f52]
|
|
2422
|
+
- Updated dependencies [fb336df]
|
|
2423
|
+
- Updated dependencies [0fce2ef]
|
|
2424
|
+
- Updated dependencies [42df928]
|
|
2425
|
+
- Updated dependencies [0e2ddd4]
|
|
2426
|
+
- Updated dependencies [b7479ab]
|
|
2427
|
+
- Updated dependencies [b2ea297]
|
|
2428
|
+
- Updated dependencies [5b5a5c3]
|
|
2429
|
+
- Updated dependencies [14582b8]
|
|
2430
|
+
- Updated dependencies [51e144e]
|
|
2431
|
+
- Updated dependencies [a691c0b]
|
|
2432
|
+
- Updated dependencies [af3861f]
|
|
2433
|
+
- Updated dependencies [515f171]
|
|
2434
|
+
- Updated dependencies [1f4e029]
|
|
2435
|
+
- Updated dependencies [258d264]
|
|
2436
|
+
- Updated dependencies [c00bf28]
|
|
2437
|
+
- Updated dependencies [93127bd]
|
|
2438
|
+
- Updated dependencies [f2158ec]
|
|
2439
|
+
- Updated dependencies [759606e]
|
|
2440
|
+
- Updated dependencies [a51fa0c]
|
|
2441
|
+
- Updated dependencies [51f3d8d]
|
|
2442
|
+
- Updated dependencies [78cbdb5]
|
|
2443
|
+
- Updated dependencies [b7543a9]
|
|
2444
|
+
- Updated dependencies [6c6cee7]
|
|
2445
|
+
- Updated dependencies [83fe6e7]
|
|
2446
|
+
- Updated dependencies [d1ab06f]
|
|
2447
|
+
- Updated dependencies [91783c4]
|
|
2448
|
+
- Updated dependencies [ca39427]
|
|
2449
|
+
- Updated dependencies [2d36552]
|
|
2450
|
+
- Updated dependencies [c9327c9]
|
|
2451
|
+
- Updated dependencies [920165d]
|
|
2452
|
+
- Updated dependencies [968dc1e]
|
|
2453
|
+
- Updated dependencies [3c73d99]
|
|
2454
|
+
- Updated dependencies [ed71d9e]
|
|
2455
|
+
- Updated dependencies [7776fc2]
|
|
2456
|
+
- Updated dependencies [e76634c]
|
|
2457
|
+
- Updated dependencies [1170ed1]
|
|
2458
|
+
- Updated dependencies [92814db]
|
|
2459
|
+
- Updated dependencies [4d73b07]
|
|
2460
|
+
- @object-ui/core@17.7.0
|
|
2461
|
+
- @object-ui/types@17.7.0
|
|
2462
|
+
|
|
3
2463
|
## 17.6.0
|
|
4
2464
|
|
|
5
2465
|
### Minor Changes
|