Repository navigation
feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) - #22421
Conversation
…t-grid defaultFilters (#11509) Tombstones, the two protocol-18 conversions, the registry entries and the absorbed step-18 narrowings. Tests and generated artifacts follow. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…e three data-source-bound elements Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…e retirement moves Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ter pins follow the binding Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ement-binding-retirement
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 139 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 714d8f2c2d2291474fa96f3adbbad797b1b86686 && git checkout 714d8f2c2d2291474fa96f3adbbad797b1b86686
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 76bc1e03a3237150934a35370406a5ddd7cde4fb 5faf12295faaf4d53d2d9d797e90470685084e67 && git checkout -B drift-repro 76bc1e03a3237150934a35370406a5ddd7cde4fb && git merge --no-ff 5faf12295faaf4d53d2d9d797e90470685084e67
node scripts/docs-audit/affected-docs.mjs --json 76bc1e03a3237150934a35370406a5ddd7cde4fb
|
…ement-binding-retirement # Conflicts: # packages/spec/dropped-refinements.baseline.json # packages/spec/src/ui/component.zod.ts
… first and keeps its flat keys as a fallback Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ording Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…ement-binding-retirement
…ement-binding-retirement Merges origin/main 76bc1e0 through scripts/pm/os-regen-merge.sh. One text conflict, in the hand-edited ledger packages/spec/dropped-refinements.baseline.json (it has no generator by design): only the two `measured` totals collided. The branch retires three rows (220/680 -> 217/677) and main adds six schemas and eight sites (220/680 -> 226/688), so both stack at 223 entries / 685 sites, which is what the merged `entries` list itself counts. The build's ledger check is the authority and is re-run on this tree. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…e merged tree Step 4 of the os-regen merge of origin/main 76bc1e0, through the packages' own generators (`gen:spec-changes`, `gen:upgrade-guide`; `gen:migration-registry` rewrote registry.ts byte-identically). The committed spec-changes.json main brought still listed the three step-18 entries this branch absorbs (element-number-filter-rule-array, element-record-picker-filter-rule-array, object-grid-default-filters-rule-array) and lacked this branch's two D3 entries. `check:spec-changes` no longer compares the committed copy (it generates in memory), so it read green, while `check-adr-0087-registration` reads the committed copy at HEAD as its parser witness and refused with "ledger parser drift". Regenerating the committed copies from the merged registry clears it. The copies also pick up registry text main changed without regenerating them (the objectui pin readings at 20c6d351a, the storage-scope and flow-slot guidance). Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
…n again At objectui 20c6d351a nothing reads a tree `titleField`: the renderer's `getTreeConfig` rung went on objectui#8841, and the `case 'tree'` arm's flatten rung went on objectui#6152 round 14 (objectui `3fd862510`), so `ListView.tsx:3985` now reads `treeCfg.labelField || 'name'` and emits no `titleField`. The OBJECT_TREE_FLAT_CONFIG_GUIDANCE docblock and the last sentence of its prescription said the key was "only ever read as that key's last fallback", a read that no longer exists. The prose now says why the key stays in the set (for its prescription, not for a read), and the prescription still names `tree.labelField`. The flat key set and every schema shape are unchanged; keeping or dropping `titleField` from the set is a schema decision outside this change. The in-test note beside the key-set pin gains the same dated reading. Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn Co-authored-by: Claude <noreply@anthropic.com>
维护者速读
改了什么
为什么改
风险与代价(含回滚)
席位意见
你要做的(一个动作)审阅并亲手合并 PR #22421。 Generated by Claude Code |
Contract reviewServed-tier: Read: card #11509's body and all 23 comments (the ruling Form. Draft; head repo equals base repo; line 1 Gates on the head. 46 check-runs, none still running: 39 ① Derived judgmentsEach row is an accept-set or public-surface change the diff implies, read off the schema, the lint rule, the conversion registry and the ledgers against their
② Semver level
③ Boundary flags
Breaker readings: none. Nothing widens; no governed path; no release act. Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11509
Clause-②: no (narrowing: the ten element-layer flat data-binding keys and
object-grid.defaultFiltersleave the accept set, and the component-props gate'sdataSource.objectwaiver becomes a refusal)ADR-0087 disposition, as the changeset's marker carries it:
registered element-flat-data-binding-retired, object-grid-default-filters-retired(.changeset/11509-element-flat-binding-retired.md).Consumer pin rule (triage
6071460039): a consumer that pins both repositories moves its objectui pin past objectui#11880 no later than its objectstack pin moves past this retirement. The converted shape is one only objectui after objectui#11880 reads.Landing gate: met. The gate is the claim's (
6072058120, ruling sub-question 2), moved by the seat (6080485027) to #22476:.objectui-shamust cover the objectui merge that repairs the designer row named in objectstack-ai/objectui#12056.dataSource). #11509's v18 retirement must ship with it #22476 landed (PR chore(objectui): bump the console pin to 20c6d351ad74 (carries objectui#12056) #22592,ba956070b1), and.objectui-shaonmainis20c6d351ad74. This branch merged thatmain(76bc1e03a), and does not move.objectui-shaitself.2a48bd408dab(the merge of objectui PR [finding] bare-root-worklist prints REACHABLE beside a REFUSE-* reason when a gate takes the escape, and --self-test stays green — the contradiction lands silently #12064, the repair for page-block inspector: theelement:repeaterrow still writesproperties.objectandproperties.limit, the flat keys objectui#11880 moved todataSourceand objectstack#11509 retires in v18 objectui#12056) is an ancestor of20c6d351ad74:git merge-base --is-ancestorexits 0, andrev-list --countreads 7 ahead / 0 behind, agreeing with thedomain:devxseat's REST compare (6093574958). The first gate, objectui5bc55c0c5a1e, is an ancestor too (exit 0).20c6d351ad74bygit show(the objectui checkout's HEAD did not move). Studio's page-block inspector row forelement:repeaterispackages/app-shell/src/views/metadata-admin/previews/block-config.ts:344-351. Its Object picker (:345) and its Limit box (:348) both carryat: 'dataSource'.PageBlockInspector.tsx:800hands everyat: 'dataSource'field topatchBinding, andpatchBinding(:483-484) writesdataSource: { ...binding, [name]: value }. No designer row writes a retired flat key: theelement:numberrow (:357-358) writesdataSource.objectas before, andelement:record_pickerhas no page-block row (the palette excludes it as page content,block-types.ts:208).What this does
Ruling
6051498447(A-narrow, all four sub-questions at their recommendation), carried out as the claim states it:element:record_pickerobject/filter/sort/limit,element:numberobject/filter,element:repeaterobject/filter/sort/limit, andobject-grid.defaultFiltersareretiredKey()tombstones inpackages/spec/src/ui/component.zod.ts. Each is refused at its key with the prescription (usedataSource.KEYon the node; what to do where the binding already sets it, by that element's old rule), and its input type isnever. The repeater's other spellings (objectName,filters,where,orderBy,sortBy,top,pageSize) were aliases of the flat keys and now point at the binding. Theobject-*block layer and the relationship-scoped blocks are unchanged.page-component-filter-record-to-rule-arrayat 36).element-flat-data-binding-to-data-sourcefollows each element's old rule: a key the binding lacks moves there; a key the binding already set is deleted where the binding always won (the picker, andelement:number'sobject);element:number's flat filter is appended to the binding's (the two always AND-combined). It reports a TODO and leaves the key in three cases: a picker key beside adataSource.viewthat sets no such key of its own, a repeater key the binding sets to a different value or beside aview(the repeater read neither until the console put its binding first, so what it applied depends on the console version), and anelement:numberfilter pair that is not two rule arrays.object-grid-default-filters-removedis the shapedefaultSort's retirement took. Whenfilteris empty (absent,null,[]or{}), the rules move into it. Whenfilterhas content, the key is deleted. Whenfilteris a value no lowering reads (a bare string), it reports a TODO rather than overwrite it.filteranddefaultFiltersdoors ofpage-component-filter-record-to-rule-array) are dropped from that entry's reach.packages/lint/src/validate-component-props.tsthe type-blindsuppliedByDataSourcewaiver is gone. One of the three elements with nodataSource.objectis the rule's existingcomponent-props-invalidfinding (warning tier, at…dataSource.object); this is not a new rule. The docs gate's twin waiver (packages/spec/scripts/check-yaml-examples.ts) turns with it.dataSourceis the clean shape: zero findings, and objectui reads it since objectui#11880. A repeater aimed by a flatobjectis refused twice: the tombstone at the key and the missing binding. Pinned invalidate-component-props.test.tsand in the docs gate's self-test.RETIRED_KEYS_BY_MAJOR[18]rows. There are two D3 entries (element-flat-data-binding-retired,object-grid-default-filters-retired), each with a step-18 rationale fragment.authorable-surface/ui.json(11 rows[RETIRED]) andcontent/docs/references/ui/component.mdxare regenerated by the repo's generators. The hand-editeddropped-refinements.baseline.jsonloses three rows whose only dropped refinement was a retiredfilter. The committedpackages/spec/spec-changes.jsonanddocs/protocol-upgrade-guide.mdare regenerated from the merged registries (gen:spec-changes,gen:upgrade-guide); see "This round" below.object-treetitleFieldrationale, corrected (offered by thedomain:devxseat,6093574958item 2). At objectui20c6d351anothing reads a treetitleField: the renderer'sgetTreeConfigrung went on objectui#8841, and thecase 'tree'arm's flatten rung went on objectui#6152 round 14 (objectui3fd862510), soListView.tsx:3985readslabelField: treeCfg.labelField || 'name'and emits notitleField. TheOBJECT_TREE_FLAT_CONFIG_GUIDANCEdocblock and the last sentence of its prescription said the key is "only ever read as that key's last fallback". Both now say the key stays in the set for its prescription, not for a read, and the prescription still namestree.labelField. The flat key set and every schema shape are unchanged, and the in-test note beside the key-set pin (component.test.ts) gains the same dated reading. Item 1 of that offer (kernel/functional-completeness.ts) is outside this PR's files and is not taken.Premises, verified on
origin/mainbb4f5cc00before the first editobjectkeys were atcomponent.zod.ts:2602(element:number),:3015(element:record_picker) and:4134(element:repeater). The repeater docblock's "NOT read at the pin" is at:4117.suppliedByDataSource(validate-component-props.ts:169-185) checks the issue path anddataSource.object, never the component type.defaultFiltersisz.array(ViewFilterRuleSchema)(:4563), and thedefaultSortprecedent isobjectGridDefaultSortRemoved(conversions/registry.ts:9264).examples/**,skills/**and the hand-writtencontent/docs/**. The one element-layer author, the showcase picker (page-variables.page.ts:59), already writesdataSource. The spec, lint and metadata-protocol tests that authored the flat keys as fixtures are re-bound here, and the tree-scoped absence pin found two of them that grep had missed (page.test.ts,i18n-resolver.test.ts).skills/objectstack-ui/rules/pages.mdnameselement:number/element:repeateronly as printable blocks and teaches no key, so noskills/**edit is needed and no hand-written docs page needed an edit.Decisions the reviewer should check
object-grid-default-filters-rule-array.element-number-filter-rule-arrayandelement-record-picker-filter-rule-arraynarrowed the two element flatfilterkeys this PR retires in the same unreleased major, and their acceptance criteria assert parses this PR refuses (safeParse({ object, aggregate, filter })succeeds). That is the same-major bookkeeping of thespec-property-retirementplaybook (§0) and the measurement's "same-major bookkeeping against … its semantic entries". The record-form guidance they carried is the binding's own entry,element-data-source-and-object-block-filter-rule-array. The CLI's migrate-meta guidance pin drops the three absorbed ids from itsREWRITTENfloor. The absorbing entries are not added there, because the list admits only entries rewritten off a tracker id and these two were born without one; the printed-block case holds them with the whole directory.component.zod.ts. The conversion registry and the lint rule each keep a copy, and each copy is pinned against the tombstones by probingComponentPropsMap. Publishing it would have widened a narrowing-only retirement.@objectstack/spec 17, not 18.check:future-spec-major(ADR-0087, amended 2026-09-13) refuses a tombstone that names the next major. The migrate sentence is--from 17, as withdefaultSort.XParsedaliases of the three element props stay, although their two shapes now coincide. Deleting a published type name would be an export removal of its own.Acceptance notes (not filed)
packages/lint/src/page-walk.ts:135-136(:137before the merge ofmain76bc1e03a) still resolves a component's object asdataSource.object ?? properties.object ?? page.objectfor the field-binding rules, andcontent/docs/deployment/validating-metadata.mdx:157describes that chain. After this PR the flatobjecton the three elements is refused by its tombstone, so the fallback only ever reads a refused key (andelement:metadata_viewer's metadata owner). This PR does not change it: the file is outside the claim. Carrier: none named; the next card that touchespage-walk.ts.propertiesmapping, so a baretype: element:repeaterfence with neitherpropertiesnordataSourceis not reported there. The lint rule judges it. This is pre-existing dispatch behaviour.mainre-measured at20c6d351a(theOBJECT_TREE_FLAT_CONFIG_GUIDANCEdocblock header, from chore(objectui): bump the console pin to 20c6d351ad74 (carries objectui#12056) #22592) is kept verbatim, as ordered, with two slips in it. It cites the arm'slabelFieldread as:3986, but at20c6d351athat read is:3985, and:3986isfields: treeCfg.fields || effectiveFields,. Its opening sentence still citesListView.tsx:3913-3932, the range its own re-read moved to3965-3989. Neither is an asserting quote, so no gate reads them. The correctedtitleFieldparagraph cites:3985. Carrier: the next re-read of that record at a pin bump.os validaterefuses the clean repeater on an html-tier page and accepts the retired one. Measured throughnode packages/cli/bin/run.js validateat this head, on a probe app carrying this repo'ssdui.manifest.json:element:repeatertag carrying onlydataSource={{ "object": "probe_task", "limit": 5 }}gets exit 1 withjsx-missing-required-prop(the repeater "is missing required prop "object"").object="probe_task"instead gets no finding.20c6d351a(components/src/renderers/basic/data-list.tsx:320-326,objectrequired: true), whichsdui.manifest.jsoncarries. The manifest also still lists the flatfilter/sort/limitinputs, andelement:number's flatobject/filter.20c6d351aparse flatobjectagainst the spec's element schemas (record-picker-inputs-spec-parity.test.ts,record-picker-empty-text-i18n.test.tsx,types/src/__tests__/element-repeater-data-source-11880.test.ts). They resolve@objectstack/spec^17from npm, so nothing reds now. They will need their fixtures re-bound when objectui moves to spec 18. Carrier: objectui's spec-18 upgrade.spec-changes.jsonand upgrade guide onmainlagmain's own registries. Since spec(changes): generate the per-major spec-changes section and the upgrade guide at publish; the pull request generates both in memory and renders the diff (#22449 B′, condition 1) #22533,check:spec-changesandcheck:upgrade-guidegenerate in memory and no longer compare the committed copies.check-adr-0087-registrationstill reads the committedspec-changes.jsonat HEAD as its parser witness, so a PR that removes a registry entry reads "ledger parser drift … widen extractIds()". The real remedy isgen:spec-changes. That is the red this round fixed (below). Regenerating also carried textmainhad changed in other entries' registries (the objectui pin readings at20c6d351a, the storage-scope and flow-slot guidance). Carrier: spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485, which deletes the committed copies.This round:
mainmerged, the gate re-measured, the Check Changeset red fixedorigin/main76bc1e03awas merged byscripts/pm/os-regen-merge.sh(merge commit1eb154c6f, no rebase, no force-push). The pre-merge tip wasd8e0996f4, and the merge base35ef501e1.packages/spec/dropped-refinements.baseline.json, only its twomeasuredtotals. That ledger has no generator by design (its header andscripts/lib/dropped-refinements.tssay why), so the order's "regenerate it" has nothing to run. It was resolved by hand:main's 220/680 → 226/688 (six schemas, eight sites) stack at 223 entries / 685 sites;entrieslist counts exactly that;component.zod.ts,component.test.ts,migrations/registry.ts,i18n-resolver.test.tsandprotocol.stored-migration.test.ts. Every linemainadded to those files between35ef501e1and76bc1e03ais present at this head, with one exception: the object-treetitleFieldparagraph'sListView.tsx:3927line, which item 6 replaces on purpose. Somain's re-measured pin readings stand verbatim, and the retirement sits on top. One example is the repeater record, which keepsmain'sdata-objectstack/src/index.ts:5048-5059with this PR's tense change and REMOVED paragraph.gen:migration-registryrewroteregistry.tsbyte-identically.content/docs/references/ui/component.mdxandauthorable-surface/ui.json(branch-only edits).check:generatedreads 15/15 current.675b121bb.Check Changesetwas red at1eb154c6f(check-run114130272368, "Require an ADR-0087 disposition on a declared-breaking changeset").node scripts/check-adr-0087-registration.mjs --base origin/mainexit 1, "ledger parser drift: 3 id(s) present in the generated packages/spec/spec-changes.json are NOT found by extractIds()". The three wereelement-number-filter-rule-array,element-record-picker-filter-rule-arrayandobject-grid-default-filters-rule-array, the entries this PR absorbs.main's committed copy still listed them.gen:spec-changesandgen:upgrade-guideregenerated the committed copies from the merged registries. At675b121bbthe gate exits 0: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered element-flat-data-binding-retired, object-grid-default-filters-retired".check-changeset-no-major.mjs --base origin/mainexits 0 at both heads (pre mode, tagnext). No label and no level was changed.5faf12295. Item 6 above.20c6d351a.pnpm --filter @objectstack/spec check:objectui-pin-citationsexit 0: 55 asserting citations match.objectui-sha(20c6d351a), and 145 historical ones are recorded and not checked.--verify-anchors(OBJECTUI_ROOT=../objectui, read bygit showat the pin) exit 0: 13 anchor content assertions verified against objectui at20c6d351a..objectui-shacitation. The objectui anchors it adds are two:data-list.tsx:320-326(the repeater registration), byte-identical acrossf0268ad78…20c6d351a, so it holds;ListView.tsx:3985, read at20c6d351a.main's and stay verbatim.spec-changes.jsonanddocs/protocol-upgrade-guide.md), +2789 / -1521 = 4310 changed lines. That is over the 3000-line human-merge line (chore(governance): the human-merge line threshold is 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22490).Tests and gates
All readings are at head
5faf12295, and every exit code was captured before any pipe. Heavy runs went throughscripts/pm/os-verify-lock.shon a shared box.@objectstack/spec, both tiers in full (vitest run --project local --project repo --maxWorkers=4): 694 files, 20015 passed, 1 todo, 0 failed. Lock verdict: command-exit 0, held 16m44s.pnpm --filter @objectstack/spec typecheck: exit 0.@objectstack/lint, full (vitest run --maxWorkers=4): 134 files, 6128 passed.typecheck: exit 0.@objectstack/cli:vitest run --project unit): 277 files, 4105 passed;test/migrate-meta-engine-guidance.test.ts, an integration-tier file this PR edits (--project integration): 3 passed;typecheck: exit 0;@objectstack/metadata-protocolsrc/protocol.stored-migration.test.ts: 40 passed.examples/app-showcasetest/my-work-visibility.test.ts: 7 passed.turbo run build --filter='@objectstack/cli^...'(58/58 tasks), and the CLI build. A locked full build followed (72/72 tasks).node scripts/pm/dispatch-gates.mjs --commandsderived 120 at5faf12295.check:skill-examplesandcheck:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: nodist/forclient-reactand eight others). After the full build both exit 0.--ran: "120 run, 0 NOT-MEASURED".check:pm-dispatch-gates: 1825 cases pass (fast tier; 6 slow sections deferred).5faf12295, read once when the report was written (not awaited): all 35 check runs completed, 33 success and 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), none red.Check Changesetis green again.维护者速读(草稿)
改了什么
properties里的数据绑定键:element:record_picker:object/filter/sort/limit;element:number:object/filter;element:repeater:object/filter/sort/limit。dataSource上,即dataSource.object、dataSource.filter、dataSource.sort、dataSource.limit。repeater 原来的别名(objectName、filters、where、orderBy、sortBy、top、pageSize)也一并指向dataSource。object-grid的defaultFilters不再接受,规则改写到filter。os validate对缺少dataSource.object的这三个元素给出警告。os migrate meta --from 17自动改写。改写不了的情况会留一条 TODO 提示作者手工处理:两种写法取值不同,或者dataSource指向了一个已保存的视图。.objectui-sha=20c6d351a)上,已经按新形状写这三个元素。repeater 的“对象”和“条数”两栏写入dataSource,作者在 Studio 里拖出来的组件不会被拒绝。为什么改
properties和dataSource),三个组件各自用不同规则合并两边,作者写下的值不一定生效。dataSource时,os validate显示通过,页面却显示“No records”。6051498447)定为:只保留dataSource一个入口,先让 objectui 读写新形状,再在 spec 里收回旧键。这一步现在已经具备条件。风险与代价(含回滚)
@objectstack/spec与@objectstack/lint带 major changeset。没有跑迁移的旧元数据会在校验时报错。20c6d351a两者都已包含。os validate仍要求element:repeater标签写平铺的object,并拒绝只写dataSource的写法。原因在 objectui 的组件注册清单,已作为发现交给席位处理。dataSource/filter的元数据,在旧版本里本来就是合法写法,回滚后不需要再迁移回去。席位意见
你要做的(一个动作)
审阅并亲手合并 PR #22421。
Generated by Claude Code