Skip to content

Commit 400f1f3

Browse files
committed
fix(components,app-shell,i18n): narrow to record_picker and number per the seat rulings; defer element:repeater
Seat rulings on objectui#11880 (claim 6062231663): - The repeater is deferred to triage: the spec at the pin still declares its flat object REQUIRED and documents that the node dataSource is not read there. data-list.tsx, the repeater's designer rows, its docs row and example, and its fixtures are restored to the base; the designer, docs and fixture changes are narrowed to element:number and element:record_picker. - B1, option B: element:record_picker's object / filter / sort / limit and element:number's object / filter stay published until the spec's v18 retirement; each description now says the key is not read and names the binding member that is, and the member-pin prose that described the flat precedence is corrected. - B3, option A: element.number.noObject in the ten language packs, and its inline defaultValue, name only dataSource.object (value change, no new key). Part of objectui#11880. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 699f6e8 commit 400f1f3

32 files changed

Lines changed: 577 additions & 505 deletions
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
---
22
'@object-ui/components': patch
33
'@object-ui/app-shell': patch
4+
'@object-ui/i18n': patch
45
---
56

6-
Page elements bind data through the node-level `dataSource` only (objectui#11880, the objectui half of objectstack#11509, ruled A-narrow).
7+
`element:record_picker` and `element:number` bind data through the node-level `dataSource` only (objectui#11880, part of the objectui half of objectstack#11509, ruled A-narrow).
78

8-
- `element:repeater` reads its query from the `dataSource` binding: `object`, `filter`, `sort` and `limit`, composed with the saved view its `view` names. A repeater bound only through `dataSource` used to render "No records". An unresolvable `view` now shows the configuration-error panel, and a resolving one the loading panel. Its registration declares the binding through the injected `dataSource` input and no longer publishes `object`, `filter`, `sort` or `limit`.
9-
- `element:record_picker` and `element:number` no longer read the flat `properties.object` / `filter` / `sort` / `limit` beside the binding, and no longer publish them as inputs. `element:number`'s filter is the binding's alone: the AND with `properties.filter` is gone. An element whose node names no `dataSource.object` issues no query.
10-
- The Studio page designer writes `dataSource.object` for `element:repeater` and `element:number`, and `dataSource.limit` for the repeater's row cap, at node level. Their field pickers read the object from `dataSource.object`. A stored `properties.object` stays visible in the inspector's Advanced section.
9+
- Neither element reads the flat `properties.object` / `filter` / `sort` / `limit` beside the binding any more: the `composed?.x ?? props.x` fallbacks are gone, and `element:number`'s filter is the binding's alone (the AND with `properties.filter` is gone). An element whose node names no `dataSource.object` issues no query. The flat keys stay published until `@objectstack/spec` retires them in v18; each input's description now says it is not read and names the binding member that is.
10+
- The Studio page designer writes `element:number`'s object to `dataSource.object` at node level, and its measure picker reads the object from there. A stored `properties.object` stays visible in the inspector's Advanced section.
11+
- `element.number.noObject` now names only `dataSource.object`, in all ten language packs.
1112

12-
Breaking for an author who wrote the flat keys on these three elements: move them into `dataSource`. `@objectstack/spec` retires the flat keys in v18.
13+
Breaking for an author who wrote the flat keys on these two elements: move them into `dataSource`. `element:repeater` is unchanged.

‎apps/console/src/__tests__/filterContextTokensSweep-10666.test.tsx‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,14 @@ interface NodeRow {
186186
}
187187

188188
const top = (base: Record<string, unknown>) => (filter: Filter) => ({ ...base, filter });
189-
// The three `element:*` nodes read their query from the node-level `dataSource`
190-
// binding only (objectui#11880), so the filter and the object ride there.
191189
const element = (base: { type: string; id: string; properties: Record<string, unknown> }) => (filter: Filter) => ({
190+
...base,
191+
properties: { ...base.properties, filter },
192+
});
193+
// `element:number` and `element:record_picker` read their query from the
194+
// node-level `dataSource` binding only (objectui#11880), so the filter and the
195+
// object ride there.
196+
const bound = (base: { type: string; id: string; properties: Record<string, unknown> }) => (filter: Filter) => ({
192197
...base,
193198
dataSource: { object: OBJECT, filter },
194199
});
@@ -229,13 +234,13 @@ const ROWS: NodeRow[] = [
229234
columns: [{ name: 'name', label: 'Name', type: 'text' }],
230235
}) },
231236
{ node: 'element:repeater', shape: 'tuple', path: 'dataSource', filterParam: '$filter',
232-
withFilter: element({ type: 'element:repeater', id: 'rep', properties: { fields: ['name'] } }) },
237+
withFilter: element({ type: 'element:repeater', id: 'rep', properties: { object: OBJECT, fields: ['name'] } }) },
233238
{ node: 'element:number (aggregate)', shape: 'tuple', path: 'dataSource', filterParam: 'filter', aggregate: true,
234-
withFilter: element({ type: 'element:number', id: 'num', properties: { aggregate: 'count' } }) },
239+
withFilter: bound({ type: 'element:number', id: 'num', properties: { aggregate: 'count' } }) },
235240
{ node: 'element:number (find fallback)', shape: 'tuple', path: 'dataSource', filterParam: '$filter', aggregate: false,
236-
withFilter: element({ type: 'element:number', id: 'num', properties: { aggregate: 'count' } }) },
241+
withFilter: bound({ type: 'element:number', id: 'num', properties: { aggregate: 'count' } }) },
237242
{ node: 'element:record_picker', shape: 'rule', path: 'dataSource', filterParam: '$filter',
238-
withFilter: element({ type: 'element:record_picker', id: 'rp', properties: {} }) },
243+
withFilter: bound({ type: 'element:record_picker', id: 'rp', properties: {} }) },
239244
// Regression rows (objectui#10607, objectui#10666 part 1).
240245
{ node: 'object-gallery (regression)', shape: 'tuple', path: 'dataSource', filterParam: '$filter',
241246
withFilter: top({ type: 'object-gallery', objectName: OBJECT, gallery: { titleField: 'name' } }) },

‎apps/console/src/__tests__/registry-inputs-spec-parity.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2772,23 +2772,23 @@ const MEMBER_PINS: Record<string, MemberPin> = {
27722772
},
27732773
'element:number.dataSource': {
27742774
file: 'packages/components/src/renderers/basic/__tests__/elementNumber.dataSourceBinding-10909.test.tsx',
2775-
pins: 'The per-element binding\'s members as this METRIC reads them, through the REAL renderer and asserted at the call it fires, with the whole options bag compared so a member forwarded by accident is red. Read: `object` is what gets aggregated and OUTRANKS `properties.object` (`dataSource.object ?? properties.object`, one value for the fetch guard, `aggregate` / `find` and the `useDataInvalidation` key — a bus event for the outranked flat object does not re-read, one for the bound object does), and while a named `view` is unresolved there is NO object, so a flat `properties.object` beside an unresolvable view aggregates nothing; a named `view`\'s `filter` scopes the aggregate, and an unresolvable `view` REPORTS and aggregates nothing; `filter` is AND-combined with `properties.filter` — the `ElementDataSourceGate` rule, lowered and merged the same way — so a binding filter and the flat one both reach the aggregate, a view filter and the flat one both reach it, and a merge the converter refuses REPORTS on the error panel and aggregates nothing. Not read: `sort` and `limit`, from the binding or from the view, never reach `aggregate()` or the `find()` fallback, because an aggregate has no ordering and a capped count is a wrong number. A binding naming no object supplies nothing and the flat object stands, and the `properties` form with no binding is the control. ⚠️ The key is INJECTED by `Registry.register` (`ELEMENT_DATA_SOURCE_INPUT`), not written by the block: the declaration lists the binding\'s five members generically (`{ object, view, filter, sort, limit }`) and does not say which of them THIS block reads — that per-block half is what this pin carries (objectui#10909).',
2775+
pins: 'The per-element binding\'s members as this METRIC reads them, through the REAL renderer and asserted at the call it fires, with the whole options bag compared so a member forwarded by accident is red. Read: `object` is what gets aggregated, and since objectui#11880 the ONLY object (one value for the fetch guard, `aggregate` / `find` and the `useDataInvalidation` key — a bus event for a flat `properties.object` beside it does not re-read, one for the bound object does); while a named `view` is unresolved there is NO object, so nothing is aggregated; a named `view`\'s `filter` scopes the aggregate, and an unresolvable `view` REPORTS and aggregates nothing; `filter` is the binding\'s (AND-combined with its view\'s), lowered, and the flat `properties.filter` is not read — a binding filter the converter refuses REPORTS on the error panel and aggregates nothing. Not read: `sort` and `limit`, from the binding or from the view, never reach `aggregate()` or the `find()` fallback, because an aggregate has no ordering and a capped count is a wrong number. A binding naming no object supplies nothing, and the flat `properties` form alone shows the no-object notice and aggregates nothing (objectui#11880). ⚠️ The key is INJECTED by `Registry.register` (`ELEMENT_DATA_SOURCE_INPUT`), not written by the block: the declaration lists the binding\'s five members generically (`{ object, view, filter, sort, limit }`) and does not say which of them THIS block reads — that per-block half is what this pin carries (objectui#10909).',
27762776
},
27772777
'element:number.filter': {
27782778
file: 'packages/components/src/renderers/basic/__tests__/elementNumberFilterMembers-8071.test.tsx',
2779-
pins: 'The TWO wire spellings one authored predicate takes, chosen by an adapter capability the author cannot see, plus the re-query rule — asserted through the real renderer on a stubbed adapter. `aggregate()` receives it FLAT under its own name and beside the members that make the call an aggregate (`field`, `function`, `groupBy: \'_all\'`), asserted as the whole options bag rather than the one key; the `find()` fallback receives it WRAPPED as `$filter`; and an unfiltered metric sends `undefined` rather than an empty envelope some adapters read as "match nothing". Collapsing the two spellings into one drops the predicate silently and the metric paints a confidently wrong number over every row, with no diagnostic and no empty state. The third half is that the key is read BY VALUE, not by identity (`filterKey` is a `JSON.stringify` memo): a deep-equal filter rebuilt by a re-rendering parent must NOT re-probe, while a changed comparand MUST and carries the new predicate — each arm the other\'s control, so a dependency array "simplified" to the raw object (a per-render fetch storm) is red. New file (objectui#8071 slice 7).',
2779+
pins: 'The TWO wire spellings one authored predicate takes, chosen by an adapter capability the author cannot see, plus the re-query rule — asserted through the real renderer on a stubbed adapter. Since objectui#11880 the predicate is the binding\'s `dataSource.filter` (lowered to the ObjectQL AST); this flat key stays published, NOT READ, until the spec\'s v18 retirement. `aggregate()` receives it FLAT under its own name and beside the members that make the call an aggregate (`field`, `function`, `groupBy: \'_all\'`), asserted as the whole options bag rather than the one key; the `find()` fallback receives it WRAPPED as `$filter`; and an unfiltered metric sends `undefined` rather than an empty envelope some adapters read as "match nothing". Collapsing the two spellings into one drops the predicate silently and the metric paints a confidently wrong number over every row, with no diagnostic and no empty state. The third half is that the key is read BY VALUE, not by identity (`filterKey` is a `JSON.stringify` memo): a deep-equal filter rebuilt by a re-rendering parent must NOT re-probe, while a changed comparand MUST and carries the new predicate — each arm the other\'s control, so a dependency array "simplified" to the raw object (a per-render fetch storm) is red. New file (objectui#8071 slice 7).',
27802780
},
27812781
'element:record_picker.dataSource': {
27822782
file: 'packages/components/src/__tests__/record-picker-element-data-source.test.tsx',
2783-
pins: 'The per-element binding\'s own members, read through the REAL renderer and asserted at the query it fires: `view` resolves a saved view whose `filter`/`sort`/row cap become the picker\'s baseline, an authored binding key WINS over the same key from the view, the binding\'s own `filter` arrives AND-combined with the view\'s rather than replacing it, and an unresolvable `view` reports instead of falling back to every record of the object — the quieter failure mode objectstack#6953 fixed, one class below the throw objectstack#5576 fixed on `list-view`. A `view`-less binding leaves every one of the four flat keys (`object`/`filter`/`sort`/`limit`) behaving exactly as it did before a `dataSource` was authored at all, which is the control that keeps the override rows from reading as a coincidence. `columns` is deliberately absent from this pin: unlike `record:related_list.dataSource` (objectui#8071 slice 2), this renderer never reads `composed.columns` at all — a select dropdown has no column list to narrow. Pre-existing file, promoted to a pin here after being read end to end (objectui#8071).',
2783+
pins: 'The per-element binding\'s own members, read through the REAL renderer and asserted at the query it fires: `view` resolves a saved view whose `filter`/`sort`/row cap become the picker\'s baseline, an authored binding key WINS over the same key from the view, the binding\'s own `filter` arrives AND-combined with the view\'s rather than replacing it, and an unresolvable `view` reports instead of falling back to every record of the object — the quieter failure mode objectstack#6953 fixed, one class below the throw objectstack#5576 fixed on `list-view`. A `view`-less binding passes its own keys through as authored, which is the control that keeps the override rows from reading as a coincidence; and since objectui#11880 the four flat keys (`object`/`filter`/`sort`/`limit`) are read by nothing — a flat `properties.object` alone issues no query, and beside a binding the flat `filter`/`sort`/`limit` change nothing. `columns` is deliberately absent from this pin: unlike `record:related_list.dataSource` (objectui#8071 slice 2), this renderer never reads `composed.columns` at all — a select dropdown has no column list to narrow. Pre-existing file, promoted to a pin here after being read end to end (objectui#8071).',
27842784
},
27852785
'element:record_picker.emptyText': {
27862786
file: 'apps/console/src/__tests__/component-input-union-specimens.test.ts',
27872787
pins: 'The `object` arm is the inline translation map, asserted at the render site that actually resolves one, with the non-matching controls (`42`, `["No records"]`) still reported (objectui#3832).',
27882788
},
27892789
'element:record_picker.filter': {
27902790
file: 'packages/components/src/__tests__/record-picker-inputs-spec-parity.test.ts',
2791-
pins: 'The `array` arm is `ViewFilterRuleSchema[]` — the one filter orthography this map\'s array-declared `filter` doors share — with the MongoDB-style RECORD spelling (`{ status }`, `{ $and }`) refused by kind at `filter` and a bare tuple refused at `filter.0`, and the description pinned to the renderer\'s own precedence read (`composed?.filter ?? props.filter`). objectui#3830 declared the key; objectstack#14406 converged it off the record form and objectui#7663 moved this side onto the array arm.',
2791+
pins: 'The `array` arm is `ViewFilterRuleSchema[]` — the one filter orthography this map\'s array-declared `filter` doors share — with the MongoDB-style RECORD spelling (`{ status }`, `{ $and }`) refused by kind at `filter` and a bare tuple refused at `filter.0`, and the description pinned to say the key is NOT READ and to name `dataSource.filter`, the one filter the renderer reads since objectui#11880 (`composed?.filter`). objectui#3830 declared the key; objectstack#14406 converged it off the record form and objectui#7663 moved this side onto the array arm; it stays published until the spec\'s v18 retirement (objectstack#11509).',
27922792
},
27932793
'element:record_picker.label': {
27942794
file: 'packages/components/src/renderers/basic/__tests__/record-picker-label-placeholder-i18n.test.tsx',
@@ -2800,7 +2800,7 @@ const MEMBER_PINS: Record<string, MemberPin> = {
28002800
},
28012801
'element:record_picker.sort': {
28022802
file: 'packages/components/src/__tests__/record-picker-sort-members.test.tsx',
2803-
pins: 'An IDENTITY pin, unlike the sibling block\'s: `record-picker.tsx` assigns `query.$orderby = sort` with no `normalizeSortSpec` in between, so a member missing `field` is NOT dropped the way `record:related_list.sort` (objectui#8071 slice 2) drops one — pinned as the current, undocumented divergence between two blocks whose registrations both say only "array of objects". Order is preserved for a multi-member array, no `$orderby` is sent when the key is absent, and PRECEDENCE against `dataSource` is pinned in both directions: a binding that declares its own `sort` replaces the flat key outright, and the flat key still applies when the binding carries none — `composed?.sort ?? props.sort`, identical to `filter`\'s read (objectui#8071).',
2803+
pins: 'An IDENTITY pin, unlike the sibling block\'s: `record-picker.tsx` assigns `query.$orderby = sort` with no `normalizeSortSpec` in between, so a member missing `field` is NOT dropped the way `record:related_list.sort` (objectui#8071 slice 2) drops one — pinned as the current, undocumented divergence between two blocks whose registrations both say only "array of objects". Order is preserved for a multi-member array and no `$orderby` is sent when the key is absent. Since objectui#11880 the read is `composed?.sort` alone, so these rows drive the binding\'s `dataSource.sort`, and the flat `sort` — still published, its description saying NOT READ — is pinned unread both beside a binding that declares its own `sort` and beside one that carries none (objectui#8071, objectui#11880).',
28042804
},
28052805
'element:text.content': {
28062806
file: 'apps/console/src/__tests__/component-input-union-specimens.test.ts',

0 commit comments

Comments
 (0)