Repository navigation
feat(types,plugin-grid)!: object-grid and list-view conditionalFormatting take the spec list view's { condition, style } rule only; the native dialect is refused by name (objectui#11533) - #11540
objectstack-fleet[bot] merged 4 commits into
Conversation
…ting take the spec list view's { condition, style } rule only; the native dialect is refused by name (objectui#11533)
The grid's and the list view's shared rule is the spec ListViewSchema
rule element by reference, .extend()-ed with objectui's string condition
arm and seven retirement tombstones (field, operator, value, expression,
backgroundColor, borderColor, textColor). The TS twin
ConditionalFormattingRule extends SpecConditionalFormattingRule with the
same seven keys declared ?: never; ObjectUIConditionalFormattingRule is
deleted. plugin-grid's input description stops advertising both forms.
resolveConditionalFormatting is untouched: stored rules still paint.
WIP step 1 of the card: faces only; tests, docs and changeset follow.
Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…t-view rule refusals on every zod face and the TS face, and that a stored native rule still paints (objectui#11533) - grid-list-view-conditional-formatting-11533.test.ts: each retired key refused by name on ObjectGridSchema, ListViewSchema, and the tolerant and strict faces on a list-view node and an object-view table slot; the spec rule by reference (style identity, condition arms, the one shared declaration); the TS face by @ts-expect-error and keyof parity. - gridRowDecorationMembers-8071: the retired members are re-framed as stored-rule reads; a new describe pins refused-at-authoring beside painted-at-render, with the respelling as the control. - ListView.storedRuleDialect-11533: a stored list view's native rules reach the grid untouched while ListViewSchema refuses them. - Respelled: round 6 MIRRORED row, the export-options neighbour control, the 10946 list-view rule pin, the object-view forwarding sentinel. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
… pin prose for the grid and list-view rule retirement (objectui#11533) - .changeset/11533-grid-listview-rule-dialect-retired.md: types and plugin-grid minor, BREAKING spelled out, before/after table, respelling, the bag left to the spec row, what still paints. - Dated notes on 10946 (the list view's rule is no longer a union) and 11522 (the evaluator keeps its arms as a stored-rule read, not because the grid and list view still declare them). - schema-reference: an ObjectGridSchema conditionalFormatting row. - zod README: conditionalFormatting leaves the broader-than-spec list. - registry-inputs-spec-parity: the object-grid member-pin prose says the retired members are stored-rule reads now. - KanbanImpl: the getCardStyles comment's reason, made false by this change, is corrected (comment only). Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…y` (objectui#11533) 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 #11533 (body; comments Check-runs on the head: first read 2026-10-03T05:54:11Z, 42 runs, 29 ① Derived judgmentsAccept-set and public-surface changes the diff implies, each read against the head sources:
② Semver levelChangeset Export loci, each sentence checked at the head with the care the 11522 round-1 FAIL set: Clause-②: no (narrowing). RIGHT. Every face moves one way (judgments 1 to 6, 10); Pending sweep (read, not recalled; 2,704 entries, grepped for
③ Boundary flags
Dev deviations, each: (1) files outside the claim's enumerated loci, judged below; (2) the bag not narrowed, judgment 14; (3) H1's one-schema fold not taken, judged below; (4) objectstack read from a sparse clone in the dev's scratch, no git command in the shared checkout, and one Out-of-scope findings, each answered: (a) Boundary note, each item:
Dev NOT MEASURED items: hotcrm, accepted on triage's own zero ( Implemented-by: VERDICT: PASS |
… only; the column `options` read is retired (objectui#11544) (objectstack-ai#11563) Fixes objectstack-ai#11544 Clause-②: no Retire, not widen (triage ruling `5966937083`): a grouped `object-grid` labels its group headers from the object field's `options` only. `ListColumnSchema` declares no `options` and is strict, so the column-level read honoured a key every publish refuses. Session `session_01FjqrwXPfSMkSfkKYDSRkN2`, branch `claude/issue-11544-group-header-field-options`. ## What changed - `packages/plugin-grid/src/ObjectGrid.tsx`, `groupValueFormatter`: the label source `colOverride?.options || objectDefField?.options` is now `objectDefField?.options`. The column lookup stays, because it also supplies `type`, a declared `ListColumn` member (a `boolean` column respells `"true"` / `"false"` as Yes / No). The lookup is now typed as `ListColumn`, with the same `c is ListColumn` guard `maskedGroupingSignature` already uses, instead of `as any[]`. As a result, an undeclared column-member read at this site no longer compiles (proved below). - `packages/plugin-grid/src/__tests__/gridGroupingMembers-8071.test.tsx`: the two cases that pinned `columns[].options` as behaviour are turned to pin the field's labels: - "the object field's `options` decide the header label, and a column `options` beside them is not read". It has three legs. The field's labels reach the header. A column carrying `options` beside the field's still shows the FIELD's labels. The same column with no field options shows the stored values. - "`order` sorts the RENDERED labels, so the field's options move the groups". This is `order: 'asc'` over the field's labels, with a no-field-labels control. - The header note ("pinned below AS BEHAVIOUR and reported as a finding") is rewritten to say the read is retired. `renderGrouped` now takes the host `objectFields` runtime prop, which is how field options reach an inline-rows grid without a schema read. - `apps/console/src/__tests__/registry-inputs-spec-parity.test.ts`: the `object-grid.grouping` `pins` descriptor follows the fixture; it had said the read was pinned as behaviour and not repaired. This is the move PR objectstack-ai#11540 made to its own descriptor. It is text only: nothing asserts on `pins` content. - `.changeset/11544-group-header-field-options.md`: `@object-ui/plugin-grid` patch. ## Premise, measured on `origin/main` `d0c0c7fe9` (after PR objectstack-ai#11554) - `@objectstack/spec` 17.6.0 `ListColumnSchema` has fourteen keys (field, label, width, align, hidden, sortable, resizable, wrap, type, pinned, summary, prefix, link, action). `safeParse({ field, options })` answers `unrecognized_keys` naming `options`, and the control `{ field, type: 'select' }` parses. - objectui `ObjectGridSchema.safeParse` with a column `options` answers `invalid_union` at `columns`, and the same grid with a column `type` parses. - One read site. A grep for `.options` in `ObjectGrid.tsx` finds only the `groupValueFormatter` line reading it off a column; every other hit reads a field. A repo grep for the column-override lookup finds only this memo. Other `col.options` hits (`GridField`, `RecordPickerDialog`, the flow inspector) read different column schemas, not `ListColumn`, and none of them labels a group. - In-repo producers of column `options`: none. The `options` spreads in app-shell build detail-form fields and import targets, not grid columns. - PM hypothesis 1 ("the now-unused column lookup is removed if nothing else in the memo needs it") is half-falsified: `type` needs the lookup, so it stays, typed. ## Tests (HEAD `92bddae`) - Fixture plus parity ledger: `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/src/__tests__/gridGroupingMembers-8071.test.tsx apps/console/src/__tests__/registry-inputs-spec-parity.test.ts` gives `Test Files 2 passed (2)` and `Tests 265 passed (265)`. - Whole package: `pnpm exec vitest run --maxWorkers=2 packages/plugin-grid/` gives `Test Files 182 passed (182)` and `Tests 1691 passed (1691)`. - `pnpm --filter @object-ui/plugin-grid type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`) exits 0. `--listFiles` on `tsconfig.test.json` lists the fixture once and `ObjectGrid.tsx` once. - Dependency closure: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' build` exits 0. ## Ablation (one-shot, run from the committed fix through objectstack's `scripts/ablation-replace.mjs` in wrap mode) - **Behaviour.** Anchor `const rawOptions = objectDefField?.options;`, replaced with `const rawOptions = (colOverride as any)?.options || objectDefField?.options;` (anchor count 1 to 0, blob `004444984f55` to `316153fc4622`). Predicted: the label case goes red and everything else stays green. Observed: `Tests 1 failed | 9 passed (10)`, `expected [ 'Column Lost', 'Column Won' ] to deeply equal [ 'Closed Lost', 'Closed Won' ]`. Restore: blob equals HEAD, and `git diff HEAD` is empty. - **Type.** The same anchor, replaced with `const rawOptions = colOverride?.options || objectDefField?.options;`. `tsc --noEmit` gives `error TS2339: Property 'options' does not exist on type` (the `ListColumn` object shape). Restore: blob equals HEAD. - The `order` case stays green under the behaviour ablation by design. It carries no column `options`; it pins that `order` sorts the field's labels, which both trees do. ## Gates (HEAD `92bddae`, each exit 0) - `pnpm check:control-bytes`: `check-control-bytes: OK`. - `pnpm check:new-line-citations`: `VERDICT new-cross-file-line-citations: 0 new citation(s)`. - `node scripts/check-changeset-presence.mjs`: `3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)`. - `node scripts/check-changeset-no-major.mjs`, `check-changeset-fixed.mjs` and `check-changeset-overwrite.mjs`: all report OK. - `pnpm check:changeset-claims` is report-only. It names six pending changesets that mention `ObjectGrid.tsx` or the parity ledger. I re-read each paragraph, and none makes a claim about group-header labels or column `options`. - `pnpm check:pending-changeset-literals`: `No test source names a pending changeset`. - `pnpm check:test-path-roots`, `node scripts/check-vi-mock-specifiers.mjs`, `node scripts/check-vi-mock-inherit.mjs`, `pnpm check:spec-symbols` and `node scripts/check-type-check-coverage.mjs`: all report OK. ## Narrowings (declared) - **Lint (CI-owned).** A narrowed run over the three changed source files. The population is from eslint's own config: `eslint --print-config` resolves all three, so none is ignored. `--format json` counts 3 files. `eslint.config.js` sets no `parserOptions.project` / `projectService`, so type-aware linting is off, and this diff cannot move an untouched file's verdict. In package form (inline config honoured, as `pnpm lint` runs it): 0 errors. In root `--no-inline-config` form: 1 error, `react-hooks/static-components`, at the annotated `AuthoredEmptyIcon` site. It is identical at BASE (BASE 1 error / 210 warnings, head 1 / 209). - Build-reading gates (`check:published-dist`, `check:node-esm-load`, `check:phantom-deps` and the rest) and the full sharded `pnpm test` belong to CI. This diff adds no import, export or dependency. ## Acceptance notes - The parity ledger's slice-17 history paragraph, which says the group-label memo reads `columns[].options`, is left as written. It records what that slice did, the way a changelog entry does. Only the live `pins` descriptor changed. - No doc page or README described the column-options read (grep of `content/docs` and `packages/plugin-grid/README.md`), so there is no doc edit. - The parity ledger is a shared file. This PR edits one string in its `object-grid.grouping` entry. - objectstack#21464 types `columns` by reference once its `.objectui-sha` covers this landing. That stays on objectstack#21464, per triage. --- _Generated by [Claude Code](https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2)_ Co-authored-by: Claude <noreply@anthropic.com>
…s the only authorable conditional-formatting rule on every list carrier (objectui#11534) (objectstack-ai#11595) Fixes objectstack-ai#11534 Clause-②: no One bullet of `skills/objectui/guides/schema-expressions.md` — the one under "CEL predicates over a row record" that opened with "Legacy shapes are translated, with a one-time warning." — is restated per the card's ruling (triage comment 5965309847, first branch; unlock comment 5966553335). Nothing else in the guide moves; no other file is touched. Governed surface (`skills/**`, the `skills-catalog` item of `GOVERNED_SURFACES`): this PR stays a DRAFT until an authorized APPROVED review is on it; the claiming seat lands it. The dev that drafted this does not flip it ready, enqueue it, or approve it. ## What the bullet says now - Authoring takes `{ condition, style }` only, on every list carrier (grid, list view, kanban). - The native `{ field, operator, value }` form and the `{ expression: "${…}" }` template form are retired, refused at the schema door by name with the respelling. - A STORED legacy rule still renders through the shared evaluator — a compatibility read, not an authoring form. - A `condition` string in legacy-only syntax still routes to the old engine with a one-time deprecation warning — a renderer-side fact that still holds and lives nowhere else, so it is kept. The old bullet's operator list and the `?.` marker went with the retired forms; the three facts above are the whole bullet. ## Reading one — premise verification on objectui `origin/main` `bdc9049` Cited by content (symbol names, test names, quoted message fragments), never by line address. **① Every list carrier's schema door refuses the native rule and the `{ expression }` rule.** Probed with `safeParse` on `ObjectKanbanSchema`, `ObjectGridSchema` and `ListViewSchema` (one `tsx` run against the `packages/types` source; the spec-shape `{ condition, style }` rule was the positive control and parsed on all three): | carrier | native `{ field, operator, value, backgroundColor }` | `{ expression, backgroundColor }` | control | |---|---|---|---| | `object-kanban` | refused at `field` / `operator` / `value` / `backgroundColor`: "belongs to the native kanban rule dialect … which `object-kanban`'s `conditionalFormatting` no longer accepts: RETIRED (objectui#11522), with no alias window. A rule is `{ condition, style }`" (`kanbanRuleKeyRetired`) | refused: "Unrecognized key(s) on this conditional formatting rule: `expression`. Did you mean `expression` → `condition`?" (the spec element's own strictness, inherited by `KanbanConditionalFormattingRuleSchema`) | success | | `object-grid` | refused at each key: "belongs to the native rule dialect … which `conditionalFormatting` on `object-grid` and `list-view` no longer accepts: RETIRED (objectui#11533), with no alias window" (`gridRuleKeyRetired`) | refused at `expression`: "`expression` is the template predicate of the native rule dialect … RETIRED (objectui#11533)" | success | | `list-view` | same `gridRuleKeyRetired` refusal (the member is `ConditionalFormattingRuleSchema`, one declaration shared by the two mirrors) | same named `expression` refusal | success | Pinned by the repository's own tests, run on this tree: `kanban-conditional-formatting.test.ts` ("refuses the native `{ field, operator, value, backgroundColor }` rule BY NAME, at each retired key"; "inherits the spec rule's strictness: an undeclared key is refused with the spec's own message") and `grid-list-view-conditional-formatting-11533.test.ts` ("refuses `expression` BY NAME, with the `condition` respelling"; "the grid's, the list view's and the named view's members all carry the one rule"). **② Stored legacy rules still render through the shared evaluator.** `resolveConditionalFormatting` in `@object-ui/core` (`listConditional.ts`) normalises a rule through `ruleToPredicate`: `condition` first, then a non-blank `expression` string, then `fieldOperatorValueToCel` for the native comparison — every arm kept, and the schema comment on both rules says so verbatim ("Stored stock is not authoring … keeps every arm as a compatibility read"). The legacy-dialect string route is `isLegacyDialectSource` + `warnLegacyDialect` (warn-once per source). Pinned by `listConditional.test.ts`: "native shape { field, operator, value } — translated to CEL", "native operators: not_equals / greater_than / less_than / contains / in", and under "legacy-dialect routing": "routes a `${…}` template string to the legacy engine and warns once". Test run (under the shared verification lock): the three files above — `Test Files 3 passed (3)` · `Tests 105 passed (105)` · `VERDICT command-exit 0`. The blocker is landed: objectui#11533 reached state completed when PR objectstack-ai#11540 merged to main; objectui#11522 (PR objectstack-ai#11532) is on main. No re-ruling away from retirement was found, so the ruling's first branch is what the bullet states. ## Reading two — the `skills/**` line readings | reading | before (`origin/main` `bdc9049`) | after (`fa1c5e7`) | |---|---|---| | the bullet | 7 lines | 8 lines (+1) | | `guides/schema-expressions.md` | 639 | 640 | | whole package, every `SKILL.md` under `skills/` (1 file) | 142 | 142 | | whole package, every `.md` under `skills/` (16 files) | 4811 | 4812 | | hits of "still work and are rewritten to CEL" in the guide | 1 | 0 | The dispatch's target was net zero against the old bullet's 7 lines; the restatement lands at +1 because it keeps the two compatibility facts the ruling asks for AND the still-true legacy-dialect-string fact, at 80 columns. No token gate exists for this catalog (objectui carries `check:skill-eval-tokens`, a must_contain oracle, not a line ceiling; green below). If the seat wants net zero, the last clause ("a `condition` in legacy-only syntax … write CEL.") is the one to drop, and the seat should say so rather than this PR guessing. ## Local gates (every exit code captured before any pipe) Green, exit 0, each verdict quoted from the gate's own last line: - `pnpm check:skills-paths` — ".claude/skills/ — 60/61 resolve across 4 file(s)" (the `skills/` root line above it also resolved) - `pnpm check:skill-eval-tokens` — "Every must_contain token is taught by its own skill bundle." · its `--self-test`: "29 cases pass" - `node scripts/check-doc-links.mjs` — "Links are valid across 17 scan roots." - `pnpm check:doc-fences` — "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript" - `pnpm check:doc-example-ids` — "415 real reference(s) all resolve in the catalog registry." - `pnpm check:doc-types` — "Every documented component type is registered." · `pnpm check:prompt-keys` — "OK" - `node scripts/check-doc-expression-carriage.mjs` — exit 0 (census ran) - `node scripts/check-changeset-presence.mjs` — "No source or published contract of a released package changed in this range, so no changeset is owed." ⇒ no changeset file added - `pnpm check:changeset-claims` — exit 0 · `pnpm check:control-bytes` — "OK (scanned 10213 tracked text file(s))" - `node scripts/check-new-cross-file-line-citations.mjs` — "0 new citation(s)" - `node scripts/check-shell-escape-residue.mjs` — "OK (5/5 root(s) resolved … skills: 16 file(s), 211 fence(s)" - `pnpm check:pre-install-import-graph` — "OK" - `pnpm check:governed-queue-guard` (self-test) — "188 cases pass" · `node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/schema-expressions.md` — "⛔ GOVERNED — 1 of 1 path(s) … skills/** x1 — the published skills catalog" (exit 3, the expected answer for this PR) NOT MEASURED locally, declared narrowings (CI runs each with its scoped build): - `pnpm check:skill-examples` compiler leg — needs the 12-package closure its `--build-filter` names; the shared verification lock was never acquired in two 540-second waits (holder: the `issue-21465` gate run, `os-verify-lock.sh --status`). The gate judges MARKED fences only, and `check-skill-examples.mjs --list` names 0 candidate fences in `schema-expressions.md`; this diff edits prose inside one bullet and no fence anywhere, so the verdict is invariant to it. Its `--self-test` scanner leg ran; the compiler leg answered "PRECONDITION NOT MET (exit 2)" on the unbuilt tree, which is not a measurement. - `pnpm check:doc-snippets` and `pnpm check:doc-examples` — their population is `DOCS_ROOT = 'content/docs'`, `ROOT_PAGES = ['README.md']` and the package READMEs; `skills/**` is deliberately outside it (`check-doc-component-types.mjs` header: "not `skills/**`, not `docs/**`"), so this diff cannot move them. - `pnpm lint` — narrowed, three pieces at `fa1c5e7`: ① the population, read from `eslint.config.js`'s own `files:` globs, is `**/*.{ts,tsx}` plus a few named `.ts` files; ② `eslint --format json` on the touched file returns one result entry with `errorCount: 0` and the single message "File ignored because no matching configuration was supplied" — 0 files linted; ③ invariance: the config declares no `projectService` / `parserOptions.project` (0 matches), so no type-aware rule exists whose verdict on an untouched file a Markdown edit could move. - The `ci.yml` / `lint.yml` heavy steps skip on a Markdown-only diff by their own in-job switch; this PR is exactly that case. ## 维护者速读(草稿) **改了什么。** 只改 `skills/objectui/guides/schema-expressions.md` 里「CEL predicates over a row record」一节的一条 bullet:原来说原生 `{ field, operator, value }` 规则和 `{ expression }` 模板规则「仍然可用、会被透明改写成 CEL」,现在说:三个列表承载面(grid、list view、kanban)都只接受 `{ condition, style }` 作为可编写的条件格式规则,两种旧形式已退役、schema 门按名拒收并给出改写;已存储的旧规则仍经共享求值器渲染(兼容读,不是编写形式);带旧语法的 `condition` 字符串仍走旧引擎并一次性告警。净 +1 行(7 → 8)。 **为什么改。** objectui#11522(kanban,PR objectstack-ai#11532)与 objectui#11533(grid / list view,PR objectstack-ai#11540)落地后,指南这条与代码矛盾:照它写规则的作者或 AI 会被校验器拒收。分诊裁决定为「一次受管编辑,等 objectstack-ai#11533 落地后写最终事实」,objectstack-ai#11533 已落地,本 PR 按第一分支写。 **风险与代价(含回滚)。** 纯文档、单文件、不发版(changeset 门禁判「无需声明」)。风险只有措辞与代码不一致,已用 safeParse 探针和仓内 105 个 pin 测试按内容核对。回滚 = revert 这一个 commit。 **席位意见。** (留空,由席位定稿) **你要做的。** 在本 PR 上留一条 APPROVED review(受管面 `skills/**`,队列守卫要求 `os-zhuang` / `hotlong` 的批准记录),之后由席位翻 ready 并入队。 ## Acceptance notes - Observation, not filed: on `object-kanban` the `{ expression }` key is refused through the spec element's inherited strictness ("Unrecognized key(s) … Did you mean `expression` → `condition`?"), while `object-grid` / `list-view` carry a named `expression` tombstone (`gridRuleKeyRetired`). Both refusals are loud and both name the respelling, so the guide's "refused at the schema door by name with the respelling" holds on all three; the asymmetry is wording, not behaviour, and is below the filing bar. - Observation, not filed: the guide's bullet no longer lists the native operators (`equals` … `in`) or the `?.` legacy marker; the operator-to-CEL table now lives only in the refusal messages (`gridRuleKeyRetired` / `kanbanRuleKeyRetired`), which is where an author meets it. - Dispatch-prompt conflict noted per the dev contract: the prompt routed `pnpm install` through the shared verification lock; the contract lists install among the runs that do not take it. The contract was followed (install ran unlocked, 6.6 s); builds and the test run took the lock. - The dev's kept lock slot `issue-ui-11534` blocks nobody (a parked place, by the lock's own status text); it expires with this session. - Written by the objectui dev seat for session `session_01CB6W87z22K2yjUCDyVrJRk`; no model identifier appears in this PR, its commit or the guide. --- _Generated by [Claude Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11533
Clause-②: no (narrowing)
conditionalFormattingonobject-grid(ObjectGridSchema, and so theobject-viewtableslot built from it) and onlist-viewtakes ONE rule dialect, the spec list view's{ condition, style }. The native{ field, operator, value }rule, itsexpressiontemplate predicate and a colour written at the top level of a rule (backgroundColor,borderColor,textColor) are retired with no alias window and refused by name. This executes triage's ruling5965301211(retire, as objectui#11522 ruled for the kanban board in5963861071; never widen the spec's list-view member). Stored stock is not authoring:resolveConditionalFormattingkeeps every arm, so a grid or list view STORED with a native rule still paints, byte for byte as before.What changes
ObjectGridSchema, anobject-viewtableor alist-view8bfc0012){ condition, style }(string, envelope or''condition){ field, operator, value, backgroundColor }field,operator,valueandbackgroundColor, each naming the retirement{ expression, backgroundColor }invalid_unionat the ruleexpressionandbackgroundColorby name{ condition, backgroundColor }invalid_unionat the rulebackgroundColorby name, andstyle(required){ condition, style, backgroundColor }invalid_unionbackgroundColoron every face{ condition, style, label }unrecognized_keyswith the spec rule's own messageThe faces read: the
ObjectGridSchemaandListViewSchemamirrors, andsafeValidateSchema(tolerant) andStrictAnyComponentSchema(strict), each on alist-viewnode and on anobject-viewtableslot. Both columns were read with a throwaway probe on each tree; it was deleted and never committed.Not judged here, and unchanged: the authored
object-gridnode'spropertiesbag. Its members are@objectstack/spec'sComponentPropsMap['object-grid']row by reference (objectui#11276). The installed 17.5.0 row typesconditionalFormattingasunknown, so the bag accepted all six rules before and after. It carries no objectui copy of the rule, and objectui does not narrow a spec row (see Acceptance notes).@object-ui/types,src/zod/objectql.zod.ts). The grid's and the list view's shared ruleConditionalFormattingRuleSchema(module-private, one declaration for both mirrors) is no longer a union. It is the specListViewSchema.conditionalFormattingelement (SpecListViewRuleSchema, now declared once above the grid mirror and.extend()-ed by both this rule and the kanban rule), so it inherits the spec rule's strictness and itsstylemap. Two things are layered on top:conditionstaysSpecRuleConditionSchema(objectui'sz.string()arm before the spec slot, so a string is not canonicalized and''is still accepted, objectui#10946), and seven retirement tombstones:field,operator,value,expression,backgroundColor,borderColor,textColor, through agridRuleKeyRetiredmessage builder. The grid member's.describe()and the "intentionally broader than spec (migration deferred)" docblock lines go;conditionalFormattingleaves that list.objectql.ts).ConditionalFormattingRuleis an interface that extendsSpecConditionalFormattingRuleand declares the same seven keys?: never.ObjectUIConditionalFormattingRuleis deleted and dropped from the root barrel (TS2305 for an importer). tsc reports each retired key at the key (TS2322).@object-ui/plugin-grid. Theobject-gridregistration'sconditionalFormattinginput description stops advertising both forms: it describes the one rule and names the retired spellings. No render path changes.conditionalFormattingrow incontent/docs/api/schema-reference.md's ObjectGridSchema table;packages/types/src/zod/README.mddrops the member from the broader-than-spec list.11533-grid-listview-rule-dialect-retired.md):@object-ui/typesand@object-ui/plugin-gridminor, BREAKING in the first line, before/after table, respelling, the bag and the paint stated as unchanged.Mechanism hypotheses, measured
H1, the faces and the shared union (a type-checker census, not grep). A TypeScript LanguageService over all 5,034 tracked TS sources, under the root tsconfig's paths (which resolve
@object-ui/typesto source):findReferenceson each declaration, plus everyconditionalFormattingidentifier resolved to the declaration it binds.ConditionalFormattingRuleSchemaObjectGridSchema.conditionalFormattingand theListViewSchemaoverride (2 refs); through the grid mirror's.shapeit also builds theobject-viewtableslotObjectGridBlockSchematakes six other mirror members by reference, not this oneConditionalFormattingRuleObjectGridSchema.conditionalFormatting,NamedListView.conditionalFormatting, the root barrel, the 10946 relay pin, a docblock link in the kanban ruleObjectUIConditionalFormattingRuleObjectGridSchema.conditionalFormattingmember (12 refs)ObjectGrid.tsx(5 reads, each handed to the resolver or the harvest as an unknown array), plugin-view's route-2 relay, testsListViewSchemamember (10 refs)ListView.tsx(evaluateConditionalFormatting's rules parameter, the harvest, the relay into its grid node), app-shellObjectView.tsx's relay (its other operand isany), testsresolveConditionalFormattingConditionalFormattingRuleLikein@object-ui/core)listViewPredicates(the predicate-field harvest)readonly unknown[]Can the kanban rule and the grid/list rule become one schema? Measured: same base element (now one declaration), same
conditionarms (pinned arm by arm), samestyle(identity, pinned). They differ in exactly two things: the carrier and card each message names, andexpression, which the grid and list rule carried and the kanban rule never did (kanban refuses it through the spec's own unrecognized-keys message with its "Did you meanexpression→condition?"). Folding them would changeobject-kanban's landed refusal text and itsexpressionpath, a surface objectui#11522's contract record judged on its landing head. Not done here; the report carries the option.H2, the census. Two instruments. (1) An AST pass (TypeScript parser) over the 59 TS files that mention
conditionalFormattingorConditionalFormattingRule: every object literal with afield+operatorpair, a top-level colour beside a predicate key, or anexpressionbeside a style key: 50 hits, each triaged by hand. The filter literals it also caught (UserFilterTabRuleSchema,ReportFilterSchema, filter fixtures) are its live control. (2) A text pass over every other tracked file that mentions the member: the 500 characters after each token, for a retired key in any syntax: 1 window, the kanban row of the schema reference, which teaches the forms as refused.unmirrored-declared-round6-6152.test.tsvalidrow) and 1 native with a wrong operator (theinvalidrow)validrespelled{ condition: "record.status == 'open'", style: { backgroundColor: '#fee2e2' } };invalidreplaced by{ condition: "record.status == 'late'", style: 'color: red' }, because its wrong value (an operator outside the native enum) has no member left to be wrong inexport-options-spec-parity.test.ts{ condition, style }line and the non-array refusalobjectViewHostSurface.test.tsx(plugin-view){ field, operator: 'eq', value, color }{ condition: "record.stage == 'won'", style: { color: '#ff0000' } }gridRowDecorationMembers-8071.test.tsx(plugin-grid)spec-expression-wire-slots-10946.test.tsconditionstraight off the rule's shapelistConditional.test.ts,predicate-fields.test.ts(core)ConditionalFormattingEditor.test.tsx(app-shell)kanban-conditional-formatting.test.ts,unmirrored-declared-round3-6152.test.tsexamples/,apps/consolenon-test code,skills/object-grid/list-viewin 5examples/files,object-gridin 1 console file and 3skills/files)examples/at4916168(my own sparse clone)conditionalFormatting(app-showcasefield-zoo.view.ts) is{ condition, style }; controls fire (object-grid/list-viewview files,{ field, operator, value }filter literals)objectstack-ai/hotcrmH3, stored rules still paint. Before and after, through the real
ObjectGridand through the realListViewhanding its node to the real grid, for all six rules of the table: the readings are byte-identical, for examplenative: Alpha=[background-color: rgb(1, 2, 3);] Beta=[null]on both trees and both renderers. Read paths that could validate a stored view: none on the render path (SchemaRendererruns only the dev-only structuralvalidateSchema; nothing parses a grid or list-view node). Outsidepackages/types, the only non-test callers of objectui's zod faces are the CLI'sobjectui validate/objectui check(authoring doors) and three unrelated schema imports (a dashboard widget, a dashboard designer door, a map config). Studio'sclientValidationjudges a view draft against@objectstack/spec/ui'sViewSchema, not objectui's face, so this change does not reach it; the installed spec's list-view rule already refuses the native rule there (unrecognized_keysnamingfield,operator,value,backgroundColor), at BASE as now. Premise held; nothing for the seat.H4, the refusals. One arm, so each refusal sits at the retired key's own path; the messages are byte-identical on the tolerant and strict faces and on both carriers. Quoted, with backticks as in the source:
conditionalFormatting.N.field(operatorandvaluethe same, key swapped):.expression:.backgroundColor(borderColorthe same;textColorendsstyle: { color }):conditionalso drawsinvalid_unionat.conditionandinvalid_typeat.style(both required); an undeclared key draws the spec rule's own message, "Unrecognized key(s) on this conditional formatting rule:label. …".The pins
packages/types/src/__tests__/grid-list-view-conditional-formatting-11533.test.ts(new, 44 tests): on each of the six faces, the{ condition, style }control (string and envelope); the native rule refused at each retired key with the message starting with the key's name and containingRETIRED (objectui#11533),{ condition, style }and the respelling, while the accepted rule at index 0 of the same document draws no issue;expressionrefused with its respelling; the flat CEL rule refused at the colour key with noinvalid_unionat the rule; all three colour keys refused beside astyle; the spec's unrecognized-keys message. The reference: the two mirrors share one rule object,styleis the spec element's own member (identity),condition's second arm is the spec slot and both arms are the kanban rule's, with the control thatconditionis not the bare slot. The TS face:@ts-expect-erroron each retired key, on a native rule in anObjectGridSchemaand aListViewSchemaliteral,keyofequality with the zod input, and the three members (ObjectGridSchema,ListViewSchema,NamedListView) carrying the one rule.gridRowDecorationMembers-8071.test.tsx(plugin-grid): the stored native rule refused byObjectGridSchemaatfieldwith the retirement, AND painted by the real grid on the matching row only; the respelling accepted and painting the same row the same colour as the control.ListView.storedRuleDialect-11533.test.tsx(plugin-list, new): a stored list view's native rules refused byListViewSchemaatfield, AND handed to itsobject-gridnode untouched; the respelling as the control.apps/console/.../registry-inputs-spec-parity.test.ts: theobject-grid.conditionalFormattingmember-pin prose says the retired members are stored-rule reads now (prose only, no logic).Pending changesets (read, not recalled)
Read as text: every pending entry naming
conditionalFormatting,ConditionalFormattingRule, the resolver, a rule union, "broader than spec", or a file this diff touches (41 by symbol and prose, and the 51 thatcheck:changeset-claimslists by file name, each grepped for the symbols this diff moves).10946-expression-wire-slots-by-reference.md. Its "the list view's and the kanban board's rule unions share oneconditionschema" now describes no union at all; the note keeps every condition claim, which still holds and is still pinned. (+16/−0)11522-kanban-rule-dialect-retired.md. Its "Not changed" paragraph gives "because the grid's and the list view's rule union still declares them" as the reason the evaluator keeps every arm; the reason is now the stored-rule compatibility read. (+15/−0)7664-kanban-arm-plugin-dialect.md(kanban only, already noted by objectui#11522);6152-objectgrid-round6-mirrored.md(says the mirror declares the key typed as the twin types it, still true, and makes no dialect claim);6152-unmirrored-round3.md(the report's rule);11276-object-grid-properties-bag.md(the bag, which did not move);10976,10885,10758,7928(relays and named views, unchanged);10689×2,7216,7727,8167,8972,9318,row-predicate-phase2-record-only-5741(harvest and condition scope, unchanged);member-pins-object-grid-row-layer-8071(names the key only);8313,8801,9242,8932-retire-kanban-enhanced,7742,7322,4730,10275,8818(kanban, i18n or FLS readings that name the key only);4535and6956(nameexport-options-spec-parity.test.tsfor its export-options rows, not the neighbour control); the entries namingKanbanImpl.tsx(card, lane and click behaviour, none about the evaluator comment);10719(a different "rule union").check:changeset-overwritereports the two modified entries with their declarations unchanged, which is the appended-note case.Verification
ebb37b7b. It differs fromaab1cc53(where the suites below ran) by one test file,ListView.storedRuleDialect-11533.test.tsx(as anytoas never), re-run atebb37b7bwith plugin-list's type-check.pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-grid/ataab1cc53: 527 files, 10,923 tests passed (VERDICT command-exit 0).aab1cc53(the plugin-list, plugin-view, plugin-kanban, app-shell, core, components, react, i18n, console and schema-catalog files that read the member or the changed files): 968 tests passed. 19scripts/__tests__suites that read the changed files (one-authority, doc-links, doc-snippet, readme-exports, changeset gates and others): 1,093 tests passed.type-check(each script name echoed, each exit read separately), afterturbo run buildof the dependency closure:@object-ui/types(src, examples and test configs;--listFilesOnlyconfirms the new pin file is compiled),plugin-grid,plugin-list(again atebb37b7b),plugin-view,plugin-kanban: exit 0.HEAD: a native rule appended to plugin-grid'sspecExpressionWire-10946.test.tsxas anObjectGridSchemaliteral turned plugin-grid's test type-check red (exit 2, TS2322 atfield,operator,value), proving it reads the rebuilt declaration; after restore the blob equalsHEAD's andgit diff HEADis empty.check:doc-snippets(777 of 777 blocks, 0 failed),check:doc-examples(exit 0, ledger-exact),check:doc-types(exit 0). With the console built:check:sdui-registration-pinsexit 0,check:component-surface-parity(report-only) exit 0 with noobject-gridrow.ebb37b7b, all exit 0:check:new-line-citations(0 new),check:control-bytes,check-changeset-no-major,check-changeset-presence,check-changeset-fixed,check:changeset-claims,check:pending-changeset-literals,check:test-path-roots,check-type-check-coverage,check-doc-links,check:spec-symbols,check:doc-fences,check-doc-expression-carriage,check:readme-exports,check-changeset-overwrite;check-governed-queue-guard --testover the 18 paths: NOT GOVERNED.Acceptance notes
object-gridbag. At the installed spec the bag still accepts any rule, because the spec row's member isunknown; objectstackmain(4916168) types it as the list view's own member by reference, so the bag narrows with the@objectstack/*bump that carries objectstack PR #21463. Carrier: objectui#11438 (the 17.6.0 trunk) or the bump after it.skills/objectui/guides/schema-expressions.md's "Legacy shapes are translated" bullet says the native andexpressionforms "still work" under a heading naming list, grid and kanban: true of the evaluator, now false of authoring on all three carriers.skills/**is governed and this PR stays ungoverned, so it is left; it is the same item as objectui#11522's contract record escalation (c).ReportComponentSchema.conditionalFormatting, a{ field, operator, value, backgroundColor?, textColor? }shapeReportViewerreads per cell) is a separate declaration, and neither spec line declares a report rule member. Untouched. Carrier: none.ROADMAP.mdrecords the dual-format union as delivered; it is a dated log and is left.packages/plugin-kanban/src/KanbanImpl.tsx(comment only:getCardStyles' stated reason became false),ListView.storedRuleDialect-11533.test.tsx(the H3 pin),objectViewHostSurface.test.tsx(sentinel respelled), the console pin prose.Session:
https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPCGenerated by Claude Code