From d6a0f7bf78e0e6c61b7f915397a52d6010022feb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 08:03:43 +0000 Subject: [PATCH 01/13] chore(objectui): bump the console pin to 62597c588072 (carries objectui 28b065800c7d) Produced by scripts/bump-objectui.sh --no-commit 62597c588072. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- .changeset/console-62597c588072.md | 47 ++++++++++++++++++++++++++++++ .objectui-sha | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .changeset/console-62597c588072.md diff --git a/.changeset/console-62597c588072.md b/.changeset/console-62597c588072.md new file mode 100644 index 00000000000..6ef6b2fa834 --- /dev/null +++ b/.changeset/console-62597c588072.md @@ -0,0 +1,47 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `62597c588072`. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 27 releasing of 30 changesets added across 37 non-merge commits; omitted: 3 release-nothing changesets, 7 commits carrying no changeset (they ship no package code). + +- **minor** — fix(plugin-grid): `object-grid`'s `pagination` and `selection` now read their own presence by the SAME rule — presence enables, an explicit off wins (objectui `62597c588`) +- **minor** — A render-time filter refusal now renders a named "this view's filter is malformed" state instead of throwing out of render (objectui#9050). (objectui `804831c2a`) +- **minor** — `record:related_list` `add.picker.filter` is declared as the contract's rule array on the authoring face, not as `unknown` (objectui#9964). (objectui `9a9780072`) +- **minor** — feat(core): `object-tree` joins the curated public tier — the spec declared the block, the roster withheld it (objectui `c131d9e69`) +- **minor** — A signed-in user's language has one source of truth: `sys_user.locale`. (objectui `63f4f928f`) +- **minor** — A record form can no longer be saved while an upload is still in flight (objectui#10166). (objectui `e686f4d9a`) +- **minor** — Nav `visible`: an ancestor's predicate now reaches the whole subtree, and a group no longer outlives its children (objectui#10119) (objectui `73a3c89af`) +- **minor** — A field-backed action param reaches its record picker, and a param whose backing field cannot be read is refused instead of rendered as an empty text box (objectui#10129). (objectui `6cc910b6d`) +- **patch** — fix(react): the refused-`data` dev warning can be reset between tests (objectui `befd40ccd`) +- **patch** — fix(components): a half-typed `between` range in the filter builder shows itself as incomplete instead of being dropped with no signal (objectui#10061) (objectui `777fca22f`) +- **patch** — fix(plugin-detail): a related list resolves `user` columns to names, like every other list (objectui `0d379f571`) +- **patch** — fix(core): a date-only value renders the calendar day it names, in every viewer timezone (objectui `516583b54`) +- **patch** — fix(plugin-detail): the record-grained write verdict is memoised PER PRINCIPAL (objectui#10107). (objectui `b06c3de2a`) +- **patch** — fix(console): a `file` field on a FormView renders the shared upload control, not a text box (objectui `7725c10a0`) +- **patch** — Stop the Studio permission matrix' Delegated Admin Scope editor authoring an `adminScope` the framework spec refuses, and make the section's collapsed badge report a scope it is c… (objectui `2d7fff3b8`) +- **patch** — fix(console): mint a `sys_file` id for files picked in the global action dialogs (objectui `98178b206`) +- **patch** — fix(plugin-detail): `requiredPermissions` on `record:details`, `record:highlights` and `record:related_list` is an ADR-0066 capability set, read fail-closed — it used to pass for… (objectui `c2dabf032`) +- **patch** — A form no longer submits — nor offers — a field the CALLER may read but not edit (objectui#10120). (objectui `80c54122e`) +- **patch** — fix(components): `page:header` resolves a lookup title candidate instead of handing its expanded object to JSX (objectui#10117) (objectui `4c6f549ef`) +- **patch** — `ObjectCalendar`'s user-visible copy now reaches the locale packs. The component was already i18n-aware — it imports and calls both translation hooks — and a set of English senten… (objectui `afb228418`) +- **patch** — `useSettledSchema` no longer republishes an EQUAL definition as a new object, so swapping the adapter on a record-bound view costs ONE record query instead of two. (objectui `162621b6a`) +- **patch** — A form no longer emits the columns the server owns, and a master-detail batch sends only the cells the user changed (objectui#10108). (objectui `e026e15f9`) +- **patch** — fix(plugin-calendar): the month gridcell's accessible name follows the resolved locale (objectui `708f2711b`) +- **patch** — The AI build bar, the Studio workbench and the chat transcript's draft cards report the runtime authoring gate's per-draft advisories (objectui#10039) (objectui `e16f504d9`) +- **patch** — fix(plugin-detail): `record:quick_actions.requiredPermissions` is an ADR-0066 capability set, read fail-closed — it used to pass for every reader of the object (objectui `28b065800`) +- **patch** — fix(plugin-map): the top-level-`style` warning now prescribes a spelling the runtime reads (objectui `2252653d0`) +- **patch** — `record:history` refuses a row cap the contract rejects instead of repairing it (objectui#10005). (objectui `cd2cb4183`) + +**In this console build, declared nowhere** — objectui merged 7 commits in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared them, so they appear 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(agents): 一次派发跑出来的 PR 停在 draft —— 给自行合并条款加第二条例外 (#10213) (objectui `87a0ed8cf`) +- _(no changeset)_ docs(agents): state the attribution-footer append as conditional on absence (#10212) (objectui `2ec310198`) +- _(no changeset)_ docs(skills): the console guide names the resolver `/` actually has (objectui#10140) (#10215) (objectui `8d7915ed2`) +- _(no changeset)_ ci(pm): adopt the board-snapshot archiver — route B, and the five files this repo's own gates demanded (objectui#9387) (#10203) (objectui `0cf2d6644`) +- _(no changeset)_ ci: compile objectui against @objectstack/spec@main as the shape gate (objectui#9860) (#9877) (objectui `6cae9a0b3`) +- _(no changeset)_ perf(console): move the `types` zod validators off the eager budgeted line (#10082) (objectui `64d250c8a`) +- _(no changeset)_ ci(bundle-analysis): retire the exhausted-headroom leg, keep every ceiling (#10151) (objectui `cbda340f0`) + +objectui range: `87af769e9a3e...62597c588072` diff --git a/.objectui-sha b/.objectui-sha index 035b6937d64..010114f9c1a 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -87af769e9a3ee28ace099fdd653d3ebd79fe82e2 +62597c588072636e9c30ea35b3d89b1e46fd765d From 1e9f2b83fb228c5a093a98a08f45526c73ebe9d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 08:04:59 +0000 Subject: [PATCH 02/13] chore(objectui): re-record the sdui manifest at pin 62597c588072 node scripts/gen-sdui-manifest-node.mjs --objectui-version 17.6.0; the manifest is byte-identical (sha256 49211fee7792), only the provenance record moves. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- scripts/sdui-manifest.record.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index d861981910a..080c31e0a5f 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -9,10 +9,10 @@ "a pin bump without regeneration goes RED there — that is the anti-rot half of the freshness gate.", "Regenerate + re-record: node scripts/gen-sdui-manifest-node.mjs (see its header for the offline mode)." ], - "objectuiSha": "87af769e9a3ee28ace099fdd653d3ebd79fe82e2", + "objectuiSha": "62597c588072636e9c30ea35b3d89b1e46fd765d", "objectuiPackagesVersion": "17.6.0", "generator": "scripts/gen-sdui-manifest-node.mjs", - "generatedAt": "2026-09-20", + "generatedAt": "2026-09-23", "sha256": "49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d", "components": 57 } From bb9b7e68c35481795f97926cf644c7365252f59d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 08:53:26 +0000 Subject: [PATCH 03/13] chore(sdui-parser): re-record the objectui lockstep at pin 62597c588072 pnpm gen:sdui-lockstep with OBJECTUI_ROOT at 62597c588; provenance fields only (grammar blob 0131f27cf86d and all 26 codes unchanged). Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- packages/sdui-parser/objectui-lockstep.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index e0a6988b1c9..5c72e4dcf8e 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": "87af769e9a3ee28ace099fdd653d3ebd79fe82e2", - "revDate": "2026-09-20T15:03:40+00:00", + "rev": "62597c588072636e9c30ea35b3d89b1e46fd765d", + "revDate": "2026-09-23T02:43:52+00:00", "source": "packages/sdui-parser/src", "files": [ "packages/sdui-parser/src/body-dialect.ts", @@ -22,7 +22,7 @@ "packages/sdui-parser/src/validate.ts" ] }, - "recordedAgainstPin": "87af769e9a3ee28ace099fdd653d3ebd79fe82e2", + "recordedAgainstPin": "62597c588072636e9c30ea35b3d89b1e46fd765d", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", From 5e018979e7fb02493877d7fffb4f9fcc6b22cb1a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 08:59:30 +0000 Subject: [PATCH 04/13] chore(spec): re-count the six pin-cited objectui corpus readings at 62597c588072 Each dark token (the def exports and key names each entry names) was re-counted with git grep -o -F at the new pin: still 0 everywhere, the Span / SpanSchema collisions unchanged at 486 / 53. The method was calibrated first: at 87af769e9a3e it reproduces every recorded count (8228 files, useState 2383, timeout 1075, objectstack 12966, ...). Only the corpus size and the lit controls move. registry.ts is regenerated by gen:migration-registry. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- ...ck-and-hot-reload-durations-unit-in-key.ts | 6 ++-- ...rnel-runtime-config-timeout-unit-in-key.ts | 4 +-- .../18.logging-durations-unit-in-key.ts | 4 +-- ...tem-metrics-jsdoc-durations-unit-in-key.ts | 8 ++--- ...ing-otel-exporter-durations-unit-in-key.ts | 6 ++-- .../18.tenant-schema-cache-ttl-unit-in-key.ts | 4 +-- packages/spec/src/migrations/registry.ts | 32 +++++++++---------- 7 files changed, 32 insertions(+), 32 deletions(-) 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 a3fb09dad88..ae93faaee66 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 @@ -45,10 +45,10 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — names ' + + 'against, `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — 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 8228 tracked files, against lit ' - + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus.', + + 'the string debounceDelay each occur 0 times across its 8303 tracked files, against lit ' + + 'controls objectstack 13125 and @objectstack/spec 5043 on the same corpus.', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' + 'one of a HotReloadConfig spells debounceDelayMs — concretely ' 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 7adc10fd67e..7feb9519649 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 @@ -42,9 +42,9 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2`, re-read from this tree — ' + + '`.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '8228 tracked files, against lit controls timeout 1075, RuntimeConfig 240 and resourceLimits ' + + '8303 tracked files, against lit controls timeout 1086, RuntimeConfig 240 and resourceLimits ' + '2 on the same corpus; 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. #15939, #15678, ' 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 058151bb6d3..1b08780a39a 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 @@ -45,9 +45,9 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — spells ' + + 'objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 8228 tracked files, against lit controls `useState` 2383 and `timeout` 1075 on ' + + 'across its 8303 tracked files, against lit controls `useState` 2389 and `timeout` 1086 on ' + 'the same corpus.', acceptanceCriteria: 'Every HTTP log destination spells `batch.flushIntervalMs`, `retry.initialDelayMs` and ' 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 5afafd579ac..746b74cf2d5 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 @@ -67,10 +67,10 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 8228 tracked ' - + 'files at that sha, against lit controls window 3464, timeout 1075, period 170, ' - + 'interval 170 and metrics 324 on that same corpus and sha, so no pin bump is owed. ' + + '`.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 8303 tracked ' + + 'files at that sha, against lit controls window 3526, timeout 1086, period 170, ' + + 'interval 179 and metrics 324 on that same corpus and sha, so no pin bump is owed. ' + '#15939, #15679, #14478, ADR-0087.', acceptanceCriteria: 'Every metric definition spells summary.maxAgeSeconds, every error-budget burn rate window ' 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 4dc94bd724b..473907c1b49 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 @@ -63,11 +63,11 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 8228 files ' + + 'pinned objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 8303 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name), against two lit controls on ' - + 'that same corpus and sha: 12966 hits for the bare token objectstack, and 4997 for the ' + + 'that same corpus and sha: 13125 hits for the bare token objectstack, and 5043 for the ' + 'package specifier @objectstack/spec.', 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 29f3ebd5c33..366730da971 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 @@ -28,8 +28,8 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — spells it 0 ' - + 'times across 8228 tracked files, against lit controls `TTL` 156 and `tenant` 976 on the ' + + 'objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — spells it 0 ' + + 'times across 8303 tracked files, against lit controls `TTL` 156 and `tenant` 987 on the ' + 'same corpus.', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 314b4832ab8..c05c5c56da2 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -9380,10 +9380,10 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — names ' + + 'against, `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — 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 8228 tracked files, against lit ' - + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus.', + + 'the string debounceDelay each occur 0 times across its 8303 tracked files, against lit ' + + 'controls objectstack 13125 and @objectstack/spec 5043 on the same corpus.', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' + 'one of a HotReloadConfig spells debounceDelayMs — concretely ' @@ -9569,9 +9569,9 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2`, re-read from this tree — ' + + '`.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '8228 tracked files, against lit controls timeout 1075, RuntimeConfig 240 and resourceLimits ' + + '8303 tracked files, against lit controls timeout 1086, RuntimeConfig 240 and resourceLimits ' + '2 on the same corpus; 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. #15939, #15678, ' @@ -9713,9 +9713,9 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — spells ' + + 'objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 8228 tracked files, against lit controls `useState` 2383 and `timeout` 1075 on ' + + 'across its 8303 tracked files, against lit controls `useState` 2389 and `timeout` 1086 on ' + 'the same corpus.', acceptanceCriteria: 'Every HTTP log destination spells `batch.flushIntervalMs`, `retry.initialDelayMs` and ' @@ -12040,10 +12040,10 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 8228 tracked ' - + 'files at that sha, against lit controls window 3464, timeout 1075, period 170, ' - + 'interval 170 and metrics 324 on that same corpus and sha, so no pin bump is owed. ' + + '`.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 8303 tracked ' + + 'files at that sha, against lit controls window 3526, timeout 1086, period 170, ' + + 'interval 179 and metrics 324 on that same corpus and sha, so no pin bump is owed. ' + '#15939, #15679, #14478, ADR-0087.', acceptanceCriteria: 'Every metric definition spells summary.maxAgeSeconds, every error-budget burn rate window ' @@ -12235,11 +12235,11 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 8228 files ' + + 'pinned objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 8303 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name), against two lit controls on ' - + 'that same corpus and sha: 12966 hits for the bare token objectstack, and 4997 for the ' + + 'that same corpus and sha: 13125 hits for the bare token objectstack, and 5043 for the ' + 'package specifier @objectstack/spec.', acceptanceCriteria: 'Every author and reader of an OpenTelemetryCompatibility spells exporter.timeoutMs, ' @@ -12335,8 +12335,8 @@ 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` = `87af769e9a3ee28ace099fdd653d3ebd79fe82e2` — spells it 0 ' - + 'times across 8228 tracked files, against lit controls `TTL` 156 and `tenant` 976 on the ' + + 'objectui checkout — `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — spells it 0 ' + + 'times across 8303 tracked files, against lit controls `TTL` 156 and `tenant` 987 on the ' + 'same corpus.', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' From 75a0f8c68eb9fc4fff79ca6e471c28e27f8a38f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:01:31 +0000 Subject: [PATCH 05/13] chore(spec): re-measure the component.test.ts objectui pin citations at 62597c588 Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- packages/spec/src/ui/component.test.ts | 71 +++++++++++++++----------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 51de61700dd..703cb455e4c 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -286,15 +286,17 @@ describe('PageAccordionProps variant (#6776)', () => { // same file's `ComponentRegistry.register('accordion', …)` publishes the key to // the Studio block designer at `:1116` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `87af769e9`. Re-derived at that pin -// 2026-09-20: `containers.tsx` is NO LONGER byte-identical to the one at -// `53ded82bf` (300 insertions, 100 deletions), so both anchors were re-READ -// rather than carried — the icon block MOVED `919-925` to `1069-1075` with its -// seven lines byte-identical, and the registration input MOVED `966` to `1116` -// with its LINE rewritten (it now declares `of: 'object'` and carries a longer -// description, and no longer carries a `label`), while the member list this -// pin cites is unchanged. Identity preserves a wrong -// anchor as faithfully as a right one, which is why neither was carried (#10274). +// builds against — `.objectui-sha` = `62597c588`. Re-derived at that pin +// 2026-09-23: `containers.tsx` changed again across the hop from `87af769e9` +// (74 insertions, 16 deletions, objectui `4c6f549ef`, the `page:header` +// title), so both anchors were re-READ rather than carried — and NEITHER +// moved: the icon block is still `1069-1075` and the input still `1116`, each +// byte-identical to its `87af769e9` text. (The hop before, off `53ded82bf`, +// moved them from `919-925` and `966` and rewrote the input LINE — it now +// declares `of: 'object'`, carries a longer description and no `label` — +// while the member list this pin cites stayed unchanged.) Identity preserves a +// wrong anchor as faithfully as a right one, which is why neither was carried +// (#10274). // // #9397 spent a full dispatch cycle re-deriving that read point from scratch // after the sweep proposed retiring the key. This block plus the `.describe()` @@ -377,14 +379,15 @@ describe('PageTabsProps items[].value / items[].count (#5775)', () => { // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the // Studio block designer at `:912` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `87af769e9`. Re-derived -// at that pin 2026-09-20: `containers.tsx` is NO LONGER byte-identical to the -// one at `53ded82bf` (300 insertions, 100 deletions), so both anchors were -// re-READ rather than carried — the icon block MOVED `730-736` to `853-859` -// with its seven lines byte-identical, and the registration input MOVED `789` -// to `912` with its LINE rewritten (it now declares `of: 'object'` and carries -// a longer description, and no longer carries a `label`), while the member list -// this pin cites is unchanged. Never inferred (#10274). +// the pin this repo builds against — `.objectui-sha` = `62597c588`. Re-derived +// at that pin 2026-09-23: `containers.tsx` changed again across the hop from +// `87af769e9` (74 insertions, 16 deletions, objectui `4c6f549ef`), so both +// anchors were re-READ rather than carried — and NEITHER moved: the icon block +// is still `853-859` and the input still `912`, each byte-identical to its +// `87af769e9` text. (The hop before, off `53ded82bf`, moved them from +// `730-736` and `789` and rewrote the input LINE — `of: 'object'`, a longer +// description, no `label` — while the member list this pin cites stayed +// unchanged.) Never inferred (#10274). // // #9397 spent a full dispatch cycle re-deriving the accordion's read point // after the sweep proposed retiring it. This block plus the `.describe()` it @@ -3362,9 +3365,11 @@ 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` = `87af769e9`; all four anchors re-READ at that pin -// 2026-09-20 — this hop moved every one of them and renamed one face outright, -// so none is carried): `plugin-kanban/src/ObjectKanban.tsx:676` +// against (`.objectui-sha` = `62597c588`; re-measured there 2026-09-23 — all +// five objectui files this block cites are byte-identical to `87af769e9` +// (`git diff --quiet`), so the four anchors hold unmoved; they were last +// re-READ at `87af769e9` 2026-09-20, the hop that moved every one of them and +// renamed one face outright): `plugin-kanban/src/ObjectKanban.tsx:676` // queries `$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT)` (100, // `:84`; the bare `??` became `resolveRowLimit` in objectui#9925, which drops // and reports a cap the contract refuses), @@ -3433,8 +3438,10 @@ 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` = `87af769e9`; re-READ there 2026-09-20, every -// anchor MOVED with its cited text byte-identical) `ObjectKanban.tsx:1563` +// against (`.objectui-sha` = `62597c588`; re-measured there 2026-09-23 — +// `ObjectKanban.tsx` and `KanbanImpl.tsx` are byte-identical to `87af769e9`, +// where every anchor was re-READ 2026-09-20 and MOVED with its cited text +// byte-identical, so none moved on this hop) `ObjectKanban.tsx:1563` // spreads the // authored bag into `KanbanRenderer` and `KanbanImpl` gates the affordance on // `quickAdd && onQuickAdd` (`KanbanImpl.tsx:621`, `:634` — the file is spelled @@ -3499,10 +3506,12 @@ describe('ObjectKanbanPropsSchema quickAdd is retired (#17260)', () => { // #9881 and #9972 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` = `87af769e9`, re-derived there 2026-09-20. Both files in -// this chain moved on this hop — `resolve-icon.ts` +203/-7 and `button.tsx` -// +6/-11 against `53ded82bf` — so no anchor below is carried and every one -// was re-READ (#10274). ⚠️ `resolveIcon` itself was rewritten: its tail no +// `.objectui-sha` = `62597c588`, re-derived there 2026-09-23: `resolve-icon.ts`, +// `button.tsx` and `lazy-icon.tsx` are all byte-identical to `87af769e9` +// (`git diff --quiet`), so no anchor moved on this hop. The hop before it, +// onto `87af769e9` (re-derived 2026-09-20), moved both files in this chain — +// `resolve-icon.ts` +203/-7 and `button.tsx` +6/-11 against `53ded82bf` — so +// no anchor below was carried there and every one was re-READ (#10274). ⚠️ `resolveIcon` itself was rewritten: its tail no // longer indexes `lucide-react`'s `icons` record, it asks `recordIconName` // for the kebab-case name and hands the pair to `lazyIconComponent`, so the // glyph arrives lazily. What an author may write did not change with it. The @@ -3619,9 +3628,13 @@ 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` = `87af769e9`; re-READ at that pin 2026-09-22 — all three -// renderers moved hard on the hop from `53ded82bf`, so NO anchor in this -// block is carried and every one was re-derived from the pinned tree. Four +// (`.objectui-sha` = `62597c588`; re-measured there 2026-09-23 — the gantt and +// tree renderers are byte-identical to `87af769e9`, and the map's +// `ObjectMap.tsx` changed only in a docblock and a dev-warning string +// (objectui `2252653d0`), so its anchors MOVED with their cited text +// byte-identical and no key set moved. They were re-READ at `87af769e9` +// 2026-09-22 — all three renderers moved hard on the hop from `53ded82bf`, +// so NO anchor in this block was carried and every one was re-derived. Four // changed CONTENT rather than position and say so where they are cited: the // map's array-shorthand head, the tree's record-source ARM, the cast on the // tree's `schema.data` read, and the tree's `titleField` rung), and From 73c0465f0da88c6834c4d7cb3ee85f88c36601e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:03:49 +0000 Subject: [PATCH 06/13] chore(spec): re-measure the component.zod.ts icon, kanban, calendar and map pin citations at 62597c588 Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- packages/spec/src/ui/component.zod.ts | 154 ++++++++++++++++---------- 1 file changed, 95 insertions(+), 59 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 4cbb0a606d9..8d05b5be7ac 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -692,13 +692,15 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `87af769e9`; re-derived at that pin 2026-09-20 — - * `containers.tsx` is NO LONGER byte-identical to the one at `53ded82bf` - * (300 insertions, 100 deletions), so both anchors were re-READ rather - * than carried: the icon block moved `730-736` -> `853-859` with its seven - * lines byte-identical, and the registration input moved `789` -> `912` - * with its LINE rewritten — it now declares `of: 'object'` and carries a - * longer description — while the member list this record cites is + * (`.objectui-sha` = `62597c588`; re-derived at that pin 2026-09-23 — + * `containers.tsx` changed again across the hop from `87af769e9` (74 + * insertions, 16 deletions, objectui `4c6f549ef`), so both anchors were + * re-READ rather than carried, and NEITHER moved: `853-859` and `912` are + * byte-identical to their `87af769e9` text. The hop before, off + * `53ded82bf`, moved the icon block `730-736` -> `853-859` with its seven + * lines byte-identical and the registration input `789` -> `912` with its + * LINE rewritten — it declares `of: 'object'` and carries a longer + * description — while the member list this record cites stayed * unchanged): `containers.tsx:853-859` * renders * `{item.icon && }` inside the @@ -1864,14 +1866,16 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `87af769e9`; re-derived at that pin 2026-09-20 — - * `containers.tsx` is NO LONGER byte-identical to the one at `53ded82bf` - * (300 insertions, 100 deletions), so both anchors were re-READ rather - * than carried: the icon block moved `919-925` -> `1069-1075` with its - * seven lines byte-identical, and the registration input moved `966` -> - * `1116` with its LINE rewritten — it now declares `of: 'object'` and - * carries a longer description — while the member list this record cites - * is unchanged): `containers.tsx:1069-1075` + * (`.objectui-sha` = `62597c588`; re-derived at that pin 2026-09-23 — + * `containers.tsx` changed again across the hop from `87af769e9` (74 + * insertions, 16 deletions, objectui `4c6f549ef`), so both anchors were + * re-READ rather than carried, and NEITHER moved: `1069-1075` and `1116` + * are byte-identical to their `87af769e9` text. The hop before, off + * `53ded82bf`, moved the icon block `919-925` -> `1069-1075` with its seven + * lines byte-identical and the registration input `966` -> `1116` with its + * LINE rewritten — it declares `of: 'object'` and carries a longer + * description — while the member list this record cites stayed + * unchanged): `containers.tsx:1069-1075` * renders * `{item.icon && }` inside the * `AccordionTrigger`, grouped with the label in the trigger's one wrapping @@ -2123,10 +2127,13 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `87af769e9`; re-derived at that pin 2026-09-20 — this - * hop is the one that moved both files: `resolve-icon.ts` +203/-7 and - * `button.tsx` +6/-11 against `53ded82bf`, so NO anchor below is carried and - * every one was re-READ. Four moved with their cited text byte-identical + * (`.objectui-sha` = `62597c588`; re-derived at that pin 2026-09-23 — + * `resolve-icon.ts`, `button.tsx` and `lib/lazy-icon.tsx` are all + * byte-identical to `87af769e9` (`git diff --quiet`), so no anchor below + * moved on this hop. The hop onto `87af769e9` (re-derived 2026-09-20) is the + * one that moved both files: `resolve-icon.ts` +203/-7 and + * `button.tsx` +6/-11 against `53ded82bf`, so NO anchor below was carried + * there and every one was re-READ. Four moved with their cited text byte-identical * (`describeIconLookup`, `toPascalCase`, the rename map, `getLazyIcon`); * `resolveIcon` itself was REWRITTEN — its tail no longer indexes * `lucide-react` directly — and the registration's two ranges both moved and @@ -2158,7 +2165,7 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * to `describeIconLookup` (`:302-305`), which PascalCases through * `toPascalCase` (`:153-158`) and then applies a one-entry rename map * (`Home` becomes `House`, `:143-145`) before the lookup. ⚠️ That last - * step is what this hop rewrote: the resolver no longer indexes + * step is what the hop onto `87af769e9` rewrote: the resolver no longer indexes * `lucide-react`'s `icons` record itself — it asks `recordIconName` for * the kebab-case name and hands the pair to `lazyIconComponent`, so the * glyph now arrives lazily. The accept/reject behaviour is unchanged. @@ -2185,7 +2192,7 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * Also measured at the same pin: the renderer's registration publishes no * `icon` input (`button.tsx:85-97` lists `label`, `variant`, `size`, * `className`; `:98-102` is `defaultProps`), so the Studio block designer - * does not offer the key. Both ranges SHRANK on this hop — the inputs went + * does not offer the key. Both ranges SHRANK on the hop onto `87af769e9` — the inputs went * `85-102` -> `85-97` and `defaultProps` `103-107` -> `98-102`, because each * input dropped its `label` and `defaultValue` members; the four input names * and the absence of an `icon` input are what was re-read, and both hold. @@ -2916,9 +2923,12 @@ 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` = `87af769e9`; re-derived at that pin 2026-09-20 — EVERY - * file in the chain below moved on this hop, so no anchor is carried and - * each was re-READ: the `object-metric` registration now begins at `:227` + * (`.objectui-sha` = `62597c588`; re-derived at that pin 2026-09-23 — all + * four objectui files in the chain below are byte-identical to `87af769e9` + * (`git diff --quiet`), so no anchor moved on this hop. On the hop onto + * `87af769e9` (re-derived 2026-09-20) EVERY file in the chain moved, so no + * anchor was carried there and each was re-READ: the `object-metric` + * registration now begins at `:227` * and its icon input lands on `:237`. ⚠️ One thing the numbers do not * carry: that input's `label: 'Icon (Lucide name)'` member is GONE — the * whole registration dropped its per-input labels — so the key is still @@ -3017,8 +3027,10 @@ 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` = `87af769e9`; re-READ there 2026-09-20 — the file moved - * hard on this hop, +708/-79 against `53ded82bf`, and this anchor is one the + * (`.objectui-sha` = `62597c588`; re-measured there 2026-09-23 — + * `ObjectKanban.tsx` is byte-identical to `87af769e9`, where it was re-READ + * 2026-09-20: the file moved hard on that hop, +708/-79 against `53ded82bf`, + * and this anchor is one the * numbers alone would have mis-carried: the read was a bare * `$top: schema.limit ?? DEFAULT_KANBAN_LIMIT` at `:264` and is now * `$top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT)`, objectui#9925 @@ -3066,9 +3078,12 @@ 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` = `87af769e9`; all four anchors - * re-READ at that pin 2026-09-20 — this hop moved every one of them, and one - * face was RENAMED rather than shifted, so nothing below is carried), four + * repo builds against (`.objectui-sha` = `62597c588`; re-measured there + * 2026-09-23 — every objectui file this block cites, `ElementDataSourceGate`, + * `element-data-source.ts`, `ListView.tsx` and `ObjectView.tsx` included, is + * byte-identical to `87af769e9`, so no anchor moved on this hop. All four + * anchors were re-READ at `87af769e9` 2026-09-20 — the hop that moved every + * one of them, and RENAMED one face rather than shifting it), four * faces agree * while this map refused the key by name: the board's one query is * `dataSource.find(objectName, { $filter: schema.filter, $top: @@ -3179,8 +3194,10 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * React-host `kanban-ui` block). * * Measured at the objectui pin this repo builds against - * (`.objectui-sha` = `87af769e9`; all four anchors re-READ at that pin - * 2026-09-20 — each MOVED with its cited text byte-identical): the board + * (`.objectui-sha` = `62597c588`; re-measured there 2026-09-23 — the three + * plugin-kanban files cited are byte-identical to `87af769e9`, where all four + * anchors were re-READ 2026-09-20 and each MOVED with its cited text + * byte-identical, so none moved on this hop): the board * forwards the key — * `ObjectKanban.tsx:1563` spreads the authored bag into `KanbanRenderer`, * which passes @@ -3189,8 +3206,10 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * 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 6 for the sibling `onCardClick` in the same - * file). So the gate was permanently false and authoring the key was a + * (0 occurrences each, against 11 for the sibling `onCardClick` in the same + * file — re-counted at `62597c588`; 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. * * ⛔ Not a silent one, which is why the retirement is worth more than a @@ -3224,9 +3243,11 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * element schemas). * * Measured at the pin this repo builds against (`.objectui-sha` = - * `87af769e9`; re-READ there 2026-09-22 — every anchor re-derived from the - * pinned tree rather than carried, none of them at its `53ded82bf` - * number and one of them no longer spelled the way this record quoted it): + * `62597c588`; re-measured there 2026-09-23 — `ObjectKanban.tsx` is + * byte-identical to `87af769e9`, so no anchor moved on this hop. They were + * 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:1210` * reads `schema.navigation ?? { mode: 'drawer' }` and hands it to * `useNavigationOverlay` (`:1218-1219`), whose result drives the card click @@ -3353,13 +3374,19 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `object-kanban`'s above. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `87af769e9`; re-READ there 2026-09-22 — every anchor re-derived from the - * pinned tree rather than carried, none of them at its `53ded82bf` number. - * Unlike its `object-kanban` twin the cast is still spelled here, ledgered - * at `:921-935` as the one objectui#8651 left standing): `ObjectCalendar.tsx:936` + * `62597c588`; re-READ there 2026-09-23 — `ObjectCalendar.tsx` changed + * across the hop from `87af769e9` (objectui `afb228418`, the calendar's + * copy routed through the locale packs, +43/-17), so every anchor was + * re-derived rather than carried: `:921-935`, `:936` and `:938-939` did not + * move, and the three after the change MOVED with their cited text + * byte-identical — the event click `1293` -> `1317`, the overlay render + * `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 + * `:921-935` as the one objectui#8651 left standing): `ObjectCalendar.tsx:936` * reads `(schema as any).navigation ?? { mode: 'drawer' }`, `:938-939` hands - * it to `useNavigationOverlay`, `:1293` fires it on an event click and - * `:1218` renders the overlay (its `NavigationOverlay` is `:1228-1241`) — + * it to `useNavigationOverlay`, `:1317` fires it on an event click and + * `:1237` renders the overlay (its `NavigationOverlay` is `:1250-1263`) — * standalone, with no enclosing view. */ navigation: NavigationConfigSchema.optional() @@ -3573,9 +3600,15 @@ 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` = `87af769e9`, re-READ there 2026-09-22. ⛔ No anchor below - * is carried from `53ded82bf`: every one was re-derived from the pinned tree, - * and one of them is a read that no longer exists rather than a number that + * `.objectui-sha` = `62597c588`, re-READ there 2026-09-23. `ObjectMap.tsx` + * changed across the hop from `87af769e9` only in `warnOnTopLevelStyleUrl`'s + * docblock and dev-warning text (objectui `2252653d0`, +27/-8, which names + * the flat `mapStyle` spelling the flatten really carries), so no read point + * changed and every anchor from `:289` on MOVED 16-19 lines with its cited + * text byte-identical; those above it and `index.tsx` did not move. ⛔ No + * anchor below is carried: each was re-mapped through the diff and re-read. + * 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` (`:182-183` — `getDataConfig` is now that one * `resolveRecordSourceConfig(schema, 'view-data')` call, rung 1 of the ruled @@ -3585,23 +3618,23 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * 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:296`), `objectName` (rung 3, `record-source.ts:303`; also - * here at `:1043`, the `useNavigationOverlay` binding, and as a cache or - * dependency key at `:236`, `:239`, `:298`, `:341` and `:946`), - * `filter` (`:814` and `:895`, handed verbatim to + * here at `:1062`, the `useNavigationOverlay` binding, and as a cache or + * dependency key at `:236`, `:239`, `:314`, `:360` and `:965`), + * `filter` (`:833` and `:914`, handed verbatim to * `$filter` on the inline and the object fetch respectively), - * `sort` (`:815` and `:896`, through the shared `convertSortToQueryParams` sink - * to `$orderby`), `map` (`:382` — `getMapConfig` branch 1, the author face and + * `sort` (`:834` and `:915`, through the shared `convertSortToQueryParams` sink + * to `$orderby`), `map` (`:401` — `getMapConfig` branch 1, the author face and * the registration's declared `{ name: 'map', type: 'object' }` input, * `plugin-map/src/index.tsx:68` and `:78`), `mapStyle` - * (`:377`, `schema.mapStyle || schema.map?.style`), `navigation` (`:1042`) and - * `enableClustering` (`:1058`). + * (`:396`, `schema.mapStyle || schema.map?.style`), `navigation` (`:1061`) and + * `enableClustering` (`:1077`). * * 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:295`, inside `warnOnTopLevelStyleUrl` (`:289`), + * - `style`. `ObjectMap.tsx:311`, inside `warnOnTopLevelStyleUrl` (`:305`), * 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. @@ -3611,11 +3644,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. `:385` validates the authored block against objectui's + * one-key gap is closed. `:404` 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 `:377` (`schema.mapStyle || schema.map?.style`). + * `getMapConfig` honours at `:396` (`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. */ @@ -3653,8 +3686,10 @@ 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` = `87af769e9`, re-READ - * 2026-09-22): `ObjectMap.tsx:814` and `:895` hand `schema.filter` verbatim + * pin this repo builds against (`.objectui-sha` = `62597c588`, re-READ + * 2026-09-23 — both anchors MOVED 19 lines from `:814` / `:895` with their + * text byte-identical, the hop changing only a docblock and a dev warning + * above them): `ObjectMap.tsx:833` and `:914` hand `schema.filter` verbatim * 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`. @@ -3670,8 +3705,9 @@ 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` = `87af769e9`, re-READ 2026-09-22): `ObjectMap.tsx:815` - * and `:896` + * (`.objectui-sha` = `62597c588`, re-READ 2026-09-23 — both anchors MOVED + * 19 lines from `:815` / `:896` with their text byte-identical): + * `ObjectMap.tsx:834` and `:915` * 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 From d7d3ab6b4fd24ac25373336d4d6a15101016e9dc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:04:44 +0000 Subject: [PATCH 07/13] chore(spec): re-measure the gantt, tree, timeline and map-row pin citations at 62597c588 Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- packages/spec/src/ui/component.zod.ts | 53 +++++++++++++++++---------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 8d05b5be7ac..3c788b87544 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -3784,10 +3784,12 @@ 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` = `87af769e9`, re-READ there 2026-09-22. ⛔ Not one anchor - * below is carried from `53ded82bf`; every number was re-derived from the - * pinned tree, and the `label` one moved far enough that the retired reading - * now lands in an unrelated callback.) + * — `.objectui-sha` = `62597c588`, re-measured there 2026-09-23: both files, + * and `core/src/utils/record-source.ts`, are byte-identical to `87af769e9` + * (`git diff --quiet`), so no anchor moved on this hop. At `87af769e9` + * (re-READ 2026-09-22) not one anchor below was carried from `53ded82bf`; + * 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 * `:613` — rung 1, `core/src/utils/record-source.ts:292`), `staticData` (rung 2, * `record-source.ts:296`), `objectName` (rung 3; also `:1487` and `:1682` here), @@ -3893,9 +3895,10 @@ export type ObjectGanttPropsParsed = z.infer; * block (`ObjectTree.tsx:235-248`) and that `ObjectView` / `ListView` EMIT when * they flatten `options.tree` (`ListView.tsx:3261-3275`, `case 'tree'`: the * product carries these keys and NO `tree` key). Both halves re-READ at the - * pin this repo builds against (`.objectui-sha` = `87af769e9`) on - * 2026-09-22 — this record carried no pin at all before, so nothing re-checked - * it when the pin moved. Read, but NOT authorable — + * pin this repo builds against (`.objectui-sha` = `62597c588`) on + * 2026-09-23 — `ObjectTree.tsx` and `ListView.tsx` are byte-identical to + * `87af769e9`, where both were re-READ 2026-09-22 (this record carried no pin + * at all before that, so nothing re-checked it when the pin moved). Read, but NOT authorable — * one composition key per concept (Prime Directive #12), the ruling the map and * the gantt carry from objectui#5018 / #6469 and the channel `object-calendar` * uses above. The registration agrees: `plugin-tree/src/index.tsx` declares @@ -3932,9 +3935,12 @@ 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` = `87af769e9`, re-READ there 2026-09-22. ⛔ No anchor - * below is carried from `53ded82bf`; every one was re-derived from the pinned - * tree, and the ladder call changed in CONTENT as well as position.) + * — `.objectui-sha` = `62597c588`, re-measured there 2026-09-23: both files, + * and `core/src/utils/record-source.ts`, are byte-identical to `87af769e9` + * (`git diff --quiet`), so no anchor moved on this hop. At `87af769e9` + * (re-READ 2026-09-22) no anchor below was carried from `53ded82bf`; every + * 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 * `:582` — rung 1, `core/src/utils/record-source.ts:292`, which returns the * authored value VERBATIM; that ONE site is the whole support @@ -4007,12 +4013,13 @@ 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` = `87af769e9`, re-COUNTED there - * 2026-09-22) its registry shell has ZERO hits for that wiring, against 3 + * builds against (`.objectui-sha` = `62597c588`, re-COUNTED there + * 2026-09-23) its registry shell has ZERO hits for that wiring, against 3 * each in `plugin-map`, `plugin-gantt`, `plugin-grid` and `plugin-calendar` - * — four controls, so the zero discriminates. ⚠️ Re-counted at BOTH ends of - * the hop with ONE method (occurrences of that identifier in each - * `src/index.tsx`): `53ded82bf` reads 3 each and 0 for the tree as well, so + * — four controls, so the zero discriminates. ⚠️ Re-counted at every end + * with ONE method (occurrences of that identifier in each `src/index.tsx`): + * `87af769e9` and `53ded82bf` both read 3 each and 0 for the tree as well — + * `plugin-grid/src/index.tsx` changed on this hop and still reads 3 — so * nothing about this reading moved. The `7 each` this record used to carry is * reproducible at neither pin by that method, nor by a whole-package count * (7 / 5 / 11 / 5, not 7 each). ⛔ A count is a reading only with its METHOD @@ -4095,8 +4102,9 @@ 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` = `87af769e9`), re-READ there - * 2026-09-22. + * repo builds against (`.objectui-sha` = `62597c588`), re-measured there + * 2026-09-23: all three files are byte-identical to `87af769e9`, where they + * were re-READ 2026-09-22, so no anchor below moved on this hop. * * ⛔ This record used to be MIXED, and the historical spelling is what let it * be: its `limit` clause was re-read at this pin on 2026-09-21 while every @@ -4269,7 +4277,8 @@ 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` = - * `87af769e9`, re-READ 2026-09-22), all in + * `62597c588`; the file is byte-identical to `87af769e9`, where they were + * re-READ 2026-09-22), all in * `ObjectTimeline.tsx`: `:255` seeds the loading state off it, `:420` * SKIPS the object query when it is present, `:442` tracks it, and `:444` * is the row source itself — `(props as any).data || boundData || @@ -4477,7 +4486,10 @@ 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` = - // `87af769e9`), all three re-READ there 2026-09-22 — per-block citations in + // `62597c588`), all three re-measured there 2026-09-23 (gantt and tree + // byte-identical to `87af769e9`, where all three were re-READ 2026-09-22; + // the map's anchors moved with their text byte-identical and no key set + // moved) — per-block citations in // each schema's header, including what each block reads and deliberately // does NOT declare, and what the hop from `53ded82bf` deleted rather than // moved. @@ -4491,7 +4503,8 @@ 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` = - // `87af769e9`), re-READ there 2026-09-22; the schema's own header + // `62597c588`), re-measured there 2026-09-23 (byte-identical to `87af769e9`, + // where it was re-READ 2026-09-22); the schema's own header // carries the per-key citations and what it deliberately does NOT declare. 'object-timeline': ObjectTimelinePropsSchema, } as const; From 613c7781aec4546f86479b1064db95d714ddc297 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:07:22 +0000 Subject: [PATCH 08/13] chore(spec): re-measure the grid, completeness-table, dataset and view pin citations at 62597c588 Re-points the two view.zod.ts ObjectMap anchors (385 -> 404, 390 -> 409) after re-reading getMapConfig at the new pin: objectui 2252653d0 rewrote the docblock and dev warning above it, the read itself is byte-identical. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- .../api-methods-batch-conformance.test.ts | 41 ++++++++++-------- .../src/kernel/functional-completeness.ts | 12 ++++-- packages/spec/src/ui/dataset.zod.ts | 16 ++++--- packages/spec/src/ui/view.zod.ts | 43 +++++++++++++------ 4 files changed, 75 insertions(+), 37 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 2d6ef49ec85..3c8541cca6d 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,29 +61,36 @@ 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` = `87af769e9`, `packages/plugin-grid`; re-measured at - // that pin, 2026-09-20 — previously measured at `53ded82bf`, `a472b0716`, - // `00d3f09c5`, `67dadd602`, before that at `d8ec8d6d4`, `9602dc820`, - // `190fbd01d`, `9a3daf8d3`, originally at `6314e87f2`. `ObjectGrid.tsx` DID - // change again across the move off `53ded82bf` (721 insertions, 159 - // deletions), so the selection block this record means was re-READ rather - // than carried: it is `ObjectGrid.tsx:3940-3955` here, was `3538-3553` at - // `53ded82bf` and `3544-3559` at `a472b0716`, and the sixteen lines are - // BYTE-IDENTICAL across the hop — `git hash-object` on both spans returns - // `6133933199230670e29d8c7f51c558d86a0af1d2`, the same id the previous two - // hops recorded, so the block only shifted 402 lines DOWN and none of its - // substance moved. The earlier hop off + // (`.objectui-sha` = `62597c588`, `packages/plugin-grid`; re-measured at + // that pin, 2026-09-23 — previously measured at `87af769e9`, `53ded82bf`, + // `a472b0716`, `00d3f09c5`, `67dadd602`, before that at `d8ec8d6d4`, + // `9602dc820`, `190fbd01d`, `9a3daf8d3`, originally at `6314e87f2`. + // `ObjectGrid.tsx` changed again across the move off `87af769e9` (119 + // insertions, 9 deletions), and THIS time the selection block's CONTENT + // moved, not only its position: objectui#10218 (`62597c588` itself) + // rewrote the `selection` arm to "presence enables; an explicit off wins", + // so an authored `selection` object with no `type` now selects + // (`DEFAULT_SELECTION_TYPE`, `'multiple'`) where it used to fall through. + // The block is `ObjectGrid.tsx:4024-4051` here — `3940-3955` at `87af769e9`, + // `3538-3553` at `53ded82bf` — and no longer hashes to the + // `6133933199230670e29d8c7f51c558d86a0af1d2` the previous three hops + // recorded (`c88443302d40c2db739ddb235470bafa29056e2e` now). Re-READ, the + // arm this record relies on is unchanged: with neither `selection` nor + // `selectable` authored, multi-select is still enabled only by + // `hasBulkActions`, and the rewritten arm is reachable only by a view that + // declares `selection` — which none of these four does. The earlier hop off // `00d3f09c5` is the one that caught the previous record's OWN grid anchor as // wrong rather than merely shifted: `3790-3805` there is // `runBulkActionAggregate` and says nothing about selection. That is the // #10274 class, and the reason a citation refresh re-READS instead of moving // numbers — arithmetic on a wrong anchor produces a fresh-looking span still // describing the wrong function. The second claim, - // `hooks/useBulkExecutor.ts:298-303`, sits in a file that is NO LONGER - // byte-identical across this hop (36 insertions, 22 deletions), so it was - // re-READ rather than carried on file identity: the six lines are themselves - // byte-identical (`git hash-object` on both spans returns - // `01083348330f10a201cdf1078b4c21c236402b6a`) and still end on + // `hooks/useBulkExecutor.ts:298-303`, sits in a file that is byte-identical + // to `87af769e9` on this hop (it last changed on the hop off `53ded82bf`, 36 + // insertions, 22 deletions, when it was re-READ rather than carried on file + // identity): the six lines hash to + // `01083348330f10a201cdf1078b4c21c236402b6a` at every one of those pins and + // still end on // `label = 'bulk delete'`, the line the `284-288` span cited six // pins ago stopped short of, truncating the second of the two branches it // names (byte-identity is never taken as proof an anchor is right): diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index 57a4b064e8a..8bba94d9ca9 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -284,7 +284,9 @@ 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` = `87af769e9`). ① `ListView.tsx`'s + * this repo builds against (`.objectui-sha` = `62597c588`; `ListView.tsx` + * and `ObjectGantt.tsx` are byte-identical to `87af769e9`, where this was + * first measured, re-checked 2026-09-23). ① `ListView.tsx`'s * `case 'gantt'` spreads `startDateField` / `endDateField` / `titleField` * only when the view DECLARED them — the `'start_date'` / `'end_date'` * floors were deleted by objectui#7070, and the `'progress'` / @@ -297,7 +299,8 @@ 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` = - * `87af769e9`). ① `ListView.tsx`'s `case 'timeline'` resolves the axis + * `62597c588`; `ObjectTimeline.tsx` is byte-identical to `87af769e9` too). + * ① `ListView.tsx`'s `case 'timeline'` resolves the axis * through `resolveTimelineDateBinding` and spreads it only when one was * declared — the `startDateField || 'created_at'` floor was deleted by * objectui#7070 step ③, on the ruling 日期轴永不虚构 (a date axis is never @@ -312,7 +315,10 @@ 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` = `87af769e9`), both faces moved together in + * pin (`.objectui-sha` = `62597c588`; `ObjectMap.tsx` changed on the hop + * from `87af769e9` only in a docblock and a dev-warning string, objectui + * `2252653d0`, and every read named below re-reads unchanged), both faces + * moved together in * objectui#8169. ① `ListView.tsx`'s `case 'map'` forwards * `resolveListMapConfig(schema)` and carries no `locationField || 'location'` * floor any more. ② `ObjectMap`'s `getMapConfig` no longer guesses diff --git a/packages/spec/src/ui/dataset.zod.ts b/packages/spec/src/ui/dataset.zod.ts index dc01c2ad81a..f764608b21e 100644 --- a/packages/spec/src/ui/dataset.zod.ts +++ b/packages/spec/src/ui/dataset.zod.ts @@ -209,8 +209,14 @@ 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` = - * `87af769e9`; re-derived at that pin 2026-09-20 — every anchor below MOVED - * on this hop and one of them changed SUBSTANCE, so nothing here is carried: + * `62597c588`; re-derived at that pin 2026-09-23 — `dataset-format.ts` is + * byte-identical to `87af769e9`, and `date-display.ts` moved (objectui + * `516583b54`, +77/-4): `formatDate` now parses through `toDisplayDate`, so a + * date-only value renders the calendar day it names in every timezone, + * while the style handling this record cites re-reads unchanged; its two + * anchors MOVED `198-233` -> `271-306` and `152` -> `225`, the second + * byte-identical. At `87af769e9` (2026-09-20) every anchor below MOVED and + * one of them changed SUBSTANCE, so nothing here was carried there either: * `formatMeasureDate`'s datetime arm no longer calls * `formatDateTime(v, { locale })` unconditionally, it SELECTS a formatter, * because `formatDateTime(value, options?)` has no style parameter to thread @@ -220,9 +226,9 @@ export const DatasetMeasureSchema = lazySchema(() => strictObject({ * non-numeric value through `formatMeasureDate` (`:229-264`, was `:185-198`) * at `:370`, * whose date-only arm threads `format` into the STYLE parameter of - * `formatDate` (`utils/date-display.ts:198-233`, was `:131-164`, whose - * `relative` branch falls back to the absolute form beyond ±7 days at - * `:152`, was `:117` — the fallback now strips the style through + * `formatDate` (`utils/date-display.ts:271-306`, was `:198-233` and before + * that `:131-164`, whose `relative` branch falls back to the absolute form + * beyond ±7 days at `:225`, was `:152` and `:117` — the fallback strips the style through * `absoluteFallbackOptions`), while its datetime arm answers `relative` with * `formatRelativeDate` (`:260`), `short` with * `formatDateTime(v, { locale, style: 'compact' })` (`:261`) and everything diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 2ecea023f2a..c9164367c6f 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -1231,8 +1231,12 @@ type RowLimitView = keyof typeof ROW_LIMIT_SUBJECT; * * ⚠️ WHAT THIS VIEW-FACE KEY REACHES TODAY — recorded, not repaired (#19228). * Measured first-hand at the pin this repo builds against (`.objectui-sha` = - * `87af769e9`), 2026-09-21T09:15Z, with TWO instruments, because one was not - * enough and the first one's answer was wrong: + * `62597c588`), with TWO instruments, because one was not enough and the + * first one's answer was wrong. First taken at `87af769e9` 2026-09-21T09:15Z; + * re-taken at `62597c588` 2026-09-23, where instrument 1 returns the SAME hit + * set line for line (probe 0; control 13 lines, 6 files) and all nine + * objectui files cited below are byte-identical to `87af769e9`, so every + * anchor and both verdicts stand unmoved: * * 1. PROPERTY-ACCESS spellings. ⛔ Published as its EXPRESSION, not as a * number — this card exists because a confident count was wrong once, so @@ -1949,9 +1953,17 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * downstream refuses one. Measured at the `.objectui-sha` pin `53ded82b` by * EXECUTING the pinned declarations, not by reading them, and RE-READ at pin * `87af769e9` on 2026-09-20 — every one of the seven anchors below moved on - * that hop, one of them lost the symbol it quoted, and the quotes now read the - * NEW tree; each anchor quotes the line it was read at, so the next pin bump - * reds instead of rotting (`check:objectui-pin-citations`): + * that hop, one of them lost the symbol it quoted, and the quotes read that + * tree. RE-READ again at pin `62597c588` on 2026-09-23: that bump redded the + * two `ObjectMap.tsx` anchors, as designed, and they are re-pointed below — + * objectui `2252653d0` rewrote the docblock and dev warning ABOVE + * `getMapConfig` (the remedy for a top-level `style` now names the declared + * `map: { style }`, which the flatten whitelist carries out as `mapStyle`), + * while `getMapConfig` itself is byte-identical, so both read points moved 19 + * lines and neither changed what it does; the other five anchors sit in files + * byte-identical to `87af769e9`. 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 @@ -1972,10 +1984,10 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * `export const ObjectMapConfigSchema = z.object({` — a plain `z.object`, * NOT strict, so an undeclared key parses clean there: zero issues, no * warning. `getMapConfig` consults that `safeParse` - * (`packages/plugin-map/src/ObjectMap.tsx:385` first line + * (`packages/plugin-map/src/ObjectMap.tsx:404` first line * `const result = ObjectMapConfigSchema.safeParse(config);`) only to decide * whether to `console.warn`, then returns a spread of the AUTHORED block - * (`:390` first line `return { ...config, style: config.style || style };`), + * (`:409` 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`). @@ -1994,10 +2006,13 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * (`schema.map?.style`) while this block did not declare it, so strictness here * refused a style URL the renderer honours — an author could not declare a map * style through this face at all (#18406, director decision batch #153 item 4). - * The two key sets match again. Re-read at the `.objectui-sha` pin `87af769e9` - * (2026-09-20; each of these three moved on the hop and each was re-READ): + * The two key sets match again. Re-read at the `.objectui-sha` pin `62597c588` + * (2026-09-23: `objectql.zod.ts` and `plugin-map.mdx` are byte-identical to + * `87af769e9`, and the `ObjectMap.tsx` read MOVED `377` -> `396` with its line + * byte-identical; at `87af769e9`, 2026-09-20, each of these three had moved + * and each was re-READ): * `packages/types/src/zod/objectql.zod.ts:1574` declares the eight keys, - * `packages/plugin-map/src/ObjectMap.tsx:377` reads + * `packages/plugin-map/src/ObjectMap.tsx:396` reads * `schema.mapStyle || schema.map?.style`, and objectui's own * `content/docs/plugins/plugin-map.mdx:143` documents `style` in the block — * so the divergence was against the documented surface this docblock cites, not @@ -3040,7 +3055,9 @@ const FormFieldBaseSchema = lazySchema(() => { colSpan: z.number().int().min(1).max(4).optional().describe("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."), /** * [#2578] Relative field width. 'full' resolves to the form grid's full - * column count (`plugin-form` `resolveColSpan`, `autoLayout.ts:153`); which + * column count (`plugin-form` `resolveColSpan`, `autoLayout.ts:154` at + * `62597c588`, one line down from `:153` at `87af769e9` — an import was + * added above it, the function is unchanged); which * container-query tiers receive the span class is the form renderer's, not * this key's. * At the `.objectui-sha` pin `87af769e9` the renderer emits one clamped @@ -3052,7 +3069,9 @@ const FormFieldBaseSchema = lazySchema(() => { * objectui#9244 / objectui#9253 (objectui `bd09957380`, 2026-09-12) land * 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. + * 720px) no longer reproduces at the pin this repo builds against + * (`.objectui-sha` = `62597c588`, re-read 2026-09-23: `form.tsx` is + * byte-identical to `87af769e9`, so `spanLadderFor` still emits the ladder). */ 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` = `87af769e9`), 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."), From 32a60d68fcd6b335a8afad110bbc37ac4a9e42cd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 09:31:55 +0000 Subject: [PATCH 09/13] chore(spec): keep both readings of the health-check entry's lit controls check:future-spec-major reads "@objectstack/spec NNNN" as a version and holds its measurement exemption to the witness sentence; the 87af769e9 clause stays verbatim (and dated) and the 62597c588 re-count follows it. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- ...ernel-health-check-and-hot-reload-durations-unit-in-key.ts | 4 +++- packages/spec/src/migrations/registry.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 ae93faaee66..fadbe60609e 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 @@ -48,7 +48,9 @@ export const entry: SemanticMigration = { + 'against, `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — 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 8303 tracked files, against lit ' - + 'controls objectstack 13125 and @objectstack/spec 5043 on the same corpus.', + + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + + 'which re-count at this pin to 13125 and 5043 respectively (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 ' + 'one of a HotReloadConfig spells debounceDelayMs — concretely ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index 98fa0fc179e..82228326a28 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -9420,7 +9420,9 @@ const step18: MigrationStep = { + 'against, `.objectui-sha` = `62597c588072636e9c30ea35b3d89b1e46fd765d` — 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 8303 tracked files, against lit ' - + 'controls objectstack 13125 and @objectstack/spec 5043 on the same corpus.', + + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + + 'which re-count at this pin to 13125 and 5043 respectively (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 ' + 'one of a HotReloadConfig spells debounceDelayMs — concretely ' From f9558e741cdb0249a246f059c83b59205a255565 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 10:51:52 +0000 Subject: [PATCH 10/13] chore(spec): restate the calendar-row copy claim and re-point the FormField.span describe to pin 62597c588 Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- .../src/kernel/functional-completeness.ts | 26 ++++++++++++------- packages/spec/src/ui/view.zod.ts | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index 8bba94d9ca9..1ed119bb7fb 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -271,16 +271,22 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * flat `startDateField`, and the `if (!calendarConfig)` arm renders the * "Calendar configuration required" refusal screen, which names * `startDateField` as the key to declare (objectui#8170 corrected that - * screen's second clause: `titleField` is NOT required). ⚠️ That literal - * is the text RENDERED at the pin: objectui#10101 landed AFTER the pin and - * moved it into a `tt('calendar.configRequired', …)` default, so on - * objectui's head a non-English locale renders other words for the same - * screen. Harmless for the console this repo ships — and the pin citation - * below is what reds when the pin moves past it. 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 are identical - * at the pin this repo builds against (`.objectui-sha` = `87af769e9`), so - * this describes the console this repo SHIPS and not only objectui's head. + * 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:1207-1208`), 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)` + * arm and what it names — did not move; only the words are per-locale now, + * so match the screen by that arm or by `startDateField`, never by the + * 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` = `62597c588`, re-read 2026-09-23: `ListView.tsx` is + * byte-identical to `87af769e9`, and `ObjectCalendar.tsx` changed there only + * in its copy), so this describes the console this repo SHIPS and not only + * objectui's head. * 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 diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index c9164367c6f..6a77770d5f2 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -3073,7 +3073,7 @@ const FormFieldBaseSchema = lazySchema(() => { * (`.objectui-sha` = `62597c588`, re-read 2026-09-23: `form.tsx` is * byte-identical to `87af769e9`, so `spanLadderFor` still emits the ladder). */ - 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` = `87af769e9`), 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` = `62597c588`), 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 72650da2d64c046611f759ac4858386400f53c27 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 10:52:36 +0000 Subject: [PATCH 11/13] chore(spec): regenerate view.mdx and add the FormField.span describe-correction changeset Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- .changeset/19503-pin-bump-describe-corrections.md | 11 +++++++++++ content/docs/references/ui/view.mdx | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .changeset/19503-pin-bump-describe-corrections.md diff --git a/.changeset/19503-pin-bump-describe-corrections.md b/.changeset/19503-pin-bump-describe-corrections.md new file mode 100644 index 00000000000..b8af1d3e6cb --- /dev/null +++ b/.changeset/19503-pin-bump-describe-corrections.md @@ -0,0 +1,11 @@ +--- +'@objectstack/spec': patch +--- + +One published `describe` sentence that dates itself to the `.objectui-sha` pin is re-pointed to the pin this release builds against, objectui `62597c588072`, after being re-read there (#19503). + +`Clause-②: no` + +- `FormField.span`: the `'auto'` clause says that at the pin this repo builds against, only textarea, markdown, html, richtext and repeater resolve to the full column count. It named `87af769e9`. Re-read at `62597c588`, the claim still holds: `plugin-form`'s `WIDE_FIELD_TYPES` is unchanged (it shifted one line when an import was added above it; repeater still reaches it through `field:grid`), and `form.tsx`'s `spanLadderFor` is byte-identical, so `'full'` is still the whole row at every multi-column tier. Only the pin the sentence names moves. + +No key, default, enum member or export moves: the same authored metadata is accepted and refused as before, and `content/docs/references/ui/view.mdx` is regenerated from the sentence. diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index b48d558ff29..1797e7d4f20 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -199,7 +199,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` = `87af769e9`), 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` = `62597c588`), 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 | @@ -364,7 +364,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` = `87af769e9`), 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` = `62597c588`), 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 3fd40bedd80463437f8435048bdc1c73da14d428 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 10:53:21 +0000 Subject: [PATCH 12/13] chore(spec): regenerate view.mdx from the merged tree Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- content/docs/references/ui/view.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 1797e7d4f20..f1695b0a764 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -317,7 +317,7 @@ View filter rule | :--- | :--- | :--- | :--- | | **field** | `string` | ✅ | Field name to filter on | | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>` | ✅ | Filter operator | -| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. | +| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | --- @@ -896,7 +896,7 @@ View filter rule | :--- | :--- | :--- | :--- | | **field** | `string` | ✅ | Field name to filter on | | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>` | optional | Filter operator | -| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. | +| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | ### Nested Shape: `ListView.userFilters` @@ -1308,7 +1308,7 @@ View filter rule | :--- | :--- | :--- | :--- | | **field** | `string` | ✅ | Field name to filter on | | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>` | optional | Filter operator | -| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. | +| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | ### Nested Shape: `ObjectListView.selection` @@ -2065,7 +2065,7 @@ View filter rule | :--- | :--- | :--- | :--- | | **field** | `string` | ✅ | Field name to filter on | | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| 'starts_with' \| 'ends_with' \| 'greater_than' \| 'less_than' \| 'greater_than_or_equal' \| … +10 more>` | ✅ | Filter operator | -| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. | +| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | ### Allowed Values: `ViewFilterRule.operator` @@ -2515,7 +2515,7 @@ View filter rule | :--- | :--- | :--- | :--- | | **field** | `string` | ✅ | Field name to filter on | | **operator** | `Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>` | ✅ | Filter operator | -| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. | +| **value** | `string \| number \| boolean \| null \| (string \| number)[]` | optional | Filter value. The accepted SHAPE depends on the operator: `in` / `not_in` take an array (any length, including []), `between` takes exactly [min, max], every other operator takes a scalar. The unary operators (is_empty / is_not_empty / is_null / is_not_null) take their direction from the operator name and ignore this key. One operator bounds the VALUE as well as the shape: `icontains` takes a NON-EMPTY STRING, the comparand the Filter Protocol conformance table declares for it — an empty comparand constrains nothing and a non-string one would answer a query nobody wrote, and both are refused at the query path too. | --- From 2a768071114e39151f9231823700d17c39f3b7f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 12:02:40 +0000 Subject: [PATCH 13/13] chore(spec): regenerate view.mdx from the merged tree gen:schema + gen:docs on the merge 557f561aa. The os-regen driver kept this branch's side of view.mdx and dropped main's fieldOrder / hiddenFields describe rows; the regenerated file equals origin/main's plus this branch's two FormField.span rows. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb --- content/docs/references/ui/view.mdx | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index f1695b0a764..1e08075ff8b 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -792,7 +792,7 @@ Map view configuration | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -817,8 +817,8 @@ Map view configuration | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | @@ -1204,7 +1204,7 @@ Tab configuration for multi-tab view interface | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -1228,8 +1228,8 @@ Tab configuration for multi-tab view interface | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | @@ -1807,7 +1807,7 @@ Tab configuration for multi-tab view interface | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -1831,8 +1831,8 @@ Tab configuration for multi-tab view interface | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | @@ -1892,7 +1892,7 @@ Tab configuration for multi-tab view interface | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -1916,8 +1916,8 @@ Tab configuration for multi-tab view interface | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | @@ -2132,7 +2132,7 @@ This schema accepts one of the following structures: | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -2157,8 +2157,8 @@ This schema accepts one of the following structures: | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. | @@ -2308,7 +2308,7 @@ This schema accepts one of the following structures: | **label** | `string \| Record` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time | | **type** | `Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>` | optional (default: `"grid"`) | | | **data** | `{ provider: 'object'; object: string } \| { provider: 'api'; read?: object; write?: object } \| { provider: 'value'; items: any[] } \| { provider: 'schema'; schemaId: string; schema?: Record }` | optional | Data source configuration (defaults to "object" provider) | -| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns | +| **columns** | `string[] \| { field: string; label?: string \| Record; width?: number; align?: Enum<'left' \| 'center' \| 'right'>; … }[]` | ✅ | Fields to display as columns — the PROJECTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: this list is the candidate set AND the baseline order; `hiddenFields` subtracts from it and `fieldOrder` orders what survives. `hiddenFields` and `fieldOrder` cannot add a field omitted here. An empty list declares no projection, so neither of them applies: which columns show is then left to the renderer (objectui's `ListView` grid derives the object's default columns). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Filter criteria (JSON Rules) | | **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | | | **searchableFields** | `string[]` | optional | Fields enabled for search | @@ -2333,8 +2333,8 @@ This schema accepts one of the following structures: | **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting | | **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) | | **rowColor** | `{ field: string; colors?: Record }` | optional | Color rows based on field value | -| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view | -| **fieldOrder** | `string[]` | optional | Explicit field display order for this view | +| **hiddenFields** | `string[]` | optional | Fields to hide in this specific view — the SUBTRACTION of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: each name listed here is removed from the `columns` projection before `fieldOrder` orders the remainder. A name `columns` never projected subtracts nothing. | +| **fieldOrder** | `string[]` | optional | Explicit field display order for this view — the ORDERING of the per-view field composition `columns` x `hiddenFields` x `fieldOrder`: it sorts what survives `columns` minus `hiddenFields` and never adds a field. A surviving column absent from `fieldOrder` sorts LAST, after every listed one, keeping its `columns`-relative order; a name listed here that did not survive orders nothing. | | **rowActions** | `string[]` | optional | Actions available for individual row items | | **bulkActions** | `string[]` | optional | Actions available when multiple rows are selected | | **bulkActionDefs** | `{ name: string; label?: string; icon?: string; variant?: Enum<'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'outline'>; … }[]` | optional | Rich bulk action definitions (schema-driven, executed via BulkActionDialog). Use a def for a mass data-plane mutation ('update' with a `patch` / 'delete') that no action expresses, or for an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the action it NAMES once for the whole selection — the renderer injects `params._selectedIds: string[]` (read that on the server, not `recordId`) so a single call can produce one aggregate artifact (zip of QR codes, merged PDF, batch print). Aggregate results are all-or-nothing: a handler that cannot cover the whole selection must reject, and per-row retry is replaced by re-running the action. `batchSize` does not apply (the call is never chunked); set `maxRecords` on defs whose server work is expensive. For the PER-RECORD dispatch use `bulkActions: ['']` instead — the bare-string form, promoted with the action's own label, params and `visible`; a 'custom' def without `execution: 'aggregate'` has no dispatcher and is refused at parse time. Toolbar url/api actions can also interpolate the current selection via `${ctx.selection.ids}` / `${ctx.selection.count}`. |