Repository navigation
Commit c73cdb5
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)
Fixes #11522
Clause-②: no (narrowing)
`object-kanban`'s `conditionalFormatting` takes 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 beside `condition` instead of inside `style`) are retired
with no alias window and refused by name. This executes triage's ruling
`5963861071` (retire, not widen). objectstack-ai/objectstack#21464 can
then type the member by reference to its list-view member.
## What changes
| Rule on `object-kanban` | Before (BASE `6903eafb`) | After |
|---|---|---|
| `{ condition, style }` (string, envelope or `''` condition) | accepted
on all three zod faces | accepted, unchanged |
| native `{ field, operator, value, backgroundColor }` | accepted on all
three faces | refused at `field`, `operator`, `value` and
`backgroundColor`, each naming the retirement |
| flat CEL `{ condition, backgroundColor }` | refused as a bare
`invalid_union` at the rule | refused at `backgroundColor` (named) and
`style` (required) |
| `{ condition, style, backgroundColor }` | tolerant face accepted it
(the colour key was stripped from the parse, while the shared resolver
paints a top-level colour over `style`, as `listConditional.test.ts`
pins); strict face refused it as `invalid_union` | refused at
`backgroundColor` on every face |
| `{ condition, style, label }` | tolerant face accepted it, strict face
refused it | refused on every face, `unrecognized_keys` with the spec
rule's own message |
The three faces are `ObjectKanbanSchema`, `safeValidateSchema`
(tolerant) and `StrictAnyComponentSchema` (strict). The before and after
columns were read with a throwaway probe on each tree. The probe was
deleted and never committed.
- **zod (`@object-ui/types`, `zod/objectql.zod.ts`).**
`KanbanConditionalFormattingRuleSchema` is the spec
`ListViewSchema.conditionalFormatting` element, taken through
`stripImportedDefaults` and `.extend()`-ed. It is no longer a union. It
inherits the spec rule's strictness and its `style` record. Two things
are layered on top. First, `condition` is `SpecRuleConditionSchema`, 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`.
- **TS (`objectql.ts`).** `KanbanConditionalFormattingRule` is an
interface that extends `SpecConditionalFormattingRule` and declares the
same six keys `?: never`. `KanbanNativeConditionalFormattingRule` is
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's
`conditionalFormatting` description 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.
- **Docs.** The `object-kanban` row in
`content/docs/api/schema-reference.md` documents the one rule and its
respelling.
- **One changeset** (`11522-kanban-rule-dialect-retired.md`): `types`
and `plugin-kanban` `minor`, 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 any
`conditionalFormatting` token, in any syntax, which catches tuples and
`key:`/`value:` rows. A fourth pass listed every `{ field, operator }`
literal in files that mention both `conditionalFormatting` and `kanban`.
Each remaining hit was triaged by hand as a filter rule, a grid,
list-view or report carrier, or a resolver unit test.
| Where | Writers on `object-kanban` at BASE | Disposition |
|---|---|---|
| `packages/types/src/__tests__/kanban-conditional-formatting.test.ts` |
3 native (2 zod documents, 1 TS literal) | turned around: pins both
refusals |
|
`packages/types/src/__tests__/object-kanban-allow-collapse-retired-8801.test.ts`
| 1 native (the "still accepts the live member" row) | respelled `{
condition: "record.status == 'open'", style: { backgroundColor:
'#fee2e2' } }` |
|
`packages/plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx`
| 1 native, 1 flat CEL | respelled `{ 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 whole `style` map |
|
`packages/plugin-kanban/src/__tests__/structuredKeysAreDeclaredAndHonoured-8313.test.ts`
| 1 native | respelled `{ condition: "record.owner == 'ann'", style: {
backgroundColor: '#eef' } }` |
|
`packages/plugin-kanban/src/__tests__/objectFieldsIsAPropNotASchemaKey-7742.test.tsx`
| 1 native (relation field) | respelled `{ condition: "record.owner ==
'u1'", style: { backgroundColor: PAINT } }` |
| `packages/plugin-kanban/src/index.tsx` (registration description) |
taught both | rewritten |
| `content/docs/api/schema-reference.md` | taught native | rewritten |
| `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`
(member-pin prose) | described both | rewritten |
| objectui `examples/`, `apps/console` non-test code, `skills/` | zero;
controls fire (`object-kanban` in 4 `examples/` files, `kanban` in 9
`skills/` files) | none |
| objectstack `examples/` at `f9a8eb88` | zero. The one
`conditionalFormatting` (app-showcase `field-zoo.view.ts`) is `{
condition, style }` on a list view. Controls fire: `kanban` hits in
`app-crm` views, and `{ field, operator, value }` filter literals match
the same matcher | none |
| `objectstack-ai/hotcrm` | NOT MEASURED: not in this container. Triage
measured zero | none |
**H2, by reference.** The installed `@objectstack/spec` 17.5.0 exports
no named rule schema. The rule is reachable as
`ListViewSchema.shape.conditionalFormatting.unwrap().element`, a strict
object of `condition` and `style`. The zod twin `.extend()`s that
element through `stripImportedDefaults`. 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 on `object-kanban`. The TS twin is
`SpecConditionalFormattingRule`, which already indexes
`ObjectListViewSchema`'s slot by reference. It is extended, not
restated. A new pin asserts `shape.style` is the spec element's own
`style`, by identity. Its control is that `shape.condition` is not the
bare slot.
**H3, the refusal.** With one arm, zod reports at the retired key's own
path, and no `invalid_union` remains at the rule. The messages are
identical on `safeValidateSchema` and `StrictAnyComponentSchema`. They
are shown below, with backticks spelled as in the source:
- Native rule, at `conditionalFormatting.N.field` (and the same at
`.operator` and `.value`, with the key name swapped):
> `field` belongs to the native kanban rule dialect `{ field, operator,
value, backgroundColor, borderColor }`, which `object-kanban`'s
`conditionalFormatting` no longer accepts: RETIRED (objectui#11522),
with no alias window. A rule is `{ condition, style }` — a CEL
`condition` over `record.*` and a CSS `style` map, the rule
`@objectstack/spec`'s `ListViewSchema.conditionalFormatting` declares.
Respell `{ field: 'priority', operator: 'equals', value: 'high',
backgroundColor: '#fee2e2' }` as `{ condition: "record.priority ==
'high'", style: { backgroundColor: '#fee2e2' } }` (`not_equals` is `!=`,
`contains` is `.contains(…)`, `in` is `record.f in [ … ]`).
- Flat CEL rule, and any top-level colour, at
`conditionalFormatting.N.backgroundColor` (`borderColor` and `textColor`
are the same; `textColor` says `style: { color }`):
> `backgroundColor` is a colour written at the top level of the rule,
which `object-kanban`'s `conditionalFormatting` no longer accepts:
RETIRED (objectui#11522), with no alias window. A rule is `{ condition,
style }` — a CEL `condition` over `record.*` and a CSS `style` map, the
rule `@objectstack/spec`'s `ListViewSchema.conditionalFormatting`
declares. Move the colour into the rule's CSS map: `style: {
backgroundColor }`.
- Beside these, a native rule also draws `invalid_union` at `.condition`
and `invalid_type` at `.style` (both required), and a flat CEL rule
draws `invalid_type` at `.style`. An undeclared key draws the spec
rule's own `unrecognized_keys` message. For `expression` that message
adds "Did you mean `expression` → `condition`?".
The `retirementTombstone` helper is the one used. `aliasKeyRefusal` does
not fit: these keys are a retired dialect, not aliases of one canonical
key.
**H4, the resolver.** Every arm of `resolveConditionalFormatting` still
has a live authorable carrier. This was measured on the built
`safeValidateSchema` and `ObjectGridSchema`:
| Arm | `object-grid` node (`properties` bag) | `ObjectGridSchema` (view
`table` slot / renderer props) | `object-view` `table` slot |
`list-view` node | `object-kanban` node |
|---|---|---|---|---|---|
| `condition` + `style` | accepts | accepts | accepts | accepts |
accepts |
| `expression` | accepts | accepts | accepts | accepts | refuses |
| `field` / `operator` / `value` | accepts | accepts | accepts | accepts
| refuses |
| `backgroundColor` / `textColor` / `borderColor` overrides | accepts |
accepts | accepts | accepts | refuses |
No arm has a measured zero carrier, so `listConditional.ts` is
untouched.
**H5, the render.** A throwaway probe drew each rule through the real
`SchemaRenderer` → `object-kanban` board, and through `KanbanRenderer`
with and without `objectFields`. It ran at BASE and again after the
change. The two readings are byte-identical:
```text
native (8313): Alpha=[background-color: rgb(1, 2, 3);] Beta=[(none)]
native respelled: Alpha=[background-color: rgb(1, 2, 3);] Beta=[(none)]
flat CEL (8313): Alpha=[(none)] Beta=[background-color: rgb(4, 5, 6);]
flat CEL respelled: Alpha=[(none)] Beta=[background-color: rgb(4, 5, 6);]
native (7742) objectFields=true: paint=[rgb(255, 0, 0)]
native (7742) objectFields=false: paint=[(none)]
native respelled (7742) objectFields=true: paint=[rgb(255, 0, 0)]
native respelled (7742) objectFields=false: paint=[(none)]
```
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 containing `RETIRED (objectui#11522)` and `{ condition,
style }`; the accepted rule at index 0 of the same document drawing no
issue; the flat CEL rule refused at `backgroundColor` with the `style: {
backgroundColor }` respelling and no `invalid_union` at the rule; and
all three colour keys refused beside a `style`. It also pins the
identity of `style` with the spec element (with its control) and the
inherited strictness. On the TS face, `@ts-expect-error` covers each
retired key, `keyof` equality holds between the two faces, and
`condition` and `style` types are equal.
- `spec-expression-wire-slots-10946.test.ts` reads `condition` straight
off the rule's shape. There is no union arm left to index.
- `zod-mirror-parity.test.ts`: the
`KanbanConditionalFormattingRuleSchema` exclusion 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 way `ExpressionWireSchema` is.
## Pending changesets
- **Dated note appended:**
`10946-expression-wire-slots-by-reference.md`. Its sentence "the list
view's and the kanban board's rule unions share one `condition` schema"
describes the kanban union.
- **Dated note appended:** `7664-kanban-arm-plugin-dialect.md`. Its
sentence "`KanbanConditionalFormattingRuleSchema` is … the rule union
the `'object-kanban'` arm already applied" is now false.
- **Left alone, still true:**
- `7664-plugin-kanban-declared-schema.md`: says nothing about rules.
- `8932-retire-kanban-enhanced.md`: only names
`KanbanConditionalFormattingRule` as 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`: about `record.*`
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`:
`conditionalFormatting` is still a live control key in `plugin-kanban`.
- `7742-kanban-arm-batch70.md`: about `objectFields`.
- `7322-object-kanban-group-by-limit.md`: its "it now authors `groupBy`"
remark about the kanban test still holds.
- `6349`, `8165` and `8261`: they name the one-authority gate, whose
behaviour did not change; only its comments did.
- The 13 grid and list-view entries that mention conditional formatting:
none makes a kanban rule claim. One of them, `10275`, names kanban only
as a view binding.
- `check:changeset-overwrite` reports 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, after `pnpm --filter '@object-ui/plugin-kanban^...' build`.
- The doc gates ran after the scoped `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.
- With the console built, `check:sdui-registration-pins` and
`check:component-surface-parity` (report-only, no kanban row): both
green.
- All exit 0: `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`.
- NOT MEASURED: the Spec Main Shape Gate (it needs objectstack `main`'s
spec; it belongs to CI). Spec `main` declares the rule with the same
`strictObject` helper and no refinements (read at `f9a8eb88`), so
`.extend()` is expected to hold there. The repo-wide lint is also left
to CI.
## Acceptance notes
- **What the board still paints.** The board still paints any rule a
relay hands it, because the shared resolver keeps every arm. For
example, `ObjectView`'s kanban branch relays a named or active view's
`conditionalFormatting`, and objectui's `ListViewSchema` member still
declares the native rule ("broader than spec, migration deferred", per
its own docblock). The generated `object-kanban` node can therefore
carry a rule the authored `object-kanban` contract now refuses. It is
never validated, which is the same shape as the `groupField` note in
that branch. Carrier: none.
- **The grid's native arm.** On objectstack `main`, the object-grid
block is typed by reference to the list view (objectstack PR #21463, per
the card). objectui's grid `properties` bag and `ListViewSchema` still
declare the native arm, and the installed 17.5.0 row is `z.unknown()`
for both `object-grid` and `object-kanban`. The grid's native arm meets
the same disagreement on a spec bump. Carrier: none named.
- **The skills guide.** `skills/objectui/guides/schema-expressions.md`
lists "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 of `object-kanban` authoring. `skills/**` is
governed, and this PR stays ungoverned, so it is left. Carrier: none.
Session: `https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC`
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9d7419b commit c73cdb5
18 files changed
Lines changed: 436 additions & 134 deletions
File tree
- .changeset
- apps/console/src/__tests__
- content/docs/api
- packages
- plugin-kanban/src
- __tests__
- types/src
- __tests__
- zod
- scripts/__tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2878 | 2878 | | |
2879 | 2879 | | |
2880 | 2880 | | |
2881 | | - | |
| 2881 | + | |
2882 | 2882 | | |
2883 | 2883 | | |
2884 | 2884 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1012 | 1012 | | |
1013 | 1013 | | |
1014 | 1014 | | |
1015 | | - | |
| 1015 | + | |
1016 | 1016 | | |
1017 | 1017 | | |
1018 | 1018 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
130 | | - | |
131 | | - | |
| 130 | + | |
| 131 | + | |
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
160 | | - | |
| 160 | + | |
161 | 161 | | |
162 | | - | |
163 | | - | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
164 | 166 | | |
165 | 167 | | |
166 | 168 | | |
| |||
Lines changed: 36 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
49 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
447 | 448 | | |
448 | 449 | | |
449 | 450 | | |
450 | | - | |
| 451 | + | |
451 | 452 | | |
452 | 453 | | |
453 | | - | |
| 454 | + | |
454 | 455 | | |
455 | 456 | | |
456 | 457 | | |
457 | 458 | | |
458 | 459 | | |
459 | | - | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
460 | 469 | | |
461 | 470 | | |
462 | 471 | | |
463 | 472 | | |
464 | | - | |
| 473 | + | |
465 | 474 | | |
466 | 475 | | |
467 | 476 | | |
| |||
474 | 483 | | |
475 | 484 | | |
476 | 485 | | |
477 | | - | |
478 | | - | |
479 | | - | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
480 | 489 | | |
481 | 490 | | |
482 | 491 | | |
483 | 492 | | |
484 | | - | |
| 493 | + | |
485 | 494 | | |
486 | 495 | | |
487 | 496 | | |
| |||
491 | 500 | | |
492 | 501 | | |
493 | 502 | | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
494 | 520 | | |
495 | 521 | | |
496 | 522 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
79 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
80 | 83 | | |
81 | 84 | | |
82 | 85 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
107 | | - | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
108 | 110 | | |
109 | 111 | | |
110 | 112 | | |
| |||
0 commit comments