Skip to content

fix: resolve context tokens in every directly authored data node's own filter (objectui#10666) - #10729

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10666-filter-token-sweep
Sep 26, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10666-filter-token-sweep

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10666
Clause-②: yes — @object-ui/react gains one hook export, useResolvedFilter. It is additive, and existing callers are unchanged.

This is the sweep half of the family closure card, under seat answer B (comment 5838380906) and the re-scoped claim 5838679906. PR objectui#10705 fixed object-gallery (part 1). This PR fixes the eleven remaining census nodes and closes the card.

What changed

One shared hold, exported from @object-ui/react. useResolvedFilter(filter, scope) sits beside useFilterScope. It is plugin-list's useResolvedAuthoredFilter together with its comparator isSameAuthoredFilter, moved into packages/react/src/hooks/useResolvedFilter.ts (git records a rename). The plugin-list module is deleted, and ListView and ObjectGallery import the shared hook. The hold, its key (the authored filter compared by structure, plus the scope's three members read one by one) and the comparator are unchanged. @object-ui/react already depended on @object-ui/core, so it can import resolveFilterPlaceholders directly.

  • One addition to the hold (a fork, see below). A filter that the resolver leaves unchanged (it has no token, or only tokens the scope cannot resolve) is handed out as the authored value itself. The resolver's structurally equal copy is not used. So a token-free filter reaches $filter by identity, as it did before.
  • Docs. The hook has JSDoc, and packages/react/README.md has a new "useFilterScope / useResolvedFilter" section. check:readme-exports --list judges both names real.

The eleven nodes. Each node now reads its own filter through the hold. The query and the fetch effect's key read the held value, never the raw filter.

node file what now reads the held value
object-calendar plugin-calendar/src/ObjectCalendar.tsx the object find, the inline ValueDataSource find, and the effect deps
object-gantt plugin-gantt/src/ObjectGantt.tsx reload's find, and both dependency lists
object-kanban plugin-kanban/src/ObjectKanban.tsx the find, and the effect deps
object-map plugin-map/src/ObjectMap.tsx the object and inline finds, the committed-query key, and the effect deps
object-timeline plugin-timeline/src/ObjectTimeline.tsx the find; filterKey (already a content key) is now taken over the held value
object-tree plugin-tree/src/ObjectTree.tsx the object and inline finds, and the effect deps
record:related_list plugin-detail/src/RelatedList.tsx toFilterNodeSafely and filterKey, before the merge with the parent scope
record:line_items plugin-form/src/LineItemsPanel.tsx toFilterNodeSafely and filterKey, before the merge with the parent scope (no save-in-flight hunk is touched)
element:repeater components/src/renderers/basic/data-list.tsx query.$filter and filterKey
element:number components/src/renderers/basic/elements.tsx the aggregate filter, the find fallback's $filter, and filterKey
element:record_picker components/src/renderers/basic/record-picker.tsx the winning composed?.filter ?? props.filter, then $filter and filterKey

@object-ui/types, prose only. The filter docblocks on ObjectMapSchema, ObjectTreeSchema, ObjectGanttSchema, ObjectCalendarSchema, ObjectKanbanSchema, ObjectChartSchema and ObjectGallerySchema used to say "forwarded verbatim as $filter". They now say the context tokens are resolved first. The five matching zod .describe() strings and one import comment were changed the same way. No type changed.

Changeset: .changeset/10666-filter-token-sweep.md: @object-ui/react minor, @object-ui/types patch, and each node package patch (plugin-calendar, plugin-gantt, plugin-kanban, plugin-map, plugin-timeline, plugin-tree, plugin-detail, plugin-form, plugin-list, components). None is major.

The pin table: red, then green

apps/console/src/__tests__/filterContextTokensSweep-10666.test.tsx is one describe.each table with 18 rows and 4 cells per row. It lives in apps/console because that is the one package that declares all nine node packages (the objectui#4409 dependency-direction method).

Each row renders the directly authored node through the real SchemaRenderer and its package registration, under FilterScopeProvider (usr_42, org_7), over a data source that records every query. For the inline paths, a spy on ValueDataSource.prototype.find records the queries instead.

The four cells are:

  • USER: {current_user_id} goes out resolved, and the whole query equals the query the literal id sends.
  • ORG: {current_org_id} goes out resolved.
  • CONTROL: a token-free filter goes out unchanged (for a pass-through node, it deep-equals the authored filter).
  • REBUILD: three re-renders that each rebuild an equal token filter add no query.

The pins-only commit 5ae7e1159d ran against the unfixed code: 38 failed, 34 passed (exit 1). The fix is 69d68598df.

row red at 5ae7e1159d green at head
object-calendar (object) USER, ORG, REBUILD 4/4
object-calendar (inline) USER, ORG, REBUILD 4/4
object-gantt USER, ORG, REBUILD 4/4
object-kanban USER, ORG, REBUILD 4/4
object-map (object) USER, ORG, REBUILD 4/4
object-map (inline) USER, ORG, REBUILD 4/4
object-timeline USER, ORG 4/4
object-tree (object) USER, ORG, REBUILD 4/4
object-tree (inline) USER, ORG, REBUILD 4/4
record:related_list USER, ORG 4/4
record:line_items USER, ORG 4/4
element:repeater USER, ORG 4/4
element:number (aggregate) USER, ORG 4/4
element:number (find fallback) USER, ORG 4/4
element:record_picker USER, ORG 4/4
object-gallery (regression) none 4/4
object-grid (regression) none 4/4
list-view (regression) none 4/4
  • The token cells failed on the literal. An example failure: "object-kanban sent the literal token: expected '[["owner","=","{current_user_id}"]]' not to contain '{current_user_id}'".
  • The REBUILD cells failed only on the nodes whose fetch effect keyed on the filter's identity. An example: "object-calendar (object) re-queried on a re-render that changed nothing: expected 4 to be 1".
  • The nodes that were already keyed by content passed REBUILD on base, as expected. Those are timeline, related_list, line_items and the three elements.
  • CONTROL was green on every row on base, so token-free behaviour is pinned as unchanged.

At head 507214a742 (after merging origin/main), the table passed 72/72 in one run with the hook test, both objectui#10607 pins, the gallery pin, all four objectui#10657 masked-column pins, the objectui#8176, #7711 and #9136 member pins, and the machine-locale census: 13 files, 172 tests passed, exit 0.

Hook unit test. packages/react/src/hooks/__tests__/useResolvedFilter.test.tsx has 10 cells:

  • it resolves the user and org tokens;
  • the same reference comes back across same-input re-renders, and across equal rebuilds;
  • {now} is held;
  • it re-resolves on a change of structure, and on a change of a scope member;
  • a token-free filter comes back as the authored value, and a resolved one comes back as a new value;
  • an absent filter passes through;
  • an unresolvable token stays in place and draws the shared warning.

Ablation on the shared hook

ablation-replace.mjs replaced return held.resolved as T; with the bare per-render return resolveFilterPlaceholders(authored, scope) as T;.

  • The anchor went from 1 hit to 0, and the blob went from 372374462041 to 8468e2e161e7.
  • The run covered the pin table and the hook test: 45 failed and 37 passed (exit 1).
  • On every identity-keyed node row (calendar, gantt, kanban, map and tree, each on both paths) and on the gallery and list-view regression rows, every cell went red, REBUILD included. The copy minted on every render re-queried without end, which gave "issued no query" readings and 15000ms timeouts.
  • object-timeline USER went red once.
  • The content-keyed rows stayed green: related_list, line_items and the three elements. There the JSON content key, not the hold, is what keeps the fetch steady.
  • In the hook test, the four stability cells went red (for example "expected [ [ 'owner', '=', 'usr_42' ] ] to be [ [ 'owner', '=', 'usr_42' ] ]").
  • Restore: the blob after restore was 372374462041, equal to HEAD, and git diff HEAD was empty.
  • @object-ui/* resolves to src/ through the root vitest alias, so this ablation has no dist/ leg.

The fork (flagged for the seat)

The existing member pins read $filter BY IDENTITY: objectui#8176 on object-kanban (3 cells), objectui#7711 on object-calendar (3 cells) and objectui#9136 on object-tree (1 cell). resolveFilterPlaceholders copies arrays, so with the hold moved exactly as it was, those 7 cells went red on a token-free filter.

  • Taken, option B: the hold hands out the authored value whenever the resolver changed nothing (one structural comparison per resolution, which runs only when the inputs change). A token-free filter now reaches the query exactly as before, by identity, and no existing pin was edited.
  • Not taken, option A: move the hold byte for byte and relax those 7 cells to toEqual. That would weaken pins whose authors chose toBe on purpose.

The one existing test that was edited is the objectui#8174 SOURCE pin in packages/types. It looked for the literal text $filter: schema.filter in the kanban and calendar sources, and it now reads both halves of the new chain: useResolvedFilter(schema.filter, filterScope) and $filter: queryFilter.

Verification at head 507214a742

Heavy runs went through the shared verify lock.

  • Build. turbo run build over the dependency closure of the 12 touched packages: 14 tasks successful.
  • Type-check. pnpm --filter over the 12 touched packages, run type-check: 12 Done, exit 0. react's tsconfig.test.json --listFilesOnly lists the new hook test once.
  • Full package suites, all exit 0:
    • After the final merge: plugin-detail (214 files passed and 1 skipped; 2102 tests passed and 8 skipped); plugin-form plus types (368 files; 6747 tests passed and 1 skipped); and components in two halves (renderers, notifications and ui: 135 files, 1289 tests passed and 10 skipped; __tests__: 177 files passed and 1 skipped, 1767 tests passed and 7 skipped).
    • After the first merge: plugin-calendar, plugin-gantt and plugin-timeline, in one run with types and the pins.
    • Before either merge: react, plugin-kanban, plugin-map, plugin-tree and plugin-list. Neither merge changed a source file in these packages.
  • Gates, each exit 0: check:changeset-claims (report-only), check:pending-changeset-literals, check:new-line-citations (0 new), check:control-bytes, check-changeset-presence (20 source files of 13 released packages, 1 changeset), changeset:check (no major), check-changeset-overwrite (1 added, 0 modified), check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:phantom-deps, check:self-import, check:test-path-roots, check:unreferenced-sources, check-type-check-coverage, check:side-effects-array, and check-governed-queue-guard --test (NOT GOVERNED, 22 paths).
  • README exports. check:readme-exports --list judges useFilterScope and useResolvedFilter in packages/react/README.md real. The whole-tree run exits non-zero only because 22 packages were not built in this worktree (0 fabricated after the rebuild), so the whole-tree verdict is NOT MEASURED.
  • eslint, plain form, on the 20 changed TS files: 0 errors. Warnings, merge-base against head: unchanged in every modified file except record-picker.tsx, which went from 11 to 10. The new files have 0, except the pin table, which has 1.
  • NOT MEASURED: check:eager-closure and check:sdui-registration-pins (both need a console production build); repo-wide pnpm lint and the full test farm, which belong to CI.

Merges

Two merges of origin/main:

  • bd254f7637: one import-line conflict in RelatedList.tsx, resolved as a union. It keeps objectui#10690's useSettledSchema and adds useFilterScope and useResolvedFilter.
  • 507214a742: brings in objectui#10657 (masked columns). It merged with no conflict, and both behaviours hold: RelatedList's masked column stamping, and its held resolved scope filter. Draft PR objectui#10650's save-in-flight hunks in LineItemsPanel.tsx are not touched. This PR changes only the filter read there.

Acceptance notes

  • Fewer queries, by design. On the five identity-keyed nodes, a host that rebuilds an equal filter inline no longer re-queries. A structurally different filter, or a new user or organization, still re-queries.
  • Date macros in these nodes' own filters now resolve on the client, in the browser's local time, as they already do for list-view, object-grid and object-view. Before, the server resolved them in the tenant's time zone.
  • The second copy. plugin-grid's ObjectGrid.tsx still holds its own copy of the hold and the comparator (useResolvedGridFilters, from PR objectui#10656). The claim did not cover that file. It could become useResolvedFilter({ filter, defaultFilters }, scope), with the same behaviour.
  • Stale prose outside this surface. Some sentences now describe the old read: pending changesets from objectui#8174 and feat(plugin-kanban,plugin-calendar): declare the filter input both renderers read #8186 describe the renderers as "lowering schema.filter onto $filter"; the objectui#8176 pin's header says "nothing between the schema and that call reads a member key"; and content/docs/api/schema-reference.md still carries "forwarded verbatim as $filter" in its ObjectKanbanSchema table. None of these was edited here.
  • Against ObjectStack, the server also expands {current_user_id} for a signed-in request, so there this is defence in depth.

The session behind this change is https://claude.ai/code/session_01D486C1axmXnrkJMNUfz2eb (domain:ui seat 3, dispatched dev).


Generated by Claude Code

…d data node (objectui#10666)

One enumeration table: the eleven census nodes (calendar, map and tree on
both query paths; number on both the aggregate and the find fallback) plus
object-gallery, object-grid and list-view as regression rows. Each row renders
the node through the real SchemaRenderer and its package registration, under
FilterScopeProvider, over a recording data source.

Committed RED against the unfixed nodes: 38 failed, 34 passed.

Co-Authored-By: Claude <noreply@anthropic.com>
…n filter (objectui#10666)

@object-ui/react exports useResolvedFilter beside useFilterScope: the hold
plugin-list used privately (useResolvedAuthoredFilter), moved here so one copy
serves every node. plugin-list's module is deleted and its two call sites
import the shared hook. The one addition: a filter the resolver leaves
unchanged is handed out as authored, so a token-free filter reaches the query
by identity, as before.

The eleven census nodes read their own filter through the hold, and feed the
query and the fetch effect's key from the held value: object-calendar,
object-map and object-tree (object and inline paths), object-gantt,
object-kanban, object-timeline, record:related_list, record:line_items, and
the repeater, number and record_picker elements.

@object-ui/types: the filter docblocks that said "forwarded verbatim as
$filter" now say context tokens are resolved first. Prose only.

Co-Authored-By: Claude <noreply@anthropic.com>
…ontext-token hold (objectui#10666)

The objectui#8174 source pin looked for the literal `$filter: schema.filter`.
Both renderers now read `schema.filter` through useResolvedFilter and send the
held value, so the pin reads both halves of that chain.

Co-Authored-By: Claude <noreply@anthropic.com>
…lter-token-sweep

# Conflicts:
#	packages/plugin-detail/src/RelatedList.tsx
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 52 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/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.

.changeset/6475-gantt-block-face-declared.md

  • names plugin-gantt/src/ObjectGantt.tsx → packages/plugin-gantt/src/ObjectGantt.tsx — edited by this change

    The gantt nested-block spelling of a gantt config ({ type: 'object-gantt', gantt: { … } }) had no mirror entry at all: it rode through BaseSchema's .passthrough() entirely unvalidated, and the published TypeScript never taught the shape either — an author writing gantt: { … } got no completion, no type checking, no error on a misspelt member. It was the 28th and last of the keys getGanttConfig (plugin-gantt/src/ObjectGantt.tsx) reads off the schema that objectui#6051 (PR feat(types,plugin-gantt): declare the flattened GanttConfig face ObjectGantt reads (#6051) #6472) did not declare — severed into its own card because, unlike the other 27, declaring it changes what a published tool refuses.

.changeset/6726-find-envelope-records-arms.md

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

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

  • names components/src/renderers/basic/elements.tsx → packages/components/src/renderers/basic/elements.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

  • names components/src/renderers/basic/record-picker.tsx → packages/components/src/renderers/basic/record-picker.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/6783-readprops-degenerate-config-bag.md

  • names elements.tsx → packages/components/src/renderers/basic/elements.tsx — edited by this change

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

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

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

  • names record-picker.tsx → packages/components/src/renderers/basic/record-picker.tsx — edited by this change

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

.changeset/6939-objectql-record-source-refinement.md

  • names plugin-map/src/ObjectMap.tsx → packages/plugin-map/src/ObjectMap.tsx — edited by this change

    Both renderers resolve their records from one of three keys, in this order — getDataConfig in plugin-map/src/ObjectMap.tsx and plugin-gantt/src/ObjectGantt.tsx: data, then staticData, then objectName. Both mirrors required objectName alone, so a document authored on staticData drew correctly and was refused by safeValidateSchema — six catalog entries, three per component.

  • names plugin-gantt/src/ObjectGantt.tsx → packages/plugin-gantt/src/ObjectGantt.tsx — edited by this change

    Both renderers resolve their records from one of three keys, in this order — getDataConfig in plugin-map/src/ObjectMap.tsx and plugin-gantt/src/ObjectGantt.tsx: data, then staticData, then objectName. Both mirrors required objectName alone, so a document authored on staticData drew correctly and was refused by safeValidateSchema — six catalog entries, three per component.

.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/7070-no-invented-gantt-date-fields.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.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/7313-object-calendar-record-source.md

  • names plugin-calendar/src/ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectCalendar resolves its records through the shared ladder (resolveRecordSourceConfig in @object-ui/core, called from plugin-calendar/src/ObjectCalendar.tsx): data first, then staticData, then objectName. The published TypeScript interface REQUIRED objectName and declared neither data nor staticData; the published Zod mirror did the same. So an object-calendar node authored on staticData — the route the plugin page documents twice — rendered correctly and was refused by safeValidateSchema, and could not be annotated with its own type (TS2741: Property 'objectName' is missing).

.changeset/7322-object-kanban-component-props.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    Inside ObjectKanban.tsx, three schema-key reads drop their as any: titleField (two sites, now honest because the object-kanban arm declares it) and cardFields / cardTitle (already declared; the casts were redundant). (schema as any).navigation stays — navigation is declared on neither face, so removing the cast would change nothing but the spelling of an index-signature read.

.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.

  • names packages/plugin-kanban/src/ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    What was measured, on this branch's base (53ded82b). packages/plugin-kanban/src/ObjectKanban.tsx reads schema.groupBy at thirteen sites (lane materialisation at :601 / :625 / :640, card moves at :747 / :865, and their effect deps) and schema.limit at two (:264, $top: schema.limit ?? DEFAULT_KANBAN_LIMIT, and the effect deps at :291). groupField has ZERO read sites anywhere under packages/plugin-kanban/ — against a control of those thirteen groupBy reads in the same query, so the zero is a reading, not a blind grep. Yet the declaration REQUIRED groupField and declared neither groupBy nor limit. Measured from source: the documented, tested, working shape — { type: 'object-kanban', objectName, groupBy, limit } — failed ObjectKanbanSchema.safeParse and safeValidateSchema on the missing groupField, and only ever reached the renderer through BaseSchema's [key: string]: any and .passthrough(), admitted unexamined. An author who followed the declaration wrote groupField and got a board that grouped nothing, with no diagnostic on either face.

.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/7499-gantt-non-axis-floors-omitted.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.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 (objectui#6939, 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/7712-kanban-calendar-filter-input.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    ObjectKanban.tsx sends the authored key to the query as $filter: schema.filter and ObjectCalendar.tsx does the same, and @objectstack/spec's ComponentPropsMap declares filter on both blocks (measured: safeParse accepts it, and refuses an undeclared key by name on the same call). But none of the four registrations that publish those two renderers listed filter in inputs, and sdui-parser's validateTree reports unknown-prop for every key no inputs entry claims. So an author writing the one spelling that WORKS was told it was unknown — objectui#6678's shape, where a correct write draws the same diagnostic as a write that does nothing. That is worse than an inert key: it actively punishes the correct behaviour, and the honest response to it is to delete working metadata.

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectKanban.tsx sends the authored key to the query as $filter: schema.filter and ObjectCalendar.tsx does the same, and @objectstack/spec's ComponentPropsMap declares filter on both blocks (measured: safeParse accepts it, and refuses an undeclared key by name on the same call). But none of the four registrations that publish those two renderers listed filter in inputs, and sdui-parser's validateTree reports unknown-prop for every key no inputs entry claims. So an author writing the one spelling that WORKS was told it was unknown — objectui#6678's shape, where a correct write draws the same diagnostic as a write that does nothing. That is worse than an inert key: it actively punishes the correct behaviour, and the honest response to it is to delete working metadata.

.changeset/7772-page-block-kanban-group-by-control.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    What was measured, on this branch's base (0c8dbc49). BLOCK_CONFIG['object-kanban'] offered exactly four controls — objectName / groupField / titleField / cardFields. ObjectKanbanSchema.groupBy is REQUIRED on both published faces (objectql.ts:2765 groupBy: string, no ?; objectql.zod.ts:1001 groupBy: z.string(), no .optional()) and had no control at all, while groupField — the only control able to set grouping — has been a retirementTombstone() on the Zod face and ?: never on the TS face since objectui#7322. ObjectKanban.tsx reads schema.groupBy at thirteen sites and groupField at zero (control: those same thirteen hits in the one query, so the zero is a reading).

.changeset/7773-kanban-adapter-groupfield-write.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    The object-kanban renderer never read it: groupField has ZERO hits anywhere under packages/plugin-kanban/, against a control of thirteen schema.groupBy read sites in ObjectKanban.tsx from the same query — so the zero is a reading, not a blind grep. The write was inert; the board grouped by groupBy and groupField rode along unread.

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

.changeset/7780-object-kanban-record-source.md

  • names packages/plugin-kanban/src/ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    packages/plugin-kanban/src/ObjectKanban.tsx resolves a board's rows in four steps: the pre-fetched data PROP a parent passes (hasExternalData), then bind through useDataScope(schema.bind), then the inline ROW ARRAY on schema.data, and only then a fetch keyed by schema.objectName — rawData = (hasExternalData ? externalData : undefined) || boundData || schema.data || fetchedData, with the fetch itself gated on schema.objectName && !boundData && !schema.data. Every objectName read is guarded. Both published faces nevertheless REQUIRED objectName, so a bind-only or data-only board — one that renders correctly today — was refused by the shipped validator and could not be annotated with its own type.

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    groupBy is untouched and stays REQUIRED (objectui#7322, PR feat(types): declare ObjectKanbanSchema.groupBy and .limit, retire groupField on both faces (objectui#7322 item 1) #7774). A record source and a lane key are different questions: every lane-less document above is still refused at groupBy, and the two readings PR feat(types): declare ObjectKanbanSchema.groupBy and .limit, retire groupField on both faces (objectui#7322 item 1) #7774 excluded from counting as a lane-less mode — the dataSource json fragment in content/docs/utilities/data-objectstack.mdx and ListView.tsx's runtime-generated node — are asserted still-refused in the new pin. The retired groupField tombstone is likewise still refused by name.

.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 (objectui#7804, 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.

.changeset/7912-schema-renderer-datasource-contract.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Five as any reads of this context in @object-ui/fields are gone — they were redundant the moment the seam became honest — and LookupField's local re-declaration of the imported context as a Context of any, which laundered its dataSource read while looking typed, is gone with them. Both directions of the contract are pinned against the real compiler in SchemaRendererContext.dataSourceType.pin.test.ts, and the card's planted documentation probe (a bare string in packages/react/README.md's provider example) now fails pnpm check:doc-snippets, where it used to exit 0 with zero diagnostics.

.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/8171-calendar-sort-input.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    objectui#7712's defect, one key over. ObjectCalendar.tsx lowers the authored key onto its own query as $orderby: convertSortToQueryParams(schema.sort), and @objectstack/spec's ComponentPropsMap['object-calendar'] declares sort (measured on 17.2.0: safeParse({ objectName, sort }) returns success: true, while the same strict schema on the same call refuses bogusProp by name — that control is what makes the acceptance a verdict). But neither of the two registrations that publish this renderer — plugin-calendar:object-calendar and view:calendar — listed sort in inputs, and sdui-parser's validateTree reports unknown-prop for every key no inputs entry claims. So an author writing the one spelling that WORKS was told it was unknown — objectui#6678's shape, where a correct write draws the same diagnostic as a write that does nothing.

.changeset/8174-kanban-calendar-filter-sort.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    filter had four declaration faces and only three of them named it: @objectstack/spec declares it (ComponentPropsMap['object-kanban'] and ['object-calendar']), both plugins' registration inputs publish it, and both renderers read it — ObjectKanban.tsx lowers schema.filter onto $filter, ObjectCalendar.tsx lowers schema.filter onto $filter and schema.sort onto $orderby through convertSortToQueryParams. This package's own published faces (the TypeScript interface and its zod mirror) named none of them, so an authored value reached the renderer only through BaseSchema's index signature and the mirror's .passthrough() — admitted, never examined. That is the same reasoning finding(types,plugin-kanban): ObjectKanbanSchema requires groupField (zero read sites) and declares neither groupBy nor limit — no working object-kanban node is assignable to any declared type #7322 used to move groupBy and limit into this same interface.

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    filter had four declaration faces and only three of them named it: @objectstack/spec declares it (ComponentPropsMap['object-kanban'] and ['object-calendar']), both plugins' registration inputs publish it, and both renderers read it — ObjectKanban.tsx lowers schema.filter onto $filter, ObjectCalendar.tsx lowers schema.filter onto $filter and schema.sort onto $orderby through convertSortToQueryParams. This package's own published faces (the TypeScript interface and its zod mirror) named none of them, so an authored value reached the renderer only through BaseSchema's index signature and the mirror's .passthrough() — admitted, never examined. That is the same reasoning finding(types,plugin-kanban): ObjectKanbanSchema requires groupField (zero read sites) and declares neither groupBy nor limit — no working object-kanban node is assignable to any declared type #7322 used to move groupBy and limit into this same interface.

.changeset/8253-export-tree-view-config.md

  • names plugin-tree/src/ObjectTree.tsx → packages/plugin-tree/src/ObjectTree.tsx — edited by this change

    What was wrong. tree is a host-composition-only view type — ruled deliberate on objectui#5321, it is a member of neither ObjectViewSchema.defaultViewType nor NamedListView.type, so the branch runs only when a host passes a views prop. On that path a per-view tree block is read at four sites, and its only description anywhere was a module-local, non-exported interface TreeConfig inside plugin-tree/src/ObjectTree.tsx. The live host is the console: it stores view records and passes them as views, and its create-view dialog offers tree. So a real consumer wrote this block with no type to write it against, and a misspelled parentFeild was admitted by the views entry's [key: string]: any, stored, read by nobody and reported by nothing. Declared ≠ enforced on a surface a non-author re-writes.

.changeset/8284-content-channel-per-component.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

.changeset/8466-calendar-color-allday-fields.md

  • names ObjectCalendar.tsx → packages/plugin-calendar/src/ObjectCalendar.tsx — edited by this change

    ObjectCalendar.tsx's getCalendarConfig reads FIVE flat keys off the node, and packages/plugin-calendar/README.md teaches all five in one sentence — "point titleField / startDateField / endDateField / allDayField / colorField at your own fields when they differ." Only three of the five were declared. The other two reached the renderer through BaseSchema's [key: string]: any on the TypeScript face and its .passthrough() on the zod mirror: admitted, never examined. A misspelling therefore left the calendar silently colourless while every published gate passed.

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

.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/8827-kanban-empty-state-settled.md

  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    A genuinely empty board still paints DataEmptyState. The reverse regression — "gating on a truthy definition would leave those boards empty forever", named in ObjectKanban.tsx itself — is what the settle contract exists to prevent, and every exit settles: the query succeeding, the query throwing, no readable source, the non-fetch record sources (external, bound and inline data are settled from the first frame), and the schema-only kanban-ui entry, which has no ObjectKanban and therefore no provider and so takes the context's settled default. Lanes, headers, counts and drop targets keep rendering while the rows are in flight; only the claim is withheld.

.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.

  • names packages/plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • packages/plugin-list/src/ListView.tsx generates the node as groupBy: laneField. objectDef loads asynchronously, so laneField is undefined on every load until it lands, and stays undefined whenever the object offers no stageField hint and none of status / stage / state / phase. The renderer serves that node; both published faces refused it. - content/docs/utilities/data-objectstack.mdx documents an object-kanban node that is exactly { type, dataSource }, with no groupBy. ⚠️ This one is weaker and is cited for what it is: that fragment is still refused after this change, at RECORD_SOURCE_REQUIRED, because dataSource is not a rung of the record-source ladder. It shows a lane-less board is a documented authoring; it is not a document this change admits.
  • names ObjectKanban.tsx → packages/plugin-kanban/src/ObjectKanban.tsx — edited by this change

    What a lane-less board does, measured rather than assumed. Every schema.groupBy read in ObjectKanban.tsx is a guarded early-return, so the board degrades instead of breaking: with no lane key and no columns it renders an empty board; with bare-string columns it draws those lanes, titled by the raw strings; card moves are inert (persistCardMove and the move callback both open if (!groupBy) return). ⚠️ Every lane-less board holds zero cards — bucketCardsIntoColumns returns before distributing records when there is no lane key — so omitting groupBy is not a way to configure a board, it is a board that groups by nothing.

.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/9308-data-root-unbound-from-adapter.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    The Data Context passages of content/docs/guide/schema-rendering.md and packages/react/README.md teach the scope channel accordingly.

.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.

.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/9618-record-source-ladder-attribution.md

  • names ObjectMap.tsx → packages/plugin-map/src/ObjectMap.tsx — edited by this change

    Their zod .describe strings and TS docs said the data → staticData → objectName ladder is resolved by getDataConfig. Neither renderer has had a function by that name since the ladder moved into @object-ui/core's shared resolveRecordSourceConfig (objectui#7632), so the text now names that function. The ladder order is unchanged, and so is every accepted document. The ObjectMapSchema text still says getDataConfig, which is true: ObjectMap.tsx keeps a local wrapper by that name that delegates to the shared ladder. The resolveRecordSourceConfig docblock in @object-ui/core now says that the text it quotes is the map faces' text.

.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/list-user-actions-collision-5398.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    The harvest now reads the object block only. Both userActions read sites in ListView.tsx carry a comment naming the collision, and __tests__/ListView.userActionsCollision.test.tsx pins each clause of it: the two shapes, a producer that manufactures the view one, the harvest's blindness to it, and the projection that must keep the object's operand with a toolbar block — or an empty block — present on the view.

.changeset/listview-comment-pair-4559.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Two comment corrections in ListView.tsx (objectui#4559, objectui#4966). No runtime behaviour changes and the emitted bundle is byte-identical; the published .d.ts does change, which is why this is a patch rather than an empty frontmatter.

.changeset/plugin-detail-8937-parent-scope-residue.md

  • names RelatedList.tsx → packages/plugin-detail/src/RelatedList.tsx — edited by this change

    • packages/plugin-detail/README.md (it is in files[], so it ships). It said the node's filter is AND-combined with { [relationshipField]: parentId }, full stop. Since objectui#7299 the parent condition is compiled to match the relationship field's arity, so a multi-valued relationship gets { [relationshipField]: { $contains: parentId } } instead. The paragraph now states both spellings and names the arbiter (@objectstack/spec/data's isMultiValueField). - The claim that the SQL driver decides arity on that same predicate. It does not: driver-sql gates the equality family on its own storage question, which reads multiple as truthy on ANY type. The two rules therefore disagree for a type outside MULTI_CAPABLE_TYPES carrying multiple: true. objectui#9184 moved the arity compiler into @object-ui/core's parent-scope seam and carried the claim with it, so the correction is recorded there — the seam now states the driver's measured rule, records the divergence as a divergence, and points at the upstream card that owns which of the two rules is right (objectstack#17469). RelatedList.tsx's pointer comment and the objectui#7299 test header carried the same sentence and are corrected to match.

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 9c78ebe3a (merge-base with origin/main): 21 file(s) changed outside .changeset/, read against 1559 pending declaration(s) that publish a body (2146 pending in total). · run

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

✅ ACCEPTED — PR objectui#10729 at 507214a74 (objectui#10666, the filter-token sweep)

domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-26T16:16Z. Clause-②: yes: @object-ui/react gains the export useResolvedFilter, which is a minor. The PR says Fixes #10666.

Reviewed: the diff was read at the head.

  • There is one hold, and it lives in @object-ui/react/src/hooks/useResolvedFilter.ts. It is PR objectui#10705's module moved there (git records a rename). The plugin-list copy is deleted, and ListView and ObjectGallery import the shared hook.
  • The 11 census nodes read their own filter through useFilterScope() plus useResolvedFilter: calendar, gantt, kanban, map, timeline, tree, record:related_list, record:line_items, repeater, number and record_picker. For example, ObjectKanban's $filter and its effect dependency list both read queryFilter, never schema.filter.
  • The @object-ui/types docblocks and the zod describes are prose-only corrections.
  • The merge with PR objectui#10725 (objectui#10657) keeps both behaviours: RelatedList stamps the masked column and feeds the held scope filter.

Pins:

Suites and gates:

  • Full suites of the touched packages exit 0.
  • Type-check covers 12/12 packages.
  • ESLint has 0 errors.
  • The changeset, vi-mock, phantom-dep, citation, control-byte, test-path, unreferenced-source and governed-queue gates exit 0.

Seat answers to the report's open questions (technical placement, no escalation):

  1. Handing out the authored value when the resolver changed nothing: keep it (B). A token-free filter sends the same $filter by identity, as before. The deliberate identity pins stay as they are.
  2. plugin-grid's own copy of the hold (useResolvedGridFilters, from fix(plugin-grid,plugin-list): resolve context tokens in a directly authored object-grid or list-view filter (objectui#10607) #10656): this is a follow-up, outside this claim, and draft PR objectui#10278 touches that file. It is noted here; this card does not carry it.

Next: ready + auto-merge once CI is green on this head.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 26, 2026 16:17
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3062.2 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-DV9bOFaE.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.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
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.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 547.82KB 131.15KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.29KB 65.64KB
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.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 81.82KB 22.51KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.05KB 35.61KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 154.90KB 39.55KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
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.66KB 3.50KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 4.74KB 2.25KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant