feat(spec)!: object-metric drillDown.report is ReportSchema, object-timeline items the entry kind its variant selects, and action:group / action:menu members a closed inline action (#21464, S-final) - #21764
Conversation
…the action container members (#21464) Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
… rows (#21464) Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…ts (#21464) Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 9 release-owned page(s) also name something this change touched. These are read-only:
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 4e874252541e08b7421e835ae74efaaf9a09f720 && git checkout 4e874252541e08b7421e835ae74efaaf9a09f720
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a6a7547074d2f705f7a2c3f525250099c42b6b72 b7335d8374ecf6ee579001bc359716f66d41bbd0 && git checkout -B drift-repro a6a7547074d2f705f7a2c3f525250099c42b6b72 && git merge --no-ff b7335d8374ecf6ee579001bc359716f66d41bbd0
node scripts/docs-audit/affected-docs.mjs --json a6a7547074d2f705f7a2c3f525250099c42b6b72
|
Fixes #21464
Clause-②: yes (narrowing)
What this does
The S-final stage of the
ComponentPropsMapz.unknown()close-out, and its last. It executes the maintainer's rulings on forks 1, 4 and 5 of the decision card #21704: fork 1 letter B (ruling record5978663135, batch #276), forks 4 and 5 letters B and A (record5979239990, batch #277), per the claim5981629450. Read points are at the.objectui-shapin2e818d0b51ec, under objectuipackages/unless named. Every cited reader file is byte-identical at objectuimain2abec3a96(the pin is an ancestor of it;plugin-timeline/src/renderHandoff.tsandtypes/src/data-display.tsdiffer there in comments and a typed click slot only, not in either arm).object-metric·drillDown.reportz.unknown()ReportSchema, by referenceobject-timeline·itemsz.array(z.unknown())variantby a row refinementaction:group·actions[]z.record(z.string(), z.unknown())per memberaction:button's keys bytype, plus the inline button'ssizeaction:menu·actions[]sizeWith these three typed, the enumeration pin's
forklines are gone, theforkstage is gone with them, and a new §6 pins the close-out: no stage is declared and no ledger line isstaged.Fork 1 B —
drillDown.reportisReportSchemareportto the shared drawer verbatim (plugin-dashboard/src/ObjectMetricWidget.tsx:742).DrillDownDrawer.tsxdraws it as areportnode whenisDatasetBoundReportholds (:92, used at:115) — a non-emptydataset, or ajoinedreport with a block that binds one — joining the metric's filter into the report's ownruntimeFilter(:150-153). Any other value lists the records. objectui types the member as this package'sReportSchemaauthor input (SpecReportInput). Both files are byte-identical fromab1879721595, where the fork was measured.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 (ed15448217) the joined arm refuses every block with nodataset, and every other type needsdatasetandvalues. The new pin's §4 restatesisDatasetBoundReportfrom the pin and checks it over 14 candidate reports: every report the member admits is drawn (zero exceptions), and the four writer shapes are admitted (a lit control).dataset, a report with noname/label, and a summary report with novalues. No measured writer authors any of them.drillDown's other members, stage 5's.enabled,title,target,columnsandmaxRowsstay the chart drill-down's by reference, andfilter/modestay refused by name. The block's describe and docblocks now sayreportisReportSchema. The metric family pin's §3 key set is unchanged.ReportSchema's defaults (type,drilldown) materialize on parse, soObjectMetricPropsParsednow also differs from the authored type ondrillDown.report. Its docblock says so. A page component'spropertiesis not parsed on the way to the renderer, so the drawer still reads the report as written.Fork 4 B — the timeline entry, both arms closed
time,title,description,variant,icon,contentandclassName(plugin-timeline/src/renderer.tsx:1612-1659vertical,:1697-1716horizontal). The gantt branch reads a row'slabel(:1899-1900) anditems(classifyGanttRows,:617-621; drawn at:1908), and each bar'sstartDate,endDate(:1909),variant(:1916) andtitle(:1918,:1921). That is exactly objectui'sTimelineFeedItem(seven keys) andTimelineGanttItem/TimelineGanttItemBar, taken as ruled.TimelineItemSchemashape. A union would fold an off-shape bar's issue into oneinvalid_unionat the entry, as objectui's docblock records.objectTimelineItemsFitVariant, restating objectui'stimelineItemsFitVariant) pairs each entry with the arm the row'svariantselects (absent meansvertical). It refuses, each at the key it names: the arm's required key absent (titleon a feed entry,labelon a gantt row), or a key only the other arm declares. The arm key lists are read off the two shapes, never restated.contentis opaque, by the ruling:z.unknown(), its describe says "Held opaque", and the enumeration pin records it under a newopaquereason naming the ruling record (§2 checks both). It is not a slot position.z.number()refusesInfinityandNaN). TheDatearm is left out, by the ruling.color,startDate,endDate,group,metaon an entry) are refused, each with what an authored entry writes instead.dateis an alias fortime.Fork 5 A — the container members, measured from the reads
action-group.tsx(InlineActionButton:91-174,DropdownActionItem:188-247,handleExecute:306-385),action-menu.tsx(ActionMenuItem:92-150,ActionAutoTrigger:177-191,handleExecute:244-334),static-params.ts(:142-148,:172-183) andauto-trigger.ts(:96). It is not transcribed fromUIActionSchema. All four files are byte-identical fromab1879721595and at objectuimain.label(orname),icon,variantandtags(separator-before, the one tag read,:224/:408).visibleanddisabled.locations(actionRendersAton the group,:304).type,name,label,description,target,openIn,method,params,bodyExtra,bodyShape,operation,patch,confirmText,successMessage,errorMessage,refreshAfter,locations,toast,resultDialog,onSuccessandobjectName.action:button's keys bytype, with two differences the reads decide, and §3 derives the key set fromActionButtonPropsSchema.shapeto pin them.undoableandrecordIdFieldare not forwarded by either container.tagsis drawn by both.sizeis read only by a group's inline button (:124,mddrawn asdefault); anaction:menuitem reads none and declares none (theaction:iconprecedent).visible/disabledtake the rows' ownactionCondition(), and §3 checks the same accept set and the same envelope. The runner-forwarded blocks stayz.unknown()with "forwarded to the runner" in their describes, so the enumeration pin'srunnerreason holds for each.actionType→type(the rows' alias table, turned round);endpoint/url/path/href→target(the rows'ACTION_TARGET_ALIASES);enabled(the rows' own text) andautoTrigger(the rows' text onaction:menu; onaction:group, which never reads it, the text says so);outcomeMessages→successMessage;className→variant, or the node's ownclassName;properties→bodyExtra, or the action as its ownaction:button;undoable/recordIdField;action:menumember'ssize.outcomeMessagesstays undeclared on all four action blocks. §3 pins that none ofaction:button,action:icon,action:group,action:menuand neither member shape declares it. Theaction:button/action:iconrows are not edited.The census (writers of all three members)
Instrument. This run's TypeScript-AST walk over code and fenced docs, with same-file constants and spreads,
.mapover a constant list, templates and same-file helper calls evaluated. It reads:typealso through a spread constant);schema={…}, orObjectMetricWidget's own props);report/items/actionskey in a file naming a block.Every static value was parsed through this branch's rows; each value with a non-static part, and each refusal, was read by hand. Cross-check: it reproduces stage 5's
drillDownpopulation exactly (26 values) and the S-objectui-held census's 40action:group/ 19action:menuvalues.1289925c0a(the base;mainmoved 5 commits to33f97917ac, merged here throughos-regen-merge.sh, none touching a census corpus file):items(component-element-navigation-17987.test.ts:213, a feed entry), which parses;component-action-element-rows-20371.test.ts), which parse, plus that file's two probes the old row already refused;2e818d0b51ecand atmain2abec3a96, the same counts at both:drillDown.report: of the 26drillDownvalues, 2 carry areport. The drawn one (objectMetricDrillDownMembers-8071.test.tsx:281) parses. The other is that file'sit.eachpair (:305), the two values the drawer does NOT draw, both refused. The loose pass's 34reportkeys: 17 parse (drill-mirror tests and the dashboard guide); 9 are refused, all refusal probes on objectui's own faces ({ name },{ name: 42 }, the matrix with novaluesindrill-down-report-name-retired-11517.test.ts); 4 are not static and 4 are not report objects (i18n strings).items: 18 values. 15 parse: feed entries, gantt rows and the empty gantt. The 3 refused are the render-time gantt date diagnostic's own probes, an array,falseandnullbar date (timeline-gantt-date-spelling-6907.test.tsx:338,timeline-gantt-date-type-rule-6781.test.tsx:419,timeline-gantt-null-date-6770.test.tsx:220).Members:
action:group40 values (26 parse, 2 refused, 12 partly non-static) andaction:menu19 (11 parse, 3 refused, 5 partly non-static), all in tests but one run-time hand-off. Every refused member is a probe of a read the ruling refuses:className(action-group-menu-inputs-11168.test.tsx:249);outcomeMessagesforward tests (action-outcomeMessages-forward-11344.test.tsx:147,:158);properties.paramsstatic-value tests (action-container-member-params-10290.test.tsx, read by hand);autoTriggerflag (action-overflow-autotrigger.test.tsx:298, and as a test device inaction-onSuccess-forward.test.tsx:182,action-objectName-onClick-4202.test.tsx:127andaction-menu-host-disabled-11182.test.tsx).The partly non-static members are predicate variables, helper-built members and code-composed
onClickfunctions, read by hand; their static keys parse.The run-time hand-off is
action:bar's overflow menu (action-bar.tsx:287). It hands the bar's own members — the registered actions a host passes — toaction:menuat run time, never through the component-props gate, and those members areActionSchemaentries (the ruled-out option C). Recorded, not a block writer.4054ec2680and cloud at2205b53010: no writer of any of the three. hotcrm's fourobject-metrictiles declare nodrillDown.Release
.changeset/21464-component-props-report-items-action-members-typed.md:'@objectstack/spec': minor, the BREAKING banner,Clause-②: yes (narrowing), the ADR-0087registeredmarker naming the three ids, a FROM → TO table and the census.18.ui-object-metric-drill-down-report-typed.ts,18.ui-object-timeline-items-typed.tsand18.ui-action-group-menu-members-typed.ts. The semantic region was regenerated bygen:migration-registry.RETIRED_KEYS_BY_MAJORrow: page-componentpropertiesis not on the save or load path, and no declared key is removed.content/docs/references/ui/component.mdx(two member tables and the timeline entry table) and the strictness counts (ui/204 → 208,component.zod.ts74 → 78: the entry, the bar and the two members).dropped-refinements.baseline.json670 → 676.ObjectMetricPropsgains the fiveReportSchemarefinement sites carried by reference, the S-forms growth-by-reuse precedent.ObjectTimelinePropsgains its root, the new pairing refinement.cel-action-member-visible(fail-closed) andcel-action-member-disabled(fail-closed), for the two new positionsactionContainerMemberShape.visible/.disabled. Each has one evaluation leg, the container's, and no node gate (feat(spec): ComponentPropsMap rows for action:button/group/menu/icon and element:definition-list/repeater #20420 and the S-forms rows are the precedent).Tests
@objectstack/spec@17.6.0(npm tarball,ComponentPropsMap[type].safeParse): 33 of the 34 values this branch refuses are ACCEPTED there. The one refused,items: 42, was already refused by the oldz.array(a control). On this branch all 34 are refused.component-report-items-action-members-typed.pin.test.ts:ReportSchema.parse(report);codeandpath(the red-first set), plus the prescriptions;outcomeMessagesabsent on all six faces, the shared condition);ast, the runner-forwarded members, the report's tworuntimeFilterpositions, the opaquecontent), the four fork lines gone, and §2 and §6 added. The metric family pin: the drawn report row moves out of the byte-identical table into its own case, asserting the parse equalsReportSchema's answer (noexpectremoved, no skip).pnpm --filter @objectstack/spec testatb7335d8374: Test Files 615 passed (615); Tests 18358 passed, 1 todo.pnpm --filter @objectstack/spec typecheckat the same head: exit 0, test layer held (52 files / 246 errors / 135 signatures; the touched pins are ontsconfig.test.json).pnpm --filter @objectstack/lint test: 119 files, 5627 tests passed. The showcasevalidate: passed. Dogfoodtest/expression-conformance.test.ts: 7 passed.scripts/ablation-replace.mjsin a driver with an EXIT / INT / TERM trap, restored and proven by blob hash (HEAD blobb4dcaf34d2,git diff HEADempty after every leg):report→z.unknown(): 17 red;itemselement →z.unknown(): 18 red;action:groupmember → an open record: 15 red;action:menumember → an open record: 13 red.lint'svalidateComponentProps. Nothing is reported for the drawn report drill, feed entries or group members.component-props-invalidis reported atdrillDown.report.dataset/drillDown.reportand atitems.0.title/.label/.items.component-props-unknown-keyis reported for an entry'scolor, a member'sactionTypeand a menu member'soutcomeMessages.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsgives 114 commands (merge base33f97917a, 14 paths, 1705 changed lines). All 114 exit 0 atb7335d8374, and the--ranreconciliation reads 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN. Two first ran as PREREQUISITE NOT MET (check:skill-examples,check:dual-build-cjs-loads) and were re-run green afterturbo run build --filter=!@objectstack/docs(72 successful).pull_requestpayload (--event):check-changeset-no-major(LEVEL AXIS:yes (narrowing), no moved package gradedpatch),check-adr-0087-registration(one declared-breaking changeset,registeredwith the three ids) andcheck-empty-changeset, each exit 0.pnpm lint:eslint --no-inline-config --format jsonover the 10 changed.tsfiles reads 10 files, 0 errors, 0 warnings. The population is the repo's oneeslint.config.mjs, which ignored none of the 10. The narrowing excludes nothing: that config never enables type-aware linting (eslint.config.mjs:327-328, noparserOptions.project), so this diff cannot move a verdict on an untouched file.pnpm lint, reason: CI-owned. objectui was read at the pin and atmain, not built against this spec.Acceptance notes
Noted, not filed:
paramsis forwarded as the request payload of atype: 'api'member only. On any other type the container drops it with a development warning (static-params.ts:172-183). The member keepsparamsas the rows do (z.unknown(), runner-forwarded), and its describe says so.action:barhand-off above composesaction:menumembers from the host's registered actions at run time. Those carryActionSchemakeys (outcomeMessages,order,component, …) the member shape refuses. No gate judges that composition, and theaction:buttonrow's docblock already records the same member path foroutcomeMessages.action-block-endpoint-to-targetrewrites a storedendpointonaction:button/action:iconnodes only. A stored memberendpointis not rewritten. The census found no writer of one, and the refusal carries the rename.ui-object-metric-drill-down-typed,ui-object-timeline-mapping-typed) still say these members "stay open". The new fragments (orders 80-82) follow them and say they are now typed, so the joined text reads in order. The older fragments are not edited, the S-forms precedent.mainas2abec3a9, not yet at the.objectui-shapin) are not declared on the S-forms runtime form field here. That is fork 2's follow-up, outside this claim's file surface; its carrier is the next pin bump.Not in this PR
action:button/action:iconrow edit, and noview.zod.ts/report.zod.tsedit.Generated by Claude Code