Repository navigation
feat(spec)!: object-kanban conditionalFormatting takes the list view's own rules, and its hold exits (#21464, S-kanban-cf) - #21711
Conversation
…s own rules (#21464, S-kanban-cf) The object-kanban page block's conditionalFormatting was z.unknown(), held while objectui's kanban authored a second rule dialect. objectui's kanban now declares the list view's { condition, style } rule as its only authoring dialect, so the row takes ListViewSchema.shape.conditionalFormatting by reference, as object-grid does. The enumeration pin drops the held-for-decision line and pins the member in a new section; one D3 entry, its step-18 rationale fragment and the changeset carry the narrowing. Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…itionalFormatting Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 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 44900e55d7fed22a383fd0578499fdd2a0037467 && git checkout 44900e55d7fed22a383fd0578499fdd2a0037467
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7e0066af7a8e05709dc60096c69bc85e299d0331 011e53fa2ad81dbddbfad0e5ac57c05274f196ec && git checkout -B drift-repro 7e0066af7a8e05709dc60096c69bc85e299d0331 && git merge --no-ff 011e53fa2ad81dbddbfad0e5ac57c05274f196ec
node scripts/docs-audit/affected-docs.mjs --json 7e0066af7a8e05709dc60096c69bc85e299d0331 |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37193945077 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Part of #21464
Clause-②: yes (narrowing)
What this does
The S-kanban-cf stage of the
ComponentPropsMapz.unknown()close-out: the exit of theobject-kanbanconditionalFormattinghold, per the claim5977940136, the landing note5977935364, triage's direction5961300594and the seat answer5963787404(item 2). The census ran first. It found no working writer that the list view's member refuses, and the reader's declared dialect is exactly the list view's rule. So the member is typed by reference (def identity) toListViewSchema.shape.conditionalFormatting, asobject-grid's is. The enumeration pin'sheld-for-decisionline leaves the ledger, and so does the emptiedheld-for-decisionstage..objectui-shapinab1879721595object-kanbanconditionalFormattingz.unknown()ListViewSchema.shape.conditionalFormatting, by reference:[{ condition, style }], a non-blank CELcondition(string or{ dialect, source }envelope) and astylemap of stringsKanbanBoardCore.tsx:114handsschema.conditionalFormattingto the board;getCardStyles(KanbanImpl.tsx:182) evaluates it throughresolveConditionalFormatting(core/src/evaluator/listConditional.ts:532), the evaluator the grid's rows use (ObjectGrid.tsx:2970)No new export and no new schema: the row reads the list view's member, so one rule is judged the same way on the list view,
object-gridandobject-kanban.The hold's exit
5963787404item 2): objectui's kanban authored a second rule dialect, the native{ field, operator, value, backgroundColor }rule and the flat colour rule, which the list view's member refuses.completed, landed by objectui#11532 (c73cdb5695).object-kanban'sconditionalFormattingtakes the spec list view's{ condition, style }rule only, and the native and flat-colour dialects are refused by name.git merge-base --is-ancestor c73cdb5695 ab1879721595exits 0, and so does the pin against objectuimain2e818d0b51. Exit 0 proves itself in any checkout, so no control leg is owed.KanbanConditionalFormattingRuleSchemaextends the spec list view's rule element, read by reference (types/src/zod/objectql.zod.ts:337,:3074), with tombstones forfield,operator,value,backgroundColor,borderColorandtextColor. Its TS twinKanbanConditionalFormattingRuleextendsSpecConditionalFormattingRule(types/src/objectql.ts:5166). The registration's description reads the same (plugin-kanban/src/index.tsx:676, recorded in this repo'ssdui.manifest.json).expressionand top-level colour arms, for a rule a relay hands the board in a stored dialect (KanbanImpl.tsx:160-166,listConditional.ts:495-499). It is the grid's evaluator, unchanged. No authoredobject-kanbanwriter uses those arms (census below), and objectui's own faces refuse them.conditiontakes a string first, so its mirror still admits a blankcondition(objectql.zod.ts:363-366, pinned by objectui'sspec-expression-wire-slots-10946.test.ts:151-155). The evaluator answers a blank predicate with the caller's fallback (listConditional.ts:252-260, single-eval route:339-340), and the board passesfallback: false(:546), so such a rule paints no card. It is refused here, as on the list view and onobject-grid. This was read, not run.main2e818d0b51:KanbanBoardCore.tsx,KanbanImpl.tsx,ObjectKanban.tsx,plugin-kanban/src/index.tsx,listConditional.tsandObjectGrid.tsxare byte-identical (git diff --quiet, exit 0 each). The two@object-ui/typesfiles changed only aroundgrouping: zero changed lines mentionconditional, and the rule schema's line3074did not move.The census
A writer is a value written on the block: a page-component node (an object literal naming
object-kanban, flat or in itspropertiesbag, or a literal asserted as one), a direct parse through the row, the block's React component inside itsschemaprop, or the argument of a same-file helper that mounts one. Values resolve through same-file constants and spreads, and parameters resolve at every same-file call site andit.eachtable. Instrument 1 is a TypeScript-AST walk over every tracked file namingconditionalFormatting(code,.json, and fenced code in.md/.mdx). It records eachconditionalFormattingmember with its static value and block context, and parses the value through the list view's member. Instrument 2 parses every rule-shaped object within 400 characters after aconditionalFormattingtoken, in any syntax (tuples,key:/value:rows), as a one-rule list. Each hit outside a block context was read by hand. Controls: instrument 1 finds the showcase's list-view writer and theobject-gridpin's writers in this repo.object-kanbanwriters16d241a6af(every tracked file; 33 nameconditionalFormatting)packages/spec/src/ui/component.test.ts:3947ab1879721595(94 files name the member, 61 of them alsokanban)main2e818d0b514054ec26802205b53010quickAddretirement:[{ field: 'priority', value: 'high' }]. It has nooperator, so the evaluator built no predicate from it (listConditional.ts:474) and painted nothing, and objectui's own faces refuse it. Its assertion is about the key, not the value, so it is respelled to{ condition: "record.priority == 'high'", style: { backgroundColor: '#fee2e2' } }and still assertssuccess: true.{ condition, style }: the board test's three rules through its mount helper (ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx:472,:492,:509), the asserted node inobjectFieldsIsAPropNotASchemaKey-7742.test.tsx:97, the declared-keys row instructuredKeysAreDeclaredAndHonoured-8313.test.ts:109(which parses through this very row on the installed spec), the live-member row inobject-kanban-allow-collapse-retired-8801.test.ts:198, the dialect test's control (kanban-conditional-formatting.test.ts:48), and the wire-slot test's string and envelope conditions (spec-expression-wire-slots-10946.test.ts:123).kanban-conditional-formatting.test.ts:88), the flat colour rule (:103), a colour besidestyle(:118, three keys), an undeclaredlabel(:139), and three malformed conditions (spec-expression-wire-slots-10946.test.ts:158). The tenth is that file's probe that its mirror still admits a blankcondition(:152), which the board answers with no paint (above).KanbanBoarddirectly (cardPredicateScope.test.tsx). Nine are view-face relays of a list view's rules (ObjectView.kanbanConditionalFormatting.test.tsx,objectViewHostSurface.test.tsx). The other members in those 61 files areobject-gridor list-view writers, which the list view's member already judges. The non-test hits are run-time hand-offs and the schema declarations. objectuimainadds one non-writer (InterfaceListPage.relayCensus-11572.test.ts, a relay census record).conditionalFormattingat all, case-insensitive and snake case included. Controls:kanbanin 48 and 35 files, andobjectNamein 115 and 145.Changes
packages/spec/src/ui/component.zod.ts: theobject-kanbanrow'sconditionalFormattingisListViewSchema.shape.conditionalFormattingwith a card-level.describe(), and its docblock records the read points, objectui's declaration, the two differences and the census. The import comment names the kanban member beside the grid's.packages/spec/src/ui/component-props-unknown-members.pin.test.ts: theheld-for-decisionline and stage leave, along with the now-unusedstaged()helper. The kanban rule'sconditionalFormatting[].condition.astjoins the expression-AST lines, exactly as the grid's does. The new §5 pins the def identity, three accepted values parsing to what the list view's member answers, eight refusals bycodeand path, the absent case, and the D3 id. The header records the exit.packages/spec/src/ui/component.test.ts: the one census writer, respelled (above). This file is outside the claim's file list. It is the member's own fixture, and the change reddens it otherwise.packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts(new) andpackages/spec/src/migrations/registry.ts: one step-18 D3 entry (the semantic region regenerated bygen:migration-registry), and one rationale fragment at order 77, the next free order onmainat the base (73 to 76 are taken, 74 twice), inserted where its id sorts. [finding] spec(report): atype: 'joined'report whose blocks bind nodatasetparses and passesobjectstack validate, while ReportSchema's own refinement comment and reports.mdx say "each block dataset-bound" #21702 also adds a fragment, so whichever lands later re-reads the order and mergesorigin/mainthroughscripts/pm/os-regen-merge.sh. No D2 conversion and noRETIRED_KEYS_BY_MAJORrow: a page component'spropertiesis not parsed on the save or load path, and the refused values are nested member values.content/docs/references/ui/component.mdx: regenerated bycheck:generated --fix, which proved onlycheck:docsstale..changeset/21464-component-props-kanban-conditional-formatting-typed.md:@objectstack/specminor, BREAKING banner, theClause-②line, FROM → TO, the census, and the ADR-0087 marker registeringui-object-kanban-conditional-formatting-typed.Measurements
Head
011e53fa2a(this branch merged withmain7e0066af7a, a comment-only re-anchor of spec test citations that touches none of these files). Base16d241a6af. Heavy runs went throughscripts/pm/os-verify-lock.sh, and every exit code was captured before any pipe.@objectstack/spec@17.6.0(the npm tarball,ComponentPropsMap['object-kanban'].safeParse) and onmain16d241a6af(built): each of these is ACCEPTED on both:conditionalFormatting: 42,'red', the native rule, the flat colour rule, anexpressionrule, a rule with nostyle, a blankcondition, and astylevalue of5. Control:navigation: 42is REFUSED there (invalid_typeatnavigation). Each junk value is refused on this branch, pinned in §5 bycodeand path.pnpm --filter @objectstack/spec testat011e53fa2a: exit 0, Test Files 612 passed (612), Tests 18199 passed, 1 todo, nothing skipped. The same at27a0e12a09, before the merge.pnpm --filter @objectstack/spec typecheckat011e53fa2a: exit 0,check:test-typecheck: OK(52 files / 246 errors / 135 signatures held).tsc -p tsconfig.test.json --listFilesOnlynames both touched tests and the new D3 entry.check:generatedproved onlycheck:docsstale,--fixregenerated that one, and the re-check at011e53fa2areads "All 15 generated artifacts are up to date".check:api-surfacereads "public API surface + factory signatures unchanged".turbo run build --filter=!@objectstack/docs --concurrency=2built 72 of 72 tasks.pnpm --filter @objectstack/lint test(after building lint's dependency closure,--filter '@objectstack/lint^...'): 119 files passed, 5622 tests passed, 5 skipped. The 5 read lint's owndist; afterpnpm --filter @objectstack/lint build,validate-component-props,lazy-depsandruntime-lazy-depspassed 61 of 61, nothing skipped. The first lint run before that closure build failed 56 files onFailed to resolve entry for package "@objectstack/formula"/"@objectstack/sdui-parser": unbuilt dependencies, not a measurement.validateComponentPropsfrom lintsrcover the built spec, oneobject-kanbannode per page). These report nothing: a{ condition, style }rule, and an envelope condition. These are reported:42ascomponent-props-invalidatproperties.conditionalFormatting; a rule with nostyleascomponent-props-invalidat…conditionalFormatting.0.style; the native rule ascomponent-props-invalidat.0.conditionand.0.stylepluscomponent-props-unknown-keyat.0.field,.0.operator,.0.valueand.0.backgroundColor; anexpressionrule at.0.conditionpluscomponent-props-unknown-keyat.0.expression; and a blank condition ascomponent-props-invalidat.0.condition.27a0e12a09; the merge did not touch these files). The driver usednode scripts/ablation-replace.mjsin wrap mode, inside its ownEXIT INT TERMrestore trap on the absolute path, with an empty hash read as failure, against HEAD blob830a02f619. The kanban member went back toz.unknown().optional(): anchor x1 → x0, replacement x0 → x1, blob →4b67209318, re-counted in the leg (ListViewSchema.shape.conditionalFormatting2 → 1). Red: 12 failed, 421 passed. §1 received exactly[ "object-kanban conditionalFormatting" ]as unlisted, and[ "object-kanban conditionalFormatting[].condition.ast" ]as an outliving line. §5 failed its identity, the string-condition parse and all eight refusals. Still green, as expected underz.unknown(): the envelope and empty-list parses, the absent case, the D3 id, and §1's size control (one member in, one out). Restore: blob equals HEAD (830a02f619),git diff HEADis empty, andgit statusis clean. The pins import./component.zodfrom source, so no build ordistpreflight sits between mutation and run.011e53fa2a:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths; merge base7e0066af7, 7 paths, 276 changed lines) derived 114 commands, all exit 0.--ranprinted "114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN". At27a0e12a09the first pass recorded six exit-3PREREQUISITE NOT METrefusals for unbuilt packages. After the builds above they re-ran exit 0, and the pass at011e53fa2aran all 114 clean.check-widening-tellsover the branch diff:--declaration noexits 0. That is no tell: the row takes an existing member by reference and adds no key, arm or export.--declaration yesexits 0.eslint --no-inline-config --format jsonover the 5 changed TypeScript files reports 5 files, 0 errors and 0 warnings. The population comes from eslint's own--print-config: those 5 resolve a config, and the changeset andcomponent.mdxresolve none. Invariance:eslint.config.mjsnever enables type-aware linting (its own text, about line 327), so this diff cannot move a verdict on an untouched file. The fullpnpm lintis CI's.pnpm lintare CI-owned. objectui was read at the pin and atmain, not built against this spec. The board's paint for a blankconditionwas read, not run. The showcasevalidatewas not run: no example in this repository authors anobject-kanbannode. CI on this PR was not waited on.type: 'joined'report whose blocks bind nodatasetparses and passesobjectstack validate, while ReportSchema's own refinement comment and reports.mdx say "each block dataset-bound" #21702's branch (c4e3ab9631) also takes order 77 forui-report-joined-block-dataset-required. That is not refused, becausejoinRationalebreaks a tie by id. Whichever lands later re-reads the next free order and mergesorigin/mainthroughscripts/pm/os-regen-merge.sh.Acceptance notes
@objectstack/specbump reads this row in one place only,structuredKeysAreDeclaredAndHonoured-8313.test.ts, whose value parses. objectui'sobject-kanbannode is its own flatObjectKanbanSchemaand takes nopropsBagover this row. The console's member-pin prose forobject-kanban.conditionalFormatting(apps/console/src/__tests__/registry-inputs-spec-parity.test.ts:3003) says "The spec row isz.unknown()", which goes stale with that bump. Carrier: objectui's next spec bump. Noted, not filed.object-grid, and the board paints nothing for it. Noted, not filed.fieldis not a propobject-kanbandeclares" for a key inside a rule. The path (properties.conditionalFormatting.0.field) and the zod text ("on this conditional formatting rule") are right. The wording predates this change and reads the same onobject-grid. Noted, not filed.Generated by Claude Code