feat(types,plugin-kanban)!: object-kanban's conditionalFormatting takes the spec list view's { condition, style } rule only; the native and flat-colour dialects are refused by name (objectui#11522) - #11532
Conversation
…st view's { condition, style } rule only; the native and flat-colour rule dialects are refused by name (objectui#11522)
KanbanConditionalFormattingRuleSchema is the spec ListViewSchema rule element
by reference (.extend()), with the list view's own condition arm and the six
retired keys (field, operator, value, backgroundColor, borderColor, textColor)
as retirement tombstones. The TS twin extends SpecConditionalFormattingRule
with the same keys as `?: never`; KanbanNativeConditionalFormattingRule is
removed. The types pin is turned around to pin both refusals on all three
zod faces.
Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
… { condition, style } rule; the kanban fixtures are respelled (objectui#11522)
The registry input description for conditionalFormatting no longer teaches
the native or flat-colour rule. The three plugin-kanban fixtures that authored
a retired dialect write the same predicate and paint as { condition, style }
and assert the same card is styled; a row pins that the whole style map
reaches the card. The schema-reference row, the console member-pin prose and
the one-authority test's note on the retired type name follow.
Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
… on the two pending entries it makes false (objectui#11522) Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
… to { condition, style } (objectui#11522)
Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
Contract reviewServed-tier: Inputs: card #11522 (body, comments Check-runs on the head: first read 2026-10-03T02:56:10Z, 42 runs, 37 ① Derived judgmentsAccept-set changes the diff implies, each judged against the head sources:
② Semver levelChangeset Clause-②: no (narrowing). RIGHT. Every face moves one way: two dialects refused, undeclared keys refused, nothing newly accepted; Pending sweep (read, not recalled). Of 2,695 entries, the readings this change makes false are exactly two, and both carry a dated, append-only note (
③ Boundary flags
Dev deviations: (1) Out-of-scope findings, each answered: (a) Boundary note, each item: Dev NOT MEASURED items: hotcrm, accepted on triage's own zero ( What the FAIL asks for, and nothing else: in Implemented-by: VERDICT: FAIL |
…ocus, a module export of src/zod/objectql.zod.ts that is not on the @object-ui/types/zod barrel (objectui#11522) The new changeset's @object-ui/types bullet and the dated note on the 7664 entry both placed the schema on `@object-ui/types/zod`. At this head that barrel does not re-export it and no `exports` entry carries it; the note now says so, and records that the 7664 bullet's own locus claim does not hold in this release either. Text only; no export added. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Round 2, re-review of round 1's record Check-runs on this head: first read 2026-10-03T03:15:20Z, 42 runs, 27 ① Derived judgments
② Semver level
Clause-②: no (narrowing), unchanged in the PR body, and a text-only commit widens nothing. RIGHT. The 7664 note stays append-only. The hunk is ③ Boundary flagsPatch report Carried from round 1, unchanged and not defects of this PR: escalation (b), the grid's native arm against objectstack main's by-reference typing, owed a Implemented-by: VERDICT: PASS |
Fixes #11522
Clause-②: no (narrowing)
object-kanban'sconditionalFormattingtakes one rule dialect, the spec list view's{ condition, style }. The native{ field, operator, value, backgroundColor, borderColor }rule and the flat CEL rule (a colour written besideconditioninstead of insidestyle) are retired with no alias window and refused by name. This executes triage's ruling5963861071(retire, not widen). objectstack-ai/objectstack#21464 can then type the member by reference to its list-view member.What changes
object-kanban6903eafb){ condition, style }(string, envelope or''condition){ field, operator, value, backgroundColor }field,operator,valueandbackgroundColor, each naming the retirement{ condition, backgroundColor }invalid_unionat the rulebackgroundColor(named) andstyle(required){ condition, style, backgroundColor }style, aslistConditional.test.tspins); strict face refused it asinvalid_unionbackgroundColoron every face{ condition, style, label }unrecognized_keyswith the spec rule's own messageThe three faces are
ObjectKanbanSchema,safeValidateSchema(tolerant) andStrictAnyComponentSchema(strict). The before and after columns were read with a throwaway probe on each tree. The probe was deleted and never committed.@object-ui/types,zod/objectql.zod.ts).KanbanConditionalFormattingRuleSchemais the specListViewSchema.conditionalFormattingelement, taken throughstripImportedDefaultsand.extend()-ed. It is no longer a union. It inherits the spec rule's strictness and itsstylerecord. Two things are layered on top. First,conditionisSpecRuleConditionSchema, the list view's and the grid's own condition arm (objectui#10946), so a string condition is still not canonicalized and''is still accepted. Second, six retirement tombstones:field,operator,value,backgroundColor,borderColor,textColor.objectql.ts).KanbanConditionalFormattingRuleis an interface that extendsSpecConditionalFormattingRuleand declares the same six keys?: never.KanbanNativeConditionalFormattingRuleis deleted and dropped from the barrel (TS2305 for an importer). tsc reports a retired key by name (TS2322 at each key).@object-ui/plugin-kanban. The registration'sconditionalFormattingdescription no longer teaches the native or flat-colour rule.KanbanImpl's comments say what did and did not narrow. No render path changed: the description string is the only non-comment line.object-kanbanrow incontent/docs/api/schema-reference.mddocuments the one rule and its respelling.11522-kanban-rule-dialect-retired.md):typesandplugin-kanbanminor, with BREAKING and FROM/TO spelled out.Mechanism hypotheses, measured
H1, the census (writers of each retired dialect). The census used three instruments, and each one caught something the others missed. (1) Rule objects inside a
conditionalFormatting: [array. (2) Every object literal carrying a top-level colour key, classified by its other keys. (3) Every rule-shaped object within 400 characters after anyconditionalFormattingtoken, in any syntax, which catches tuples andkey:/value:rows. A fourth pass listed every{ field, operator }literal in files that mention bothconditionalFormattingandkanban. Each remaining hit was triaged by hand as a filter rule, a grid, list-view or report carrier, or a resolver unit test.object-kanbanat BASEpackages/types/src/__tests__/kanban-conditional-formatting.test.tspackages/types/src/__tests__/object-kanban-allow-collapse-retired-8801.test.ts{ condition: "record.status == 'open'", style: { backgroundColor: '#fee2e2' } }packages/plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx{ condition: "record.owner == 'ann'", style: { backgroundColor: 'rgb(1, 2, 3)' } }and{ condition: "record.owner == 'bob'", style: { backgroundColor: 'rgb(4, 5, 6)' } }, plus one new row for the wholestylemappackages/plugin-kanban/src/__tests__/structuredKeysAreDeclaredAndHonoured-8313.test.ts{ condition: "record.owner == 'ann'", style: { backgroundColor: '#eef' } }packages/plugin-kanban/src/__tests__/objectFieldsIsAPropNotASchemaKey-7742.test.tsx{ condition: "record.owner == 'u1'", style: { backgroundColor: PAINT } }packages/plugin-kanban/src/index.tsx(registration description)content/docs/api/schema-reference.mdapps/console/src/__tests__/registry-inputs-spec-parity.test.ts(member-pin prose)examples/,apps/consolenon-test code,skills/object-kanbanin 4examples/files,kanbanin 9skills/files)examples/atf9a8eb88conditionalFormatting(app-showcasefield-zoo.view.ts) is{ condition, style }on a list view. Controls fire:kanbanhits inapp-crmviews, and{ field, operator, value }filter literals match the same matcherobjectstack-ai/hotcrmH2, by reference. The installed
@objectstack/spec17.5.0 exports no named rule schema. The rule is reachable asListViewSchema.shape.conditionalFormatting.unwrap().element, a strict object ofconditionandstyle. The zod twin.extend()s that element throughstripImportedDefaults. It does not override the condition with the bare spec slot, because the spec slot canonicalizes a string condition into an envelope and refuses''. The 10946 pins assert both behaviours onobject-kanban. The TS twin isSpecConditionalFormattingRule, which already indexesObjectListViewSchema's slot by reference. It is extended, not restated. A new pin assertsshape.styleis the spec element's ownstyle, by identity. Its control is thatshape.conditionis not the bare slot.H3, the refusal. With one arm, zod reports at the retired key's own path, and no
invalid_unionremains at the rule. The messages are identical onsafeValidateSchemaandStrictAnyComponentSchema. They are shown below, with backticks spelled as in the source:conditionalFormatting.N.field(and the same at.operatorand.value, with the key name swapped):conditionalFormatting.N.backgroundColor(borderColorandtextColorare the same;textColorsaysstyle: { color }):invalid_unionat.conditionandinvalid_typeat.style(both required), and a flat CEL rule drawsinvalid_typeat.style. An undeclared key draws the spec rule's ownunrecognized_keysmessage. Forexpressionthat message adds "Did you meanexpression→condition?".The
retirementTombstonehelper is the one used.aliasKeyRefusaldoes not fit: these keys are a retired dialect, not aliases of one canonical key.H4, the resolver. Every arm of
resolveConditionalFormattingstill has a live authorable carrier. This was measured on the builtsafeValidateSchemaandObjectGridSchema:object-gridnode (propertiesbag)ObjectGridSchema(viewtableslot / renderer props)object-viewtableslotlist-viewnodeobject-kanbannodecondition+styleexpressionfield/operator/valuebackgroundColor/textColor/borderColoroverridesNo arm has a measured zero carrier, so
listConditional.tsis untouched.H5, the render. A throwaway probe drew each rule through the real
SchemaRenderer→object-kanbanboard, and throughKanbanRendererwith and withoutobjectFields. It ran at BASE and again after the change. The two readings are byte-identical:Each respelling paints the card the retired rule painted, with the same style string. The respelled 8313 and 7742 rows pin this permanently through the real board, each with the live non-matching control.
The pins
kanban-conditional-formatting.test.ts(turned around). For each of the three faces, it pins: the{ condition, style }control; the native rule refused at each retired key, with the message starting with the key's name and containingRETIRED (objectui#11522)and{ condition, style }; the accepted rule at index 0 of the same document drawing no issue; the flat CEL rule refused atbackgroundColorwith thestyle: { backgroundColor }respelling and noinvalid_unionat the rule; and all three colour keys refused beside astyle. It also pins the identity ofstylewith the spec element (with its control) and the inherited strictness. On the TS face,@ts-expect-errorcovers each retired key,keyofequality holds between the two faces, andconditionandstyletypes are equal.spec-expression-wire-slots-10946.test.tsreadsconditionstraight off the rule's shape. There is no union arm left to index.zod-mirror-parity.test.ts: theKanbanConditionalFormattingRuleSchemaexclusion reason is rewritten. Its old reason ("a union of two rule dialects … the'kanban'arm") was false twice over. It stays an exclusion, compared where it is pinned, the wayExpressionWireSchemais.Pending changesets
10946-expression-wire-slots-by-reference.md. Its sentence "the list view's and the kanban board's rule unions share oneconditionschema" describes the kanban union.7664-kanban-arm-plugin-dialect.md. Its sentence "KanbanConditionalFormattingRuleSchemais … the rule union the'object-kanban'arm already applied" is now false.7664-plugin-kanban-declared-schema.md: says nothing about rules.8932-retire-kanban-enhanced.md: only namesKanbanConditionalFormattingRuleas the type to use, which still exists.8313-kanban-structured-authoring-keys.md: the key is still declared and honoured, and its description still states what the board reads.7727-conditional-formatting-record-scope.md: aboutrecord.*conditions.7928-listviews-by-reference-fold.md,9242-stray-kanban-groupby-lane-second-route.md: about named-view kanban config keys.8801-object-kanban-allow-collapse-retired.md:conditionalFormattingis still a live control key inplugin-kanban.7742-kanban-arm-batch70.md: aboutobjectFields.7322-object-kanban-group-by-limit.md: its "it now authorsgroupBy" remark about the kanban test still holds.6349,8165and8261: they name the one-authority gate, whose behaviour did not change; only its comments did.10275, names kanban only as a view binding.check:changeset-overwritereports the two modified entries with their declarations unchanged (types: minor), which is the appended-note case.Verification (head
6d3b4da0)pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-kanban/ apps/console/src/__tests__/registry-inputs-spec-parity.test.ts scripts/__tests__/one-authority-per-exported-name-6273.test.ts: 411 files, 9878 tests passed.VERDICT command-exit 0.pnpm --filter @object-ui/types run type-check(src, examples and test configs): exit 0. Before the types pin was turned around, the same check reported TS2322 at the four retired keys of its old native literal.pnpm --filter @object-ui/plugin-kanban run type-check: exit 0, afterpnpm --filter '@object-ui/plugin-kanban^...' build.turbo run build(35/35 tasks):check:doc-snippets: 777 of 777 blocks, 0 failed.check:doc-examples: exit 0.check:doc-types,check:readme-exports: OK.check:sdui-registration-pinsandcheck:component-surface-parity(report-only, no kanban row): both green.check:new-line-citations(0 new),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check:spec-symbols,check:doc-fences,check-changeset-no-major,check-changeset-presence,check-changeset-fixed,check-doc-expression-carriage,check-type-check-coverage.main's spec; it belongs to CI). Specmaindeclares the rule with the samestrictObjecthelper and no refinements (read atf9a8eb88), so.extend()is expected to hold there. The repo-wide lint is also left to CI.Acceptance notes
ObjectView's kanban branch relays a named or active view'sconditionalFormatting, and objectui'sListViewSchemamember still declares the native rule ("broader than spec, migration deferred", per its own docblock). The generatedobject-kanbannode can therefore carry a rule the authoredobject-kanbancontract now refuses. It is never validated, which is the same shape as thegroupFieldnote in that branch. Carrier: none.main, the object-grid block is typed by reference to the list view (objectstack PR #21463, per the card). objectui's gridpropertiesbag andListViewSchemastill declare the native arm, and the installed 17.5.0 row isz.unknown()for bothobject-gridandobject-kanban. The grid's native arm meets the same disagreement on a spec bump. Carrier: none named.skills/objectui/guides/schema-expressions.mdlists "the native{ field, operator, value }form … still work" under renderer-side facts for "list/grid/kanban". That is true of the evaluator and no longer ofobject-kanbanauthoring.skills/**is governed, and this PR stays ungoverned, so it is left. Carrier: none.Session:
https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPCGenerated by Claude Code