Repository navigation
chore(objectui): bump the console pin to 9dfaca654311 (carries objectui#11611, #11614 and #11619) - #21800
Conversation
Written by scripts/bump-objectui.sh 9dfaca654311cddd81714153c4f82c241d7cdc54 --no-commit. objectui@9dfaca654311cddd81714153c4f82c241d7cdc54 Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…at pin 9dfaca654311 Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
… re-measure pin citations (part) Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…aca654311 Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…54311 Every anchor in each asserting record was mapped through the objectui diff 2e818d0b51ec..9dfaca654311 and re-read; moved anchors are re-pointed with their text byte-identical, and each record gains a dated hop sentence. Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…clared-breaking entries Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
….span describe Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1daaae3700752d9aeb2c9cbbdcba31be63a5a6d7 && git checkout 1daaae3700752d9aeb2c9cbbdcba31be63a5a6d7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 4b9519ea23d31a3b7eb688f32bb52a895f8f7533 && git checkout -B drift-repro e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 && git merge --no-ff 4b9519ea23d31a3b7eb688f32bb52a895f8f7533
node scripts/docs-audit/affected-docs.mjs --json e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8
|
… decision in words instead of a tracker number (stage 15) (objectstack-ai#21810) Part of objectstack-ai#20749 Clause-②: no Stage 15 of this card: the next area of class (e), the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on objectstack-ai#20513. This stage takes the first name-ordered file group directly under `packages/spec/src/data/`: the 20 test files from `aggregate-field-type-compatibility.test.ts` to `date-range-presets.test.ts`. They carried 94 messages and 102 tracker ids, citing 60 records. Every one of those ids now either states what its record decided, in words (form D), or is dropped where the title already says it. Text only: no assertion, identifier, test count or code comment changes. ## Census at the base (`0a3480311a`) Instruments: `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`), `census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), `census-wide.cjs` (md5 `c98410a19529c439adb0afbfb00026a2`) and `dirtable.cjs` (md5 `dda605c54745b4a60cc14c9a686e4eff`). They are byte-identical to the copies stages 10 to 14 used. A literal counts as a test title when its folded message is argument 0 of a `describe` / `it` / `test` call, `.each` / `.skip` / `.only` chains included. Everything else is an "other" string. Both instruments read **1325 messages / 1406 ids in 282 files**, the seat's reading at `0a3480311a` (stage 14's head). | directory | files | messages / ids | titles | other | |:--|--:|--:|--:|--:| | `data/` (this PR: the first 20 files) | 95 | 468 / 501 | 445 / 475 | 23 / 26 | | `ui/` | 81 | 393 / 416 | 375 / 398 | 18 / 18 | | `api/` | 40 | 189 / 201 | 181 / 193 | 8 / 8 | | `system/` | 34 | 154 / 165 | 128 / 138 | 26 / 27 | | (files directly in `src/`) | 30 | 118 / 120 | 117 / 119 | 1 / 1 | | `ai/` | 1 | 2 / 2 | 0 | 2 / 2 | | `contracts/` | 1 | 1 / 1 | 0 | 1 / 1 | | **total** | **282** | **1325 / 1406** | **1246 / 1323** | **79 / 83** | The group reads **94 messages / 102 ids in 20 files**, the seat's figures, file for file: | file (under `data/`) | messages / ids | titles | other | |:--|--:|--:|--:| | `aggregate-field-type-compatibility.test.ts` | 4 / 4 | 4 / 4 | 0 | | `analytics-date-range-closed-vocabulary.test.ts` | 1 / 1 | 1 / 1 | 0 | | `analytics-date-range-two-bound-window.test.ts` | 3 / 3 | 3 / 3 | 0 | | `analytics-query-window-integer.test.ts` | 2 / 2 | 2 / 2 | 0 | | `analytics-strictness-batchd.test.ts` | 9 / 9 | 9 / 9 | 0 | | `analytics.test.ts` | 6 / 6 | 6 / 6 | 0 | | `api-derivation.test.ts` | 6 / 6 | 6 / 6 | 0 | | `api-methods-batch-conformance.test.ts` | 3 / 4 | 1 / 1 | 2 / 3 | | `authoring-key-lint.test.ts` | 2 / 2 | 2 / 2 | 0 | | `autonumber-format.test.ts` | 3 / 3 | 3 / 3 | 0 | | `autonumber-unanchored-boundary.test.ts` | 3 / 4 | 3 / 4 | 0 | | `bulk-write-hook-conformance.test.ts` | 2 / 2 | 2 / 2 | 0 | | `calendar-day.test.ts` | 2 / 2 | 2 / 2 | 0 | | `context-tokens.test.ts` | 1 / 1 | 1 / 1 | 0 | | `currency-mode-family-closure.pin.test.ts` | 2 / 2 | 2 / 2 | 0 | | `currency-precision-iso4217.test.ts` | 7 / 7 | 7 / 7 | 0 | | `data-engine.test.ts` | 20 / 25 | 20 / 25 | 0 | | `datasource-credential-redaction.test.ts` | 6 / 6 | 6 / 6 | 0 | | `datasource.test.ts` | 10 / 10 | 10 / 10 | 0 | | `date-range-presets.test.ts` | 2 / 3 | 2 / 3 | 0 | | **20 files** | **94 / 102** | **92 / 99** | **2 / 3** | - **Controls.** Lit, a title: `data/document.test.ts` reads 2 / 2 at the head. Lit, "other" strings: the two in `data/external-lookup-retirement.test.ts` (`:89`, `:130`) still read at the head. Dark: the file comment at `data/analytics-strictness-batchd.test.ts:4` (it names the strictness batch by its number) reads 0. Planted in a scratch copy of the head `data/calendar-day.test.ts`: an id put into a title reads 1 / 1, and an id put into a comment reads 0. - **A wider pattern** (any `#` plus digits) reads the same totals in 19 of the 20 files. In `aggregate-field-type-compatibility.test.ts` it reads one more, a decision-batch number at `:150` that sits beside a cited record in the same literal. The gate's pattern needs three to five digits, so it is not counted there. - **At the head:** 1231 messages / 1304 ids in 262 files. The 20 files read 0 / 0 on both patterns, and no other file moved. ## How the area was chosen `data/` has no subdirectory to split by (449 ids directly under it, `data/driver/` 52), so its stages take name-ordered file groups near the ~100-id bound, as stage 14's report proposed. This census reads the first group at exactly 102, the claim's figure, so the rule needed no re-cut. **Named for the next stages** (re-cut from the head census, 1231 / 1304; `data/` 374 / 399 left): - `data/` in four more stages, name-ordered: 1. `default-value-shape.test.ts` to `filter-comparand-shape.test.ts`: 20 files, 94 messages / 100 ids; 2. `filter-comparand-type.test.ts` to `filter-view-operator-parity.test.ts`: 20 files, 95 / 99; 3. `filter.test.ts` to `object.test.ts`: 17 files, 103 / 114. `object.test.ts` alone carries 42, so no cut lands nearer the bound; 4. `query-transport.test.ts` to `validation.test.ts` (11 files, 34 / 34) with `data/driver/` (7 files, 48 / 52): 86 ids. - `ui/` 416, about four stages. `api/` 201, two. `system/` 165, two. The files directly in `src/`, 120, one. - The three docblock needles (`ai/build-progress.test.ts:236`, `:237`, `contracts/approval-service.test.ts:274`), one stage with their docblocks. ## What each id became 24 literals (28 ids) now state a decision in words. 2 literals (2 ids) get their subject back in words where the number stood in for it. 69 literals (72 ids) drop a number the title already explains. (95 literals in 94 messages: the `sys_organization` reason string is one message over two lines.) Every cited record was read with its comments through REST: 55 answer 200. objectstack-ai#6345, objectstack-ai#8876, objectstack-ai#9040, objectstack-ai#10194 and objectstack-ai#17014 answer 404, and their decisions were read from what landed: `e2798fa` (one driver vocabulary for start and migrate), `d634e66` (the username half of the URL userinfo grammar), `2420641` (a credential in the mongo `options` passthrough is refused), `2306a76` (`theme` / `analytics_cube` validated at the `/meta` write door) and `80aef80` (a one-day window for the one-day presets), each with its CHANGELOG entry. No cross-repo record is cited in this group. | record(s) | literal (under `data/`) | now reads | |:--|:--|:--| | objectstack-ai#11152 | `aggregate-field-type-compatibility.test.ts:150` | "accepts `sum` / `avg` / `min` / `max` over booleans — numbers on every backend, a ruling that outranks the refused-by-default rule". The maintainer ruled that booleans aggregate as numbers on every backend; decision batch 80 held that ruling over batch 59's blanket refusal of unnamed pairs. That batch number went with the id. | | objectstack-ai#4001 (3) | `analytics-strictness-batchd.test.ts:83`, `:248`, `:306` | "batch D, unknown keys refused — …" before "the doors the cube family is reachable through", "alias claims are true of the surfaces they point at" and "deliberate non-closures (re-verdicts, not omissions)". The campaign's decision: an unknown key is refused, not stripped. | | objectstack-ai#3878 (2) | `analytics-strictness-batchd.test.ts:270`, `:297` | "matching the dispatcher's bespoke hint at the /analytics entry" and "the retired-envelope tombstones still fire". The body is the bare `AnalyticsQuery`; the `{ cube, query }` envelope was retired with tombstones, and the entry answers 400 with a hint at `where`. | | objectstack-ai#18612 | `analytics.test.ts:314` | "a persisted cube heals at the door — the retired join `sql` / `relationship` are stripped (ADR-0087 D2)". | | objectstack-ai#3391 | `api-derivation.test.ts:16` | "api-derivation — one table resolves the effective operations from six primitives". The server is the only adjudicator, through one derivation table. | | objectstack-ai#3543 | `api-derivation.test.ts:286` | "vocabulary split — authors write six primitives, the wire speaks operations". The authored enum shrank; the wire vocabulary stayed byte-stable. | | objectstack-ai#15873 | `api-methods-batch-conformance.test.ts:221` | A declared reason string: "(a ruling grants `update`; both are column-clamped per row by ADR-0092 D2)". Option (a), decision batch 64. | | objectstack-ai#3786 | `authoring-key-lint.test.ts:37` | "lintAuthoredRecordKeys — an unknown authoring key is reported, not swallowed", the decision its source docblock records. | | objectstack-ai#6555 | `autonumber-format.test.ts:23` | "DEFAULT_AUTONUMBER_FORMAT / resolveAutonumberFormat — one declared default both sides read". Route 3: `{0000}` became the contract default, and both fallbacks went away. | | objectstack-ai#5038 | `bulk-write-hook-conformance.test.ts:114` | "records the after half as DELIVERED — the engine fires it once per row". | | objectstack-ai#5574 | `bulk-write-hook-conformance.test.ts:119` | "records the before half as DELIVERED — the engine dispatches it per row too". | | objectstack-ai#20126 | `currency-mode-family-closure.pin.test.ts:348` | "currency-mode family — the enumerating closure pin: `defaultCurrency` holds only under `fixed`". | | objectstack-ai#19992 | `currency-precision-iso4217.test.ts:163` | "the removed `currencyConfig.precision` at rest: a stored row carrying the baked `precision: 2` is served canonical". | | objectstack-ai#7918 | `currency-precision-iso4217.test.ts:224` | "… where the ISO 4217 width check used to refuse it". That check was the record's option A, later reversed. | | objectstack-ai#3407, objectstack-ai#6437 | `data-engine.test.ts:1185` | "DroppedFieldsEventSchema.reason — why a write dropped submitted fields, widened past the readonly pair". | | objectstack-ai#6262, objectstack-ai#6433, objectstack-ai#6435 | `data-engine.test.ts:1198` | "primary_key is the value the engine reports when it strips a payload id it ruled is not an identifier", the schema's own wording of that strip on the bulk and the by-id paths. | | objectstack-ai#8300 | `datasource-credential-redaction.test.ts:70` | "(the drift guard on the one credential-key definition)". | | objectstack-ai#8876 | `datasource-credential-redaction.test.ts:232` | "— the username half of the same alignment". | | objectstack-ai#8337 | `datasource-credential-redaction.test.ts:243` | "redactUrlCredentialQueryParams — the read half: a credential query parameter is never served back". | | objectstack-ai#8153 | `datasource.test.ts:673` | "— unchanged by the managed-row credentialsRef allowance". The ruling allowed `external.credentialsRef`, and only it, on managed rows. | | objectstack-ai#4614, objectstack-ai#8793 | `date-range-presets.test.ts:14` | "date-range preset vocabulary — one source of truth, read by both the UI and the data side". | **Subject restored (2 ids):** objectstack-ai#20126 at `currency-mode-family-closure.pin.test.ts:403` ("currency-mode closure controls — each rule can fail, and passes what it must") and objectstack-ai#7918 at `currency-precision-iso4217.test.ts:311` ("carries the measured anchors — 0 digits for JPY, 2 for USD, 3 for KWD"). That literal moved from double to single quotes, since it no longer holds an apostrophe. **Dropped only (72 ids):** objectstack-ai#1603, objectstack-ai#2377, objectstack-ai#3026, objectstack-ai#3391, objectstack-ai#3543, objectstack-ai#3545, objectstack-ai#3795 (9), objectstack-ai#4001 (2), objectstack-ai#4286, objectstack-ai#4346 (2), objectstack-ai#4538, objectstack-ai#4583, objectstack-ai#5586, objectstack-ai#6345, objectstack-ai#6555, objectstack-ai#6560, objectstack-ai#7178 (5), objectstack-ai#7265, objectstack-ai#7287 (2), objectstack-ai#7802 (2), objectstack-ai#8032, objectstack-ai#8057 (2), objectstack-ai#8153 (7), objectstack-ai#8336, objectstack-ai#8337, objectstack-ai#9040, objectstack-ai#10194, objectstack-ai#10414, objectstack-ai#13802, objectstack-ai#16041, objectstack-ai#16632, objectstack-ai#17014, objectstack-ai#17296, objectstack-ai#17598 (2), objectstack-ai#18278, objectstack-ai#19992 (3), objectstack-ai#20011, objectstack-ai#20300 (2), objectstack-ai#20550, objectstack-ai#20600, objectstack-ai#20808 (3), objectstack-ai#21365 (2). - Each of these titles already states the decision it pins: for example "empty array → deny-all (flipped semantics)" for objectstack-ai#3391, "accepts the BARE query string — the canonical ADR-0061 D1 spelling" for objectstack-ai#7178, or "`currencyConfig.precision` is removed: refused with the prescription, whatever its value" for objectstack-ai#19992. - **Small rewordings that carry no new claim:** `analytics-strictness-batchd.test.ts:307` reads "are CLOSED now" where it named the record; `autonumber-unanchored-boundary.test.ts:51` reads "(ruled: mixed content is out of contract)"; `datasource.test.ts:553` reads "(the happy path)". The circled part numbers after objectstack-ai#17598 went with the id. - **The two `api-methods-batch-conformance.test.ts` reason strings** (`sys_api_key`, `sys_organization`) end "rather than hitting /batch." now. The table is read only through `!== undefined`, so no assertion reads their text. ## Readers - **Test-name filters:** none. A tracked-tree search for `-t` and `--testNamePattern` finds only `packages/qa/dogfood/README.md:142` (`-t "owner-scoped"`), which is unrelated. - **Snapshots:** none. No `__snapshots__` directory exists under `data/`, and no `.snap` file is tracked under `packages/spec`. - **Projects:** two touched files are listed in `packages/spec/vitest.repo-tests.json`: `api-methods-batch-conformance.test.ts` and `currency-mode-family-closure.pin.test.ts`. Both were run in the `repo` project at the base and at the head, and the other 18 in `local`. - **By substring:** every old literal, plus a window around each id (289 needles), was searched across the tracked tree outside its own file. No gate, doc, filter, snapshot or `scripts/check-*.mjs` self-test reads one. The 14 hits are: - **sibling titles in other lanes:** `service-analytics` `aggregate-nontemporal-measure-refusal.test.ts:344` and `objectql` `engine-autonumber-default-format.test.ts:248`; - **this card's later `data/` stage:** `data/driver/postgres.test.ts:169`, the same "placeholders are not resolved here" title, already in the census; - **comments, CHANGELOG, an audit ledger and liveness evidence:** `lint` `validate-dataset-measure-aggregates.test.ts:179`, `service-analytics` `dataset-compiler.ts:227`, `objectql` `engine.ts:6206` and `:6272`, `analytics.zod.ts:1002`, `docs/audits/2026-07-unknown-key-strictness-ledger.md:728`, two `packages/spec/CHANGELOG.md` entries and the `liveness/field.json:218` evidence string, which quotes the `engine.ts` comment. None reads a test title. - **Same-text titles named in stage 14's ACCEPT** (`(objectstack-ai#15680)`, `(objectstack-ai#5955)`, `objectstack-ai#3896 close-out`): none falls in this group. ## Text-only proof Stage 10's scratch tool (`textonly10.cjs`, md5 `d5e4801dbb4329ab1984da91e92fc47c`) compares base and head file by file on three legs: 1. **Skeleton:** the full AST, with string pieces masked. It must be identical. 2. **Comments:** every comment, byte-equal. 3. **Strings:** each changed string leaf must sit in a test-call title position or on a declared line, must carry a tracker id before, and must carry no `#` plus digits after. The declared lines are the three reason-string leaves in `api-methods-batch-conformance.test.ts`. - **Result:** 20 of 20 files SAME on all three legs, as predicted in writing before the run. - **Totals:** 95 changed literals, 92 titles and 3 declared. The diff's `+` and `-` lines are exactly the 95 planned lines, and every file keeps its line count. - **Controls (10 of 10 as predicted, on scratch copies, each anchor hit once):** identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string given an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; a declared string given a new id VIOLATION; an undeclared `expect` message changed VIOLATION; a title re-split into a `+` chain DIFF. **Test counts:** the 20 files were run at the base, in a separate base worktree, and at the head, with `--project local --project repo`. Both sides read 553 / 553 passed, with the same count and status sequence per file in 20 of 20. 291 full test names change, and each equals the base name with the planned replacements applied (0 mismatches). No full name repeats on either side. ## `main` merged in, once objectstack-ai#21800 (the console pin bump) landed while this branch was being verified, and it rewrites the comment block at `:61-77` of `api-methods-batch-conformance.test.ts`. This PR edits only string literals in that file, more than 100 lines below the block, so `origin/main` (`18c2ddc1ec`, which also carries objectstack-ai#21801) was merged in with a plain merge, no rebase, and no conflict. The PR's delta against `main` is still exactly the 20 files, +95 / -95. Every reading in this body was re-taken on the merged head `bf16ad1190`, against `18c2ddc1ec` as the base: the census (1325 / 1406 there, 1231 / 1304 here, unchanged by the two commits), the text-only proof and its controls (the three declared lines now sit at `:202`, `:230` and `:235`), the 20-file runs, the full build, the suite, the typecheck and the gates. Re-fetched just before this PR opened, `origin/main` was one commit further (`75ddcd1b41`, objectstack-ai#21805, in `cloud-connection`, `metadata-core` and `runtime`). It touches no `packages/spec` path and no file here, so it was not merged. ## Changeset: `skip-changeset` Measured, not assumed: - `npm pack --dry-run` of `@objectstack/spec` lists 2068 files. 0 of the 20 touched files are in it, and no `*.test.ts` at all. Of `src/`, only the `*.zod.ts` sources ship: the controls `src/data/analytics.zod.ts`, `src/data/data-engine.zod.ts` and `dist/data/index.js` are in it. - In the built `dist/`, five new phrases and four old literals each read in 0 files. The control `Unrecognized key(s) on` reads in 42. So this PR publishes nothing, and no changeset is added. ## Verification (at `bf16ad1190`) - `pnpm turbo run build` over all packages: 71 / 71 (also 71 / 71 at the pre-merge head `89c4b300c2`). - `@objectstack/spec`: - `vitest run --project local`: 615 files, 18360 passed, 1 todo. - `typecheck` exit 0, including `check:test-typecheck` (52 files / 246 errors / 135 pinned signatures held). Its program holds all 20 touched files, counted with `tsc --listFilesOnly -p tsconfig.test.json`. - `check:generated`: all 15 generated artifacts up to date after the merge. - **Gates:** `dispatch-gates --commands` derived 79 families, the same set as stages 13 and 14, and all 79 exit 0. `--ran` reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN. - The five roster families whose rosters sit under a touched directory were also run, and each exits 0: `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing` and `check:filter-alias-parity`. - **ESLint, a proven narrowing:** `--no-inline-config` over the 20 files reads 0 errors and 0 warnings. The population comes from ESLint's own config: 20 configured, 0 ignored. No file sets `parserOptions.project` or `projectService`, so no untouched file's verdict can move. - `check-governed-merges --test`: NOT governed, 190 changed lines. ## Acceptance notes - **No needle in this group.** Every id was a title or a declared reason string; no expected value of an assertion over a source docblock was found. The three known needles are untouched. - **Same-id test titles in other packages** are their lanes' test-string shares. A search of `describe` / `it` / `test` lines outside `packages/spec` finds 156 lines citing ids this PR handled, in 79 files of 23 packages: `objectql` 73 (29 files), `rest` 18 (7), `runtime` 7 (4), `plugin-security` 6 (5), `cli` 6 (3), `lint` 6 (4), `service-datasource` 6 (3), `driver-sql` 4 (4), `platform-objects` 4 (2), `plugin-approvals` 3 (2), `service-automation` 3 (2), `driver-mongodb` 3 (1), `plugin-auth` 3 (1), `service-analytics` 3 (3), `metadata-core` 2 (1), `plugin-hono-server` 2 (1), and one each in `client`, `triggers`, `core`, `metadata-protocol`, `qa/dogfood`, `types` and `driver-memory`. - **Two spec test files outside `src/`** carry same-id titles: `packages/spec/scripts/file-description.test.ts:66` and `packages/spec/scripts/format-type.test.ts:85`. They are outside class (e) as ruled ("the test strings shipped under `src/`"). - **Numeric delivery fields:** `bulk-write-hook-conformance.test.ts:115-116` and `:129-130` assert `engineDeliveryIssue: 5038` / `5574`, numbers in the source contract table. They are not strings, the gate's pattern cannot see them, and they are not this card's share. - **Code comments still carry ids** in these files and their sources, for example the header of `analytics-strictness-batchd.test.ts` and the `SINGLE_RECORD_WRITE_ONLY` comments in `api-methods-batch-conformance.test.ts`. Comments are not this card's share, and none is touched here. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ Co-authored-by: Claude <noreply@anthropic.com>
…t at the objectui pin (objectstack-ai#21824) Part of objectstack-ai#21765 Clause-②: no Seam: `spec:ObjectSchema.imageField` → `renderer:objectui record chrome (page:header, containers.tsx)` **Why `Part of` and not a closing line.** The card's item 3 stays open: whether Studio's object form offers `imageField`. Its `omit` row lives in this repo, and a written rule says the row is stale once the key is enforced. The same rule hands the offer itself to a decision, and the gate refuses the row's deletion unless that decision is made in the same change (measured below). So item 3 goes back to the seat as an open question. This PR does items 1 and 2, and adds the declaring example the director seat's unblock asked for. ## The reader, measured at the pin `.objectui-sha` on `main` is `9dfaca654311`. `git merge-base --is-ancestor c096f032793d 9dfaca654311` exits 0 in the objectui clone, so the pin carries objectui PR 11619's merge. In objectui at `9dfaca654311`, `packages/components/src/renderers/layout/containers.tsx`: - `:1474` `PageHeaderRenderer`. On its record-context branch (`objectSchema` and `data` from the record context), `:2392` reads `objSchema?.imageField`. - `:1458` `recordPictureUrl(value)` resolves the served row's value of that field. It takes the expanded `{ url }` form, a bare `sys_file` id (served from `/api/v1/storage/files/`), a legacy URL string, or the first entry of a `multiple` list that resolves. - `:2449` `icon={recordPicture}` puts the picture in the record chip's `icon` slot beside the H1. An `avatar` field is drawn round and cropped, an `image` field whole in a rounded square. An empty value draws nothing: no initials, no placeholder. ## What changed 1. **Liveness row** `packages/spec/liveness/object.json` `imageField`: `planned` → `live`, `verifiedAt` 2026-10-05, `evidenceScope: cross-repo`. The evidence has three parts: the authoring judgement (`object.zod.ts#refuseNonPictureImageField`), plus `objectui 9dfaca654311` `containers.tsx#PageHeaderRenderer` and `#recordPictureUrl` with the lines above. The note is rewritten to what is true now. Both halves are live, the carrier is gone, and the note names the reference-app declaration. 2. **Describe and TSDoc** `packages/spec/src/data/object.zod.ts`: - The `.describe()` drops "Pending renderer: the record chrome does not draw it yet." Its "is to draw" becomes "draws". - The TSDoc paragraph that said the reader "does not read the key yet" now names the reader (`PageHeaderRenderer`, the record chip's icon slot). No shape, refusal or error text moves. 3. **Hand-written doc** `content/docs/data-modeling/objects.mdx` → Display: "is to draw ... once the renderer reads it; no renderer draws it yet" becomes "draws beside the title". The row also gains the describe's own sentence: an empty field shows no picture. 4. **Regenerated by the repo's tooling, not by hand.** `pnpm --filter @objectstack/spec check:generated` named two stale artifacts, `check:docs` and `check:liveness`. `--fix` regenerated exactly those two: - `content/docs/references/{api/metadata,data/object,system/migration}.mdx`; - `liveness/state-counts/object.md`, where `object` moves to live 51 / planned 1. 5. **Declaring example** `examples/app-showcase/src/data/objects/field-zoo.object.ts`: `imageField: 'f_image'`, on the existing `Field.image()`. This is the object and field the pin-bump smoke drew with a temporary edit. - Not seeded. The showcase seeds the field zoo, but a stored `image` value is a managed `sys_file` id. The seed's own note on `showcase_task.cover` (`src/data/seed/index.ts`, ADR-0104) says why a seed cannot honestly mint one. 6. **Changeset** `.changeset/21765-object-image-field-live.md`: `@objectstack/spec` `patch`, `Clause-②: no`. It is text only. ## Item 3: Studio's object-form `omit` row (measured, not decided) - **Where it lives:** in this repo. The row is `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:394-400`, in the group "Declared, not enforced yet — no offer until it is enforced" (`:386`). The form it excuses is `packages/spec/src/data/object.form.ts`. - **The written rule** is at `:307-309`: "The not-enforced-yet rows hold only while the verdict does. Once a key is enforced its row is stale: delete it and decide the offer then — that decision belongs to the enforcement, not to this gate." - **Deleting the row alone, measured** on the committed state with `scripts/ablation-replace.mjs` in wrap mode: - The anchor hit 1 → 0, blob `26f47a279c33` → `bb160bb1b747`. - The run went **1 failed / 75 passed**: `object: every top-level key the author may write is offered, or its omission is recorded`, which printed "object.(root): accepted by the Zod but unauthorable in the form — offer it, or add a root ledger entry that records why it is not offered: expected [ 'imageField' ] to deeply equal []". - Restore: blob == HEAD (`26f47a279c33`) and `git diff HEAD` empty. Baseline before the probe: 76/76. - **Why it is left unchanged.** The rule's first half cannot land without its second half, and the rule calls the second half a decision. Neither way out is mechanical: - A form row repeats the call `objectstack-ai#19331` made for 45 keys. It also takes the four `metadata-forms.generated.ts` catalogs in `packages/platform-objects`, with authored `zh-CN` / `ja-JP` / `es-ES` leaves. Those files are outside this claim's file surface. - A recorded reason fits no existing class. The ruled classes admit only their ruled key lists. - **Consequence while it is open:** from this PR's landing, the row's reason says "liveness verdict `planned`" while the ledger says `live`. The test reads liveness for no class except the three ruled ones, so it stays green. The stale reason is what the open question is about. ## Tests and gates (tree `80a7677773`, the final commit) - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: **615 files passed, 18360 passed / 1 todo**, `VERDICT command-exit 0`. - `pnpm --filter @objectstack/spec run typecheck`: exit 0, "52 file(s) / 246 error(s) / 135 pinned signature(s) held". - `pnpm --filter @objectstack/example-showcase verify`: `os validate` "Validation passed", `tsc --noEmit` clean, **32 files / 399 tests passed**, `VERDICT command-exit 0`. The dependency closure was built first (`turbo run build --filter=@objectstack/example-showcase^...`, 60/60). - `check:liveness`: exit 0. `object` reads 52 classified (live 51, planned 1); the planned one is `externalSharingModel`. The repo total reads 1000 live · 1 experimental · 1 live-elsewhere · 108 dead · 8 planned = 1118. The repo-local evidence path and the `#refuseNonPictureImageField` anchor resolve. The `objectui:` paths are attributed and counted, never resolved: that is the gate's boundary. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **107** commands from 9 paths against merge base `75ddcd1b4`. `--ran` reconciles **107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN**, and every one exited 0. - Lint, a measured narrowing: - `eslint --no-inline-config --format json` on the two changed TS files: **2 files, 0 errors, 0 warnings**. - The population is `eslint.config.mjs:971` (`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`). - No type-aware linting (`eslint.config.mjs:328`, no `parserOptions.project`), so this diff cannot move a verdict on an untouched file. - Served-object check on a fresh showcase backend (my own port, torn down after): - `GET /api/v1/meta/object/showcase_field_zoo` serves `imageField: 'f_image'` with `fields.f_image.type: 'image'`. That is the `objectSchema` the reader reads. - I uploaded a 1×1 PNG through `/api/v1/storage/upload/presigned` + `complete` and set `f_image` to its id. The record then serves `f_image` as `{ id, name, size, mimeType, url: '/api/v1/storage/files/…' }`, the form `recordPictureUrl` takes first. That URL answers 200 `image/png` after the redirect, with bytes identical to the upload. - Field-consumer warning, an `os validate` A/B on the declaration. Without it, `f_image` carries "declared but nothing in this stack reads or displays it" (71 warnings). With it, that warning goes and nothing else moves (70). - No ablation or reverse verification applies: this PR authors no behaviour. It moves a ledger row and text, and adds one declaration the parse already accepts. ## Acceptance notes - **Browser check: NOT MEASURED.** It needs a console build at the pin, about 9 minutes of the shared box. The pin-bump smoke on PR objectstack-ai#21800 already drew this picture on `showcase_field_zoo` / `f_image` at this same pin. This PR makes that temporary declaration permanent, and the served-object check above covers what the reader reads. - `packages/spec/CHANGELOG.md` still says `imageField` "is accepted, stored and served but nothing draws it". That entry is release-owned and was true when it shipped, so it is not touched. - The `omit` row's `why` text is stale from this PR's landing; see item 3 above. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ui#11636, objectstack-ai#11637, objectstack-ai#11635, objectstack-ai#11624 and objectstack-ai#11640) (objectstack-ai#21827) Fixes objectstack-ai#21807 Clause-②: no This moves the bundled Console's objectui pin from `9dfaca654311` (objectstack-ai#21772, PR objectstack-ai#21800) to `0abd4f9f8769fc4c19ad2f96707684876f74c09f`, which was objectui `main` at write time and is past `39a3e91fad`. The Console now carries objectui#11636, the fix for objectui#11092: the screen-flow runner names the flow by its served label, translated. That merge is what objectstack-ai#20318 waits on. The range also carries objectui#11637 (objectui#11170), objectui#11635 (objectui#11095), objectui#11624 (objectui#11396) and objectui#11640 (objectui#11628). ⛔ This is not a release act. Version PR objectstack-ai#21352 is untouched, and no tag, publish or Release was made. ## Range - objectui `git ls-remote origin refs/heads/main` read `0abd4f9f8769fc4c19ad2f96707684876f74c09f` at 2026-10-05T04:53:29Z, just before the bump. - Re-read at 05:37:53Z, it reads `59917c4b2` (objectui#11639, a served view's toolbar change written inside `config`), one commit past the pin. This PR does not carry it. - The objectui clone is full (`--is-shallow-repository`: false). `git merge-base --is-ancestor` exits 0 against `0abd4f9f8` for `39a3e91fa`, `c4c506b9e`, `22ddcd5c5` and `1c2e2c46c`. - `9dfaca654311..0abd4f9f8769` has 5 commits and 0 merges. - objectui declared 5 changesets over the range: 3 release and 2 release nothing. Every commit carries a changeset. None declares `major`, and none carries the breaking annotation. The highest declared level is `minor`, so the console changeset is `minor`. These counts come from the bump's own digest and were re-read from the changeset blobs. - **No commit subject in the range carries `!`.** `git log --format='%h %s' 9dfaca654..0abd4f9f8 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'` matches nothing (exit 1). | objectui commit | landing | changeset | note | |---|---|---|---| | `1c2e2c46c` | objectui#11624 (objectui#11396): `MasterDetailDetailConfig` is derived from the spec's `details` entry by reference, member for member the same | release-nothing | moves the manifest (below) | | `22ddcd5c5` | objectui#11635 (objectui#11095): a dataset-bound KPI tile's re-read on the data-invalidation bus gets a pin; tests and a doc comment only | release-nothing | | | `39a3e91fa` | objectui#11636 (objectui#11092): the flow runner names the flow by `flows.FLOW.label`, then the served `flowLabel`, then the API name | minor (`Clause-②: yes (widening)`) | smoke below; unlocks objectstack-ai#20318 | | `c4c506b9e` | objectui#11637 (objectui#11170): one declaration of per-type NODE SLOTS; `objectui check`, core `validateSchema`, the SDUI parser's `validateTree` and the `kind:'html'` compile walk them | minor (`Clause-②: yes (narrowing)`) | answered below | | `0abd4f9f8` | objectui#11640 (objectui#11628): the External Datasource panel unwraps the `{ success, data }` envelope | patch | smoke below | ## Declared-breaking entries and the ADR-0087 disposition **There are none to dispose of.** No commit subject carries `!`, no changeset declares `major` or the breaking annotation, and the bump wrote no `adr-0087: TODO` placeholder. `check-adr-0087-registration --base origin/main` reports "this PR adds no declared-breaking changeset". `check-changeset-no-major --base origin/main` reports "This diff introduces no `major` bump". One entry narrows by its own declaration, so here is how this repo answers it. **objectui#11170 (`c4c506b9e`, `Clause-②: yes (narrowing)`)** widens the reach of four objectui validators: each now judges nodes held in a renderer's declared slots, not only in `children`. It adds, removes or renames no ObjectStack-authorable key, and no Zod schema or stored `sys_metadata` shape moves. Its sdui-parser half only takes effect when a manifest is built with `slotsFor`. This repo's `scripts/gen-sdui-manifest-node.mjs` calls `manifestFromConfigs(configs)` without that option, and the regenerated `sdui.manifest.json` carries 0 `slots` keys. So the save gate's walk does not move with this bump. The lockstep reading is in Acceptance notes. ## What changed here (22 files, +255 / -93) - **`.objectui-sha` and `.changeset/console-0abd4f9f8769.md`.** `scripts/bump-objectui.sh 0abd4f9f8769fc4c19ad2f96707684876f74c09f --no-commit` wrote both, with `OBJECTUI_ROOT` set to a read-only objectui clone in the scratchpad. The range walked completely and the level was auto-set to `minor`. - The digest rendered objectui#11170's bullet as its first line, the bare `**Clause-②: yes (narrowing)**`. That bullet is rewritten to say what landed. - A closing paragraph states the range has no declared-breaking entry and names the release-nothing pair. - **`sdui.manifest.json` and `scripts/sdui-manifest.record.json`.** `node scripts/gen-sdui-manifest-node.mjs` regenerated them over the tree that `pnpm objectui:build` built at the pin. It still has 107 components, and the sha256 moves from `6f921896ffac…` to `f95d406a584e…`. - One input moved: `object-master-detail-form`'s `details` gains `of: "object"` and a description of the spec's closed entry (objectui#11396). `"of": "object"` occurrences go from 12 to 13. - The record moves its pin and `modulesRoot`. objectui's workspace version stays 17.7.0, confirmed against the pin by `check-sdui-manifest --require-objectui`. - **`packages/sdui-parser/objectui-lockstep.json`.** `gen:sdui-lockstep` re-recorded it from the clone at the pin. It records 214 grammar lines (blob `0131f27cf86d`), 25 diagnostic codes and containment predicate `76c18fb95d1f`, all unchanged, so no port is owed by that gate. - **The 54 asserting pin citations in `packages/spec/src`, re-measured, not restamped.** - **Method.** The range changes 50 paths. Each asserting record's cited objectui paths were resolved against the tree at `9dfaca654`. Ambiguous bare names were disambiguated by the record's own directory. Each `index.tsx` and `types.ts` cited is a `plugin-kanban`, `plugin-dashboard`, `plugin-map`, `plugin-gantt`, `plugin-grid`, `plugin-tree` or `plugin-timeline` file. None is `plugin-form/src/index.tsx` or `sdui-parser/src/types.ts`, the two same-named files the range touches. - **Result.** No asserting record cites a changed path. So every cited file is byte-identical across the hop, and every anchor held unmoved. - **Records.** Each of the 41 hand-written records gains a dated 2026-10-05 hop sentence and keeps its earlier history. - **Counts.** Three records carry a count, and each was re-taken by its own method; all three read the same at `2e818d0b5`, `9dfaca654` and `0abd4f9f8`: - the `keyboardNavigation` hit lines: 15, against 3 for the `schema.editable` control; - `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd`: 2 each, against 11 for `onCardClick`; - the `ElementDataSourceGate` occurrences in the five `src/index.tsx` shells: 0, 3, 3, 3 and 4. - **Corpus counts.** The six migration entries' counts were re-taken with `git grep -o -F`. That method first reproduced every `9dfaca654` number: 7632 files, `objectstack` 17313, `@objectstack/spec` 7186, `timeout` 1360, `useState` 2477, `TTL` 182, `tenant` 1318, `RuntimeConfig` 293, `resourceLimits` 2, `window` 4193, `period` 238, `interval` 195, `metrics` 401 and `Span` 508. - The new readings are 7650 files, `objectstack` 17390, `@objectstack/spec` 7209 and `useState` 2478. `window` reads 4194. Every other control is unchanged. - All 98 checked tokens (the export lists of `plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and `metrics.zod.ts`, plus every named key) read the same at both pins: every zero is still zero, and `Span` / `SpanSchema` read 508 / 57. - `packages/spec/src/migrations/registry.ts` was regenerated with `gen:migration-registry`. - **`.changeset/objectui-pin-citations-0abd4f9f8769.md`** is a `@objectstack/spec` patch, because the `FormField.span` describe and six migration descriptions name the pin. `content/docs/references/ui/view.mdx` was regenerated by `check:generated --fix`. No example, test or gate needed adapting, and no code changed outside generated records, citations and changesets. ## Console build (the local Console Pin Gate equivalent) `turbo run build --filter=@objectstack/client... --filter=@objectstack/spec...` and then `pnpm objectui:build` ran as one command under the verify lock. That is a clean build: mode 3 shallow-cloned objectui at the pin into `.cache/objectui-0abd4f9f8769`. Exit 0, 10m51s on the shared box. The build log reports: - "Bundle canary 'import/jobs' present". - "Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5}". - **"Console bundle carries THIS tree's @objectstack/spec, and only it"**: the spec-injection check passes. - "@objectstack/console dist ready (64232 KB) from objectui@0abd4f9f8769". `check:console-sha` and `check:console-injection` (self-test 67 assertions, then the dist check) exit 0 against that dist. ## Browser smoke: `examples/app-showcase` with the Console built at `0abd4f9f8769` The server ran `pnpm dev -- --fresh --ui --no-watch -p 41907` with `OS_PORT=41907`, on its own ephemeral DB with the seeded admin. Headless Chromium (`/opt/pw-browsers/chromium`) drove it, signing in through the console's login form. Page errors, console errors and every 4xx/5xx response were captured. Only the PIDs this run started were stopped. **Flow label (objectui#11636 / objectui#11092).** The launch was Tasks list, select a row, then the toolbar's "Reassign…" (`showcase_bulk_reassign`, which targets the screen flow `showcase_reassign_wizard`, label "Reassign Task"). | leg | trigger answer | runner header line | dialog title (accessible name) | API name shown | completion toast | |---|---|---|---|---|---| | `en` | 200, `status: paused`, `flowLabel: "Reassign Task"` | `Reassign Task` | `New Assignee` (the screen's own title) | no | `Flow "Reassign Task" completed` | | `zh-CN`, with a bundle entry `flows.showcase_reassign_wizard.label` | 200, `flowLabel: "Reassign Task"` | `重新分配任务` | `New Assignee` | no | `流程「重新分配任务」已完成` | - The resume answered 200 with `flowLabel` on both legs. - **The showcase bundle declares no `flows` translations** (`git grep` finds none), so the `zh-CN` leg needed one to exist. It was made by a temporary local edit, as objectstack-ai#21800's smoke did for `imageField`: - `node scripts/ablation-replace.mjs --hold` added `flows: { showcase_reassign_wizard: { label: '重新分配任务' } }` to the `zh-CN` block of `examples/app-showcase/src/system/translations/index.ts`. - The server booted with `--compile`. - The file was restored at once with `--restore`. The tool reports the blob back to HEAD's `f0517049b565` and `git diff HEAD` empty, and `git status --porcelain` is empty. - `GET /api/v1/i18n/translations/zh-CN` served the entry, and the session's `html[lang]` read `zh-CN`. - The showcase `dist/` was rebuilt afterwards (`turbo run build --filter=@objectstack/example-showcase --force`), and the held string has 0 hits in `dist/objectstack.json`. Nothing of the edit is in this diff. - **Verdict:** at this pin the runner header and the completion toast name the flow by the active language's `flows.FLOW.label`, then the served label, and never by the API name. The launcher button still reads the ACTION's label ("Reassign…"), which is the action's own string and not the flow's. **The range's other landings.** | landing | surface | observed | |---|---|---| | objectui#11640 (objectui#11628) | Setup → `metadata/datasource/showcase_external` | The External Datasource panel lists the remote tables `customers` (7 columns) and `orders` (7), each with Import. "Refresh catalog" (`POST …/external/refresh-catalog` 200) shows `snapshot 10/5/2026, 5:25:23 AM`. "Run validation" (`POST …/external/validate` 200) renders "All 2 objects match the remote schema." with `showcase_ext_customer` and `showcase_ext_order`, and no render error. | | objectui#11624 (objectui#11396) | `page/showcase_project_workspace` (`object-master-detail-form`, `details: [{ title: 'Tasks', childObject: 'showcase_task', addLabel: 'Add task' }]`) | The master form draws its 6 fields. The Tasks section draws, and "Add task" opens the child's inline form with 14 more fields (Title*, Assignee, Priority, …). 0 page errors. | | objectui#11637 (objectui#11170) | the three `kind:'html'` pages: `showcase_start_here`, `showcase_capability_map`, `showcase_command_center_jsx` | All three render (1563 / 2426 / 882 characters of text), with no compile or validation text and 0 page errors. | | objectui#11635 (objectui#11095) | `dashboard/showcase_ops_dashboard`, `showcase_revenue_pulse`, `showcase_chart_gallery` | The ops tiles read Active Projects 2, At-Risk (Red) 1, Awaiting Review 2 and Total Budget 1,090,000, matching objectstack-ai#21710's REST cross-check. All three dashboards have 0 page errors. This landing changes no executable code (its changeset: tests and one doc comment). An out-of-band REST `PATCH` of a task to `in_review` did not re-read the open tile: it stayed 2 with 0 dataset queries in 6s, and read 3 after a reload. That mutation never travels the console's own invalidation bus, so this is no reading of objectui#11095's pin. **NOT MEASURED** there. | **Console messages across the run:** 0 page errors. The failed loads are the pre-login `401 GET /api/v1/auth/get-session`, one `404` per page load that the response listener did not attribute (`/favicon.ico` answers 404 on this server, as objectstack-ai#21800 recorded), and `404 GET /api/v1/meta/datasource/showcase_external?state=draft`, the panel's draft probe for a datasource that has no draft. ## Gates and tests (head `e40526e564`) - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 127 commands from the 22-path diff, and all 127 were run at `e40526e564` and exited 0. `--ran` reports "127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded. The full workspace build (`turbo run build --filter=!@objectstack/docs`) ran first, so no dist-reading gate met a missing prerequisite. - Also exit 0: - `check:objectui-pin-citations --verify-anchors` with `OBJECTUI_ROOT` at the pin: "54 asserting objectui pin citation(s) match .objectui-sha (0abd4f9f8)", and "7 anchor content assertion(s) verified against objectui at 0abd4f9f8". - `check:objectui-bump` (20 assertions across 5 cases), `check:sdui-lockstep`, `check-sdui-manifest --require-objectui`, `check:console-sha`, `check:console-injection`. - `check-adr-0087-registration --base origin/main` and `check-changeset-no-major --base origin/main`. - `@objectstack/spec check:generated`: all 15 artifacts current after the `--fix`. - `pnpm --filter @objectstack/spec exec vitest run`: 668 files, 19261 passed, 1 todo. `pnpm --filter @objectstack/spec typecheck`: exit 0. - `@objectstack/sdui-parser` test (14 files, 225 passed) and typecheck: exit 0. - These suites read the regenerated manifest: - `@objectstack/lint` (119 files, 5627 passed); - `@objectstack/metadata-protocol` `src/protocol.runtime-authoring-gate.test.ts` (70 passed); - `@objectstack/cli` unit tier `src/utils/sdui-manifest.test.ts` and `test/validate-build-gate-parity.test.ts` (2 files, 79 passed). - The CLI integration tier is declared to CI. - `eslint --no-inline-config --format json` on the 15 changed TS files: 15 files, 0 errors and 0 warnings. - The lint population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` (`eslint.config.mjs:971`). - The config enables no type-aware linting (`:328`), so this diff cannot move a verdict on an untouched file. - Repo-wide `pnpm lint` is left to CI. ## Acceptance notes - **Lockstep, a reading and not a gate result.** objectui#11170 gives objectui's `validateTree` a slot walk: `slotElements` plus the `comp?.slots` loop in `packages/sdui-parser/src/validate.ts`. This repo's port has none (`git grep -n slots packages/sdui-parser/src`: 0). - The walk only fires for a manifest built with `slotsFor`, and this repo's committed manifest carries no `slots`. So the save gate and objectui's runtime parser still agree on that manifest. - objectui's `kind:'html'` compile (`getJsxManifest`) is now built with `slotsFor`. A slot-held node that fails validation could fail the renderer's compile while this repo's save gate accepts it. - `check:sdui-lockstep` compares the grammar region, the codes and the containment predicate, so it cannot see a walk-depth change. That class is outside its reach by its own header ("CANNOT see … WHEN a code fires" beyond the predicate). - Not measured through a public door here, so it is noted rather than filed. Carrier: none. - `main` moved three commits past this branch's base (objectstack-ai#21810, objectstack-ai#21808, objectstack-ai#21809). `git merge-tree --write-tree HEAD origin/main` is clean. Two of them touch files this diff touches: `api-methods-batch-conformance.test.ts` (test titles) and `component.test.ts` (objectstack-ai#21808 re-points a non-asserting anchor). The merged tree still carries 1 and 6 citations at the new pin and 0 at the old one. No merge was made; the merge queue rebuilds on the current `main`. - objectui `main` reached `59917c4b2` (objectui#11639) after the pin was read. It is not in this range. - Writes: one draft PR through the relay and the report comment on objectstack-ai#21807. No label, PR assignee, ready flag or auto-merge was written; this dispatch's write budget names none of them. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…objectstack-ai#21994) Fixes objectstack-ai#21989 Clause-②: no ## What this is This PR adds the curated release page for 17.7.0, `content/docs/releases/v17/17-7.mdx` (1,402 lines). It was written after the publish: npm `latest` moved on 2026-10-06, and `@objectstack/spec@17.7.0` went out at 12:22:14Z. It also wires the page in: - `content/docs/releases/v17/meta.json`: `17-7` comes ahead of `17-6`. - `content/docs/releases/v17/index.mdx`: the status blockquote, the minors warning, the per-release list and the checklist links now name 17.7.0 as current. This is the same shape objectstack-ai#21362 used for 17.6.0. - `scripts/docs-audit/handwritten-docs.json`: the page joins the docs-accuracy audit scope. - `content/docs/releases/v17/17-6.mdx`: one dated correction (2026-10-06) on the anonymous-endpoints known issue. objectstack-ai#21158 was closed as not planned on 2026-10-04. The page follows the 17.6 page's structure: 1. Highlights. 2. What's new: the counts, and the runtime changes that happen silently. 3. Breaking changes and migration, triaged by door and subject. It includes a coverage table that names the section carrying the migration for every ADR-0087 entry added since 17.6.0 (8 conversions, 41 D3 entries). 4. New capabilities. 5. Notable fixes. Security fixes are described only as classes and doors. 6. New in Console: each objectui declared-breaking change, with this repo's answer. 7. The code that shipped but is not listed. 8. The upgrade checklist. Every line is marked *Not exercised.* ## Sources and counts - The version commit `4e4e881427` (objectstack-ai#21352) consumed 323 changesets. 322 are new. One, `748b240` (objectstack-ai#21270), shipped in 17.6.0 and is listed again; the page explains this in its own section. - 69 CHANGELOGs carry a 17.7.0 section, and 48 of them have entries. Their 444 entries (194 minor, 250 patch) de-duplicate to the 323 changesets. - Two commits landed on `main` after the Version Packages PR's last refresh and before it merged: - `8a399b2b15` (objectstack-ai#21977, for objectstack-ai#21923) - `04e776b39a` (objectstack-ai#21976, for objectstack-ai#21968) Both are ancestors of the version commit (exit 0 for each), so the 17.7.0 packages carry their code. But the version commit did not consume their changesets, so no 17.7.0 CHANGELOG line names them. The release-integrity audit named both in a warning. - objectui: the pin moved five times and ends at `0abd4f9f8769`: - `89cad75d5570` (objectstack-ai#21380) - `ab1879721595` (objectstack-ai#21625) - `2e818d0b51ec` (objectstack-ai#21710) - `9dfaca654311` (objectstack-ai#21800) - `0abd4f9f8769` (objectstack-ai#21827) Across 173 commits, 254 changesets were added and 229 of them release something. 65 are declared breaking, plus one commit marked `!`. The Console table answers each. - `PROTOCOL_VERSION` is still 17.0.0. ## Premise corrections - The dispatch named two pin moves ending at `9dfaca654311`. The tree has five, ending at `0abd4f9f8769` (`8832655`, objectstack-ai#21827). The page covers all five. - One new D3 id contains a word that `check:role-word` refuses on docs pages, and release pages are not in its baseline. The coverage table therefore names that entry by its subject and points at `os migrate meta --from 17`. ## Fact-check A second pass checked every cited SHA, PR number, key name and behavioural claim against the commits, changesets and registry entries. It corrected **31 claims** (commit `e4a6280b46`). Two more corrections came earlier, while drafting: - objectstack-ai#21361 is closed; it is not tracking the issue. - There are seventeen `ui-object-*` members, not eighteen. Mechanical checks on the final page: - All 276 cited SHAs resolve, in objectstack or in an objectui clone carrying the final pin's history. - Each of the 216 distinct sha–PR pairs matches its commit subject. - Every printable new D3 id (40) and all 8 conversions appear on the page. - The MDX for 17-7, 17-6 and index compiles with @mdx-js/mdx 3.1.1 and remark-gfm 4.0.1. After the fact-check, `main` gained `1abfc58` (objectstack-ai#21985), which lands the read half of objectstack-ai#21922. It is not an ancestor of the version commit: the test returned exit 1, and the control commit `753e7a1` returned exit 0. So in 17.7.0, the metadata door's reads still serve a stored row under a code-defined datasource name. Commit `8347e0d172` says so in the datasource migration bullet. ## Independent fact-check and fix round An independent, read-only fact-check of head `8347e0d172` returned **FAIL** with 13 findings (record: objectstack-ai#21989 comment 6018402030): 2 wrong facts (a flow's `get_record` node still reads the stored-metadata tables, in projected form), 1 security line that named the filter shapes and depth threshold evading 17.6.0's refusal, 1 breaking change missing from the Breaking section (`0fc8087`, objectstack-ai#21626), 1 link whose label did not match its target, 4 overstatements, 1 missing security fix (`49524f6`, objectstack-ai#21420), 1 missing rollback caveat on `os secret rewrap --apply`, and 2 minor wording issues. All 13 were re-verified against their sources and applied in `a53c972fa7`; none was refuted. A scan for any other line naming a bypass shape of a fixed issue reduced two more lines to their class (`0728cbf`, `fb69825`). ## Measured on `a53c972fa7` - Derived gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derives 57 commands; all 57 exited 0 (`--ran`: "57 run, 0 NOT-MEASURED (a DERIVED zero)"). The verdicts include: - check-doc-anchors: 458 links resolve. - check-issue-citations: 305 resolve as a PR, 9 resolve, 15 are cross-repo; every citation this change adds resolves. - `check:role-word`, `check:release-notes` and `check:release-page-status`: OK. - check-release-section-coverage: OK, both plain and with `--strict` (10 minors). - The docs-audit scope check and `check:nul-bytes`: OK. - Docs production build: `TURBO_FORCE=true pnpm turbo run build --filter=@objectstack/docs`, run under the verify lock, reports `Tasks: 2 successful, 2 total` and `Cached: 0 cached`. The built `releases/v17/17-7.html` carries the corrected text and none of the removed text. - Mechanical checks: 231 distinct sha–PR pairs, 0 unresolved, 0 subject mismatches; the MDX of 17-7, 17-6 and index compiles. - Not measured locally: the repo-wide lint and the CI-only families. CI owns them. ## Not in this PR - No changeset. The PR touches only docs and a docs-audit list, nothing a package ships (`skip-changeset`). - Nobody has walked the 17.6.0 → 17.7.0 upgrade. The checklist says so on each line. --- _Generated by [Claude Code](https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21772
Clause-②: no
This moves the bundled Console's objectui pin from
2e818d0b51ec(#21710) to objectuimainas of the write time,9dfaca654311cddd81714153c4f82c241d7cdc54, which is pastc096f03. The Console now carries objectui#11611 (theref:datasetspec-form widget), objectui#11614 (the grid widget's camelCase keys) and objectui#11619 (the record chrome's picture). Those are the three merges #21714, #21768 and #21765 wait on.⛔ This is not a release act. Version PR #21352 is untouched, and no tag, publish or Release was made. The card's "release first" hold is lifted by the maintainer's order recorded in the claim: 「浏览器测试已经在运行,还有很多问题在处理,服务端开发不应该被阻塞」.
Range
git ls-remote origin refs/heads/mainread9dfaca654311cddd81714153c4f82c241d7cdc54at 2026-10-05T00:51:29Z, just before the bump, and the same sha again before this PR was opened.git merge-base --is-ancestorexits 0 against9dfaca654311for all three carried merges:b508ac50d9(objectui#11611),2abec3a96c(objectui#11614) andc096f03279(objectui#11619). The objectui clone is shallow, but exit 0 proves itself there.2e818d0b51ec..9dfaca654311has 17 commits and 0 merges.major. Four carry the author's breaking annotation, and the highest declared level isminor, so the console changeset isminor. These counts come fromscripts/objectui-range.mjs --from 2e818d0b51ec --to 9dfaca654311 --jsonand from the bump's own digest. They were measured, not copied from the card.!.git log --format='%h %s' 2e818d0b5..9dfaca654 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'gives9db9ff3f9,8b14aecbd,2abec3a96andb403bb36f.9dfaca654simpleform draws a self-describing inline section entry;ObjectFormSection.fieldsgains the form view's{ field }arm15f67025bderiveInlineGridColumns4c127cdefrecord:related_liststops requiringcolumnsc096f0327imageField6e9090c269db9ff3f9!subforms[].columnsentry is the spec'sInlineGridColumnSchema, by reference7c9a6b1948b14aecbd!PartialSchemais retired from@object-ui/types2abec3a96!b403bb36f!BaseSchemaloses its index signatureClause-②: yes (narrowing), no BREAKING word)!, answered belowfd060f076objectName, and a node with neither shows a no-object hintb508ac50dref:datasetspec-form widget, fed by the report inspector's dataset catalogd2e859936GET /usage/storageonly when the runtime servesfeatures.storageUsageb92329c89902ebab63record:line_itemsstops requiringchildObject278d2444eb61c116b2record:detailsread mode keeps a textarea's line breaksDeclared-breaking changes, and how this repo answers each
The ADR-0087 disposition is
not-required (no-migration-prescription). It replaces the bump'sadr-0087: TODOplaceholder in.changeset/console-9dfaca654311.md, and it covers all five entries below.check-adr-0087-registration --base origin/maingives "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition".check-changeset-no-major --base origin/maingives "This diff introduces nomajorbump".2abec3a96): the grid widget's eight keys.min_rows,max_rows,allow_add,allow_delete,allow_reorder,total_field,add_labelandsort_fieldbecomeminRows…sortField. objectui has no dual read: its zod faces refuse a snake_case key by name, and itsGridFielddraws an inline alert instead of the grid.@objectstack/spec's runtime form field (buildObjectFormRuntimeField,packages/spec/src/ui/component.zod.ts) already refuses the eight snake_case keys by name, with guidance that names objectui#11610. The camelCase keys are not declared there yet.object-formruntime form field declares the grid widget's eight camelCase keys once.objectui-shacarries objectstack-ai/objectui#11614 (#21704 fork 2 follow-up) #21768, an ObjectStack-authoredgridform field can spell the keys neither way. Measured on this head's built spec:object-formwithcustomFields: [{ name: 'items', type: 'grid', min_rows: 1 }]is refused withunrecognized_keysatcustomFields.0and the grid-key guidance. The same field withminRows: 1is refused withunrecognized_keysatcustomFields.0, as an undeclared key. So agridfield takes itscolumnsand the widget's own defaults. The refusal's prescription says these keys "come in once the widget reads a camelCase spelling". At this pin the widget does, and spec(ui): theobject-formruntime form field declares the grid widget's eight camelCase keys once.objectui-shacarries objectstack-ai/objectui#11614 (#21704 fork 2 follow-up) #21768 declares the keys and retargets that prescription.git grepoverexamples/andpackages/finds no authored snake_case grid key. The only hits are the spec's own refusal list and its pin test, migration prose, and one historical note about objectui's internal master-detail adapter.9dfaca654): thesimpleform's inline section entries. For TypeScript readers ofObjectFormSection.fields, the type gains the spec'sFormFieldInputarm. No code in this repo imports@object-ui/types; the only@object-ui/*import isscripts/gen-sdui-manifest-node.mjsreading@object-ui/corefrom the built tree. The behaviour change is that the defaultsimpleform now draws an inline{ name, … }entry, as the other five form types already did. The showcase'sobject-formnodes arewizard,drawerandmodalforms whose sections list field names, so none of them draws differently.9db9ff3f9):subforms[].columns. objectui's validator now judges a column by@objectstack/spec's ownInlineGridColumnSchema. This repo has enforced that closed shape since feat(spec)!: a form view's subform columns are the inline grid column contract, and an identity-only column is judged as the type it renders (#20901) #20927. objectui's verdict now matchesos validate, and the ObjectStack accept set does not move.8b14aecbd):PartialSchema. It leaves@object-ui/types. Nothing in this repo names it (git grep PartialSchema: 0).b403bb36f):BaseSchemaloses its index signature. This narrows the TypeScript face of objectui's node types, and nothing here compiles against them. objectui's zod faces keep their accept sets for every key exceptvisibleWhen. That key widens to the{ dialect, source }envelope this repo's own parse writes.The three cards this unblocks
reportForm's "Joined blocks" row offers a block'sdatasetas free text with norequired, so Studio saves the block #21702 now refuses #21714: objectui#11601'sref:datasetwidget (PR objectui#11611,b508ac50d) is now in the pinned build, soreport.form.ts's joined-blockdatasetrow can declarewidget: 'ref:dataset'.object-formruntime form field declares the grid widget's eight camelCase keys once.objectui-shacarries objectstack-ai/objectui#11614 (#21704 fork 2 follow-up) #21768: objectui#11610's camelCase keys (PR objectui#11614,2abec3a96) are now what the pinned widget reads, so the spec's runtime form field can declare them.object.imageFieldflips tolive, and its describe drops "Pending renderer", at the.objectui-shabump that carries objectui#11383 #21765: objectui#11383's record picture (PR objectui#11619,c096f0327) is now in the pinned build, soobject.imageField's liveness row can move tolive. The smoke below observes that read in the browser. The reader ispackages/components/src/renderers/layout/containers.tsx, in thepage:headerrecord chrome.What changed here
.objectui-shaand.changeset/console-9dfaca654311.md.scripts/bump-objectui.sh 9dfaca654311cddd81714153c4f82c241d7cdc54 --no-commitwrote both, withOBJECTUI_ROOTset to the container's objectui clone aftergit fetch origin main. The range walked completely without a deepen, and the level was auto-set tominor.sdui.manifest.jsonandscripts/sdui-manifest.record.json.node scripts/gen-sdui-manifest-node.mjsregenerated them over the tree thatpnpm objectui:buildbuilt at the pin. There are 107 components at both pins, and the sha256 moves from0ead67c1111d…to6f921896ffac…. This time the manifest moves:required: trueand gain a description. Nine areobjectName, onobject-grid,list-view,object-form,embeddable-form,object-master-detail-form,object-kanban,object-metric,object-chartandobject-pivot(objectui#11605). The other two arerecord:related_listcolumns(objectui#11613) andrecord:line_itemschildObject(objectui#11569).object-master-detail-form'sfieldsdescription is rewritten for objectui#11615's inline entries.ComponentPropsMaprow, these inputs are already optional there. Measured on the built spec:object-grid,object-form,object-kanban,object-metricandobject-master-detail-formobjectName,record:related_listcolumnsandrecord:line_itemschildObject. So the manifest now agrees with the spec rows. The JSX page compile reads the manifest, and it stops refusing a node whosedataSourcebinding names the object.modulesRoot. objectui's workspace version stays 17.7.0.packages/sdui-parser/objectui-lockstep.json.pnpm gen:sdui-lockstepre-recorded it againstOBJECTUI_ROOT=.cache/objectui-9dfaca654311. It records 214 grammar lines (blob0131f27cf86d), 25 codes and containment predicate76c18fb95d1f. All are unchanged, and objectui'spackages/sdui-parserhas no diff over the range, so no port is owed.packages/spec/src. They were re-measured at the new pin, not restamped:2e818d0b5, mapped throughgit diff -U0 2e818d0b5 9dfaca654, and re-read at the new pin. Each record gains a dated 2026-10-05 hop sentence and keeps its earlier history.ObjectGrid.tsx,ObjectTree.tsx,ObjectGantt.tsx,ObjectCalendar.tsx,SchemaRenderer.tsx,plugin-view/src/ObjectView.tsx,plugin-map/src/index.tsxandplugin-gantt/src/index.tsx.ObjectKanban.tsx, objectui#8347 added a privateGateBoundKanbanSchemaread type, so its fetch, navigation reads and spread moved +30.plugin-kanban/src/index.tsx,plugin-dashboard/src/index.tsxandElementDataSourceGate.tsx. Theobject-metricicon input moved281→299and is still{ name: 'icon', type: 'string' }.page:tabsandpage:accordionicon anchors incontainers.tsxby +3.ObjectKanbanSchema.limit,ObjectMapConfigSchemaandLIST_VIEW_LOCAL_OVERRIDESinobjectql.tsandobjectql.zod.ts.view.noObjectto theen,zhanddelocale packs. Their citedcalendar.configRequiredstrings did not change or move.ObjectKanban.tsx:10, the type import, gainedSortConfigbeside theObjectKanbanSchemathe record cites.keyboardNavigationhit lines (15, against theschema.editablecontrol's 3) and theElementDataSourceGateoccurrences in fivesrc/index.tsxshells (0, 3, 3, 3 and 4).ui/view.zod.ts's map record redded at this pin, and each was re-read and re-pointed:case 'map':moved2299→2300,ObjectMapConfigSchema2370→2388, andLIST_VIEW_LOCAL_OVERRIDES1419→1437.git grep -o -F. That method first reproduced every2e818d0b5number: 7579 files,objectstack17227,@objectstack/spec7134,timeout1351,useState2477,TTL182,tenant1317 andSpan505.RuntimeConfig276 → 293,resourceLimits2 → 2,window4175 → 4193,period238,interval195 andmetrics374 → 401.Span(508) andSpanSchema(57). The three newSpanhits arecolSpanin objectui'sobject-form-section-field-entry-11615.test.ts. BothresourceLimitshits are still prose inpackages/app-shell.packages/spec/src/migrations/registry.tswas regenerated withgen:migration-registry..changeset/objectui-pin-citations-9dfaca654311.mdis a@objectstack/specpatch, because theFormField.spandescribe and six migration descriptions name the pin.content/docs/references/ui/view.mdxwas regenerated bycheck:generated --fix.No example, test or gate needed adapting, and no code changed outside generated records, citations and changesets.
Console build and canaries
build-console.shran locally under the verify lock, afterturbo run build --filter=@objectstack/client..., the same two steps theConsole Pin Gatejob runs. Exit 0, 9m02s on a shared four-core box. The build log reports:check:console-shaandcheck:console-injectionexit 0 against that dist.Browser smoke:
examples/app-showcasewith the Console built at9dfaca654311The server ran
pnpm dev -- --fresh --ui --no-watch --compile -p 41877withOS_PORT=41877, on its own ephemeral DB with the seeded admin. Headless Chromium (/opt/pw-browsers/chromium) drove it, signing in through the console's own login form, with console messages, page errors and every 4xx/5xx response captured. Only the PIDs this run started were stopped.No showcase object declares
imageField, so the record picture needs one to exist.git grep imageField examplesgives 0 hits. To exercise objectui#11619's read, the smoke made a temporary local edit:imageField: 'f_image'toshowcase_field_zoo, throughnode scripts/ablation-replace.mjs --hold.--restore. The tool reports the blob back to HEAD's4130858b8f8bandgit diff HEADempty, andgit status --porcelainis empty.dist/was restored from turbo's cache for the clean source: sha256cc1034dd9d23…, as before the smoke. The stray runtime bundle of the edited compile was deleted.Nothing of the edit is in this diff. A real
sys_filewas uploaded through/api/v1/storage/upload/presigned→PUT→/upload/complete(a 64×64 red PNG) and written to "Specimen — Full"'sf_image.[data-record-picture]f_imageset)rounded-md(animagefield, notavatar)src="/api/v1/storage/files/b1f4e384-…",alt="", loaded,naturalWidth64f_imageempty)Verdict: at this pin the record header draws the record's picture from the object's
imageField, drawing the stored file through/api/v1/storage/files/:id, and draws nothing when the value is empty.showcase_task's record page is the customtask-detailpage, which has nopage:header, so it draws no record chrome to put a picture in. That page was smoked too: it renders its path bar, highlights and sections with 0 page errors.Console messages across the run: 0 page errors. The only failed loads are a 401 on
GET /api/v1/auth/get-session(the pre-login probe) and a 404 for/favicon.ico. There is no/api/v1/usage/storage404. #21625's and #21710's smokes both recorded one, and objectui#11002 (d2e859936) stopped the request on a runtime that does not serve it.Gates and tests (head
4b9519ea23)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 127 commands from the 22-path diff, and all were run at4b9519ea23.--ranreports "127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded.PREREQUISITE NOT MET) on the first pass:check:skill-examples(noclient-reactdist) andcheck:dual-build-cjs-loads(no dist for 34 packages).turbo run build --filter=!@objectstack/docs(71 of 72 tasks were cache hits), and those are the codes recorded.check:objectui-pin-citations --verify-anchorswith objectui at the pin: 54 asserting citations match9dfaca654, and 7 anchor content assertions are verified.check:objectui-bump(20 assertions across 5 cases),check:sdui-lockstep,check-sdui-manifest,check:console-sha,check:console-injection,check:migration-registry.@objectstack/spec check:generated: all 15 artifacts current after the--fix.pnpm --filter @objectstack/spec exec vitest run(both projects,localandrepo): 668 files, 19259 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.@objectstack/sdui-parsertest (14 files, 225 passed) and typecheck: exit 0.@objectstack/linttest: 119 files, 5627 passed. It declaressdui.manifest.jsonas a test input.@objectstack/metadata-protocolsrc/protocol.runtime-authoring-gate.test.ts: 70 passed.@objectstack/cliunit tiersrc/utils/sdui-manifest.test.tsandtest/validate-build-gate-parity.test.ts: 2 files, 79 passed.test/jsx-gate-manifest-notice.e2e.test.tsbelongs to neither CLI tier; it runs nightly, so it is NOT MEASURED here and is left to CI.eslint --no-inline-config --format jsonon the 15 changed TS files: 15 files, 0 errors and 0 warnings. The lint population is**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}(eslint.config.mjs:971). The config enables no type-aware linting (:327-328), so this diff cannot move a verdict on an untouched file. Repo-widepnpm lintis left to CI.Acceptance notes
mainmoved two commits past this branch's base (docs(pm-dispatch): say how the claim reads the Clause-② public surface #21783, fix(cloud-connection): refuse install-local sample data for a session with no active organization (ADR-0123 D2/D4) #21780). Neither touches a path this diff touches, so no merge was made. The merge queue rebuilds on the currentmain.containers.tsxanchors inui/component.zod.tsdocblocks with no sha, andcomponent.test.ts'splugin-dashboard/src/index.tsx:204(theObjectMetricPropsSchema icon livenesstest, already reading an unrelated line atab1879721595, as chore(objectui): bump the console pin to 2e818d0b51ec (carries objectui#11466, #11574, #11578, #11581 and #11583) #21710 noted).object-formruntime form field declares the grid widget's eight camelCase keys once.objectui-shacarries objectstack-ai/objectui#11614 (#21704 fork 2 follow-up) #21768 retargets it when it declares the camelCase keys. It is left as is here, because the pin bump changes no accept set.Generated by Claude Code