Repository navigation
feat(types,core)!: an authored flex takes its props in the properties bag; the flat spelling is refused by name (objectui#11276) - #11406
Conversation
… bag; the flat spelling is refused by name The `flex` batch of objectui#11276, under the maintainer's ruling A on objectui#11300. `@objectstack/spec`'s strict page component refuses a prop written on the node as mis-layered (ADR-0089 D3a); objectui's `flex` arm was the flat mirror, so the strict face refused the objectstack showcase's bag-shaped `flex` nodes and both faces accepted the flat node `os validate` refuses. - `FlexBlockSchema` arms the authored node: `BaseSchema` + the literal + `NODE_ENVELOPE` + `properties`. The spec has no `ComponentPropsMap['flex']` row, so the bag is the flat mirror's own members by reference (the `object-chart` construct); the child list is in the bag, its list entries judged once by the spec page walk. Flat members are refused by name through the shared `flatPropRefusals` (new optional no-row guidance), `body` toward `properties.children`. `FlexSchema` stays published as the post-hoist node. - objectui's own producers move to the bag: 70 schema-catalog documents (248 nodes), `layout.md`, `flex.mdx`, the `@object-ui/types` README and examples. `@object-ui/core`'s `flex()` builder emits the bag. - objectui#6751's fence is revoked for `flex` only; its pins flip to bag pins. `stack` and every other node-level arm are unchanged. The render path is untouched: a stored flat node still draws (pinned). Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
…der (core) Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 Co-authored-by: Claude <noreply@anthropic.com>
…hanges Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42 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
|
…props and children (objectui#11276) The guide's "Pick the right layout primitive" section said the layout primitives all "take `children` and read every key off the node", and tabulated `flex`'s props. With this PR an authored `flex` takes its props AND its child list in its `properties` bag, and the flat spelling is refused by name on both authoring faces, so the sentence taught a shape `objectui validate` refuses. Two sentences change, nothing else: the intro now says `stack`, `grid`, `container` and `box` keep reading every key off the node while `flex` takes its props and `children` in `properties`, and the `flex` / `stack` intent sentence names the refusal. The table's "Key props (renderer defaults)" column stays as it is: those are the renderer's defaults, and only where they are authored moved. Net `skills/**` lines: 0 (353 → 353). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42
✅ 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
|
维护者速读(终稿)
改了什么。 落地您在 objectui#11300 的裁决「A」:页面里手写的 为什么改。 spec 的页面契约要求组件参数住在 风险与代价(含回滚)。
席位意见。 建议批准。路线是您裁过的「A」,形状照 triage 的 addendum,没有发明 spec 行(17.5.0 没有 你要做的。 在本 PR 留一条 APPROVED review(
Generated by Claude Code |
Fixes #11276
Clause-②: yes
Clause-②: yes, copied from the PM claim5940199465: the strict face starts accepting thepropertiesbag on an authoredflex, which widens it, while the flat spelling narrows. This is objectui's narrowing-as-minorrule with a BREAKING (authoring) banner in the changeset, as in PR objectui#11328 and PR objectui#11399.The
flexbatch of objectui#11276, the last one. It executes triage's addendum5926377520under the maintainer's ruling on objectui#11300, verbatim: 「A」. With this PR the card's three arms (object-chart,object-grid,flex) all take the spec's bag.What changed
packages/types/src/zod/layout.zod.ts: newFlexBlockSchema, the authoredflexarm, built the wayObjectChartBlockSchemais (PR objectui#11328):BaseSchema+ theflexliteral +NODE_ENVELOPE+propertiesthroughpropsBag.@objectstack/spec17.5.0 has noComponentPropsMap['flex']row (55 rows, noneflex;object-gridpresent as the control), so no row is invented. The bag'sdirection,justify,align,gapandwrapare the flatFlexSchemamirror's own members, the same schema objects. The bag keeps the mirror's.passthrough()posture.properties.children, where the showcase writes it and the spec's page walk reads it. It keeps the mirror's accept set (one node, or a list), but the LIST's entries are left to the page-walk judgmentAnyComponentSchemaalready runs at everyproperties.children(objectui#11223), as on thepage:containers. Measured before choosing this: with the mirror's ownchildrenmember in the bag, a refused child was reported twice (aninvalid_unionatproperties.childrenplus the child's own issue) and every nesting level was judged twice. A single node, which the walk does not descend, is judged bySchemaNodeSchema.flatPropRefusals(public-blocks.zod.ts), the helper PR objectui#11399 used. It gains an optional third argument, the refusal detail for a bag that is not a spec row, because its default detail namesComponentPropsMap['KEY'], a rowflexdoes not have. Every existing caller is unchanged.bodyis refused towardproperties.children, replacing the mirror's refusal, whose remedy (a node-levelchildren) this arm refuses.FlexBlockSchemareplacesFlexSchemainLayoutSchema.FlexSchemastays exported, unchanged in shape: it is the node as theflexrenderer reads it after the hoist, and as code composes it.packages/types/src/layout.ts: the TypeScriptFlexSchemais re-declared, not retired (docblock only).FlexLayoutProps, already exported, is the bag's TypeScript type; typed authored nodes check their bag withsatisfies FlexLayoutProps.packages/core/src/builder/schema-builder.ts:flex()builds{ type: 'flex', properties: { … } };build()is typedBaseSchema & { type: 'flex'; properties: FlexLayoutProps }.5460852791, and5542677253's "flexdeclares its own keys") is revoked forflexonly. Its pins flip to bag pins; itsprops-envelope half (the deliberate counter-examples inskills/**) stands and is untouched.packages/types/src/__tests__/flex-properties-bag-11276.test.ts(43 rows plus a type-level block) andpackages/components/src/__tests__/flex-properties-bag-render-11276.test.tsx(render through the realSchemaRendererand registry: a bag node and a stored flat node draw the same class string and subtree;SchemaRenderer's hoist is untouched)..changeset/11276-flex-properties-bag.md(@object-ui/typesminor, BREAKING (authoring), names the migration) and.changeset/11276-core-flex-builder-properties-bag.md(@object-ui/coreminor, the builder's output shape).skills/objectui/guides/page-builder.md§3b ("Pick the right layout primitive"), commit56d5f7fd, under the seat's claim amendment5941574748: the section said the layout primitives all "takechildrenand read every key off the node" and tabulatedflex's props, which after this PR is a refused shape. Two sentences change: the intro now saysstack,grid,containerandboxkeep reading every key off the node whileflextakes its props andchildreninproperties, and theflex/stackintent sentence names the refusal (objectui validaterefuses flatflexprops). The table's "Key props (renderer defaults)" column is unchanged: those stay the renderer's defaults, only where they are authored moved. Netskills/**lines 0 (file 353 → 353; bundle 4806 → 4806). Measured first at5af4d2e7through the built faces: the flatflexas §3b taught it is refused bysafeValidateSchemaandStrictAnyComponentSchema(fourinvalid_typerefusals, each namingproperties.KEY), the bag and the showcase leaf shape pass both, a flatstack/grid/container/boxpasses both (a bagstackis refused by the strict face: the bag is not the contract there). §3b holds no fence and the bundle holds noflexnode (grep overskills/,.claude/,docs/adris empty;object-gridhitspage-builder.mdas the control). Three-waygit merge-fileagainst draft PR objectui#11404'spage-builder.md(headd83d2748): exit 0, 0 markers, both edits present. With this file the guard answers GOVERNED (skills/**x1 of 94 paths), so the PR takes the governed path: draft until an authorized APPROVED review, as the amendment says.Files beyond the claimed surface, each forced: census rows the new export forces (
zod-mirror-parity.test.ts, oneEXCLUSIONSrow); fixture triage where a reader readsflexprops off a catalog node (examples/schema-catalog/test/layout-props-conversion.test.tsxgains apropsOfreader and the ninespace-xsite paths now step throughproperties.children;badge-demo-label-6829.test.tsxlocations); the guide's remedy fixture inpage-actions-refusal-7926.test.ts;packages/types/src/zod/README.md.Zone 2, measured first
6aa029b63f, through the built faces: a bagflexnode ({ type: 'flex', properties: { direction: 'col', gap: 2, children: [ … ] } }) and the showcase shape ({ id, type: 'flex', responsiveStyles, properties: { children: [] } }) passedsafeValidateSchemaand were refused by the strict face (unrecognized_keysnamingproperties); the flat spelling passed both. The spec'sPageComponentSchemaaccepted the bag node and refused the flat one (unrecognized_keysnamingdirection,gap,children); control: a flataction:buttonactionTypeis refused the same way.ComponentPropsMapin installed 17.5.0 has noflexkey, so the bag is judged by objectui's mirror members (direction,justify,align,gap,wrap, and the child list as described above).6aa029b63f. Instrument:git grep -lEfor atypekey equal toflexin any quoting, minus tests / snapshots / e2e: 84 non-test files; plus a search for code that emits aflexnode without that literal.examples/schema-catalog/src/schemas, 248 nodes (a JSON walk: node keys were onlytype,gap,wrap,children,align,justify,className,direction)auth/login-simple.jsonandauth/signup.jsonby hand to keep their compact linescontent/docs/guide/layout.md(3 nodes),content/docs/components/layout/flex.mdx(its Schema block)flex.mdxnow shows a bag JSON example and the bag's members, with the full declaredalign/justifyvocabularies@object-ui/typesdocs and examplesREADME.md(2 nodes),examples/dashboard.ts(2 nodes),examples/data-display-examples.json(1 node)BaseSchemaplussatisfies FlexLayoutProps@object-ui/core'sflex()builder (super('flex'), missed by the literal grep)packages/plugin-designer/src/PageDesigner.tsx(a palette entry that builds aDesignerComponent, the designer canvas model, carrying no props);packages/sdui-parser/verify.ts(a manifest entry for thekind: 'html'JSX tier, whose compiled nodes never meet the zod face); theflexregistration'sdefaultProps(a designer / manifest seed; no composer turns it into a document node on the zod path)packages/types/src/layout.tsskills/objectui/guides/page-builder.md§3b: prose that taughtflex's props on the node. No governed file holds aflexnode (the same grep overskills/,AGENTS.md,CLAUDE.md,.claude/,docs/adr/exits 1, while the pattern for other types hitsskills/, the control)56d5f7fdunder the seat's amendment5941574748, so this PR is GOVERNEDThe census's "16 tests in 8 files" re-derives differently because this PR moves the catalog together with the arm: red first at
b453ead8before the pins flipped wasTests 30 failedin 7 files (the 6751, 8761 and 10872 pins, the 7926 remedy row, the parity census row, and two catalog readers that readflexprops off the node); the catalog-through-the-face pins the census simulation turned red (8499, 6939, 6942, 8284) stay green because the documents moved too.4.
layout.mdand PR objectui#11069.git merge-fileof this branch'slayout.mdagainst PR objectui#11069's heada11f73347dover their merge base: exit 0, 0 conflict markers, both edits present.git merge-treeof this head against that PR: the only conflict ispackages/cli/src/commands/check.ts, the same onemainat base has with it;layout.mdauto-merges.5. The showcase. objectstack
origin/main3a7b6eb0has 15type: 'flex'literal sites incommand-center.page.tsandstyling-gallery.page.ts, plus 1 taught node inskills/objectstack-ui/rules/pages.md: the 16. Evaluated with their helpers they are 60 runtime nodes in the two pages plus the taught one (a scratch instrument, not committed):6aa029b63fflexnode's own verdict, tolerant faceflexnode's own verdict, strict face (the face PR objectui#11069 wires)propertiesrefused)PageComponentSchemaThe 3 whole-subtree refusals at head are not
flex: they are the command-center root, its KPI panel and its KPI band, whoseobject-metrictiles writefilteras a record ({ status: 'active' }), which the spec's ownComponentPropsMap['object-metric']row refuses ("takes the ViewFilterRule ARRAY form"). See Acceptance notes. PR objectui#11069's own census script does not read objectstack, so its count is read through the strict face it wires, which accepts all 61flexnodes. Through the realobjectui validatebinary (packages/cli/dist/cli.js, built from this tree): a flatflexexits 1 with three named refusals pointing atproperties.children,properties.justifyandproperties.gap; the bag node and the showcase leaf shape exit 0.Construct reused, and why
PR objectui#11328's (
object-chart: no spec row, the bag is the mirror's own members by reference, the description says so) rather than PR objectui#11399's (object-grid: a spec row by reference), becauseflexhas no row. The refusals come from PR objectui#11399's sharedflatPropRefusalsrather than a per-arm map, so there is one copy; its new optional detail is the no-row counterpart ofpropsBag's optional description, which PR objectui#11328 added for the same reason.Tests (heavy runs through
os-verify-lock.sh, slotissue-11276-flex; Node v22.22.2 from/opt/nvm)b453ead8/bacbb4d7(the content of this head; the later commits mergemain, which touched onlypackages/layout, and edit one changeset sentence):pnpm exec vitest run packages/types/ examples/schema-catalog/ packages/cli/ packages/core/src/builder/ packages/components/src/__tests__/flex-properties-bag-render-11276.test.tsx:Test Files 384 passed (384),Tests 11192 passed (11192), VERDICT 0.FlexSchema,LayoutSchema, the builder, pluspackages/sdui-parser/:Test Files 61 passed (61),Tests 1035 passed, 16 skipped(the skips areTimelineGantt.dateOnlyZone-10866's timezone-gated rows). 73 more files naming the faces,content/docsor aflexliteral:Test Files 72 passed (72),Tests 1089 passed.scripts/__tests__/:Test Files 177 passed, 2 skipped (179),Tests 5417 passed, 2 skipped.packages/core/,packages/components/src/__tests__/,packages/sdui-parser/,packages/plugin-designer/:Test Files 441 passed, 1 skipped (442),Tests 6294 passed, 34 skipped. Each VERDICT 0.pnpm --filter @object-ui/types build,type-check(all three legs: the examples program holdsexamples/dashboard.ts, the test program holds the new pin, read with--listFilesOnly),pnpm --filter @object-ui/core build,type-check: VERDICT 0.scripts/ablation-replace.mjs, WRAP mode with its EXIT/INT/TERM restore on the absolute path):FlexSchemaback inLayoutSchemain place ofFlexBlockSchema(anchor x1 to x0, blob1c3af5f2to4fbf4fb8). Over 7 files:Tests 37 failed, 218 passed (255): red = 29 rows of the new pin, 6 of the 10872 file, the builder row, the 8761 counter-probe; green = theobject-chartandobject-gridpins (controls) and the 6751 file, which parsesFlexBlockSchemadirectly. Restored: blob == HEAD1c3af5f2,git diff HEADempty.childrenreturned to the mirror's own member (anchor x1 to x0). Exactly the 3 judged-once rows go red; restored, blob == HEAD.flatPropRefusalsspread deleted,tsc -p tsconfig.test.jsonexits 2 with exactly 2 TS2344 errors, the two flat-keys-take-nothing rows of the new pin. Restored, blob == HEAD.flexnode (70), both faces: the refused set is identical at base and head (2 tolerant, 4 strict, the same documents, codes and keys: carouselitems, resizablepanels.N.id, input-otpseparator, labelrequired), only the paths gainingproperties.. Every JSON fence in tracked.md/.mdxholding aflexnode: base 3, head 4, refused 0 at both.objectui checkover the repository (the same binary at both trees): base 267 validated / 166 recognised / 1 did not validate; head 287 / 146 / 1 (the same file, the vscode-extension JSON schema): root-flexdocuments that carry no other structural key are now parsed in full, and pass.flex.mdxinterface compile against the built types (tsc --strict, exit 0); control:direction: 'column'in the README bag is TS2322, exit 2.Gates (exit codes captured before any pipe)
25844284:check:new-line-citations("VERDICT new-cross-file-line-citations: 0 new citation(s)"),check:control-bytes("OK (scanned 9930 tracked text file(s); skipped 85 binary)"),markdown-test-inputs --audit("109 candidate test files, all adjudicated; 81 declared entries, all present"),check-changeset-presence("13 source file(s) of 3 released package(s) changed, and this change declares 2 changeset(s)"),changeset:check,check:spec-symbols,check:doc-types,check:doc-example-ids("415 real reference(s) all resolve"),check:doc-fences,docs:check-links,check:handler-key-reads,check:pending-changeset-literals,check:test-path-roots,check-governed-queue-guard.mjs --testover the 93 paths ("NOT GOVERNED"; at56d5f7fd, with §3b added, GOVERNED over 94 paths). Re-run at5af4d2e7(a changeset sentence only): presence,changeset:check,check:control-bytes,check:new-line-citations, each exit 0.bacbb4d7:check:vi-mock-specifiers,check:component-surface-parity,check:element-data-source-declaration,check:installed-pin-claims,check:prompt-keys,check:doc-example-readers,check:governed-queue-guard,check:changeset-claims(report-only; its re-read list holds no paragraph this change falsifies).**/*.{ts,tsx};eslint --no-inline-config --format jsonover the 16 changed.ts/.tsxfiles: 16 files, 0 errors, 5no-explicit-anywarnings, none on an added line.eslint.config.jssets noprojectService/parserOptions.project, so no untouched file's verdict can move. The fullpnpm lintis CI's.check:doc-snippets(exit 2),check:doc-examples(exit 2),check:readme-exports(exit 1, "the population COLLAPSED"; among what it judged, 0 wrong-path, 0 fabricated). The changed TS blocks were compiled separately (above). CI builds first.Deviations
childrenis not the mirror's member by reference. It keeps the mirror's accept set and hands the list's entries to the page-walk judgment, because the mirror's member double-reports every nested refusal (measured, and pinned by the child-list ablation above). One edge differs from the mirror and matches thepage:containers: an entry that is itself an array is not visited by the walk.flatPropRefusalsgained an optional argument (the no-row refusal detail). Existing callers and their messages are unchanged.@object-ui/coreships the builder.../objectui-issue-11276-flex-base, removed after use) carried the before readings; a copy of this tree's built CLI (its code is unchanged by this PR) ranobjectui checkthere.Acceptance notes
flexrefusals. The command-center KPI tiles (object-metric) writefilter: { status: 'active' }and{ status: { $ne: 'done' } }; the installed spec rowComponentPropsMap['object-metric']refuses the record form by its own message, and so do both objectui faces, at the tiles'properties.filter. This is independent offlexand of this PR (it was already refused at base on the tolerant face). Whetheros validate's props gate refuses it too is NOT MEASURED. Filed by the seat as finding(examples): the app-showcase command-center KPI tiles writeobject-metricfilteras a record, which the spec's ownComponentPropsMap['object-metric'].filterrefuses ("takes the ViewFilterRule ARRAY form") objectstack#21251.skills/objectui/guides/page-builder.md§3b said the layout primitives "takechildrenand read every key off the node" and tabulatedflex's props. It holds noflexnode, but after this PR it would teach the flat spelling forflex, so it is fixed in this PR (commit56d5f7fd, under the seat's amendment5941574748).scripts/measure-strict-authoring-face.mjs(PR objectui#11069's M1 instrument) reads 3 documents cleaner than the shipped strict face at this head (whole-tree strict refusals 69 to 66: 2 catalog, 1 authored), because its strict twin does not close components nested at a bag's child list. The shipped face refuses all 3, unchanged from base (checked onexamples/dashboard.ts: the pre-existing cardcontent, atproperties.children.1.properties.children.1.children.0). Itsflexrow readsFlexBlockSchema, 250 documents, 0 strict-refused.flex(10872-flat-arm-responsive-styles,6751-flex-props-envelope-lift,8284-content-channel-per-component, and the7926remedy) describe the flat mirror, which still behaves as they say; the new changeset says so.Overlap
Merged
mainat7728c67c(one commit,packages/layoutonly) before opening. Open PRs touching these files: PR objectui#11069 (layout.md, measured above).Session:
https://claude.ai/code/session_01JG2jy8a9su7ia4Hx7zxv42(dispatched by seatdomain:ui#2; this run is its subagent on branchclaude/issue-11276-flex-properties-bag).维护者速读(草稿)
改了什么。 ObjectUI 页面里的
flex(横向排布容器)从此和其他业务组件一样,把自己的参数(方向、对齐、间距、换行)和子节点统一写进properties包里;平铺在节点上的旧写法被objectui validate按键名拒绝,错误信息逐键指出新位置。配套一并改到位:仓内 70 份示例目录文档(248 个flex节点)、layout.md与flex.mdx两份文档、@object-ui/types的 README 与示例、@object-ui/core的flex()构建器全部改成新写法;新增两组 pin 测试(校验面 43 行 + 渲染 1 份);发布给客户 agent 的技能包skills/objectui/guides/page-builder.md§3b 改两句话,说明flex的参数去向(技能包净行数 0)。渲染端一个字节不动:已存的旧节点照样画出来,且与新写法画得完全一致。为什么改。 这是您在 objectui#11300 的裁决「A」的落地。
@objectstack/spec的页面契约要求组件自己的参数住在properties包里;ObjectUI 过去的flex却只认平铺写法,于是 objectstack showcase 里按 spec 写的 16 处flex节点在严格校验面上全部被拒,而 ObjectUI 自己文档教的写法又被os validate拒绝 —— 两端各说一套,AI 按任一端写都会在另一端报错。统一到 spec 的写法后,showcase 的 61 个运行时flex节点按节点自身判定在两个校验面上全部通过(整树读数 58/61,余下 3 处是object-metric的filter写法,与flex无关,已另立 objectstack-ai/objectstack#21251);平铺写法被按键名拒绝并给出改法。风险与代价(含回滚)。 对按 ObjectUI 旧文档写页面的外部作者是 authoring 层面的 breaking:他们的
flex节点要把参数移进properties,错误信息会逐键指出目标位置;changeset 已标 BREAKING(authoring)并写明迁移。运行时零风险:SchemaRenderer的 hoist 不变,已存的平铺节点与新写法渲染结果逐字节一致(有渲染 pin)。本 PR 超过 5000 行人工合并阈值(大头是 70 份示例文档的机械迁移),故停在 draft 等您批准。回滚代价低:整体 revert 这一个 squash commit 即可,无数据迁移、无存储格式变化;注意skills/**那两句与代码改动同 PR 落地,只回滚其中一半会让技能包教一个被拒绝的写法。席位意见。
你要做的。 在 PR objectui#11406 上留下一条 APPROVED review(或回复「不批」并说明理由)—— 一个动作。
Generated by Claude Code