Skip to content

feat(components,plugin-grid,types): object-grid honours keyboardNavigation — one roving Tab stop with arrow-key cell navigation, on by default when the grid renders editable; the row-1 booking is struck (objectui#11068) - #11554

Merged
objectstack-fleet[bot] merged 40 commits into
mainfrom
claude/issue-11068-keyboard-navigation-17-6
Oct 3, 2026

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11068

Clause-②: yes (widening)

What this PR is

This is the keyboardNavigation BUILD (triage 5896596687, option A). object-grid now honours the key its @objectstack/spec 17.6.0 row declares.

  • @object-ui/components' data-table gains a host-set keyboardNavigation flag, OFF by default for every other host. With it on:
    • the table is a WAI-ARIA grid;
    • the data cells are ONE roving Tab stop, clamped to the page;
    • ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, Ctrl+Home and Ctrl+End move focus;
    • focus returns to the cell after an edit ends with Enter or Escape.
  • ObjectGrid relays schema.keyboardNavigation ?? inlineEditable, the declared default read against the editable the grid renders (ObjectGrid's editable schema key offers inline editing with no can(object,'update') gate — the SDUI-authored grid path #4647 did not reach #5143's one verdict). An editable grid has it on. A read-only grid keeps today's Tab behaviour unless true is authored, and false turns it off.
  • GRID_QUERY_INPUTS publishes the key WITH its reader, so the Studio input, the manifest and the parser know it.
  • @object-ui/types: DataTableSchema declares the host-set flag (runtime-only, beside editable). The ObjectGridSchema docblock describes the behaviour. The object-view table slot still withholds the key, now for the reason "not relayed".
  • Docs: plugin-grid.mdx, its README and data-table.mdx.

BREAKING (marked minor, per objectui's rule): every grid that renders editable changes its Tab sequence without a document edit. That includes list views with inline edit. In-cell widgets (record links, the row action menu, the selection checkboxes) keep their own Tab stops (Q2 = A).

The row-1 booking is struck

objectui#11531 landed with row 1 booked to this card. This PR strikes it in registry-inputs-spec-parity.test.ts:

  • the entry is removed;
  • unpublishedKeys goes 12 → 11;
  • the owner count goes to 0;
  • BOOKED_GA_KEYS is emptied.

The two parity rows go green by publication, with no exemption.

Reviews

  • The seat's ACCEPT with rulings 5969066187: Q1 = B (land on main after objectui#11531), Q2 = A, Q3 = A.
  • The contract review PASS 5969185521 on cc755a8d2.
  • This head merges main and adds one dated correction to .changeset/11227-object-grid-17-6-keys.md, as the PASS's landing note asks. Against the reviewed head, it moves only the trunk's own files (already on main) and that paragraph.

Tests (dev reports 5969030441 and 5969896459)

  • the parity union: 371 tests green;
  • packages/plugin-grid and packages/types: 529 files and 10,997 tests green;
  • type-check passes for types, components and plugin-grid;
  • the four ablations each went red, and each restore was proven by blob hash;
  • the changeset gates and citations (0 new) pass. NOT GOVERNED.

Changeset

.changeset/11068-keyboard-navigation.md: components, plugin-grid and types, all minor. There are dated corrections on the 10976, 11068-grid-declared-keys, 11068-row1-booking-17-6 and 11227-object-grid-17-6-keys pending changesets.

The spec's [EXPERIMENTAL — not enforced] marker on this key is dropped by objectstack#21582 once this lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn

claude added 30 commits October 2, 2026 22:23
…i#11438)

Regenerated with pnpm 10.31.0 only, by the raise-and-restore route the
17.5.0 bump used: every @objectstack/* specifier raised to ^17.6.0 for one
`pnpm install`, every manifest restored byte-for-byte to HEAD, then a second
`pnpm install` reconciles the importers to their declared ranges.

Resolution census against the base lock: the seven @objectstack/* packages
(spec, types, core, client, formula, lint, sdui-parser) move 17.5.0 -> 17.6.0
and nothing else moves. No importer specifier changes. The registry's new
`deprecated` field on twelve @yuku-analyzer/binding-* 0.8.7 entries is
written into the lock by the same tooling.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…, a component under a name @objectstack/spec 17.6.0 now exports (objectui#11438)

@objectstack/spec 17.6.0 exports `EmptyState` from `@objectstack/spec/ui`:
the authoring type of a list view's empty-state config (title, message,
icon). @object-ui/components publishes `EmptyState` as an alias of the
`DataEmptyState` React component. A component is not a config shape, so
neither import nor derivation fits; the alias is a published name, so its
retirement or rename belongs to its own card. Same disposition objectui#11073
gave the four `Object*Props` collisions 17.5.0 surfaced.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…ing now that @objectstack/spec 17.6.0 carries the arm (objectui#11438)

objectui#11206 widened `STRIPPED_LINE`'s key type by `| 'computed'` so it
compiled against both a three-arm pin and objectstack `main`, and left a
tripwire in `writeWarningToast.test.ts` for the bump that carries the arm.
17.6.0's `DroppedFieldsEventSchema.reason` lists `computed`, so this does what
the tripwire says: the key type is the spec union again, the `computed` case
is spelled as the literal, the spent tripwire is deleted, and
spec-symbol-batch6's `_ReasonIsTheEnum` pins the exact four-member union.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…spec/migrations, where 17.6.0 moved it (objectui#11438)

17.6.0 carries the spec's migrations-entry-split: `MIGRATIONS_BY_MAJOR` is
exported from `@objectstack/spec/migrations` and no longer from the root. The
test read the root dynamically so it compiled against both the old pin and
objectstack `main`, with no fallback, and its docblock asked the bump that
moves the pin past the split to replace that with a static import from the
subpath. This does that.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…jectstack/spec 17.6.0 refuses by name (objectui#11438)

17.6.0 carries objectstack#20758: `PageHeaderProps.breadcrumb` is a tombstone
refused by name. objectui's renderer has not read it since objectui#11166,
and its `inputs` entry said it stayed only while the spec accepted the key and
would leave with the retirement. It leaves now, so the registry no longer
publishes an input the contract refuses
(`registry-inputs-spec-parity.test.ts`, "page:header declares no top-level
input the spec does not accept").

The slotted-pages guide example drops `breadcrumb: true`, which the contract
now refuses (its pin parses the example against the spec row), and the
page-header doc and the renderer comment say the key is refused rather than
"accepted until retired".

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…7.6.0 made stale (objectui#11438)

objectui#11111 decision 3 = B booked `action:button.endpoint` and
`action:icon.endpoint` as spec keys held unpublished, owed to objectui#11168.
17.6.0 refuses `endpoint` on both blocks (objectstack b3917d90, the rename to
`target`), so neither entry names a key the spec declares, and the
dangling-exemption check reds on them. Both are struck and the ledger cap
falls from 3 to 1 (`undoable` stays, still owed to objectui#11168).

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
… group, which @objectstack/spec 17.6.0 adds (objectui#11438)

17.6.0's `GetTranslationsResponseSchema.translations` carries a `picklists`
group (objectstack addbbf02, the `picklist` metadata kind). The recogniser's
group list is written out, and the console's `loadLanguage.test.tsx` walks the
spec's list so a group the spec adds turns it red instead of a bundle carrying
only that group being returned untransformed and read by nothing. Added; the
transform already copies an unlisted group through onto `app`.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…t its key, as @objectstack/spec 17.6.0 raises it (objectui#11438)

Through 17.5.0 the spec raised the reserved-name refusal on the `fields` map
(`custom @ fields`). objectstack#20997 names the key, and 17.6.0 carries it
(`custom @ fields.constructor`). The pin said only this reading moves when
that lands; it moves, in the instrument row and in the banner row, which now
names `fields.constructor`.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…@objectstack/spec 17.6.0 leaves true, and ledger the four that name a past release (objectui#11438)

`check:installed-pin-claims` reads every present-tense claim about the
installed `@objectstack/*` version against the resolved artifact. 38 sites
named 17.5.0. Each was decided, never find-and-replaced:

- RESTAMPED after re-measuring the fact the sentence states against the
  installed 17.6.0 (each probe was also run against a 17.5.0 tarball as the
  control, and answered the same on both): `FieldSchema`'s `dependsOn`, `rows`
  and option `description` readings; `object-calendar.data` and `.calendar`;
  `record:details.columns`; `record:highlights` `fields[]` and `layout`;
  `SelectOptionSchema` labels; the `@objectstack/lint` field-rule verdict text;
  `referenceTo` and its four alias spellings; `CalendarConfigSchema`'s required
  key, options and refusal hints; `record:alert.visible`; `object-kanban`'s
  fifteen keys; the screen node's `waitForInput`; `object-calendar`'s ten flat
  members; `ObjectListViewSchema`'s members; the `view.list.navigation.view`
  tombstone in the gantt README pin; and the sites whose own tests measure the
  fact and pass on 17.6.0.
- REWORDED where the fact moved: the retired-key type is the branded
  `[REMOVED]` mark at the pinned 17.6.0 (bare `undefined` through 17.5.0), on
  `retired-key-type.ts` and the five pins that cite its spellings.
- LEDGERED as `historical` (four files, seven sites): lines whose version says
  WHEN something changed or names a release's record set, where `pinned` is an
  unrelated verb or the sentence sits under a dated update heading.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…ack/spec 17.6.0 moves, and pair the live-e2e backend with it (objectui#11438)

Each pin names what moved, measured against a 17.5.0 control:

- `ObjectSchema`'s accepted keys are 44, not 43: 17.6.0 adopts `imageField`
  (gained set exactly that one, lost set empty). Both corpus-guard counts are
  re-pointed as the 17.3.0 adoption was.
- The spec's exports map has 21 entries, not 20: `./migrations` is added (no
  `browser` arm), nothing removed. Every count and its prose follow.
- `check-lockfile-integrity`'s two lockfile samples are rebuilt on the 17.6.0
  snapshot keys their own tripwire named.
- The objectui#11111 owner total for objectui#11168 follows the cap to 1 (the
  two `endpoint` entries struck earlier on this branch).
- `e2e/live/ci/backend.env` pins `OBJECTSTACK_VERSION=17.6.0`, the version the
  lockfile resolves, as the pin requires (`@objectstack/cli@17.6.0` resolves
  to the 17.6.0 tag commit).
- The page-header doc drops the version literal it gained on this branch;
  `doc-version-claims` holds docs to pointers, not version numbers.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…k/spec 17.6.0, so it joins the covered arms (objectui#11438)

17.6.0 types `ComponentPropsMap['object-grid'].grouping` as the grouping
config instead of `z.unknown()`. The authored arm's `properties` is that row
by reference, so a padded grouping field name is now refused at its entry on
all four faces. The census's recorded reason for leaving the bag member
uncovered stopped holding; it moves to the declaring table and is pinned like
every other arm. The node-level `grouping` stays uncovered: it is the flat
spelling's by-name refusal.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…stack/spec 17.6.0's walk descends it (objectui#11438)

objectui#11223's block (e) held `page:card`'s `properties.footer` as the one
slot the spec's exported walk did not descend, and said to retire itself when
that changed. 17.6.0's walk descends `properties.footer[]`, objectui's faces
now judge a footer child, and block (d) asked for the position to be added to
(a). It is: the footer is a row of (a) on both faces, (e) is deleted, and the
CLI doc no longer names the footer as an unjudged slot.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
… @objectstack/spec 17.6.0's root entry shed (objectui#11438)

The second payback of objectui#11073's ruled raise, objectstack#20646, reaches
objectui with the 17.6.0 bump: the spec root no longer exports its migration
chain. Measured with two console builds in one lock hold (`CI=true pnpm exec
vite build` after the packages build): `main` at `2d576e46` (17.5.0) weighs
3,636,289 gzipped eager bytes, this branch at `2ba091c` weighs 3,386,193;
`vendor-objectstack` alone is -250,108, the other 329 chunks +12.

Left alone, the ceiling would sit 3.01x the regression above the payload and
the gate's own sensitivity check exits 2 (a blind gauge). So, as objectui#11101
did for the first payback: `MAX_EAGER_CLOSURE_GZIP_BYTES` 3,660,380 ->
3,410,284 and `vendor-objectstack` 1,689,265 -> 1,439,157, each lowered by
exactly what it recovered; BASELINE and PER_CHUNK_BASELINE move to the
`2ba091c` reading; headroom 0.26x and 0.20x, the bands `main` already had.
The test's rendered literal is re-taken (3306.8). The changeset says what the
console gains.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…#11536 under objectui#11438 ruling A′ (objectui#11438)

@objectstack/spec 17.6.0 gives `record:line_items` a ComponentPropsMap row
(objectstack 24c554da, #21142 / #21244), so registry-inputs-spec-parity
judges it from that pin on. objectui#11438 ruling A′ (record 5965062249)
applies objectui#11111 decision 3 = B to the bump: the population is
re-pinned and every difference is booked to the owner card filed for the
group, objectui#11536, with an expiry. Nothing is declared or pinned here.

- EXPECTED_COVERED gains `MINOR_17_6_BLOCKS` (`record:line_items`), armed
  when the installed spec carries it; the census goes 55 -> 56 carried and
  39 -> 40 judged.
- UNPUBLISHED_EXEMPTIONS books the ten keys `inputs` omit; MEMBER_PIN_EXEMPTIONS
  books `columns` and `dataSource`, and MEMBER_PIN_EXEMPTION_CEILING goes
  0 -> 2 with the booking.
- The objectui#11111 ledger gains objectui#11536 as a fourth owner, routed by
  block. Its entries cite ruling A′ and decision 3 = B and expire 2026-11-02
  (thirty days after the ruling, as the 17.5.0 entries' 2026-10-30 is after
  theirs). Caps: unpublishedKeys 1 -> 11, memberPins 0 -> 2, owner total 12.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
… beside a pin marker (objectui#11438)

`check:installed-pin-claims` read two of the lines the booking added as
present-tense claims about the installed spec: the `MINOR_17_6_BLOCKS`
guard's docblock ("Does the installed ... carry the 17.6.0 set?") and the
`memberPins` cap comment, where the version sat beside the objectui#11168
slice's "pinned". Both were true at 17.6.0 and would go stale at the next
bump. The guard's docblock now asks about the spec this tree resolves, and
the two cap comments name the ruling that booked the entries instead of the
version. No assertion moves.

Claude-Session: https://claude.ai/code/session_01HRYqpqGcWpJuJkDmbRF75w
Co-authored-by: Claude <noreply@anthropic.com>
…y on @objectstack/spec 17.6.0 (objectui#11094)

17.6.0 stops folding the empty pair onto is_null / is_not_null and lowers
it to $empty, which joins FILTER_OPERATORS (objectstack#20570,
objectstack#20446). The matcher had arms only for the null pair, so a
stored is_empty rule on a provider: 'value' source reached the refusal
arm and selected no row.

The AST arm gains is_empty / is_not_empty and the object arm gains
$empty, both through the spec's isEmptyFilterValue with no declared row:
this adapter holds no field declarations, so it judges by value (null,
absent, '' and [] are empty). A non-boolean $empty flag is refused.

ValueDataSource.astFilterVocabulary is rewritten to the new fold, the
dollar-vocabulary parity table gains $empty, and the conformance file's
staged-$empty partition moves into the executed table as its expiry row
instructed.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…it refused (objectui#11094)

$empty is a member of FILTER_OPERATORS since @objectstack/spec 17.6.0,
and ValueDataSource now executes it, but the $-dialect lowering had no
arm and threw "Unknown filter operator". The objectui#8976 shape, one
operator over: the README pin's FILTER_OPERATORS completeness check
reddened on it.

$empty: true lowers to [f, 'is_empty', true] and false to
[f, 'is_not_empty', true], the inverse of the spec's parseFilterAST,
written the way $null is. A non-boolean flag throws the INVALID_FILTER /
400 envelope, matching ValueDataSource's refusal. The unknown-operator
message lists $empty, and the data-objectstack README gains its row.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
… and declare the slice's changeset (objectui#11094)

filter-source-merge: the server now reads the valueless is_empty
2-tuple as $empty: true, not $null: true; the assertion's point (no
invented value slot) is unchanged.

FilterConditionField.operators: the expiry row flips to "FILTER_OPERATORS
admits $empty". The entry stays in KNOWN_UNREACHABLE with its reason
restated: which builder row authors $empty is objectui#10813's decision,
and a new expiry row reddens the day a builder operator emits it.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ription / emptyState rows — publish both, type emptyState text as I18nLabel, resolve maps at both renderers (objectui#11227)

@objectstack/spec 17.6.0 declares description, emptyState and
keyboardNavigation on its object-grid row (objectstack#20694). This slice
follows the first two:

- GRID_QUERY_INPUTS publishes description (string and object arms, an
  I18nLabel the grid already resolves) and emptyState.
- ObjectGridSchema.emptyState is the spec's EmptyState; the zod twin takes
  EmptyStateSchema by reference, not a second shape. NamedListView.emptyState
  indexes ListViewSchema's own member. title and message are I18nLabel.
- ObjectGrid and ListView resolve emptyState.title / .message with the spec's
  resolveI18nLabel against the display locale. ObjectGrid handed a map to
  DataEmptyState as an object child, which React refuses; ListView's typeof
  guard drew the default copy for every map.
- The objectui#11276 bag pins flip to the 17.6.0 readings (not deleted), the
  objectui#11068 twin pin reads invalid_union, member pins are registered for
  object-grid.description and .emptyState, and the docs move description into
  the bag (written flat it is now refused toward properties.description).

keyboardNavigation is not published: nothing reads it yet. The parity gate
stays red on that one key, reported for a decision.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…e pending changesets its slice falsified

.changeset/11227-object-grid-17-6-keys.md declares plugin-grid and types
minor and plugin-list patch. Three pending changesets of the same release
said emptyState was three optional strings or not authorable in a document;
each gets a dated correction paragraph instead of a rewrite.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ng to any (objectui#11094)

The new $empty pin's row helper casts to QueryParams['$filter'], and the
FilterConditionField expiry row needs no cast at all (BuilderCondition's
value is already loose). Runtime-identical; eslint's no-explicit-any
count on the touched files returns to its base reading.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
… and the READMEs drop the version literal (objectui#11094)

The full suite caught two things the targeted run could not:

- filter-refusal-subject-9050 enumerates every FilterOperatorError throw
  site in filter-converter.ts and reddened on the new $empty one (13
  sites, 12 rows). Its row names $empty as the subject.
- doc-version-claims refuses an unledgered version literal on a scanned
  doc surface; the data-objectstack README now cites objectstack#20446
  instead of the release number, and the core adapters README cites
  objectstack#20570 the same way.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…e-read the NamedListView loose census, cite objectstack#20694 instead of a version literal (objectui#11227)

- imported-defaults-8317: the flat mirror now imports the spec's
  EmptyStateSchema, so it joins IMPORTED and the differential measures it.
- object-view-unmirrored-keys-7779: NamedListView.emptyState no longer
  restates a nested object literal, so the loose regex reads 73 (gap 9),
  not 76 (gap 12). The member census is unchanged at 64.
- The plugin-grid README, plugin-grid.mdx and schema-reference.md cite the
  upstream card instead of a spec version literal, which
  doc-version-claims refuses unless ledgered.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
One conflict, resolved mechanically: packages/plugin-grid/src/index.tsx,
GRID_QUERY_INPUTS. main's rewritten `conditionalFormatting` input
(objectui#11533) is kept exactly as main has it, and objectui#11227's
`emptyState` comment and entry follow it. No other edit.

Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
…ensus to objectui#11334 under objectui#11438 ruling A″

The spec that objectui#11531 resolves adds `checkDashboardWidgetDimensionlessMeasureArity`
to `DashboardWidgetSchema` (objectstack `11d28c17`, #21053). objectui's mirror cannot
re-attach it by static import: objectstack `main` renamed the export (`32d57690`), and
the Spec Main Shape Gate refuses the 17.6 name. objectui#11438 ruling A″ (record
5968177777) books row 3 to objectui#11334 with an expiry, in objectui#11111 decision
3 = B's shape.

- The `DashboardWidgetSchema (complex.zod.ts)` row's `notAttachable` list books the
  check, written OWED TO objectui#11334. Expires when objectui resolves an
  `@objectstack/spec` carrying `32d57690`, or 2026-11-02, whichever is first.
- The census bounds the booking: the row's count equality and the exact export-name
  match admit nothing beyond the listed entry, and the expiry condition turns the
  name census red by name.
- An empty-frontmatter changeset declares no release; the test is excluded from
  `dist`.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…ation — arrow-key cell navigation on the WAI-ARIA grid pattern, published in GRID_QUERY_INPUTS with its reader (objectui#11068)

data-table gains a keyboardNavigation flag: the table is exposed as a
grid, its data cells are one roving Tab stop, the arrow keys / Home / End /
Ctrl+Home / Ctrl+End move focus, and an edit ended with Enter or Escape
hands focus back to its cell. Off (the default for every other host) the
table is unchanged.

ObjectGrid resolves the object-grid node's keyboardNavigation with its
declared default — on when the grid renders editable (inlineEditable) —
and relays it to the data-table it builds. GRID_QUERY_INPUTS publishes the
key, so the console parity rows that read ['keyboardNavigation'] close by
the build, with no exemption.

Types: DataTableSchema declares the host-set flag (ledgered runtime-only
beside editable / singleClickEdit); the object-view table slot still
withholds the key, now for the not-relayed reason.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
claude added 10 commits October 3, 2026 10:41
…igation changeset, dated corrections to two pending changesets it falsified, the derived runtime-only key total, and focus flushed inside act in the roving-stop pins

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…ws of both 8894 measure-door tests to objectui#11334 under objectui#11438 ruling A″

Both files derive FAMILY by effect, so the spec's dimensionless measure-arity
check (objectstack `11d28c17`, #21053) put pie, donut, funnel, scatter, treemap,
sankey and radar into it, and objectui's door does not refuse them: the mirror
waits for objectui#11334, because objectstack `main` renamed the export
(`32d57690`). The seat ruled Q1 = A on objectui#11334 (record 5968413148): a
bounded in-file ledger with inverted pins, under ruling A″ (record 5968177777)
and objectui#11111 decision 3 = B (record 5902351047).

- `OWED_TO_OBJECTUI_11334` names exactly the seven types in each file, with the
  owner, the rulings and the expiry: objectui resolves an `@objectstack/spec`
  carrying `32d57690`, or 2026-11-02, whichever is first.
- The listed types' rows assert today's difference: the spec refuses, and
  objectui's door accepts (types); the add control is offered and no stored
  refusal is shown (panel).
- Cap rows require the FAMILY types objectui fails to refuse to equal the ledger
  exactly, so a new difference is red, and so is a listed type objectui starts
  refusing once the mirror attaches.
- The metric family's rows keep their assertions byte for byte; they iterate
  FAMILY minus the ledger, and a row keeps that set non-empty.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…igation, to objectui#11068 under objectui#11438 ruling A″

@objectstack/spec 17.6.0's object-grid row declares `keyboardNavigation`, and
the grid's `inputs` do not publish it: objectui#11068 is building the key's
reader and publishes the key with it. objectui#11438 ruling A″ (record
5968177777) books a row whose slice is not accepted when the row-3 booking is
pushed "the same way, to their own cards", in objectui#11111 decision 3 = B's
shape (record 5902351047). On the trunk tip c4775ed the parity file had
exactly two red rows, both reading ['keyboardNavigation']; with this booking
it reads 255 / 255.

- UNPUBLISHED_EXEMPTIONS books `object-grid.keyboardNavigation` OWED TO
  objectui#11068, with an expiry: 2026-11-02 (ruling A″ is of the same day as
  ruling A′), or when objectui#11068 lands, whichever is first.
- objectui#11068 joins the ledger's owners as the fifth, routed by exact id;
  the `unpublishedKeys` cap goes 11 -> 12 and the per-owner totals gain
  objectui#11068: 1. No other cap moves (the key is boolean-armed, so no
  member pin is owed).
- `the four GA blocks resolve their ruled split` refused any exemption on the
  four GA blocks except the ruled carve-out, so the ledger entry alone left it
  red. It now admits this one OWED id BY NAME: the OWED entries on the four
  blocks must equal ['object-grid.keyboardNavigation'] exactly, the undeclared
  set must equal the booked set (so the build's landing turns it red until the
  booking is struck), and every other exemption on the four blocks is still
  refused. objectui#4648's docblock states the exception.
- Changeset: empty frontmatter; the file is unreachable from the console
  bundle and `plugin.*`.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…, in English (objectui#11068)

The owner paragraph quoted the maintainer's order in Chinese; this repo's
codebase rule keeps code comments English, so it now cites record 5968177777
and says what the order was. No assertion moves.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…e host beside editable, and what the table does with it (objectui#11068)

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…igation marker sentence and point at the installed row instead (objectui#11068)

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…ctui#11068's keyboardNavigation slice

The trunk advanced from da4c629 while this slice was built: objectui#11094's
slice, the row-3 booking (objectui#11334) and the row-1 booking
(object-grid.keyboardNavigation, owed to objectui#11068) under objectui#11438
ruling A″. Merged, not rebased (AGENTS.md: no history rewrite on a pushed
branch), so the next commit can strike the row-1 booking this build makes stale.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…hes object-grid.keyboardNavigation with its reader, so the ruling-A″ entry is stale (objectui#11068)

The booking prescribed exactly this at the card's landing: the
UNPUBLISHED_EXEMPTIONS entry goes, the unpublishedKeys cap comes down by
the one entry (12 to 11), the owner's count reads 0, and the GA-block
split row's booked list is emptied, so an OWED entry on a GA block is red
again. The row-1 booking changeset carries a dated correction.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…dNavigation build (objectui#11068)

Ruling Q1 = B: this build lands on main as an ordinary PR after the bump,
and strikes row 1's booking there.

main's 6158e4c is a squash whose tree equals the old trunk tip 0507d67;
this branch already holds that trunk up to bebd7ec. The merge base is
therefore pre-trunk (6f5719e), so eight files the trunk and the branch
both touched conflicted. Every conflict resolves to the clean three-way
merge of this head with 0507d67 (base bebd7ec), whose two sides are
file-disjoint: the build's 18 files take this branch's side, and the
trunk's six later files (row-5 booking, the types floor ^17.6.0, the bump
changeset's range sentence, the lockfile) take main's side.

- registry-inputs-spec-parity.test.ts keeps the STRUCK row-1 booking:
  entry removed, unpublishedKeys 11, 'objectui#11068': 0, BOOKED_GA_KEYS
  empty.
- pnpm-lock.yaml was regenerated by pnpm install, and is byte-identical to
  main's.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…eyboardNavigation is published and read since objectui#11068's build

The entry's bullet says the key "is not published" and "nothing in the
grid reads it yet". That text is still pending on main, so it would ship
false beside this build. A dated correction paragraph is appended, in the
shape this branch used on 10976-table-slot-relay.md; the original text
is kept.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 69 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10872-container-children-channel.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Also in this change, with no behaviour change: objectql.zod.ts's two public-block arms (object-metric, object-master-detail-form) build their properties member with the same propsBag helper as the other public-block arms, instead of a byte copy of it. The member's description text is unchanged.

.changeset/11355-small-p1-sites-r2.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ObjectChartSchema.isAnimationActive?: boolean is declared on the TypeScript face. Code sets it false for a render with no entrance animation: DashboardRenderer and DashboardGridLayout on the object-chart nodes they build, and DatasetWidget and DatasetReportRenderer on the nodes they hand the chart registration. ChartRenderer honours it. The zod mirror declares no member for it, so it is not an authoring key; zod-mirror-parity.test.ts files it as runtime-only.

.changeset/11522-kanban-rule-dialect-retired.md

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • KanbanConditionalFormattingRule is now an interface that extends SpecConditionalFormattingRule, with field, operator, value, backgroundColor, borderColor and textColor declared ?: never. It used to be the union of SpecConditionalFormattingRule and KanbanNativeConditionalFormattingRule. - KanbanNativeConditionalFormattingRule is removed. Importing it is a compile error (TS2305). - KanbanConditionalFormattingRuleSchema, the rule schema ObjectKanbanSchema applies, is a module export of this package's src/zod/objectql.zod.ts. It is not on the @object-ui/types/zod barrel or on any other entry of the package's exports map, so it is not an import a consumer can name. It is the spec ListViewSchema.conditionalFormatting rule taken by reference and extended, not a union. It keeps the spec rule's strictness and its style map. Its condition is the same schema the list view's and the grid's { condition, style } arm reads, so a string condition is still not canonicalized into an envelope and '' is still accepted. The six retired keys are retirement tombstones.

.changeset/4730-retire-dead-locale-key-batch.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • Superseded twin vocabularies. cellRender.* and rowAction.* duplicated a grid.* vocabulary that won. RowActionMenu.tsx is fully i18n-wired and reads grid.openMenu / grid.edit / grid.delete; ObjectGrid.tsx reads grid.empty / grid.yes / grid.no / grid.systemFields. The twins had no reader on either side. - Labels that outlived their control. calendar.agenda labelled a view mode b55a34647 retired from CalendarViewMode (now 'month' | 'week' | 'day'). home.quickActions.createApp*, layout.systemNav.createApp, actionDialog.defaultActionTitle / .ok and grid.bulk.selectPlaceholder sit in namespaces whose consumers are live and wired but demonstrably read other siblings. - Surfaces that left the product. map.* is the strongest form: @object-ui/plugin-map declares no @object-ui/i18n dependency and contains no t() call at all, so it cannot consume a locale string. home.stats.* and recordDetail.viewersTooltip name surfaces nothing renders.

.changeset/5453-retire-grid-column-wrap-forward.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    generateColumns() copied a per-column wrap onto every column object handed to data-table, and packages/components' data-table.tsx never read it. Measured on the current ref rather than inherited from the card, with comments stripped so prose mentions cannot be counted as reads: a column-level wrap scores 0, against accessorKey 34, align 5, header 4, className 4, width 8 and fitContent 2 in the same query shape. Those sibling counts are the positive control — the search style does find the keys that are genuinely consumed, so the zero is a measurement and not a mis-aimed grep. The raw string wrap does occur in that file; every occurrence is flex-wrap, whitespace-nowrap, or a variable named wrapper.

.changeset/5853-tablecolumn-type-canonical-union.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    @object-ui/types exports TABLE_COLUMN_TYPES (the canonical tuple — the single declaration the zod mirror builds its enum from, so the two cannot drift), the TableColumnType union, and normalizeTableColumnType() for producers. The as any cast in data-table.tsx is deleted and the read is typed, so re-introducing an undeclared spelling is a tsc error rather than a silent widening.

.changeset/5903-objectgantt-declared-keys.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743); the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair — but at that time the ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction, not that nothing was there. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger requires every registered pair's mirrored-but-undeclared key set to equal that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/5926-empty-action-visible-when.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The fix is a route, not a new check: no visibleWhen test was added to data-table.tsx. A local check on this slot would have been a fourth evaluator, which is the drift page:tabs' item-level predicate already records on this card. The slot now mounts its authored node exactly the way the empty renderer's action slot always has.

.changeset/5928-classname-style-props-rename.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Where the non-pair is recorded now. zod-mirror-parity.test.ts keys its existing EXCLUSIONS entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to ClassNameStylePropsSchema. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for.

.changeset/5993-button-shared-icon-resolver.md

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743) — the same retirement objectui#5903's entry carries, for the same clause and the same reason; the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair, at a time when that ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger reconciles every registered pair's mirrored-but-undeclared key set against that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/6150-undeclared-but-consumed-keys.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ AMENDED, and the amendment ships in this same release. The TreeViewSchema slice (604476d97) gave the key a zod arm after all — a NAMED REFUSAL (handlerKeyRefusal(key, 'runtime-slot', label)), never a shape — because "no mirror entry" is not neutral under BaseSchema.passthrough(): it meant an authored { "type": "tree-view", "onNodeClick": { "action": "toast" } } parsed GREEN, survived the parse, and reached a call site that expects a function. ⇒ the three clauses this bullet used to carry are no longer true of the code shipping beside it. The key is now a MEMBER of TreeViewSchema.shape and an authored value is refused BY NAME at path onNodeClick; it has LEFT zod-mirror-parity.test.ts's RuntimeOnlyDeclared for that file's KnownDrift; and it is no longer "the first pair to sit there without also sitting in UnmirroredDeclared" — draining it emptied that difference, so RuntimeOnlyDeclared is now a SUBSET of UnmirroredDeclared and the union of the two equals UnmirroredDeclared itself. ⛔ objectui#6152's ruling is untouched by any of this: what the key still does not have, and never will, is a z.function() shape — no serialized document could satisfy one.

.changeset/6152-object-form-unmirrored-members.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectFormSchema in @object-ui/types (objectql.ts) declared a set of members that its zod mirror in @object-ui/types/zod had never heard of. Every one of them is read by the object-form renderer (ObjectForm in @object-ui/plugin-form). The two published faces answered differently:

.changeset/6175-column-state-persistence.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    • @object-ui/components — DataTableSchema has declared onColumnResize?: (columnKey, width) => void all along, and data-table.tsx invoked it nowhere: the resize drag updated the table's local columnWidths state and stopped there. It now reports the settled width once, at mouseup. Once, deliberately — the host turns this callback into a write to shared view config, so a per-mousemove callback would be a write storm. - @object-ui/plugin-grid — ObjectGrid emitted onColumnReorder (singular) while the renderer invokes the near-duplicate onColumnsReorder (with the s), a different declared key with a different signature. The producer now emits the spelling the renderer actually invokes, mapping the reported TableColumn[] to the accessorKey order columnState stores.
  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Nothing is retired. Both spellings remain declared on DataTableSchema; onColumnReorder stays declared and stays unwired, exactly as the RuntimeOnlyDeclared ledger in zod-mirror-parity.test.ts records it. Which of the two survives is a declared-surface ruling that stays open and is deliberately not settled here.

.changeset/6269-objectview-slot-key-lists.md

  • names packages/types/src/__tests__/object-view-slot-key-lists.test.ts → packages/types/src/__tests__/object-view-slot-key-lists.test.ts — edited by this change

    Each Omit is now a Partial<PickANGLE-BRACKETS(…)> over an explicit key list — at this change 59 keys for table, 64 for form, i.e. every declared member minus the identity keys the view itself fixes. Pick with literal keys never computes keyof T, so it cannot collapse the same way. The key lists are pinned against silent drift by packages/types/src/__tests__/object-view-slot-key-lists.test.ts, which recomputes each source schema's declared members through the TypeScript checker and, at this change, requires set equality; a member added to ObjectGridSchema and not to the list turns it red.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

.changeset/6373-datatable-emit-boundary.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The measured read set of the consumer (data-table.tsx, comments stripped) contains none of the six, so all six retire from the emit rather than being declared — declaring a key nothing reads is the same declared != enforced defect facing the other way. Rendering is unchanged because none of those keys was the live path for its own value: the FieldMeta the cell closure captures is what this widget's type-aware rendering has always read, and it is untouched. Authored spellings still pass through, so a column the author wrote as { format: '$0,0' } keeps its format exactly as before.

.changeset/6424-tablecolumn-declares-headericon.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Scope of the enforcement claim, measured: the declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

.changeset/6597-retire-fieldmeta-referenceto.md

  • names plugin-grid/src/ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    No authoring story survived the search either. ObjectGrid's own relational-meta pass-through (applyRelationalMeta, plugin-grid/src/ObjectGrid.tsx) copies reference_to / reference / display_field / etc. from the SCHEMA field def only, at all three of its call sites — never from an authored column override. No doc, example, or fixture in this repo shows a table column pinning a lookup's target away from what its schema field already says. Under the maintainer's standing startup-stage rule (2026-08-27: deprecated/alias spellings retire immediately, no transition windows), no measured demand selects withdraw.

.changeset/6639-objectgrid-title-mirrored.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The gain is the typed refusal: the mirror's .passthrough() base was already admitting any title unexamined, and it now enforces the declared string. zod-mirror-parity.test.ts's UnmirroredDeclared ledger records the key as worked off — the ledger's first shrink by repair (97 + 1 mirrored + 23 reclassified is what the seeded "121" now means).

.changeset/6650-listcolumn-wrap-implemented.md

  • names packages/plugin-grid/README.md → packages/plugin-grid/README.md — edited by this change

    @objectstack/spec declares ListColumn.wrap and describes it to authors as "Allow text wrapping", and packages/plugin-grid/README.md shows it in its authored-column example. No renderer anywhere implemented it. Long cell text stayed clipped to one line, with no error, no warning and no feedback of any kind — a promise made at authoring time and silently broken at render time.

.changeset/6881-retire-data-table-toolbar.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6882-datatable-declare-render-cell-editor-and-cell-classname.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    data-table has read both keys on its production path all along — renderCellEditor through a (schema as any) cast, cellClassName by destructuring it into the class of its three utility cells (the selection checkbox, the row number, the row actions). Neither was declared, so authoring either one was unchecked: a misspelling produced no error and no widget, and no editor completion offered them. DataTableSchema now declares both, and the cast in data-table.tsx is gone rather than replaced.

  • names content/docs/components/complex/data-table.mdx → content/docs/components/complex/data-table.mdx — edited by this change

    ⚠️ What the schema-level cellClassName actually styles. It is NOT the table-level twin of the per-column key: the two reach disjoint cells. Measured on the render, the schema-level key is folded into the utility cells only — the selection-checkbox cell, the row-number cell and the row-actions cell — while every data cell folds TableColumn.cellClassName and nothing else. Row density is therefore a pair of settings (ObjectGrid sets both), and the schema-level key alone leaves data cells at the primitive's default p-4. The docblock, the zod describe and content/docs/components/complex/data-table.mdx all say this now.

.changeset/6909-fieldeditwidget-dom-pass-through.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Also corrects a comment in @object-ui/components' data-table.tsx that this change falsifies. It justified the injected editor's document-level pointerdown listener partly with "FieldEditWidget forwards autoFocus and nothing else out of the DOM block, so a host handler could not reach the control through it even if one were passed" — no longer true. The listener is still load-bearing for the other half of that reason, which is untouched: the renderCellEditor context object has nowhere to put an onBlur in the first place. Comment only; no behaviour change in that package.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/6951-tree-view-data-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

.changeset/6972-markdown-inert-keys-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured, on this branch's base. sanitize was declared ?: boolean with @default true on both published faces — data-display.ts and the Zod mirror — documented, and read by NOTHING. Worse than an ordinary inert key, it implied a switch that does not exist: sanitization is unconditional. rehypePlugins in plugin-markdown/src/MarkdownImpl.tsx is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema], handed to ReactMarkdown as-is — no ternary, no if, no runtime assembly. MarkdownRenderer forwards exactly content and className, and MarkdownImplProps accepts only those two. A repo-wide grep for schema.sanitize over packages/ and apps/ returns nothing, against a control of 20 .tsx files reading schema.content in the same query shape, so the zero is a reading, not a blind query. An author writing sanitize: false believed they turned XSS filtering off; one writing sanitize: true believed they turned it on. Neither was true.

.changeset/7113-chart-data-model.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7200-object-form-section-style-keys-undeclared.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7322-object-kanban-group-by-limit.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

.changeset/7344-handler-string-any-mirrors.md

.changeset/7352-drill-down-config-mirror.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7363-objectql-union-arms.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.changeset/7632-shared-record-source-config.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    That ladder is published contract on both faces — packages/types/src/objectql.ts and its zod mirror both ship .describe() strings naming getDataConfig's order (77cb489b4, maintainer ruling 2026-09-02), pinned by objectql-record-source-refinement-6939.test.ts — and it was hand-copied into five plugin components with no gate holding them together. A change to the ruled order had five edit sites and nothing that noticed a missed one; that is the AGENTS.md #0.1 drift class.

.changeset/7654-floating-chatbot-trigger-icon-tombstone.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Every other tombstone in this package pairs ?: never with a retirementTombstone() refusal on the Zod twin. There is no twin here to carry one: FloatingChatbotConfig has no Zod mirror at all, and floatingConfig sits in the UnmirroredDeclared ledger (zod-mirror-parity.test.ts, complex.zod.ts#ChatbotSchema). BaseSchema is .passthrough(), so the whole floatingConfig object rides through unvalidated — before this change and after it. Minting a mirror to host a refusal would be the declared-but-UNMIRRORED axis (objectui#6152), a different defect: a key can be mirrored and inert, or unmirrored and live, and fixing one says nothing about the other. This change does not widen into it.

.changeset/7664-kanban-arm-plugin-dialect.md

  • names src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ⚠️ Dated note, 2026-10-03 — KanbanConditionalFormattingRuleSchema is no longer a union — objectui#11522. At this change KanbanConditionalFormattingRuleSchema was "the rule union the 'object-kanban' arm already applied": the native { field, operator, value } comparison or { condition, style }. Now it is one object, the spec list view's { condition, style } rule by reference, and the native rule and a top-level colour key (backgroundColor, borderColor, textColor) are refused by name; the 'kanban' arm it was shared with has itself retired in this release (objectui#8802). It keeps its name, and it is a module export of src/zod/objectql.zod.ts inside @object-ui/types, NOT an export of the @object-ui/types/zod barrel. Measured at objectui#11522's change: that barrel re-exports KanbanCardSchema, KanbanColumnSchema and ObjectKanbanSchema from the kanban family and not this schema, and no entry of the package's exports map carries it. So the bullet above that calls it "newly exported from @object-ui/types/zod" does not hold in this release either; whether it held at objectui#7664's own commit was not measured. The rest of this entry is kept as the reading of this change.

.changeset/7722-wrapper-class-five-more.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7762-object-grid-export-options-bare-array-refusal.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    What was measured, on this branch's base. The mirror declared NO exportOptions member at all, and BaseSchema is .passthrough(), so ObjectGridSchema.safeParse({ type: 'object-grid', objectName: 'accounts', exportOptions: ['csv', 'xlsx'] }) returned success: true with the array back VERBATIM — as did { formats: ['csv', 'pdf'], compression: 'gzip' }. Nothing on the render path parses, and ObjectGrid.tsx reads schema.exportOptions?.formats and only that, so the authored array then lost SILENTLY to the ['csv', 'json'] default: the useEffect that warns about dropped formats reads .formats too and returns early when it is absent, while !!schema.exportOptions kept the export button on screen. An author declared ['csv', 'xlsx'] and got csv/json with no error, no warning and no console line. The two authoring faces disagreed in the direction opposite to objectui#6956's: the TypeScript interface already declared the object form only, so TS refused what zod admitted.

.changeset/7804-data-table-handler-key-arms.md

  • names renderers/complex/data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    BaseSchema is .passthrough(), so a key no arm declares is not refused — it stops being judged and the value is KEPT. All seven were in that state while renderers/complex/data-table.tsx read and INVOKED each one, so an authored { "type": "data-table", "onRowClick": { "action": "toast" } } parsed GREEN and that action object was handed to a call site expecting a function. Each key is now a named refusal on the mirror (handlerKeyRefusal, the objectui#6124 shape), and the arm's message says why JSON cannot author it and what to write instead.

.changeset/7804-objectql-handler-key-arms.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The four plain objectql.ts node faces declare the nine handler keys their registered renderers read (the objectql.ts slice): ObjectFormSchema.onCancel / .onError / .onOpenChange / .onStepChange / .onSuccess, ObjectGallerySchema.onCardClick / .onRowClick, ObjectGridSchema.onNavigate and ObjectViewSchema.onNavigate.

.changeset/7804-tree-view-handler-slot.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    'runtime-slot' and not 'retired', measured at this key's own channel. 'retired' publishes "no renderer reads this key, so nothing could ever run it" — true of the two siblings already tombstoned on this arm (onSelectChange, onExpandChange) and flatly false here, since the read is live and INVOKED. ⚠️ No in-repo host builds a tree-view node carrying the key: the channel is wired end to end and only the supplier is absent, which is the same shape as ObjectFormSchema.onStepChange in this card's objectql.ts slice and is not evidence of a dead read. The TypeScript declaration is unchanged and still callable, so a programmatic host supplies it exactly as before.

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The pair moves from zod-mirror-parity.test.ts's RuntimeOnlyDeclared to its KnownDrift, which empties the former of the one entry the latter did not also hold — so the two unmirrored ledgers are now in a containment relation, and the cross-ledger figure that recorded their difference states the containment instead.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/7963-alert-dialog-footer-keys-retired.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Nothing else moves. These spellings are overloaded across the tree and every other owner is a live key on a different declaration — FormSchema.cancelLabel, objectql.ts's confirmLabel, plugin-designer's ConfirmDialog React props, plugin-grid's def.confirmLabel, and plugin-form's ModalForm / DrawerForm, which build a local cancelLabel from schema.cancelText. None is an AlertDialogSchema; none is touched, and a pin asserts it. No fixture, catalog schema, example app or doc fence authored any of the three on an alert-dialog node, so no shipped document is stranded.

.changeset/8067-component-input-member-kind.md

  • names apps/console/src/__tests__/registry-inputs-spec-parity.test.ts → apps/console/src/__tests__/registry-inputs-spec-parity.test.ts — edited by this change

    A registration's type: 'array' said a value was a list and stopped there, so a member that drifted from @objectstack/spec was invisible to every layer that reads a declaration. page:header.actions is the measured cost: the contract declares z.array(z.string()) ("Action IDs"), the renderer read the members as ActionDef objects, and the repo-wide parity gate in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts stayed green for the whole life of the drift because both sides carried the key and neither could say what was inside it. What settled it was a maintainer ruling, not a test — and even after the fix, "these are ids" survived only as English in the registration's description.

.changeset/8331-data-table-empty-action-primitive-node.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

.changeset/8338-retire-toast-action.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The parity ledgers drain with it, every figure re-derived by zod-mirror-parity.test.ts's own AST and mirror instruments rather than stepped by hand: KnownDrift 42 entries / 64 keys → 41 / 63 (the entry's whole content, so the entry went too — the ledger's first loss by RETIRING a key rather than by moving either face toward the other), and WiderThanDeclared 23 / 36 / 47 arms, split 6 / 30 / 0 / 11 → 22 / 35 / 45, split 6 / 29 / 0 / 10. The pair itself stays registered, so EXPECTED_MIRROR_PAIRS does not move.

.changeset/8478-describe-line-addresses.md

.changeset/8572-chatbot-body-retired.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Why this key and not another. It was the ONE place in this vocabulary where body did not mean "what goes inside this component": zod-mirror-parity.test.ts carried the pair under KnownDrift as "two different meanings of one key", and the same collision was the whole reason chatbot was the single arm of the component union whose output was not assignable to SchemaNode. Both ledger rows move with this change, and the two pins that recorded the old state are INVERTED rather than deleted (see below).

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8767-object-grid-refuses-string-sort.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    Migration. Write the array: sort: [{ field: 'name', order: 'desc' }]. Both keys are required. SortConfig.order carries no ? in @object-ui/types (packages/types/src/objectql.ts) and no .optional() in its zod mirror, and the protocol's own reusable SortItemSchema requires order as well — measured: that schema refuses [{ field: 'name' }] with invalid_value at 0.order. Do not omit it: this block's array arm interpolates whatever is present, so an omitted order lowers to $orderby: 'name undefined' today. That is pre-existing behaviour on the arm this change does not touch, and it is filed as a successor card rather than widened into here.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.
  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8885-object-chart-drilldown-title-compareto.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/types/src/objectql.ts → packages/types/src/objectql.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

  • names objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9092-inline-locale-declared-face.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    • AppComponentSchema.label (app.ts) - ObjectGridSchema.label and .description (objectql.ts) - PageNodeSchema.aria.ariaLabel (layout.ts)

.changeset/9256-list-timeline-content-channels.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    These two were held out of the previous family-D slice for a SERIAL constraint on packages/types/src/data-display.ts and never for a verdict. Readership was re-derived for both rather than inherited: a TypeScript compiler-API sweep files every .body / .children read under the declared type of its receiver and answers zero for ListSchema and TimelineSchema while its live controls fire. timeline's bare-key owner is any-typed, so it was attributed directly as well — packages/plugin-timeline contains no channel read of any kind.

.changeset/9309-object-gallery-filter-destination-typed.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

.changeset/9511-record-id-is-a-string.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ The declaration alone would not have been enough, and this is the reusable part. A TypeScript declaration does not run at parse time. The hand-written zod mirror is the only face in this repository that can refuse an authored number, so narrowing the declaration without the mirror would have shipped declared !== enforced on a published surface — and zod-mirror-parity.test.ts would have stayed GREEN through it, because that instrument asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes. Both faces moved together for that reason.

.changeset/9549-tree-filter-declared.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9550-object-tree-root-barrel.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    ObjectQLComponentSchema declares the node types an ObjectQL block may be. Every one of its arms was a named export of this package's root barrel except ObjectTreeSchema, which was declared in objectql.ts, applied by the union, and re-exported by the ./zod barrel (objectui#7917) — while no TypeScript consumer could name it. There is no ./objectql subpath to reach around the barrel: the package's exports map is pinned by packages/types/src/__tests__/package-exports-manifest.test.ts, and the root barrel was the only route to this type.

.changeset/9606-object-kanban-card-title.md

  • names zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

.changeset/9628-kanban-column-collapsed-honoured.md

  • names objectql.ts → packages/types/src/objectql.ts — edited by this change

    The key was declared on both published faces of the object-kanban arm — the lane element of ObjectKanbanSchema (objectql.ts and its Zod mirror) and the runtime lane KanbanColumn (complex.ts and its mirror) — and read by KanbanEnhanced alone, a module no production source imports. An authored { "id": "todo", "title": "To Do", "collapsed": true } therefore parsed green on both faces and reached a board that did nothing with it: KanbanImpl's only collapse is the SWIMLANE row's, held in viewer state under objectui:kanban-collapsed:ANGLE-BRACKETS(swimlaneField) and never keyed to a lane's declared value. That is the ADR-0049 declared-but-unhonoured shape.

.changeset/9722-bulk-executor-datasource-face.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • ObjectGrid.tsx → RecordDetailPanel: removing it type-checks GREEN. It was paying for nothing — both sides already declare the same DataSource | undefined from @object-ui/types. Removed outright. - ObjectGrid.tsx → BulkActionDialog: removing it reddens, and it was paying for TWO things at once — the optional-vs-required arm (the grid declares dataSource?: DataSource, the dialog demands one) AND the bulk-door contravariance. Only the first still needs erasing, so it is now a non-null assertion: today's runtime behaviour is preserved exactly, and any future drift of the face reddens at that site instead of passing silently.

.changeset/9853-grid-non-positive-page-size.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    Refuse a non-positive pageSize at all three of ObjectGrid.tsx's read points, instead of giving two different answers for one authored value (objectui#9853).

.changeset/dashboard-stable-empty-rows-4629.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Nothing rendered wrong before or after; this is wasted work in the empty window, plus the live react-hooks/exhaustive-deps warning the conditional raised. It is the same module-scope frozen empty data-table.tsx adopted for its own EMPTY_ROWS (objectui#4618), applied to the provider: 'object' siblings.

.changeset/object-view-unmirrored-keys-7779.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Who is NOT affected: every correctly typed document, and every document that never wrote these keys — absent stays valid on all nine. No renderer changed. The parity ledger (zod-mirror-parity.test.ts) records the move: UnmirroredDeclared 14 entries / 96 keys to 14 / 87, the ObjectViewSchema entry re-derived into the SPEC-DERIVED half because the mirror now references the spec in code.

.changeset/table-renderer-declared-column-contract-5350.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    renderers/complex/table.tsx resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name]. Neither label nor name is declared on TableColumn, which declares header and accessorKey — both required (packages/types/src/data-display.ts). This was the fourth site of the column-alias family, after data-table, ObjectDataTable and ObjectGrid (objectui#5350).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 7121221fa (merge-base with origin/main): 14 file(s) changed outside .changeset/, read against 2074 pending declaration(s) that publish a body (2708 pending in total). · run

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 14:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 14:04
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3308.2 KB 3330.4 KB
Main entry chunk (gzip) 150.3 KB 350 KB
Entry file index-6g-NBxis.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 572.77KB 137.31KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 231.08KB 64.07KB
fields (index.js) 261.51KB 66.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.26KB 38.44KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.43KB 64.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.05KB 64.60KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.55KB 28.95KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 154075a Oct 3, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11068-keyboard-navigation-17-6 branch October 3, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types,plugin-grid): eight declared ObjectGridSchema keys have no read in ObjectGrid, and the schema reference teaches two of them

1 participant