Repository navigation
feat(spec)!: PageSchema refuses a page authoring both slots.details and slots.tabs; sys_user_detail moves its details into its first tab - #22600
Conversation
…s and slots.tabs The tabs slot replaces the whole tab strip the Details tab lives in, so a details slot beside it never rendered. checkPageSlotPair refuses the pair at slots.details, naming both slots and the fix: the record:details component as a child of a tabs item. sys_user_detail moves its record:details, with its sections and hideFields unchanged, in as the first tabs item. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <[email protected]>
…ment) and pin it The D3 semantic entry page-slots-details-beside-tabs-refused and its step-18 rationale fragment, registry.ts regenerated; the refusal and its controls pinned in page-slot-pair.test.ts; sys_user_detail's first-tab details body pinned in platform-objects; the changeset. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <[email protected]>
…ageSlotPair export Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <[email protected]>
…ion in the D3 entry and changeset Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <[email protected]>
…tails-tabs-slot-pair
📓 Docs Drift CheckThis PR changes 2 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 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 a44599356ffc085542710c9441f8d7eafc83e016 && git checkout a44599356ffc085542710c9441f8d7eafc83e016
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3e72f9391b44721c3c3ba12bc3beef5c34d98cca c963de955d981777acec283de95af30efe4d9d1a && git checkout -B drift-repro 3e72f9391b44721c3c3ba12bc3beef5c34d98cca && git merge --no-ff c963de955d981777acec283de95af30efe4d9d1a
node scripts/docs-audit/affected-docs.mjs --json 3e72f9391b44721c3c3ba12bc3beef5c34d98cca
|
…lotPair export The diff exports checkPageSlotPair from the spec ui entry, a public-surface widening beside the accept-set narrowing; widen-plus-narrow is spelled yes (narrowing). Levels unchanged (spec minor, platform-objects patch). Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <[email protected]>
Contract reviewServed-tier: Inputs read: card #22568 (body; comments Check-runs on the head: 35, all ① Derived judgmentsEach accept-set or public-surface change the diff makes, judged against triage direction (b) and the sources.
② Semver level
③ Boundary flagsEach dev deviation and out-of-scope finding, answered or escalated.
Implemented-by: VERDICT: PASS |
Fixes #22568
Clause-②: yes (narrowing)
Triage
6092519848, direction (b): the contract refuses the pair, and the platform's own producer moves. objectui is not touched.What changes
packages/spec/src/ui/page.zod.ts).checkPageSlotPairis a new exported object-level check, attached toPageSchemaby identifier beside the three page checks already there. It refuses a page whoseslotsmap carries bothdetailsandtabs, atslots.details, with acustomissue. The issue names both slots and the fix: move therecord:detailscomponent into thetabsitems (as the first item'schildren) and deleteslots.details, or deleteslots.tabsto keep the synthesized tabs. Either slot may be one component or an array. An emptydetails: []is refused like a full one, and the page'skinddoes not matter. A page authoring only one of the two slots is unchanged.slotsTSDoc now states the precedence:detailsis the body of the Details tab, that tab lives inside the synthesizedpage:tabsstrip, andtabsreplaces the whole strip, sotabswins. The.describe()is unchanged, so no reference page or JSON schema moves.packages/platform-objects/src/pages/sys-user.page.ts).sys_user_detailmoves itsrecord:detailsin as the firsttabsitem (Details,file-text, four-locale label, the conventionsys_approval_request_detailuses). ItssectionsandhideFieldsare byte-for-byte unchanged. The page no longer authors adetailsslot. The file's strategy docblock now matches the page.page-slots-details-beside-tabs-refused(protocol 18), its step-18 rationale fragment, andregistry.tsregenerated withgen:migration-registry. There is no tombstone, because no key is removed. There is no D2 conversion, because which tab item carries the body (and under which label) is the author's choice. Per PR 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, nospec-changes.jsonor upgrade-guide regeneration is owed.check:spec-changesandcheck:upgrade-guideare green..changeset/22568-page-slots-details-beside-tabs-refused.md:@objectstack/specminor (a BREAKING accept-set narrowing),@objectstack/platform-objectspatch. It carries theClause-②line, the ADR-0087registeredmarker, a FROM → TO table and the one-line fix.packages/spec/api-surface/ui.jsonandpackages/spec/export-origins/ui.json, one line each.The three hypotheses, measured
slotsmap holding both adetailsand atabskey.d66e53bdf5, the only producer ispackages/platform-objects/src/pages/sys-user.page.ts. Noexamples/**page authors the pair. Control: the census seesproject-detail.page.ts,sys-organization.page.tsandsys-position.page.ts, and each authorstabsonly. The other hits arepackages/spec/src/system/i18n-resolver.test.tswalker fixtures, which are never parsed (see Acceptance notes), pluspage.zod.tsandregistry.tsprose.mainf0afcbda07: zero pages author both. Control: its one slotted page,src/sales/pages/account_detail.page.ts, is found, and it authorsheaderanddiscussiononly. Every other hotcrm page iskind: 'full'.maind758f2f: no producer writesslots.detailsorslots.tabs. The only both-slot file is the synthesizer's own test, which callsbuildDefaultPageSchemawith options and never parses a spec page.PageSchema. The slot map's own refusals are shape-level (a closed key set plusaliases), and it has no cross-slot rule. Every cross-field page refusal inpage.zod.tsis an exportedcheckPage*attached with.superRefine(checkX).object-refinement-check-exports.test.tspins that shape: parity, count plus bijection, attachment by identifier, and barrel identity. objectui's mirror re-attaches those exports. I measured one more reason against a nested refinement on theslotsobject: it would add newx-dropped-refinementssites (for exampleui/Page slots, andmanifest.pages.element.slotsunder the installed-package schemas). That means edits to the hand-keptdropped-refinements.baseline.jsonand churn in published JSON schemas. A check at the page root adds no site, because the root""is already recorded.maind758f2f, read only).buildDefaultPageSchema.tsabout lines 1072 to 1095:tabswins. With onlydetails, the synthesizer puts the authored node, as is, intotabItems[0].children.page:tabsrenderer (components/src/renderers/layout/containers.tsx) renders eachitems[].childrenthroughrenderChildren.SchemaRendererhoistsproperties.*onto the node.record:details(plugin-detail/src/renderers/record-details.tsx) readsschema.hideFields(about line 490) andschema.sections(about lines 769 and 827).record:detailsthat is a tab item's child renders through the same path, with the samesectionsandhideFields, as the synthesized Details tab body.Doors measured
PageSchema.safeParse,definePage(page-slot-pair.test.ts)customatslots.detailsdefineStack(page-slot-pair.test.ts)STACK_SCHEMA_INVALID/ 422, issue atpages.1.slots.detailsobjectstack validatevalidate.tsmakes (ObjectStackDefinitionSchema.safeParse), run on the built specdistsuccess=false,[{"path":"pages.1.slots.details","code":"custom"}]; control with only the moved page:success=true. The CLI binary itself was not run (itsdistis not built here).metadata-protocol/src, using the realsaveMetaItemwrite path over a stub engine (the face-inventory harness), removed after the run,git statuscleanINVALID_METADATA/ 422,issues: [{path: "slots.details", code: "custom"}]on both faces, nothing persisted; control: the moved page saves (1 row)computeMetadataDiagnostics('page', …)valid: false, error atslots.detailsBASE'ssys-user.page.tsrestored on disk against the new refusal, then the platform-objectssrc/pages/suite'SysUserDetailPage' parses through PageSchema (door 1 precondition), atslots.details; restored to blob == HEAD7c8b12e1a7,git diff HEADemptyTests (final head
d66e53bdf5unless stated)pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 639 files, 19061 passed, 1 todo.pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2: 54 files, 915 passed (ate92b99226e, and again atd66e53bdf5).page-slot-pair,object-refinement-check-exports,page,page-print,page-requires-compiled-kinds,src/migrations/,src/conversions/,step18-rationale-merge): 21 files, 1003 passed.pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2: 69 files, 1082 passed, including the newsys-user-details-tab.test.ts(4 tests).pnpm --filter @objectstack/spec typecheckandpnpm --filter @objectstack/platform-objects typecheck: both exit 0 (ate92b99226e, and again atd66e53bdf5), and the test layers hold their ledgers.protocol.runtime-authoring-gate.test.ts(133 passed).packages/spec/src/ui/page-slot-pair.test.ts: the refusal, the controls, the stack envelope, the D3 entry, and stored-row replay.checkPageSlotPairrows inobject-refinement-check-exports.test.ts: 7 fixtures plus legs 2 to 4.packages/platform-objects/src/pages/sys-user-details-tab.test.ts.Ablation
The fix was committed first (
e92b99226e). Then I usednode scripts/ablation-replace.mjsin wrap mode:.superRefine(checkPageSlotPair));with);inpage.zod.ts. Anchor count went 1 to 0, and the blob wentdc9d9046ae35to2a161352abd6. On disk during the mutation, the attachment line count was 0../page.zodrelatively, so vitest readssrc.page-slot-pair.test.tsplusobject-refinement-check-exports.test.ts, 14 failed and 206 passed. That includes every pair refusal, the stack envelope, the stored-row refusal, and leg 2 (count plus bijection) and leg 3 (attachment).git checkout HEAD: blob == HEADdc9d9046ae35, andgit diff HEADis empty. I checked both again independently.Gates
The 93 commands from
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(the dispatch-time 92, pluscheck:generated, added by this diff) were run at the final headd66e53bdf5, and all 93 exit 0.dispatch-gates --ranwith a per-command exit code reports: 93 derived, 93 run, 0 NOT-MEASURED.Three of them exited 3 (PREREQUISITE NOT MET) on the first pass at
e92b99226e, because the workspace was not built:check:dual-build-cjs-loads,check:lean-entry-closureandcheck:i18n. On the final pass they measured, readingdist/trees that the specreposuite and the closure builds produced in this worktree. Some of those trees predate the merge ofmain.check:i18nreports all 9 packages' bundles in sync.pnpm --filter @objectstack/spec check:generatedatd66e53bdf5: all 15 generated artifacts are up to date.Acceptance notes (not filed)
packages/spec/src/system/i18n-resolver.test.tskeeps two walker fixtures that authordetailsbesidetabs(theslotsroots case and thecontractPagetranslation case). They feedwalkAddressedPageComponents/translatePage, which never parse. A walker must tolerate a stored row that already carries the pair, so I left them as they are.packages/types/src/zod/layout.zod.ts) re-attachescheckPageSourceCompletenessandcheckPageRequiresKindonto its.shape-derived schema. Until objectui re-attachescheckPageSlotPairafter its next spec bump, its own authoring validator accepts the pair. The spec doors refuse it. Carrier: the objectui spec-bump PR.tabs-wins branch stays as it is. With the pair refused at every spec door, it is simply never reached by valid metadata.Deviations from the claimed file surface
These are additions within the claim's intent, each needed by a file the claim names:
packages/spec/api-surface/ui.jsonandpackages/spec/export-origins/ui.json: generated, owed by the new export. The export itself is required by leg 2 ofobject-refinement-check-exports.test.ts, which holdsPageSchema's custom-check count equal to its exports.packages/spec/src/ui/object-refinement-check-exports.test.ts: the existing pin for page checks.packages/spec/src/ui/page-slot-pair.test.ts: this change's own tests.Generated by Claude Code