diff --git a/.changeset/console-31971ff1e28f.md b/.changeset/console-31971ff1e28f.md new file mode 100644 index 00000000000..54507fa9391 --- /dev/null +++ b/.changeset/console-31971ff1e28f.md @@ -0,0 +1,99 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) refreshed to `31971ff1e28f`. This pin carries objectui#11353 (objectui `31971ff1e`): the console bundles one zod instance again, so the Studio's spec-derived forms render their fields — the New Package dialog creates a package and the dashboard and report inspectors show the spec schema. The previous pin shipped objectui's zod 4.4.3 beside the injected spec's 4.6.1. That commit carries no objectui changeset, so it is listed under "declared nowhere" below. Frontend changes in this range: + +Derived from the changesets objectui declared over the range — 56 releasing of 58 changesets added across 44 non-merge commits; omitted: 2 release-nothing changesets, 3 commits carrying no changeset (they ship no package code). + +- **minor** — `FormulaFieldMetadata` declares `@objectstack/spec`'s `expression` in place of `formula`, and three readers of a lookup's display pointer read the spec's `displayField` alone (obj… (objectui `19f484f54`) +- **minor** — **BREAKING** — feat(types): an authored `object-gantt` takes its props in the spec's `properties` bag; the flat spelling is refused by name (objectui#10859, batch 6) (objectui `db0beb2aa`) +- **minor** — `object-form.layout` publishes only `vertical` and `horizontal`. This is objectui#11168 slice 3 and delivers objectui#7759 group C. `@objectstack/spec` 17.5.0 retired `inline` and… (objectui `17dc16793`) +- **minor** — The form layout mirrors state the two values the spec and the renderers honour. This is objectui#11168 slice 3 and delivers objectui#7759 group C. (objectui `17dc16793`) +- **minor** — **BREAKING** — feat(layout): a navigation label written as an inline locale map renders in the viewer's locale; three inert resolver props retire (objectui#11299) (objectui `770cc5ba4`) +- **minor** — fix(types): a navigation entry's `label` accepts an inline locale map, as the spec does (objectui#11299) (objectui `770cc5ba4`) +- **minor** — **BREAKING** — feat(types): an authored `object-chart` takes its props in the `properties` bag; the flat spelling is refused by name (objectui#11276) (objectui `5262f7dd3`) +- **minor** — `navigation` is declared on the `object-timeline` block, by reference to `@objectstack/spec` (objectui#8654). This is the timeline arm of the objectui#8652 maintainer ruling: the… (objectui `95bd23c90`) +- **minor** — feat(types): a node bound through `dataSource.object` needs no `objectName` on the validator (objectui#11117) (objectui `0e6e76bc4`) +- **minor** — feat(types)!: retire `data-table`'s `selectionStyle` and `chatbot`'s `floatingConfig` on both faces, and stop teaching `data-table`'s inline-edit flags as authored keys (objectui#… (objectui `b5b928ab0`) +- **minor** — fix(components): a related list's row menu shows an action whose `visible` is blank, as its toolbar does (objectui `be5211522`) +- **minor** — **Breaking (types only):** the `ActionGroup` interface is removed from `@object-ui/types` (objectui#11168, slice 2). Nothing in this repository imported it. (objectui `cd5b19a7e`) +- **minor** — `element:definition-list`, `element:repeater` and `action:button` publish their inputs as the `@objectstack/spec` 17.5.0 rows declare them and as their renderers read them (object… (objectui `cd5b19a7e`) +- **minor** — **BREAKING (authoring, TypeScript only):** `chartConfig.aria` on a dashboard widget is now a compile error, the same verdict the validator already gives (objectui#4044). (objectui `f3c2bb0f9`) +- **minor** — **BREAKING** — feat(types): an authored `object-map` takes its props in the spec's `properties` bag; the flat spelling is refused by name (objectui#10859, batch 5) (objectui `997ce38cb`) +- **minor** — The text-family field types and every length reader use `@objectstack/spec`'s own `minLength` / `maxLength`, and the snake_case `min_length` / `max_length` are retired at once, wi… (objectui `dd5ff190e`) +- **minor** — `element:text` takes the nine `variant` values `ui:text` publishes (`h1`-`h6`, `body`, `caption`, `overline`) and renders each one the way `ui:text` does (objectui#7450). (objectui `caa0cd392`) +- **minor** — **BREAKING** — `showFilters` is retired on `object-grid` (objectui#11068). (objectui `582edef1c`) +- **minor** — fix(plugin-dashboard): a served dashboard draws its own title, description and sub-caption, not the packaged catalog's (objectui#11295) (objectui `b28bde8a4`) +- **minor** — `navigation` is declared on the `object-kanban` and `object-calendar` blocks, by reference to `@objectstack/spec` (objectui#8652). This is the objectui half of the maintainer ruli… (objectui `d79f525d9`) +- **minor** — **BREAKING** — The object-metadata write guard now holds a `select` / `radio` field that has no option source. (objectui `1563d3e10`) +- **minor** — **BREAKING** — The Field Designer's drawer no longer offers `select` as a field type for a new field, or as a type to change an existing non-choice field into. (objectui `1563d3e10`) +- **minor** — A related list inside a record now places its child object's `record_related` actions on each row (objectui#11270; the renderer half of the enforce answer on objectstack-ai/object… (objectui `a8b988933`) +- **minor** — **BREAKING** — feat(types): an authored `object-form` takes its props in the spec's `properties` bag; the flat spelling is refused by name (objectui#10859, batch 4) (objectui `e3782d26e`) +- **minor** — **BREAKING (authoring):** `assignedProfiles` on a `page` node is now refused on both published faces (objectui#9409). (objectui `02f1813f8`) +- **minor** — `JoinedReportBlock` is now the spec's own type, derived from the installed `@objectstack/spec` instead of hand-written (objectui#10940). It is `JoinedReportBlock` from `@objectsta… (objectui `92970c4d6`) +- **minor** — **BREAKING** — fix(core): `ActionDef` no longer declares `aria`, which `@objectstack/spec` 17.5.0 retired on an action (objectui `e2271568f`) +- **minor** — feat(types): four declared keys nothing honoured are retired on both faces, and two chatbot keys gain their zod mirror (objectui#6152, round 4) (objectui `3e4fa2cfb`) +- **minor** — A blank gate predicate is diagnosed on the three paths that still drew it in silence, and objectui's two gate mirrors whose protocol key refuses a blank now refuse it too (objectu… (objectui `58da8aeca`) +- **patch** — fix(plugin-detail): `record:quick_actions.requiredPermissions` publishes what the gate does — the contract's shared record-block describe, verbatim (objectui `fd6f5da44`) +- **patch** — perf(console): the first screen no longer downloads the spec entries only the metadata designers' validation uses (objectui `993f27e60`) +- **patch** — docs(plugin-gantt): authored `object-gantt` examples write their props in the `properties` bag (objectui#10859, batch 6) (objectui `db0beb2aa`) +- **patch** — The page-block inspector no longer offers `Inline` and `Grid` for an `object-form`'s `layout` (objectui#11168 slice 3, objectui#7759 group C). `@objectstack/spec` 17.5.0 refuses b… (objectui `17dc16793`) +- **patch** — fix(app-shell): a Studio pillar whose draft load is cancelled no longer leaves its canvas on "Loading…" (objectui#11331) (objectui `bef9f204a`) +- **patch** — fix(app-shell): a navigation label written as an inline locale map shows in the viewer's language across the console (objectui#11299) (objectui `770cc5ba4`) +- **patch** — chore(plugin-designer): follow `@object-ui/layout`'s and `@object-ui/types`' navigation label changes (objectui#11299) (objectui `770cc5ba4`) +- **patch** — fix(runner): the sidebar no longer reads the app's retired `version` key (objectui `47e3ce008`) +- **patch** — fix(plugin-gantt, plugin-calendar): the `objectName` input description names the `dataSource.object` binding (objectui#11117) (objectui `0e6e76bc4`) +- **patch** — fix(plugin-grid): a grid row shows an action whose `visible` is blank, as the action's toolbars do (objectui `be5211522`) +- **patch** — `object-calendar` is taught with its `calendar` block, not with flat field-name keys (objectstack-ai/objectui#8831). (objectui `50583364a`) +- **patch** — docs(plugin-map): authored `object-map` examples write their props in the `properties` bag, and the `objectName` input names the `dataSource` binding (objectui#10859, batch 5) (objectui `997ce38cb`) +- **patch** — Message text only: the refusal an `element:text` node draws for an authored `body` or `children` describes what the block renders in the converged `variant` vocabulary, the headin… (objectui `caa0cd392`) +- **patch** — The Studio page-block inspector's `element:text` **Variant** select offers the nine values `ui:text` publishes (Heading 1 to Heading 6, Body, Caption, Overline) in place of Headin… (objectui `caa0cd392`) +- **patch** — The `page` preview sample authors its "Quick links" section heading as `variant: 'h3'`, replacing the pre-convergence spelling `subheading` that the ruling retires (objectui#7450)… (objectui `caa0cd392`) +- **patch** — fix(app-shell): Studio's Automations rail stops telling a flow with a declared trigger that it has "no trigger" (objectui `9cfe9977f`) +- **patch** — fix(app-shell): a served dashboard and a served list view are named as served, on the dashboard page, the view tab and the breadcrumb (objectui#11295) (objectui `b28bde8a4`) +- **patch** — fix(layout): a present navigation label renders as written, with no exception, and an inline locale-map label renders its text (objectui#11201) (objectui `1ccb5ba7d`) +- **patch** — fix(app-shell): a Studio pillar never saves one item's document into another while the second is loading, after a package switch, or after visiting a non-editable leaf (objectui `5f2d9676c`) +- **patch** — docs(plugin-form): the README's authored `object-form` examples write their props in the `properties` bag (objectui#10859, batch 4) (objectui `e3782d26e`) +- **patch** — The flow designer's edge `condition` type now mirrors the server's edge slot, the spec's `EvaluatedExpressionInput` (objectui#8946). (objectui `48401689f`) +- **patch** — docs(core): the `ElementDataSourceConfig.filter` note now separates what an author may write from what a renderer may still receive (objectui#8945) (objectui `969d4f291`) +- **patch** — fix(app-shell): the generic metadata editor renders a stored `view`, and a string-array repeater edits strings (objectui `1b30c0fe0`) +- **patch** — `object-grid` re-reads its rows when an input its query reads changes: a `conditionalFormatting` rule, a row or bulk action def, or the view's `searchableFields` (objectui#10689). (objectui `be0ad007b`) +- **patch** — `list-view` re-reads its rows when a `conditionalFormatting` rule, a row action def or a bulk action def adds a field its predicates read (objectui#10689). (objectui `be0ad007b`) +- **patch** — fix(app-shell,components,console): the three remaining select placeholders show the locale's own "Select…" word (objectui#11252) (objectui `7fed09d07`) +- **patch** — fix(plugin-dashboard): a dimensionless table renders a row of every measure, and a dimensionless chart one mark per measure (objectui `b4333ab8a`) + +⚠️ 12 of these carry a breaking change: 12 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + +**In this console build, declared nowhere** — objectui merged 3 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)_ fix(console): an injected spec shares the console's one zod instance (objectui#11327) (#11353) (objectui `31971ff1e`) +- _(no changeset)_ ci(half-state-patrol): call objectstack's composite action pinned to a sha, with the no-anchor opt-in (objectui#11174) (#11332) (objectui `2c274e3a8`) +- _(no changeset)_ docs: layout.md names the object-grid object with objectName, and flex.mdx teaches the four direction values (objectui#11298) (#11314) (objectui `0c6f9bbd7`) + + + +objectui range: `e420df310f5b...31971ff1e28f` diff --git a/.changeset/objectui-pin-citations-31971ff1e28f.md b/.changeset/objectui-pin-citations-31971ff1e28f.md new file mode 100644 index 00000000000..0a44fd01eff --- /dev/null +++ b/.changeset/objectui-pin-citations-31971ff1e28f.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `31971ff1e28f`. + +Clause-②: no + +Several records were corrected rather than moved, because objectui changed what they describe on this hop: `object-calendar` and `object-timeline` now read `navigation` with no cast, and `object-kanban`'s read compiles through a declared member, since objectui declared the key on all three blocks (objectui#8652, objectui#8654); `object-timeline` also publishes a `navigation` input (objectui#8654); and the `action:button` registration now publishes the five `size` values its row declares (objectui#11168). Two stale readings are also corrected: the `object-timeline` start/end binding anchor began one line early at the previous pin as well, and the `object-tree` optionality count now records the comment that names the gate in `plugin-map`'s shell. No key, default, enum member or export moves. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0dd471516f0..37fcd675c89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1507,13 +1507,38 @@ jobs: - name: Build run: pnpm run build - # This key hashes the pin and the build script. ci.yml's Console Pin Gate - # (#4290) hashes a SUPERSET: those two, the probe scripts and the spec's - # entry layout (#20765). So the two workflows no longer share dist entries, - # and each one builds its own on a miss. Keeping this key narrower is - # deliberate. Following ci.yml would rebuild the console on every Version - # Packages merge, and publishing a cached console whose spec content lags - # is the existing cache design. + - name: Read the spec's declared zod range (Console dist key input) + id: spec-zod + # Fails rather than keying on an empty or `undefined` range: a key that + # silently stopped carrying the range would restore across a zod move. + run: | + node -e ' + const range = require("./packages/spec/package.json").dependencies?.zod; + if (typeof range !== "string" || range.trim() === "") { + console.error("packages/spec/package.json declares no dependencies.zod, so the Console dist key cannot carry it."); + process.exit(1); + } + console.log("range=" + range.trim()); + ' >> "$GITHUB_OUTPUT" + + # This key hashes the pin and the build script, and carries the spec's + # DECLARED zod range (the step above). ci.yml's Console Pin Gate (#4290) + # hashes a SUPERSET of the files: those two, the probe scripts and the + # spec's entry layout (#20765). So the two workflows no longer share dist + # entries, and each one builds its own on a miss. Keeping this key narrower + # is deliberate. + # Following ci.yml would rebuild the console on every Version Packages + # merge, and publishing a cached console whose spec content lags is the + # existing cache design. + # + # The zod range is the one spec input the cached dist is NOT allowed to + # lag (#21108). The console bundles a single zod instance, the console's + # own copy, and its build refuses a console zod outside the injected spec's + # declared `zod` range (objectui#11353). A dist restored after that range + # moved would publish a console the build never judged against it. The + # range is read out of packages/spec/package.json, not hashed with it: the + # file's `version` moves on every Version Packages merge, and hashing the + # whole file would turn every publish into a cold console rebuild. # # Split restore/save, matching ci.yml, and not the combined actions/cache # action: the combined form's post-step saves even when the job FAILED, @@ -1526,7 +1551,7 @@ jobs: uses: actions/cache/restore@v6 with: path: packages/console/dist - key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }} + key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}-zod-${{ steps.spec-zod.outputs.range }} - name: Build vendored @objectstack/console SPA # Clones objectstack-ai/objectui at the SHA pinned in .objectui-sha, @@ -1550,7 +1575,7 @@ jobs: # (no dist, or a dist no build ever stamped) is wrong on a publish lane. - name: Verify the restored Console dist bundles this tree's spec env: - CONSOLE_DIST_CACHE_KEY: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }} + CONSOLE_DIST_CACHE_KEY: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}-zod-${{ steps.spec-zod.outputs.range }} run: pnpm check:console-injection --require-stamp # Reached only with every console assertion above green: Actions steps @@ -1562,7 +1587,7 @@ jobs: uses: actions/cache/save@v6 with: path: packages/console/dist - key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }} + key: ${{ runner.os }}-console-dist-${{ hashFiles('.objectui-sha', 'scripts/build-console.sh') }}-zod-${{ steps.spec-zod.outputs.range }} - name: Downstream backward-compat smoke (live hotcrm) # Pre-publish gate (#2035): the about-to-publish @objectstack/spec must diff --git a/.objectui-sha b/.objectui-sha index ec99114727a..a3d306939c4 100644 --- a/.objectui-sha +++ b/.objectui-sha @@ -1 +1 @@ -e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c +31971ff1e28f89cfc45f0c19bc5b05e443f28b79 diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index e173131c7d9..219e5d29f49 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -213,7 +213,7 @@ Column footer summary configuration | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **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` = `31971ff1e28f`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | | **widget** | `string` | optional | Custom widget/component name (overrides type-based inference) | | **language** | `string` | optional | Code editor language (for type=code) | | **keyField** | `{ field?: string; label?: string \| Record; placeholder?: string \| Record; helpText?: string \| Record; … }` | optional | Key column config for record-typed fields | @@ -378,7 +378,7 @@ View filter rule | **required** | `boolean` | optional | Required override | | **hidden** | `boolean` | optional | Hidden override | | **colSpan** | `integer` | optional | Absolute column span (1-4). The renderer clamps it to the form grid's current column count, so the cell starts at a real column boundary at every surface width and never overflows (`colSpan: 4` in a 3-column grid renders as 3); a `colSpan` within the column count renders as authored, and `colSpan: 1` emits no span class at all. | -| **span** | `Enum<'auto' \| 'full'>` | optional (default: `"auto"`) | Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest. | +| **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` = `31971ff1e28f`), 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 | diff --git a/packages/sdui-parser/objectui-lockstep.json b/packages/sdui-parser/objectui-lockstep.json index 395122f00fe..c8842fa2881 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": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", - "revDate": "2026-09-30T22:03:26+00:00", + "rev": "31971ff1e28f89cfc45f0c19bc5b05e443f28b79", + "revDate": "2026-10-01T07:33:37+00:00", "source": "packages/sdui-parser/src", "files": [ "packages/sdui-parser/src/body-dialect.ts", @@ -21,7 +21,7 @@ "packages/sdui-parser/src/validate.ts" ] }, - "recordedAgainstPin": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", + "recordedAgainstPin": "31971ff1e28f89cfc45f0c19bc5b05e443f28b79", "grammarRegion": { "file": "packages/sdui-parser/src/parse.ts", "delimiter": "/* ---------------------- the JS literal subset (#6614) ---------------------- */", 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 bd2553bc7fe..750b64dabad 100644 --- a/packages/spec/src/data/api-methods-batch-conformance.test.ts +++ b/packages/spec/src/data/api-methods-batch-conformance.test.ts @@ -61,13 +61,21 @@ 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` = `e420df310`, `packages/plugin-grid`; re-measured at - // that pin, 2026-09-30. On the hop off `db11afd49`, `ObjectGrid.tsx` changed + // (`.objectui-sha` = `31971ff1e`, `packages/plugin-grid`; re-measured at + // that pin, 2026-10-01. On the hop off `e420df310`, `ObjectGrid.tsx` changed + // again (41 insertions, 1 deletion: objectui#10689's re-read on a harvested + // query input), every hunk above the selection block, which only MOVED by + // +40: `ObjectGrid.tsx:4791-4818` here (`4751-4778` at `e420df310`), still + // hashing to `c88443302d40c2db739ddb235470bafa29056e2e` (hash-object of the + // block), re-READ with the same reading below; `hooks/useBulkExecutor.ts` is + // byte-identical to `e420df310` and its `:298-303` still hashes to + // `01083348330f10a201cdf1078b4c21c236402b6a`. At `e420df310`, 2026-09-30: + // on the hop off `db11afd49`, `ObjectGrid.tsx` changed // again (164 insertions, 48 deletions: objectui#9853's display page size, // objectui#11021's searched grouped grid, objectui#9547's `onNavigate` prop, // objectui#7297's `{record_id}` filter values and objectui#11070's // `reference` spelling among them), none of it inside the selection block, - // which only MOVED by +114: `ObjectGrid.tsx:4751-4778` here (`4637-4664` at + // which only MOVED by +114: `ObjectGrid.tsx:4751-4778` there (`4637-4664` at // `db11afd49`), still hashing to // `c88443302d40c2db739ddb235470bafa29056e2e` (hash-object of the block), // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is @@ -83,7 +91,7 @@ const SINGLE_RECORD_WRITE_ONLY: Record = { // re-READ with the same reading below; `hooks/useBulkExecutor.ts` is // byte-identical to `dd3f7e1be` and its `:298-303` still hashes to // `01083348330f10a201cdf1078b4c21c236402b6a`. Previously measured at - // `db11afd49`, `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, + // `e420df310`, `db11afd49`, `dd3f7e1be`, `f8a9d0fb0`, `62597c588`, // `87af769e9`, `53ded82bf`, `a472b0716`, `00d3f09c5`, `67dadd602`, before // that at `d8ec8d6d4`, `9602dc820`, `190fbd01d`, `9a3daf8d3`, originally at // `6314e87f2`. On the hop off `f8a9d0fb0`, `ObjectGrid.tsx` changed hard diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index e1436446417..bf21a64dd6e 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -278,7 +278,7 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * screen's second clause: `titleField` is NOT required). ⚠️ The screen's * copy is LOCALISED at the pin: objectui#10101 (objectui `afb228418`), which * the pin now contains, routes it through `tt('calendar.configRequired', …)` - * (`ObjectCalendar.tsx:1311-1312`), whose default and the `en` pack both + * (`ObjectCalendar.tsx:1303-1304`), whose default and the `en` pack both * read "Calendar configuration required. Please specify startDateField, …", * while a non-English locale renders its own pack's words for the same * screen (e.g. `zh`, `de`). The refusal itself — the `if (!calendarConfig)` @@ -287,7 +287,16 @@ export function checkFieldCompleteness(def: unknown): CompletenessFinding[] { * English sentence. So the loss is total rather than partial — every * record, on every object — and the author is told at render time as well * as here. Both reads hold at the pin this repo builds against - * (`.objectui-sha` = `e420df310`, re-read 2026-09-30: `ObjectCalendar.tsx` + * (`.objectui-sha` = `31971ff1e`, re-read 2026-10-01: `ObjectCalendar.tsx` + * changed on the hop off `e420df310` only at its `navigation` read + * (objectui#8652 declared the key and dropped that read's cast, 11 + * insertions, 19 deletions), below `getCalendarConfig`, which is + * byte-identical and unmoved at `:294`, and above the `if (!calendarConfig)` + * arm and its `tt(…)` lines, byte-identical and moved `:1304` → `:1296` and + * `:1311-1312` → `:1303-1304`; `ListView.tsx` changed on this hop too + * (objectui#10689's re-read on a harvested query input) with every `case` + * arm byte-identical, and the `en` / `zh` / `de` pack strings did not + * change. At `e420df310` (2026-09-30) `ObjectCalendar.tsx` * changed on the hop off `db11afd49` only in comments (6 insertions, 3 * deletions, objectui#11073's 17.5.0 re-citations), so `getCalendarConfig` * is byte-identical and unmoved at `:294`, and the `if (!calendarConfig)` @@ -320,8 +329,11 @@ 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` = `e420df310`, re-read - * 2026-09-30; on the hop off `db11afd49` `ObjectGantt.tsx` changed again + * this repo builds against (`.objectui-sha` = `31971ff1e`, re-read + * 2026-10-01; on the hop off `e420df310` `ObjectGantt.tsx` is byte-identical, + * so `getGanttConfig` `:607` and the `if (!ganttConfig)` arm `:2261` did not + * move, and `ListView.tsx`'s `case 'gantt'` is byte-identical; at + * `e420df310` (2026-09-30), on the hop off `db11afd49`, `ObjectGantt.tsx` changed again * (85 insertions, 26 deletions: objectui#11141's inclusive date-only end, * objectui#8348's record-source ladder and objectui#11070's `reference` * spelling), so `getGanttConfig` MOVED `:560` → `:607` and the @@ -359,9 +371,14 @@ 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` = - * `e420df310`; `ObjectTimeline.tsx`, `ListView.tsx`'s `case 'timeline'` and - * `resolveTimelineDateBinding` are byte-identical to `db11afd49` on the hop - * onto this pin, so `:533-535` and `:862` did not move; they were + * `31971ff1e`; on the hop onto this pin `ListView.tsx`'s `case 'timeline'` + * and `resolveTimelineDateBinding` are byte-identical, and + * `ObjectTimeline.tsx` changed above both reads (objectui#8654's typed + * `navigation` member, 42 insertions, 2 deletions), so the start-date chain + * and the refusal arm are byte-identical and MOVED `:533-535` → `:568-570` + * and `:862` → `:902`. At `e420df310`, `ObjectTimeline.tsx`, `ListView.tsx`'s + * `case 'timeline'` and `resolveTimelineDateBinding` were byte-identical to + * `db11afd49`, so `:533-535` and `:862` did not move; they were * byte-identical to `dd3f7e1be` on the hop onto `db11afd49` too. At `dd3f7e1be`, `ObjectTimeline.tsx`, byte-identical from `87af769e9` through * `62597c588` to `f8a9d0fb0`, changed on this hop — objectui#6356's * declared timeline item, objectui#10222's retired `metaFields` read, @@ -388,7 +405,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` = `e420df310`; on the hop onto this pin + * pin (`.objectui-sha` = `31971ff1e`; on the hop onto this pin + * `ObjectMap.tsx`, `ListView.tsx`'s `case 'map'` and `resolveListMapConfig` + * are byte-identical, so `getMapConfig` `:387`, `hasCoordinateBinding` + * `:486` and the refusal arm `:1284` did not move. At `e420df310` * `ObjectMap.tsx` changed only in the record-source docblock above * `getMapConfig` (16 insertions, 17 deletions, objectui#8348), so * `getMapConfig`, `hasCoordinateBinding` and the refusal arm are 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 80ca634c77c..51d711932e3 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts @@ -52,13 +52,13 @@ export const entry: SemanticMigration = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names ' + + 'against, `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — 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 9800 tracked files (0 across the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 9912 tracked files (0 across the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49 and to 15352 and 6024 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310 and to 15691 and 6206 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts index 1bc4fcdba2a..5f58767697c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.kernel-runtime-config-timeout-unit-in-key.ts @@ -46,10 +46,10 @@ export const entry: SemanticMigration = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + + '`.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '9800 tracked files, against lit controls timeout 1293, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '9912 tracked files, against lit controls timeout 1303, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', diff --git a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts index 12f7ea1c6dd..e6ede3b3fd7 100644 --- a/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.logging-durations-unit-in-key.ts @@ -47,10 +47,10 @@ export const entry: SemanticMigration = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells ' + + 'objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 9800 tracked files, against lit controls `useState` 2464 and `timeout` 1293 on ' - + 'the same corpus (all four 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 9912 tracked files, against lit controls `useState` 2469 and `timeout` 1303 on ' + + 'the same corpus (all four 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: diff --git a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts index 10deb6d7d56..68ed1db5810 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-metrics-jsdoc-durations-unit-in-key.ts @@ -73,10 +73,11 @@ export const entry: SemanticMigration = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 9800 tracked ' - + 'files at that sha, against lit controls window 3873, timeout 1293, period 233, ' - + 'interval 196 and metrics 352 on that same corpus and sha (0 across 9546, against 3772 / ' + + '`.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 9912 tracked ' + + 'files at that sha, against lit controls window 3916, timeout 1303, period 234, ' + + 'interval 196 and metrics 354 on that same corpus and sha (0 across 9800, against 3873 / ' + + '1293 / 233 / 196 / 352, at e420df310, 0 across 9546, against 3772 / ' + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + '1096 / 171 / 179 / 326, at f8a9d0fb0, and 0 across 8303, against 3526 / 1086 / 170 / 179 / ' + '324, at 62597c588), so no pin bump is owed. ' 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 f4dc25058cd..ff96dc038a8 100644 --- a/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.system-tracing-otel-exporter-durations-unit-in-key.ts @@ -69,12 +69,13 @@ export const entry: SemanticMigration = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 9800 files ' + + 'pinned objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 9912 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 15352 hits for the bare token objectstack, ' - + 'and 6024 for the package specifier @objectstack/spec (at db11afd49: 0 across 9546, Span 486, ' + + 'two lit controls on that same corpus and sha: 15691 hits for the bare token objectstack, ' + + 'and 6206 for the package specifier @objectstack/spec (at e420df310: 0 across 9800, Span 486, ' + + '15352 and 6024; at db11afd49: 0 across 9546, Span 486, ' + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + 'Span 488, 13347 and 5123; at 62597c588: 0 across 8303, Span 486, 13125 and 5043).', acceptanceCriteria: 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 c0bd783d287..eb409f35d42 100644 --- a/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts +++ b/packages/spec/src/migrations/entries/semantic/18.tenant-schema-cache-ttl-unit-in-key.ts @@ -31,9 +31,9 @@ export const entry: SemanticMigration = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells it 0 ' - + 'times across 9800 tracked files, against lit controls `TTL` 181 and `tenant` 1235 on the ' - + 'same corpus (0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — spells it 0 ' + + 'times across 9912 tracked files, against lit controls `TTL` 181 and `tenant` 1237 on the ' + + 'same corpus (0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index a32c39272fd..094f6df4810 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -12909,13 +12909,13 @@ const step18: MigrationStep = { + 'the audience that does not parse. Measured on 884e8347d: the only in-repo readers are ' + 'packages/core/src/health-monitor.ts and packages/core/src/hot-reload.ts, both moved in ' + 'this same change; and the pinned objectui checkout — the pin this repo builds ' - + 'against, `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names ' + + 'against, `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — 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 9800 tracked files (0 across the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + + 'the string debounceDelay each occur 0 times across its 9912 tracked files (0 across the 9800 at e420df310, the 9546 at db11afd49, the 9283 at dd3f7e1be, the ' + '8512 at f8a9d0fb0 and the 8303 at 62597c588 too), against lit ' + 'controls objectstack 12966 and @objectstack/spec 4997 on the same corpus at 87af769e9, ' + 'which re-count to 13125 and 5043 respectively at 62597c588, to 13347 and 5123 at ' - + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49 and to 15352 and 6024 at this pin (git grep -o -F, the method that reproduces ' + + 'f8a9d0fb0, to 13745 and 5466 at dd3f7e1be, to 14704 and 5545 at db11afd49, to 15352 and 6024 at e420df310 and to 15691 and 6206 at this pin (git grep -o -F, the method that reproduces ' + 'every earlier count).', acceptanceCriteria: 'Every producer and reader of a PluginHealthCheck spells intervalMs and timeoutMs, and every ' @@ -13120,10 +13120,10 @@ const step18: MigrationStep = { + 'spells timeout 0 times; outside the zod file and its test the only live occurrences are the ' + 'generated rows in content/docs/references/kernel/plugin-security-advanced.mdx, which this ' + 'rename regenerates. The pinned objectui checkout — this is the pin we build against, ' - + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' + + '`.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79`, re-read from this tree — ' + 'spells resourceLimits.timeout 0 times across ' - + '9800 tracked files, against lit controls timeout 1293, RuntimeConfig 273 and resourceLimits ' - + '2 on the same corpus (0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + + '9912 tracked files, against lit controls timeout 1303, RuntimeConfig 273 and resourceLimits ' + + '2 on the same corpus (0 across 9800, and 1293 / 273 / 2, at e420df310; 0 across 9546, and 1197 / 273 / 2, at db11afd49; 0 across 9283, and 1172 / 263 / 2, at dd3f7e1be; 0 across 8512, and 1096 / 245 / 2, at f8a9d0fb0; 0 across 8303, and ' + '1086 / 240 / 2, at 62597c588); both resourceLimits hits are prose in packages/app-shell recording ' + 'that objectui\'s own AppShellRuntimeConfig shares not one key with the spec\'s ' + 'RuntimeConfig, so nothing there authors this key and no pin bump is owed. ADR-0087.', @@ -13359,10 +13359,10 @@ const step18: MigrationStep = { + 'no in-repo runtime reads any of the four — outside `packages/spec/src/system/logging.zod.ts` ' + 'and its test the only occurrences are the generated rows in ' + '`content/docs/references/system/logging.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells ' + + 'objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — spells ' + '`flushInterval` 0 times, `initialDelay` 0, `HttpDestinationConfig` 0 and `LoggingConfig` 0 ' - + 'across its 9800 tracked files, against lit controls `useState` 2464 and `timeout` 1293 on ' - + 'the same corpus (all four 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + + 'across its 9912 tracked files, against lit controls `useState` 2469 and `timeout` 1303 on ' + + 'the same corpus (all four 0 across 9800, against 2464 and 1293, at e420df310, 0 across 9546, against 2449 and 1197, at db11afd49, 0 across 9283, against 2435 and 1172, at dd3f7e1be, 0 across 8512, ' + 'against 2391 and 1096, at f8a9d0fb0, and 0 across ' + '8303, against 2389 and 1086, at 62597c588).', acceptanceCriteria: @@ -17018,10 +17018,11 @@ const step18: MigrationStep = { + 'against a lit control of 1195 defineStack occurrences on that same corpus at fc28c1d38 ' + '(1195 again at 9b62f54671); and the objectui ' + 'checkout this repo builds against — this is the pin, ' - + '`.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c`, re-read from this tree — ' - + 'spells all six metrics def names and both distinctive keys 0 times across 9800 tracked ' - + 'files at that sha, against lit controls window 3873, timeout 1293, period 233, ' - + 'interval 196 and metrics 352 on that same corpus and sha (0 across 9546, against 3772 / ' + + '`.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79`, re-read from this tree — ' + + 'spells all six metrics def names and both distinctive keys 0 times across 9912 tracked ' + + 'files at that sha, against lit controls window 3916, timeout 1303, period 234, ' + + 'interval 196 and metrics 354 on that same corpus and sha (0 across 9800, against 3873 / ' + + '1293 / 233 / 196 / 352, at e420df310, 0 across 9546, against 3772 / ' + '1197 / 228 / 196 / 341, at db11afd49, 0 across 9283, against 3681 / 1172 / 183 / 176 / 340, at dd3f7e1be, 0 across 8512, against 3581 / ' + '1096 / 171 / 179 / 326, at f8a9d0fb0, and 0 across 8303, against 3526 / 1086 / 170 / 179 / ' + '324, at 62597c588), so no pin bump is owed. ' @@ -17235,12 +17236,13 @@ const step18: MigrationStep = { + 'dark control of 0; inside packages/spec the ' + 'only occurrences are tracing.zod.ts, its test, and the generated rows in ' + 'content/docs/references/system/tracing.mdx, which this rename regenerates. And the ' - + 'pinned objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — names none of it: all 37 exports of ' - + 'tracing.zod.ts and each of the four key names occur 0 times across the 9800 files ' + + 'pinned objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — names none of it: all 37 exports of ' + + 'tracing.zod.ts and each of the four key names occur 0 times across the 9912 files ' + 'tracked at that sha (the 486 Span and 53 SpanSchema hits are objectui\'s own HTML ' + 'text-span component, TextSpanSchema, an unrelated name, plus colSpan and prose), against ' - + 'two lit controls on that same corpus and sha: 15352 hits for the bare token objectstack, ' - + 'and 6024 for the package specifier @objectstack/spec (at db11afd49: 0 across 9546, Span 486, ' + + 'two lit controls on that same corpus and sha: 15691 hits for the bare token objectstack, ' + + 'and 6206 for the package specifier @objectstack/spec (at e420df310: 0 across 9800, Span 486, ' + + '15352 and 6024; at db11afd49: 0 across 9546, Span 486, ' + '14704 and 5545; at dd3f7e1be: 0 across 9283, Span 485, 13745 and 5466; at f8a9d0fb0: 0 across 8512, ' + 'Span 488, 13347 and 5123; at 62597c588: 0 across 8303, Span 486, 13125 and 5043).', acceptanceCriteria: @@ -17348,9 +17350,9 @@ const step18: MigrationStep = { + 'bd25e897dc: no in-repo runtime reads the key — outside `packages/spec/src/system/tenant.zod.ts` ' + 'and its test the only occurrences are the four generated rows in ' + '`content/docs/references/system/tenant.mdx`, which this rename regenerates; and the pinned ' - + 'objectui checkout — `.objectui-sha` = `e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c` — spells it 0 ' - + 'times across 9800 tracked files, against lit controls `TTL` 181 and `tenant` 1235 on the ' - + 'same corpus (0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + + 'objectui checkout — `.objectui-sha` = `31971ff1e28f89cfc45f0c19bc5b05e443f28b79` — spells it 0 ' + + 'times across 9912 tracked files, against lit controls `TTL` 181 and `tenant` 1237 on the ' + + 'same corpus (0 across 9800, against 181 and 1235, at e420df310; 0 across 9546, against 181 and 1200, at db11afd49; 0 across 9283, against 181 and 1185, at dd3f7e1be; 0 across 8512, against 156 and 1034, at f8a9d0fb0; 0 across 8303, against 156 ' + 'and 987, at 62597c588).', acceptanceCriteria: 'Every schema-level tenant isolation source spells `performance.schemaCacheTtlSeconds`; ' diff --git a/packages/spec/src/ui/component.test.ts b/packages/spec/src/ui/component.test.ts index 83c6d57f399..7461c2b221c 100644 --- a/packages/spec/src/ui/component.test.ts +++ b/packages/spec/src/ui/component.test.ts @@ -288,7 +288,12 @@ describe('PageAccordionProps variant (#6776)', () => { // same file's `ComponentRegistry.register('accordion', …)` publishes the key to // the Studio block designer at `:1220` (the `items` input, documented as // `[{ label, icon?, collapsed?, children }]`). Measured at the pin this repo -// builds against — `.objectui-sha` = `e420df310`. Re-derived at that pin +// builds against — `.objectui-sha` = `31971ff1e`. Re-derived at that pin +// 2026-10-01: `containers.tsx` is byte-identical across the hop from +// `e420df310` (`git diff --quiet`), and both anchors were re-READ in place and +// still say what this block says: the icon block `1171-1177`, inside +// `PageAccordionRenderer`'s `AccordionTrigger`, and the input `1220`, inside +// the `register('accordion', …)` inputs. At `e420df310`, re-derived at that pin // 2026-09-30: `containers.tsx` changed again across the hop from `db11afd49` (40 // insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot and // objectui#11212's fail-closed permission gates), every hunk of it at `:1286` @@ -403,8 +408,13 @@ describe('PageTabsProps items[].value / items[].count (#5775)', () => { // same file's `ComponentRegistry.register('tabs', …)` publishes the key to the // Studio block designer at `:1005` (the `items` input, documented as // `[{ label, value?, icon?, count?, visibleWhen?, children }]`). Measured at -// the pin this repo builds against — `.objectui-sha` = `e420df310`. Re-derived at -// that pin 2026-09-30: `containers.tsx` changed again across the hop from +// the pin this repo builds against — `.objectui-sha` = `31971ff1e`. Re-derived at +// that pin 2026-10-01: `containers.tsx` is byte-identical across the hop from +// `e420df310` (`git diff --quiet`), and both anchors were re-READ in place and +// still say what this block says: the icon block `946-952`, inside +// `PageTabsRenderer`'s `TabsTrigger`, and the input `1005`, inside the +// `register('tabs', …)` inputs. At `e420df310`, re-derived at that pin +// 2026-09-30: `containers.tsx` changed again across the hop from // `db11afd49` (40 insertions, 26 deletions: objectui#11166's `page:header` // breadcrumb slot and objectui#11212's fail-closed permission gates), every hunk // of it at `:1286` or below, so both anchors were re-READ in place and NEITHER @@ -3474,7 +3484,16 @@ 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` = `e420df310`; re-measured there 2026-09-30 — +// against (`.objectui-sha` = `31971ff1e`; re-measured there 2026-10-01 — +// `plugin-kanban/src/types.ts` is byte-identical to `e420df310` and still +// declares no `limit`; `ObjectKanban.tsx` changed only in the comment above +// its `navigation` read (objectui#8652), below both anchors, so `:722` and +// `:97` did not move; `index.tsx` changed below the mapping (objectui#8652's +// `navigation` input), which did not move from `487-491`; `objectql.ts` +// MOVED the member `4430` -> `4588` byte-identical, still inside +// `ObjectKanbanSchema`; and `plugin-kanban.mdx` gained a `navigation` +// Properties row below the `limit` row and still teaches `limit: 250`. At +// `e420df310`, re-measured there 2026-09-30 — // `plugin-kanban/src/types.ts` is byte-identical to `db11afd49` and still // declares no `limit`; the other four files changed, so their anchors were // re-READ. `ObjectKanban.tsx` changed at BOTH anchors: objectui#9853 renamed @@ -3522,7 +3541,7 @@ describe('#7751 — object-* block props schemas', () => { // `OBJECT_KANBAN_DATA_SOURCE`, ⚠️ `KanbanSchema` is RETIRED at this pin and // `plugin-kanban/src/types.ts` declares the member no more — the published // twin is `ObjectKanbanSchema`, declaring `limit?: number` at -// `packages/types/src/objectql.ts:4430` — and `content/docs/plugins/plugin-kanban.mdx` +// `packages/types/src/objectql.ts:4588` — and `content/docs/plugins/plugin-kanban.mdx` // teaches `limit: 250` with a Properties row. The strict map refused the key by // name — the same `unrecognized_keys` verdict as the `bogusProp` control — so an // author following the published docs wrote a node the save gate rejected. @@ -3584,7 +3603,13 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // since retired that block, objectui#8257). 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` = `e420df310`; re-measured there 2026-09-30 — +// against (`.objectui-sha` = `31971ff1e`; re-measured there 2026-10-01 — +// `KanbanImpl.tsx` and `KanbanBoardCore.tsx` are byte-identical to +// `e420df310`, so `:621`, `:634`, `:78` and `:111-112` did not move and were +// re-read in place; `ObjectKanban.tsx` changed only in the comment above its +// `navigation` read (objectui#8652), above the spread, which MOVED `1641` -> +// `1639` with its line byte-identical; and the counts re-read the same, 2 / +// 2 / 11. At `e420df310`, re-measured there 2026-09-30 — // `KanbanImpl.tsx` is byte-identical to `db11afd49`, so `:621` and `:634` did // not move and were re-read in place; `ObjectKanban.tsx` changed, carrying // objectui's own half of this retirement: objectui#8285 stopped forwarding @@ -3612,7 +3637,7 @@ describe('ObjectKanbanPropsSchema limit — the row cap four objectui faces alre // below it, not the spread; at `f8a9d0fb0` (2026-09-24) it // had MOVED `1563` -> `1578` the same way (objectui#10068's `$orderby`); both // files were byte-identical across the hop onto `62597c588`, and every anchor -// was re-READ at `87af769e9` 2026-09-20) `ObjectKanban.tsx:1641` +// was re-READ at `87af769e9` 2026-09-20) `ObjectKanban.tsx:1639` // spreads the // authored bag into `KanbanBoardCore` (into `KanbanRenderer` until // objectui#11234, which reads the pair off it no more) and `KanbanImpl` gates the affordance on @@ -3715,7 +3740,11 @@ describe('ObjectKanbanPropsSchema quickAdd is retired (#17260)', () => { // #9881 and commit 60e0f900a recorded the accordion and tab items; these two close the set. // // The button record re-measured at the pin this repo builds against — -// `.objectui-sha` = `e420df310`, re-derived there 2026-09-30: `button.tsx`, +// `.objectui-sha` = `31971ff1e`, re-derived there 2026-10-01: `button.tsx`, +// `lazy-icon.tsx`, `resolve-icon.ts` and the generated +// `lucide-record-icon-names.ts` are byte-identical to `e420df310` +// (`git diff --quiet`), so every anchor below holds unmoved and was re-read in +// place. At `e420df310`, 2026-09-30: `button.tsx`, // `lazy-icon.tsx`, `resolve-icon.ts` and the generated // `lucide-record-icon-names.ts` are byte-identical to `db11afd49`, so every // anchor below holds unmoved and was re-read in place. At `db11afd49`, @@ -3861,8 +3890,12 @@ 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` = `e420df310`; re-measured there 2026-09-30 — all three -// renderers changed on the hop off `db11afd49`, `ObjectMap.tsx` in one docblock +// (`.objectui-sha` = `31971ff1e`; re-measured there 2026-10-01 — +// `ObjectMap.tsx`, `ObjectGantt.tsx`, `ObjectTree.tsx` and `record-source.ts` +// are byte-identical across the hop off `e420df310` (`git diff --quiet`), so +// every anchor below holds unmoved and the set of `schema.*` keys each renderer +// reads is the one recorded at `e420df310`. At `e420df310`, 2026-09-30 — all +// three renderers changed on the hop off `db11afd49`, `ObjectMap.tsx` in one docblock // only, `ObjectGantt.tsx` +85/-26 (objectui#11141's inclusive date-only end, // objectui#8348, objectui#11070) and `ObjectTree.tsx` +29/-26, and the set of // `schema.*` keys each reads is the same at both pins. ⚠️ One read changed diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 3726abc67df..ddb34275e88 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -773,8 +773,12 @@ export const PageTabsProps = strictObject({ * false candidate a component over). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — - * `containers.tsx` changed again across the hop from `db11afd49` (40 + * (`.objectui-sha` = `31971ff1e`; re-derived at that pin 2026-10-01 — + * `containers.tsx` is byte-identical across the hop from `e420df310` + * (`git diff --quiet`), so both anchors were re-READ in place, each still + * the read point this record names: `946-952` and `1005`. At `e420df310` + * (2026-09-30) `containers.tsx` changed again across the hop from + * `db11afd49` (40 * insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot * and objectui#11212's fail-closed permission gates), every hunk at `:1286` * or below, so both anchors were re-READ in place and NEITHER moved, each @@ -967,7 +971,15 @@ export const PageCardProps = strictObject({ * declarations identical. * * Each clause is read off the objectui pin this repo builds against - * (`.objectui-sha` = `e420df310f5b`, re-read there 2026-09-30: across the hop + * (`.objectui-sha` = `31971ff1e28f`, re-read there 2026-10-01: across the hop + * from `e420df310f5b` `record-details.tsx`, `record-highlights.tsx` and the + * three `permissions` files are byte-identical, so NO anchor in those five + * moved; `record-related-list.tsx` changed one docblock line above the gate + * (objectui#11270's `record_related` row actions, +3/-2) and + * `record-quick-actions.tsx` rewrote its fail-closed docblock to say the bar + * is replaced by the notice rather than hidden (objectui#10224, +8/-2), so + * every anchor in those two MOVED with its cited text byte-identical, by 1 + * and by 6. At `e420df310f5b`, 2026-09-30: across the hop * from `db11afd4967c` `record-quick-actions.tsx` and `usePermissions.ts` are * byte-identical, `MePermissionsProvider.tsx` and `PermissionProvider.tsx` each * gained one `effectiveObjects` member below every cited line (objectui#4421), @@ -997,22 +1009,22 @@ export const PageCardProps = strictObject({ * * 1. CAPABILITIES, NOT OBJECT ACTIONS. Every block gates through * `perms.hasCapabilities(required)` — `record-details.tsx:244`, - * `record-highlights.tsx:102`, `record-related-list.tsx:364`, - * `record-quick-actions.tsx:267` — and never `perms.can(objectName, …)`; + * `record-highlights.tsx:102`, `record-related-list.tsx:365`, + * `record-quick-actions.tsx:273` — and never `perms.can(objectName, …)`; * each renderer's docblock states the capability "is not object-scoped" * (`record-details.tsx:233`, `record-highlights.tsx:81`, - * `record-related-list.tsx:351`, `record-quick-actions.tsx:256`). `read` + * `record-related-list.tsx:352`, `record-quick-actions.tsx:262`). `read` * is looked up in the capability set like any other name. * 2. ALL OF THEM. `MePermissionsProvider.tsx:416` is * `required.every((p) => held.has(p))`. * 3. THE OUTCOME. Each block returns a `role="status"` "Insufficient * permissions to view …" notice instead of its content — * `record-details.tsx:244-252`, `record-highlights.tsx:160-173`, - * `record-related-list.tsx:364-372`, `record-quick-actions.tsx:267-275`. + * `record-related-list.tsx:365-373`, `record-quick-actions.tsx:273-281`. * Checks that already withhold the content run first on two of them (no * record bound, `record-details.tsx:188`; no object, - * `record-related-list.tsx:297`; the related object's read gate, - * `record-related-list.tsx:315`), which is why the text says "wherever it + * `record-related-list.tsx:298`; the related object's read gate, + * `record-related-list.tsx:316`), which is why the text says "wherever it * would otherwise render" rather than promising the notice unconditionally. * 4. PRESENTATION ONLY. The gate is renderer code: nothing in this repo's * server packages reads a page component's `requiredPermissions`. The data @@ -2065,8 +2077,12 @@ export const PageAccordionProps = strictObject({ * re-derive the same false candidate). * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — - * `containers.tsx` changed again across the hop from `db11afd49` (40 + * (`.objectui-sha` = `31971ff1e`; re-derived at that pin 2026-10-01 — + * `containers.tsx` is byte-identical across the hop from `e420df310` + * (`git diff --quiet`), so both anchors were re-READ in place, each still + * the read point this record names: `1171-1177` and `1220`. At `e420df310` + * (2026-09-30) `containers.tsx` changed again across the hop from + * `db11afd49` (40 * insertions, 26 deletions: objectui#11166's `page:header` breadcrumb slot * and objectui#11212's fail-closed permission gates), every hunk at `:1286` * or below, so both anchors were re-READ in place and NEITHER moved, each @@ -2344,11 +2360,12 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ * the button. * * The key is LIVE at the objectui pin this repo builds against - * (`.objectui-sha` = `e420df310`; re-derived at that pin 2026-09-30 — + * (`.objectui-sha` = `31971ff1e`; re-derived at that pin 2026-10-01 — * `button.tsx`, `lib/lazy-icon.tsx`, `renderers/action/resolve-icon.ts` and the - * generated `lucide-record-icon-names.ts` are all byte-identical to `db11afd49` + * generated `lucide-record-icon-names.ts` are all byte-identical to `e420df310` * across the hop onto this pin, so every anchor below holds unmoved and was - * re-checked in place; they were byte-identical to `dd3f7e1be` across the hop + * re-checked in place; they were byte-identical to `db11afd49` across the hop + * onto `e420df310` (2026-09-30), and byte-identical to `dd3f7e1be` across the hop * onto `db11afd49` (2026-09-29) as well. At `dd3f7e1be` (2026-09-28) * `button.tsx` and `lib/lazy-icon.tsx` are byte-identical to `f8a9d0fb0` * (`git diff --quiet`); `resolve-icon.ts` changed on this hop (+42/-17, @@ -2817,9 +2834,16 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * had no row for — `action:button`, `action:group`, `action:menu`, * `action:icon`, `element:definition-list`, `element:repeater` * (`core/src/registry/public-blocks.ts:117-122` at the pin this repo builds - * against, `.objectui-sha` = `e420df310`; first measured at `.objectui-sha` + * against, `.objectui-sha` = `31971ff1e`; first measured at `.objectui-sha` * pin `f8a9d0fb0`, every read point below re-derived at the current pin - * 2026-09-30: `public-blocks.ts`, `auto-trigger.ts` and `static-params.ts` are + * 2026-10-01: `public-blocks.ts`, `auto-trigger.ts`, `static-params.ts`, + * `action-group.tsx`, `action-menu.tsx` and `action-icon.tsx` are + * byte-identical to `e420df310`, so their anchors held unmoved, and + * `action-button.tsx` changed only inside its registration (+9/-1, + * objectui#11168 slice 2: the `size` input now publishes the five sizes its + * row declares, where it published `sm` / `md` / `lg`), so every read point + * in it held unmoved and the `inputs` range grew `414-551` -> `414-559`. At + * `e420df310` (2026-09-30): `public-blocks.ts`, `auto-trigger.ts` and `static-params.ts` are * byte-identical to `db11afd49`, and all four `action-*.tsx` renderers changed * — objectui#11168 slice 1 rewrote every registration's `inputs` to publish * the keys the block path honours, objectui#11212 made each `visible` gate @@ -2999,14 +3023,16 @@ const ACTION_NODE_GUIDANCE = { * `target` (`ACTION_NODE_ALIASES` above) — one concept, one spelling, one * verdict on the action and on the block that runs it. * - * The registration's `inputs` (`:414-551`) publish twenty-seven of the + * The registration's `inputs` (`:414-559`) publish twenty-seven of the * twenty-nine keys the renderer forwards or reads since objectui#11168 slice 1 - * (at `db11afd49` they published seven, `:395-416`); the two left unpublished - * are `endpoint` (refused here, above) and `undoable`, each still forwarded - * (`:215-311`), on objectui's measurement that the console's own `api` handler - * reads `target` and never `endpoint`, and that the runner offers Undo only - * with a host row stash this block never writes. It also publishes - * `className`, which is read but is a node key. + * (at `db11afd49` they published seven, `:395-416`), and since slice 2 the + * `size` input publishes the same five values this row declares (`default`, + * `sm`, `md`, `lg`, `icon`); the two left unpublished are `endpoint` (refused + * here, above) and `undoable`, each still forwarded (`:215-311`), on + * objectui's measurement that the console's own `api` handler reads `target` + * and never `endpoint`, and that the runner offers Undo only with a host row + * stash this block never writes. It also publishes `className`, which is read + * but is a node key. */ export const ActionButtonPropsSchema = lazySchema(() => strictObject({ surface: 'this `action:button`', @@ -3916,11 +3942,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` = `e420df310`; re-derived at that pin 2026-09-30 — + * (`.objectui-sha` = `31971ff1e`; re-derived at that pin 2026-10-01 — * `plugin-dashboard/src/index.tsx`, `ObjectMetricWidget.tsx`, * `MetricWidget.tsx`, `MetricCard.tsx` and `lazy-icon.tsx` are all - * byte-identical to `db11afd49`, so every anchor below holds unmoved and was - * re-read in place. At `db11afd49` (2026-09-29) + * byte-identical to `e420df310`, so every anchor below holds unmoved and was + * re-read in place; they were byte-identical to `db11afd49` across the hop + * onto `e420df310` (2026-09-30) as well. At `db11afd49` (2026-09-29) * `MetricWidget.tsx` and `lazy-icon.tsx` are byte-identical to `dd3f7e1be`; * `ObjectMetricWidget.tsx` changed in one comment line (`:321`), so `:230` and * `:595` did not move; `plugin-dashboard/src/index.tsx` gained the @@ -4044,7 +4071,11 @@ 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` = `e420df310`; re-measured there 2026-09-30 — + * (`.objectui-sha` = `31971ff1e`; re-measured there 2026-10-01 — + * `ObjectKanban.tsx` changed on this hop only in the comment above its + * `navigation` read (+12/-14, objectui#8652: the key is now declared on + * `ObjectKanbanSchema`), far below the anchor, which did not move and was + * re-read in place. At `e420df310` (2026-09-30) * `ObjectKanban.tsx` changed on this hop, +40/-13: objectui#9853 renamed the * default `DEFAULT_KANBAN_LIMIT` to `DEFAULT_KANBAN_FETCH_BATCH_SIZE`, still * 100, on the anchor line itself, and objectui#11234 mounts an internal @@ -4112,7 +4143,21 @@ 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` = `e420df310`; re-measured there + * repo builds against (`.objectui-sha` = `31971ff1e`; re-measured there + * 2026-10-01 — `plugin-kanban/src/types.ts` (still no row-cap member) and + * `ElementDataSourceGate.tsx` are byte-identical to `e420df310`, so + * `:218-220` and `:437-456` did not move; `ObjectKanban.tsx` changed only in + * the comment above its `navigation` read (objectui#8652), below every + * anchor here, so the query `715-725`, the default `:97`, the `queryFilter` + * resolution `:584-585` and the import `:10` did not move; + * `plugin-kanban/src/index.tsx` changed below the mapping (objectui#8652's + * `navigation` input), which did not move from `487-491`; + * `element-data-source.ts` changed above `savedViewRawLimit` (+33/-6, + * objectui#8945's re-stated `dataSource.filter` note), which MOVED byte-identical `241-245` -> `268-272`; + * `objectql.ts` MOVED the member `4430` -> `4588` byte-identical, still + * inside `ObjectKanbanSchema`; and `plugin-kanban.mdx` gained a `navigation` + * Properties row below the `limit` row, which, with the `limit: 250` + * snippet, did not change. At `e420df310`, re-measured there * 2026-09-30 — `plugin-kanban/src/types.ts` (still no row-cap member), * `ElementDataSourceGate.tsx` and `element-data-source.ts` are * byte-identical to `db11afd49`, so `:218-220`, `:437-456` and `:241-245` did @@ -4187,7 +4232,7 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * through is `ObjectKanbanSchema` — ⚠️ `KanbanSchema` was RETIRED on this hop * (maintainer ruling 2026-09-09) and `plugin-kanban/src/types.ts` no longer * declares the member at all — imported at `ObjectKanban.tsx:10` and - * declaring `limit?: number` at `packages/types/src/objectql.ts:4430`, + * declaring `limit?: number` at `packages/types/src/objectql.ts:4588`, * and `content/docs/plugins/plugin-kanban.mdx` * teaches it with a typed snippet (`limit: 250`) plus a Properties row. So * an author following the published docs wrote a node the save gate @@ -4230,7 +4275,7 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * * ⚠️ And the view half is `pagination.pageSize` ALONE on this face. * `savedViewLimit` does fall back to a flat `view.limit` - * (`core/src/data-scope/element-data-source.ts:241-245`), but that names a + * (`core/src/data-scope/element-data-source.ts:268-272`), but that names a * THIRD face — a saved-view RECORD as the adapter's `listViews()` returns it * — not an authored view document. Measured on this tree: `ListViewSchema` * REFUSES a flat `limit` with `unrecognized_keys: ["limit"]`, the same @@ -4282,8 +4327,15 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * quick-add control and no block a document can name offers one either). * * Measured at the objectui pin this repo builds against - * (`.objectui-sha` = `e420df310`; re-measured there 2026-09-30 — - * `KanbanImpl.tsx` is byte-identical to `db11afd49`, so `:621` and `:634` did + * (`.objectui-sha` = `31971ff1e`; re-measured there 2026-10-01 — + * `KanbanImpl.tsx` and `KanbanBoardCore.tsx` are byte-identical to + * `e420df310`, so `:621`, `:634` and `:78` did not move and were re-read in + * place; `ObjectKanban.tsx` changed only in the comment above its + * `navigation` read (objectui#8652), above the spread, which MOVED `1641` -> + * `1639` with its line byte-identical; and `plugin-kanban/src/index.tsx` + * changed below the exported `KanbanRenderer` (objectui#8652's `navigation` + * input), whose pass-through did not move from `345-346`. At `e420df310` + * (2026-09-30) `KanbanImpl.tsx` was byte-identical to `db11afd49`, so `:621` and `:634` did * not move and were re-read in place. objectui's own half of this retirement * landed on this hop and changed the PATH, not the verdict: objectui#8285 * stopped forwarding `quickAdd`, and objectui#11234 retired @@ -4324,9 +4376,10 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * host-supplied FUNCTION that JSON cannot carry and no producer puts on an * `object-kanban` node. `ObjectKanban.tsx` names neither half of the pair * in code (0 occurrences each, against 11 for the sibling `onCardClick` in the same - * file — re-counted at `db11afd49`, at `f8a9d0fb0` and at `62597c588`; at this - * pin 2 each, all four inside the two objectui#11234 comments that record - * the cut, and still 11 for `onCardClick`; this record said 6, which the identical + * file — re-counted at `db11afd49`, at `f8a9d0fb0` and at `62597c588`; at + * `e420df310` and again at this pin 2 each, all four inside the two + * objectui#11234 comments that record the cut, and still 11 for + * `onCardClick`; this record said 6, which the identical * file at `87af769e9` does not reproduce either, so the control was * miscounted rather than moved, and the verdict rests on the 0). So the gate was permanently false and authoring the key was a * parse-clean no-op — the accepted-and-dropped class. @@ -4344,8 +4397,8 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * renders directly and can hand the runtime function to. ⚠️ Re-read at * `e420df310`: objectui retired the schema-only `kanban-ui` registration * long before this pin (objectui#8257; 0 registrations at `dd3f7e1be`, - * `db11afd49` and this pin), and the pair now lives on the exported - * `KanbanRenderer` React component a host mounts directly + * `db11afd49`, `e420df310` and this pin), and the pair now lives on the + * exported `KanbanRenderer` React component a host mounts directly * (`plugin-kanban/src/index.tsx:345-346`), not on any block a document can * name. ⛔ So the tombstone below prescribes "delete the key" and names no * block: this spec declares no `kanban-ui` component type, so a node an @@ -4371,7 +4424,15 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * element schemas). * * Measured at the pin this repo builds against (`.objectui-sha` = - * `e420df310`; re-measured there 2026-09-30 — `ObjectKanban.tsx` changed on + * `31971ff1e`; re-measured there 2026-10-01 — `ObjectKanban.tsx` changed on + * this hop in one place, the comment above the `navigation` read (+12/-14, + * objectui#8652, the objectui half of the same ruling: the key is now + * declared on `ObjectKanbanSchema`), so the read itself did not change and + * every anchor MOVED up by 2 with its cited text byte-identical: the read + * `1282` -> `1280`, the hand-off `1290-1291` -> `1288-1289`, the overlay + * render `1540` -> `1538` and its `NavigationOverlay` `1556-1569` -> + * `1554-1567`, and the card click `1665` -> `1663`. At `e420df310` + * (2026-09-30) `ObjectKanban.tsx` changed on * this hop, +40/-13: objectui#9853's renamed fetch-batch default, and * objectui#11234's internal `KanbanBoardCore` with the comments that record * the Quick Add cut, all of it re-mapped through the diff. Every anchor MOVED @@ -4397,15 +4458,17 @@ export const ObjectKanbanPropsSchema = lazySchema(() => strictObject({ * re-READ at `87af769e9` 2026-09-22 — every anchor re-derived from that * tree rather than carried, none of them at its `53ded82bf` number and one * of them no longer spelled the way this record quoted it): - * `ObjectKanban.tsx:1282` + * `ObjectKanban.tsx:1280` * reads `schema.navigation ?? { mode: 'drawer' }` and hands it to - * `useNavigationOverlay` (`:1290-1291`), whose result drives the card click - * (`:1665`) and the detail overlay (`:1540`, whose `NavigationOverlay` is - * `:1556-1569`) — on a STANDALONE board, with no enclosing view to resolve + * `useNavigationOverlay` (`:1288-1289`), whose result drives the card click + * (`:1663`) and the detail overlay (`:1538`, whose `NavigationOverlay` is + * `:1554-1567`) — on a STANDALONE board, with no enclosing view to resolve * a mode from. ⚠️ The `(schema as any)` cast this record used to quote is - * GONE at this pin: the read is spelled `schema.navigation` and compiles - * through `BaseSchema`'s index signature, which the comment above it - * records (objectui#9726). The READ is unchanged — only its spelling is — + * GONE at this pin: the read is spelled `schema.navigation`, and it now + * compiles through a DECLARED `ObjectKanbanSchema.navigation` member + * (objectui#8652, the mirror of this row), where through `e420df310` it + * compiled through `BaseSchema`'s index signature, as the comment above it + * then recorded (objectui#9726). The READ is unchanged — only its spelling is — * so nothing this record says about the key moves. Until this key the same * document ran correctly in the renderer and was refused BY NAME at the * authoring door, through the generic `unrecognized_keys` rule a typo gets. @@ -4523,7 +4586,18 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * `object-kanban`'s above. * * Measured at the pin this repo builds against (`.objectui-sha` = - * `e420df310`; re-measured there 2026-09-30 — `ObjectCalendar.tsx` changed + * `31971ff1e`; re-measured there 2026-10-01 — `ObjectCalendar.tsx` changed + * across the hop from `e420df310` in one place (+11/-19, objectui#8652, the + * objectui half of the same ruling): `navigation` is now DECLARED on + * `ObjectCalendarSchema`, so the ledger comment `1008-1025` that recorded + * the one cast objectui#8651 left standing is replaced by the declaration + * note `1008-1017`, and the read `1026` -> `1018` CHANGED CONTENT, toward + * this row: it is spelled `schema.navigation ?? { mode: 'drawer' }`, with no + * cast. Every anchor after it MOVED up by 8 with its cited text + * byte-identical — the hand-off `1028-1029` -> `1020-1021`, the event click + * `1421` -> `1413`, the overlay render `1341` -> `1333`, its + * `NavigationOverlay` `1354-1367` -> `1346-1359`. At `e420df310` + * (2026-09-30) `ObjectCalendar.tsx` changed * across the hop from `db11afd49` in comments only (+6/-3, objectui#11073), * two of them inside the ledger comment, which now says `@objectstack/spec` * 17.5.0 ships the `navigation` declaration that card waited on and that @@ -4560,12 +4634,12 @@ export const ObjectCalendarPropsSchema = lazySchema(() => strictObject({ * 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 - * `:1008-1025` as the one objectui#8651 left standing): `ObjectCalendar.tsx:1026` - * reads `(schema as any).navigation ?? { mode: 'drawer' }`, `:1028-1029` hands - * it to `useNavigationOverlay`, `:1421` fires it on an event click and - * `:1341` renders the overlay (its `NavigationOverlay` is `:1354-1367`) — + * `87af769e9` (2026-09-22) none was at its `53ded82bf` number. Through + * `e420df310` the cast was still spelled here, unlike its `object-kanban` + * twin; at this pin it is gone on both): `ObjectCalendar.tsx:1018` + * reads `schema.navigation ?? { mode: 'drawer' }`, `:1020-1021` hands + * it to `useNavigationOverlay`, `:1413` fires it on an event click and + * `:1333` renders the overlay (its `NavigationOverlay` is `:1346-1359`) — * standalone, with no enclosing view. */ navigation: NavigationConfigSchema.optional() @@ -4827,7 +4901,13 @@ 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` = `e420df310`, re-measured there 2026-09-30: all three files + * `.objectui-sha` = `31971ff1e`, re-measured there 2026-10-01: `ObjectMap.tsx` + * and `core/src/utils/record-source.ts` are byte-identical to `e420df310`, so + * every anchor in them holds unmoved and was re-read in place; `index.tsx` + * changed (+7/-3, objectui#10859 batch 5: the comment above the registration + * and the `objectName` input's description now count the node's + * `dataSource` binding), the `map` input MOVING `:97` -> `:101` + * byte-identical. At `e420df310` (2026-09-30) all three files * changed across the hop from `db11afd49`, so every anchor was re-mapped * through the diff and re-READ. `ObjectMap.tsx` (+16/-17, objectui#8348) * changed only in the record-source docblock above `getDataConfig`, one line @@ -4896,7 +4976,7 @@ const OBJECT_MAP_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * `sort` (`:952` and `:1031`, through the shared `convertSortToQueryParams` sink * to `$orderby`), `map` (`:405` — `getMapConfig` branch 1, the author face and * the registration's declared `{ name: 'map', type: 'object' }` input, - * `plugin-map/src/index.tsx:97`; the second copy this record cited at `:78` + * `plugin-map/src/index.tsx:101`; the second copy this record cited at `:78` * belonged to the bare `map` registration, retired on objectui#10393, so * `object-map` is now the one registration declaring it), `mapStyle` * (`:400`, `schema.mapStyle || schema.map?.style`), `navigation` (`:1182`) and @@ -4959,7 +5039,11 @@ 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` = `e420df310`, re-READ there + * pin this repo builds against (`.objectui-sha` = `31971ff1e`, re-READ there + * 2026-10-01: `ObjectMap.tsx` is byte-identical to `e420df310`, so all three + * anchors held unmoved, and `plugin-map/src/index.tsx` changed above its + * inputs (objectui#10859 batch 5), the `filter` input MOVING `:100` -> + * `:104` byte-identical. At `e420df310`, re-READ there * 2026-09-30: `ObjectMap.tsx` changed only in the record-source docblock * above both anchors, one line shorter (objectui#8348), so each MOVED up one * line with its text byte-identical, `:858` -> `:857`, `:952` -> `:951` and @@ -4993,7 +5077,11 @@ 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` = `e420df310`, re-READ there 2026-09-30 — `ObjectMap.tsx` + * (`.objectui-sha` = `31971ff1e`, re-READ there 2026-10-01 — `ObjectMap.tsx` + * is byte-identical to `e420df310`, so both anchors held unmoved, and + * `plugin-map/src/index.tsx` changed above its inputs (objectui#10859 batch + * 5), the `sort` input MOVING `:101` -> `:105` byte-identical. At + * `e420df310`, re-READ there 2026-09-30 — `ObjectMap.tsx` * changed only in the record-source docblock above both anchors, one line * shorter (objectui#8348), so both MOVED up one line with their text * byte-identical, `:953` -> `:952` and `:1032` -> `:1031`; and @@ -5016,7 +5104,7 @@ export const ObjectMapPropsSchema = lazySchema(() => strictObject({ * declare against, so the legacy string clause is refused here for the same * ruling. Through `db11afd49` `plugin-map/src/index.tsx` declared no `sort` * input at all; since objectui#8220 it declares one in this array form - * (`:101`), so the registry and this door agree. + * (`:105`), so the registry and this door agree. */ sort: z.array(SortItemSchema).optional() .describe('Marker order for the fetched records — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array`'), @@ -5088,7 +5176,13 @@ 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` = `e420df310`, re-measured there 2026-09-30: all three + * — `.objectui-sha` = `31971ff1e`, re-measured there 2026-10-01: + * `ObjectGantt.tsx` and `record-source.ts` are byte-identical to `e420df310`, + * so every anchor in them holds unmoved and was re-read in place; + * `plugin-gantt/src/index.tsx` changed (+3/-2, objectui#11117: the + * `objectName` input's description and the comment above the registration + * now count a `dataSource.object` binding), the `gantt` input MOVING `:145` + * -> `:146` byte-identical. At `e420df310` (2026-09-30) all three * files changed across the hop from `db11afd49`, and every anchor was * re-mapped through the diff and re-READ; NONE changed content. `ObjectGantt.tsx` * +85/-26 (objectui#11141's inclusive date-only end, objectui#8348's @@ -5134,7 +5228,7 @@ const OBJECT_GANTT_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * `$filter`; at `f8a9d0fb0` it was sent verbatim), `sort` (`:1064`, through * `convertSortToQueryParams` to `$orderby`), `gantt` (`:610-612` — * `getGanttConfig` branch 1, the author face and the registration's declared - * `{ name: 'gantt', type: 'object' }` input (`plugin-gantt/src/index.tsx:145`), + * `{ name: 'gantt', type: 'object' }` input (`plugin-gantt/src/index.tsx:146`), * validated there against this * repo's own {@link GanttConfigSchema}), `navigation` (`:1923`), `label` * (`:2505`, resolved through `resolveInlineI18nLabel` for the export file @@ -5241,10 +5335,15 @@ export type ObjectGanttPropsParsed = z.infer; /** * The flat `TreeConfig` spellings `getTreeConfig` reads ahead of the `tree` * block (`ObjectTree.tsx:281-294`) and that `ObjectView` / `ListView` EMIT when - * they flatten `options.tree` (`ListView.tsx:3722-3741`, `case 'tree'`: the + * they flatten `options.tree` (`ListView.tsx:3756-3775`, `case 'tree'`: the * product carries these keys, the EFFECTIVE `filter` objectui#10250 added, and * NO `tree` key). Both halves re-READ at the - * pin this repo builds against (`.objectui-sha` = `e420df310`) on 2026-09-30 — + * pin this repo builds against (`.objectui-sha` = `31971ff1e`) on 2026-10-01 — + * `ObjectTree.tsx` is byte-identical to `e420df310`, so `281-294` and + * `233-254` did not move; `ListView.tsx` changed above its `case 'tree'` arm + * (+35/-1, objectui#10689's re-read on a harvested query input), which MOVED + * byte-identical `3722-3741` -> `3756-3775`, so the flat key set each reads or + * emits did not move. At `e420df310`, 2026-09-30 — * `ObjectTree.tsx` changed (+29/-26, objectui#8348's record-source arm and the * host-data read below it), none of it inside `281-294` or `233-254`, which did * not move; `ListView.tsx` changed above its `case 'tree'` arm (objectui#11021 @@ -5269,7 +5368,7 @@ export type ObjectGanttPropsParsed = z.infer; * * `titleField` is in the set although no `tree` block key is spelled that way: * `ListView`'s flatten resolves `treeCfg.titleField` into `labelField` before - * emitting (`ListView.tsx:3736`, `labelField: treeCfg.labelField || + * emitting (`ListView.tsx:3770`, `labelField: treeCfg.labelField || * treeCfg.titleField || 'name'`), so the author's intent is always the block's * `labelField`, and the prescription below says so. * @@ -5298,8 +5397,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` = `e420df310`, re-measured there 2026-09-30: - * `plugin-tree/src/index.tsx` is byte-identical to `db11afd49`; + * — `.objectui-sha` = `31971ff1e`, re-measured there 2026-10-01: + * `plugin-tree/src/index.tsx`, `ObjectTree.tsx` and + * `core/src/utils/record-source.ts` are byte-identical to `e420df310` + * (`git diff --quiet`), so every anchor below holds unmoved and was re-read in + * place. At `e420df310` (2026-09-30) + * `plugin-tree/src/index.tsx` was byte-identical to `db11afd49`; * `core/src/utils/record-source.ts` changed (+38/-27) and its three rungs MOVED * by 8 with their text byte-identical; `ObjectTree.tsx` changed (+29/-26, * objectui#8348), and TWO anchors changed CONTENT, both toward this row, and @@ -5395,8 +5498,8 @@ const OBJECT_TREE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * This was also the one block of the three whose type was absent from the * tracked `sdui.manifest.json`, so `check:react-declaration-parity` reported * it as missing from the registry rather than comparing it. ⚠️ Re-read at - * `e420df310`: the manifest regenerated at this pin carries an `object-tree` - * entry, as the one at `db11afd49` already did. The derivation above is from + * `31971ff1e`: the manifest regenerated at this pin carries an `object-tree` + * entry, as the ones at `db11afd49` and `e420df310` already did. The derivation above is from * the renderer's sources at the pin either way, which is what #7751's method * asks for. * @@ -5420,7 +5523,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` = `e420df310`, re-COUNTED there 2026-09-30 by + * builds against (`.objectui-sha` = `31971ff1e`, re-COUNTED there 2026-10-01 by + * the same method — 0 for the tree, whose shell is byte-identical, and 3 each + * for `plugin-grid` (byte-identical), `plugin-gantt` and `plugin-calendar` + * (both changed on the hop: objectui#11117 in each, and objectui#8652's + * `navigation` input in the calendar's), but 4 for `plugin-map`: objectui#10859 batch 5 added a + * comment that names the gate, while its wiring is the same three lines; + * re-COUNTED at `e420df310` 2026-09-30 by * the same method — 3 each again and 0 for the tree; `plugin-map` and * `plugin-gantt`'s `src/index.tsx` changed on the hop (objectui#8220's * `filter` / `sort` inputs) and still read 3, the tree's is byte-identical; @@ -5429,8 +5538,9 @@ export const ObjectTreePropsSchema = lazySchema(() => strictObject({ * `plugin-grid/src/index.tsx` changed on the hop and still reads 3; re-COUNTED * at `dd3f7e1be` * 2026-09-28) 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 every end + * each in `plugin-gantt`, `plugin-grid` and `plugin-calendar` and 4 in + * `plugin-map` (three of them the wiring, one the comment above it) — four + * controls, so the zero discriminates. ⚠️ Re-counted at every end * with ONE method (occurrences of that identifier in each `src/index.tsx`): * `f8a9d0fb0`, `62597c588`, `87af769e9` and `53ded82bf` all read 3 each and 0 for the tree as well — * `plugin-grid/src/index.tsx` changed on the hop onto `62597c588` and still reads 3, and @@ -5518,8 +5628,20 @@ 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` = `e420df310`), re-measured there - * 2026-09-30: `ObjectTimeline.tsx` is byte-identical to `db11afd49`, so every + * repo builds against (`.objectui-sha` = `31971ff1e`), re-measured there + * 2026-10-01: `renderer.tsx` is byte-identical to `e420df310`, so its three + * anchors hold unmoved; `ObjectTimeline.tsx` changed (+42/-2, objectui#8654: + * a typed `navigation` member on the component's props, and the navigation + * read without its cast), every anchor below line 173 MOVING by 35 (by 40 + * past the navigation read) with its cited text byte-identical, and ONE + * changing CONTENT, toward this row: the navigation binding `:753-756` -> + * `:793-796` now reads `schema.navigation` where it read + * `(schema as any).navigation`; `index.tsx` changed (+21/-0, objectui#8654: a + * `navigation` input on both registrations, with its shared description), the + * `variant` input MOVING `374-375` -> `393-394` byte-identical and `:333` not + * moving. The `schema.*` read set of all three is unchanged — `navigation` + * was read before, through the cast. At `e420df310` (2026-09-30) + * `ObjectTimeline.tsx` was byte-identical to `db11afd49`, so every * anchor in it held unmoved and was re-read in place; `index.tsx` changed * (+17/-0, objectui#8220: `filter` / `sort` inputs on both registrations, in * the array forms this row declares, and their shared descriptions above the @@ -5570,32 +5692,32 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * directions: nothing accepted a real key and nothing refused a typo. * * Read points per key, in `ObjectTimeline.tsx` unless named otherwise: - * `objectName` (`:256` the fetch gate, `:336-339` the object-def load through - * `useSettledSchema`, `:374` the `fetchesForItself` gate — the check that + * `objectName` (`:291` the fetch gate, `:371-374` the object-def load through + * `useSettledSchema`, `:409` the `fetchesForItself` gate — the check that * sat inside the fetch effect at `:420` is hoisted to render scope at this - * pin, where `:375` also subscribes the invalidation bus on it - * (objectui#10623) and the effect reads it at `:479` — `:381` / `:439` + * pin, where `:410` also subscribes the invalidation bus on it + * (objectui#10623) and the effect reads it at `:514` — `:416` / `:474` * inside the fetch effect, - * `:750` the pull-to-refresh gate, `:561` the entry composition and `:755` - * the navigation binding), `timeline` (`:273`, + * `:785` the pull-to-refresh gate, `:596` the entry composition and `:795` + * the navigation binding), `timeline` (`:308`, * the canonical nested config every field resolution prefers), `filter` - * (`:361`, read once through `useResolvedFilter`, which resolves its context - * tokens — objectui#10666 — and holds the result; `:362` keys the fetch on - * it and `:440` sends it to `$filter`, where `f8a9d0fb0` sent `schema.filter` - * verbatim at `:341` / `:405`), `sort` (`:364`, `:441` — through + * (`:396`, read once through `useResolvedFilter`, which resolves its context + * tokens — objectui#10666 — and holds the result; `:397` keys the fetch on + * it and `:475` sends it to `$filter`, where `f8a9d0fb0` sent `schema.filter` + * verbatim at `:341` / `:405`), `sort` (`:399`, `:476` — through * the shared `convertSortToQueryParams` sink, as `object-calendar`'s does), - * `limit` (`:442`, the fetch's one top-level `$top`, through + * `limit` (`:477`, the fetch's one top-level `$top`, through * `resolveRowLimit(schema.limit, DEFAULT_TIMELINE_LIMIT)` with the default - * `100` at `:29` and the refused-cap diagnostic at `:290`), `items` (`:254`, `:374`, `:546`, - * `:771` — the authored pass-through that short-circuits the object query), - * `data` (`:255`, `:374`, `:501`, `:503` — the pre-fetched record source, + * `100` at `:29` and the refused-cap diagnostic at `:325`), `items` (`:289`, `:409`, `:581`, + * `:811` — the authored pass-through that short-circuits the object query), + * `data` (`:290`, `:409`, `:536`, `:538` — the pre-fetched record source, * read off REACT PROPS rather than `schema`; the door's own docblock carries * how an authored key reaches that channel and why a `schema.*` sweep alone * publishes a false refusal), - * `descriptionField` (`:537`), `mapping` (`:510`, `:535`, `:537`, `:538`), - * `variant` (`:809`) and `navigation` (`:753-756` → `:888` → `:943-944`). + * `descriptionField` (`:572`), `mapping` (`:545`, `:570`, `:572`, `:573`), + * `variant` (`:849`) and `navigation` (`:793-796` → `:928` → `:983-984`). * Four more are read by the presentational renderer off the schema this - * component spreads into it (`effectiveSchema`, `:872-891`): `dateFormat` + * component spreads into it (`effectiveSchema`, `:912-931`): `dateFormat` * (`plugin-timeline/src/renderer.tsx:1569`, every variant — its number was the * same at `53ded82bf` and `87af769e9`, as `plugin-timeline/src/index.tsx:333`'s * also was, which is why the number alone is never the reading) and the gantt @@ -5610,8 +5732,8 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * ({@link OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE}) — the runtime handoff * `ListView` emits, not a second authoring spelling; * - `bind` — the objectui data-scope key the section header above rules out - * for every block in this family (`:294` here); - * - `className` (`:875`) — a node-level key on `PageComponentSchema`, not a + * for every block in this family (`:329` here); + * - `className` (`:915`) — a node-level key on `PageComponentSchema`, not a * per-block prop; * - `onItemClick` / `onRowClick` — host callbacks JSON cannot carry. * @@ -5625,10 +5747,10 @@ const OBJECT_TIMELINE_FLAT_CONFIG_GUIDANCE: readonly KeySetGuidance[] = [ * reference, for the reason the ruling gives. * * ⚠️ `variant: 'gantt'` is declared because the registration declares it - * (`plugin-timeline/src/index.tsx:374-375`) and the renderer reads it — but the + * (`plugin-timeline/src/index.tsx:393-394`) and the renderer reads it — but the * OBJECT-BOUND composed path REFUSES it loudly (objectui#6655, - * `ObjectTimeline.tsx:809-811`: - * `:809` gates on `!hasAuthoredItems && schema.variant === 'gantt'` and `:811` + * `ObjectTimeline.tsx:849-851`: + * `:849` gates on `!hasAuthoredItems && schema.variant === 'gantt'` and `:851` * renders `timeline-unsupported-variant`. ⚠️ The `:518` this clause carried is * that gate at the RETIRED `53ded82bf` and a comment line at this pin. It sat on * a line the 2026-09-22 conversion never touched — and so did eight other @@ -5680,7 +5802,17 @@ 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` = - * `e420df310`, re-READ there 2026-09-30 — the file is byte-identical to + * `31971ff1e`, re-READ there 2026-10-01 — the file changed across the hop + * from `e420df310` (+42/-2, objectui#8654's typed `navigation` member and + * cast-free navigation read), all of it above or away from these reads, so + * every anchor MOVED by 35 with its cited read byte-identical: `:255` -> + * `:290`, `:374` -> `:409`, `:479` -> `:514`, `:501` -> `:536`, `:503` -> + * `:538`, `:653` -> `:688`. One was re-pointed rather than moved: the + * start/end bindings this record carried as `621-622` began one line early, + * on the `rawData.map` head, at `e420df310` too, where the two bindings are + * `622-623`; they are `657-658` here. `SchemaRenderer.tsx` and + * `record-source.ts` are byte-identical, so the prop channel above still + * carries the key. At `e420df310`, 2026-09-30, the file was byte-identical to * `db11afd49`, so every anchor held unmoved; and the prop channel above * still carries the key here: `SchemaRenderer` strips an authored `data` * prop only on the types `core`'s `RECORD_SOURCE_DATA_ARM_BY_TYPE` puts on @@ -5699,13 +5831,13 @@ export const ObjectTimelinePropsSchema = lazySchema(() => strictObject({ * `recordDisplayValueAt` (`:653`, objectui#10530), not `item[titleField]`. * At `f8a9d0fb0` the file was byte-identical to `62597c588` and * `87af769e9`, where they were re-READ 2026-09-22), all in - * `ObjectTimeline.tsx`: `:255` seeds the loading state off it, `:374` + * `ObjectTimeline.tsx`: `:290` seeds the loading state off it, `:409` * SKIPS the object query when it is present (the gate the effect reads at - * `:479`), `:501` tracks it, and `:503` + * `:514`), `:536` tracks it, and `:538` * is the row source itself — `(props as any).data || boundData || * fetchedData`, so it wins over both the data-scope binding and the fetch. * Unlike `items` one line down, these rows are RECORDS: they go through the - * same `timeline` field bindings a fetched row takes (`:621-622`, `:653`). + * same `timeline` field bindings a fetched row takes (`:657-658`, `:688`). */ data: z.array(z.unknown()).optional() .describe("Pre-fetched records — read FIRST as the rail's row source, ahead of the data-scope binding and the fetch, and composed into entries through the same `timeline` field bindings a fetched row takes; authoring it suppresses the object query entirely. Distinct from `items`, which is the already-composed entry shape and wins over this key when both are written"), @@ -5898,20 +6030,27 @@ export const ComponentPropsMap = { // set from this map's keys), on the three-part evidence that vocabulary's // string-arm ledger asks of a type admitted without an enum member — all // measured at the pin this repo builds against (`.objectui-sha` = - // `e420df310`; re-measured there 2026-09-30: `data-list.tsx`, + // `31971ff1e`; re-measured there 2026-10-01: `public-blocks.ts` and + // `block-types.ts` byte-identical to `e420df310`; `data-list.tsx` changed + // (+48/-6, objectui#11168 slice 2: both registrations publish their + // spec-declared inputs), the `definition-list` registration not moving and + // the `repeater` one MOVING `205` -> `233`, each still in the `element` + // namespace; `block-config.ts` changed (+17/-4) above the two inspectors, + // which MOVED `283-317` -> `296-330` byte-identical. At `e420df310`, + // 2026-09-30: `data-list.tsx`, // `public-blocks.ts` and `block-types.ts` byte-identical to `db11afd49`, and // `block-config.ts` changed (+12/-3) outside the two inspectors — two // line-neutral one-line edits above them and an insertion below — so every // anchor below held unmoved; at `db11afd49`, 2026-09-29, all four files were // byte-identical to `dd3f7e1be`): // - registration: `@object-ui/components` registers both in the `element` - // namespace (`components/src/renderers/basic/data-list.tsx:75`, `:205`); + // namespace (`components/src/renderers/basic/data-list.tsx:75`, `:233`); // - publication: both are `PUBLIC_BLOCKS` members // (`core/src/registry/public-blocks.ts:117-118`), which is how the // tracked `sdui.manifest.json` carries both; // - authorship: the Studio page designer's palette offers both // (`app-shell/src/views/metadata-admin/previews/block-types.ts:136-137`), - // each with its own inspector (`previews/block-config.ts:283-317`), so + // each with its own inspector (`previews/block-config.ts:296-330`), so // stored pages hold them. 'element:definition-list': ElementDefinitionListPropsSchema, 'element:repeater': ElementRepeaterPropsSchema, @@ -5945,7 +6084,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` = - // `e420df310`), all three re-measured there 2026-09-30 (all three renderers + // `31971ff1e`), all three re-measured there 2026-10-01 (`ObjectMap.tsx`, + // `ObjectGantt.tsx` and `ObjectTree.tsx` are byte-identical to `e420df310`, + // so no declared key set moved and no read point died. At `e420df310`, + // 2026-09-30, all three renderers // changed and were re-READ: `ObjectMap.tsx` in one docblock, `ObjectGantt.tsx` // in objectui#11141's inclusive date-only end, and `ObjectTree.tsx`, whose // rung-1 call now passes the `view-data` arm this row declares and whose @@ -5979,7 +6121,13 @@ 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` = - // `e420df310`), re-measured there 2026-09-30 (`ObjectTimeline.tsx` + // `31971ff1e`), re-measured there 2026-10-01 (`renderer.tsx` byte-identical + // to `e420df310`; `ObjectTimeline.tsx` and `index.tsx` changed for + // objectui#8654 — a typed `navigation` member read without its cast, and a + // `navigation` input on both registrations — both re-READ, the `schema.*` + // read set unchanged, since `navigation` was already read and is declared + // on this row, and every anchor moved with its text byte-identical but the + // one navigation read. At `e420df310`, 2026-09-30: `ObjectTimeline.tsx` // byte-identical to `db11afd49`; `index.tsx` gained `filter` / `sort` inputs, // objectui#8220, and `renderer.tsx` changed, objectui#11141, both re-READ, // the `schema.*` read set unchanged and every anchor moved with its text diff --git a/packages/spec/src/ui/dataset.zod.ts b/packages/spec/src/ui/dataset.zod.ts index b00f4cc3254..b9c62c41d69 100644 --- a/packages/spec/src/ui/dataset.zod.ts +++ b/packages/spec/src/ui/dataset.zod.ts @@ -214,8 +214,10 @@ 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` = - * `e420df310`; re-derived at that pin 2026-09-30 — `dataset-format.ts` is - * byte-identical to `db11afd49`, so `formatMeasureDate` `:229-263`, its call + * `31971ff1e`; re-derived at that pin 2026-10-01 — `dataset-format.ts` and + * `date-display.ts` are byte-identical to `e420df310`, so every anchor below + * held unmoved and was re-READ in place. At `e420df310` (2026-09-30) + * `dataset-format.ts` was byte-identical to `db11afd49`, so `formatMeasureDate` `:229-263`, its call * at `:369` and its datetime arm `:259`-`:261` did not move and were re-READ * in place, and `date-display.ts` changed (+68/-1, objectui `858eafb4f`, * objectui#11141: a header comment naming the new `toDisplayEndDate` / diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 60cb550dd31..7c6a6d6a061 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -2129,6 +2129,15 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * two `getMapConfig` lines `409` -> `408` and `414` -> `413`, byte-identical: * objectui `846cec0ef` (objectui#8348) rewrote the record-source docblock above * `getMapConfig` one line shorter and left the function untouched. The + * `ListView.tsx` anchors did not move. RE-READ again at pin `31971ff1e` on + * 2026-10-01: that bump redded two anchors, both in `objectql.zod.ts`, which + * grew above them (objectui#10859 batches 4 to 6 and objectui#11276 moved + * `object-form`, `object-map`, `object-gantt` and `object-chart` onto the + * `properties` bag, among others), and neither changed what it reads — + * `ObjectMapConfigSchema` `2056` -> `2123`, its ten-line declaration + * byte-identical and still closed with `.strict()`, and + * `LIST_VIEW_LOCAL_OVERRIDES` `1083` -> `1138`, the whole list byte-identical + * and still without `map`; `ObjectMap.tsx`, `ObjectView.tsx` and the * `ListView.tsx` anchors did not move. Each anchor quotes the line it was read at, * so the next pin bump reds instead of rotting * (`check:objectui-pin-citations`): @@ -2148,7 +2157,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * there, but by a whitelist and in SILENCE: no parse, no warning, no * diagnostic of any kind. * - **The renderer's own zod schema does not close the set.** - * `packages/types/src/zod/objectql.zod.ts:2056` first line + * `packages/types/src/zod/objectql.zod.ts:2123` first line * `export const ObjectMapConfigSchema = z.object({` — a plain `z.object` at `dd3f7e1be`, * NOT strict, so an undeclared key parses clean there: zero issues, no * warning. ⚠️ At `db11afd49` the declaration is closed with `.strict()` @@ -2171,7 +2180,7 @@ export const TreeConfigSchema = lazySchema(() => strictObject({ * `db11afd49` it is closed by `.strict()` and still warn-only). And this parse is * the only * place an author is told ANYWHERE: `map` is not in objectui's - * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:1083` + * `LIST_VIEW_LOCAL_OVERRIDES` (`packages/types/src/zod/objectql.zod.ts:1138` * first line `const LIST_VIEW_LOCAL_OVERRIDES = [`), so objectui's own * `ListViewSchema` imports THIS block by reference and the document check on * that side is this same schema. The two key sets MIRROR each other, key for @@ -3349,7 +3358,15 @@ const FormFieldBaseSchema = lazySchema(() => { * inside the `53ded82bf7...87af769e9` range, so the widest-tier-only * under-span this block used to record (#17328: one cell of two at * 720px) no longer reproduces at the pin this repo builds against - * (`.objectui-sha` = `e420df310`, re-read 2026-09-30: `autoLayout.ts` and + * (`.objectui-sha` = `31971ff1e`, re-read 2026-10-01: `autoLayout.ts` and + * `fields`' `field-type-alias.ts` are byte-identical to `e420df310`, so + * `resolveColSpan` `:154`, `WIDE_FIELD_TYPES` `:58-69` and the `repeater` -> + * `field:grid` mapping held unmoved, and `form.tsx` changed on this hop — 59 + * insertions, 112 deletions: objectui#11070 round 5, the text family reading + * the spec's `minLength` / `maxLength` alone — none of it inside + * `spanLadderFor`, still `:204-231` byte-identical, and its one call site + * moved `:2985` -> `:2986` with the call byte-identical, so it still emits the + * ladder. At `e420df310`, re-read 2026-09-30: `autoLayout.ts` and * `fields`' `field-type-alias.ts` are byte-identical to `db11afd49`, so * `resolveColSpan` `:154`, `WIDE_FIELD_TYPES` `:58-69` and the `repeater` -> * `field:grid` mapping held unmoved, and `form.tsx` changed on this hop — 142 @@ -3375,7 +3392,7 @@ const FormFieldBaseSchema = lazySchema(() => { * had changed only in its registration's input list, objectui#9910's * `children` slot; at `62597c588` it was byte-identical to `87af769e9`). */ - span: z.enum(['auto', 'full']).default('auto').describe("Relative field width. 'auto' (default — omit it): the renderer sizes the field from its widget type × the current column count — at the pin this repo builds against (`.objectui-sha` = `e420df310f5b`), only textarea, markdown, html, richtext and repeater resolve to the full column count (repeater reaches it through the wide `field:grid` widget it maps to). 'full': resolves to the form grid's full column count. How far down the container-query tiers that span is emitted is the renderer's, not this key's: at that same pin the renderer emits one clamped col-span class per multi-column tier (`@md:col-span-2 @2xl:col-span-3` for a 3-column grid), so the field takes the whole row at every multi-column tier, not just the widest."), + 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` = `31971ff1e28f`), 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)'), diff --git a/scripts/build-console.sh b/scripts/build-console.sh index 50a43619fd7..9fa1ff98860 100755 --- a/scripts/build-console.sh +++ b/scripts/build-console.sh @@ -19,6 +19,10 @@ # # Usage: # scripts/build-console.sh +# scripts/build-console.sh --check-single-zod +# run ONLY the single-zod canary (below) over an +# existing dist's assets/ and exit with its verdict; +# builds nothing, writes nothing # # Env: # OBJECTUI_ROOT override path to objectui checkout @@ -29,6 +33,102 @@ set -euo pipefail +# ── Single-zod canary (objectstack#21108) ──────────────────────────── +# The console bundle must carry exactly ONE zod instance. Two copies split +# zod's internals: a spec schema built by one instance and walked by the other +# (`z.toJSONSchema`) throws, so every spec-derived Studio form lost its fields — +# the New Package dialog could not create a package and the dashboard/report +# inspectors read "Spec schema unavailable". The pin `db11afd4967c` shipped that +# split: objectui's zod 4.4.3 in `vendor-zod` beside the injected spec's 4.6.1 +# in `vendor-objectstack`, each carrying its own version literal. +# +# objectui#11353 (objectui#11327) fixed it at the source: inside the console's +# Vite build it pins every `zod` import to the console's copy, asserts the +# console's zod satisfies the injected spec's declared range, and fails the +# build on more than one zod package directory in the emitted chunks. This is +# the belt to that brace, measured on what this script SHIPS rather than on how +# the pinned objectui build is configured, so it still holds for a future pin +# that drops or weakens that guard. +# +# What it counts: zod 4 stamps every instance with a version object literal +# (`version` in `zod/v4/core/versions`, read back as `_zod.version`), which the +# minifier emits as `{major:4,minor:6,patch:5}`. One instance = one literal, so +# the canary requires EXACTLY ONE occurrence across assets/*.js: +# - two DIFFERENT literals: two zod versions, the shipped defect; +# - one literal found twice: two instances of the same version — still two +# instances, which is what the crash keys on; +# - none at all: the matcher has gone blind (the console always bundles zod), +# and a negative check that cannot see its subject would pass forever. +ZOD_VERSION_LITERAL='\{[[:space:]]*major:[[:space:]]*[0-9]+,[[:space:]]*minor:[[:space:]]*[0-9]+,[[:space:]]*patch:[[:space:]]*[0-9]+[[:space:]]*,?[[:space:]]*\}' + +assert_single_zod() { + local assets="$1" + if [[ ! -d "$assets" ]]; then + echo "✗ Single-zod canary: ${assets} is not a directory." + return 1 + fi + local files=() + local f + for f in "$assets"/*.js; do + [[ -f "$f" ]] && files+=("$f") + done + if (( ${#files[@]} == 0 )); then + echo "✗ Single-zod canary: no .js files under ${assets} — nothing was measured." + return 1 + fi + local hits="" rc=0 + hits="$(grep -oHE "$ZOD_VERSION_LITERAL" "${files[@]}")" || rc=$? + if (( rc > 1 )); then + echo "✗ Single-zod canary: grep failed (exit ${rc}) reading ${assets}/*.js." + return 1 + fi + # One line per occurrence: :, the literal's whitespace and any + # trailing comma dropped so a re-formatted bundle reads the same version. + local occurrences + occurrences="$(printf '%s\n' "$hits" | sed -e '/^$/d' -e 's/[[:space:]]//g' -e 's/,}$/}/')" + local count=0 distinct=0 + if [[ -n "$occurrences" ]]; then + count="$(printf '%s\n' "$occurrences" | wc -l | tr -d ' ')" + distinct="$(printf '%s\n' "$occurrences" | sed 's/^.*:{/{/' | sort -u | wc -l | tr -d ' ')" + fi + if (( count == 0 )); then + echo "✗ Single-zod canary: none of the ${#files[@]} assets/*.js files carries a zod version" + echo " literal ({major:N,minor:N,patch:N}). The console always bundles zod, so zero means" + echo " this matcher can no longer see it — and a check that cannot see its subject cannot" + echo " fail. Fix the matcher (ZOD_VERSION_LITERAL in scripts/build-console.sh) before" + echo " trusting this bundle." + return 1 + fi + if (( count > 1 )); then + if (( distinct > 1 )); then + echo "✗ Single-zod canary: the console bundle carries ${distinct} different zod versions" + echo " (${count} version literals) and must carry exactly one:" + else + echo "✗ Single-zod canary: the console bundle carries ${count} copies of ONE zod version" + echo " literal — two instances of the same version are still two instances:" + fi + printf '%s\n' "$occurrences" | sed "s|^${assets%/}/| |" + echo " A schema built by one zod instance and walked by another breaks every spec-derived" + echo " Studio form. If OBJECTSTACK_SPEC_DIST was injected, the pinned objectui build did not" + echo " pin the spec's zod imports to the console's copy; otherwise objectui's own lockfile" + echo " resolves zod twice. Move .objectui-sha to an objectui commit whose console build" + echo " holds zod to one copy." + return 1 + fi + echo "✓ Single-zod canary: exactly one zod version literal $(printf '%s' "$occurrences" | sed 's/^.*:{/{/') in ${occurrences%%:*}." + echo " (${#files[@]} assets/*.js files read)" + return 0 +} + +if [[ "${1:-}" == "--check-single-zod" ]]; then + if [[ -z "${2:-}" ]]; then + echo "usage: scripts/build-console.sh --check-single-zod " + exit 2 + fi + if assert_single_zod "$2"; then exit 0; fi + exit 1 +fi + FRAMEWORK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SHA_FILE="${FRAMEWORK_ROOT}/.objectui-sha" @@ -259,6 +359,14 @@ if ! grep -rq "$BUNDLE_CANARY" "${TARGET}/assets"; then fi echo "✓ Bundle canary '${BUNDLE_CANARY}' present — framework client is in the bundle." +# Assert ONE zod instance in what ships (see the single-zod canary at the top). +# Placed after the bundle canary and, like it, after the SHA stamp: CI's split +# restore/save saves only when every step passed, so a failure here never +# seeds the dist cache. +if ! assert_single_zod "${TARGET}/assets"; then + exit 1 +fi + # Assert the injected SPEC landed too. Deliberately NOT a frozen literal like # BUNDLE_CANARY above: "does the bundle carry the surface the framework declares # now" is a moving target, and any string pinned here would be carried by the diff --git a/scripts/sdui-manifest.record.json b/scripts/sdui-manifest.record.json index d9a3ec8487f..f07e54d2e63 100644 --- a/scripts/sdui-manifest.record.json +++ b/scripts/sdui-manifest.record.json @@ -11,12 +11,12 @@ "a pin bump without regeneration goes RED there — that is the anti-rot half of the freshness gate.", "Regenerate + re-record: pnpm objectui:build && node scripts/gen-sdui-manifest-node.mjs" ], - "objectuiSha": "e420df310f5ba9526fd19d5fe65c2a52d6ac6d8c", + "objectuiSha": "31971ff1e28f89cfc45f0c19bc5b05e443f28b79", "source": "built-tree", - "modulesRoot": ".cache/objectui-e420df310f5b/apps/console", + "modulesRoot": ".cache/objectui-31971ff1e28f/apps/console", "objectuiWorkspaceVersion": "17.6.0", "generator": "scripts/gen-sdui-manifest-node.mjs", - "generatedAt": "2026-09-30", - "sha256": "f3473ef8c5978880b7008e6d234fbb06e5a7f67f84f5dd589f305e3ce4fbd6d1", + "generatedAt": "2026-10-01", + "sha256": "6a61f1742154aa1e67845f1a42154e0af890cdf811c95439f72e99aeb61bc7f5", "components": 107 } diff --git a/sdui.manifest.json b/sdui.manifest.json index 787d719044e..8ed65315ae9 100644 --- a/sdui.manifest.json +++ b/sdui.manifest.json @@ -293,10 +293,9 @@ "type": "enum", "enum": [ "vertical", - "horizontal", - "inline", - "grid" - ] + "horizontal" + ], + "description": "Label placement: `vertical` puts each label above its field (the default), `horizontal` beside it." }, { "name": "columns", @@ -692,6 +691,11 @@ "type": "array", "description": "Per-card style rules, each evaluated against that card’s own record. Two member dialects are accepted: the native `{ field, operator, value, backgroundColor?, borderColor? }` and the spec CEL `{ condition, backgroundColor?, borderColor? }`. A matching rule colours that card alone. A rule comparing a relation field sees the stored foreign key rather than the expanded record." }, + { + "name": "navigation", + "type": "object", + "description": "What a card click opens — the `{ mode, size, openNewTab, preventNavigation }` block a list view declares. With the key ABSENT a click opens the record in a drawer. `mode` is an overlay (`drawer`, `modal`, `split`, `popover`), `new_window`, `page` or `none`; write it whenever the block is written, because a block without `mode` takes the spec’s `page` default and `page` opens nothing on a board no parent view navigates for. `openNewTab: true` opens the record page in a new tab and outranks the mode, `preventNavigation: true` opens nothing, and `size` sets the overlay width. A click handler from a parent view outranks the whole key." + }, { "name": "dataSource", "type": "object", @@ -707,7 +711,7 @@ { "name": "objectName", "type": "string", - "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-calendar` schema refuses a block that declares none of them." + "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-calendar` schema refuses a block that declares none of them, unless the node's `dataSource.object` names the object, which then lands on this key." }, { "name": "calendar", @@ -754,6 +758,11 @@ "type": "boolean", "description": "External loading state, honoured ONLY alongside an array `data`: it then replaces the calendar with its loading placeholder. On a calendar fed by `staticData` or by `objectName` it is dropped, because that calendar owns its own loading state." }, + { + "name": "navigation", + "type": "object", + "description": "What an event click opens — the `{ mode, size, openNewTab, preventNavigation }` block a list view declares. With the key ABSENT a click opens the record in a drawer. `mode` is an overlay (`drawer`, `modal`, `split`, `popover`), `new_window`, `page` or `none`; write it whenever the block is written, because a block without `mode` takes the spec’s `page` default and `page` opens nothing on a calendar no parent view navigates for. `openNewTab: true` opens the record page in a new tab and outranks the mode, `preventNavigation: true` opens nothing, and `size` sets the overlay width. An overlay mode wins over a parent view’s click handler; any other mode hands the click to it." + }, { "name": "dataSource", "type": "object", @@ -769,7 +778,7 @@ { "name": "objectName", "type": "string", - "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-gantt` schema refuses a block that declares none of them." + "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-gantt` schema refuses a block that declares none of them, unless the node's `dataSource.object` names the object, which then lands on this key." }, { "name": "gantt", @@ -832,6 +841,11 @@ "type": "array", "description": "Entry order in `[{ field, order }]` form, ordering the records the timeline fetches. Lowered to `$orderby` on the same query." }, + { + "name": "navigation", + "type": "object", + "description": "What an entry click opens — the `{ mode, size, openNewTab, preventNavigation }` block a list view declares. Write it with `mode`: with the key ABSENT, or with a block that omits `mode` (it takes the spec’s `page` default), a click opens nothing on a timeline no parent view navigates for. `drawer`, `modal` and `popover` open the entry’s record in that overlay; `new_window` opens the record page in a new tab; `page`, `none` and `split` open nothing here, because the timeline hands the split shell no main panel. `openNewTab: true` opens the record page in a new tab and outranks the mode, `preventNavigation: true` opens nothing, and `size` sets the overlay width. A click handler from a parent view outranks the whole key." + }, { "name": "dataSource", "type": "object", @@ -847,7 +861,7 @@ { "name": "objectName", "type": "string", - "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-map` schema refuses a block that declares none of them." + "description": "ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`, or the node’s `dataSource` binding; the `object-map` schema refuses a block that declares none of them." }, { "name": "map", @@ -1576,7 +1590,7 @@ "name": "requiredPermissions", "type": "array", "of": "string", - "description": "Hide the whole bar unless the user holds these permissions" + "description": "[ADR-0066] Capabilities the user must ALL hold — names that permission sets grant through `systemPermissions`, not object actions: `read` or `update` here is an ordinary capability name, not the object's read or edit permission. When the client has resolved the user's capabilities and any of these is missing, this block does not render its content; wherever it would otherwise render, an insufficient-permissions notice takes its place. Presentation only: it authorises nothing, and the data API still serves the same data to the same user. A client that cannot resolve the user's capabilities (no permission provider, or one that does not report `systemPermissions`) renders this block as if they were held — it fails open." }, { "name": "location", @@ -1831,11 +1845,19 @@ "name": "variant", "type": "enum", "enum": [ - "heading", - "subheading", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", "body", - "caption" - ] + "caption", + "overline", + "heading", + "subheading" + ], + "description": "One of the nine text styles ui:text uses: h1-h6 (rendered as that heading level), body, caption or overline. heading and subheading are earlier spellings the contract still accepts and renders as h2 and h3; write h2 or h3 instead." }, { "name": "align", @@ -1968,16 +1990,23 @@ { "name": "items", "type": "array", - "required": true, - "description": "Term/description pairs [{ term, description }]" + "of": "object", + "description": "Term/description pairs [{ term, description }], in order: `term` is the row label, `description` is shown as-is (an object as JSON, an omitted one as an em dash). Omitted or empty renders the \"No details\" state" }, { "name": "columns", "type": "enum", "enum": [ - "1", - "2" - ] + { + "label": "1", + "value": 1 + }, + { + "label": "2", + "value": 2 + } + ], + "description": "Grid columns from the small breakpoint up: the NUMBER 1 or 2 (default 1)" }, { "name": "inline", @@ -2003,15 +2032,19 @@ { "name": "fields", "type": "array", - "description": "Columns per row — bare names or { field, label? }" + "description": "Fields shown after the title on each line, in order: a bare field name, or `{ field }`" }, { "name": "filter", - "type": "array" + "type": "array", + "of": "object", + "description": "Filter rules `[{ field, operator, value }]`. A string `value` may be a context token such as `{current_user_id}`, resolved before the query" }, { "name": "sort", - "type": "array" + "type": "array", + "of": "object", + "description": "Sort order `[{ field, order }]`, applied in list order" }, { "name": "limit", @@ -2075,10 +2108,13 @@ "name": "size", "type": "enum", "enum": [ + "default", "sm", "md", - "lg" - ] + "lg", + "icon" + ], + "description": "Button size: the Button primitive's `default`, `sm`, `lg` or `icon`, plus `md`, which renders as `default` (default: `default`)" }, { "name": "className",