feat(types): declare ObjectKanbanSchema.groupBy and .limit, retire groupField on both faces (objectui#7322 item 1) - #7774
Conversation
…oupField on both faces The object-kanban renderer reads schema.groupBy (thirteen sites) and schema.limit (the $top row cap); groupField had zero read sites under packages/plugin-kanban yet was the REQUIRED lane key on both published faces, so the documented working node failed validation and a node authored from the declaration grouped nothing, with no diagnostic. - objectql.ts: groupBy: string (required), limit?: number, and a groupField?: never tombstone in the TimelineSchema.timeScale shape; node-local — the view-level kanban.groupField alias is untouched - objectql.zod.ts: groupBy / limit declared and validated; groupField is a retirementTombstone() whose message names groupBy - docs: the row-cap block on plugin-kanban.mdx is annotated ObjectKanbanSchema and imports it from @object-ui/types - fixture: kanban-conditional-formatting.test.ts authors groupBy - pin: object-kanban-group-by-limit-7322.test.ts - changeset: @object-ui/types minor, breaking for authored metadata Card: objectui#7322, item 1 of 2 (item 2 is the plugin's prop type and lands separately). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
…ect-kanban-group-by-limit
…ect-kanban-group-by-limit
✅ Console Performance Budget
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
Size Limits
|
|
Gate re-run on the current head
Still NOT MEASURED locally: Generated by Claude Code |
✅ Console Performance Budget
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
Size Limits
|
|
Generated by Claude Code |
… objectui#7322 landed objectui#7322 (PR #7774, 669d71b) made groupBy the required lane key on ObjectKanbanSchema and retired groupField as a never-typed tombstone. The ts-expect-error control in object-calendar-record-source-7313.test.ts wrote groupField: 'status', so after the merge the literal would miss two members and carry a tombstone violation — the directive would be satisfied by omissions unrelated to objectName and stop saying anything about it. It now supplies groupBy so objectName is again the one member the literal is missing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
Refs #7322 (item ①). Item ② —
ObjectKanbanComponentProps.schemainpackages/plugin-kanban— is not addressed here; it lands indomain:uiafter this is onmain, and #7322 stays open for it.What this does
ObjectKanbanSchemanow declares the two keys theobject-kanbanrenderer reads and retires the one it never read, on BOTH published faces:packages/types/src/objectql.ts)packages/types/src/zod/objectql.zod.ts)groupBygroupBy: string— REQUIREDz.string()limitlimit?: numberz.number().int().positive().optional()groupFieldgroupField?: never—@deprecated RETIRED (objectui#7322) — author groupBy(theTimelineSchema.timeScaleshape, #6355)retirementTombstone('RETIRED (objectui#7322) — groupField is not read by the object-kanban renderer; author groupBy. (The view-level kanban.groupField alias is unaffected.)')Every other member and its docblock is unchanged; member order is the same on both faces. The tombstone stays a MEMBER of the mirror shape, so the parity ratchet's key sets stay equal and an authored
groupFieldis refused BY NAME at its own path (not stripped, not admitted).Plus: the row-cap block on
content/docs/plugins/plugin-kanban.mdxis annotatedconst board: ObjectKanbanSchema = { … }withimport type { ObjectKanbanSchema } from '@object-ui/types'(the page's semicolon-free import style) — triage's completion signal; the one in-repo fixture that authoredgroupFieldon this node now authorsgroupBy; a new pinpackages/types/src/__tests__/object-kanban-group-by-limit-7322.test.ts; a changeset (@object-ui/types: minor, "Breaking for authored metadata").Measured on the branch base (
53ded82b; the two faces are byte-identical on the merge-base tip2760075f)Read sites (
packages/plugin-kanban/src/ObjectKanban.tsx):schema.groupByat 13 sites — lane materialisation:601,:602,:613,:625,:626,:631,:640,:641, deps:649, card moves:747,:837,:865,:895;schema.limitat:264($top: schema.limit ?? DEFAULT_KANBAN_LIMIT,DEFAULT_KANBAN_LIMIT = 100at:71) and:291(deps).groupField: 0 hits underpackages/plugin-kanban/(control: the 13groupByhits in the same query).Before → after, from source, both entry paths (
ObjectKanbanSchema.safeParseandsafeValidateSchema):{ type, objectName, groupBy: 'status', limit: 20 }— the documented shapegroupField(required, missing)groupByandlimitsurvive{ …, groupField: 'status' }— the old declared shapegroupField, message namesgroupBy; and atgroupBy(required){ …, groupField, groupBy }groupField{ …, groupBy: 42 }/{ …, limit: 'twenty' }groupFieldrefusal; under.passthrough()the wrong-typed value was admitted unexaminedgroupBy/ atlimit{ type, objectName }— lane-lessgroupFieldgroupBy{ …, laneField: 42 }— control, a key the renderer does not readBaseSchemauntouched, #5155)groupByrequired-ness measurement (the dispatch's open question): declared REQUIRED. Evidence: the retired contract required a lane field; the renderer'sif (!schema.groupBy)branches at:601/:613are defensive early-returns; the docs page has exactly 1object-kanbannode (:159) and it authorsgroupBy;packages/plugin-kanbantests carry 20object-kanbannode literals, 19 authorgroupBy, and the one that does not (registration.test.tsx:32,{ type: 'object-kanban' }) mocks./ObjectKanbanwholesale and tests only that the renderer wrapper forwardsdataSource— not a lane-less board;ElementDataSourceGate(packages/react) maps a bounddataSource.objectontoobjectNameand never derives a lane key, and its ownobject-kanbancase authorsgroupBy: 'status'next to the binding. Two readings considered and NOT counted as a lane-less mode:content/docs/utilities/data-objectstack.mdx:322is ajsonfragment (not compiled, not validated) that omitsgroupByANDobjectName— an illustration of the binding keys, never a complete node under the old contract either; andListView.tsx:2411can emit a runtime-generated node withgroupBy: undefineduntilobjectDefloads (ADR-0085 then fills it from the object's lifecycle field) — a typed-anyadapter output, not authored metadata. If the PM reads either as a documented lane-less mode,groupBy?: stringis a one-line change on each face plus the two required-ness pins.Corpus rewrite count. Named docs / app corpus (
plugin-kanban.mdx,guide/data-source.md,guide/react-pages.md,utilities/data-objectstack.mdx,apps/console/src/register-plugins.tsand its tests): 2object-kanbannode literals, 0 authorgroupField, 0 rewritten.packages/**test files: 24object-kanbannode literals across 16 files, 1 authorsgroupField(kanban-conditional-formatting.test.ts:22) → rewritten togroupBy; the other 23 untouched.skills/objectui/guides/*: 2 prose mentions, 0 node literals.Node-local. The view-level
groupFieldalias is live and untouched:packages/core/src/utils/normalize-list-view.ts:191,packages/plugin-list/src/ListView.tsx:1462/:1816/:2091,packages/plugin-view/src/ObjectView.tsx:1234, and theKanbanConfig.groupFielddeclaration in the same zod file (objectql.zod.ts:352). The pin reads all four sites plus that declaration off disk as a control.Runtime reach of the tombstone.
SchemaRendererruns@object-ui/core's structuralvalidateSchema(no zod); the zod validator is reached by the CLIvalidate/checkcommands, the VS Code extension and tests. Boards render exactly as before.Parity
Key sets on both faces move together (
groupBy,limitadded;groupFieldkept as a tombstone).WiderThanDeclared(the fifth ledger, #7758) /NarrowerThanDeclared/UnmirroredDeclared:undefinedvsundefined,stringvsstring,number | undefinedvsnumber | undefined— no ledger row needed, none added. Measured on the tip that already carries #7758.Gates (exit captured before any pipe; verdict lines quoted)
pnpm --filter @object-ui/types build(verify lock)d7250015✓ dist completeness: 1 package(s) complete (124 emitted files verified)pnpm exec vitest run --maxWorkers=2 packages/types/(verify lock)d7250015Test Files 122 passed (122)·Tests 2116 passed (2116)(onb99b8ad2, before the merge: 122 / 2109)pnpm exec vitest run --maxWorkers=2 packages/plugin-kanban/(verify lock; the plugin's tests author the node shape)d7250015Test Files 22 passed (22)·Tests 112 passed (112)pnpm --filter @object-ui/types type-check(tsc --noEmit,tsconfig.examples.json,tsconfig.test.json— the last compiles the pin's type-level legs and its two@ts-expect-errorlines)d7250015error TSlinepnpm --filter @object-ui/plugin-kanban type-check, read-only, against the dependency-closure dists (pnpm exec turbo run build --filter='@object-ui/plugin-kanban^...' --concurrency=2under the lock:Tasks: 12 successful, 12 total, 1m15s shared-box)d7250015error TSline — the plugin imports noObjectKanbanSchemaand resolves@object-ui/typesthrough the root tsconfigpathsto SOURCE, so this gate is structurally independent of the diffpnpm --filter @object-ui/types lintd7250015✖ 268 problems (0 errors, 268 warnings)— 0 findings on the two touched test filesnode scripts/check-changeset-presence.mjs·check-changeset-fixed.mjs·check-changeset-no-major.mjsd7250015✅ 4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)·✅ All workspace packages are in the changeset fixed group.·✅ No changeset declares a major bump.node scripts/check-control-bytes.mjsd7250015✅ check-control-bytes: OK (scanned 6329 tracked text file(s); skipped 85 binary)pnpm check:spec-symbolsd7250015✅ spec member citations: no comment cites a key its spec symbol does not declare.pnpm check:doc-types·pnpm check:doc-fences·node scripts/check-doc-links.mjsd7250015✅ Every documented component type is registered.·✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …·Links are valid across 17 scan roots.node scripts/pm/check-governed-merges.mjs --test …(objectstack, the six paths)0 of 6 path(s) hit the register— NOT governedNOT MEASURED locally, declared:
pnpm check:doc-snippets— its--build-filterclosure is 26 packages, which the shared box cannot build inside a foreground turn; CI'sDoc Snippet Type Checkis the measurement. Narrowed measurement in its place: the annotated block, extracted from the page and compiled with the gate's ownCOMPILER_OPTIONS(ES2020 / ESNext / bundler / react-jsx / strict / skipLibCheck /types: []) and itspathsderivation (@object-ui/types→packages/types/dist/index.d.ts, rebuilt from this branch):tsc exit=0; the same block against a dist rebuilt from the BASE faces:TS2741: Property 'groupField' is missing in type … but required in type 'ObjectKanbanSchema'— the card's own diagnostic, reproduced.pnpm --filter @object-ui/console type-check: skipped —apps/consoledoes not author the node (register-plugins.ts:120registers it; the two tests list the type name in string arrays).Ablation (red-first, both faces reverted to
53ded82b, restore proven)Run 1's docs leg was VOID — the probe tsconfig used
baseUrl, which TypeScript 6 refuses (TS5101) before compiling anything — and was re-run; run 2 is the reading. Mutation on disk:objectql.tsblob2714449218…(= BASE, ≠ HEAD1db955cf26…),objectql.zod.tsblob70fec93c61…(= BASE, ≠ HEAD45c5facc52…); anchors:groupField: string;x1,groupField?: neverx0,retirementTombstone('RETIRED (objectui#7322)x0,groupByin the zod block x0. Legs: the pin + fixture + parity from source →vitest exit=1,Test Files 2 failed | 1 passed (3),Tests 18 failed | 33 passed (51)(parity stays green: the reverted faces are a consistent pair);tsc -p tsconfig.test.json→ exit 2, 8 errors — sixTS2344on the type-level pins,TS2741on the documented literal,TS2578unused@ts-expect-error; a dist rebuilt from the reverted faces (marker x0,groupField: stringx1) → the docs block failsTS2741. Restore bygit checkout HEAD -- …undertrap … EXIT INT TERM: blobs equal HEAD (1db955cf26…,45c5facc52…),git diff HEADempty, dist rebuilt from HEAD (markerobjectui#7322x4 inobjectql.d.ts, x2 inzod/objectql.zod.js,groupField?: neverx1).Serial constraints / overlap
2760075f; this branch was fast-forwarded onto it before any commit and the parity suite ran there.exportOptionsreconciliation never landed — zod mirror still takes'pdf'and declares nostreaming, and bothas anyreads survive #6956, theexportOptionsregion ofobjectql.zod.ts): landed at4dfdcc3cwhile this branch was open; merged into the branch asd7250015(a merge commit — no rebase of a pushed branch) before opening, and every gate above was re-run on that head. Different region of the same file; no textual overlap with theObjectKanbanSchemaconst.domain:uiseat's finding(types/plugin-kanban): an authoredkanbandocument can PASSsafeValidateSchemaand still render empty — the validator and the renderer honour different faces #7664 holdspackages/plugin-kanban/**; this PR does not touch that package (read-only: gates only).Out of scope — filed, not touched here
groupField— a key the renderer never reads and, after objectui#7322, the validator refuses by name; nogroupBy/limitcontrol #7772 — app-shell: the page-block designer'sobject-kanbaninspector (block-config.ts:222) writesgroupField(label "Group by field"), a key the renderer never reads and, after this PR, the validator refuses by name; it offers nogroupBy/limitcontrol. Not mechanical (i18n keys en / zh + their pin + a possible strip-on-load for saved pages);domain:ui/ app-shell.groupFieldalongsidegroupByonto the generated object-kanban node — an inert duplicate write held in place by two pins #7773 — plugin-view / plugin-list:ObjectView.tsx:1283andListView.tsx:2416writegroupField: groupByonto the generatedobject-kanbannode (inert — the renderer readsgroupBy; the node isany-typed and never reaches the zod validator at runtime), held in place by two pins. Unblocked; the alias READS stay untouched.Not in this PR by dispatch: item ② (
packages/plugin-kanban/**), the view-level alias sites,BaseSchema's index signature (#5155), the declarativeKanbanSchema.Angle-bracket shapes are spelled out in words throughout this body because GitHub's sanitizer strips tag-shaped fragments (see AGENTS.md).
Generated by Claude Code
Generated by Claude Code