Repository navigation
feat(spec,lint): a field declares conditionalFormatting cell rules over value and record, in the list view's own rule element - #22546
Conversation
…st view's own rule element
The rule element { condition, style } moves to shared/conditional-formatting.zod.ts
so FieldSchema and ListViewSchema mount one declaration.
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…ue and record Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
… and ledgers Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…nd strictness counts Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…tadata-core and driver-sql Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…eld-conditional-formatting # Conflicts: # packages/lint/src/validate-expressions.ts
Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…module Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 140 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d7ea01440fe7aa0b557cd0b68ee3530a6e17ca17 && git checkout d7ea01440fe7aa0b557cd0b68ee3530a6e17ca17
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin faf6348508519197c6047b46fb30b6ae8910f6b2 ffce73a2acdd4bc3202cc963f3fb9db1890452e5 && git checkout -B drift-repro faf6348508519197c6047b46fb30b6ae8910f6b2 && git merge --no-ff ffce73a2acdd4bc3202cc963f3fb9db1890452e5
node scripts/docs-audit/affected-docs.mjs --json faf6348508519197c6047b46fb30b6ae8910f6b2
|
Contract reviewServed-tier: Reviewed on the net diff of ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named right or wrong:
② Semver levelChangeset
③ Boundary flags
Required contexts on the head, all Implemented-by: VERDICT: PASS Generated by Claude Code |
Landing pre-checks at
|
Fixes #22228
Clause-②: yes (widening)
The maintainer-directed field-level formatting block from the decision discussion on objectstack-ai/objectui#11815 (the director seat's proposal, answered 「同意」). Spec and authoring gate only; objectui's cell renderer is its own card after the spec publishes.
What
FieldSchema.conditionalFormatting(optional): an ordered list of{ condition, style }rules. The first rule whose CELconditionholds applies its CSSstylemap to that field's cell, wherever the field renders. A list view's row rules still style the row, and both may apply.{ condition, style }element was declared inline inListViewSchema. A field definition cannot import it from there (ui/view.zod.tsimportsdata/field.zod.ts, so the reverse import is a cycle and a data-on-UI dependency). It moved topackages/spec/src/shared/conditional-formatting.zod.tsasConditionalFormattingRuleSchema, and bothListViewSchema.conditionalFormattingandFieldSchema.conditionalFormattingmount it. Theobject-grid/object-kanbanblocks still reuse the list view member by identity, so they mount it too. Not exported from thesharedbarrel: no public export is added.valueandrecord, nothing else.@objectstack/lint's field walk (validate-expressions.ts) judges eachconditionalFormatting[i].conditionatos build,os validateand the object save door: it must parse, read fields asrecord.FIELD(never bare), name declared fields, and read no root butvalueandrecord.valuereaches the shared validator throughExprSchemaHint.rootsfor this one call;SCOPE_ROOTSis not widened. A new root verdict,fieldFormattingRootIssue, refusesprevious,parent, the user roots and every other judged root, in its own words (the field-rule family's message namesprevious/parentas bound, and here they are not).stylemap is the row block's, by the identity above.Zone 2 mechanism assumptions, measured
ee8751d41e: inline inListViewSchema(ui/view.zod.ts, astrictObjectinsidez.array), withListViewSchema.shape.conditionalFormattingthe single definition the grid and kanban blocks reuse by identity.data/field.zod.tscannot importui/view.zod.ts(view imports field). So: the second form the dispatch allowed, a rule schema both members share. Pinned by identity infield-conditional-formatting.test.ts(FieldSchema...elementandListViewSchema...elementare bothtoBe(ConditionalFormattingRuleSchema)).215e66204awas read. Its field-rule family bindsFIELD_RULE_BOUND_ROOTS=record,previous,parentand refusesvalueas a bare reference, so this slot could not join that loop: it is a sibling pass beside it, with its own bound set (FIELD_FORMATTING_BOUND_ROOTS=value,record) and the family's membership-test shape.ee8751d41eFieldSchemais astrictObject(field.zod.ts:1053) andauthorable-surface/data.jsonhas nodata/Field:conditionalFormattingrow, so the key was refused before and no stored source can carry a legacy spelling.git grep conditionalFormatting ee8751d41e -- exampleshits only the showcase field-zoo LIST VIEW.node scripts/check-adr-0087-registration.mjs --base origin/main: "this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)", exit 0.c76edeb8c6) and was merged in (5b81e00352; one hand conflict in thecheckclosure, both parameters stacked:servedRowSitethenextraRoots). The new call site passes noservedRowSite, so it is fields-only by construction. TheSERVED_ROW_SITESdocblock names it beside a field'svisibleWhen.Liveness ledger
packages/spec/liveness/field.jsonrowconditionalFormatting:planned, drilled (condition,style) becausecheck:livenessrefuses an undeclared container verdict. The in-repo reader is the authoring gate, which validates and does not make the key do anything, so the ledger'slive("has a runtime consumer") does not apply. Cross-repo, measured: objectui main2a48bd4and the.objectui-shapinf0268ad78readconditionalFormattingonly off list views, the grid and kanban blocks and reports (control:plugin-grid/src/ObjectGrid.tsxcarries 8 hits at the pin); no field-level read. NoauthorWarn(thesettledWhenprecedent). The flip condition is written into the row.Files beyond the claim's declared surface (each one demanded by a gate or by the shared element)
packages/spec/src/shared/conditional-formatting.zod.tsandpackages/spec/src/ui/view.zod.ts: the shared element and the list view mounting it.ui/view.zod.ts:ListViewShapeSchema.conditiontoshared/conditional-formatting.zod.ts:ConditionalFormattingRuleSchema.condition:packages/qa/dogfood/test/expression-conformance.ledger.ts(still thecel-uirow, with a note that the position now has two mounts and the field mount has no evaluator yet) andpackages/spec/src/shared/evaluated-slot-population.test.ts(count stays 34).packages/spec/src/system/metadata-form-zod-reconciliation.test.ts: a rootomitrow forfield.conditionalFormatting, thepage.print"planned, not yet rendered" precedent, instead of a form control nothing would paint.packages/metadata-core/src/object-schema-fls-references.ts(+ test):conditionalFormatting: ruleEntries, so a rule whose condition reads a denied field is dropped whole (a barestylewould read as always-on) and an emptied list is deleted.packages/drivers/driver-sql/src/builtin-column-collision.ts:presentation.packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts:field77 to 78.packages/spec/llms.txt:check:llms-txtcounts (shared 15 to 16, total 202 to 203).authorable-surface/data.json,liveness/state-counts/field.md,content/docs/references/**, the strictness-ledger counts, and threeskills/*/references/_index.mdlines (generator-owned:check-governed-merges.mjs --testreports "0 of 28 path(s) hit the register after 3 generated-artifact lift(s)", NOT governed).Docs
content/docs/data-modeling/fields.mdx: a row in the Conditional Logic table and a "Cell formatting" subsection with the card's worked example (an amount below zero in red), the authoring checks, the presentation-only boundary (a deadline staysdueLike/settledWhen), and one closing status paragraph: the console reads the rules in a later objectui release. That paragraph is dropped on the ledger flip.Tests (all locked runs through
os-verify-lock)Every suite below ran on the merged tree at
14b85354d3. The one later commit,ffce73a2ac, changes onlypackages/spec/llms.txt:check:llms-txtpasses atffce73a2ac, and the one spec test that names that file (in a comment,filter-array-declaration.test.ts) re-ran green there with the new pin file (2 files, 32 passed). Filter direction: per package,--filter PKGitself (no consumer sweep); the consumer radius named by the dispatch (packages/spec,packages/lint,packages/metadata*, the examples) is run package by package.@objectstack/spec--project local@objectstack/spec--project repo@objectstack/lint(whole package)@objectstack/metadata-core@objectstack/metadata-protocol(whole package)@objectstack/metadata@objectstack/driver-sql@objectstack/dogfoodtest/expression-conformance.test.tsobjectstack validate(showcase, crm, todo)typecheck: spec (incl.check:test-typecheck), lint (incl.check:test-typecheck), metadata-core, driver-sql, metadata-protocol, dogfoodNew pins:
packages/spec/src/data/field-conditional-formatting.test.ts(19: identity of the element on both members, accept, order, any field type, control without the key, refusals at the rule with issue path plus the message's first sentence) andpackages/lint/src/validate-expressions.field-conditional-formatting.test.ts(41:valueandrecordaccepted,today(), a non-parsing condition refused naming the field and the rule, bare field, unknown field, every judged root other thanrecordrefused with this surface's verdict and only that one, control on the field-rule family's own slots,os buildand the runtime object door underexpression-invalid), plus one FLS case inobject-schema-fls-references.test.ts.@objectstack/cliintegration tier: not run locally, declared to CI (no spawn entry touched).Ablation (each committed first, mutated through
scripts/ablation-replace.mjs, restored to blob == HEAD with an emptygit diff HEAD; subjects resolve tosrcthrough relative imports, so nodistleg)Expected direction for each: red. Observed: red.
checkcall passes novaluerootfieldFormattingRootIssuereturnsnullalwaysFieldSchema.conditionalFormattingmounts a loosez.objectinstead of the shared elementarrayOf(block(...))) instead of the whole ruleGates
At
ffce73a2ac:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 116 families (the same 116 as before the last commit); with the 51 artifact-roster commands, 166 commands ran.--ran: "116 derived, 115 run, 0 NOT-MEASURED, 1 UNRUN" — the one ispnpm check:dual-build-cjs-loads, NOT MEASURED by dispatch (no whole-workspace build), left to CI. Of the 115: 114 exit 0, andpnpm check:platform-checklistexit 1, which is red identically on a pristineorigin/mainfaf6348508worktree (see Acceptance notes). Roster: 48 exit 0; the three PR-context guards (check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths) answer exit 2 without PR context; they are re-run with this PR's context after it opens, and their verdicts are recorded in theos-dev-reportcomment on #22228.check:generated(spec, after a build): all 15 artifacts current.Acceptance notes
historysentence is now true for both members, and thecolorguidance leads with "put it instyle" before pointing a list view atrowColor. The accept set is unchanged; no test or doc pinned the old wording (git grepof both sentences: 0 hits outside the schema).cel-uirow keepsenforcedfor the list-row mount and its note records that the field mount has no evaluator yet; the liveness row carries the field mount'splannedstate.check:doc-formula-expressionsadmits only the field-level*Whenslots; aconditionnested inconditionalFormattingis outside its arms, so the new docs example is judged by nothing at the corpus level (it is the same predicate the lint pins accept). Noted, not extended.parentgate on this slot, likevisibleWhen: those are write-path gates and nothing on the write path reads it. Whether a cell renderer hydratesrecord.REFERENCE.FIELDis for objectui's card to declare (carrier: the objectui renderer card).authorWarn. Until objectui reads the key an authored rule styles nothing; the docs status paragraph and the changeset say so. A warned field row would reopen the lint's field walk for one display key (thesettledWhenprecedent).check:platform-checklistis red onorigin/mainitself (faf6348508, a pristine worktree:areas/attachments-storage.jsonABSENT SYMBOLattachment-access-hooks.ts#canEdit, anchors 27 of floor 28). This diff touches neither file.Generated by Claude Code