Skip to content

feat(types): declare the filter/sort members both renderers read (objectui#8174) - #8788

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-8174-kanban-calendar-declare-filter-sort
Sep 9, 2026
Merged

os-bill merged 3 commits into
mainfrom
claude/issue-8174-kanban-calendar-declare-filter-sort

Conversation

@os-bill

@os-bill os-bill commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8174

ObjectKanbanSchema gains filter. ObjectCalendarSchema gains filter and sort. Both published faces of @object-ui/types — the TypeScript interface in packages/types/src/objectql.ts and its hand-written zod mirror in packages/types/src/zod/objectql.zod.ts.

Re-measured on fb0102271, not inherited from the card's 9bfd618

main moved a great deal between the filing and this branch, so every anchor below was re-derived; the card's :2744 / :2698 line addresses no longer hold.

Member lists, pulled from the interface bodies — the full list per interface is its own control that the extraction works:

  • ObjectKanbanSchema (objectql.ts:2779-2900), 11 members: type, objectName?, groupBy, groupField?, limit?, titleField?, cardFields?, quickAdd?, coverImageField?, allowCollapse?, conditionalFormatting? — no filter, no sort.
  • ObjectCalendarSchema (objectql.ts:2733-2774), 8 members: type, objectName?, data?, staticData?, startDateField?, endDateField?, titleField?, defaultView? — no filter, no sort.

Read census — schema.KEY occurrences off disk, with objectName as the positive control that the query reaches the file:

file schema.filter schema.sort control schema.objectName
plugin-kanban/src/ObjectKanban.tsx 2 0 26
plugin-calendar/src/ObjectCalendar.tsx 7 (2 live, 5 inside a docblock) 2 19

The live sites: $filter: schema.filter on the kanban dataSource.find and again in that effect's dependency list; $filter: schema.filter and $orderby: convertSortToQueryParams(schema.sort) on the calendar's, and both again in its dependency list.

The other two faces, re-read rather than quoted: @objectstack/spec 17.3.0's ComponentPropsMap declares filter on object-kanban (13 keys) and filter plus sort on object-calendar (9 keys), and declares no sort on object-kanban. Both plugins' registration inputs publish the same set — plugin-kanban/src/index.tsx:523, plugin-calendar/src/index.tsx:400-401, all type: 'array'.

So the key had four declaration faces and this package's two were the silent ones. An authored value reached the renderer through BaseSchema's [key: string]: any (base.ts:467, the interface's last member) and through the mirror's .passthrough() — admitted, never examined. That is verbatim objectui#7322's reasoning for moving groupBy and limit into this same interface, one key over.

No sort on the board, and the absence is measured rather than overlooked: zero schema.sort read sites in ObjectKanban.tsx, and the spec declares none either. Declaring it would be this repo inventing a key.

The judgement the card delegated to the taker

The card asks whether this is worth the edit at all, or whether it should wait behind objectui#7927 — which measured that BaseSchema ends in an index signature, so no annotation on any node schema catches a MISSPELLED key.

Proceeding. The ceiling is real, and it caps a different dimension from the one this buys.

  1. objectui#7927 caps the KEY dimension. A misspelling stays admitted on both faces either way, and this branch pins that rather than claiming otherwise.
  2. What it does not cap is the VALUE dimension. filter: 'status = open' type-checked green through the index signature and parsed green through .passthrough(); it is now a type error and a named refusal. That is a measurable acquisition, not editor completion.
  3. The sort half is the sharpest case, and it is a live silent failure. objectui#8221 retired the legacy string clause: convertSortToQueryParams no longer admits a string in its signature and returns undefined for one after reporting the retired spelling (core/src/utils/sort-query.ts:133,141). So sort: 'start_date asc' on an object-calendar node type-checked green, parsed green, and then drew an UNSORTED calendar with nothing refusing it anywhere. Declaring the member is what makes that retirement audible at the authoring boundary.
  4. Ordering runs the opposite way from "wait behind it". If objectui#7927 ever tightens BaseSchema, it tightens the KEY dimension — and a tightening that lands while these three members are still undeclared turns every correctly authored filter / sort node into a refusal. Declaring them is a prerequisite for that card, not a duplicate of it.
  5. The precedent settles the rest: objectui#7322 weighed this same trade on this same interface under this same ceiling and went ahead.

The pin, and the ablation that shows it can fail

packages/types/src/__tests__/kanban-calendar-filter-sort-8174.test.ts asserts membership on the mirror's own .shape rather than on parse acceptance — under .passthrough() acceptance cannot tell "declared" from "admitted unexamined". Type-level pins use invariant equality, so a member that fell back to the index signature reads as any and therefore as a failure. A control key must stay undeclared on both faces, and a misspelling must stay admitted.

That file IS compiled. packages/types' package tsconfig excludes the test tree, but type-check runs a third program, tsconfig.test.json, that includes it — verified with --listFiles rather than assumed: the pin is line 413 of that program's file list and absent from the main program's.

Three ablation legs. Each proved the mutation reached disk before its run was read, and each restored, with the restore proved by an empty git diff HEAD and a hash equal to the HEAD blob:

leg mutation measured
1 delete ObjectCalendarSchema.sort from objectql.ts (anchor count 4 to 3) type-check exit 2, 5 errors: 3 x TS2344 on the invariant-equality pins, 2 x TS2578 unused @ts-expect-error (the retired string clause, and the bad order value)
2 delete sort from the calendar mirror (anchor count 4 to 3) vitest exit 1, 4 failures: shape membership, plus the three refusals at the key
3 same mutation as leg 2, judged by tsc instead type-check exit 2, one error: zod-mirror-parity.test.ts TS2322, that pair not assignable to never

Leg 3 is the one worth reporting. Leg 2 alone would have read as "the mirror half is covered only by my own new file", because the parity ledger stayed green under vitest. It is not: the ledger's unmirrored-declared half is a TYPE-level operator, so it lands on tsc and not on the test runner. The mirror edit is mandatory rather than a courtesy — and mandatory in a way a vitest-only run cannot see.

A first attempt at leg 1 was a no-op: the slice anchor was off by one newline, nothing changed on disk, and the script's own before/after count refused to run the leg. Reported because the failure mode it caught is exactly the one that otherwise reads as a passing ablation.

Nothing in the parity ledger needed editing: mirroring at the same requiredness as the interface — both optional on both faces — leaves it at zero drift for these pairs. __tests__/zod-mirror-parity.test.ts is untouched, as are zod/layout.zod.ts, zod/form.zod.ts, layout.ts and form.ts, all held by PR objectui#8763.

Gates

Run in this worktree at a9c30cb, on a merge of origin/main fb0102271. Exit codes captured by redirecting first and reading the status after, never through a pipe.

command exit
pnpm --filter @object-ui/types type-check (all three programs) 0
pnpm exec vitest run packages/types/ from the repo root 0 — 156 files, 3091 tests
pnpm exec vitest run over plugin-kanban, plugin-calendar, the console registry/spec parity file and the two app-shell block-config files 0 — 70 files, 711 tests
pnpm --filter @object-ui/types build 0 — dist completeness, 128 emitted files
pnpm --filter @object-ui/types lint 0 — 271 pre-existing warnings, 0 errors
node scripts/check-changeset-fixed.mjs 0
node scripts/check-changeset-no-major.mjs 0
node scripts/check-control-bytes.mjs 0
node scripts/check-spec-symbol-derivation.mjs 0
node scripts/check-doc-component-types.mjs 0
node scripts/check-unreferenced-sources.mjs 0
node scripts/check-element-data-source-declaration.mjs 0
node scripts/check-governed-queue-guard.mjs --test over the four changed paths 0 — NOT GOVERNED

Four whole-tree scans returned a prerequisite complaint instead of a verdict, because they need every package built and this worktree builds only the affected one: check:doc-snippets, check:doc-examples and check:sdui-registration-pins (exit 2 each, each printing the build it wants), and check:readme-exports (exit 1, its own message being "run pnpm build first" for five documented types across four packages, followed by a collapsed-population report). Those read here as NOT MEASURED, not as green and not as red; they belong to CI, which builds the tree.

Scope

Additive only: 4 files, +486 / −0, all inside the claimed surface. The changeset is minor for @object-ui/types — the accept set only widens, but a wrong-typed value at a correctly spelled key changes verdict, which is more than a patch. major is refused by check-changeset-no-major, and skip-changeset does not apply because packages/types ships these declarations.

BaseSchema's index signature is untouched — that is objectui#7927. No other undeclared member on these two interfaces was touched: objectui#7742 and objectui#7780 are censuses over the same two interfaces on different keys, and they are not this card.

Merge posture

DRAFT, and it stays draft. Clause-② applies — this adds declared members to a published interface — so it carries needs:contract-review and a ceiling review runs before it may be enqueued. Not flipped ready, not enqueued, no auto-merge.

Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi


Generated by Claude Code

…ectui#8174)

`ObjectKanbanSchema` declared no `filter`; `ObjectCalendarSchema` declared
neither `filter` nor `sort` — while `@objectstack/spec` declares them
(`ComponentPropsMap['object-kanban']` carries `filter`,
`['object-calendar']` carries `filter` and `sort`), both plugins'
registration `inputs` publish them (`plugin-kanban/src/index.tsx:523`,
`plugin-calendar/src/index.tsx:400-401`, all `type: 'array'`), and both
renderers read them (`ObjectKanban.tsx` `$filter: schema.filter`;
`ObjectCalendar.tsx` `$filter: schema.filter` and
`$orderby: convertSortToQueryParams(schema.sort)`). The declaration face of
this package was the only one that stayed silent, so an authored value
reached the renderer through `BaseSchema`'s `[key: string]: any`
(`base.ts:467`) and the mirror's `.passthrough()` — admitted, never
examined. That is verbatim objectui#7322's reasoning for moving `groupBy`
and `limit` into this same interface.

Spelled exactly as `ObjectGanttSchema` spells them (`filter?: any[]`,
`sort?: SortConfig[]`, and the matching `z.array(z.any())` /
`z.array(SortConfigSchema)` mirrors) so the views' query vocabularies
cannot fork. Both members optional on both faces, so the mirrors stay at
zero drift for these pairs.

No `sort` on the kanban board, and the absence is measured rather than
overlooked: `ObjectKanban.tsx` has ZERO `schema.sort` read sites and the
spec's `object-kanban` entry declares no `sort` either.

objectui#7927's ceiling is unchanged and is pinned rather than claimed
away: a MISSPELLED key still rides the index signature on both faces. What
declaring buys is the VALUE dimension — `filter: 'status = open'` and the
objectui#8221-retired `sort: 'name asc'` move from "type-checks green,
parses green, then silently drops at runtime" to refused at authoring time.

Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi

Co-authored-by: Claude <noreply@anthropic.com>
…ban-calendar-declare-filter-sort

# Conflicts:
#	packages/types/src/zod/objectql.zod.ts
Additive on the type face and on the accept set; what changes verdict is a
wrong-typed value at a correctly spelled key, which the index signature and
`.passthrough()` used to admit unexamined.

Claude-Session: https://claude.ai/code/session_012W3vMLTFY9SPr2LyxhSeYi

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.2 KB 3512.7 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-DYFWoIYP.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) 15.67KB 5.75KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
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) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 26.68KB 8.94KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 245.88KB 62.05KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
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.39KB 3.10KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.39KB 65.32KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.25KB 33.52KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 210.67KB 57.19KB
plugin-kanban (index.js) 55.85KB 15.86KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.54KB 20.84KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
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) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 14.27KB 5.47KB
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

@os-bill
os-bill marked this pull request as ready for review September 9, 2026 07:51
@os-bill
os-bill added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit ee4d19f Sep 9, 2026
35 checks passed
@os-bill
os-bill deleted the claude/issue-8174-kanban-calendar-declare-filter-sort branch September 9, 2026 08:09
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.

types: ObjectKanbanSchema and ObjectCalendarSchema declare no filter (and no sort) — the fourth face of the key #7712 declares everywhere else

2 participants