You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit fb239eb
Browse filesBrowse the repository at this point in the historyBrowse files
The `object-grid` page block now declares `description`, `emptyState` and `keyboardNavigation`, so a page that authors them validates clean instead of having each reported as a prop the block does not declare (#20694).
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-**`description`** — an `I18nLabel` (a string, or an inline locale map): one line of help text the grid draws above its rows, resolved against the display locale.
10
+
-**`emptyState`** — `{ title?, message?, icon? }`, what the grid draws instead of an empty table. It is the list view's own empty-state shape, now exported as `EmptyStateSchema` (author type `EmptyState`) and taken by reference on both `list-view` and `object-grid`, so the two cannot drift apart.
11
+
-**`keyboardNavigation`** — a boolean, marked `[EXPERIMENTAL — not enforced]`: arrow-key cell navigation on the WAI-ARIA grid pattern, on by default when `editable` is set. No renderer reads it yet, so authoring it changes nothing today.
12
+
13
+
Nothing that parsed before is refused now. The `object-grid` row still refuses every key it does not declare, and the list view's `emptyState` accepts exactly the values it accepted before the shape was extracted. One refusal message is reworded: an `action` or `button` key inside an empty state is still refused and still points at the list view's `addRecord` block, now phrased so that it also reads true on `object-grid`, which has no add-record block.
14
+
15
+
On `object-grid`, write `emptyState.title` and `emptyState.message` as plain strings for now: the grid renderer draws both as they are and does not yet resolve an inline locale map there the way it resolves `description`, so a map in either member fails to render.
16
+
17
+
Where it surfaces: the component-props gate (`os validate`, `os build`, `os lint`) no longer reports these three keys on an `object-grid` node, and the published JSON Schema for `ObjectGridProps` describes them.
`isUninterpretableTemporalComparand` reads a bare wall clock on a `time` column by the spec's `ClockTimeValueSchema` (`@objectstack/spec/data`), not by a private copy of it (#20771)
6
+
7
+
Clause-②: no
8
+
9
+
The wall-clock half of core's `time` rule (`HH:MM[:SS[.fraction]]`, hours 00 to 23, minutes and seconds 00 to 59, no time zone) was spelled twice: once as the spec's `ClockTimeValueSchema`, the stored form of a `time` value, and once as a private regex in `@objectstack/core`. The two admitted the same strings, but nothing tied them together, so an edit to either one changed one side only. Core now asks the spec schema. Every caller of `isUninterpretableTemporalComparand('time', …)` therefore answers from the rule the spec's `time` default gate uses: the engine's temporal-comparand door, the analytics comparand check, the record validator's `time` arm and the import's `time` coercion.
10
+
11
+
Unchanged:
12
+
13
+
- Every string gets the verdict it got before. Measured over 8,655,360 generated strings: 8,640 read by both the old regex and the schema, the rest refused by both, 0 answered differently.
14
+
- An instant, a number or a `Date` on a `time` column is judged as before.
fix(service-analytics)!: a cube or dataset dimension on a structured-JSON field is refused with `INVALID_FIELD` / 400 at the analytics door, before any SQL is built
6
+
7
+
Clause-②: no (narrowing)
8
+
9
+
<!-- adr-0087: not-required (no-migration-prescription) a refusal of a grouping TARGET at the analytics door: a `dimensions` entry, or a bucketed `timeDimensions` entry, whose column is a declared json, composite, repeater, record, location, address or vector field. No authorable key, spelling, export or stored shape moves (the door module is internal; `@objectstack/service-analytics` exports nothing new and nothing less, and `CubeSchema`, `DatasetSchema` and the analytics query body keep parsing every member), and no stored row is read or rewritten. The grouping had no shared meaning to preserve (one group per serialized document on SQLite, a 500 on PostgreSQL), and which scalar part of the document a caller meant to group on is not something a ledger entry can rewrite. The other categories are closed on facts: the package publishes (not `unpublished`); no ADR-0087 id covers a grouping target (not `already-registered`); and the change is runtime behaviour, not a declaration (not `runtime-interface-only` / `type-surface-only`). -->
10
+
11
+
**BREAKING**: this narrows what the analytics query doors accept as a dimension. A cube dimension, or a dataset dimension, whose column is a declared field of the structured-JSON class (`json`, `composite`, `repeater`, `record`, `location`, `address`, `vector`) is refused before either strategy builds a statement, when it groups the result: a `dimensions` entry, or a `timeDimensions` entry with a `granularity`. The column is judged where it is declared: on the cube's object, or, for a dotted path such as a dataset dimension over `account.hq`, on the object the cube's declared join for that path names. It holds on `POST /api/v1/analytics/query`, on its dry run `POST /api/v1/analytics/sql`, and on `POST /api/v1/analytics/dataset/query`, on every driver. It ships as `minor` under the launch-window convention for accept-set narrowings. No export or published type changes.
12
+
13
+
**What an author sees now.**`400 INVALID_FIELD`, naming the member as the request wrote it (the cube dimension, or the dataset dimension), the column it groups by, the object and the column's declared type, saying the query was not run, and naming the route: group by a field that stores one scalar value, storing the part of the document you group on in a field of its own. The thrown error carries `member`, `param` (`dimensions` or `timeDimensions`), `cube`, `field` and `object`.
14
+
15
+
**Why a refusal.** A JSON document is no group key the SQL dialects share. Measured through `POST /api/v1/analytics/query` over three rows with a different document each, on the service `AnalyticsServicePlugin` composes over a real engine: SQLite answered 200 with one group per serialized document, and PostgreSQL 16 answered 500 `DATABASE_ERROR`. A dataset dimension over a joined object's `json` field answered the same two ways through `POST /api/v1/analytics/dataset/query`. The native-SQL strategy compiled the `GROUP BY` itself, so the engine's own refusal of a structured-JSON `groupBy` never saw the query; the engine-aggregate strategy did reach that refusal, but named the engine's `groupBy[0]` position rather than the member the caller wrote. The class is `@objectstack/spec/data`'s `STRUCTURED_JSON_TYPES`, the one the engine's refusal reads. No producer groups by such a field: no cube or dataset dimension in the example apps names one.
16
+
17
+
**Who is affected.** A dashboard, report or caller that grouped an analytics query by a structured-JSON field on SQLite and read one group per serialized document as real groups. On PostgreSQL the same query was already a 500.
18
+
19
+
**Unchanged.** A dimension on any other type; a `timeDimensions` entry with no `granularity`, which bounds a range and groups nothing; measures (this door judges only the members that group); a dotted dimension path the cube declares no join for, whose object is not a declaration; a member naming a column the object does not have, which keeps its existing `INVALID_FIELD` answer first; and a host that wires no `sourceFieldMeta`, where the column's type cannot be read.
fix(spec): the `ui-object-grid-page-size-positive-integer-refused` migration entry states what the platform does with a stored `object-grid` page size of `0` — the page saves and loads, and the finding is advisory on the CLI
6
+
7
+
Clause-②: no
8
+
9
+
**`ui-object-grid-page-size-positive-integer-refused` (protocol 18).** The entry's acceptance
10
+
criterion said that a stored page whose `object-grid` node carries `pageSize: 0` "is refused on
11
+
its next authoring-path save with a per-key issue at `pagination.pageSize`". Nothing on the
12
+
metadata save path judges a page component's `properties`. `PageComponentSchema.properties` is an
13
+
open record, and the one judge of the `ComponentPropsMap` row is the component-props gate, an
14
+
advisory rule that runs on the CLI only. Measured through `saveMetaItem`, the call behind
15
+
`PUT /api/v1/meta/page`: such a page saves (`success: true`, and the result carries no `advisories` member), is stored as an
16
+
active row and reads back with `pageSize: 0` intact. On the same page, `os validate`, `os build`
17
+
and `os lint` each report one advisory `component-props-invalid` finding at
18
+
`properties.pagination.pageSize` and no error. The criterion now says exactly that. It also names
19
+
the `pageSizeOptions` entry and the flat `pageSize` shorthand, which are reported the same way at
20
+
their own paths.
21
+
22
+
Text only: no entry id, conversion or matching logic changes, and `os migrate meta` rewrites
23
+
exactly what it rewrote before. The generated migration registry carries the corrected text. The
24
+
sibling entry `object-grid-default-filters-rule-array` already stated the advisory-only outcome
fix(automation): boot-time flow precedence takes which same-named flow is the packaged one from the package loader's set, not from the flow definitions' own provenance (#20864)
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
When several flow definitions share one name at startup, the automation plugin arms one of them and shadows the rest: a flow authored in the deployment wins over the packaged flow of that name (ADR-0005). Which contender counts as the packaged one is now the answer of the set of flows a managed package's loader registered. That is the same answer the ADR-0126 §7.3 subflow guards, the arming gate and the activation switch read since #20761. The package provenance a flow definition carries is kept for display only.
10
+
11
+
-`resolveFlowPrecedence(items, logger?, packagedFlowOwner?)` and `describeFlowContender(item, packagedFlowOwner?)` take the reader as a new optional last argument, typed `PackagedFlowSource` (the reader `AutomationEngine.setPackagedFlowSource` takes). `AutomationServicePlugin` passes the engine's own `packagedFlowOwner` for you.
12
+
- A definition that claims a package's provenance for a name no package loaded ranks as a flow of the deployment. The shadowing record (`getShadowedFlows()`, and the startup warnings) no longer names that package as its source.
13
+
- With no reader, no contender is packaged. That is the engine's own answer when no reader is attached.
14
+
- Two contenders that both rank as the deployment's keep the order they were listed in. The package id orders packaged contenders only, as before.
15
+
- A startup whose registry the package loader and the stored-flow hydration filled arms the same flows as before: those entries already agree with the loader's set.
16
+
17
+
**If you call `resolveFlowPrecedence` or `describeFlowContender` yourself:** pass the loader's-set reader as the last argument, for example `(name) => engine.packagedFlowOwner(name)`. Without it no contender ranks as packaged.
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+11Lines changed: 11 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -693,6 +693,8 @@ Sort field and direction pair
693
693
|**objectName**|`string`| optional | Object this grid binds to. Optional because the component-level `dataSource` binding can supply the object instead |
694
694
|**label**|`string \| Record<string, string>`| optional | Grid label — used as the table caption and export file title |
695
695
|**title**|`string \| Record<string, string>`| optional | Fallback for `label` (the renderer reads `label \|\| title`) |
696
+
|**description**|`string \| Record<string, string>`| optional | One line of help text drawn above the grid's rows — a string, or an inline locale map resolved against the display locale |
697
+
|**emptyState**|`{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string }`| optional | What the grid draws instead of an empty table: `{ title, message, icon }` — the list view's own empty-state shape |
696
698
|**columns**|`any[]`| optional | Columns: field names or column definition objects |
697
699
|**fields**|`any[]`| optional | Field list fallback used when `columns` is absent |
698
700
|**filter**|`{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]`| optional | Base query filter — the ViewFilterRule array form `[{ field, operator, value }, ...]`, the one filter orthography every `filter` door in this map shares; lowered to the wire `$filter`. THE key, singular — not the plural misspelling. The MongoDB-style record form is refused — see migration `element-data-source-and-object-block-filter-rule-array`|
@@ -718,6 +720,7 @@ Sort field and direction pair
718
720
|**navigation**|`any`| optional | Row-click navigation config (`{ mode: page \| drawer \| modal \| split \| popover \| new_window \| none }`) — all seven `NavigationModeSchema` values, since the shared `useNavigationOverlay` hook types its own mode union as that schema |
|**singleClickEdit**|`boolean`| optional | Enter cell edit on single click (default true when editable) |
723
+
|**keyboardNavigation**|`boolean`| optional |[EXPERIMENTAL — not enforced] Arrow-key cell navigation on the WAI-ARIA grid pattern. Defaults to on when `editable` is set; a read-only grid keeps its Tab behaviour unless this is `true`. No renderer reads it yet: it is declared ahead of the grid's keyboard-navigation build, so authoring it changes nothing today |
721
724
|**resizable**|`boolean`| optional | Allow column resize (read before `resizableColumns`) |
722
725
|**resizableColumns**|`boolean`| optional | Alternate spelling of `resizable` (the renderer reads `resizable ?? resizableColumns`) |
0 commit comments