Skip to content

Commit 4331a6b

Browse files
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)
Fixes #21464 Clause-②: yes (narrowing) ## What this does The S-final stage of the `ComponentPropsMap` `z.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 record `5978663135`, batch #276), forks 4 and 5 letters B and A (record `5979239990`, batch #277), per the claim `5981629450`. Read points are at the `.objectui-sha` pin `2e818d0b51ec`, under objectui `packages/` unless named. Every cited reader file is byte-identical at objectui `main` `2abec3a96` (the pin is an ancestor of it; `plugin-timeline/src/renderHandoff.ts` and `types/src/data-display.ts` differ there in comments and a typed click slot only, not in either arm). | row · member | was | now | |:--|:--|:--| | `object-metric` · `drillDown.report` | `z.unknown()` | `ReportSchema`, by reference | | `object-timeline` · `items` | `z.array(z.unknown())` | a closed entry (module-private, built once) of objectui#6356's two arms, paired with the row's `variant` by a row refinement | | `action:group` · `actions[]` | `z.record(z.string(), z.unknown())` per member | a closed inline action (module-private): `action:button`'s keys by `type`, plus the inline button's `size` | | `action:menu` · `actions[]` | the same | the same member without `size` | With these three typed, the enumeration pin's `fork` lines are gone, the `fork` stage is gone with them, and a new §6 pins the close-out: no stage is declared and no ledger line is `staged`. ## Fork 1 B — `drillDown.report` is `ReportSchema` - **The read.** The tile hands `report` to the shared drawer verbatim (`plugin-dashboard/src/ObjectMetricWidget.tsx:742`). `DrillDownDrawer.tsx` draws it as a `report` node when `isDatasetBoundReport` holds (`:92`, used at `:115`) — a non-empty `dataset`, or a `joined` report with a block that binds one — joining the metric's filter into the report's own `runtimeFilter` (`:150-153`). Any other value lists the records. objectui types the member as this package's `ReportSchema` author input (`SpecReportInput`). Both files are byte-identical from `ab1879721595`, where the fork was measured. - **The premise, re-measured: admitted implies drawn.** Since #21702 (`ed15448217`) the joined arm refuses every block with no `dataset`, and every other type needs `dataset` and `values`. The new pin's §4 restates `isDatasetBoundReport` from 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). - **The remaining difference runs one way only, and is pinned as such.** The drawer also draws four incomplete reports the member refuses: a joined report with only some blocks bound, a joined report with a container `dataset`, a report with no `name` / `label`, and a summary report with no `values`. No measured writer authors any of them. - **`drillDown`'s other members, stage 5's.** `enabled`, `title`, `target`, `columns` and `maxRows` stay the chart drill-down's by reference, and `filter` / `mode` stay refused by name. The block's describe and docblocks now say `report` is `ReportSchema`. The metric family pin's §3 key set is unchanged. - **Parsed type.** `ReportSchema`'s defaults (`type`, `drilldown`) materialize on parse, so `ObjectMetricPropsParsed` now also differs from the authored type on `drillDown.report`. Its docblock says so. A page component's `properties` is 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 - **The arms, read at the pin.** The feed branches read `time`, `title`, `description`, `variant`, `icon`, `content` and `className` (`plugin-timeline/src/renderer.tsx:1612-1659` vertical, `:1697-1716` horizontal). The gantt branch reads a row's `label` (`:1899-1900`) and `items` (`classifyGanttRows`, `:617-621`; drawn at `:1908`), and each bar's `startDate`, `endDate` (`:1909`), `variant` (`:1916`) and `title` (`:1918`, `:1921`). That is exactly objectui's `TimelineFeedItem` (seven keys) and `TimelineGanttItem` / `TimelineGanttItemBar`, taken as ruled. - **One entry shape, not a union.** The entry declares every member of both arms, each optional, closed against anything else — objectui's own `TimelineItemSchema` shape. A union would fold an off-shape bar's issue into one `invalid_union` at the entry, as objectui's docblock records. - **The row refinement** (`objectTimelineItemsFitVariant`, restating objectui's `timelineItemsFitVariant`) pairs each entry with the arm the row's `variant` selects (absent means `vertical`). It refuses, each at the key it names: the arm's required key absent (`title` on a feed entry, `label` on a gantt row), or a key only the other arm declares. The arm key lists are read off the two shapes, never restated. - **`content` is opaque**, by the ruling: `z.unknown()`, its describe says "Held opaque", and the enumeration pin records it under a new `opaque` reason naming the ruling record (§2 checks both). It is not a slot position. - **Gantt-bar dates** are a string or a finite number (`z.number()` refuses `Infinity` and `NaN`). The `Date` arm is left out, by the ruling. - **The record-composed keys** (`color`, `startDate`, `endDate`, `group`, `meta` on an entry) are refused, each with what an authored entry writes instead. `date` is an alias for `time`. ## Fork 5 A — the container members, measured from the reads - **The read set, measured** from `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`) and `auto-trigger.ts` (`:96`). It is not transcribed from `UIActionSchema`. All four files are byte-identical from `ab1879721595` and at objectui `main`. - Drawn: `label` (or `name`), `icon`, `variant` and `tags` (`separator-before`, the one tag read, `:224` / `:408`). - Gated: `visible` and `disabled`. - Placed: `locations` (`actionRendersAt` on the group, `:304`). - Forwarded to the runner: `type`, `name`, `label`, `description`, `target`, `openIn`, `method`, `params`, `bodyExtra`, `bodyShape`, `operation`, `patch`, `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, `locations`, `toast`, `resultDialog`, `onSuccess` and `objectName`. - **That is `action:button`'s keys by `type`**, with two differences the reads decide, and §3 derives the key set from `ActionButtonPropsSchema.shape` to pin them. `undoable` and `recordIdField` are not forwarded by either container. `tags` is drawn by both. `size` is read only by a group's inline button (`:124`, `md` drawn as `default`); an `action:menu` item reads none and declares none (the `action:icon` precedent). - **Value schemas are the rows'**, key by key. `visible` / `disabled` take the rows' own `actionCondition()`, and §3 checks the same accept set and the same envelope. The runner-forwarded blocks stay `z.unknown()` with "forwarded to the runner" in their describes, so the enumeration pin's `runner` reason holds for each. - **Refused, each with a prescription:** - `actionType` → `type` (the rows' alias table, turned round); - `endpoint` / `url` / `path` / `href` → `target` (the rows' `ACTION_TARGET_ALIASES`); - `enabled` (the rows' own text) and `autoTrigger` (the rows' text on `action:menu`; on `action:group`, which never reads it, the text says so); - `outcomeMessages` → `successMessage`; - a member `className` → `variant`, or the node's own `className`; - `properties` → `bodyExtra`, or the action as its own `action:button`; - `undoable` / `recordIdField`; - an `action:menu` member's `size`. - **`outcomeMessages` stays undeclared on all four action blocks.** §3 pins that none of `action:button`, `action:icon`, `action:group`, `action:menu` and neither member shape declares it. The `action:button` / `action:icon` rows 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, `.map` over a constant list, templates and same-file helper calls evaluated. It reads: - object literals naming the block (the `type` also through a spread constant); - typed literals; - direct parses through the row (receiver constants resolved); - the block's JSX component (`schema={…}`, or `ObjectMetricWidget`'s own props); - helper parameters at every same-file call site, for both member and whole-node positions; - a loose pass over every `report` / `items` / `actions` key 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 `drillDown` population exactly (26 values) and the S-objectui-held census's 40 `action:group` / 19 `action:menu` values. - **objectstack** at `1289925c0a` (the base; `main` moved 5 commits to `33f97917ac`, merged here through `os-regen-merge.sh`, none touching a census corpus file): - one drill report (the metric pin's own), which parses; - one timeline `items` (`component-element-navigation-17987.test.ts:213`, a feed entry), which parses; - three container members (`component-action-element-rows-20371.test.ts`), which parse, plus that file's two probes the old row already refused; - no example, doc or skill writes any of the three. - **objectui** at the pin `2e818d0b51ec` and at `main` `2abec3a96`, the same counts at both: - **`drillDown.report`:** of the 26 `drillDown` values, 2 carry a `report`. The drawn one (`objectMetricDrillDownMembers-8071.test.tsx:281`) parses. The other is that file's `it.each` pair (`:305`), the two values the drawer does NOT draw, both refused. The loose pass's 34 `report` keys: 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 no `values` in `drill-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, `false` and `null` bar 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:group` 40 values (26 parse, 2 refused, 12 partly non-static) and `action:menu` 19 (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: - the member pin's `className` (`action-group-menu-inputs-11168.test.tsx:249`); - the `outcomeMessages` forward tests (`action-outcomeMessages-forward-11344.test.tsx:147`, `:158`); - the `properties.params` static-value tests (`action-container-member-params-10290.test.tsx`, read by hand); - the host's `autoTrigger` flag (`action-overflow-autotrigger.test.tsx:298`, and as a test device in `action-onSuccess-forward.test.tsx:182`, `action-objectName-onClick-4202.test.tsx:127` and `action-menu-host-disabled-11182.test.tsx`). The partly non-static members are predicate variables, helper-built members and code-composed `onClick` functions, 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 — to `action:menu` at run time, never through the component-props gate, and those members are `ActionSchema` entries (the ruled-out option C). Recorded, not a block writer. - **hotcrm** at `4054ec2680` and **cloud** at `2205b53010`: no writer of any of the three. hotcrm's four `object-metric` tiles declare no `drillDown`. - **No measured working writer is refused**, so the stop valve does not trip on any of the three members. ## Release - `.changeset/21464-component-props-report-items-action-members-typed.md`: `'@objectstack/spec': minor`, the BREAKING banner, `Clause-②: yes (narrowing)`, the ADR-0087 `registered` marker naming the three ids, a FROM → TO table and the census. - Three D3 entries in step 18: `18.ui-object-metric-drill-down-report-typed.ts`, `18.ui-object-timeline-items-typed.ts` and `18.ui-action-group-menu-members-typed.ts`. The semantic region was regenerated by `gen:migration-registry`. - Three rationale fragments at orders 80, 81 and 82, the next free after S-forms' 79. - No D2 conversion and no `RETIRED_KEYS_BY_MAJOR` row: page-component `properties` is not on the save or load path, and no declared key is removed. - No export is added or removed: the entry, bar and member builders are module-private. - Generated: `content/docs/references/ui/component.mdx` (two member tables and the timeline entry table) and the strictness counts (`ui/` 204 → 208, `component.zod.ts` 74 → 78: the entry, the bar and the two members). - `dropped-refinements.baseline.json` 670 → 676. `ObjectMetricProps` gains the five `ReportSchema` refinement sites carried by reference, the S-forms growth-by-reuse precedent. `ObjectTimelineProps` gains its root, the new pairing refinement. - Dogfood expression-conformance ledger: two rows, `cel-action-member-visible` (fail-closed) and `cel-action-member-disabled` (fail-closed), for the two new positions `actionContainerMemberShape.visible` / `.disabled`. Each has one evaluation leg, the container's, and no node gate (#20420 and the S-forms rows are the precedent). ## Tests - **Red first,** through the published `@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 old `z.array` (a control). On this branch all 34 are refused. - **The new pin `component-report-items-action-members-typed.pin.test.ts`:** - §1: the writer shapes parse — timeline entries and members byte-identical, drill reports to exactly `ReportSchema.parse(report)`; - §2: 34 refusals by `code` and `path` (the red-first set), plus the prescriptions; - §3: the vocabulary pins (the report def identity, the arms' key sets, the member key set derived from the button's, `outcomeMessages` absent on all six faces, the shared condition); - §4: admitted implies drawn; - §5: the D3 ids. - **The enumeration pin:** 23 new reasoned lines (the member predicates' `ast`, the runner-forwarded members, the report's two `runtimeFilter` positions, the opaque `content`), 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 equals `ReportSchema`'s answer (no `expect` removed, no skip). - `pnpm --filter @objectstack/spec test` at `b7335d8374`: Test Files 615 passed (615); Tests 18358 passed, 1 todo. `pnpm --filter @objectstack/spec typecheck` at the same head: exit 0, test layer held (52 files / 246 errors / 135 signatures; the touched pins are on `tsconfig.test.json`). `pnpm --filter @objectstack/lint test`: 119 files, 5627 tests passed. The showcase `validate`: passed. Dogfood `test/expression-conformance.test.ts`: 7 passed. - **Ablation**, one leg per member (two for the timeline), each through `scripts/ablation-replace.mjs` in a driver with an EXIT / INT / TERM trap, restored and proven by blob hash (HEAD blob `b4dcaf34d2`, `git diff HEAD` empty after every leg): - `report` → `z.unknown()`: 17 red; - `items` element → `z.unknown()`: 18 red; - the pairing refinement dropped: 5 red; - the `action:group` member → an open record: 15 red; - the `action:menu` member → an open record: 13 red. - **Public door,** through built `lint`'s `validateComponentProps`. Nothing is reported for the drawn report drill, feed entries or group members. `component-props-invalid` is reported at `drillDown.report.dataset` / `drillDown.report` and at `items.0.title` / `.label` / `.items`. `component-props-unknown-key` is reported for an entry's `color`, a member's `actionType` and a menu member's `outcomeMessages`. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` gives 114 commands (merge base `33f97917a`, 14 paths, 1705 changed lines). All 114 exit 0 at `b7335d8374`, and the `--ran` reconciliation 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 after `turbo run build --filter=!@objectstack/docs` (72 successful). - **The changeset gates with this body as the `pull_request` payload** (`--event`): `check-changeset-no-major` (LEVEL AXIS: `yes (narrowing)`, no moved package graded `patch`), `check-adr-0087-registration` (one declared-breaking changeset, `registered` with the three ids) and `check-empty-changeset`, each exit 0. - **eslint, a proven narrowing of `pnpm lint`:** `eslint --no-inline-config --format json` over the 10 changed `.ts` files reads 10 files, 0 errors, 0 warnings. The population is the repo's one `eslint.config.mjs`, which ignored none of the 10. The narrowing excludes nothing: that config never enables type-aware linting (`eslint.config.mjs:327-328`, no `parserOptions.project`), so this diff cannot move a verdict on an untouched file. - **NOT MEASURED:** the Console Pin Gate, the Dogfood Regression Gate's full suite and the full `pnpm lint`, reason: CI-owned. objectui was read at the pin and at `main`, not built against this spec. ## Acceptance notes Noted, not filed: - **A member's object `params`** is forwarded as the request payload of a `type: 'api'` member only. On any other type the container drops it with a development warning (`static-params.ts:172-183`). The member keeps `params` as the rows do (`z.unknown()`, runner-forwarded), and its describe says so. - **The `action:bar` hand-off** above composes `action:menu` members from the host's registered actions at run time. Those carry `ActionSchema` keys (`outcomeMessages`, `order`, `component`, …) the member shape refuses. No gate judges that composition, and the `action:button` row's docblock already records the same member path for `outcomeMessages`. - **The D2 conversion `action-block-endpoint-to-target`** rewrites a stored `endpoint` on `action:button` / `action:icon` nodes only. A stored member `endpoint` is not rewritten. The census found no writer of one, and the refusal carries the rename. - **Earlier step-18 rationale fragments** (`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. - **The grid widget's camelCase keys** (objectui#11610, landed on objectui `main` as `2abec3a9`, not yet at the `.objectui-sha` pin) 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 - No `action:button` / `action:icon` row edit, and no `view.zod.ts` / `report.zod.ts` edit. - No objectui change and no pin bump. --- _Generated by [Claude Code](https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b7a13c7 commit 4331a6b

14 files changed

Lines changed: 1528 additions & 177 deletions
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: an `object-metric` drill-down's `report` takes a report definition, an `object-timeline`'s `items` take the entry kind its `variant` selects, and each `action:group` / `action:menu` member takes a closed inline action, instead of any value (#21464)
6+
7+
Clause-②: yes (narrowing)
8+
9+
<!-- adr-0087: registered ui-object-metric-drill-down-report-typed ui-object-timeline-items-typed ui-action-group-menu-members-typed -->
10+
11+
**BREAKING** — three accept-set narrowings on a published authoring surface, shipped as `minor` under the repo's launch-window convention for accept-set narrowings. What reads the rows: the component-props gate on `objectstack validate`, `objectstack build` and `objectstack lint`, which reports a refused value as an advisory `component-props-invalid` / `component-props-unknown-key` finding. A stored page still saves and loads, because a page component's `properties` is not parsed on the metadata save or load path.
12+
13+
**`@objectstack/spec`**
14+
15+
- **`object-metric` `drillDown.report` is a report definition — `ReportSchema`, by reference.** It was `z.unknown()`. The tile hands it to the shared drill drawer, which draws a dataset-bound report (with the metric's filter joined into the report's own `runtimeFilter`) and lists the records for any other value. A joined report already refuses a block that binds no `dataset`, so every report the member admits is one the drawer draws; a report with no `dataset`, a bare report name, a `{ name }` reference or the retired `objectName` / `columns` form is refused. The drawer still draws a few incomplete reports the member refuses (no `name` / `label`, a non-joined report with no `values`, a joined one with a container `dataset` or with only some blocks bound) — no measured writer authors one.
16+
- **`object-timeline` `items` takes the entry kind the block's `variant` selects.** It was `z.array(z.unknown())`. On `vertical` (the default) or `horizontal` an entry is a feed entry `{ time?, title, description?, variant?, icon?, content?, className? }`; on `gantt` it is a gantt row `{ label, items? }` whose bars are `{ title?, startDate?, endDate?, variant? }`, each date a string or epoch milliseconds — objectui's two ruled element kinds, closed. A row refinement pairs each entry with its kind: a feed entry with no `title`, a gantt row with no `label`, and a key of the other kind are refused at the entry, by path. `variant` is one of `default`, `success`, `warning`, `danger`, `info`. A feed entry's `content` (child components) is not judged yet. The keys the record-bound rail composes onto its entries (`color`, `startDate`, `endDate`, `group`, `meta`) are refused on an authored entry with what to write instead.
17+
- **Each `action:group` / `action:menu` member is a closed inline action.** It was an open record. A member takes `action:button`'s keys with its executor spelled `type` (a member is an action entry): `name`, `label`, `icon`, `type`, `variant`, `visible`, `disabled`, `tags`, `params`, `description`, `target`, `openIn`, `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`, `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`, `locations`, `toast`, `resultDialog`, `onSuccess`, `objectName` — and `size` on an `action:group` member, whose inline button reads it (an `action:menu` item reads none). `tags` takes `separator-before`, the one tag the containers draw. Refused, each with what to write instead: `actionType` (write `type`), `endpoint` / `url` / `path` / `href` (write `target`), `enabled` (write `disabled`, inverted), `autoTrigger`, `outcomeMessages` (write `successMessage`), a member `className`, a member `properties` bag, `undoable` and `recordIdField`. `outcomeMessages` stays undeclared on all four action blocks (`action:button`, `action:icon`, `action:group`, `action:menu`).
18+
- **`ObjectMetricProps`, `ObjectTimelineProps`, `ActionGroupProps`, `ActionMenuProps`** and their parsed types carry these shapes instead of `unknown`; the member and entry shapes are module-private. `ObjectMetricPropsParsed` now also differs from the authored type on `drillDown.report`, whose `ReportSchema` defaults (`type`, `drilldown`) materialize on parse. No export is added or removed.
19+
20+
## FROM → TO
21+
22+
| you wrote | write instead |
23+
|:--|:--|
24+
| `drillDown: { report: 'pipeline' }` or `{ report: { name: 'pipeline' } }` | `drillDown: { report: { name: 'pipeline', label: 'Pipeline', dataset: 'deals_ds', values: ['amount_sum'] } }` |
25+
| `drillDown: { report: { name, label, objectName: 'deal', columns: [ … ] } }` | the dataset-bound report: `{ name, label, dataset, rows, values }` |
26+
| `drillDown: { report: { …, type: 'joined', blocks: [{ name: 'notes' }] } }` | bind every block: `blocks: [{ name: 'notes', dataset: 'notes_ds', values: [ … ] }]` |
27+
| `items: [{ date: '2026-01-15', title: 'Kickoff' }]` | `items: [{ time: '2026-01-15', title: 'Kickoff' }]` |
28+
| `items: [{ title: 'Kickoff', color: 'green' }]` | `items: [{ title: 'Kickoff', variant: 'success' }]` |
29+
| `items: [{ label: 'Backend', items: [ … ] }]` with no `variant` | `variant: 'gantt', items: [{ label: 'Backend', items: [ … ] }]` |
30+
| `variant: 'gantt', items: [{ title: 'Kickoff' }]` | `variant: 'gantt', items: [{ label: 'Kickoff', items: [{ startDate, endDate }] }]`, or drop `variant: 'gantt'` |
31+
| `actions: [{ name: 'go', actionType: 'url', target: '/x' }]` | `actions: [{ name: 'go', type: 'url', target: '/x' }]` |
32+
| `actions: [{ name: 'save', type: 'api', endpoint: '/api/save' }]` | `actions: [{ name: 'save', type: 'api', target: '/api/save' }]` |
33+
| `actions: [{ name: 'del', outcomeMessages: { archived: 'Archived' } }]` | `actions: [{ name: 'del', successMessage: 'Archived' }]` |
34+
| `actions: [{ name: 'del', className: 'text-red-600' }]` | `actions: [{ name: 'del', variant: 'destructive' }]` |
35+
| `actions: [{ name: 'run', enabled: "record.status == 'open'" }]` | `actions: [{ name: 'run', disabled: "record.status != 'open'" }]` |
36+
| `actions: [{ name: 'edit', properties: { params: { … } } }]` on `action:group` / `action:menu` | `bodyExtra` for a `type: 'api'` request body, or the action as its own `action:button` node with a `params` object |
37+
| `action:menu` `actions: [{ name: 'a', size: 'sm' }]` | `actions: [{ name: 'a' }]` — the menu's own `size` sizes the trigger |
38+
39+
The one-line fix: write a drill report as the report definition, each timeline entry as the kind the block's `variant` draws, and each container member with `action:button`'s keys and `type` as its executor. No conversion is registered: nothing on the load path refuses these shapes, and the census below found no working value to respell — the D3 entries `ui-object-metric-drill-down-report-typed`, `ui-object-timeline-items-typed` and `ui-action-group-menu-members-typed` carry that judgment.
40+
41+
## Who is affected, measured
42+
43+
A writer is a value written on the block: a page-component node (an object literal naming the block, flat or in its `properties` bag, or with its `type` arriving through a spread constant), a literal annotated as one, a direct parse through the row, the block's React component (`schema={…}`, or its props), and the argument a local helper passes in that position at every same-file call site. Values resolve through same-file constants and spreads, a `.map` over a constant list, templates and same-file helper calls (`member('alpha', { size })`). Every static value was parsed through this branch's rows; each value with a non-static part, and each refusal, was read by hand.
44+
45+
- **objectstack** at `1289925c0a`, this branch's merge base: one drill report (the metric pin's own), one timeline `items` (a spec test, a feed entry) and three `action:group` / `action:menu` members (a spec test) — all parse. No example, doc or skill writes any of the three.
46+
- **objectui** at the `.objectui-sha` pin `2e818d0b51ec` and at `main` `2abec3a96` (every cited reader byte-identical between the two, and the same census at both): **drill report** — the drawn report drill (`objectMetricDrillDownMembers-8071.test.tsx:281`) parses; refused are only the probes of what the drawer does NOT draw (that file's two `it.each` values, and the `@object-ui/types` drill-mirror tests' `{ name }` / incomplete-report refusal probes). **Timeline `items`** — 18 values: 15 parse (feed entries and gantt rows); the 3 refused are the render-time gantt date diagnostic's own probes (an array, `false` and `null` bar date). **Members** — `action:group` 40 values and `action:menu` 19, all in tests but for one run-time hand-off: every static member parses except the probes of the very reads the ruling refuses — the member pin's `className` (`action-group-menu-inputs-11168.test.tsx:249`), the `outcomeMessages` forward tests, the `properties.params` static-value tests, and the host's `autoTrigger` flag in the overflow / forward tests. The hand-off is `action:bar`'s overflow menu (`action-bar.tsx:287`), which hands the bar's own members — a host's registered actions — to `action:menu` at run time, never through the component-props gate.
47+
- **hotcrm** at `4054ec2680` and **cloud** at `2205b53010`: no writer of any of the three (hotcrm's four `object-metric` tiles declare no drill-down).
48+
- **Deployed metadata** was not measured.

0 commit comments

Comments
 (0)