Repository navigation
feat(spec): page:header placedActions draws every placed header action, refused beside an actions list (#30018) - #30028
Conversation
…red actions list placedActions: true draws every action of the bound object placed at record_header / record_more, read live, in place of an authored actions id list. The actions list keeps its meaning (an authored list is the whole set). The pair is refused at placedActions; false beside a list is accepted. Declared ahead of the header renderer: [PLANNED] describe marker and a liveness/page.json _note record. Claude-Session: https://claude.ai/code/session_01WGufzeWKWceaGdgGYf37di Co-authored-by: Claude <noreply@anthropic.com>
…uthorable key dropped-refinements.baseline.json gains ui/PageHeaderProps (root site): the placedActions/actions rule is value-conditional, which no projected refinement pattern publishes. authorable-surface/ui.json gains ui/PageHeaderProps:placedActions (gen:schema). Claude-Session: https://claude.ai/code/session_01WGufzeWKWceaGdgGYf37di Co-authored-by: Claude <noreply@anthropic.com>
…ops row The ledger's measured totals must equal its body: 251 schemas, 1019 sites. Claude-Session: https://claude.ai/code/session_01WGufzeWKWceaGdgGYf37di Co-authored-by: Claude <noreply@anthropic.com>
gen:docs output for the new PageHeaderProps key and the rewritten actions describe. Claude-Session: https://claude.ai/code/session_01WGufzeWKWceaGdgGYf37di Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 169 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 7f3e4eadbbd9f1cfa03a29d5de1868f2847e8144 && git checkout 7f3e4eadbbd9f1cfa03a29d5de1868f2847e8144
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 140d8ce805dc55245d9841a6ece6dfb956d9e071 371cb0fd9d323a0cc3c3b536831c10fab7ec78b4 && git checkout -B drift-repro 140d8ce805dc55245d9841a6ece6dfb956d9e071 && git merge --no-ff 371cb0fd9d323a0cc3c3b536831c10fab7ec78b4
node scripts/docs-audit/affected-docs.mjs --json 140d8ce805dc55245d9841a6ece6dfb956d9e071
|
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz Contract reviewServed-tier: Inputs read: card #30018 (body, the claim 6624302286, triage 6624681862, os-dev-report 6626669313), the ruling 6624172200 on #29951, PR #30028 (body, 12-file list, net diff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Check-runs on the head at the time of this record: 24 success, 7 skipped, 0 failure, 31 in progress (Build Core, Build & E2E UI, Live E2E UI, Test Core 1-8, Test UI 1-16, Lint & Repo Gates, Lint UI, Type Check UI, Type Check · workspace). Completed and green include Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, Dogfood Regression Gate, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Build Docs, Build UI Deps and the scoped Lint / Type Check source-gate jobs. Nothing has concluded red. The verdict below is the contract verdict on the tree; the derived gates still running are the lane seat's merge condition. Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz 席位核对 — ACCEPT · domain:spec#2 · head
|
Requested by Yibo Wang · project thread
Fixes #30018
维护者速读
page:header新增一个可选开关placedActions(布尔,没有默认值)。写true,页头画出该对象所有放在record_header/record_more的动作,实时读取;不写或写false,actions列表照旧,写了哪些就只画哪些。placedActions: true和actions列表同时写(空列表也算)会被os validate/os build/os lint和页面保存门以component-props-invalid错误拒绝,消息点名两个键,让作者只留一个;false加列表可以。这个组合以前因为是未知键本来就被拒,所以没有已通过的页面会变红。渲染器还没读这个键,写了也不改变显示。回滚 = revert 本 PR。packages/spec/src/ui/component.zod.ts(新键、.refine互斥规则和它的消息常量);ui/apps/console/src/__tests__/registry-inputs-spec-parity.test.ts(ui 下唯一一行:豁免条目,console(record header): a url action placed at record_header on sys_user no longer renders in the 18.3 Console header menu, drawer or full page (18.2 rendered it) #29951 的 ui 半边落地时删掉);skills/objectstack-ui/rules/pages.md(唯一受管文件,一行)。pnpm --filter @objectstack/spec exec vitest run src/ui/component.test.ts -t placedActions,预期 8 个用例全绿,其中「refuses … beside an authored actions list」断言错误路径是placedActions、消息含Keep one。skills/objectstack-ui/rules/pages.md一行成为受管(Tier H),席位复核后请你批准合并。Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #29951 — this diff names the key in
skills/objectstack-ui/rules/pages.mdand in the docs, marked not drawn yet; no eval exercises it while no renderer reads it, so the eval and the qualifier drop ride with the reader.What changes
PageHeaderProps.placedActions(packages/spec/src/ui/component.zod.ts):z.boolean().optional()besideactions, no default, so an unset key stays unset in the parsed output.trueasks the header to draw every action of the page's object whoselocationsincluderecord_headerorrecord_more, read live from the object's metadata. Omitted orfalse, theactionslist rules as today: an authored list is the whole set (PageHeaderProps.actionsdeclaresz.array(z.string())but objectui's canonicalpage:headerrenderer consumes ActionDef objects — satisfying the schema deletes every header button #11592).Refused together. The schema now ends in a
.refinethat refusesplacedActions: truebeside an authoredactionslist, an empty list included, at pathplacedActions. The message names both keys and tells the author to keep one:actionsfor a curated list that is the whole set, orplacedActions: truefor every placed action. The refine keeps the closedstrictObjectconstructor, so unknown keys are still refused.@objectstack/lintvalidateComponentPropssafe-parsesComponentPropsMap['page:header'], so the refusal reachesos validate/os build/os lintand the page save door as acomponent-props-invaliderror.defineStackdoes not parse the properties bag, so nothing changes there.Docblocks. The
actionsTSDoc and.describe()now name the opt-in and the exclusivity. The new key's describe opens with[PLANNED — not yet rendered].Liveness, honest PLANNED. Component-level keys have no ledger row (the walk stops at the undrilled
page/regionscontainer), so the record is the describe marker plus aPLANNED RECORD (#30018 …)sentence inpackages/spec/liveness/page.json_note, measured at maineee41490:PageHeaderRendererinui/packages/components/src/renderers/layout/containers.tsxreads noplacedActions. The reader is console(record header): a url action placed at record_header on sys_user no longer renders in the 18.3 Console header menu, drawer or full page (18.2 rendered it) #29951's ui half (the renderer and the three platform record pages).dropped-refinements.baseline.jsongainsui/PageHeaderProps(1 site, root). The rule depends on the valuetrue, which none of the projectable arms inshared/refinement-projection.tsstates, so the published JSON Schema still accepts the pair and the parse refuses it. The row is the one the build printed.Generated:
packages/spec/authorable-surface/ui.jsongainsui/PageHeaderProps:placedActions.One line under
ui/, as the seat ruled:UNPUBLISHED_EXEMPTIONS['page:header.placedActions']inui/apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, citing console(record header): a url action placed at record_header on sys_user no longer renders in the 18.3 Console header menu, drawer or full page (18.2 rendered it) #29951. The parity test's reverse direction reads the workspace spec dist and would otherwise refuse a spec key nopage:headerregistration input publishes. console(record header): a url action placed at record_header on sys_user no longer renders in the 18.3 Console header menu, drawer or full page (18.2 rendered it) #29951's ui half must strike this entry when it declares the input (carries no stale unpublished-key exemptionrefuses it once the input is published).Docs:
content/docs/ui/pages.mdx(theactionsrow names the alternative; a newplacedActionsrow) andcontent/docs/protocol/objectui/layout-dsl.mdx(one sentence), both saying the console renderer does not draw it yet.Skills (Tier H, the only governed file):
skills/objectstack-ui/rules/pages.md, thepage:headercatalogue row namesplacedActions: true (all placed; not drawn yet). The token ratchet has zero headroom on this file, so the row is paid for by deleting a redundant clause from the print-page paragraph ("and afullpage with no regions"), which the condition just before it already states.Changeset:
@objectstack/specminor,@objectstack/skillsminor, an ordinary minor. Nothing ships in 18.3.x.Generated reference:
content/docs/references/ui/component.mdxregenerated bygen:docs(the new row and the rewrittenactionsdescribe), not hand-edited.Qualifiers #29951 drops when the reader lands
The key is marked "not drawn yet" in six places, all true today and all false once the renderer reads it: the describe marker in
component.zod.ts(the generated reference follows it), thepage.jsonPLANNED sentence, the parity-test exemption, thepages.mdxrow ("Declared ahead of the console's header renderer…"), thelayout-dsl.mdxsentence, andpages.md("not drawn yet").Why
Ruling A on #29951 (record 6624172200): a record page's header should be able to say "every action placed on this object" without copying ids, so an action another package places at
record_header/record_moreshows up without a page edit. #11592 made an authoredactionslist the whole set, which stays the default; the opt-in is a separate, explicit key, and the two cannot be set together because they would be two different answers to the same question. This PR is the spec half only. Nothing ships in 18.3.x.Tests
packages/spec/src/ui/component.test.ts, new describe "PageHeaderProps placedActions — every placed header action, or the authored list, never both", with the five cases the card asks for plus three:placedActions: truealone is accepted and kept.actionslist alone parses as before, and noplacedActionskey is materialized.placedActions: truebeside['convert_lead']and beside[]is refused as exactly[{ code: 'custom', path: 'placedActions' }]; the message contains`placedActions: true`,`actions`andKeep one.placedActions: falsebeside anactionslist is accepted.{},{ title },{ title, actions }).'true'is refused asinvalid_typeatplacedActions.ComponentPropsMap['page:header']isPageHeaderPropsand refuses the same pair.packages/lint/src/validate-component-props.test.ts, new describe for spec(page:header): an explicit opt-in to draw every action placed at record_header / record_more, read live (#29951 ruling A, spec half) #30018: the pair gives onecomponent-props-invaliderror atpages[0].regions[0].components[0].properties.placedActions(both list forms), withwhereset topage "probe_page" · page:headerand a message naming both keys; silent on true alone, a list alone, and false beside a list; throughrunAuthoringRules('validate', …)the finding is an error.Acceptance notes
falsebesideactionsaccepted: tests 3 and 4, plus the lint door test.actionsdocblock and describe rewritten to name the opt-in: yes.page.jsonsentence.pages.mdxandlayout-dsl.mdx(docs),pages.md(skills).authorable-surface/ui.json, the dropped-refinements row;check:generatedresult in Verification.sys_user_detail,sys_organization_detail,sys_approval_request_detail).PageHeaderProps: none. At head371cb0fd, I fetched the head of each of the 19 open PRs and diffed it against its merge base withmain. None touches any of this PR's 12 files, and none changes aPageHeaderPropsorplacedActionsline incomponent.zod.ts.skills/objectui/guides/page-builder.mdstill lists the retiredpage:headerkeybreadcrumb, so a skill teaches a key the closed shape refuses. I left it alone because that guide describes what renders and namingplacedActionsthere would be untrue; the stalebreadcrumbmention is worth its own card.Verification
All runs went through
scripts/pm/os-verify-lock.sh. The box was shared, so the timings are wall-clock on a shared machine.pnpm build --concurrency=2, 119/119 tasks, exit 0.pnpm --filter @objectstack/spec test, 811/811 files, 24378 passed plus 1 todo, exit 0.dropped-refinements.test.ts, "header totals match its body". The ledger'smeasuredtotals now count the new row (251 schemas, 1019 sites).pnpm --filter @objectstack/spec typecheck, exit 0.pnpm --filter @objectstack/spec test:repo, 90/90 files, 1539 tests, exit 0.pnpm --filter @objectstack/spec check:generated, 14/14 artifacts, exit 0.content/docs/references/**as stale, because the describe changed.gen:docsregenerated it, and that commit is in this PR.pnpm --filter @objectstack/spec check:authorable-surface, exit 0.pnpm --filter @objectstack/lint exec vitest run src/validate-component-props.test.ts, 121 passed, exit 0.registry-inputs-spec-parity, run through the console project against the built spec dist (which carriesplacedActions), 261 passed, exit 0.node scripts/check-skills-token-ratchet.mjs, exit 0.skills/objectstack-ui/rules/pages.mdis 5669 tokens against a ceiling of 5669 (+0). The file has 427 lines before and after (22673 → 22674 bytes). The sum of allSKILL.mdfiles is unchanged: 4536 lines and 226744 bytes. NoSKILL.mdwas edited.check-changeset-no-major --base origin/main --eventexits 0. It readsClause-②: yes (widening)andMetadata-protocol: added, finds nomajor, and grades no moved packagepatch.Metadata-protocol: noneexits 1, becausepackages/spec/authorable-surface/ui.jsonmoved.node scripts/pm/dispatch-gates.mjs --commandsprinted 113 commands at head371cb0fd. I ran each one byte for byte, and all 113 exited 0.--ranreconciliation: "113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN".scripts/ablation-replace.mjsreplaced the pair predicate incomponent.zod.tswith(header) => header !== null. The anchor went from 1 hit to 0, and the blob changed from68041b88to00fa1529.vitest run --project local src/ui/component.test.tsthen went red: 2 failed and 359 passed. The two failures were the pair refusal and theComponentPropsMaprefusal. The other six tests in the describe stayed green.68041b88, matching HEAD, andgit diff HEADis empty.Ablation readout
Changeset gate readout (this body)
Not run locally: the dogfood battery, because the title has no
!and there is no(narrowing)arm. The PR's check-runs carry everything else CI runs.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses. (The four changed TypeScript files passeslint --no-inline-configwith the root config, exit 0. CI'spnpm lintcovers the full tree.)main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.## 维护者速读block, which is written in Chinese for the maintainer.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzeWKWceaGdgGYf37di