Repository navigation
fix(components): place action:bar members authored action:menu / action:group (objectui#10345) - #10470
Conversation
…on:group On an action:bar, a single action's `component` now decides where it renders (ruling A). `action:menu` puts it in the bar's one overflow menu without spending an inline slot; `action:group` renders it as an action:button inside a button group shared with the adjacent action:group members of the inline row. Both used to hand the single action to a renderer that reads `schema.actions`, so the action vanished and a ?runAction= deep link to it ran nothing. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
…ntrol leg Without it the lacking-permission rows read "absent", which is also what the unplaced member read before the fix, so they could not fail. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
…entence true The pending objectui#10274 entry said action:bar renders each inline action with the renderer its `component` names. An inline action:group member now renders through action:button, so the sentence is narrowed to the two values it still holds for. Front matter unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C
✅ 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
|
Contract reviewServed-tier: Rendered by an isolated review subagent spawned by the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10345
Clause-②: no
On an
action:bar, a single action'scomponentnow decides where it renders (ruling A, comment 5825579152). Before this change, a member authoredcomponent: 'action:menu'or'action:group'was handed alone to a renderer that readsschema.actions. That renderer returned null, so the action vanished and a?runAction=deep link to it ran nothing.What changed (all in
action-bar.tsx)action:menu: the action goes into the bar's existing single overflow menu (the one "More" button). It is taken out before themaxVisiblesplit, so it never spends an inline slot.page:headeralready reads the key this way ("forces an action into the⋯menu regardless of the count").action:group: the action renders inline, through theaction:buttonrenderer, inside aButtonGroup(the existingcustom/button-groupprimitive,role="group"). The group is shared with its adjacentaction:groupmembers.trash-2icon is untouched (draft PR objectui#8941 edits that line).action-menu.tsxandaction-group.tsxare untouched, and neither renderer is taught to accept a single action."Adjacent", as measured and pinned
Adjacent means consecutive in the inline row the bar draws: after ordering (
order, then primary), after themaxVisiblecut, and with the menu-placed actions already removed.[G1, G2, Plain, G3]gives two groups,[G1 G2]and[G3].[G1, MenuPlaced, G2]gives one group,[G1 G2]. The menu-placed action is not in the row, so it does not split the run.maxVisible. A member past the budget spills into the "More" menu like any inline member.PM mechanism assumptions, measured
combinedOverflowruns its deep link throughaction:menu's existingActionAutoTrigger/useAutoTriggerOnce, with no new trigger code. Pinned by the probe's menu row and the deep-link rows.action-group.tsxdraws its ownInlineActionButton. It consumes noautoTrigger, and itsvisiblegate is fail-soft whereaction:button's is fail-closed. Routing grouped members through theaction:grouprenderer would therefore leave the deep link dead, and would gate them differently from an inline member. So the bar draws the group itself and renders each member throughaction:button, the renderer an ungrouped member gets. The deep link runs and the gates are identical by construction.action-group.tsxneeded no change, so there was no needs-decision fork.maxVisible(pinned with[M1, B1, B2],maxVisible: 2: both B's stay inline). An overflow that holds only menu-placed members renders exactly one "More" (pinned by the child count and thearia-haspopupcount).maxVisiblecome first, then the menu-placed ones, each in the bar's own order, then the separator and the system actions. This is the same relative orderpage:headeruses. The pin includes a menu-placed member withorder: -1, which sorts first in the bar but still does not claim the primary inline slot.requiredPermissionsis filtered before the split, so it gates every placement identically. Pins:The card's four-row probe, after the fix
componentaction:buttonaction:iconaction:menuaction:groupVerification (at
da122c4cf)action-bar-member-placement-10345.test.tsx: 21/21 green.action-bar.tsxwas replaced with its blob at the base commit0348bc9f1, which removes the placement branch. The on-disk mutation was confirmed with grep counts:inlineRowwent from 2 to 0,inlineActions.mapfrom 0 to 1, and the file hash equalled the base blob. The suite then read 19 failed, 2 passed. The 2 are theaction:button/action:iconprobe rows, which are correct in both worlds. The menu and group probe rows read 0 runs, matching the card's table. The file was restored withgit checkout HEAD, and the restore was proved by the hash equalling the HEAD blob and an emptygit diff HEAD. The first ablation run showed the capability rows staying green (they were vacuous), so the held control legs were added and the proof was re-run.pnpm exec vitest runfrom the repo root). They were: every@object-ui/componentssuite namingaction-bar,action:bar,action:menu,action:grouporuseAutoTriggerOnce; every file underrenderers/action/__tests__/; the app-shell deep-link consumers (EnvironmentListToolbardeep-link suites anduseOfferedNavRunAction); and the two script pins that nameaction:bar.@object-ui/componentsdependency closure was built, thenpnpm --filter @object-ui/components type-checkexited 0. That runs the package's owntsc --noEmitandtsconfig.test.json, and--listFilesshows both include the touched and new files.action-bar.tsxhas 5 warnings at the base commit and 5 now, all on untouched lines.check-changeset-presence,check-changeset-no-major,check:new-line-citations(0 new),check:control-bytes,check:changeset-claims("No pending changeset names a file this change touches"),check-vi-mock-override-shape,check-test-path-rootsandcheck-lint-coverageall exited 0.empty:hiddenwere not observed.Changesets
.changeset/10345-action-bar-menu-group-placement.md(@object-ui/components, patch).action:barrenders each inline action with the renderer itscomponentnames". An inlineaction:groupmember now renders throughaction:button, so that sentence is narrowed toaction:button/action:icon. Only the body changed; the front matter is byte-identical.maxVisible" sentences describe members whosecomponentdoes not place them, and the literaltype: componentType, actionType: action.typespread is kept.Acceptance notes (observations, not filed)
action:group's own leaves (InlineActionButton,DropdownActionItem) consume noautoTrigger, whileauto-trigger.tsstates that every renderer that can receive an action consumes it. No host sends a flagged action into a standaloneaction:grouptoday (the deep-link host composes anaction:bar), so this is dormant and was not filed.visiblestill shows an empty "More" trigger. That was already true for spilled actions before this change.content/docs/guide/slotted-pages.mdsayspage:headeruses "the same rules asaction:bar", includingcomponent: 'action:menu'pinning an action into the⋯menu. That sentence was false foraction:barbefore this change and is true after it.Dispatched by the
domain:ui#4seat; this run's session ishttps://claude.ai/code/session_01BP8CMtACxTdLjqR6rhd33C.Generated by Claude Code