Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit 072b7e8

Browse files
feat(plugin-form): record:line_items publishes every key of its 17.6.0 spec row, each by measurement (objectui#11536) (#11557)
Fixes #11536 Clause-②: yes Why yes: the ten keys are declared on the block's registry `inputs`, which widens a published surface. The SDUI manifest, the JSX intrinsics and the page validator now accept them instead of reporting `unknown-prop`. The renderer does not change: it already read every one of them. ## What changed - `packages/plugin-form/src/index.tsx`: the `line_items` registration declares the ten keys of the 17.6.0 `RecordLineItemsProps` row it did not publish (`parentObject`, `parentId`, `recordId`, `title`, `readonly`, `minRows`, `maxRows`, `filter`, `sort`, `limit`). `filter` and `sort` are `type: 'array'` with `of: 'object'`; `title` is `'string'` only, because the row declares a plain string and the panel draws it as a React child. Each input carries a description of what the panel does with it. - New pin file `packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx` (30 tests): the measurement every declaration rests on, plus the member pins for `columns`, `dataSource`, `filter` and `sort`. - `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`: strikes every `OWED TO objectui#11536` entry (the ten unpublished keys and the two owed member pins), registers the four member pins in `MEMBER_PINS`, and lowers the caps those entries counted in (see "Shared cap line" below). - `apps/console/src/__tests__/record-block-record-reach.test.tsx`: a file outside the claim's surface, added this round because this change turned it red (see "Deviation" below). - `.changeset/11536-line-items-published-inputs.md`: `@object-ui/plugin-form` minor. ## The measurement, key by key Method. A read in `LineItemsPanel` is not proof on its own: `SchemaRenderer` hoists the `properties` bag onto the node, and the block wraps the panel in `ElementDataSourceGate`. So each key is authored the way the spec authors it, as `{ type: 'record:line_items', properties: { … } }`. It is rendered through the real `SchemaRenderer` and this package's own registration, and asserted at what the key does: the query the adapter receives, the batch Save sends, or the DOM. Every row has a control beside it that differs only by that key. Read sites are named by symbol, per AGENTS.md #11. | key | read site | measured effect (pin-file row) | declared | |---|---|---|---| | `parentObject` | `parentObject` const in `LineItemsPanel` (the key, else the page record's `objectName`), used by `save` for the rollup gate and `buildMasterDetailEditBatch`'s parent leg | names the parent leg of the Save batch: `{ object: 'purchase_order', action: 'update', id: 'p1', data: { total_amount: 5 } }`; outranks the page record's object; without it and with no page record, the batch has no parent leg | yes | | `parentId` | `parentId` const (the key, else `recordId`, else the page record's `recordId`) → `load` (`find(childObject, { $filter: { [relationshipField]: parentId } })`) and `save` | `$filter: { po: 'p1' }`; a new line is saved with `po: 'p1'`; CONTROL: with no parent id, no `find` and the "Save the record first" state | yes | | `recordId` | same `parentId` const, second in order | `$filter: { po: 'r9' }`; outranks the page record (`page-1`); loses to `parentId` | yes | | `title` | `CardTitle` child: the key, else the `form.lineItems.title` default | heading reads "Order lines"; CONTROL "Line Items" | yes | | `readonly` | Save button gate, `allow_add` / `allow_delete` in `gridField`, and the `readonly` prop on `LineItemsField` (`GridField` draws its read-only table) | no Save, no Add line / Duplicate row / Remove row, read-only table, no inputs; CONTROL: all present | yes | | `minRows` | `gridField.min_rows` → `GridField`'s `minRows` (`removeRow` guard and Remove button `disabled`) | with 2 lines and `minRows: 2` both Remove row buttons are disabled; no blank lines are added; CONTROL: enabled | yes | | `maxRows` | `gridField.max_rows` → `GridField`'s `maxRows` (`addRow` / `duplicateRow` guards, ghost row, Add and Duplicate `disabled`) | with 2 lines and `maxRows: 2`, Add line and Duplicate row are disabled and the entry row is gone (2 inputs, not 3); CONTROL: enabled, 3 inputs | yes | | `filter` | `useResolvedFilter(schema.filter, …)` → `toFilterNodeSafely` → `mergeFilterNodes(parentScope, …)` in `load` | `$filter: ['and', ['po', '=', 'p1'], [['billable', 'equals', true]]]`; CONTROL `{ po: 'p1' }` | yes | | `sort` | `convertSortToQueryParams(schema.sort)` → `$orderby` in `load` | `$orderby: { amount: 'desc' }`; CONTROL: no `$orderby` | yes | | `limit` | `resolveRowLimit(schema.limit, DEFAULT_LINE_ITEMS_LIMIT)` → `$top` in `load` | `$top: 25`; CONTROL `$top: 500` | yes | Keys left out: none. The row has fifteen keys. The other five (`childObject`, `relationshipField`, `columns`, `amountField`, `totalField`) were declared before this change and are not re-judged here, apart from the `childObject` note under Acceptance notes. `filter` / `sort` / `limit` are TOP-LEVEL reads, not only `dataSource` members (PM hypothesis 2, measured). With no binding, `useElementDataSourceSchema` returns the schema by reference ("BY REFERENCE when there is nothing to apply") and the panel reads the three keys itself, which is what the rows above assert with no `dataSource` on the node. With a binding, the gate composes them first: the panel's own `filter` is ANDed with the binding's and the view's, and the panel's own `sort` / `limit` outrank the view's while losing to the binding's own (pinned in the `dataSource` block of the pin file). ## Member pins | id | what the pin asserts | |---|---| | `record:line_items.columns` | one column per member in authored order, headed by `label` or else `name`; `type` picks the cell control and a typeless member is a text cell, with no hydration from the child object; `defaultHidden` hides a member, `required` outranks it and marks the header; `name` is the field Save writes | | `record:line_items.dataSource` | `object` lands on `childObject` and outranks an authored one; `view` contributes filter (behind the parent scope), sort and page size; an unresolvable view reports and loads nothing; panel `sort` / `limit` outrank the view's, binding `sort` / `limit` outrank the panel's; binding `filter` is ANDed with the panel's; a view's `columns` are not read | | `record:line_items.filter` | `ViewFilterRule` members reach `$filter` in order as one group ANDed behind the parent scope, operator word unchanged; a member naming the relationship field does not replace the scope; `[]` is no criterion | | `record:line_items.sort` | `{ field, order }` members reach `$orderby` in authored order; a member with no `field` is dropped while siblings survive; an `order` other than `desc` reads ascending; no usable member sends no `$orderby` | `filter` and `sort` entered the array-armed population by being declared, and were pinned in the same change, so `MEMBER_PIN_EXEMPTION_CEILING` goes to 0 rather than staying at 2. ## PM hypotheses, as measured 1. The ten keys: confirmed by the gate's own output. With the fix committed (`c9e2fe3`) and `index.tsx` reverted to the base `6158e4c93`, `record:line_items publishes every top-level key its spec props schema declares` received exactly `parentObject, parentId, recordId, title, readonly, minRows, maxRows, filter, sort, limit`. 2. Top-level versus `dataSource`-only: all three are read top-level (above). 3. `memberPins` names `columns` and `dataSource` on `main`: confirmed, and used. 4. End state: no `OWED TO objectui#11536` entry is left in any ledger. The owner stays in `OBJECTUI_11111_OWNERS` with a per-owner total of 0, the shape objectui#8652 and objectui#8649 left when they were struck. - The brief's note that the parity test "reads built packages" did not hold. The console's vite config aliases `@object-ui/plugin-form` to its `src`, so the parity test reads source. The closure build was still needed, for `type-check`. ## Reverse validation (one-off, no file kept) The fix was committed first (`c9e2fe3`). Then `packages/plugin-form/src/index.tsx` was replaced with the base blob under a `trap … EXIT INT TERM` restore. The mutation on disk: `grep -c "name: 'parentObject'"` 0, against 1 at HEAD. The parity test then went red, the expected direction: 2 failed / 253 passed. The two reds were the ten unpublished keys above and `every member pin names a key that is still array/object-armed on a covered block`, which received `record:line_items.filter` and `record:line_items.sort`. Restored with `git checkout HEAD -- …`, checked by blob hash equal to `HEAD:packages/plugin-form/src/index.tsx` (`ef2d497e`) and an empty `git diff HEAD`. With the fix: 255 / 255 green. ## Shared cap line (serial note for objectui#11068) Changed in `registry-inputs-spec-parity.test.ts`: - `OBJECTUI_11111_LEDGER_CAPS.unpublishedKeys` 12 → 2, removing objectui#11536's 10; objectui#11168's 1 and objectui#11068's 1 remain. - `memberPins` 2 → 0. - The per-owner totals: `'objectui#11536'` 12 → 0. - `MEMBER_PIN_EXEMPTION_CEILING` 2 → 0. Whichever of this PR and objectui#11068 lands second recomputes `unpublishedKeys` from the ledger. After both, it reads 1, which is objectui#11168's. ## Deviation: one file outside the claim's surface `apps/console/src/__tests__/record-block-record-reach.test.tsx` went red under this change. Its sampler fills every declared input, and the newly declared `parentId` / `recordId` (sampled `'x'`) outrank the bound record. That is the declared behaviour, but it detached the panel from the record, so the probe reported "the bound record does not reach its output". The fix restates the renderer default, the key absent, for the three override keys in `SAMPLE_BY_INPUT`, beside the six defaults that table already restates for the same reason. It also annotates the docblock sentence "NO public block declares a `recordId` input", which this change made false. 13 / 13 green after the fix. ## Verification (HEAD `390a65f`) | command | exit | verdict line | |---|---|---| | `pnpm exec vitest run` on the pin file, the old `LineItemsPanel.elementDataSource` test, the parity test and the 11 console tests that read registry `inputs` (public-contract, sdui-manifest-build-11403, sdui-intrinsics-compile-11075, component-input-union-specimens, record-block-record-reach, public-block-binding-reach, element-data-source-input-injection, preview-samples-registry-resolvable, html-tier-manifest, i18nLabelInputsManifest-10993, filterContextTokensSweep-10666) | 0 | `Test Files 14 passed (14)` · `Tests 499 passed (499)` | | `pnpm exec vitest run packages/plugin-form/` (at `c9e2fe3`; the second commit touches no plugin-form file) | 0 | `Test Files 162 passed (162)` · `Tests 1878 passed · 1 skipped (1879)` | | `pnpm --filter @object-ui/plugin-form run type-check` (closure built; the new test file is in `tsconfig.test.json`'s program per `--listFiles`) | 0 | `tsc --noEmit && tsc -p tsconfig.test.json` clean | | `pnpm --filter @object-ui/console run type-check` (console closure built) | 0 | clean | | `pnpm check:sdui-registration-pins` (after a console `vite build`) | 0 | `All 14 registration(s) a sideEffects array promises are present in the built console` | | `pnpm check:element-data-source-declaration` | 0 | `OK — 14 gate-consuming file(s) checked` | | `pnpm check:component-surface-parity` | 0 | report-only; no `record:line_items` row | | `pnpm check:control-bytes` | 0 | `OK (scanned 10153 tracked text file(s))` | | `pnpm check:new-line-citations` | 0 | `0 new citation(s)` | | `pnpm check:changeset-claims` | 0 | report-only; one pending body names the parity file (`8067-component-input-member-kind.md`), read, still true | | `pnpm check:pending-changeset-literals` | 0 | `No test source names a pending changeset` | | `node scripts/check-changeset-presence.mjs` | 0 | `declares 1 changeset(s)` | | `check:unreferenced-sources`, `check:spec-symbols`, `check:handler-key-reads`, `check-test-path-roots`, `check-vi-mock-override-shape`, `check-vi-mock-specifiers`, `check-vi-mock-inherit`, `check-lint-coverage`, `check-type-check-coverage`, `check-changeset-fixed`, `check-changeset-no-major`, `check-registry-bare-name-collisions` | 0 each | OK | Lint is narrowed to the four changed TS files (`eslint --no-inline-config --format json`), a measurement rather than a skip: - Population: the root `eslint.config.js` applies to every `**/*.{ts,tsx}`. - Count: the JSON output holds 4 files, 0 errors and 33 warnings (`no-explicit-any` / `react-refresh/only-export-components`, the kinds the neighbouring code already carries). - Invariance: the config enables no type-aware linting (no `parserOptions.project` / `projectService`), and no rule under `eslint-rules/` reads another file. So this diff cannot move a verdict on an untouched file. The repo-wide `pnpm lint` is CI's. Docs: no page lists this block's inputs. The plugin-form README and the docs table name the block and its fallback only, and the manifest is generated from the registration, so no doc changes. ## Acceptance notes (not filed here) 1. `childObject` is declared `required: true`, while the 17.6.0 row declares it optional. The row's own description says why: the component-level `dataSource` binding can supply it. Measured once through the real `manifestFromConfigs` + `validateTree` pair: a node that binds the object through `dataSource` and omits `childObject` gets a `missing-required-prop` error on `childObject`. `RecordLineItemsProps.safeParse` accepts the same props, and the panel renders such a node (the `dataSource` pin rows). This is not changed here, because it is not one of the ten ruled keys. It is in the report for the seat. 2. `packages/types/src/zod/public-blocks.zod.ts` still says `record:line_items` has no spec row "on purpose". 17.6.0 carries the row, so the sentence went stale at the bump. Carrier: objectui#10872, which holds that arming. 3. A `ViewFilterRule` operator reaches `$filter` as written, so a word outside the spec's operator enum is forwarded too. The spec's authoring gate refuses such a word. Observation only. --- _Generated by [Claude Code](https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 806f327 commit 072b7e8

5 files changed

Lines changed: 690 additions & 40 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@object-ui/plugin-form': minor
3+
---
4+
5+
`record:line_items` now publishes every key of its `@objectstack/spec` 17.6.0
6+
row (objectui#11536). Each key was decided by measuring it through
7+
`SchemaRenderer` and the block's registration, per the objectui#11111 ruling:
8+
declare what the panel honours, leave out what it does not. All ten keys the
9+
row added over the registration move the panel, so all ten are declared.
10+
11+
Newly published, so the SDUI manifest, the JSX intrinsics and the page
12+
validator accept them instead of reporting `unknown-prop`:
13+
14+
- `parentId` and `recordId`: the parent record whose lines are loaded and
15+
saved. `parentId` outranks `recordId`, and both outrank the record the page
16+
shows.
17+
- `parentObject`: the parent object the line total is written to on Save,
18+
together with `totalField`. It outranks the object of the record the page
19+
shows.
20+
- `title`: the panel heading, a plain string.
21+
- `readonly`: lines are shown without editing (no Save, no row actions).
22+
- `minRows` / `maxRows`: Remove row is disabled at the floor; Add line and
23+
Duplicate row are disabled at the cap.
24+
- `filter`, `sort` and `limit`: the additional criteria (AND-combined behind
25+
the parent relationship, never replacing it), the load order and the row
26+
cap (default 500), each read as a top-level key as well as through a
27+
`dataSource` binding.
28+
29+
Nothing changes at runtime: the panel already read every one of these keys.

‎apps/console/src/__tests__/record-block-record-reach.test.tsx‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,15 @@ const SAMPLE_BY_INPUT: Readonly<Record<string, unknown>> = {
286286
align: 'end',
287287
variant: 'default',
288288
size: 'sm',
289+
// `record:line_items` declares three keys that OUTRANK the bound record
290+
// (objectui#11536): `parentId` and `recordId` name the parent record and
291+
// `parentObject` the parent object, each in place of the record the page
292+
// shows. Any value here detaches the panel from that record, and the probe
293+
// could not ask its question. The renderer default is the key ABSENT, so
294+
// that is what is restated.
295+
parentId: undefined,
296+
recordId: undefined,
297+
parentObject: undefined,
289298
};
290299

291300
/** Fill one declared input. */
@@ -549,7 +558,10 @@ const NO_RECORD_REACH: Readonly<Record<string, string>> = {
549558
* the repo are on `view:detail` and `detail-view`, neither of which is in the
550559
* curated public set). That is the same result objectstack#4472's direction (d)
551560
* hit — a slice proposed from the declarations, unavailable once you look at
552-
* what is actually declared.
561+
* what is actually declared. (Read when written. objectui#11536 has since
562+
* declared `recordId` and `parentId` on `record:line_items`, but as OVERRIDES
563+
* of the bound record rather than a binding to it, so this probe leaves both
564+
* unset: see `SAMPLE_BY_INPUT`.)
553565
*
554566
* What IS available is stronger anyway: two blocks whose REQUIRED
555567
* `relationshipField` / `childObject` must land in the query, mechanically

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

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,9 @@ const specCarries175LoadedBlocks = MINOR_17_5_LOADED_BLOCKS.every((type) => type
648648
* map went from 55 entries to 56 when `record:line_items` gained its row
649649
* (objectstack `24c554da`, #21142 / #21244). `@object-ui/plugin-form`
650650
* registers it eagerly with `inputs`, so this file JUDGES it from that pin on.
651-
* Its differences are booked to objectui#11536 under objectui#11438 ruling A′
652-
* (see `OWED_TO` above), not declared in the bump.
651+
* Its differences were booked to objectui#11536 under objectui#11438 ruling A′
652+
* (see `OWED_TO` above), not declared in the bump; that card's landing declared
653+
* the ten keys and pinned the member shapes, and struck every entry.
653654
*
654655
* Same shape as `MINOR_17_5_BLOCKS`: this repo registered the block with
655656
* `inputs` before the spec described it, so what moved at the bump is the
@@ -750,7 +751,8 @@ function isRetiredUpstream(type: string): boolean {
750751
*
751752
* - objectui#11536 — every entry on `record:line_items`: the keys its spec
752753
* row declares that `inputs` does not publish, and the member pins its
753-
* array/object-armed inputs owe.
754+
* array/object-armed inputs owe. STRUCK by that card's landing, which
755+
* declared all ten keys and registered the pins.
754756
*
755757
* A fifth owner, under the same bump: objectui#11438 ruling A″ (record
756758
* 5968177777, the maintainer's order to land 17.6.0 first) books a row whose
@@ -890,9 +892,9 @@ const owedIdsOf = (ledger: Record<string, string>): string[] =>
890892
const OBJECTUI_11111_LEDGER_CAPS = {
891893
unjudgedBlocks: 0, // objectui#11168 loaded and judged all four: slice 3 object-map and object-tree, slice 4 object-gantt, slice 5 object-timeline
892894
offSpecInputs: 0, // objectui#11168 slice 1 retired action:group.name
893-
unpublishedKeys: 11, // objectui#11168: 1 (action:button undoable; the two `endpoint` entries left at the 17.6.0 bump, objectui#11438, when the spec stopped declaring the key); objectui#8652: 0 and objectui#8649: 0 (each struck by its landing); objectui#11536: 10 (record:line_items, booked by objectui#11438 ruling A′); objectui#11068: 0 (object-grid keyboardNavigation, booked by objectui#11438 ruling A″, struck by that card's build)
895+
unpublishedKeys: 1, // objectui#11168: 1 (action:button undoable; the two `endpoint` entries left at the 17.6.0 bump, objectui#11438, when the spec stopped declaring the key); objectui#8652: 0, objectui#8649: 0, objectui#11536: 0 and objectui#11068: 0 (each struck by its landing; objectui#11536 declared all ten record:line_items keys, objectui#11068's build published object-grid keyboardNavigation)
894896
refusedArms: 0, // objectui#11168: slice 2 narrowed element:definition-list.columns, slice 3 object-form.layout
895-
memberPins: 2, // objectui#11168 slice 2 pinned element:definition-list.items and element:repeater ×3; objectui#11536: 2 (record:line_items columns and dataSource, booked by objectui#11438 ruling A′)
897+
memberPins: 0, // objectui#11168 slice 2 pinned element:definition-list.items and element:repeater ×3; objectui#11536 pinned record:line_items columns and dataSource
896898
} as const;
897899

898900
/**
@@ -1535,26 +1537,15 @@ const UNPUBLISHED_EXEMPTIONS: Record<string, string> = {
15351537
// types and describe text, so each entry went stale and `carries no stale
15361538
// unpublished-key exemption` would refuse it.
15371539

1538-
/*
1539-
* ⚠️ THE 17.6.0 BOOKINGS — objectui#11438 ruling A′ (record 5965062249),
1540-
* which applies objectui#11111 decision 3 = B to the 17.6.0 bump.
1541-
*
1542-
* The spec keys `record:line_items` declares from 17.6.0 on that its
1543-
* `inputs` do not publish. The same bar and the same ruling as the 17.5.0
1544-
* bookings above: the bump declares nothing, and objectui#11536 measures
1545-
* each key against the line-items renderer, declares what is honoured and
1546-
* leaves the rest out. Capped with the 17.5.0 entries at
1547-
* `OBJECTUI_11111_LEDGER_CAPS.unpublishedKeys`.
1548-
*/
1549-
...owedEntries(
1550-
'record:line_items',
1551-
[
1552-
'parentObject', 'parentId', 'recordId', 'title', 'readonly', 'minRows', 'maxRows', 'filter',
1553-
'sort', 'limit',
1554-
],
1555-
'objectui#11536',
1556-
'A SPEC KEY NOT PUBLISHED: `record:line_items` entered `covered` with 17.6.0 and its `inputs` omit this key its spec row declares.',
1557-
),
1540+
// ⚠️ THE 17.6.0 BOOKINGS — objectui#11438 ruling A′ (record 5965062249).
1541+
// objectui#11536's ten entries — `parentObject`, `parentId`, `recordId`,
1542+
// `title`, `readonly`, `minRows`, `maxRows`, `filter`, `sort` and `limit` on
1543+
// `record:line_items` — are STRUCK: that card measured each key through the
1544+
// real `SchemaRenderer` and this block's registration, found every one moving
1545+
// the panel, and DECLARED all ten on the block's `inputs`
1546+
// (`packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx`),
1547+
// so each entry went stale and `carries no stale unpublished-key exemption`
1548+
// would refuse it.
15581549

15591550
// objectui#11068's one entry — row 1 of the 17.6.0 bump,
15601551
// `object-grid.keyboardNavigation`, booked by objectui#11438 ruling A″
@@ -3327,6 +3318,22 @@ const MEMBER_PINS: Record<string, MemberPin> = {
33273318
file: 'packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx',
33283319
pins: 'Members are ADR-0066 CAPABILITY names and the block needs ALL of them, driven through the real renderer under a REAL stock `MePermissionsProvider` (not a mocked `usePermissions`, which cannot discriminate the two reading paths): an unheld capability and an unrecognised one each put the insufficient-permissions notice in place of the block, a held one renders it, an EMPTY array and an ABSENT key are no gate at all, and the discriminating row is a PARTIAL grant on a two-entry array — gated only when the members are read as `.every` over the WHOLE array. Two discriminators pin that a member is a capability and not an object action (`allowRead` on the object does not open the gate; the enum member `manage` is gated rather than resolved to the read bit), and the role-based `PermissionProvider` pins the unreported-capabilities fail-open. One file runs every row against all three record blocks (objectui#10155); promoted to a member pin by objectui#8649, which published the key.',
33293320
},
3321+
'record:line_items.columns': {
3322+
file: 'packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx',
3323+
pins: 'Members are inline grid column objects, driven through the real `SchemaRenderer` in the `{ type, properties }` form and asserted on the drawn grid and on the batch Save sends. Each member is one column in AUTHORED order, headed by its `label` or, without one, by its `name`. A member draws exactly what it declares: the panel hands `columns` to the grid through the field-security pass only, with no hydration from the child object, so the same value is a `number` cell under `type: \'number\'` and a text cell under a typeless member. `defaultHidden` keeps a member out of the drawn columns while `required` outranks it and marks the header. `name` is the field the cell edits, and Save writes the edit under it. The spec row is `InlineGridColumnSchema`; the registration declares a bare array, so the read site is the whole member contract (objectui#11536).',
3324+
},
3325+
'record:line_items.dataSource': {
3326+
file: 'packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx',
3327+
pins: 'The per-element binding\'s members as this block reads them through `RECORD_LINE_ITEMS_DATA_SOURCE`, asserted at the query the adapter receives. `object` lands on `childObject` and OUTRANKS an authored one. A named `view` contributes its filter (ANDed BEHIND the parent scope), its sort and its page size; an unresolvable `view` REPORTS and loads nothing. The panel\'s own `sort` / `limit` outrank the view\'s, the binding\'s own outrank the panel\'s, and the binding\'s `filter` is ANDed with the panel\'s with the parent scope first. A view\'s `columns` are NOT read: the grid keeps the authored columns. The key is INJECTED by `Registry.register` (`ELEMENT_DATA_SOURCE_INPUT`) and is absent from `ComponentPropsMap[\'record:line_items\']`, so the per-block half is what this pin carries; `LineItemsPanel.elementDataSource.test.tsx` in the same package holds the older top-level-form rows (objectui#11536).',
3328+
},
3329+
'record:line_items.filter': {
3330+
file: 'packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx',
3331+
pins: 'Members are spec `ViewFilterRule` objects `{ field, operator, value }`, authored in the node\'s `properties` bag and asserted at the query: they reach `$filter` in order as `[field, operator, value]` triples in ONE group ANDed BEHIND the parent relationship condition, with the spec operator word unchanged (the adapter lowers it). A member naming the relationship field itself does not replace the parent scope, and an empty list is no criterion (the query stays the parent scope object). The registration declares `of: \'object\'`, the contract\'s single member kind (objectui#11536).',
3332+
},
3333+
'record:line_items.sort': {
3334+
file: 'packages/plugin-form/src/__tests__/lineItemsDeclaredInputs-11536.test.tsx',
3335+
pins: 'Members are `{ field, order }` objects, authored in the node\'s `properties` bag and asserted at the query: they reach `$orderby` in AUTHORED order; a member with no `field` is dropped while its siblings survive, an `order` other than `desc` reads ascending, and a list with no usable member sends no `$orderby` at all. The registration declares `of: \'object\'`, the contract\'s single member kind (objectui#11536).',
3336+
},
33303337
'record:path.stages': {
33313338
file: 'packages/plugin-detail/src/renderers/__tests__/recordPathStagesMembers-8071.test.tsx',
33323339
pins: 'The stage member set `{ value, label, terminal? }`, read through the real renderer. `value` is stage IDENTITY — compared against the record\'s `statusField` to choose the current stage, controlled by moving the RECORD rather than the array, by an unmatched value leaving NO stage current, and by a record carrying some stage\'s LABEL still matching nothing. `label` is the rendered text and the `value` behind it never appears. `terminal` OUTRANKS the `WON_TOKENS`/`LOST_TOKENS` heuristic, pinned only on fixtures where the member CONTRADICTS the heuristic and each paired with the identical stage minus `terminal` to show which way the heuristic was pointing, plus an unclassified control. Deliberately the DESKTOP row only: `record-path.crossRowClassification.test.tsx` owns the cross-row agreement invariant, which is a different claim (objectui#8071 slice 6). The `label` member is an `I18nLabel`: its locale-map arm, resolved before the stages are classified or rendered, is pinned through the real `SchemaRenderer` under `zh` and `en` by `record-path.stageLabelI18nLabel-10993.test.tsx` (objectui#10993).',
@@ -3526,18 +3533,12 @@ const MEMBER_PIN_EXEMPTIONS: Record<string, string> = {
35263533
// `fields`, `filter` and `sort` by slice 2, each registered in
35273534
// `MEMBER_PINS`. Header kept as a landmark for a future grep.
35283535

3529-
// ⚠️ 17.6.0 BOOKINGS — objectui#11438 ruling A′ (record 5965062249), which
3530-
// applies objectui#11111 decision 3 = B to the 17.6.0 bump. The
3531-
// array/object-armed inputs of `record:line_items`, the block 17.6.0 newly
3532-
// judges, owed a member pin by objectui#11536. Counted into
3533-
// `MEMBER_PIN_EXEMPTION_CEILING` (0 -> 2) and capped at these two; that
3534-
// card's pins strike them.
3535-
...owedEntries(
3536-
'record:line_items',
3537-
['columns', 'dataSource'],
3538-
'objectui#11536',
3539-
'A MEMBER PIN OWED: this `record:line_items` input is array/object-armed on a block 17.6.0 newly judges, and no pin states its members.',
3540-
),
3536+
// ⚠️ 17.6.0 BOOKINGS — objectui#11438 ruling A′ (record 5965062249). The
3537+
// two array/object-armed inputs of `record:line_items` owed a member pin by
3538+
// objectui#11536, `columns` and `dataSource`, stood here. That card pinned
3539+
// both, and the two array keys it declared (`filter`, `sort`) with them,
3540+
// each registered in `MEMBER_PINS`, so no booking is left. Header kept as a
3541+
// landmark for a future grep.
35413542
};
35423543

35433544
/**
@@ -4363,8 +4364,16 @@ const NEWLY_JUDGED_UNPINNED_MEMBERS: string[] = [];
43634364
* = 2, the exact count, and `the objectui#11111 ledger is capped at exactly the
43644365
* entries it lists` caps the two and routes each to objectui#11536.
43654366
* objectui#11536's pins take this back to 0.
4367+
*
4368+
* ## 2 -> 0 (objectui#11536) — the 17.6.0 ruling paid off
4369+
*
4370+
* The card registered member pins for `record:line_items.columns` and
4371+
* `record:line_items.dataSource`, measured through the real renderer, and
4372+
* deleted their two booked entries. The two array keys it declared beside them
4373+
* (`filter`, `sort`) entered the population already pinned, so nothing stands
4374+
* in the list and the ceiling follows it back to 0.
43664375
*/
4367-
const MEMBER_PIN_EXEMPTION_CEILING = 2;
4376+
const MEMBER_PIN_EXEMPTION_CEILING = 0;
43684377

43694378
/**
43704379
* Every test file a member pin can live in, as LAZY `?raw` loaders.
@@ -5992,7 +6001,7 @@ describe('registry `inputs` vs `@objectstack/spec` ComponentPropsMap (repo-wide)
59926001
'objectui#11168': 1,
59936002
'objectui#8652': 0,
59946003
'objectui#8649': 0,
5995-
'objectui#11536': 12,
6004+
'objectui#11536': 0,
59966005
'objectui#11068': 0,
59976006
});
59986007
});

0 commit comments

Comments
 (0)