From f5f5bc0bc8063da8c4c3a6c74cfdd5531b1b11f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:46:18 +0000 Subject: [PATCH 01/17] chore: bump objectui to e420df310f5b fix(plugin-detail): a related-list toolbar action authored `visible: false` is hidden (objectui#11244) (#11275) objectui@e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .changeset/console-e420df310f5b.md | 105 +++++++++++++++++++++++++++++ .objectui-sha | 2 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 .changeset/console-e420df310f5b.md diff --git a/.changeset/console-e420df310f5b.md b/.changeset/console-e420df310f5b.md new file mode 100644 index 00000000000..0fc60e7f574 --- /dev/null +++ b/.changeset/console-e420df310f5b.md @@ -0,0 +1,105 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `e420df310f5b`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 87 releasing of 93 changesets added across 90 non-merge commits; omitted: 6 release-nothing changesets, 1 commit carrying no changeset (they ship no package code). + +- **minor** — `flex`, `object-grid` and `object-chart` accept the node-level `responsiveStyles` that `@objectstack/spec`'s `PageComponentSchema` declares on every page component, and judge it a… (objectui `f3135a4d1`) +- **minor** — Setup › Packaged automation shows the platform's reason for a packaged flow the engine has not armed, verbatim and in muted text under the flow name (objectui#9217). (objectui `1cb3732ce`) +- **minor** — **BREAKING (scored `minor` per this repo's version-alignment convention)** — `ObjectKanbanSchema.onQuickAdd` is retired on `object-kanban` (objectui#11234). This completes ruling… (objectui `52aad5cef`) +- **minor** — fix: a row-menu action gated through `disabled` stays disabled until the permissions payload has loaded, in a grid and in a related list's table, and an `` answers… (objectui `18d1a0abd`) +- **minor** — `safeValidateSchema` — and so `objectui validate` — and `StrictAnyComponentSchema` judge a component nested in a page container's props bag (objectui#11223). (objectui `7d074baae`) +- **minor** — `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不是前端的项目吗?后端… (objectui `f61dab169`) +- **minor** — `record:related_list` now renders the actions its `actions` key names (objectui#11163; ENFORCE ruling on objectstack#20665). (objectui `f4ed2387e`) +- **minor** — feat(app-shell): the sidebar and `nav:menu` hide a `doc` entry the member may not read (objectui#10188) (objectui `d6a1a80d5`) +- **minor** — feat(console): the docs portal refuses a doc or book the member may not read, and opens a doc in the book that claims it (objectui#10188) (objectui `d6a1a80d5`) +- **minor** — feat(layout): a host can hide a `doc` navigation entry the member may not read (objectui#10188) (objectui `d6a1a80d5`) +- **minor** — Every ADR-0080 public-block arm accepts the node-level `responsiveStyles` that `@objectstack/spec`'s `PageComponentSchema` declares, and judges it as the spec does (objectui#10872… (objectui `54a78308a`) +- **minor** — fix(types,components,plugin-form,console,core): a faulted `visibleWhen` refuses the submit, naming the field and the rule; a blank field rule is refused; blank gates are diagnosed… (objectui `af9e9572c`) +- **minor** — `safeValidateSchema` — and so `objectui validate` — accepts one more registered node type: `object-timeline` (objectui#10859, batch 3). (objectui `ae0b9d390`) +- **minor** — feat(app-shell): the flow node inspector marks the config keys the installed spec refuses the node without (objectui#10948). (objectui `68c9ca721`) +- **minor** — **BREAKING** — The formula and summary field widgets read `@objectstack/spec`'s own spellings, `returnType` and `summaryOperations`, and the snake_case spellings they used to read are retired at… (objectui `615346d61`) +- **minor** — feat(app-shell): Studio has a Markdown editor for `doc` items, with a live preview and book placement (objectui#10188) (objectui `02fe8ca8a`) +- **minor** — Declare the `filter` and `sort` inputs on the `object-map`, `object-gantt`, `object-timeline` and `view:timeline` registrations (objectui#8220) — the html tier stops reporting `un… (objectui `233a1b318`) +- **minor** — fix: an action gated on `current_user.can(object, verb)` stays hidden until the permissions payload has loaded on every action `visible` surface, a `page:header` action gated thro… (objectui `8bab1571d`) +- **minor** — fix(fields): a currency grid column's width is its currency's minor unit, never an authored `scale`; a hydrated currency column's `scale` is reported (objectui#10783) (objectui `b32e7debc`) +- **minor** — **BREAKING** — `quickAdd` is retired on `object-kanban` (objectui#8285, ruling B of the director seat's decision batch #91: the board does not grow an inline record-creation write… (objectui `6f864cf62`) +- **minor** — feat(types): four zod mirrors declare members their TypeScript twins already declared, and `pagination` retires its `page` spelling (objectui#6152, round 3) (objectui `0c95d3d8d`) +- **minor** — The four `page:` containers take their child list in `properties.children`, the member their `@objectstack/spec` row declares, and refuse a node-level `children` by name: `page:ca… (objectui `dded788ad`) +- **minor** — fix(app-shell,plugin-detail): the record feed says "no permission" when its read is refused, instead of showing an empty list (objectui `1263e405d`) +- **minor** — `ObjectGrid` takes `onNavigate` as a component prop, and the list channel's navigation callback takes one closed mode token, `'view' | 'new_window'`. (objectui `c3df43a42`) +- **minor** — feat: a `doc` navigation entry (ADR-0046) validates and draws as a link into the docs portal (objectui `e6bc087a3`) +- **minor** — **BREAKING** — **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… (objectui `3c13675e5`) +- **minor** — feat: an action's `visible` / `disabled` predicate can ask `current_user.can(object, verb)` — the caller's object permissions, from the payload the built-in Edit / Delete buttons… (objectui `9cebfca5a`) +- **minor** — A filter on a record page can now be scoped to the record the page shows (objectui#7297). Write `{record_id}` as a filter value, for example `{ "assignee": "{record_id}" }` on an… (objectui `cfc9b6db9`) +- **minor** — A page size with nothing declared is now the one `@objectstack/spec` declares for `pagination.pageSize`, on every surface that has a pager; a fetch that has no pager keeps its own… (objectui `de5d400bf`) +- **minor** — The four `action:*` blocks now publish the `@objectstack/spec` keys their renderers honour, and stop publishing what the spec refuses (objectui#11168, slice 1). Each key was decid… (objectui `3cc4fe567`) +- **minor** — **Breaking behaviour change — `object-tree` now honours only the `data` spelling its published row declares.** (objectui `846cec0ef`) +- **minor** — feat(types): the `object-form` zod mirror declares the members its TypeScript twin already declared (objectui#6152, round 1) (objectui `3a3db763b`) +- **minor** — `record:details`, `record:highlights` and `record:related_list` declare the field-security triple — `enforceFieldSecurity`, `redactFields` and `requiredPermissions` — on their pub… (objectui `647908686`) +- **minor** — The six public blocks that objectui#10872 batch 4 armed now refuse an authored `children`, and name the right remedy for a flat `body`: `action:button`, `action:icon`, `action:gro… (objectui `3f9d9263e`) +- **minor** — feat(types,layout,app-shell): a navigation entry with no `label` shows its target's current label, resolved at render time (objectui#9868) (objectui `a8198de22`) +- **minor** — `safeValidateSchema`, and so `objectui validate`, accepts the six ADR-0080 public blocks held back until `@objectstack/spec` carried a `ComponentPropsMap` row for each: `action:bu… (objectui `e978ed5ea`) +- **minor** — The strict authoring face accepts keys a registered renderer reads, which it used to refuse as undeclared (objectui#11070). Each key below is now declared on the TypeScript face a… (objectui `b0a05dda1`) +- **minor** — feat(cli): `objectui check` refuses a `${…}` on a text key its node never evaluates (objectui `33da643e9`) +- **minor** — 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/co… (objectui `81f849852`) +- **minor** — fix(plugin-gantt,core,plugin-timeline): both gantt surfaces read a date-only end inclusively through one core rule, and a drag writes the same day back (objectui#11141) (objectui `858eafb4f`) +- **patch** — fix(plugin-detail): a related list's `list_toolbar` action authored `visible: false` is hidden (objectui `e420df310`) +- **patch** — fix(console): the docs portal's book sidebar keeps a doc placed by its own `group` key from outside the book's package (objectui#11245) (objectui `f16c01e90`) +- **patch** — fix(app-shell): switching Studio to another flow, page or package no longer saves the previous item's unsaved edit into the one just opened (objectui `fc650380d`) +- **patch** — fix(app-shell): the Studio surface, its nav-item inspector and a new canvas entry inherit a label-less entry's label, the way the console does (objectui#11196) (objectui `02a22957c`) +- **patch** — fix(fields,plugin-detail,plugin-grid): every percent face reads its width through the spec's `resolveFieldScale`, so an undeclared percent renders the same everywhere (objectui `741864f7b`) +- **patch** — fix(app-shell): a flow screen select with no placeholder shows the locale's own "Select…" word (objectui#11220) (objectui `f523bd684`) +- **patch** — fix(plugin-list,plugin-grid): a grouped list view under a toolbar search groups on the server, and each group counts all its matching rows (objectui `d0ae5d025`) +- **patch** — fix(app-shell,plugin-designer): standard navigation entries are written with no `label`, never with a copy of their target's text (objectui#11201) (objectui `cb2f6fb5b`) +- **patch** — fix(app-shell,plugin-designer): the designer's nav surfaces name a nav entry with no `label` the way the console draws it (objectui#11196) (objectui `8741cb71a`) +- **patch** — fix(app-shell): Studio's draft autosave saves an edit made while a save is in flight, in every editor that uses it (objectui `395f4fa51`) +- **patch** — fix(components): a `kind:'react'` page no longer writes the host adapter under each block's `dataSource` (objectui `c021b3529`) +- **patch** — fix(components): an `action:menu` trigger stays disabled while its action runs, and a disabled `action:group` disables its buttons (objectui#11182) (objectui `2e3da72ad`) +- **patch** — fix(app-shell): the Studio flow screen preview draws a screen field's `options`, `placeholder` and `defaultValue` as the runtime dialog does (objectui `ed498ac91`) +- **patch** — fix(app-shell): the page designer stops offering the retired `page:header` breadcrumb toggle (objectui#11173) (objectui `52bf34824`) +- **patch** — fix(app-shell): the permission editor's row-level-security policy list draws each policy's label and description (objectui `f8334f877`) +- **patch** — fix(app-shell): Studio's nav autosave sends every nav edit it has shown — "Done" sends the edit, and a completing save clears only what it sent (objectui `0389650f3`) +- **patch** — fix(app-shell): the save warning has a sentence of its own for a formula field the server ignored (objectui `9419df198`) +- **patch** — fix(data-objectstack): the rule-entry filter form refuses an empty or non-string `icontains` comparand (objectui `0b8c63831`) +- **patch** — fix(app-shell): Studio prints an app's own locale-map label in the designer locale instead of `[object Object]` (objectui `39e625de2`) +- **patch** — feat(app-shell): the screen-flow dialog renders a screen field's declared bound, help text and lookup target (objectui `81778b955`) +- **patch** — fix(app-shell): Studio's Interfaces pillar closes nav editing when the package answers read-only, so no edit is taken on screen that its autosave will refuse (objectui `37d166280`) +- **patch** — fix(plugin-grid): a search reaches the grouped grid's header query and every group's row query, so the groups are the searched ones (objectui `7e4fa1bb2`) +- **patch** — Studio's Interfaces pillar no longer loses its nav rail when a nav item's label is a locale map (objectui#11158). (objectui `3ab51503b`) +- **patch** — fix(app-shell): the metadata form routes a condition builder to a member the served derivation marks as an erased string arm (objectui `32b131017`) +- **patch** — The ingestion choke point's diagnostic for a stored `id_field` now carries the reason `@objectstack/spec` publishes for that key (objectui#7650). (objectui `1e215c40b`) +- **patch** — `page:header` no longer draws an empty breadcrumb slot, and an authored `breadcrumb` is ignored (objectui#11166). (objectui `0ffc423b1`) +- **patch** — fix(app-shell): the report and dashboard-widget pickers show a dataset member's label and the dataset's description (objectui `cecd6a031`) +- **patch** — fix(app-shell): a shared `?sel=nav:ID` link survives the designer's mount and opens that nav item, without entering editing on a read-only package (objectui#11153) (objectui `957f69520`) +- **patch** — fix(fields,plugin-form,i18n): the line-items grid's required-cell text and the master-detail form's config hints read the locale packs (objectui `c2a8d23c6`) +- **patch** — fix(plugin-form,fields,i18n): the line-items panel, the grid field and the master-detail heading finish speaking the user's language (objectui `a8c550938`) +- **patch** — fix(app-shell): Studio's nav-item inspector shows a locale-map label and edits only the designer locale's entry (objectui `9a45088c4`) +- **patch** — An unlabelled undoable action's Undo and Redo toast names the object and carries no English verb (objectui#11080). (objectui `a782fa732`) +- **patch** — fix(fields): a multi-value select shows its placeholder while nothing is selected (objectui `3a0e7ab05`) +- **patch** — fix(app-shell): Studio's Interfaces pillar hands its page inspectors, canvas and source editor the package's real read-only flag (objectui `d71d972ae`) +- **patch** — fix(plugin-form): a master-detail child with authored inline columns derives its sort field and amount field, so line order persists and the total shows (objectui#11144) (objectui `263dcd77f`) +- **patch** — The console's global Undo and Redo toasts read the session's language (objectui#11080). (objectui `1d6a23d60`) +- **patch** — The `object-master-detail-form` registration's `fields` description no longer calls that key "the submitted set" (objectui#11114). (objectui `3fa193856`) +- **patch** — A `record:path` block that sets `statusField` and leaves out `stages` now shows the status field's picklist as its stages, instead of the "record:path — no stages configured" plac… (objectui `3f61eef1b`) +- **patch** — fix(app-shell): Studio's app designer canvas shows a locale-map nav label and renames only the current locale's entry (objectui#11128) (objectui `9babfa433`) +- **patch** — fix(app-shell): the flow designer stops warning on an edge guard that reads its source node's own outputs (objectui `f75e1f7e0`) +- **patch** — fix(plugin-form,fields,i18n): the record page's line-items panel and the line-items grid speak the user's language (objectui `385ebc5c6`) +- **patch** — fix(plugin-form): the master-detail form's Subtotal / Tax / Total show the amount's currency, not a hard-coded yen sign (objectui#11132) (objectui `1923d35d2`) +- **patch** — fix(core,app-shell): Undo of a lookup update restores the stored id, not the expanded record (objectui#11122) (objectui `bf43afafa`) +- **patch** — fix(plugin-timeline,types): a gantt-variant timeline draws a date-only end through the end of that day (objectui#11112) (objectui `c27b575ed`) +- **patch** — A hand-authored form field `{ type: 'select', multiple: true }` now renders the multi-value select and submits an array (objectui#11116). The form renderer's built-in `select` bra… (objectui `84b275c01`) +- **patch** — fix(app-shell): Studio's Automations pillar honours a read-only package on its Enabled switch, flow inspector and canvas (objectui `9fd6c2c6f`) +- **patch** — fix(app-shell,i18n): the designer's flow, federated-panel and field-stub chrome speak the user's language (objectui#10862, slice 4) (objectui `78abf3021`) + +⚠️ 5 of these carry a breaking change: 5 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + +**In this console build, declared nowhere** — objectui merged 1 commit in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared it, so it appears in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + +- _(no changeset)_ docs: re-fence the plugins and core remainder of objectui#5867 as ts, 17 blocks across 8 pages (batch 6) (#11176) (objectui `340dc718f`) + + + +objectui range: `db11afd4967c...e420df310f5b` diff --git a/.objectui-sha b/.objectui-sha index 544c6f430dd..ec99114727a 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -db11afd4967cd9d39381c5e21dc2deec9d706204 +e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c From e21e717988444b253a1e1452527c823db24d0e48 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:53:14 +0000 Subject: [PATCH 02/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 1: migration entries, dataset, batch conformance) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .../data/api-methods-batch-conformance.test.ts | 17 ++++++++++++++--- ...heck-and-hot-reload-durations-unit-in-key.ts | 6 +++--- ...kernel-runtime-config-timeout-unit-in-key.ts | 6 +++--- .../18.logging-durations-unit-in-key.ts | 6 +++--- ...ystem-metrics-jsdoc-durations-unit-in-key.ts | 9 +++++---- ...acing-otel-exporter-durations-unit-in-key.ts | 9 +++++---- .../18.tenant-schema-cache-ttl-unit-in-key.ts | 6 +++--- packages/spec/src/ui/dataset.zod.ts | 15 ++++++++++++--- 8 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/spec/src/data/api-methods-batch-conformance.test.ts b/packages/spec/src/data/api-methods-batch-conformance.test.ts index 990e433fe10..bd2553bc7fe 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,8 +61,19 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // `revoked` on ONE key. The multi-select surface this rule protects does not // exist for API keys, and the shape a future one would take does not need // `bulk` either — both read off the console build this release pins - // (`.objectui-sha` = `db11afd49`, `packages/plugin-grid`; re-measured at - // that pin, 2026-09-29. On the hop off `dd3f7e1be`, `ObjectGrid.tsx` changed + // (`.objectui-sha` = `e420df310`, `packages/plugin-grid`; re-measured at + // that pin, 2026-09-30. On the hop off `db11afd49`, `ObjectGrid.tsx` changed + // again (164 insertions, 48 deletions: objectui#9853's display page size, + // objectui#11021's searched grouped grid, objectui#9547's `onNavigate` prop, + // objectui#7297's `{record_id}` filter values and objectui#11070's + // `reference` spelling among them), none of it inside the selection block, + // which only MOVED by +114: `ObjectGrid.tsx:4751-4778` here (`4637-4664` at + // `db11afd49`), still hashing to + // `c88443302d40c2db739ddb235470bafa29056e2e` (hash-object of the block), + // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is + // byte-identical to `db11afd49` and its `:298-303` still hashes to + // `01083348330f10a201cdf1078b4c21c236402b6a`. At `db11afd49`, 2026-09-29: + // on the hop off `dd3f7e1be`, `ObjectGrid.tsx` changed // again (105 insertions, 16 deletions: objectui#11068's grid keys, // objectui#11105's server-grouped column-name fix, objectui#10993's title // locale map among them), none of it inside the selection block, which only @@ -72,7 +83,7 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is // byte-identical to `dd3f7e1be` and its `:298-303` still hashes to // `01083348330f10a201cdf1078b4c21c236402b6a`. Previously measured at - // `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, + // `db11afd49`, `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, // `87af769e9`, `53ded82bf`, `a472b0716`, `00d3f09c5`, `67dadd602`, before // that at `d8ec8d6d4`, `9602dc820`, `190fbd01d`, `9a3daf8d3`, originally at // `6314e87f2`. On the hop off `f8a9d0fb0`, `ObjectGrid.tsx` changed hard diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts index b5032b29c7c..80ca634c77c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts @@ -52,13 +52,13 @@ export const entry: SemanticMigration = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — names ' + + 'against, `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names ' + 'neither def and neither key: all thirteen exports of plugin-lifecycle-advanced.zod.ts and ' - + 'the string debounceDelay each occur 0 times across its 9546 tracked files (0 across the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 9800 tracked files (0 across the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be and to 14704 and 5545 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49 and to 15352 and 6024 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts index cb463c1292b..1bc4fcdba2a 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -46,10 +46,10 @@ export const entry: SemanticMigration = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204`, re-read from this tree — ' + + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '9546 tracked files, against lit controls timeout 1197, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '9800 tracked files, against lit controls timeout 1293, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', diff --git a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts index 3b35f0bd1ec..12f7ea1c6dd 100644 --- a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts @@ -47,10 +47,10 @@ export const entry: SemanticMigration = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — spells ' + + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 9546 tracked files, against lit controls `useState` 2449 and `timeout` 1197 on ' - + 'the same corpus (all four 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 9800 tracked files, against lit controls `useState` 2464 and `timeout` 1293 on ' + + 'the same corpus (all four 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: diff --git a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts index e3e6508aa56..10deb6d7d56 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts @@ -73,10 +73,11 @@ export const entry: SemanticMigration = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 9546 tracked ' - + 'files at that sha, against lit controls window 3772, timeout 1197, period 228, ' - + 'interval 196 and metrics 341 on that same corpus and sha (0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 9800 tracked ' + + 'files at that sha, against lit controls window 3873, timeout 1293, period 233, ' + + 'interval 196 and metrics 352 on that same corpus and sha (0 across 9546, against 3772 / ' + + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + '1096 / 171 / 179 / 326, at f8a9d0fb0, and 0 across 8303, against 3526 / 1086 / 170 / 179 / ' + '324, at 62597c588), so no pin bump is owed. ' + 'ADR-0087.', diff --git a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts index 3cf68d27d6c..f4dc25058cd 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts @@ -69,12 +69,13 @@ export const entry: SemanticMigration = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 9546 files ' + + 'pinned objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 9800 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 14704 hits for the bare token objectstack, ' - + 'and 5545 for the package specifier @objectstack/spec (at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + + 'two lit controls on that same corpus and sha: 15352 hits for the bare token objectstack, ' + + 'and 6024 for the package specifier @objectstack/spec (at db11afd49: 0 across 9546, Span 486, ' + + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + 'Span 488, 13347 and 5123; at 62597c588: 0 across 8303, Span 486, 13125 and 5043).', acceptanceCriteria: 'Every author and reader of an OpenTelemetryCompatibility spells exporter.timeoutMs, ' diff --git a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts index 5912248a440..c0bd783d287 100644 --- a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts @@ -31,9 +31,9 @@ export const entry: SemanticMigration = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — spells it 0 ' - + 'times across 9546 tracked files, against lit controls `TTL` 181 and `tenant` 1200 on the ' - + 'same corpus (0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells it 0 ' + + 'times across 9800 tracked files, against lit controls `TTL` 181 and `tenant` 1235 on the ' + + 'same corpus (0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/ui/dataset.zod.ts b/packages/spec/src/ui/dataset.zod.ts index deeb5e18103..b00f4cc3254 100644 --- a/packages/spec/src/ui/dataset.zod.ts +++ b/packages/spec/src/ui/dataset.zod.ts @@ -214,7 +214,16 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * and this docblock and the `describe` beneath it both said so. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `db11afd49`; re-derived at that pin 2026-09-29 — `date-display.ts` is + * `e420df310`; re-derived at that pin 2026-09-30 — `dataset-format.ts` is + * byte-identical to `db11afd49`, so `formatMeasureDate` `:229-263`, its call + * at `:369` and its datetime arm `:259`-`:261` did not move and were re-READ + * in place, and `date-display.ts` changed (+68/-1, objectui `858eafb4f`, + * objectui#11141: a header comment naming the new `toDisplayEndDate` / + * `toInclusiveEndDay` exports, and those two functions added after + * `toDisplayDate`, above every anchor here), so `formatDate` `378-413` -> + * `445-480` and the ±7-day fallback `332` -> `399` MOVED byte-identical, + * re-READ with the same reading below. At `db11afd49` (2026-09-29) + * `date-display.ts` was * byte-identical to `dd3f7e1be` and `dataset-format.ts` changed only in three * comment lines (`:529`, `:589`, `:660`, objectui `63ab76112`), all below every * anchor here, so `formatDate` `378-413`, the ±7-day fallback `332`, @@ -251,9 +260,9 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * non-numeric value through `formatMeasureDate` (`:229-263`, was `:229-264` * and before that `:185-198`) at `:369`, * whose date-only arm threads `format` into the STYLE parameter of - * `formatDate` (`utils/date-display.ts:378-413`, was `:355-390`, `:271-306`, + * `formatDate` (`utils/date-display.ts:445-480`, was `:378-413`, `:355-390`, `:271-306`, * `:198-233` and before that `:131-164`, whose `relative` branch falls back to the absolute form - * beyond ±7 days at `:332`, was `:309`, `:225`, `:152` and `:117` — the fallback strips the style through + * beyond ±7 days at `:399`, was `:332`, `:309`, `:225`, `:152` and `:117` — the fallback strips the style through * `absoluteFallbackOptions`), while its datetime arm answers `relative` with * `formatRelativeDate` (`:259`), `short` with * `formatDateTime(v, { locale, style: 'compact' })` (`:260`) and everything From 33396318ff4c3fa36c524b3de3b40ad05979d92b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:56:06 +0000 Subject: [PATCH 03/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 2: view, functional-completeness) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .../src/kernel/functional-completeness.ts | 43 ++++++++++++++----- packages/spec/src/ui/view.zod.ts | 33 ++++++++++---- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index baf14a85ae1..e1436446417 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -278,7 +278,7 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * screen's second clause: `titleField` is NOT required). ⚠️ The screen's * copy is LOCALISED at the pin: objectui#10101 (objectui `afb228418`), which * the pin now contains, routes it through `tt('calendar.configRequired', …)` - * (`ObjectCalendar.tsx:1308-1309`), whose default and the `en` pack both + * (`ObjectCalendar.tsx:1311-1312`), whose default and the `en` pack both * read "Calendar configuration required. Please specify startDateField, …", * while a non-English locale renders its own pack's words for the same * screen (e.g. `zh`, `de`). The refusal itself — the `if (!calendarConfig)` @@ -287,7 +287,15 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * English sentence. So the loss is total rather than partial — every * record, on every object — and the author is told at render time as well * as here. Both reads hold at the pin this repo builds against - * (`.objectui-sha` = `db11afd49`, re-read 2026-09-29: `ObjectCalendar.tsx` + * (`.objectui-sha` = `e420df310`, re-read 2026-09-30: `ObjectCalendar.tsx` + * changed on the hop off `db11afd49` only in comments (6 insertions, 3 + * deletions, objectui#11073's 17.5.0 re-citations), so `getCalendarConfig` + * is byte-identical and unmoved at `:294`, and the `if (!calendarConfig)` + * arm and its `tt(…)` lines are byte-identical, moved `:1301` → `:1304` and + * `:1308-1309` → `:1311-1312`; `ListView.tsx` changed on this hop too + * (objectui#11021's searched grouped list among others) with every `case` + * arm byte-identical, and the `en` / `zh` / `de` pack strings did not + * change. At `db11afd49` (2026-09-29) `ObjectCalendar.tsx` * changed again on the hop off `dd3f7e1be` (18 insertions, 11 deletions: * objectui#11005's and its sibling's DST wall-clock moves, in * `toStoredDateValue` and the handlers below the config gate, plus comment @@ -312,8 +320,14 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * This repo already records the same deletion one door over: the #13817 * check in `../ui/view.zod.ts` names objectui#7029 as its runtime half. * - `gantt` → NO fallback, and no silence [#19630]. Measured at the pin - * this repo builds against (`.objectui-sha` = `db11afd49`, re-read - * 2026-09-29; on the hop off `dd3f7e1be` `ObjectGantt.tsx` changed again + * this repo builds against (`.objectui-sha` = `e420df310`, re-read + * 2026-09-30; on the hop off `db11afd49` `ObjectGantt.tsx` changed again + * (85 insertions, 26 deletions: objectui#11141's inclusive date-only end, + * objectui#8348's record-source ladder and objectui#11070's `reference` + * spelling), so `getGanttConfig` MOVED `:560` → `:607` and the + * `if (!ganttConfig)` arm `:2202` → `:2261`, both byte-identical, and + * `ListView.tsx`'s `case 'gantt'` is byte-identical; at `db11afd49` + * (2026-09-29), on the hop off `dd3f7e1be`, `ObjectGantt.tsx` changed again * (14 insertions, 10 deletions: objectui#10866's zoned-chart DST day read * and write, and a comment citation), so `getGanttConfig` MOVED `:556` → * `:560` and the `if (!ganttConfig)` arm `:2198` → `:2202`, both @@ -328,7 +342,7 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * it reads and writes a date-only day (objectui#10866), `case 'gantt'` only * in a comment; `getGanttConfig` and the `if (!ganttConfig)` arm are * byte-identical to `f8a9d0fb0`, moved `:498` → `:556` and `:1973` → - * `:2198` (`:560` and `:2202` at `db11afd49`), and none of it touches which date fields are bound or the + * `:2198` (`:560` and `:2202` at `db11afd49`, `:607` and `:2261` at `e420df310`), and none of it touches which date fields are bound or the * config gate read below. ① `ListView.tsx`'s * `case 'gantt'` spreads `startDateField` / `endDateField` / `titleField` * only when the view DECLARED them — the `'start_date'` / `'end_date'` @@ -345,9 +359,10 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * requires. So the view does not draw a blank chart: it refuses, by name. * - `timeline` → date axis: NO fallback [#19630]; title: `titleField || 'name'`, * which still stands. Measured at the same pin (`.objectui-sha` = - * `db11afd49`; `ObjectTimeline.tsx` and `ListView.tsx`'s `case 'timeline'` - * are byte-identical to `dd3f7e1be` on the hop onto this pin, so - * `:533-535` and `:862` did not move. At `dd3f7e1be`, `ObjectTimeline.tsx`, byte-identical from `87af769e9` through + * `e420df310`; `ObjectTimeline.tsx`, `ListView.tsx`'s `case 'timeline'` and + * `resolveTimelineDateBinding` are byte-identical to `db11afd49` on the hop + * onto this pin, so `:533-535` and `:862` did not move; they were + * byte-identical to `dd3f7e1be` on the hop onto `db11afd49` too. At `dd3f7e1be`, `ObjectTimeline.tsx`, byte-identical from `87af769e9` through * `62597c588` to `f8a9d0fb0`, changed on this hop — objectui#6356's * declared timeline item, objectui#10222's retired `metaFields` read, * objectui#10866's date-only day in its sort and date buckets, @@ -373,10 +388,16 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * block does render; the warning still fires there, because the block the * view TYPE names is the one that is missing. Unchanged by this row. * - `map` → NO fallback, and no silence [#19630]. Measured at the same - * pin (`.objectui-sha` = `db11afd49`; `ObjectMap.tsx` is byte-identical to - * `dd3f7e1be` on the hop onto this pin, `getMapConfig` `:388`, + * pin (`.objectui-sha` = `e420df310`; on the hop onto this pin + * `ObjectMap.tsx` changed only in the record-source docblock above + * `getMapConfig` (16 insertions, 17 deletions, objectui#8348), so + * `getMapConfig`, `hasCoordinateBinding` and the refusal arm are + * byte-identical, each moved up one line: `:388` → `:387`, `:487` → `:486`, + * `:1285` → `:1284`; `ListView.tsx`'s `case 'map'` and + * `resolveListMapConfig` are byte-identical. At `db11afd49` `ObjectMap.tsx` + * was byte-identical to `dd3f7e1be`, `getMapConfig` `:388`, * `hasCoordinateBinding` `:487` and the refusal arm `:1285` did not move, - * and `ListView.tsx`'s `case 'map'` is byte-identical. At `dd3f7e1be` + * and `ListView.tsx`'s `case 'map'` was byte-identical. At `dd3f7e1be` * `ObjectMap.tsx` changed on that hop — * objectui#10664's single expanded read keyed on the query it issues, * objectui#10623's invalidation re-read, objectui#7508's row ceiling among diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 7df808d003a..60cb550dd31 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -2120,14 +2120,23 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * -> `944` (the whole list, still without `map`); the `ListView.tsx` and * `ObjectMap.tsx` anchors did not move. ⚠️ One CONTENT change rides on the * `ObjectMapConfigSchema` move, spelled beside it below: objectui#5157 closed - * that declaration with `.strict()`. Each anchor quotes the line it was read at, + * that declaration with `.strict()`. RE-READ again at pin `e420df310` on + * 2026-09-30: that bump redded five anchors, and none of them changed what it + * reads — `case 'map':` `2280` -> `2293` (the whole arm byte-identical), + * `ObjectMapConfigSchema` `2037` -> `2056` (its ten-line declaration + * byte-identical, still closed with `.strict()`), `LIST_VIEW_LOCAL_OVERRIDES` + * `944` -> `1083` (the whole list byte-identical, still without `map`), and the + * two `getMapConfig` lines `409` -> `408` and `414` -> `413`, byte-identical: + * objectui `846cec0ef` (objectui#8348) rewrote the record-source docblock above + * `getMapConfig` one line shorter and left the function untouched. The + * `ListView.tsx` anchors did not move. Each anchor quotes the line it was read at, * so the next pin bump reds instead of rotting * (`check:objectui-pin-citations`): * * - **The block this face feeds is FLATTENED, not forwarded.** `ListView` * (`packages/plugin-list/src/ListView.tsx:146` first line * `function resolveListMapConfig(schema: { map?: unknown; options?: { map?: unknown } }): Record {`) - * and `ObjectView` (`packages/plugin-view/src/ObjectView.tsx:2280` first line + * and `ObjectView` (`packages/plugin-view/src/ObjectView.tsx:2293` first line * `case 'map':`) copy it through a HAND-LISTED whitelist * (`packages/plugin-list/src/ListView.tsx:85` first line * `export const FLAT_MAP_CONFIG_SPELLING = {`) — ⚠️ re-read at the new pin: @@ -2139,7 +2148,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * there, but by a whitelist and in SILENCE: no parse, no warning, no * diagnostic of any kind. * - **The renderer's own zod schema does not close the set.** - * `packages/types/src/zod/objectql.zod.ts:2037` first line + * `packages/types/src/zod/objectql.zod.ts:2056` first line * `export const ObjectMapConfigSchema = z.object({` — a plain `z.object` at `dd3f7e1be`, * NOT strict, so an undeclared key parses clean there: zero issues, no * warning. ⚠️ At `db11afd49` the declaration is closed with `.strict()` @@ -2148,10 +2157,10 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * the node with an `unrecognized_keys` issue at `map` — the flatten whitelist * above still drops the key in silence, and the spread below still returns the * authored block. `getMapConfig` consults that `safeParse` - * (`packages/plugin-map/src/ObjectMap.tsx:409` first line + * (`packages/plugin-map/src/ObjectMap.tsx:408` first line * `const result = ObjectMapConfigSchema.safeParse(config);`) only to decide * whether to `console.warn`, then returns a spread of the AUTHORED block - * (`:414` first line `return { ...config, style: config.style || style };`), + * (`:413` first line `return { ...config, style: config.style || style };`), * undeclared key and all. That spread is reached by objectui's own * component-node `map` prop, never by this face's flatten product ("neither * flattener emits a `map` key at all", `getMapConfig`). @@ -2162,7 +2171,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * `db11afd49` it is closed by `.strict()` and still warn-only). And this parse is * the only * place an author is told ANYWHERE: `map` is not in objectui's - * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:944` + * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:1083` * first line `const LIST_VIEW_LOCAL_OVERRIDES = [`), so objectui's own * `ListViewSchema` imports THIS block by reference and the document check on * that side is this same schema. The two key sets MIRROR each other, key for @@ -3340,7 +3349,15 @@ const FormFieldBaseSchema = lazySchema(() => { * inside the `53ded82bf7...87af769e9` range, so the widest-tier-only * under-span this block used to record (#17328: one cell of two at * 720px) no longer reproduces at the pin this repo builds against - * (`.objectui-sha` = `db11afd49`, re-read 2026-09-29: `form.tsx` changed in + * (`.objectui-sha` = `e420df310`, re-read 2026-09-30: `autoLayout.ts` and + * `fields`' `field-type-alias.ts` are byte-identical to `db11afd49`, so + * `resolveColSpan` `:154`, `WIDE_FIELD_TYPES` `:58-69` and the `repeater` -> + * `field:grid` mapping held unmoved, and `form.tsx` changed on this hop — 142 + * insertions, 5 deletions: objectui#8069's faulted-`visibleWhen` submit + * refusal and objectui#11116's multi-value select — none of it inside + * `spanLadderFor`, still `:204-231` byte-identical, and its one call site + * moved `:2848` -> `:2985` with the call byte-identical, so it still emits the + * ladder. At `db11afd49`, re-read 2026-09-29: `form.tsx` changed in * comment citations and one further comment line (`:2454`) and `autoLayout.ts` * in comment citations only, so `spanLadderFor` `:204-231`, its call site * `:2848`, `resolveColSpan` `:154` and `WIDE_FIELD_TYPES` `:58-69` did not move @@ -3358,7 +3375,7 @@ const FormFieldBaseSchema = lazySchema(() => { * had changed only in its registration's input list, objectui#9910's * `children` slot; at `62597c588` it was byte-identical to `87af769e9`). */ - span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `db11afd4967c`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), + span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), /** Custom widget override — only needed when auto-inference is insufficient */ widget: z.string().optional().describe('Custom widget/component name (overrides type-based inference)'), From 592c69ac194ccb16c6a2ec88bac531bca3da3ca4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 22:59:29 +0000 Subject: [PATCH 04/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 3: component.test.ts) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.test.ts | 91 +++++++++++++++++++++----- 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index f38983aaa79..dbfc3980a02 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -285,8 +285,15 @@ describe('PageAccordionProps variant (#6776)', () => { // same file's `ComponentRegistry.register('accordion', …)` publishes the key to // the Studio block designer at `:1220` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `db11afd49`. Re-derived at that pin -// 2026-09-29: `containers.tsx` changed again across the hop from `dd3f7e1be` (46 +// builds against — `.objectui-sha` = `e420df310`. Re-derived at that pin +// 2026-09-30: `containers.tsx` changed again across the hop from `db11afd49` (40 +// insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot and +// objectui#11212's fail-closed permission gates), every hunk of it at `:1286` +// or below, so both anchors were re-READ in place and NEITHER moved, each +// byte-identical: the icon block `1171-1177`, still inside +// `PageAccordionRenderer`'s `AccordionTrigger`, and the input `1220`, still +// inside the `register('accordion', …)` inputs. At `db11afd49`, re-derived at +// that pin 2026-09-29: `containers.tsx` changed again across the hop from `dd3f7e1be` (46 // insertions, 6 deletions: objectui `0ecaa7dbb`'s block-level nested `aria` bags // and comment re-citations), so both anchors were re-READ rather than carried, // and BOTH MOVED with their text byte-identical, the icon block by 32 and the @@ -393,7 +400,14 @@ describe('PageTabsProps items[].value / items[].count (#5775)', () => { // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the // Studio block designer at `:1005` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `db11afd49`. Re-derived at +// the pin this repo builds against — `.objectui-sha` = `e420df310`. Re-derived at +// that pin 2026-09-30: `containers.tsx` changed again across the hop from +// `db11afd49` (40 insertions, 26 deletions: objectui#11166's `page:header` +// breadcrumb slot and objectui#11212's fail-closed permission gates), every hunk +// of it at `:1286` or below, so both anchors were re-READ in place and NEITHER +// moved, each byte-identical: the icon block `946-952`, still inside +// `PageTabsRenderer`'s `TabsTrigger`, and the input `1005`, still inside the +// `register('tabs', …)` inputs. At `db11afd49`, re-derived at // that pin 2026-09-29: `containers.tsx` changed again across the hop from // `dd3f7e1be` (46 insertions, 6 deletions: objectui `0ecaa7dbb`'s block-level // nested `aria` bags and comment re-citations), so both anchors were re-READ @@ -3411,7 +3425,21 @@ describe('#7751 — object-* block props schemas', () => { // #16503 — the spec half of objectui#8172 (decision batch #68, 2026-09-07, // option A: the contract declares the capability that already ships, is // documented and is in use). Measured at the objectui pin this repo builds -// against (`.objectui-sha` = `db11afd49`; re-measured there 2026-09-29 — +// against (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — +// `plugin-kanban/src/types.ts` is byte-identical to `db11afd49` and still +// declares no `limit`; the other four files changed, so their anchors were +// re-READ. `ObjectKanban.tsx` changed at BOTH anchors: objectui#9853 renamed +// the default `DEFAULT_KANBAN_LIMIT` to `DEFAULT_KANBAN_FETCH_BATCH_SIZE` +// (still 100, now documented as the board's fetch batch, not a page size) and +// objectui#11234 moved the board into an internal `KanbanBoardCore`, so the +// query line now reads `$top: resolveRowLimit(schema.limit, +// DEFAULT_KANBAN_FETCH_BATCH_SIZE)` and moved `712` -> `722`, and the constant +// `:88` -> `:97` — same cap, same default, a renamed name. `index.tsx` lost +// the Quick Add plumbing above the mapping (objectui#8285, objectui#11234), so +// `OBJECT_KANBAN_DATA_SOURCE` MOVED `506-510` -> `487-491` byte-identical; +// `objectql.ts` MOVED the member `4302` -> `4430` byte-identical, still inside +// `ObjectKanbanSchema`; `plugin-kanban.mdx` still teaches `limit: 250` and its +// Properties row. At `db11afd49`, re-measured there 2026-09-29 — // `plugin-kanban.mdx`, `plugin-kanban/src/types.ts` and // `plugin-kanban/src/index.tsx` are byte-identical to `dd3f7e1be`; // `ObjectKanban.tsx` changed in one comment line (`:1367`), so `:712` and `:88` @@ -3437,15 +3465,15 @@ describe('#7751 — object-* block props schemas', () => { // `limit?: number` member moved `3735` -> `3832`. All five files were // byte-identical across the hop onto `62597c588`, and were last re-READ at // `87af769e9` 2026-09-20, the hop that moved every anchor and renamed one -// face outright): `plugin-kanban/src/ObjectKanban.tsx:712` -// queries `$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT)` (100, -// `:88`; the bare `??` became `resolveRowLimit` in objectui#9925, which drops +// face outright): `plugin-kanban/src/ObjectKanban.tsx:722` +// queries `$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE)` +// (100, `:97`; the bare `??` became `resolveRowLimit` in objectui#9925, which drops // and reports a cap the contract refuses), -// `plugin-kanban/src/index.tsx:506-510` maps `limit: 'limit'` in +// `plugin-kanban/src/index.tsx:487-491` maps `limit: 'limit'` in // `OBJECT_KANBAN_DATA_SOURCE`, ⚠️ `KanbanSchema` is RETIRED at this pin and // `plugin-kanban/src/types.ts` declares the member no more — the published // twin is `ObjectKanbanSchema`, declaring `limit?: number` at -// `packages/types/src/objectql.ts:4302` — and `content/docs/plugins/plugin-kanban.mdx` +// `packages/types/src/objectql.ts:4430` — and `content/docs/plugins/plugin-kanban.mdx` // teaches `limit: 250` with a Properties row. The strict map refused the key by // name — the same `unrecognized_keys` verdict as the `bogusProp` control — so an // author following the published docs wrote a node the save gate rejected. @@ -3506,7 +3534,20 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // on the React-host `kanban-ui` block). Unlike `limit` above — a key four // objectui faces already implemented, so the spec was the half that was wrong // — `quickAdd` was FORWARDED and never read: at the pin this repo builds -// against (`.objectui-sha` = `db11afd49`; re-measured there 2026-09-29 — +// against (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — +// `KanbanImpl.tsx` is byte-identical to `db11afd49`, so `:621` and `:634` did +// not move and were re-read in place; `ObjectKanban.tsx` changed, carrying +// objectui's own half of this retirement: objectui#8285 stopped forwarding +// `quickAdd`, and objectui#11234 retired `onQuickAdd` and mounts an internal +// `KanbanBoardCore` where it mounted `KanbanRenderer`. So the spread MOVED +// `1614` -> `1641` with its line byte-identical, but it now feeds +// `KanbanBoardCore`, which reads neither key off `schema` and takes the pair +// only as explicit props (`KanbanBoardCore.tsx:78`, `:111-112`) that +// `ObjectKanban` never passes. ⚠️ The 0 / 0 / 11 counts did NOT re-read the +// same: `ObjectKanban.tsx` now spells `quickAdd` 2 times and `onQuickAdd` 2 +// times, all four inside the two objectui#11234 comments that record the cut, +// so it still names neither half in code, against 11 for `onCardClick`. At +// `db11afd49`, re-measured there 2026-09-29 — // `KanbanImpl.tsx` is byte-identical to `dd3f7e1be` and `ObjectKanban.tsx` // changed in one comment line (`:1367`), so `:1614`, `:621` and `:634` did not // move and were re-read in place, and the 0 / 0 / 11 counts re-read the same. At @@ -3521,14 +3562,16 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // below it, not the spread; at `f8a9d0fb0` (2026-09-24) it // had MOVED `1563` -> `1578` the same way (objectui#10068's `$orderby`); both // files were byte-identical across the hop onto `62597c588`, and every anchor -// was re-READ at `87af769e9` 2026-09-20) `ObjectKanban.tsx:1614` +// was re-READ at `87af769e9` 2026-09-20) `ObjectKanban.tsx:1641` // spreads the -// authored bag into `KanbanRenderer` and `KanbanImpl` gates the affordance on +// authored bag into `KanbanBoardCore` (into `KanbanRenderer` until +// objectui#11234, which reads the pair off it no more) and `KanbanImpl` gates the affordance on // `quickAdd && onQuickAdd` (`KanbanImpl.tsx:621`, `:634` — the file is spelled // here because those two ranges are NOT in `ObjectKanban.tsx`), while // `onQuickAdd` is a // host-supplied FUNCTION no producer puts on an `object-kanban` node -// (`ObjectKanban.tsx` names neither half: 0 each re-counted at this pin, +// (`ObjectKanban.tsx` names neither half in code: 0 each re-counted at this pin +// outside the two comments that record objectui's cut, 2 each with them, // against 11 for the sibling `onCardClick` in the same file). describe('ObjectKanbanPropsSchema quickAdd is retired (#17260)', () => { const kanban = ComponentPropsMap['object-kanban']; @@ -3586,7 +3629,11 @@ describe('ObjectKanbanPropsSchema quickAdd is retired (#17260)', () => { // #9881 and commit 60e0f900a recorded the accordion and tab items; these two close the set. // // The button record re-measured at the pin this repo builds against — -// `.objectui-sha` = `db11afd49`, re-derived there 2026-09-29: `button.tsx`, +// `.objectui-sha` = `e420df310`, re-derived there 2026-09-30: `button.tsx`, +// `lazy-icon.tsx`, `resolve-icon.ts` and the generated +// `lucide-record-icon-names.ts` are byte-identical to `db11afd49`, so every +// anchor below holds unmoved and was re-read in place. At `db11afd49`, +// 2026-09-29: `button.tsx`, // `lazy-icon.tsx` and `resolve-icon.ts` are byte-identical to `dd3f7e1be`, so // every anchor below holds unmoved. At `dd3f7e1be`, 2026-09-28: `button.tsx` and // `lazy-icon.tsx` are byte-identical to `f8a9d0fb0` (`git diff --quiet`), so @@ -3728,7 +3775,21 @@ describe('ObjectMetricPropsSchema icon liveness (#10053)', () => { // // The acceptance the card names, pinned: each row's KEY SET is the one the // renderer's read points support at the pin this repo builds against -// (`.objectui-sha` = `db11afd49`; re-measured there 2026-09-29 — `ObjectMap.tsx` +// (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — all three +// renderers changed on the hop off `db11afd49`, `ObjectMap.tsx` in one docblock +// only, `ObjectGantt.tsx` +85/-26 (objectui#11141's inclusive date-only end, +// objectui#8348, objectui#11070) and `ObjectTree.tsx` +29/-26, and the set of +// `schema.*` keys each reads is the same at both pins. ⚠️ One read changed +// CONTENT, in the direction of this row: objectui#8348 (objectui `846cec0ef`) +// moved the tree's rung-1 call from `resolveRecordSourceConfig(schema, +// 'undeclared')` to `'view-data'` (`ObjectTree.tsx:632` -> `:634`), the arm its +// siblings pass, and `record-source.ts` now lists the four tree tags as +// `view-data`; and the shorthand read `(rest as any).data ?? schema.data` +// became `(rest as any).data` (`:862` -> `:865`), so the renderer no longer +// honours an authored bare array at all. The in-test notes below that say the +// tree's arm is `'undeclared'` and WIDER than this row are the `87af769e9` +// reading they are dated to; at this pin the renderer and the row agree. At +// `db11afd49`, 2026-09-29 — `ObjectMap.tsx` // is byte-identical to `dd3f7e1be`, `ObjectTree.tsx` changed in one comment line // and `ObjectGantt.tsx` in its date-only DST handling and citations, and the set // of `schema.*` keys each reads is the same at both pins. At `dd3f7e1be`, From 793f1f2ec73bf55251b68c8abe7ee23c194827d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:03:07 +0000 Subject: [PATCH 05/17] chore(console): regenerate the SDUI manifest at objectui e420df310f5b node scripts/gen-sdui-manifest-node.mjs over .cache/objectui-e420df310f5b/apps/console (107 components, @object-ui/core 17.6.0). Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- scripts/sdui-manifest.record.json | 8 +- sdui.manifest.json | 387 +++++++++++++++++++++++++++++- 2 files changed, 380 insertions(+), 15 deletions(-) diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index f988e1344ad..d9a3ec8487f 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -11,12 +11,12 @@ "a pin bump without regeneration goes RED there — that is the anti-rot half of the freshness gate.", "Regenerate + re-record: pnpm objectui:build && node scripts/gen-sdui-manifest-node.mjs" ], - "objectuiSha": "db11afd4967cd9d39381c5e21dc2deec9d706204", + "objectuiSha": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", "source": "built-tree", - "modulesRoot": ".cache/objectui-db11afd4967c/apps/console", + "modulesRoot": ".cache/objectui-e420df310f5b/apps/console", "objectuiWorkspaceVersion": "17.6.0", "generator": "scripts/gen-sdui-manifest-node.mjs", - "generatedAt": "2026-09-29", - "sha256": "7c7b6b6ebcb14d423712fc03c1133ca571cc784131713c7583cf5881b8271adf", + "generatedAt": "2026-09-30", + "sha256": "f3473ef8c5978880b7008e6d234fbb06e5a7f67f84f5dd589f305e3ce4fbd6d1", "components": 107 } diff --git a/sdui.manifest.json b/sdui.manifest.json index 2b7739e33f4..787d719044e 100644 --- a/sdui.manifest.json +++ b/sdui.manifest.json @@ -571,7 +571,7 @@ { "name": "fields", "type": "array", - "description": "Which parent fields to show, in order — and it is NOT ignored when `sections` is given: the two INTERSECT. The parent field pool is built from this key first and every section then resolves its own members against that pool, so a section member this key does not list is dropped from the rendered form, and a section that loses EVERY member that way disappears with its heading. Each such drop is reported once via `console.warn` (objectui#9884); it is not repaired, because this key is also the parent pool for values, create defaults and the submitted set. Author one or the other, or list every section member here too. Members are bare field names (`{ name }` tolerated); NOT the spec `FormFieldSchema` object `sections[].fields` accepts (identity key `field`) — that shape resolves to no name here and is silently skipped (the parent form renders through the same `ObjectForm` / `SimpleObjectForm` as `object-form` — see its `fields` description)." + "description": "Which parent fields to show, in order — and it is NOT ignored when `sections` is given: the two INTERSECT. The parent field pool is built from this key first and every section then resolves its own members against that pool, so a section member this key does not list is dropped from the rendered form, and a section that loses EVERY member that way disappears with its heading. Each such drop is reported once via `console.warn` (objectui#9884); it is not repaired, because this key bounds what the form DRAWS and edits, not what Save writes: on a create, the submitted set is the drawn fields plus any parent value seeded through `initialValues` (or its alternate spelling `initialData`), drawn or not. A seed for an undeclared, server-owned, computed or read-only field is still stripped, as on any save. Author one or the other, or list every section member here too. Members are bare field names (`{ name }` tolerated); NOT the spec `FormFieldSchema` object `sections[].fields` accepts (identity key `field`) — that shape resolves to no name here and is silently skipped (the parent form renders through the same `ObjectForm` / `SimpleObjectForm` as `object-form` — see its `fields` description)." }, { "name": "title", @@ -786,6 +786,16 @@ "type": "array", "description": "Inline records, read SECOND on the record-source ladder: a `data` configuration wins and this key is then never reached, while `objectName` is read AFTER it, so a gantt carrying both charts these rows and never queries that object. `filter` and `sort` narrow and order these rows exactly as they do fetched ones." }, + { + "name": "filter", + "type": "array", + "description": "Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object, an `api` request, or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape." + }, + { + "name": "sort", + "type": "array", + "description": "Task order in `[{ field, order }]` form, ordering the rows the gantt charts. Lowered to `$orderby` on the same query." + }, { "name": "dataSource", "type": "object", @@ -812,6 +822,16 @@ "gantt" ] }, + { + "name": "filter", + "type": "array", + "description": "Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the records the timeline fetches. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. Authored `items` or `data` suppress that query, so the key then narrows nothing. The MongoDB-style record form is not this key’s shape." + }, + { + "name": "sort", + "type": "array", + "description": "Entry order in `[{ field, order }]` form, ordering the records the timeline fetches. Lowered to `$orderby` on the same query." + }, { "name": "dataSource", "type": "object", @@ -844,6 +864,16 @@ "type": "array", "description": "Inline records, read SECOND on the record-source ladder: a `data` configuration wins and this key is then never reached, while `objectName` is read AFTER it, so a map carrying both plots these rows and never queries that object. `filter` and `sort` narrow and order these rows exactly as they do fetched ones." }, + { + "name": "filter", + "type": "array", + "description": "Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the markers the map plots — fetched rows and inline (`staticData`, `{ provider: 'value' }`) rows alike. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape." + }, + { + "name": "sort", + "type": "array", + "description": "Marker order in `[{ field, order }]` form, ordering the rows the map plots. Lowered to `$orderby` on the same query." + }, { "name": "dataSource", "type": "object", @@ -1207,6 +1237,23 @@ "name": "showHeader", "type": "boolean", "description": "Render the detail body's own title / follow-star / copy-id chip above the fields. Off by default because this block is normally composed under a `page:header` that already draws that chrome, and turning it on there shows the record title twice. Set it true only when this block is the whole page." + }, + { + "name": "enforceFieldSecurity", + "type": "boolean", + "description": "Fold this block's field list through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty row behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — with it off a denied field still arrives masked or stripped, and with it on the server still decides every value." + }, + { + "name": "redactFields", + "type": "array", + "of": "string", + "description": "Field names this block never renders, whatever the permission answer (renderer default: render everything authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. Neighbours `hideFields`, which is the dedupe channel the renderer also writes to." + }, + { + "name": "requiredPermissions", + "type": "array", + "of": "string", + "description": "[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open." } ] }, @@ -1228,6 +1275,23 @@ "vertical" ], "description": "Layout orientation for highlight fields" + }, + { + "name": "enforceFieldSecurity", + "type": "boolean", + "description": "Fold this block's highlight chips through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty chip behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — the record is fetched either way and the server still decides every value." + }, + { + "name": "redactFields", + "type": "array", + "of": "string", + "description": "Field names this block never renders as a chip, whatever the permission answer (renderer default: render every field authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set." + }, + { + "name": "requiredPermissions", + "type": "array", + "of": "string", + "description": "[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open." } ] }, @@ -1297,6 +1361,23 @@ "type": "object", "description": "Adds an \"Add\" button that assigns EXISTING records instead of creating one — the m2m/junction case. Shape: `{ picker: { object, valueField?, labelField?, filter? }, linkField?, label? }`. `picker.object` (required) is the object whose records the dialog offers. `picker.valueField` is the field of the picked record used as the link value (default \"id\"); `picker.labelField` is the column shown in the picker rows (default \"name\", and the other columns are derived from that object's schema). With `linkField` set, selecting records CREATES rows in this list's own object as `{ [relationshipField]: parentValue, [linkField]: pickedId }` — the junction case; omit `linkField` and the picked child is RE-PARENTED instead, by setting its own `relationshipField` to this parent. `label` is the button text (default \"Add\", localizable inline). Setting `add` also enables generic link removal on rows when no host delete handler is wired. `picker.filter` restricts which records the dialog offers — a list of `{ field, operator, value }` rules in the same vocabulary as this list's own `filter`, applied as a hard constraint the user cannot widen (it never appears as an editable filter row)." }, + { + "name": "enforceFieldSecurity", + "type": "boolean", + "description": "Fold this list's `columns` through the caller's FIELD-read permissions on the RELATED object before rendering (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — the rows are fetched either way and the server still decides every value." + }, + { + "name": "redactFields", + "type": "array", + "of": "string", + "description": "Field names this list never renders, whatever the permission answer (renderer default: render every column authored or derived). Applies to the authored `columns` AND to the columns the list derives for itself when none are authored. Presentation only, evaluated in the browser after the rows are fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set." + }, + { + "name": "requiredPermissions", + "type": "array", + "of": "string", + "description": "[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open." + }, { "name": "dataSource", "type": "object", @@ -2002,6 +2083,128 @@ { "name": "className", "type": "string" + }, + { + "name": "visible", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Visibility predicate: `true`/`false`, a bare CEL expression, or the `{ dialect: 'cel', source }` envelope, evaluated against the row the host binds; the button is not rendered when it is false, nor when the predicate fails to evaluate. Omit for always-visible" + }, + { + "name": "disabled", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Disabled predicate, in the same three shapes as `visible`: the button is shown but cannot be pressed while it holds. Omit for never-disabled" + }, + { + "name": "params", + "type": "array", + "description": "The parameters to collect from the user before the action runs (`ActionParam` objects: `name`, `type`, `label`, …); each collected value reaches the executor under its parameter `name`. Static execution values ride `properties.params` instead" + }, + { + "name": "description", + "type": "string", + "description": "Action description — the parameter dialog shows it under its title" + }, + { + "name": "openIn", + "type": "enum", + "enum": [ + "self", + "new-tab" + ], + "description": "For a `url` action: `self` navigates in place, `new-tab` opens a new browser tab" + }, + { + "name": "method", + "type": "string", + "description": "HTTP method of an `api` action (`POST` when omitted)" + }, + { + "name": "bodyExtra", + "type": "object", + "description": "Static request-body fields of an `api` action, merged last, so a constant here overrides a collected value of the same name" + }, + { + "name": "bodyShape", + "type": [ + "enum", + "object" + ], + "enum": [ + "flat" + ], + "description": "How an `api` action shapes its request body: `flat` (the default) or `{ wrap: KEY }` to nest the collected values under that key, with `bodyExtra` beside it" + }, + { + "name": "operation", + "type": "enum", + "enum": [ + "update" + ], + "description": "`update` declares a single-record field write: the runner dispatches it to the platform action route with `patch` merged under the collected values" + }, + { + "name": "patch", + "type": "object", + "description": "For `operation: update`: the field values written, merged UNDER the values the user supplies, so a collected value of the same name wins" + }, + { + "name": "confirmText", + "type": "string", + "description": "Confirmation question asked before the action runs; the action runs only if it is confirmed" + }, + { + "name": "successMessage", + "type": "string", + "description": "Toast text when the action succeeds (a message the server returns takes precedence)" + }, + { + "name": "errorMessage", + "type": "string", + "description": "Toast text when the action fails, in place of the raw error" + }, + { + "name": "refreshAfter", + "type": "boolean", + "description": "Refresh the surrounding data after the action succeeds" + }, + { + "name": "locations", + "type": "array", + "of": "string", + "description": "The placements the action declares (`list_toolbar`, `list_item`, `record_header`, …). A `script` or `flow` action declared record-scoped (`list_item`, `record_header`, `record_more` or `record_section`) refuses to run when no record or single selection is in scope, instead of running without one" + }, + { + "name": "toast", + "type": "object", + "description": "Toast behaviour: `{ showOnSuccess?, showOnError?, duration? }` — `false` suppresses that toast, `duration` is handed to the toast" + }, + { + "name": "resultDialog", + "type": "object", + "description": "One-shot result dialog for a value the response shows exactly once (a 2FA code, a fresh secret); it replaces the success toast" + }, + { + "name": "onSuccess", + "type": "object", + "description": "Post-success navigation `{ navigate, openIn? }`: `navigate` is a route template that can read `${result.*}`, `openIn` is `self` (the default) or `newTab`" + }, + { + "name": "objectName", + "type": "string", + "description": "Object the action acts on — dispatch goes to this object instead of the page's. Omit to act on the page's object" + }, + { + "name": "recordIdField", + "type": "string", + "description": "For a `script` action run against a single selected row: the row field whose value is sent as the record id (default `id`)" } ] }, @@ -2009,10 +2212,6 @@ "type": "action:group", "namespace": "action", "inputs": [ - { - "name": "name", - "type": "string" - }, { "name": "label", "type": "string" @@ -2023,7 +2222,9 @@ }, { "name": "actions", - "type": "object" + "type": "array", + "of": "object", + "description": "The actions in this group, in order. Each member is an action object the group draws and runs itself (`name`, `label`, `icon`, `type`, `target`, `locations`, `visible`, `disabled`, …); a member's executor is its own `type`" }, { "name": "display", @@ -2033,6 +2234,19 @@ "dropdown" ] }, + { + "name": "location", + "type": "enum", + "enum": [ + "list_toolbar", + "list_item", + "record_header", + "record_more", + "record_related", + "record_section" + ], + "description": "Render only the members whose `locations` include this location. Omit to render every member" + }, { "name": "variant", "type": "enum", @@ -2047,10 +2261,21 @@ "name": "size", "type": "enum", "enum": [ + "default", "sm", - "md", - "lg" - ] + "lg", + "icon" + ], + "description": "Button size for the dropdown trigger and for every inline member that sets none" + }, + { + "name": "visible", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Visibility predicate for the whole group: `true`/`false`, a bare CEL expression, or the `{ dialect: 'cel', source }` envelope, evaluated against the row the host binds. Omit for always-visible" }, { "name": "className", @@ -2072,7 +2297,9 @@ }, { "name": "actions", - "type": "object" + "type": "array", + "of": "object", + "description": "The menu's actions, in order. Each member is an action object the menu draws and runs itself (`name`, `label`, `icon`, `type`, `target`, `visible`, `disabled`, `tags`, …); a member's executor is its own `type`" }, { "name": "variant", @@ -2084,6 +2311,26 @@ "ghost" ] }, + { + "name": "size", + "type": "enum", + "enum": [ + "default", + "sm", + "lg", + "icon" + ], + "description": "Trigger button size (renderer default: `icon`)" + }, + { + "name": "visible", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Visibility predicate for the whole menu: `true`/`false`, a bare CEL expression, or the `{ dialect: 'cel', source }` envelope, evaluated against the row the host binds; a predicate that fails to evaluate hides it. Omit for always-visible" + }, { "name": "className", "type": "string" @@ -2135,6 +2382,123 @@ { "name": "className", "type": "string" + }, + { + "name": "visible", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Visibility predicate: `true`/`false`, a bare CEL expression, or the `{ dialect: 'cel', source }` envelope, evaluated against the row the host binds; the icon is not rendered when it is false. Omit for always-visible" + }, + { + "name": "disabled", + "type": [ + "boolean", + "string", + "object" + ], + "description": "Disabled predicate, in the same three shapes as `visible`: the icon is shown but cannot be pressed while it holds. Omit for never-disabled" + }, + { + "name": "params", + "type": "array", + "description": "The parameters to collect from the user before the action runs (`ActionParam` objects: `name`, `type`, `label`, …); each collected value reaches the executor under its parameter `name`. Static execution values ride `properties.params` instead" + }, + { + "name": "description", + "type": "string", + "description": "Action description — the tooltip text when there is no `label`, and what the parameter dialog shows under its title" + }, + { + "name": "openIn", + "type": "enum", + "enum": [ + "self", + "new-tab" + ], + "description": "For a `url` action: `self` navigates in place, `new-tab` opens a new browser tab" + }, + { + "name": "method", + "type": "string", + "description": "HTTP method of an `api` action (`POST` when omitted)" + }, + { + "name": "bodyExtra", + "type": "object", + "description": "Static request-body fields of an `api` action, merged last, so a constant here overrides a collected value of the same name" + }, + { + "name": "bodyShape", + "type": [ + "enum", + "object" + ], + "enum": [ + "flat" + ], + "description": "How an `api` action shapes its request body: `flat` (the default) or `{ wrap: KEY }` to nest the collected values under that key, with `bodyExtra` beside it" + }, + { + "name": "operation", + "type": "enum", + "enum": [ + "update" + ], + "description": "`update` declares a single-record field write: the runner dispatches it to the platform action route with `patch` merged under the collected values" + }, + { + "name": "patch", + "type": "object", + "description": "For `operation: update`: the field values written, merged UNDER the values the user supplies, so a collected value of the same name wins" + }, + { + "name": "confirmText", + "type": "string", + "description": "Confirmation question asked before the action runs; the action runs only if it is confirmed" + }, + { + "name": "successMessage", + "type": "string", + "description": "Toast text when the action succeeds (a message the server returns takes precedence)" + }, + { + "name": "errorMessage", + "type": "string", + "description": "Toast text when the action fails, in place of the raw error" + }, + { + "name": "refreshAfter", + "type": "boolean", + "description": "Refresh the surrounding data after the action succeeds" + }, + { + "name": "locations", + "type": "array", + "of": "string", + "description": "The placements the action declares (`list_toolbar`, `list_item`, `record_header`, …). A `script` or `flow` action declared record-scoped (`list_item`, `record_header`, `record_more` or `record_section`) refuses to run when no record or single selection is in scope, instead of running without one" + }, + { + "name": "toast", + "type": "object", + "description": "Toast behaviour: `{ showOnSuccess?, showOnError?, duration? }` — `false` suppresses that toast, `duration` is handed to the toast" + }, + { + "name": "resultDialog", + "type": "object", + "description": "One-shot result dialog for a value the response shows exactly once (a 2FA code, a fresh secret); it replaces the success toast" + }, + { + "name": "onSuccess", + "type": "object", + "description": "Post-success navigation `{ navigate, openIn? }`: `navigate` is a route template that can read `${result.*}`, `openIn` is `self` (the default) or `newTab`" + }, + { + "name": "objectName", + "type": "string", + "description": "Object the action acts on — dispatch goes to this object instead of the page's. Omit to act on the page's object" } ] }, @@ -2487,7 +2851,8 @@ }, { "name": "breadcrumb", - "type": "boolean" + "type": "boolean", + "description": "Ignored: the header draws no breadcrumb, and the key is being retired from the contract. Leave it out." }, { "name": "recordChrome", From d9a073335923ba85bb36be256b47038126005eed Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:04:09 +0000 Subject: [PATCH 06/17] chore(sdui-parser): re-record the objectui lockstep at e420df310f5b pnpm gen:sdui-lockstep against the pin's own checkout: 214 grammar-region lines, 25 diagnostic codes on objectui's side. check:sdui-lockstep reports one code-drift finding (inert-quick-add, deleted on objectui's side by objectui#8285); the port it asks for is outside this card's file surface and is reported, not made. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/sdui-parser/objectui-lockstep.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index 3836118cfcc..395122f00fe 100644 --- a/packages/sdui-parser/objectui-lockstep.json +++ b/packages/sdui-parser/objectui-lockstep.json @@ -6,8 +6,8 @@ ], "objectui": { "repo": "https://github.com/objectstack-ai/objectui.git", - "rev": "db11afd4967cd9d39381c5e21dc2deec9d706204", - "revDate": "2026-09-29T18:01:28+00:00", + "rev": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", + "revDate": "2026-09-30T22:03:26+00:00", "source": "packages/sdui-parser/src", "files": [ "packages/sdui-parser/src/body-dialect.ts", @@ -15,14 +15,13 @@ "packages/sdui-parser/src/dashboard-widget-options.ts", "packages/sdui-parser/src/index.ts", "packages/sdui-parser/src/input-type.ts", - "packages/sdui-parser/src/kanban-quick-add.ts", "packages/sdui-parser/src/parse.ts", "packages/sdui-parser/src/provenance.ts", "packages/sdui-parser/src/types.ts", "packages/sdui-parser/src/validate.ts" ] }, - "recordedAgainstPin": "db11afd4967cd9d39381c5e21dc2deec9d706204", + "recordedAgainstPin": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", @@ -39,7 +38,6 @@ "forbidden-attr", "forbidden-tag", "inert-expression", - "inert-quick-add", "invalid-enum", "member-type-mismatch", "mismatched-tag", From f82f8f852fcfd0f8531a677e4ada67ef414fe780 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:06:15 +0000 Subject: [PATCH 07/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 4: component.zod.ts part 1) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.zod.ts | 214 +++++++++++++++++--------- 1 file changed, 139 insertions(+), 75 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 17b0f4ff727..841da527dbb 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -724,7 +724,12 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-derived at that pin 2026-09-29 — + * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — + * `containers.tsx` changed again across the hop from `db11afd49` (40 + * insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot + * and objectui#11212's fail-closed permission gates), every hunk at `:1286` + * or below, so both anchors were re-READ in place and NEITHER moved, each + * byte-identical: `946-952` and `1005`. At `db11afd49` (2026-09-29) * `containers.tsx` changed again across the hop from `dd3f7e1be` (46 * insertions, 6 deletions: objectui `0ecaa7dbb`'s block-level nested `aria` * bags and comment re-citations), so both anchors were re-READ rather than @@ -905,7 +910,18 @@ export const PageCardProps = strictObject({ * declarations identical. * * Each clause is read off the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd4967c`, re-read there 2026-09-29: across the hop + * (`.objectui-sha` = `e420df310f5b`, re-read there 2026-09-30: across the hop + * from `db11afd4967c` `record-quick-actions.tsx` and `usePermissions.ts` are + * byte-identical, `MePermissionsProvider.tsx` and `PermissionProvider.tsx` each + * gained one `effectiveObjects` member below every cited line (objectui#4421), + * so NO anchor in those four moved, and `record-details.tsx`, + * `record-highlights.tsx` and `record-related-list.tsx` changed around the gate + * without touching it — objectui#8649 reads `requiredPermissions` UN-CAST + * (output-identical), their docblocks now say the notice replaces the content + * rather than hiding the block, and objectui#11163 placed the related list's + * authored `actions` above its gate — so every anchor in those three MOVED with + * its cited text byte-identical, and each is cited below at this pin. At + * `db11afd4967c`, 2026-09-29: across the hop * from `dd3f7e1be356` `record-highlights.tsx`, `record-quick-actions.tsx` and * `usePermissions.ts` are byte-identical, and `record-details.tsx`, * `record-related-list.tsx`, `MePermissionsProvider.tsx` and @@ -923,28 +939,28 @@ export const PageCardProps = strictObject({ * with its cited text byte-identical, by 1 and by 4 respectively: * * 1. CAPABILITIES, NOT OBJECT ACTIONS. Every block gates through - * `perms.hasCapabilities(required)` — `record-details.tsx:236`, - * `record-highlights.tsx:93`, `record-related-list.tsx:276`, + * `perms.hasCapabilities(required)` — `record-details.tsx:244`, + * `record-highlights.tsx:102`, `record-related-list.tsx:364`, * `record-quick-actions.tsx:267` — and never `perms.can(objectName, …)`; * each renderer's docblock states the capability "is not object-scoped" - * (`record-details.tsx:225`, `record-highlights.tsx:77`, - * `record-related-list.tsx:263`, `record-quick-actions.tsx:256`). `read` + * (`record-details.tsx:233`, `record-highlights.tsx:81`, + * `record-related-list.tsx:351`, `record-quick-actions.tsx:256`). `read` * is looked up in the capability set like any other name. * 2. ALL OF THEM. `MePermissionsProvider.tsx:416` is * `required.every((p) => held.has(p))`. * 3. THE OUTCOME. Each block returns a `role="status"` "Insufficient * permissions to view …" notice instead of its content — - * `record-details.tsx:236-244`, `record-highlights.tsx:151-164`, - * `record-related-list.tsx:276-284`, `record-quick-actions.tsx:267-275`. + * `record-details.tsx:244-252`, `record-highlights.tsx:160-173`, + * `record-related-list.tsx:364-372`, `record-quick-actions.tsx:267-275`. * Checks that already withhold the content run first on two of them (no * record bound, `record-details.tsx:188`; no object, - * `record-related-list.tsx:218`; the related object's read gate, - * `record-related-list.tsx:236`), which is why the text says "wherever it + * `record-related-list.tsx:297`; the related object's read gate, + * `record-related-list.tsx:315`), which is why the text says "wherever it * would otherwise render" rather than promising the notice unconditionally. * 4. PRESENTATION ONLY. The gate is renderer code: nothing in this repo's * server packages reads a page component's `requiredPermissions`. The data * stays in reach — a gated `record:highlights` registers no field names - * (`record-highlights.tsx:146-149`), so `record:details` no longer + * (`record-highlights.tsx:155-158`), so `record:details` no longer * de-duplicates those fields out of its body. To keep a value from a * user, gate the object, the field or the action. * 5. UNRESOLVED CAPABILITIES FAIL OPEN. `hasCapabilities` answers `true` when @@ -1992,7 +2008,12 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-derived at that pin 2026-09-29 — + * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — + * `containers.tsx` changed again across the hop from `db11afd49` (40 + * insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot + * and objectui#11212's fail-closed permission gates), every hunk at `:1286` + * or below, so both anchors were re-READ in place and NEITHER moved, each + * byte-identical: `1171-1177` and `1220`. At `db11afd49` (2026-09-29) * `containers.tsx` changed again across the hop from `dd3f7e1be` (46 * insertions, 6 deletions: objectui `0ecaa7dbb`'s block-level nested `aria` * bags and comment re-citations), so both anchors were re-READ rather than @@ -2266,11 +2287,12 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-derived at that pin 2026-09-29 — + * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — * `button.tsx`, `lib/lazy-icon.tsx`, `renderers/action/resolve-icon.ts` and the - * generated `lucide-record-icon-names.ts` are all byte-identical to `dd3f7e1be` + * generated `lucide-record-icon-names.ts` are all byte-identical to `db11afd49` * across the hop onto this pin, so every anchor below holds unmoved and was - * re-checked in place. At `dd3f7e1be` (2026-09-28) + * re-checked in place; they were byte-identical to `dd3f7e1be` across the hop + * onto `db11afd49` (2026-09-29) as well. At `dd3f7e1be` (2026-09-28) * `button.tsx` and `lib/lazy-icon.tsx` are byte-identical to `f8a9d0fb0` * (`git diff --quiet`); `resolve-icon.ts` changed on this hop (+42/-17, * objectui `fb336df01`, the lucide-react 1.31.0 -> 1.43.0 bump: the glyph @@ -2738,12 +2760,23 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * had no row for — `action:button`, `action:group`, `action:menu`, * `action:icon`, `element:definition-list`, `element:repeater` * (`core/src/registry/public-blocks.ts:117-122` at the pin this repo builds - * against, `.objectui-sha` = `db11afd49`; first measured at `.objectui-sha` + * against, `.objectui-sha` = `e420df310`; first measured at `.objectui-sha` * pin `f8a9d0fb0`, every read point below re-derived at the current pin - * 2026-09-29: `public-blocks.ts` and `auto-trigger.ts` are byte-identical to - * `dd3f7e1be`, and `action-button.tsx`, `action-group.tsx`, `action-icon.tsx` and - * `action-menu.tsx` changed only in comment lines that re-qualify an objectstack - * card number, none of them a cited line, so every anchor here holds unmoved). + * 2026-09-30: `public-blocks.ts`, `auto-trigger.ts` and `static-params.ts` are + * byte-identical to `db11afd49`, and all four `action-*.tsx` renderers changed + * — objectui#11168 slice 1 rewrote every registration's `inputs` to publish + * the keys the block path honours, objectui#11212 made each `visible` gate + * fail CLOSED on a faulting predicate, objectui#11182 made `action:group` and + * `action:menu` consume the host's evaluated `disabled` verdict, and + * objectui#11073 renamed the renderers' React prop types — so every anchor was + * re-READ at this pin and the rows below cite it here. Every declared key is + * still read where its row cites it; what changed is what the registrations + * publish, one read count on `action:icon`, the fault policy of the `visible` + * gates, and a host `disabled` verdict the two containers now consume — each + * said where it is written. At + * `db11afd49` (2026-09-29) `public-blocks.ts` and `auto-trigger.ts` were + * byte-identical to `dd3f7e1be`, and the four renderers changed only in comment + * lines that re-qualify an objectstack card number, none of them a cited line). * The missing row failed in two different ways: * * - The four `action:*` types sit outside every namespace the @@ -2793,10 +2826,10 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * * `objectName` IS declared on `action:button` / `action:icon`: absent at the * first measurement, it is forwarded to the runner at the current pin - * (`action-button.tsx:307`, `action-icon.tsx:195`), and the console resolves + * (`action-button.tsx:311`, `action-icon.tsx:208`), and the console resolves * its dispatch target as `action.objectName || `. On * `action:group` / `action:menu` the forward is the MEMBER's - * (`action-group.tsx:323`, `action-menu.tsx:313`), so it rides each member + * (`action-group.tsx:378`, `action-menu.tsx:324`), so it rides each member * object and the container rows gain no key. */ @@ -2861,36 +2894,40 @@ const ACTION_NODE_GUIDANCE = { * runner (`components/src/renderers/action/action-button.tsx` at the pin). * Read points, per key: * - * - `name` — `:119` (the visibility-diagnostic label, `schema.name ?? - * schema.label`) and `:212` (forwarded). OPTIONAL, as it is read: an inline + * - `name` — `:123` (the visibility-diagnostic label, `schema.name ?? + * schema.label`) and `:216` (forwarded). OPTIONAL, as it is read: an inline * page button is not a registered object action, and the runner takes a * nameless action on its `type` leg. `UIActionSchema` declares it required; * the read does not. - * - `label` — `:383`, placed as a React child (so a literal string: an inline + * - `label` — `:387`, placed as a React child (so a literal string: an inline * locale map is not resolved on this path; the translation bundle's - * `components..label` is the localization channel); also `:119`, `:216`. - * - `icon` — `:134`, through the shared `resolveIcon`. - * - `actionType` — `:211`, forwarded as the runner's `type`. - * - `variant` / `size` — `:137` / `:138`: `primary` → the primitive's + * `components..label` is the localization channel); also `:123`, `:220`. + * - `icon` — `:138`, through the shared `resolveIcon`. + * - `actionType` — `:215`, forwarded as the runner's `type`. + * - `variant` / `size` — `:141` / `:142`: `primary` → the primitive's * `default` and `md` → `default`, everything else handed to `Button` as-is. - * - `visible` / `disabled` — `:117` + `:335` / `:130` + `:371`, evaluated + * - `visible` / `disabled` — `:121` + `:339` / `:134` + `:375`, evaluated * against the row the host binds (`usePredicateRecordContext`). - * - `params` — `:176-179`. An array is the input list, forwarded as + * - `params` — `:180-183`. An array is the input list, forwarded as * `actionParams`; the static values are read off `properties.params` * itself (`readStaticParamValues`, `static-params.ts:91-101`). On a page * node the two are one key: this row IS `properties`, and `SchemaRenderer`'s * hoist makes `schema.params` the same object, so an array here is the input * list and an object is the static values. (A node-level object `params` * outside `properties` is ignored with a development warning.) - * - Forwarded to the runner (`:211-307`): `description`, `target`, `openIn`, + * - Forwarded to the runner (`:215-311`): `description`, `target`, `openIn`, * `endpoint`, `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`, * `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, * `undoable`, `recordIdField`, `locations`, `toast`, `resultDialog`, * `onSuccess`, `objectName`. * - * The registration's `inputs` (`:395-416`) publish seven of these twenty-nine - * keys; the other twenty-two are read and unpublished. It also publishes - * `className`, which is read but is a node key. + * The registration's `inputs` (`:414-551`) publish twenty-seven of these + * twenty-nine keys since objectui#11168 slice 1 (at `db11afd49` they published + * seven, `:395-416`); the two left unpublished are `endpoint` and `undoable`, + * each still forwarded (`:215-311`), on objectui's measurement that the + * console's own `api` handler reads `target` and never `endpoint`, and that + * the runner offers Undo only with a host row stash this block never writes. + * It also publishes `className`, which is read but is a node key. */ export const ActionButtonPropsSchema = lazySchema(() => strictObject({ surface: 'this `action:button`', @@ -2957,18 +2994,22 @@ export type ActionButtonPropsParsed = z.infer; * (`components/src/renderers/action/action-icon.tsx` at the pin). The same * runner path as `action:button`, measured separately because the two differ: * - * - `size` is NOT read — `:107` pins the primitive's `icon` size — so it is + * - `size` is NOT read — `:120` pins the primitive's `icon` size — so it is * not declared (the registration does not publish it either). - * - `undoable` and `recordIdField` are NOT forwarded (`:134-196` lists the + * - `undoable` and `recordIdField` are NOT forwarded (`:147-209` lists the * rest of `action:button`'s forward and not these two), so not declared. - * - `label` is read four times: `:152` (forwarded), `:265` (`aria-label`, - * falling back to `name`), `:274` (its first letter when no icon resolves) - * and `:280` / `:286` (the tooltip). `description` is the tooltip's - * fallback (`:286`) as well as forwarded (`:153`). - * - `visible` / `disabled` — `:97` + `:229` / `:102` + `:258`. `variant` — - * `:106`, `primary` mapped to `default`, renderer default `ghost`. - * - `params` — `:130-133`, routed exactly as on `action:button`. - * - Forwarded (`:147-195`): `actionType`, `name`, `target`, `openIn`, + * - `label` is read five times at this pin (four at `db11afd49`): + * `:109` (the visibility-diagnostic label, `schema.name ?? schema.label`, + * new with objectui#11212's fail-closed `visible`), `:165` (forwarded), + * `:278` (`aria-label`, falling back to `name`), `:287` (its first letter + * when no icon resolves) and `:293` / `:299` (the tooltip). `description` + * is the tooltip's fallback (`:299`) as well as forwarded (`:166`). + * - `visible` / `disabled` — `:107` + `:242` / `:115` + `:271`; `visible` + * fails CLOSED on a faulting predicate since objectui#11212, as on + * `action:button`. `variant` — `:119`, `primary` mapped to `default`, + * renderer default `ghost`. + * - `params` — `:143-146`, routed exactly as on `action:button`. + * - Forwarded (`:160-208`): `actionType`, `name`, `target`, `openIn`, * `endpoint`, `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`, * `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, * `locations`, `toast`, `resultDialog`, `onSuccess`, `objectName`. @@ -3035,8 +3076,10 @@ export type ActionIconPropsParsed = z.infer; /** * The member list `action:group` and `action:menu` both read — a LIST, as the - * renderers read it (`schema.actions || []`, then `.filter` / `.map`), where - * both registrations publish the input as `type: 'object'`. + * renderers read it (`schema.actions || []`, then `.filter` / `.map`). Both + * registrations published the input as `type: 'object'` through `db11afd49`; + * since objectui#11168 slice 1 both publish `type: 'array', of: 'object'`, the + * shape declared here. * * Each member is an action object the container draws and runs ITSELF, never * through `SchemaRenderer`, so a member is not a page component and this row @@ -3060,30 +3103,46 @@ const actionMemberList = () => z.array(z.record(z.string(), z.unknown())); * (`components/src/renderers/action/action-group.tsx` at the pin). Read * points, per key: * - * - `actions` — `:248`, then filtered by `location` through `actionRendersAt` - * (`:249`); members are read at `:81-134` (inline), `:166-204` (dropdown) - * and forwarded at `:274-324` (static values `:274-280`, `objectName` - * `:323`). - * - `display` — `:346`, `inline` unless it is `dropdown`. - * - `label` / `icon` — `:365` / `:350`: the DROPDOWN trigger's text (default + * - `actions` — `:303`, then filtered by `location` through `actionRendersAt` + * (`:304`); members are read at `:114-171` (inline), `:205-243` (dropdown), + * both through the shared `useMemberVisible` for `visible` (`:81-86`), and + * forwarded at `:329-379` (static values `:329-335`, `objectName` + * `:378`). + * - `display` — `:401`, `inline` unless it is `dropdown`. + * - `label` / `icon` — `:421` / `:405`: the DROPDOWN trigger's text (default * `Actions`) and icon. Inline mode renders neither. - * - `variant` — `:356` (dropdown trigger) and `:397` (each inline member's + * - `variant` — `:411` (dropdown trigger) and `:453` (each inline member's * fallback); no `primary` mapping at group level, so the primitive's six. - * - `size` — `:357` maps `md` → `default` for the dropdown trigger, but `:398` - * hands the group size to each inline member raw, and `:91` maps only a + * - `size` — `:412` maps `md` → `default` for the dropdown trigger, but `:454` + * hands the group size to each inline member raw, and `:124` maps only a * member's OWN `md`. So `md` renders only in dropdown mode, and in the * default inline mode reaches the Button primitive, which has no `md`. - * Declared: the primitive's four sizes. (The registration publishes - * `sm` / `md` / `lg`; `md` is objectui's to map on both paths or drop.) - * - `visible` — `:238` + `:343`. `:343` tests the raw value's truthiness, so a + * Declared: the primitive's four sizes. (Since objectui#11168 the + * registration publishes those same four, `:484-525`; through `db11afd49` + * it published `sm` / `md` / `lg`. A stored `md` is still mapped in + * dropdown mode only, which objectui records as a back-compat read.) + * - `visible` — `:290` + `:398`; `:290` fails CLOSED on a faulting predicate + * since objectui#11212. `:398` tests the raw value's truthiness, so a * literal `false` is honoured by `SchemaRenderer`'s node gate, which also * evaluates the hoisted value, rather than by this check. * - * NOT read: the group's own `name`. The registration publishes it (`:415`), - * the renderer never reads `schema.name`, and inline mode only spreads it - * onto the wrapping `
` as a DOM attribute. Refused with a prescription, - * the `page:accordion` item `value` precedent: an author copying the - * designer's output is told what happened rather than merely refused. + * ⚠️ New at this pin, and NOT a key of this row: objectui#11182 has the group + * consume the host's EVALUATED `disabled` verdict by name (`:274`), the one + * `SchemaRenderer`'s generic enablement gate computes from a hoisted + * `disabled` on any node, and apply it to every inline member (`:159`) or to + * the dropdown trigger (`:415`). So a `properties.disabled` on this block now + * greys it out at render while this strict row refuses the key at save. The + * row stays as measured from the block's own reads: declaring the key is a + * contract decision, not a pin re-measure. + * + * NOT read: the group's own `name`. The renderer never reads `schema.name`, + * and inline mode only spreads it onto the wrapping `
` as a DOM + * attribute. The registration published it through `db11afd49` (`:415` + * there); objectui#11168 stopped publishing it because nothing reads it and + * this row refuses it (`action-group.tsx:40-42`). Refused with a + * prescription, the `page:accordion` item `value` precedent: an author + * copying an older designer's output is told what happened rather than + * merely refused. */ export const ActionGroupPropsSchema = lazySchema(() => strictObject({ surface: 'this `action:group`', @@ -3126,20 +3185,25 @@ export type ActionGroupPropsParsed = z.infer; * (`components/src/renderers/action/action-menu.tsx` at the pin). Read * points, per key: * - * - `actions` — `:328`; members are read at `:80`, `:108-147` and `:389`, run - * through `ActionAutoTrigger` (`:349-356`), and forwarded at `:253-314` - * (static values `:253-259`, `objectName` `:313`). - * - `label` — `:370` (`aria-label`, default: the translated "More actions") - * and `:379-380` (trigger text; icon-only when omitted). - * - `icon` — `:229`, default the `MoreHorizontal` glyph. - * - `variant` / `size` — `:230` / `:231`, handed to the Button primitive + * - `actions` — `:339`; members are read at `:80`, `:108-147` and `:405`, run + * through `ActionAutoTrigger` (`:360-367`), and forwarded at `:264-325` + * (static values `:264-270`, `objectName` `:324`). + * - `label` — `:386` (`aria-label`, default: the translated "More actions") + * and `:395-396` (trigger text; icon-only when omitted). + * - `icon` — `:240`, default the `MoreHorizontal` glyph. + * - `variant` / `size` — `:241` / `:242`, handed to the Button primitive * unmapped (defaults `ghost` / `icon`): no `primary`, no `md` here. - * - `visible` — `:224` + `:322`, fail-closed; the same truthiness note as + * - `visible` — `:235` + `:333`, fail-closed; the same truthiness note as * `action:group`'s applies to a literal `false`. * - * The registration's `inputs` (`:410-419`) publish `label`, `icon`, - * `actions`, `variant` and `className`; `size` and `visible` are read and - * unpublished. + * The registration's `inputs` (`:432-460`) publish `label`, `icon`, + * `actions`, `variant`, `size`, `visible` and `className` since objectui#11168 + * slice 1, every key this row declares; through `db11afd49` (`:410-419` + * there) `size` and `visible` were read and unpublished. ⚠️ As on + * `action:group`, objectui#11182 has the trigger consume the host's evaluated + * `disabled` verdict (`:219`, `:385`) — at `db11afd49` it already reached the + * trigger through the `...rest` spread — so a `properties.disabled` here greys + * the trigger out while this row refuses it; recorded, not declared. */ export const ActionMenuPropsSchema = lazySchema(() => strictObject({ surface: 'this `action:menu`', From fca00f9e0b2e5edb99b5181b6360219c0596337c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:08:55 +0000 Subject: [PATCH 08/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 5) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.zod.ts | 85 ++++++++++++++++++++------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 841da527dbb..f90c459b403 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -3836,7 +3836,11 @@ export const ObjectMetricPropsSchema = lazySchema(() => strictObject({ * same record for the metric tile. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-derived at that pin 2026-09-29 — + * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — + * `plugin-dashboard/src/index.tsx`, `ObjectMetricWidget.tsx`, + * `MetricWidget.tsx`, `MetricCard.tsx` and `lazy-icon.tsx` are all + * byte-identical to `db11afd49`, so every anchor below holds unmoved and was + * re-read in place. At `db11afd49` (2026-09-29) * `MetricWidget.tsx` and `lazy-icon.tsx` are byte-identical to `dd3f7e1be`; * `ObjectMetricWidget.tsx` changed in one comment line (`:321`), so `:230` and * `:595` did not move; `plugin-dashboard/src/index.tsx` gained the @@ -3960,7 +3964,13 @@ export type ObjectMetricPropsParsed = z.infer; * DESIGNER's spelling with * zero read points (#7973 class) — aliased to the `groupBy` the board reads. * `limit` (#16503) was measured later, at the pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-measured there 2026-09-29 — + * (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — + * `ObjectKanban.tsx` changed on this hop, +40/-13: objectui#9853 renamed the + * default `DEFAULT_KANBAN_LIMIT` to `DEFAULT_KANBAN_FETCH_BATCH_SIZE`, still + * 100, on the anchor line itself, and objectui#11234 mounts an internal + * `KanbanBoardCore` board. So the anchor was re-READ and MOVED `712` -> `722`, + * now `$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE)`: + * the same lowering under a renamed default. At `db11afd49` (2026-09-29) * `ObjectKanban.tsx` changed in one comment line only (`:1367`), so the anchor * did not move and was re-read in place. At `dd3f7e1be` (2026-09-28) * `ObjectKanban.tsx` changed on this hop, +44/-8 (objectui#10572's @@ -3981,7 +3991,7 @@ export type ObjectMetricPropsParsed = z.infer; * having put a refusal in front of it — a contract-refused row cap is dropped * and reported at `:554` instead of being sent. The pinned fact is unchanged: * `schema.limit` still lowers into the query's top-level `$top`): - * `ObjectKanban.tsx:712`, the `$top` of the + * `ObjectKanban.tsx:722`, the `$top` of the * board's one query — its docblock below carries the four-face record. */ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ @@ -4022,8 +4032,24 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * Row cap (#16503 — the spec half of objectui#8172; decision batch #68, * 2026-09-07, option A: the contract declares the capability that already * ships, is documented and is in use). Measured at the objectui pin this - * repo builds against (`.objectui-sha` = `db11afd49`; re-measured there - * 2026-09-29 — `plugin-kanban.mdx`, `plugin-kanban/src/types.ts` and + * repo builds against (`.objectui-sha` = `e420df310`; re-measured there + * 2026-09-30 — `plugin-kanban/src/types.ts` (still no row-cap member), + * `ElementDataSourceGate.tsx` and `element-data-source.ts` are + * byte-identical to `db11afd49`, so `:218-220`, `:437-456` and `:241-245` did + * not move; `ObjectKanban.tsx` changed (+40/-13) and its query CHANGED ONE + * MEMBER: objectui#9853 renamed the default `DEFAULT_KANBAN_LIMIT` to + * `DEFAULT_KANBAN_FETCH_BATCH_SIZE` (still `100`, now documented as the + * board's fetch batch, not a page size), so `$top` reads + * `resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE)`, and the + * query MOVED `705-715` -> `715-725`, the default `:88` -> `:97` and the + * `queryFilter` resolution `:574-575` -> `:584-585`, while the import at + * `:10` did not move; `plugin-kanban/src/index.tsx` lost the Quick Add + * plumbing above the mapping (objectui#8285, objectui#11234), which MOVED + * byte-identical `506-510` -> `487-491`; `objectql.ts` MOVED the member + * `4302` -> `4430` byte-identical, still inside `ObjectKanbanSchema`; and + * `plugin-kanban.mdx` changed around, not in, the `limit: 250` snippet and + * its Properties row. At `db11afd49`, re-measured there 2026-09-29 — + * `plugin-kanban.mdx`, `plugin-kanban/src/types.ts` and * `ElementDataSourceGate.tsx` are byte-identical to `dd3f7e1be`, * `plugin-kanban/src/index.tsx` too, `ObjectKanban.tsx` and * `element-data-source.ts` changed in comment lines only and none of their @@ -4067,20 +4093,21 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * faces agree * while this map refused the key by name: the board's one query is * `dataSource.find(objectName, { $filter: queryFilter, $orderby: …, $top: - * resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT) })` - * (`plugin-kanban/src/ObjectKanban.tsx:705-715`, where `queryFilter` is - * `schema.filter` with its context tokens resolved at `:574-575` — + * resolveRowLimit(schema.limit, DEFAULT_KANBAN_FETCH_BATCH_SIZE) })` + * (`plugin-kanban/src/ObjectKanban.tsx:715-725`, where `queryFilter` is + * `schema.filter` with its context tokens resolved at `:584-585` — * objectui#10666; at `f8a9d0fb0` the member read `schema.filter` verbatim — - * the default `100` at `:88` — a REAL top-level `$top` since objectui#4025; + * the default `100` at `:97`, named `DEFAULT_KANBAN_LIMIT` until + * objectui#9853 — a REAL top-level `$top` since objectui#4025; * before that the cap sat under a `options` key no adapter read, and the * bare `??` became `resolveRowLimit` in objectui#9925, which drops and * reports a cap the contract refuses instead of sending it), * `OBJECT_KANBAN_DATA_SOURCE` maps `limit: 'limit'` - * (`plugin-kanban/src/index.tsx:506-510`), the type the board reads `schema` + * (`plugin-kanban/src/index.tsx:487-491`), the type the board reads `schema` * through is `ObjectKanbanSchema` — ⚠️ `KanbanSchema` was RETIRED on this hop * (maintainer ruling 2026-09-09) and `plugin-kanban/src/types.ts` no longer * declares the member at all — imported at `ObjectKanban.tsx:10` and - * declaring `limit?: number` at `packages/types/src/objectql.ts:4302`, + * declaring `limit?: number` at `packages/types/src/objectql.ts:4430`, * and `content/docs/plugins/plugin-kanban.mdx` * teaches it with a typed snippet (`limit: 250`) plus a Properties row. So * an author following the published docs wrote a node the save gate @@ -4173,8 +4200,19 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * React-host `kanban-ui` block). * * Measured at the objectui pin this repo builds against - * (`.objectui-sha` = `db11afd49`; re-measured there 2026-09-29 — - * `KanbanImpl.tsx` and `plugin-kanban/src/index.tsx` are byte-identical to + * (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — + * `KanbanImpl.tsx` is byte-identical to `db11afd49`, so `:621` and `:634` did + * not move and were re-read in place. objectui's own half of this retirement + * landed on this hop and changed the PATH, not the verdict: objectui#8285 + * stopped forwarding `quickAdd`, and objectui#11234 retired + * `ObjectKanbanSchema.onQuickAdd` and has `ObjectKanban` render an internal + * `KanbanBoardCore` that reads neither key off `schema` + * (`KanbanBoardCore.tsx:78`). So the spread MOVED `1614` -> `1641` with its + * line byte-identical but now feeds `KanbanBoardCore`, and the pass-through + * below sits only in the exported `KanbanRenderer`, MOVED `357-358` -> + * `345-346`, which `ObjectKanban` no longer mounts. At `db11afd49` + * (2026-09-29) `KanbanImpl.tsx` and `plugin-kanban/src/index.tsx` are + * byte-identical to * `dd3f7e1be` and `ObjectKanban.tsx` changed in one comment line (`:1367`), so * `:1614`, `:357-358`, `:621` and `:634` did not move and were re-read in * place. At `dd3f7e1be` (2026-09-28) @@ -4194,17 +4232,19 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * `313` -> `363`. All * three files were byte-identical across the hop onto `62597c588`, and all * four anchors were re-READ at `87af769e9` 2026-09-20, where each MOVED with - * its cited text byte-identical): the board - * forwards the key — - * `ObjectKanban.tsx:1614` spreads the authored bag into `KanbanRenderer`, + * its cited text byte-identical): through `db11afd49` the board + * forwarded the key — + * `ObjectKanban.tsx:1614` there spread the authored bag into `KanbanRenderer`, * which passes * `quickAdd={schema.quickAdd}` and `onQuickAdd={schema.onQuickAdd}` - * (`plugin-kanban/src/index.tsx:357-358`) — but the affordance is gated on + * (`plugin-kanban/src/index.tsx:357-358` there, `:345-346` at this pin) — but the affordance is gated on * BOTH (`KanbanImpl.tsx:621` and `:634`), and `onQuickAdd` is a * host-supplied FUNCTION that JSON cannot carry and no producer puts on an * `object-kanban` node. `ObjectKanban.tsx` names neither half of the pair - * (0 occurrences each, against 11 for the sibling `onCardClick` in the same - * file — re-counted at this pin, at `f8a9d0fb0` and at `62597c588`; this record said 6, which the identical + * in code (0 occurrences each, against 11 for the sibling `onCardClick` in the same + * file — re-counted at `db11afd49`, at `f8a9d0fb0` and at `62597c588`; at this + * pin 2 each, all four inside the two objectui#11234 comments that record + * the cut, and still 11 for `onCardClick`; this record said 6, which the identical * file at `87af769e9` does not reproduce either, so the control was * miscounted rather than moved, and the verdict rests on the 0). So the gate was permanently false and authoring the key was a * parse-clean no-op — the accepted-and-dropped class. @@ -4219,7 +4259,12 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * * The control itself is NOT withdrawn from the platform: it stays on * `kanban-ui`, the block a React host renders directly and can hand the - * runtime function to. Sources are stripped by the D2 conversion + * runtime function to. ⚠️ Re-read at `e420df310`: objectui retired the + * schema-only `kanban-ui` registration long before this pin (objectui#8257; + * 0 registrations at `dd3f7e1be`, `db11afd49` and this pin), and the pair + * now lives on the exported `KanbanRenderer` React component a host mounts + * directly (`plugin-kanban/src/index.tsx:345-346`), not on any block a + * document can name. Sources are stripped by the D2 conversion * `object-kanban-quick-add-removed` (a pure lossless delete — the key never * had an effect to preserve). */ From 37bbfafe6f3277f00cd48ba82baaf8605549f2c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:10:56 +0000 Subject: [PATCH 09/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 6) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.zod.ts | 137 ++++++++++++++++++-------- 1 file changed, 96 insertions(+), 41 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index f90c459b403..3a4d664501b 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -4285,7 +4285,17 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * element schemas). * * Measured at the pin this repo builds against (`.objectui-sha` = - * `db11afd49`; re-measured there 2026-09-29 — `ObjectKanban.tsx` changed in one + * `e420df310`; re-measured there 2026-09-30 — `ObjectKanban.tsx` changed on + * this hop, +40/-13: objectui#9853's renamed fetch-batch default, and + * objectui#11234's internal `KanbanBoardCore` with the comments that record + * the Quick Add cut, all of it re-mapped through the diff. Every anchor MOVED + * with its cited text byte-identical and none of the edits touches the + * `navigation` read or where it lands: the read `1261` -> `1282`, the + * hand-off `1269-1270` -> `1290-1291`, the overlay render `1519` -> `1540` + * and its `NavigationOverlay` `1535-1548` -> `1556-1569` by 21, and the card + * click `1638` -> `1665` by 27, the six extra lines being the + * `KanbanBoardCore` comment above the board it now clicks through. At + * `db11afd49` (2026-09-29) `ObjectKanban.tsx` changed in one * comment line only (`:1367`, a line-neutral comment re-spelling that no anchor * below cites), so every anchor below held unmoved and was re-read in place. At * `dd3f7e1be` (2026-09-28) `ObjectKanban.tsx` changed on @@ -4301,11 +4311,11 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * re-READ at `87af769e9` 2026-09-22 — every anchor re-derived from that * tree rather than carried, none of them at its `53ded82bf` number and one * of them no longer spelled the way this record quoted it): - * `ObjectKanban.tsx:1261` + * `ObjectKanban.tsx:1282` * reads `schema.navigation ?? { mode: 'drawer' }` and hands it to - * `useNavigationOverlay` (`:1269-1270`), whose result drives the card click - * (`:1638`) and the detail overlay (`:1519`, whose `NavigationOverlay` is - * `:1535-1548`) — on a STANDALONE board, with no enclosing view to resolve + * `useNavigationOverlay` (`:1290-1291`), whose result drives the card click + * (`:1665`) and the detail overlay (`:1540`, whose `NavigationOverlay` is + * `:1556-1569`) — on a STANDALONE board, with no enclosing view to resolve * a mode from. ⚠️ The `(schema as any)` cast this record used to quote is * GONE at this pin: the read is spelled `schema.navigation` and compiles * through `BaseSchema`'s index signature, which the comment above it @@ -4427,7 +4437,16 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `object-kanban`'s above. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `db11afd49`; re-measured there 2026-09-29 — `ObjectCalendar.tsx` changed + * `e420df310`; re-measured there 2026-09-30 — `ObjectCalendar.tsx` changed + * across the hop from `db11afd49` in comments only (+6/-3, objectui#11073), + * two of them inside the ledger comment, which now says `@objectstack/spec` + * 17.5.0 ships the `navigation` declaration that card waited on and that + * mirroring it is that card's next step, so the ledger grew `1008-1022` -> + * `1008-1025` and every anchor after it MOVED by 3 with its cited text + * byte-identical — the read `1023` -> `1026`, still spelled with the cast, + * the hand-off `1025-1026` -> `1028-1029`, the event click `1418` -> `1421`, + * the overlay render `1338` -> `1341`, its `NavigationOverlay` `1351-1364` -> + * `1354-1367`. At `db11afd49` (2026-09-29) `ObjectCalendar.tsx` changed * again across the hop from `dd3f7e1be` (+18/-11: objectui#11005's DST * wall-clock moves in `toStoredDateValue` and the month-grid handlers, and * comment re-citations, two of them inside the ledger comment, which now says @@ -4457,10 +4476,10 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `1218` -> `1237`, its `NavigationOverlay` `1228-1241` -> `1250-1263`. At * `87af769e9` (2026-09-22) none was at its `53ded82bf` number. Unlike its * `object-kanban` twin the cast is still spelled here, ledgered at - * `:1008-1022` as the one objectui#8651 left standing): `ObjectCalendar.tsx:1023` - * reads `(schema as any).navigation ?? { mode: 'drawer' }`, `:1025-1026` hands - * it to `useNavigationOverlay`, `:1418` fires it on an event click and - * `:1338` renders the overlay (its `NavigationOverlay` is `:1351-1364`) — + * `:1008-1025` as the one objectui#8651 left standing): `ObjectCalendar.tsx:1026` + * reads `(schema as any).navigation ?? { mode: 'drawer' }`, `:1028-1029` hands + * it to `useNavigationOverlay`, `:1421` fires it on an event click and + * `:1341` renders the overlay (its `NavigationOverlay` is `:1354-1367`) — * standalone, with no enclosing view. */ navigation: NavigationConfigSchema.optional() @@ -4722,9 +4741,26 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-map` (objectui `plugin-map/src/ObjectMap.tsx` plus the registry shell * `plugin-map/src/index.tsx`, read at the pin this repo builds against — - * `.objectui-sha` = `db11afd49`, re-measured there 2026-09-29: `ObjectMap.tsx`, - * `index.tsx` and `core/src/utils/record-source.ts` are all byte-identical to - * `dd3f7e1be`, so every anchor below holds unmoved and was re-checked in place. + * `.objectui-sha` = `e420df310`, re-measured there 2026-09-30: all three files + * changed across the hop from `db11afd49`, so every anchor was re-mapped + * through the diff and re-READ. `ObjectMap.tsx` (+16/-17, objectui#8348) + * changed only in the record-source docblock above `getDataConfig`, one line + * shorter, so every anchor from `:186` on MOVED up one line with its cited + * text byte-identical and `:155-162` did not move; `index.tsx` (+10/-0, + * objectui#8220) gained a comment above the registration and `filter` / + * `sort` inputs after `map`, whose own input MOVED `:89` -> `:97` + * byte-identical; `record-source.ts` (+38/-27, + * objectui#8348 moving the tree tags to `view-data`) MOVED the three rungs + * by 8, byte-identical. ⚠️ One CONTENT change, and it corrects this record: + * the carrier paragraph `:173-181` is rewritten (`:174-180` now), and says + * what objectui#9571 had already made true before `db11afd49` — the + * `SchemaRenderer` spread that carried an authored `data` array to this + * renderer as a React prop was stopped for object-arm blocks, so an authored + * array no longer draws through `SchemaRenderer` at all; only a HOST's own + * `data` prop does. The `data` entry below says so. At `db11afd49` + * (2026-09-29) `ObjectMap.tsx`, + * `index.tsx` and `core/src/utils/record-source.ts` were all byte-identical to + * `dd3f7e1be`, so every anchor held unmoved and was re-checked in place. * At `dd3f7e1be` (2026-09-28) all three of * `ObjectMap.tsx` (+193/-72), `index.tsx` (+62/-10) and * `core/src/utils/record-source.ts` (+27/-20) changed across the hop from @@ -4748,39 +4784,44 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * At `87af769e9` (2026-09-22) none was carried from `53ded82bf` either, and * one of them is a read that no longer exists rather than a number that * moved.) - * Read points per key: `data` (`:187-188` — `getDataConfig` is now that one + * Read points per key: `data` (`:186-187` — `getDataConfig` is now that one * `resolveRecordSourceConfig(schema, 'view-data')` call, rung 1 of the ruled - * record-source ladder, `core/src/utils/record-source.ts:299`. ⚠️ The + * record-source ladder, `core/src/utils/record-source.ts:307`. ⚠️ The * array-shorthand head this record used to cite beside it is GONE, deleted on - * objectui#8348 — `:155-162` records the deletion and `:173-181` records what - * survives it: an authored array still reaches this renderer, but through the - * React props channel, never through the ladder), `staticData` (rung 2, - * `record-source.ts:303`), `objectName` (rung 3, `record-source.ts:310`; also - * here at `:1184`, the `useNavigationOverlay` binding, and as a cache key at - * `:241`, `:244`, `:319` and `:365`. ⚠️ The dependency-key read this record + * objectui#8348 — `:155-162` records the deletion and `:174-180` records the + * carrier that used to survive it and no longer does: objectui#9571 stopped + * `SchemaRenderer` spreading an authored `data` array onto object-arm blocks + * as a React prop, so an authored array reaches this renderer neither through + * the ladder nor through the props channel; only a host's own `data` prop + * does. Until `db11afd49` this record said the props channel still carried + * it, reading an objectui docblock that was itself stale), + * `staticData` (rung 2, + * `record-source.ts:311`), `objectName` (rung 3, `record-source.ts:318`; also + * here at `:1183`, the `useNavigationOverlay` binding, and as a cache key at + * `:240`, `:243`, `:318` and `:364`. ⚠️ The dependency-key read this record * cited at `:965` is GONE at this pin: objectui#10664 replaced the metadata - * effect whose dependency list it was with `useSettledSchema` (`:796-799`), - * keyed on the ladder's `recordSourceObjectName` (`:770`), so the object is + * effect whose dependency list it was with `useSettledSchema` (`:795-798`), + * keyed on the ladder's `recordSourceObjectName` (`:769`), so the object is * still read there, only no longer through that list), - * `filter` (`:858`, read once through `useResolvedFilter`, which resolves its - * context tokens — objectui#10666 — and holds the result, which `:952` and - * `:1031` hand to `$filter` on the inline and the object fetch respectively; + * `filter` (`:857`, read once through `useResolvedFilter`, which resolves its + * context tokens — objectui#10666 — and holds the result, which `:951` and + * `:1030` hand to `$filter` on the inline and the object fetch respectively; * at `f8a9d0fb0` both handed `schema.filter` verbatim), - * `sort` (`:953` and `:1032`, through the shared `convertSortToQueryParams` sink - * to `$orderby`), `map` (`:406` — `getMapConfig` branch 1, the author face and + * `sort` (`:952` and `:1031`, through the shared `convertSortToQueryParams` sink + * to `$orderby`), `map` (`:405` — `getMapConfig` branch 1, the author face and * the registration's declared `{ name: 'map', type: 'object' }` input, - * `plugin-map/src/index.tsx:89`; the second copy this record cited at `:78` + * `plugin-map/src/index.tsx:97`; the second copy this record cited at `:78` * belonged to the bare `map` registration, retired on objectui#10393, so * `object-map` is now the one registration declaring it), `mapStyle` - * (`:401`, `schema.mapStyle || schema.map?.style`), `navigation` (`:1183`) and - * `enableClustering` (`:1199`). + * (`:400`, `schema.mapStyle || schema.map?.style`), `navigation` (`:1182`) and + * `enableClustering` (`:1198`). * * Measured and deliberately NOT declared: * * - the flat `map`-config spellings — the ObjectView/ListView flatten product, * ruled an internal transport form (objectui#5018). They get * {@link OBJECT_MAP_FLAT_CONFIG_GUIDANCE}'s wrong-layer prescription. - * - `style`. `ObjectMap.tsx:316`, inside `warnOnTopLevelStyleUrl` (`:310`), + * - `style`. `ObjectMap.tsx:315`, inside `warnOnTopLevelStyleUrl` (`:309`), * reads it only to say it is NOT consumed as a map style (objectui#5017): * it is `BaseSchema.style`, the node's inline CSS * record, and `COMPONENT_NODE_KEYS` above already sends it back to the node. @@ -4790,11 +4831,11 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * * VALUE posture for `map`: {@link ListMapConfigSchema}, this repo's own block — * the later value ratchet the previous posture here deferred, taken now that the - * one-key gap is closed. `:409` validates the authored block against objectui's + * one-key gap is closed. `:408` validates the authored block against objectui's * `ObjectMapConfigSchema` and this block declares the same eight keys, `style` * included; until that declaration landed the door had to stay `z.unknown()`, * because pointing it at a schema missing `style` would have refused a value - * `getMapConfig` honours at `:401` (`schema.mapStyle || schema.map?.style`). + * `getMapConfig` honours at `:400` (`schema.mapStyle || schema.map?.style`). * `mapStyle` above is unaffected: it stays the component-level spelling read * FIRST, and is not a member of the config block. */ @@ -4832,7 +4873,13 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Base query filter — the `ViewFilterRule` ARRAY form, the one filter * orthography every `filter` door in this map shares (ui#6206-B reaching the * `object-*` family: #15449, decision batch #55, option A). Measured at the - * pin this repo builds against (`.objectui-sha` = `db11afd49`, re-READ there + * pin this repo builds against (`.objectui-sha` = `e420df310`, re-READ there + * 2026-09-30: `ObjectMap.tsx` changed only in the record-source docblock + * above both anchors, one line shorter (objectui#8348), so each MOVED up one + * line with its text byte-identical, `:858` -> `:857`, `:952` -> `:951` and + * `:1031` -> `:1030`; and objectui#8220 now publishes a `filter` input on the + * `object-map` registration in this same rule-array form, `type: 'array'` + * (`plugin-map/src/index.tsx:100`). At `db11afd49`, re-READ there * 2026-09-29: `ObjectMap.tsx` is byte-identical to `dd3f7e1be`, so both anchors * held unmoved; at `dd3f7e1be`, re-READ there * 2026-09-28 — `ObjectMap.tsx` changed on this hop, and so did the CONTENT @@ -4843,8 +4890,8 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * `schema.filter` verbatim. At `f8a9d0fb0`, byte-identical to `62597c588` * and re-READ there 2026-09-23, both anchors had MOVED 19 lines from * `:814` / `:895` with their text byte-identical, that hop changing only a - * docblock and a dev warning above them): `ObjectMap.tsx:858` reads - * `schema.filter`, and `:952` and `:1031` hand it, tokens resolved, to + * docblock and a dev warning above them): `ObjectMap.tsx:857` reads + * `schema.filter`, and `:951` and `:1030` hand it, tokens resolved, to * `$filter` on the inline and the object fetch, where the adapter lowers a * rule array exactly as it does for the kanban and the calendar. The record * form is refused at `filter`. @@ -4860,7 +4907,14 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * Marker order — the `SortItem` ARRAY form, the one sort orthography every * DECLARED `sort` door on this platform carries (objectui#8221, decision batch * #77, option B). Measured at the pin this repo builds against - * (`.objectui-sha` = `db11afd49`, re-READ there 2026-09-29 — `ObjectMap.tsx` + * (`.objectui-sha` = `e420df310`, re-READ there 2026-09-30 — `ObjectMap.tsx` + * changed only in the record-source docblock above both anchors, one line + * shorter (objectui#8348), so both MOVED up one line with their text + * byte-identical, `:953` -> `:952` and `:1032` -> `:1031`; and + * `plugin-map/src/index.tsx` CHANGED what this record says about it: + * objectui#8220 declared a `sort` input on the `object-map` registration, + * `type: 'array'` in this same `[{ field, order }]` form (`:101`). At + * `db11afd49`, re-READ there 2026-09-29 — `ObjectMap.tsx` * and `plugin-map/src/index.tsx` are byte-identical to `dd3f7e1be`, so both * anchors held unmoved; at `dd3f7e1be`, 2026-09-28, `ObjectMap.tsx` * and `plugin-map/src/index.tsx` both changed on this hop; both anchors @@ -4870,12 +4924,13 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * retired (objectui#10393), still declares no `sort`. At `f8a9d0fb0` both files were byte-identical * to `62597c588`, re-READ there 2026-09-23 — both anchors had MOVED 19 * lines from `:815` / `:896` with their text byte-identical): - * `ObjectMap.tsx:953` and `:1032` + * `ObjectMap.tsx:952` and `:1031` * hand `schema.sort` to the shared `convertSortToQueryParams` sink as the * fetch's `$orderby` — the same sink `object-grid` and `object-calendar` * declare against, so the legacy string clause is refused here for the same - * ruling. `plugin-map/src/index.tsx` declares no `sort` input at all, so - * nothing on the registry side moves. + * ruling. Through `db11afd49` `plugin-map/src/index.tsx` declared no `sort` + * input at all; since objectui#8220 it declares one in this array form + * (`:101`), so the registry and this door agree. */ sort: z.array(SortItemSchema).optional() .describe('Marker order for the fetched records — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array`'), From 005ce8c6ba1e92b640cfea2b7863ee7f6ad1e55c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:12:49 +0000 Subject: [PATCH 10/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 7) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.zod.ts | 153 +++++++++++++++----------- 1 file changed, 91 insertions(+), 62 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 3a4d664501b..6fd6d711af1 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -5002,8 +5002,18 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-gantt` (objectui `plugin-gantt/src/ObjectGantt.tsx` plus the registry * shell `plugin-gantt/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `db11afd49`, re-measured there 2026-09-29: - * `plugin-gantt/src/index.tsx` and `record-source.ts` are byte-identical to + * — `.objectui-sha` = `e420df310`, re-measured there 2026-09-30: all three + * files changed across the hop from `db11afd49`, and every anchor was + * re-mapped through the diff and re-READ; NONE changed content. `ObjectGantt.tsx` + * +85/-26 (objectui#11141's inclusive date-only end, objectui#8348's + * record-source ladder, objectui#11070's `reference` spelling), every anchor + * MOVING with its cited text byte-identical — by 47 through `getGanttConfig`, + * by 49 through the reload, by 51 through the layout key and by 59 after it; + * `plugin-gantt/src/index.tsx` +10/-0 (objectui#8220: a comment and `filter` / + * `sort` inputs, in the array forms this row declares), the `gantt` input + * MOVING `:137` -> `:145`; `record-source.ts` +38/-27, the three rungs MOVING + * by 8. At `db11afd49` (2026-09-29) + * `plugin-gantt/src/index.tsx` and `record-source.ts` were byte-identical to * `dd3f7e1be`, and `ObjectGantt.tsx` changed (+14/-10: objectui#10866's * zoned-chart DST day read and write through `invertTo` / `invertFrom`, and * comment re-citations), the first insertion at `:391` and every later anchor @@ -5031,46 +5041,46 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * every number was re-derived from that tree, and the `label` one moved far * enough that the retired reading now lands in an unrelated callback.) * Read points per key: `data` (`resolveRecordSourceConfig` at - * `:673` — rung 1, `core/src/utils/record-source.ts:299`), `staticData` (rung 2, - * `record-source.ts:303`), `objectName` (rung 3; also `:1736` and `:1931` here), - * `filter` (`:869`, read once through `useResolvedFilter`, which resolves its - * context tokens — objectui#10666 — and `:1014` sends the held result to - * `$filter`; at `f8a9d0fb0` `:869` sent it verbatim), `sort` (`:1015`, through - * `convertSortToQueryParams` to `$orderby`), `gantt` (`:563-565` — + * `:722` — rung 1, `core/src/utils/record-source.ts:307`), `staticData` (rung 2, + * `record-source.ts:311`), `objectName` (rung 3; also `:1787` and `:1990` here), + * `filter` (`:918`, read once through `useResolvedFilter`, which resolves its + * context tokens — objectui#10666 — and `:1063` sends the held result to + * `$filter`; at `f8a9d0fb0` it was sent verbatim), `sort` (`:1064`, through + * `convertSortToQueryParams` to `$orderby`), `gantt` (`:610-612` — * `getGanttConfig` branch 1, the author face and the registration's declared - * `{ name: 'gantt', type: 'object' }` input (`plugin-gantt/src/index.tsx:137`), + * `{ name: 'gantt', type: 'object' }` input (`plugin-gantt/src/index.tsx:145`), * validated there against this - * repo's own {@link GanttConfigSchema}), `navigation` (`:1864`), `label` - * (`:2446`, resolved through `resolveInlineI18nLabel` for the export file + * repo's own {@link GanttConfigSchema}), `navigation` (`:1923`), `label` + * (`:2505`, resolved through `resolveInlineI18nLabel` for the export file * name — this record said `resolveI18nLabel`, which the line did not read at * `f8a9d0fb0` either — * ⚠️ the retired reading `:1849` was the comment ABOVE that chain and at this * pin `87af769e9` was a BLANK line (nothing cites `:1849` now), two above a dependency-delete callback's own comment, * so it is re-READ here, not re-pointed), - * `skipWeekends` (`:1582`), `holidays` (`:1583`), `persistLayout` (`:1734`), - * `viewName` (`:1736`), `markers` (`:2401`), `criticalPath` (`:2404`), - * `showBaselines` (`:2407`), `readOnly` (`:2243` and `:2408`) and - * `mobileReadOnly` (`:2409`). + * `skipWeekends` (`:1633`), `holidays` (`:1634`), `persistLayout` (`:1785`), + * `viewName` (`:1787`), `markers` (`:2460`), `criticalPath` (`:2463`), + * `showBaselines` (`:2466`), `readOnly` (`:2302` and `:2467`) and + * `mobileReadOnly` (`:2468`). * * Measured and deliberately NOT declared: the flat `GanttConfig` spellings (the * flatten product — {@link OBJECT_GANTT_FLAT_CONFIG_GUIDANCE}); `title`, which * this renderer never reads (the export-name chain is `gantt.exportFileName` → - * `label` → the OBJECT's label → `objectName`, `:2444-2450`); a row cap — the + * `label` → the OBJECT's label → `objectName`, `:2503-2509`); a row cap — the * reload's `$top` is the platform ceiling, spread in as core's - * `nonGridRowCeilingQuery()` (`:1025`: one probe row past the ceiling, which + * `nonGridRowCeilingQuery()` (`:1074`: one probe row past the ceiling, which * `applyNonGridRowCeiling` slices back off; at `f8a9d0fb0` a bare * `$top: NON_GRID_ROW_CEILING_TOP`, until objectui#7508 homed the ceiling in - * core) and the renderer's own comment marks it "⛔ Not authorable" (`:1023`); and + * core) and the renderer's own comment marks it "⛔ Not authorable" (`:1072`); and * the `onTaskClick` / `onRowClick` / `onBeforeTaskUpdate` callbacks, which are * host props. ⚠️ New at `f8a9d0fb0` and recorded, not ruled: objectui#10250 * gave the renderer two more reads, `search` and `searchableFields` - * (`:836-839`, sent as `$search` / `$searchFields` at `:1029-1034`), which + * (`:885-888`, sent as `$search` / `$searchFields` at `:1078-1083`), which * `ListView.tsx` writes onto the node it generates from its toolbar Search * box — a host-generated key pair in the same position as the flatten * product, and not declared here. * * VALUE posture: `gantt` is the one config block in this family whose value - * contract is already the SPEC's — `ObjectGantt.tsx:565` validates it against + * contract is already the SPEC's — `ObjectGantt.tsx:612` validates it against * `GanttConfigSchema` imported from `@objectstack/spec/ui` — so the read point * names the schema and this door takes it rather than `z.unknown()`. The * scalars below are read as their coercions say: `!!schema.readOnly`, @@ -5145,10 +5155,15 @@ export type ObjectGanttPropsParsed = z.infer; /** * The flat `TreeConfig` spellings `getTreeConfig` reads ahead of the `tree` * block (`ObjectTree.tsx:281-294`) and that `ObjectView` / `ListView` EMIT when - * they flatten `options.tree` (`ListView.tsx:3668-3687`, `case 'tree'`: the + * they flatten `options.tree` (`ListView.tsx:3722-3741`, `case 'tree'`: the * product carries these keys, the EFFECTIVE `filter` objectui#10250 added, and * NO `tree` key). Both halves re-READ at the - * pin this repo builds against (`.objectui-sha` = `db11afd49`) on 2026-09-29 — + * pin this repo builds against (`.objectui-sha` = `e420df310`) on 2026-09-30 — + * `ObjectTree.tsx` changed (+29/-26, objectui#8348's record-source arm and the + * host-data read below it), none of it inside `281-294` or `233-254`, which did + * not move; `ListView.tsx` changed above its `case 'tree'` arm (objectui#11021 + * among others), which MOVED byte-identical `3668-3687` -> `3722-3741`, so the + * flat key set each reads or emits did not move. At `db11afd49`, 2026-09-29 — * `ObjectTree.tsx` changed in one comment line (`:1054`) and `ListView.tsx` in * comment citations and its region-aria read, none of it inside these ranges, so * `281-294` and `3668-3687` did not move; measured at `dd3f7e1be` on @@ -5168,7 +5183,7 @@ export type ObjectGanttPropsParsed = z.infer; * * `titleField` is in the set although no `tree` block key is spelled that way: * `ListView`'s flatten resolves `treeCfg.titleField` into `labelField` before - * emitting (`ListView.tsx:3682`, `labelField: treeCfg.labelField || + * emitting (`ListView.tsx:3736`, `labelField: treeCfg.labelField || * treeCfg.titleField || 'name'`), so the author's intent is always the block's * `labelField`, and the prescription below says so. * @@ -5197,10 +5212,18 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ /** * `object-tree` (objectui `plugin-tree/src/ObjectTree.tsx` plus the registry * shell `plugin-tree/src/index.tsx`, read at the pin this repo builds against - * — `.objectui-sha` = `db11afd49`, re-measured there 2026-09-29: - * `plugin-tree/src/index.tsx` and `core/src/utils/record-source.ts` are - * byte-identical to `dd3f7e1be` and `ObjectTree.tsx` changed in one comment line - * (`:1054`), so every anchor below held unmoved and was re-read in place. At + * — `.objectui-sha` = `e420df310`, re-measured there 2026-09-30: + * `plugin-tree/src/index.tsx` is byte-identical to `db11afd49`; + * `core/src/utils/record-source.ts` changed (+38/-27) and its three rungs MOVED + * by 8 with their text byte-identical; `ObjectTree.tsx` changed (+29/-26, + * objectui#8348), and TWO anchors changed CONTENT, both toward this row, and + * say so where they are cited: the rung-1 call now passes `'view-data'`, and + * the host-data read no longer reads the authored `schema.data`; every other + * anchor MOVED by 2 or 3 with its cited text byte-identical. At `db11afd49` + * (2026-09-29) `plugin-tree/src/index.tsx` and + * `core/src/utils/record-source.ts` were byte-identical to `dd3f7e1be` and + * `ObjectTree.tsx` changed in one comment line (`:1054`), so every anchor held + * unmoved and was re-read in place. At * `dd3f7e1be` (2026-09-28): * `plugin-tree/src/index.tsx` is byte-identical to `f8a9d0fb0` (`git diff * --quiet`); `core/src/utils/record-source.ts` changed only in docblocks and @@ -5223,25 +5246,27 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * one was re-derived from that tree, and the ladder call changed in CONTENT * as well as position.) * Read points per key: `data` (`resolveRecordSourceConfig` at - * `:632` — rung 1, `core/src/utils/record-source.ts:299`, which returns the - * authored value VERBATIM; that ONE site is the whole support - * for the arm this row declares. ⚠️ The ARM this renderer passes is - * `'undeclared'`, not the `'view-data'` its siblings pass, so rung 1 honours - * any truthy value there — WIDER than this row, which is the harmless - * direction: the bare array this door refuses is one the renderer would have - * taken, never the reverse), `staticData` (rung 2, - * `record-source.ts:303`), `objectName` (rung 3; also `:1023`, `:1074` and - * `:1146` here, each behind `resolveRecordSourceObjectName`, plus the - * parent-field detection at `:955-956`), - * `filter` (`:737`, read once through `useResolvedFilter`, which resolves its - * context tokens — objectui#10666 — and holds the result: `:829` sends it to - * `$filter` on the object fetch and `:913` on the inline `value` provider's + * `:634` — rung 1, `core/src/utils/record-source.ts:307`, which returns the + * authored value VERBATIM once it is on the arm; that ONE site is the whole + * support for the arm this row declares. ⚠️ At this pin the ARM this renderer + * passes is `'view-data'`, the one its siblings pass (objectui#8348), so a + * bare array under `data` is not a record source there and the ladder falls + * through to `staticData`, then `objectName`: renderer and row now draw the + * same line. Through `db11afd49` it passed `'undeclared'`, which honoured any + * truthy value — WIDER than this row, the harmless direction), + * `staticData` (rung 2, + * `record-source.ts:311`), `objectName` (rung 3; also `:1026`, `:1077` and + * `:1149` here, each behind `resolveRecordSourceObjectName`, plus the + * parent-field detection at `:958-959`), + * `filter` (`:739`, read once through `useResolvedFilter`, which resolves its + * context tokens — objectui#10666 — and holds the result: `:831` sends it to + * `$filter` on the object fetch and `:916` on the inline `value` provider's * `ValueDataSource` query, objectui#9136's; at `f8a9d0fb0` both sent * `schema.filter` verbatim, at `:753` and `:837`), `tree` (`:282`, the * nested config block `getTreeConfig` (`:281-294`) reads, and the * registration's declared `{ name: 'tree', type: 'object' }` input - * (`plugin-tree/src/index.tsx:27-31`)) and `navigation` (`:1060`, handed to - * `useNavigationOverlay` at `:1043`). + * (`plugin-tree/src/index.tsx:27-31`)) and `navigation` (`:1063`, handed to + * `useNavigationOverlay` at `:1046`). * * ⚠️ `data` IS declared here, and that is the measurement, not a family * symmetry. objectui#9234 left this block's rung-1 read marked `undeclared` @@ -5254,36 +5279,40 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * resolving for this block means, and the mirror is the face that has to * follow. * - * ⛔ `:862` is NOT a second site for the object arm, and citing it as one would - * be citing a read of the opposite SHAPE: `(rest as any).data ?? schema.data` - * (the cast on `schema` went away with objectui#8655; the one on `rest` stays) - * is gated by `Array.isArray(passed)` on the very next line, so it - * honours only the bare-ARRAY shorthand this row REFUSES — the same shorthand - * `object-map` measures and declines one section up. One ladder site is - * sufficient, and `:632` is it. + * ⛔ `:865` is NOT a second site for the object arm, and at this pin it is not + * a read of the authored key at all: it is `(rest as any).data`, gated by + * `Array.isArray(passed)` on the next line — the rows a HOST hands down as the + * `data` React prop. objectui#8348 dropped the `?? schema.data` fallback that + * line carried through `db11afd49` (`:862` there), the one reader that let an + * authored bare array draw rows whatever the ladder said, and objectui#9571 + * had already stopped `SchemaRenderer` delivering the authored key as that + * prop. One ladder site is sufficient, and `:634` is it. * * Measured and deliberately NOT declared: the flat `TreeConfig` spellings - * ({@link OBJECT_TREE_FLAT_CONFIG_GUIDANCE}); the bare-array `data` shorthand - * `:862-863` accepts, which `ViewData` cannot publish (a discriminated union - * over OBJECT variants) and for which `staticData` is this block's declared - * door; `sort` — this renderer's fetch - * (`:828-840`) carries `$filter`, `$top` and `$expand` and NO `$orderby`, the - * inline query (`:912-921`) carries `$filter` and `$top` only, and + * ({@link OBJECT_TREE_FLAT_CONFIG_GUIDANCE}); the bare-array `data` shorthand, + * which `ViewData` cannot publish (a discriminated union over OBJECT variants), + * for which `staticData` is this block's declared door, and which the renderer + * itself no longer accepts from an authored node at this pin (see `:865` + * above; `:862-863` accepted it through `db11afd49`); `sort` — this renderer's fetch + * (`:830-842`) carries `$filter`, `$top` and `$expand` and NO `$orderby`, the + * inline query (`:915-924`) carries `$filter` and `$top` only, and * nothing else reads an order, so declaring one would publish a key with no read * site; a row cap, for the same reason `object-gantt` declares none (the `$top` * is the platform ceiling, spread in as core's `nonGridRowCeilingQuery()` at - * `:838` and again at `:920` — a bare `$top: NON_GRID_ROW_CEILING_TOP` at + * `:840` and again at `:923` — a bare `$top: NON_GRID_ROW_CEILING_TOP` at * `f8a9d0fb0`, until objectui#7508 — marked "⛔ Not - * authorable" at `:837`); and `filter.tree`, the legacy stash — ⚠️ no longer + * authorable" at `:839`); and `filter.tree`, the legacy stash — ⚠️ no longer * read since `f8a9d0fb0`: objectui#9549 deleted that arm, so `getTreeConfig` * (`:282`) takes the block from `tree` alone — which was a shape to stop * writing and is not a key to declare. * - * This is also the one block of the three whose type is absent from the tracked - * `sdui.manifest.json`, so `check:react-declaration-parity` reports it as - * missing from the registry rather than comparing it — the derivation above is - * from the renderer's sources at the pin either way, which is what #7751's - * method asks for. + * This was also the one block of the three whose type was absent from the + * tracked `sdui.manifest.json`, so `check:react-declaration-parity` reported + * it as missing from the registry rather than comparing it. ⚠️ Re-read at + * `e420df310`: the manifest regenerated at this pin carries an `object-tree` + * entry, as the one at `db11afd49` already did. The derivation above is from + * the renderer's sources at the pin either way, which is what #7751's method + * asks for. * * VALUE posture for `tree`: {@link TreeConfigSchema}, this repo's own block — * #15469 closed it against unknown keys on exactly this measurement From 89a578bb1f74505b1b3dfa239a5fe77dbf27109a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:14:34 +0000 Subject: [PATCH 11/17] wip(spec): re-measure objectui pin citations at e420df310f5b (batch 8: component.zod.ts done) Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/ui/component.zod.ts | 58 +++++++++++++++++++++------ 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 6fd6d711af1..8391292d16b 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -5334,7 +5334,11 @@ export const ObjectTreePropsSchema = lazySchema(() => strictObject({ * holds for them because each registers through `ElementDataSourceGate`, * which lowers the spec binding onto `objectName` before the renderer sees * the node. `plugin-tree/src/index.tsx` does NOT: at the pin this repo - * builds against (`.objectui-sha` = `db11afd49`, re-COUNTED there 2026-09-29 by + * builds against (`.objectui-sha` = `e420df310`, re-COUNTED there 2026-09-30 by + * the same method — 3 each again and 0 for the tree; `plugin-map` and + * `plugin-gantt`'s `src/index.tsx` changed on the hop (objectui#8220's + * `filter` / `sort` inputs) and still read 3, the tree's is byte-identical; + * re-COUNTED at `db11afd49` 2026-09-29 by * the same method — 3 each again and 0 for the tree; * `plugin-grid/src/index.tsx` changed on the hop and still reads 3; re-COUNTED * at `dd3f7e1be` @@ -5428,8 +5432,18 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * `object-timeline` (objectui `plugin-timeline/src/ObjectTimeline.tsx`, the * presentational `plugin-timeline/src/renderer.tsx` it composes into, and the * registry shell `plugin-timeline/src/index.tsx` — all read at the pin this - * repo builds against (`.objectui-sha` = `db11afd49`), re-measured there - * 2026-09-29: `ObjectTimeline.tsx` and `index.tsx` are byte-identical to + * repo builds against (`.objectui-sha` = `e420df310`), re-measured there + * 2026-09-30: `ObjectTimeline.tsx` is byte-identical to `db11afd49`, so every + * anchor in it held unmoved and was re-read in place; `index.tsx` changed + * (+17/-0, objectui#8220: `filter` / `sort` inputs on both registrations, in + * the array forms this row declares, and their shared descriptions above the + * first), the `variant` input MOVING `361-362` -> `374-375` byte-identical and + * `:333` not moving; and `renderer.tsx` changed (+83/-8, objectui#11141's + * inclusive date-only end), its three anchors MOVING with their cited text + * byte-identical, `1497` -> `1569`, `1794` -> `1869`, `1722-1737` -> + * `1794-1809`; its schema-key read set (`schema.*`) is unchanged. At + * `db11afd49` (2026-09-29) `ObjectTimeline.tsx` and `index.tsx` were + * byte-identical to * `dd3f7e1be`, and `renderer.tsx` changed hard (+387/-177: objectui#11079's * continuous gantt axis and objectui `9e6619ffa`'s date-only day in every zone), * its three anchors MOVING with their cited text byte-identical, `1295` -> @@ -5496,11 +5510,11 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * `variant` (`:809`) and `navigation` (`:753-756` → `:888` → `:943-944`). * Four more are read by the presentational renderer off the schema this * component spreads into it (`effectiveSchema`, `:872-891`): `dateFormat` - * (`plugin-timeline/src/renderer.tsx:1497`, every variant — its number was the + * (`plugin-timeline/src/renderer.tsx:1569`, every variant — its number was the * same at `53ded82bf` and `87af769e9`, as `plugin-timeline/src/index.tsx:333`'s * also was, which is why the number alone is never the reading) and the gantt - * trio `rowLabel` (`plugin-timeline/src/renderer.tsx:1794`), `minDate` / - * `maxDate` (`plugin-timeline/src/renderer.tsx:1722-1737`). ⚠️ All three name their + * trio `rowLabel` (`plugin-timeline/src/renderer.tsx:1869`), `minDate` / + * `maxDate` (`plugin-timeline/src/renderer.tsx:1794-1809`). ⚠️ All three name their * package because objectui has a second `renderer.tsx` (in `plugin-chatbot`): * a suffix that matches two files names neither, and the gate drops it from * the population rather than guessing. @@ -5525,7 +5539,7 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * reference, for the reason the ruling gives. * * ⚠️ `variant: 'gantt'` is declared because the registration declares it - * (`plugin-timeline/src/index.tsx:361-362`) and the renderer reads it — but the + * (`plugin-timeline/src/index.tsx:374-375`) and the renderer reads it — but the * OBJECT-BOUND composed path REFUSES it loudly (objectui#6655, * `ObjectTimeline.tsx:809-811`: * `:809` gates on `!hasAuthoredItems && schema.variant === 'gantt'` and `:811` @@ -5580,7 +5594,13 @@ export const ObjectTimelinePropsSchema = lazySchema(() => strictObject({ * first publishes a refusal for a key the renderer honours. * * Read points at the pin this repo builds against (`.objectui-sha` = - * `db11afd49`, re-READ there 2026-09-29 — the file is byte-identical to + * `e420df310`, re-READ there 2026-09-30 — the file is byte-identical to + * `db11afd49`, so every anchor held unmoved; and the prop channel above + * still carries the key here: `SchemaRenderer` strips an authored `data` + * prop only on the types `core`'s `RECORD_SOURCE_DATA_ARM_BY_TYPE` puts on + * the `view-data` arm (objectui#9571), and `object-timeline` is not listed, + * so it stays `undeclared` and keeps the seat. At `db11afd49`, 2026-09-29, + * the file was byte-identical to * `dd3f7e1be`, so every anchor held unmoved; at `dd3f7e1be`, 2026-09-28, the * file changed across the hop * from `f8a9d0fb0` (+129/-26), so every anchor was re-mapped through the @@ -5792,8 +5812,12 @@ export const ComponentPropsMap = { // set from this map's keys), on the three-part evidence that vocabulary's // string-arm ledger asks of a type admitted without an enum member — all // measured at the pin this repo builds against (`.objectui-sha` = - // `db11afd49`; re-measured there 2026-09-29, all four files byte-identical to - // `dd3f7e1be`): + // `e420df310`; re-measured there 2026-09-30: `data-list.tsx`, + // `public-blocks.ts` and `block-types.ts` byte-identical to `db11afd49`, and + // `block-config.ts` changed (+12/-3) outside the two inspectors — two + // line-neutral one-line edits above them and an insertion below — so every + // anchor below held unmoved; at `db11afd49`, 2026-09-29, all four files were + // byte-identical to `dd3f7e1be`): // - registration: `@object-ui/components` registers both in the `element` // namespace (`components/src/renderers/basic/data-list.tsx:75`, `:205`); // - publication: both are `PUBLIC_BLOCKS` members @@ -5835,7 +5859,13 @@ export const ComponentPropsMap = { // authority for map and gantt while tree's rung-1 `data` read stayed // undeclared on every face. Key sets measured from the renderers' read // points at the pin this repo builds against (`.objectui-sha` = - // `db11afd49`), all three re-measured there 2026-09-29 (`ObjectMap.tsx` + // `e420df310`), all three re-measured there 2026-09-30 (all three renderers + // changed and were re-READ: `ObjectMap.tsx` in one docblock, `ObjectGantt.tsx` + // in objectui#11141's inclusive date-only end, and `ObjectTree.tsx`, whose + // rung-1 call now passes the `view-data` arm this row declares and whose + // host-data read stopped reading the authored `schema.data`, objectui#8348; + // the `schema.*` read set of each is unchanged, so no declared key set moved + // and no read point died. At `db11afd49`, 2026-09-29: `ObjectMap.tsx` // byte-identical to `dd3f7e1be`, `ObjectTree.tsx` changed in one comment line, // `ObjectGantt.tsx` in its date-only DST handling and citations; the `schema.*` // read set of each is unchanged, so no declared key set moved and no read point @@ -5863,7 +5893,11 @@ export const ComponentPropsMap = { // it — so `object-timeline` was unjudged in both directions, a real key and // a typo riding through alike. Key set measured from the renderer's read // points at the pin this repo builds against (`.objectui-sha` = - // `db11afd49`), re-measured there 2026-09-29 (`ObjectTimeline.tsx` and + // `e420df310`), re-measured there 2026-09-30 (`ObjectTimeline.tsx` + // byte-identical to `db11afd49`; `index.tsx` gained `filter` / `sort` inputs, + // objectui#8220, and `renderer.tsx` changed, objectui#11141, both re-READ, + // the `schema.*` read set unchanged and every anchor moved with its text + // byte-identical. At `db11afd49`, 2026-09-29: `ObjectTimeline.tsx` and // `index.tsx` byte-identical to `dd3f7e1be`; `renderer.tsx` changed and was // re-READ, its `schema.*` read set unchanged and its three anchors moved with // their text byte-identical. At `dd3f7e1be`, 2026-09-28, `index.tsx` From c0032f3516a819ece926455aab8b0ec1cd4b3e8a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:15:04 +0000 Subject: [PATCH 12/17] chore(spec): regenerate the migration registry for the re-measured pin citations Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- packages/spec/src/migrations/registry.ts | 42 +++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 3f4d9bba6f0..a4189a11d2a 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -12619,13 +12619,13 @@ const step18: MigrationStep = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — names ' + + 'against, `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names ' + 'neither def and neither key: all thirteen exports of plugin-lifecycle-advanced.zod.ts and ' - + 'the string debounceDelay each occur 0 times across its 9546 tracked files (0 across the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 9800 tracked files (0 across the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be and to 14704 and 5545 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49 and to 15352 and 6024 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' @@ -12830,10 +12830,10 @@ const step18: MigrationStep = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204`, re-read from this tree — ' + + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '9546 tracked files, against lit controls timeout 1197, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '9800 tracked files, against lit controls timeout 1293, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', @@ -13069,10 +13069,10 @@ const step18: MigrationStep = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — spells ' + + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 9546 tracked files, against lit controls `useState` 2449 and `timeout` 1197 on ' - + 'the same corpus (all four 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 9800 tracked files, against lit controls `useState` 2464 and `timeout` 1293 on ' + + 'the same corpus (all four 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: @@ -16730,10 +16730,11 @@ const step18: MigrationStep = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 9546 tracked ' - + 'files at that sha, against lit controls window 3772, timeout 1197, period 228, ' - + 'interval 196 and metrics 341 on that same corpus and sha (0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 9800 tracked ' + + 'files at that sha, against lit controls window 3873, timeout 1293, period 233, ' + + 'interval 196 and metrics 352 on that same corpus and sha (0 across 9546, against 3772 / ' + + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + '1096 / 171 / 179 / 326, at f8a9d0fb0, and 0 across 8303, against 3526 / 1086 / 170 / 179 / ' + '324, at 62597c588), so no pin bump is owed. ' + 'ADR-0087.', @@ -16946,12 +16947,13 @@ const step18: MigrationStep = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 9546 files ' + + 'pinned objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 9800 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 14704 hits for the bare token objectstack, ' - + 'and 5545 for the package specifier @objectstack/spec (at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + + 'two lit controls on that same corpus and sha: 15352 hits for the bare token objectstack, ' + + 'and 6024 for the package specifier @objectstack/spec (at db11afd49: 0 across 9546, Span 486, ' + + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + 'Span 488, 13347 and 5123; at 62597c588: 0 across 8303, Span 486, 13125 and 5043).', acceptanceCriteria: 'Every author and reader of an OpenTelemetryCompatibility spells exporter.timeoutMs, ' @@ -17058,9 +17060,9 @@ const step18: MigrationStep = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `db11afd4967cd9d39381c5e21dc2deec9d706204` — spells it 0 ' - + 'times across 9546 tracked files, against lit controls `TTL` 181 and `tenant` 1200 on the ' - + 'same corpus (0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells it 0 ' + + 'times across 9800 tracked files, against lit controls `TTL` 181 and `tenant` 1235 on the ' + + 'same corpus (0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' From 2e2d11ebdb52db24159f18f38a93a45338d81411 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:15:23 +0000 Subject: [PATCH 13/17] chore(changeset): @objectstack/spec patch for the re-measured objectui pin citations Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .changeset/objectui-pin-citations-e420df310f5b.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/objectui-pin-citations-e420df310f5b.md diff --git a/.changeset/objectui-pin-citations-e420df310f5b.md b/.changeset/objectui-pin-citations-e420df310f5b.md new file mode 100644 index 00000000000..43c54fac628 --- /dev/null +++ b/.changeset/objectui-pin-citations-e420df310f5b.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `e420df310f5b`. + +Clause-②: no + +Several records were corrected rather than moved, because objectui changed what they describe on this hop: the four `action:*` registrations now publish the keys their rows declare (objectui#11168), `action:group` and `action:menu` now apply a host-evaluated `disabled` that their rows do not declare (objectui#11182, recorded, not declared), the `object-kanban` default row cap is named `DEFAULT_KANBAN_FETCH_BATCH_SIZE` (objectui#9853), the board no longer forwards `quickAdd` (objectui#8285, objectui#11234), the `object-tree` ladder now judges `data` on the `view-data` arm its row declares (objectui#8348), and `object-map` / `object-gantt` / `object-timeline` now publish `filter` and `sort` inputs (objectui#8220). Two older statements that were already stale are also corrected: an authored `data` array on `object-map` no longer reaches the renderer through the React props channel (objectui#9571), and the `quickAdd` retirement record now notes that the schema-only `kanban-ui` block it points to is retired in objectui (objectui#8257). No key, default, enum member or export moves. From 4e10a19d639c6ce60808570a8c80dfe545134ea0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:15:33 +0000 Subject: [PATCH 14/17] chore(changeset): name the two action:button keys objectui leaves unpublished Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .changeset/objectui-pin-citations-e420df310f5b.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/objectui-pin-citations-e420df310f5b.md b/.changeset/objectui-pin-citations-e420df310f5b.md index 43c54fac628..122253a50ba 100644 --- a/.changeset/objectui-pin-citations-e420df310f5b.md +++ b/.changeset/objectui-pin-citations-e420df310f5b.md @@ -6,4 +6,4 @@ The spec's objectui citations, and the shipped description text that names the ` Clause-②: no -Several records were corrected rather than moved, because objectui changed what they describe on this hop: the four `action:*` registrations now publish the keys their rows declare (objectui#11168), `action:group` and `action:menu` now apply a host-evaluated `disabled` that their rows do not declare (objectui#11182, recorded, not declared), the `object-kanban` default row cap is named `DEFAULT_KANBAN_FETCH_BATCH_SIZE` (objectui#9853), the board no longer forwards `quickAdd` (objectui#8285, objectui#11234), the `object-tree` ladder now judges `data` on the `view-data` arm its row declares (objectui#8348), and `object-map` / `object-gantt` / `object-timeline` now publish `filter` and `sort` inputs (objectui#8220). Two older statements that were already stale are also corrected: an authored `data` array on `object-map` no longer reaches the renderer through the React props channel (objectui#9571), and the `quickAdd` retirement record now notes that the schema-only `kanban-ui` block it points to is retired in objectui (objectui#8257). No key, default, enum member or export moves. +Several records were corrected rather than moved, because objectui changed what they describe on this hop: the four `action:*` registrations now publish the keys their rows declare, `endpoint` and `undoable` aside (objectui#11168), `action:group` and `action:menu` now apply a host-evaluated `disabled` that their rows do not declare (objectui#11182, recorded, not declared), the `object-kanban` default row cap is named `DEFAULT_KANBAN_FETCH_BATCH_SIZE` (objectui#9853), the board no longer forwards `quickAdd` (objectui#8285, objectui#11234), the `object-tree` ladder now judges `data` on the `view-data` arm its row declares (objectui#8348), and `object-map` / `object-gantt` / `object-timeline` now publish `filter` and `sort` inputs (objectui#8220). Two older statements that were already stale are also corrected: an authored `data` array on `object-map` no longer reaches the renderer through the React props channel (objectui#9571), and the `quickAdd` retirement record now notes that the schema-only `kanban-ui` block it points to is retired in objectui (objectui#8257). No key, default, enum member or export moves. From 184a592990207de0f859d63ef0423182cfc973aa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:21:47 +0000 Subject: [PATCH 15/17] docs(references): regenerate view.mdx for the FormField.span describe re-measured at e420df310f5b Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- content/docs/references/ui/view.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index b35b9b07ab3..e173131c7d9 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -213,7 +213,7 @@ Column footer summary configuration | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `db11afd4967c`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | @@ -378,7 +378,7 @@ View filter rule | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `db11afd4967c`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | From c013d1a2b072d73848ac8ad28579dd3e58041ebd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 01:55:45 +0000 Subject: [PATCH 16/17] fix(sdui-parser)!: retire the interim inert-quick-add diagnostic, mirroring objectui 6f864cf62 objectui 6f864cf62 (objectui#8285) deleted kanban-quick-add.ts, its wiring in validate.ts and its four barrel exports from @object-ui/sdui-parser once @objectstack/spec tombstoned object-kanban.quickAdd. This copy deletes the same module, wiring and exports (checkKanbanQuickAdd, INERT_QUICK_ADD, QUICK_ADD_HOST_TYPES, QUICK_ADD_KEY), so an authored quickAdd draws the walk's own unknown-prop warning again and check:sdui-lockstep agrees on all 25 codes. The pin is rewritten for the new state, as objectui's was. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .../src/__tests__/inert-quick-add.test.ts | 257 ++++-------------- packages/sdui-parser/src/index.ts | 6 - packages/sdui-parser/src/kanban-quick-add.ts | 177 ------------ packages/sdui-parser/src/validate.ts | 21 -- 4 files changed, 58 insertions(+), 403 deletions(-) delete mode 100644 packages/sdui-parser/src/kanban-quick-add.ts diff --git a/packages/sdui-parser/src/__tests__/inert-quick-add.test.ts b/packages/sdui-parser/src/__tests__/inert-quick-add.test.ts index 723cad21333..192972600e0 100644 --- a/packages/sdui-parser/src/__tests__/inert-quick-add.test.ts +++ b/packages/sdui-parser/src/__tests__/inert-quick-add.test.ts @@ -1,68 +1,37 @@ /** - * `quickAdd` on the `ObjectKanbanRenderer` tag is DIAGNOSED, and diagnosed - * truthfully — the objectui#8285 mechanism (ruling of 2026-09-08, batch #91), - * ported into this copy in lockstep (objectstack#17645). + * What this tier says about `quickAdd` on `` once the key is + * RETIRED (objectui#8285, ruling B of decision batch #91, 2026-09-08) — the + * objectstack half of the lockstep, mirroring objectui `6f864cf62`. * - * WHY THESE PINS EXIST HERE. Two copies of this parser exist — objectui's - * `packages/sdui-parser` (which the browser RENDERER runs) and this hoisted one - * (which the SAVE GATE runs). These pins are the objectstack half of the - * lockstep for this diagnostic; the ported module is byte-equal to objectui's. - * Before this port, this copy answered an authored `quickAdd` with the generic - * `unknown-prop` while objectui answered `inert-quick-add` — the same authored - * page getting two different readings from the two tiers, which is the dialect - * split the lockstep invariant forbids. + * ## What changed * - * ## What is red here before the change, and what is not + * Until objectui#8285 closed, `quickAdd` was a key `@objectstack/spec` + * published on `object-kanban` and the board could not honour, so both copies + * of this parser carried an interim `inert-quick-add` warning in place of the + * `unknown-prop` the prop walk would otherwise emit: "has no prop quickAdd" was + * FALSE against the published contract. The ruling scheduled that interim for + * deletion the day the spec's own refusal landed ("the spec refusal by name + * replaces that diagnostic once it lands"). * - * ⚠️ Only the rows that assert `inert-quick-add` are red before this change. - * `quickAdd` is undeclared on the kanban registrations (objectui#8201 escalated - * it rather than declaring it), so this tier ALREADY reported it — as - * `unknown-prop`, a message that is false against the contract - * `@objectstack/spec` still publishes. The port replaces a wrong reading; it - * does not add a first one. + * It landed: `@objectstack/spec` 17 tombstones the key (`retiredKey`, #17260), + * and objectui `6f864cf62` deleted its interim module and its four barrel + * exports. This copy deletes the same module and the same four exports, so the + * two tiers keep one diagnostic-code set (`check:sdui-lockstep`), and the walk's + * own answer is back. * - * The remaining rows are green in BOTH worlds by construction, and each is kept - * because it names a WRONG FIX that would otherwise pass: + * ## Why a bare `unknown-prop` is the accurate answer now * - * - the unknown-prop control — a fix that suppressed the generic walk for - * this block rather than for this one key; - * - the two RETIRED kanban-ish tags (`kanban`, `kanban-ui`) — a fix scoped - * to "a kanban-ish tag" rather than to the one registration that serves - * `ObjectKanbanRenderer`. Both left objectui's registry (objectui#8802 / - * objectui#8257), so neither is a host any more; they stay declared in the - * hand-built manifest below precisely so this row can still discriminate; - * - `quickAdd: false` — a fix keyed on the KEY's presence rather than on the - * author asking for the control, which would warn about a value that got - * exactly what it asked for; - * - the braced marker — a fix that reads the parser's opaque `$expr` object - * as "truthy, therefore the author asked for it"; - * - `ok` staying true — an escalation to `error`, which the objectui#5709 - * ruling ("no new red gates") and objectui#6614 Q2 both put elsewhere. + * "`` has no prop quickAdd" is TRUE on every face after the + * retirement, and on this tier it is also the whole remedy: a constrained-JSX + * page cannot write the `onQuickAdd` function the control needs, so the only + * edit open to its author is to delete the key. */ import { describe, expect, it } from 'vitest'; -import { - INERT_QUICK_ADD, - QUICK_ADD_HOST_TYPES, - QUICK_ADD_KEY, - compile, - manifestFromConfigs, - validateTree, -} from '../index.js'; +import * as sduiParser from '../index.js'; +import { compile, manifestFromConfigs, validateTree } from '../index.js'; import type { Diagnostic, Manifest } from '../types.js'; -/** - * The three kanban blocks, carrying the inputs their registrations declared — - * `quickAdd` on NONE of them, which is the state this port leaves untouched. - * `card` is a non-kanban control block. - * - * ⚠️ `kanban` and `kanban-ui` are RETIRED registrations (objectui#8802, - * objectui#8257) and are kept here ON PURPOSE, as the discrimination controls - * below: a manifest is an argument to `validateTree`, so this file can still - * ask what the rule says about a tag the live registry no longer produces — - * and the answer must be "not a host". ⛔ Do not read their presence as a - * claim that either tag resolves; against a manifest built from the live - * registry both answer `unknown-component`. - */ +/** The `object-kanban` registration's shape: `quickAdd` is not among its inputs. */ const manifest: Manifest = manifestFromConfigs([ { type: 'object-kanban', @@ -72,163 +41,53 @@ const manifest: Manifest = manifestFromConfigs([ { name: 'groupBy', type: 'string' }, ], }, - { - type: 'kanban', - namespace: 'view', - inputs: [ - { name: 'objectName', type: 'string', required: true }, - { name: 'groupBy', type: 'string' }, - ], - }, - { type: 'kanban-ui', namespace: 'plugin-kanban', inputs: [{ name: 'columns', type: 'array' }] }, - { type: 'card', namespace: 'ui', inputs: [] }, ]); const diagnose = (node: Record): Diagnostic[] => validateTree(node as never, manifest).diagnostics; -const codesFor = (node: Record, key: string): string[] => - diagnose(node).filter((d) => d.message.includes(`"${key}"`)).map((d) => d.code); - -const HOST_TAGS = [...QUICK_ADD_HOST_TYPES].sort(); - -describe('an authored `quickAdd` is diagnosed on the ObjectKanban tags', () => { - it('the host set is the one surviving ObjectKanbanRenderer tag, and is not empty', () => { - // Anti-vacuity for every row below: an empty set would make the negative - // rows trivially true and the positive rows unreachable. - // - // ⚠️ This row is the ONLY thing in this package that a change to - // `QUICK_ADD_HOST_TYPES` reddens — every other row is `it.each(HOST_TAGS)` - // and re-derives itself from the constant, so a narrowing would otherwise - // just delete cases silently. `kanban` left the set with its registration - // (objectui#8802): `checkKanbanQuickAdd` is reached only from - // `validate.ts`'s prop walk, which runs only for a tag the manifest - // RESOLVED, so a tag no registration produces is answered by - // `unknown-component` one level up and never reaches this module. - expect(HOST_TAGS).toEqual(['object-kanban']); - }); - - it('the stamped literal equals the exported constant', () => { - // objectui stamps `code: INERT_QUICK_ADD`; this copy stamps the inline - // literal, because `check:dispatcher-error-vocabulary` cannot reduce a - // kebab-case constant at a `code:` position (the divergence is documented - // at the stamp, and is the same one `unconsumed-widget-option` carries). - // This row is what keeps the two spellings from drifting apart — and the - // lockstep gate resolves the identifier through this constant, so both - // copies still agree on the same code. - expect(INERT_QUICK_ADD).toBe('inert-quick-add'); - const [diagnostic] = diagnose({ type: 'object-kanban', objectName: 'task', quickAdd: true }); - expect(diagnostic.code).toBe(INERT_QUICK_ADD); - }); - - it.each(HOST_TAGS)('<%s> — an authored `quickAdd: true` draws exactly one warning', (tag) => { - const diagnostics = diagnose({ type: tag, objectName: 'task', quickAdd: true }); - expect(diagnostics).toHaveLength(1); - expect(diagnostics[0].code).toBe(INERT_QUICK_ADD); - expect(diagnostics[0].severity).toBe('warning'); - expect(diagnostics[0].tag).toBe(tag); - }); - - it.each(HOST_TAGS)('<%s> — it REPLACES the false `unknown-prop`, it does not join it', (tag) => { - // The reading this port exists to correct: before it, the only thing this - // tier said about this key was that the block "has no prop quickAdd" — - // false against `ComponentPropsMap['object-kanban']`, which publishes it. - expect(codesFor({ type: tag, objectName: 'task', quickAdd: true }, QUICK_ADD_KEY)).toEqual([ - INERT_QUICK_ADD, - ]); - }); - - it.each(HOST_TAGS)('<%s> — the message names the missing half and where the pair works', (tag) => { - // Not a prose pin: these two tokens are what makes the diagnostic - // ACTIONABLE, and a message that dropped either would send its reader back - // to the contract with no explanation, which is the state being fixed. - const [{ message }] = diagnose({ type: tag, objectName: 'task', quickAdd: true }); - expect(message).toContain('onQuickAdd'); - // ⚠️ The remedy names the COMPONENT, not the `kanban-ui` TAG it used to - // name: objectui#8257 retired that registration, so a page written to the - // old advice would now draw `unknown-component` — an ERROR. `KanbanRenderer` - // is still exported from `@object-ui/plugin-kanban` and still forwards both - // halves by identity, so it is the surviving way to get the pair. - expect(message).toContain('KanbanRenderer'); - }); - - it.each(HOST_TAGS)('<%s> — control: a genuinely unknown prop is still reported', (tag) => { - // Guards a fix that turned the generic walk off for this block instead of - // answering for this one key. Green in both worlds by construction. - expect(codesFor({ type: tag, objectName: 'task', bogusProp: 'x' }, 'bogusProp')).toEqual([ - 'unknown-prop', - ]); - }); +/** The retired interim's code, spelled here because the constant left the barrel. */ +const RETIRED_CODE = 'inert-quick-add'; - it.each(HOST_TAGS)( - '<%s> — control: a falsy `quickAdd` asks for no control, so it draws none', - (tag) => { - // `KanbanImpl` gates on `quickAdd && onQuickAdd`: `false` got exactly what - // it wrote and nothing was dropped. Guards a fix keyed on the key's mere - // presence. Green in both worlds — the `unknown-prop` half is the status - // quo this port deliberately leaves alone. - expect(codesFor({ type: tag, objectName: 'task', quickAdd: false }, QUICK_ADD_KEY)).toEqual([ - 'unknown-prop', +describe('objectui#8285 — `quickAdd` on draws the walk\'s own `unknown-prop`', () => { + it.each([[true], [false], [{ $expr: 'rows.length > 0' }]])( + 'value %p — exactly one diagnostic, an `unknown-prop` warning naming the key', + (value) => { + const diagnostics = diagnose({ type: 'object-kanban', objectName: 'task', quickAdd: value }); + expect(diagnostics.map((d) => [d.severity, d.code, d.tag])).toEqual([ + ['warning', 'unknown-prop', 'object-kanban'], ]); + expect(diagnostics[0].message).toContain('"quickAdd"'); }, ); - it.each(HOST_TAGS)( - '<%s> — control: a braced value this tier never evaluates is not read as a request', - (tag) => { - // The parser's deferred marker is an opaque object — truthy in JS, and no - // reading at all about what the author asked for. Guards a fix that tested - // truthiness alone. Green in both worlds. - expect( - codesFor( - { type: tag, objectName: 'task', quickAdd: { $expr: 'rows.length > 0' } }, - QUICK_ADD_KEY, - ), - ).toEqual(['unknown-prop']); - }, - ); - - it.each(['kanban-ui', 'kanban'])( - 'control: `%s` is NOT a host — a kanban-ish tag alone does not arm this diagnostic', - (tag) => { - // Guards a fix scoped to "a kanban-ish tag". Both spellings are RETIRED - // registrations (objectui#8257, objectui#8802), so neither can reach this - // module through a manifest built from the live registry at all — they are - // declared in this file's hand-built manifest so the discrimination is - // still measurable here, which is the one thing a synthetic manifest can - // do that the live one cannot. `kanban-ui`'s pair itself survives on the - // exported `KanbanRenderer` component, which no tag resolves to. - const node = { type: tag, columns: [], quickAdd: true }; - expect(diagnose(node).map((d) => d.code)).not.toContain(INERT_QUICK_ADD); - expect(codesFor(node, QUICK_ADD_KEY)).toEqual(['unknown-prop']); - }, - ); - - it('control: a non-kanban block is untouched', () => { - expect(diagnose({ type: 'card', quickAdd: true }).map((d) => d.code)).toEqual(['unknown-prop']); + it('the retired interim code is emitted for no value of the key', () => { + // `true` is the row that drew it before; `false` and the braced marker are + // the two it deliberately skipped. None of them may draw it now. + for (const value of [true, false, { $expr: 'x' }]) { + const codes = diagnose({ type: 'object-kanban', objectName: 'task', quickAdd: value }).map((d) => d.code); + expect(codes).not.toContain(RETIRED_CODE); + } }); - it('through the whole pipeline: the diagnostic survives parse + validate on real source', () => { - // A SUBJECT row, not a control — it is red without the port, like the rows - // above. It is here because every row above hands `validateTree` a - // hand-built node: this one starts from source text, so it also proves the - // parser really materializes the braced `true` as a boolean rather than as - // the `$expr` marker the falsy/braced controls are about. - const { diagnostics } = compile('', manifest); - expect(diagnostics.map((d) => d.code)).toEqual([INERT_QUICK_ADD]); + it('through the whole pipeline: source text compiles, with the warning and nothing harder', () => { + const result = compile('', manifest); + expect(result.diagnostics.map((d) => d.code)).toEqual(['unknown-prop']); + // A warning, as the interim was: the save gate's pass/fail does not move. + expect(result.ok).toBe(true); + // Non-vacuity for that `true`: the same pipeline does turn `ok` false. + expect(compile('', manifest).ok).toBe(false); }); - it('control: the page still COMPILES — a warning, not a new red gate', () => { - // `ok` is `!diagnostics.some(d => d.severity === 'error')`, i.e. the save - // gate's pass/fail, and it is true in BOTH worlds: `unknown-prop` was a - // warning too, so nothing hardens here. Kept because it names the wrong - // fix — escalating an inert authored key to `error`, which objectui#5709 - // ("no new red gates") and objectui#6614 Q2 both put elsewhere, and which - // would stop a page that saves today from saving. - expect(compile('', manifest).ok).toBe(true); - // Non-vacuity for that `true`: the same pipeline DOES turn `ok` false when - // an error-severity diagnostic is present, so this is a reading about this - // diagnostic's severity and not about `ok` being unreachable. - expect(compile('', manifest).ok).toBe(false); + it('the barrel no longer publishes the interim\'s four exports', () => { + const exported = Object.keys(sduiParser); + for (const name of ['checkKanbanQuickAdd', 'INERT_QUICK_ADD', 'QUICK_ADD_HOST_TYPES', 'QUICK_ADD_KEY']) { + expect(exported).not.toContain(name); + } + // Control on the same instrument: the sibling interim check this copy + // carries is still exported, so the four absences are a reading of this + // barrel. (objectui's pin names `checkRetiredBodyDialect`, a module this + // copy does not carry.) + expect(exported).toContain('checkDashboardWidgetOptions'); }); }); diff --git a/packages/sdui-parser/src/index.ts b/packages/sdui-parser/src/index.ts index 78bc31e5edd..6466b94e212 100644 --- a/packages/sdui-parser/src/index.ts +++ b/packages/sdui-parser/src/index.ts @@ -15,12 +15,6 @@ export { DASHBOARD_WIDGET_HOST_TYPES, UNCONSUMED_WIDGET_OPTION, } from './dashboard-widget-options.js'; -export { - checkKanbanQuickAdd, - INERT_QUICK_ADD, - QUICK_ADD_HOST_TYPES, - QUICK_ADD_KEY, -} from './kanban-quick-add.js'; export { generateDts, propsName, generateBlockList } from './codegen.js'; export type { CodegenOptions } from './codegen.js'; export { inputTypeArms, canonicalizeInputType, MANIFEST_INPUT_TYPES } from './input-type.js'; diff --git a/packages/sdui-parser/src/kanban-quick-add.ts b/packages/sdui-parser/src/kanban-quick-add.ts deleted file mode 100644 index 20f9f74b9f0..00000000000 --- a/packages/sdui-parser/src/kanban-quick-add.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * The inert `quickAdd` on the `ObjectKanbanRenderer` tag (objectui#8285, - * director-seat ruling of 2026-09-08, decision batch #91) - * - * ## WHY THIS MODULE EXISTS HERE - * - * Two copies of this parser exist: objectui's `packages/sdui-parser` (which the - * browser RENDERER runs) and this one (which the SAVE GATE runs). Everything - * from the `import` line below to end of file is the port of objectui's copy. - * The two copies must agree on the accepted grammar AND on diagnostic codes — - * if they drift, the save gate and the renderer speak different dialects and a - * page can save clean and render inert. Change this only together with the - * objectui copy, and re-record `pnpm gen:sdui-lockstep` when you do. - * - * ## What is wrong, measured rather than argued - * - * `@objectstack/spec`'s `ComponentPropsMap['object-kanban']` publishes - * `quickAdd: z.boolean().optional()`, and the board does not honour it. - * `KanbanImpl` gates the Quick Add control on BOTH halves of a pair — - * `quickAdd && onQuickAdd`, at the in-column button and again at - * `QuickAddForm` — and `onQuickAdd` is a RUNTIME SLOT: a host-supplied - * FUNCTION, refused by name in `complex.zod.ts` because JSON has no function - * value. `ObjectKanban` substitutes its own `onCardMove` and `onCardClick` and - * stops there; it supplies no `onQuickAdd`. - * - * On THIS tier the second half is not merely absent, it is unwritable: a - * handler attribute on a parsed page draws `forbidden-attr`, because this tier - * parses and never executes. So `quickAdd` is inert for every author who can - * reach it here — there is no out-of-band consumer to protect, which is the - * fact that makes the diagnostic below unconditional rather than escape-hatched - * the way `unconsumed-widget-option` is. - * - * ## What this replaces, and why the previous reading was WRONG rather than absent - * - * `quickAdd` is deliberately absent from the kanban registrations' declared - * inputs (objectui#8201 escalated it rather than declaring it), so - * `validateTree` already reported it — as `unknown-prop`, "has no prop - * quickAdd". - * - * That message is a MISDIAGNOSIS. objectui#8201's pin header records the same - * shape from the other direction: a tier telling an author that spellings which - * WORK are unknown "trains authors (AI authors included) to delete working - * metadata". Here the falsehood runs the other way and is worse for it — the - * spec DOES publish this prop, so an author who checks the contract finds the - * warning contradicted, concludes the manifest is behind the spec, and keeps a - * key that will never do anything. It also cannot be told apart from a typo, so - * nothing anywhere records WHY the key is refused. - * - * This module says the true thing instead: the pair, the missing half, and - * where the pair does work. - * - * ## Severity — WARNING, and the escalation is deliberately not taken - * - * The ruling's word is "diagnosed", not "refused", and every diagnostic in this - * family is a warning: `unknown-prop` (what this replaces, so nothing hardens), - * `inert-expression`, and `unconsumed-widget-option`, whose objectui#5709 - * ruling states the constraint directly — "no gate weakening and no new red - * gates were ruled". `validate.ts`'s `inert-expression` note records that - * escalating an inert authored key to `error` is a SEPARATE question - * (objectui#6614 Q2) belonging at the save gate, and it is not answered here: - * an error would stop a page that saves today from saving, which is a cost, and - * costs are the maintainer's to impose. - * - * The contract-side refusal is where the hardening belongs, and it is already - * ruled: option B retires `object-kanban.quickAdd` from the spec's - * `ComponentPropsMap` (ADR-0049 enforce-or-remove), after which a strict parse - * refuses the key BY NAME and this module is deleted along with the interim it - * names. ⚠️ THIS FILE IS THAT INTERIM. - * - * ## Scope — the three places this deliberately does not fire - * - * - `KanbanRenderer`, the component. The ruling keeps the `quickAdd` / - * `onQuickAdd` pair there untouched: it forwards both halves by identity, - * and a React host mounting it CAN pass the function. ⚠️ It is no longer - * reachable as a TAG — objectui#8257 retired the `kanban-ui` registration, - * so `` is now an `unknown-component` ERROR, not a block with a - * working pair. The component stays exported from - * `@object-ui/plugin-kanban`, which is the surviving way to get the pair, - * and is what this module's message names. - * - `quickAdd: false`, and any other falsy value. The renderer's own gate is - * `quickAdd && onQuickAdd`, so a falsy value asks for no control and gets - * none — the author got what they wrote, and nothing was dropped. The - * ruling names `quickAdd: true`. - * - A braced value this tier never evaluates (the parser's `$expr` marker). - * It is opaque here, so "the author asked for the control" is not a reading - * this module can make; the value's own inertness is `inert-expression`'s - * subject, not this one's. - */ -import type { Diagnostic } from './types.js'; - -/** The diagnostic `code` this module emits. */ -export const INERT_QUICK_ADD = 'inert-quick-add'; - -/** The authored key it is about. */ -export const QUICK_ADD_KEY = 'quickAdd'; - -/** - * The tags served by `ObjectKanbanRenderer` — the registrations in objectui's - * `packages/plugin-kanban/src/index.tsx`, which is where another one would - * appear. Restated here as data because this package is deliberately free of - * any dependency on the registry or on a plugin (see `RegistryConfigLike` in - * `index.ts`). - * - * ⚠️ **`kanban` left this set with its registration** (objectui#8802, ruled - * 2026-09-09; `kanban-ui` and `kanban-enhanced` went the same way under - * objectui#8257). Keeping it would have been dead data, MEASURED and not - * assumed: `checkKanbanQuickAdd` has exactly one call site, inside - * `validate.ts`'s prop walk, and that walk runs only in the branch where - * `manifest.components[node.type]` RESOLVED. A tag no registration produces is - * answered one level up, by `unknown-component`, and its props are never walked - * at all — so on a manifest built from the live registry a `` - * node draws `error/unknown-component` and nothing else, against a firing - * control on `` that draws `warning/inert-quick-add`. - * ⛔ Nothing is silently dropped by the narrowing: the retired spelling is - * refused BY NAME at the tag, which is a louder answer than this warning, and - * stacking both would be the two-diagnostics-for-one-mistake shape - * `checkMemberTypes` already refuses (objectui#8067). The one path that could - * still reach a `kanban` entry is a HAND-BUILT manifest declaring a component - * of that name — which, after the retirement, is somebody else's component, and - * the message below asserts things about `ObjectKanban` that would be false of - * it. - */ -export const QUICK_ADD_HOST_TYPES: ReadonlySet = new Set(['object-kanban']); - -const isPlainObject = (v: unknown): v is Record => - typeof v === 'object' && v !== null && !Array.isArray(v); - -/** The parser's deferred-expression marker — opaque here, never evaluated. */ -const isExpr = (v: unknown): boolean => isPlainObject(v) && '$expr' in v; - -/** - * The diagnostic for ONE authored prop, or `null` when this module has nothing - * to say about it. - * - * Per-prop rather than per-node — unlike `checkDashboardWidgetOptions`, which - * runs after the prop walk — because this diagnostic REPLACES the one the walk - * would otherwise emit for the same key. Two diagnostics for one mistake is the - * shape `validate.ts` already refuses at `checkMemberTypes` (objectui#8067). - * - * Deliberately independent of whether the registration DECLARES the key: the - * claim is about the render path, which a declaration cannot change. Reading it - * out of the `!input` branch would make declaring `quickAdd` silently disarm - * this — the one edit that must instead leave the warning standing. - */ -export function checkKanbanQuickAdd(tag: string, key: string, value: unknown): Diagnostic | null { - if (key !== QUICK_ADD_KEY) return null; - if (!QUICK_ADD_HOST_TYPES.has(tag)) return null; - if (isExpr(value)) return null; - if (!value) return null; - return { - severity: 'warning', - // DIVERGENCE FROM OBJECTUI, and the only one below this file's header: - // objectui writes `code: INERT_QUICK_ADD` here. This repo runs - // `check:dispatcher-error-vocabulary`, whose `objlitconst` shape reads the - // SCREAMING_SNAKE constant NAME at a `code:` position and then must reduce - // it to a literal — and its literal grammar is `[A-Za-z][A-Za-z0-9_]*`, - // which a KEBAB-case value cannot satisfy. So the constant form is reported - // as an unresolvable code constant, and that finding cannot be declared - // away. `inert-quick-add` is a parser DIAGNOSTIC code, not an ADR-0112 wire - // code, and an inline quoted literal is the form both vocabulary gates - // already accept for the sibling diagnostic codes in `validate.ts` and for - // `unconsumed-widget-option` next door. The emitted VALUE is unchanged, and - // the test next door pins it equal to `INERT_QUICK_ADD` so the two - // spellings cannot drift apart. ⛔ This does NOT weaken the lockstep: that - // gate resolves an identifier at a code position through the package's own - // module-level constants, so both spellings reduce to the same code and the - // two copies still agree on all 26. - code: 'inert-quick-add', - message: - `<${tag}> prop "${QUICK_ADD_KEY}" reaches no control — the Quick Add button is gated on ` + - `BOTH "${QUICK_ADD_KEY}" and an "onQuickAdd" handler, and this block supplies neither half ` + - `of the pair: "onQuickAdd" takes a FUNCTION, which no page on this tier can write (this tier ` + - `parses, never executes) and which this board substitutes none of its own for. Drop the key, ` + - `or mount KanbanRenderer from "@object-ui/plugin-kanban" in a React host that passes "onQuickAdd".`, - tag, - }; -} diff --git a/packages/sdui-parser/src/validate.ts b/packages/sdui-parser/src/validate.ts index 55b456ca8b8..9381efe2f4e 100644 --- a/packages/sdui-parser/src/validate.ts +++ b/packages/sdui-parser/src/validate.ts @@ -22,7 +22,6 @@ import type { } from './types.js'; import { inputTypeArms } from './input-type.js'; import { checkDashboardWidgetOptions } from './dashboard-widget-options.js'; -import { checkKanbanQuickAdd } from './kanban-quick-add.js'; /** * The protocol's ONE child-list key — `BaseSchema.children` — and therefore the @@ -218,26 +217,6 @@ export function validateTree(tree: SchemaElement | null, manifest: Manifest): Va // (objectui#11044): skipped only when this type declares no input of // that name, so a declared one keeps its type check below. if (WHERE_UNDECLARED_BASE_PROPS.has(key) && !byName.has(key)) continue; - // The `object-kanban` Quick Add pair (objectui#8285): a key - // `@objectstack/spec` still publishes and the renderer cannot honour, - // because the control is gated on a RUNTIME SLOT no parsed page can - // write. It REPLACES whatever the rules below would say about the key — - // `unknown-prop` today, and a coarse type check if the key were ever - // declared — because two diagnostics for one mistake is what - // `checkMemberTypes` already refuses (objectui#8067), and because - // "has no prop quickAdd" is FALSE against the published contract. Asked - // AHEAD of the declaration lookup on purpose: the claim is about the - // render path, so declaring the key must not silently disarm it. - // Interim, by the ruling — the spec's refusal by name replaces it. - // - // LOCKSTEP: this call site and the module behind it are the port of - // objectui's copy. The two copies must agree on the accepted grammar - // AND on diagnostic codes — change this only together with objectui. - const quickAdd = checkKanbanQuickAdd(node.type, key, value); - if (quickAdd) { - diagnostics.push(quickAdd); - continue; - } const input = byName.get(key); if (!input) { diagnostics.push({ From bb035326a1b6e16e315b7132cfa9724f70cbf2ff Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 01:56:41 +0000 Subject: [PATCH 17/17] chore(changeset): ADR-0087 dispositions for the console pin bump and the sdui-parser port The console changeset's placeholder becomes the maintainer's ruling, not-required (no-migration-prescription), worded after the #20638 precedent and fitted to this diff. The @objectstack/sdui-parser changeset declares the export removal as a narrowing, shipped as minor under the launch-window convention. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude --- .changeset/console-e420df310f5b.md | 27 ++++++++++++++++++- .../sdui-parser-retire-inert-quick-add.md | 11 ++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .changeset/sdui-parser-retire-inert-quick-add.md diff --git a/.changeset/console-e420df310f5b.md b/.changeset/console-e420df310f5b.md index 0fc60e7f574..5823fd0d9eb 100644 --- a/.changeset/console-e420df310f5b.md +++ b/.changeset/console-e420df310f5b.md @@ -100,6 +100,31 @@ Derived from the changesets objectui declared over the range — 87 releasing of - _(no changeset)_ docs: re-fence the plugins and core remainder of objectui#5867 as ts, 17 blocks across 8 pages (batch 6) (#11176) (objectui `340dc718f`) - + objectui range: `db11afd4967c...e420df310f5b` diff --git a/.changeset/sdui-parser-retire-inert-quick-add.md b/.changeset/sdui-parser-retire-inert-quick-add.md new file mode 100644 index 00000000000..b36899b01c4 --- /dev/null +++ b/.changeset/sdui-parser-retire-inert-quick-add.md @@ -0,0 +1,11 @@ +--- +'@objectstack/sdui-parser': minor +--- + +fix(sdui-parser)!: the interim `inert-quick-add` diagnostic is retired, mirroring objectui `6f864cf62` (objectui#8285) + +Clause-②: no (narrowing) + + + +**BREAKING**: shipped as `minor` under the launch-window convention. The save-time parser stops emitting the interim `inert-quick-add` warning for an authored `quickAdd` on ``. The prop walk's own `unknown-prop` warning replaces it, so severity is unchanged and no page that saves today stops saving. The package's barrel no longer exports `checkKanbanQuickAdd`, `INERT_QUICK_ADD`, `QUICK_ADD_HOST_TYPES` or `QUICK_ADD_KEY`. A caller that matched on the `inert-quick-add` code should match `unknown-prop` on the `quickAdd` key instead, and an importer of those four names should delete the import: nothing replaces them. On the authored side, delete the `quickAdd` key: the spec refuses it by name.