Skip to content

feat(plugin): let composer pills render their own icon - #60000

Open
arvsrn wants to merge 2 commits into
devfrom
pill-icon-render
Open

arvsrn wants to merge 2 commits into
devfrom
pill-icon-render

Conversation

@arvsrn

@arvsrn arvsrn commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Issue for this PR

None. Follows #59437, which added the goal plugin's composer pill and the pill menu.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Lets a composer pill render its own icon, and makes a pill's own popover fully composable.

ComposerPill.icon(input)

  • An optional render on the pill contribution, in place of the view's named icon and tone. It's meant for things the icon set can't draw, such as an animated status glyph.
  • It renders once per mounted pill, in a 16px slot. show() runs again whenever what it reads changes, so an icon returned from show() would be rebuilt each time and restart its animation. A render method keeps the same element and updates through its own reactivity, the same way menu and slot renders work.
  • It gets its own error boundary: an icon that throws records the error and shows nothing, and the pill keeps working.
  • ComposerPillView.icon and tone are unchanged, so existing pills don't change. Menu rows still take icon names only.

No host heading in a pill's own popover

  • For pills with menu, the host no longer draws title (or label) as an 11px heading above the plugin's content. The host keeps the popover's frame (320px wide, 4px inset, placement, focus, dismissal), and everything inside belongs to the plugin, including any heading.
  • title still names the popover for assistive technology.
  • Only the goal plugin uses menu, and its redesign in the stacked PR draws its own content. Until that lands, the goal popover shows only its buttons.

Leading icon inset

  • A pill that starts with an icon now sits it 8px from the edge with a 6px gap to the label. One :has() rule on the shared pill (composer-lane.css) does it, keyed on a new data-composer-pill-icon marker. Pills without an icon keep 10px on both sides.
  • This covers the activity, todo and plugin pills. The todo pill's own padding and gap override is removed.
  • The activity menu's rows follow the capsule's new inset and gap, so their icons still line up with the capsule's.

How did you verify your code works?

  • bun typecheck in packages/plugin, packages/app and packages/gui-plugins
  • bun script/sdk-docs.ts, oxlint and prettier on the changed files
  • Measured the activity and todo pills in their stories: icon 8px from the edge, 6px to the label, activity menu rows aligned with the capsule
  • Component tests: the three composer pill cases in plugin-host.spec.ts, including a new one. It checks that a custom icon replaces the named one and stays the same element while show() re-runs. Making the host rebuild the icon from the view fails it.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none">
<style>
.r{animation:r 2.4s infinite;transform-box:fill-box;transform-origin:center}
.c{animation:c 2.4s infinite;transform-box:fill-box;transform-origin:center}
@keyframes r{0%,16.7%{transform:scale(1);opacity:1;animation-timing-function:cubic-bezier(.2,.6,.35,1)}91.7%,100%{transform:scale(2.8);opacity:0}}
@keyframes c{0%,16.6%{transform:scale(1);opacity:1}16.7%,33.3%{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.33,0,.2,1)}75%,100%{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.r,.c{animation:none}}
</style>
<rect x="64" y="24" width="112" height="24" rx="6" stroke="currentColor" stroke-opacity=".35" stroke-width=".75"/>
<g stroke="currentColor" stroke-opacity=".25" stroke-dasharray="2 2" stroke-width=".75"><rect x="70" y="28" width="16" height="16" rx="2"/></g>
<circle cx="78" cy="36" r="5" stroke="currentColor"/>
<circle class="r" cx="78" cy="36" r="1.6" fill="currentColor"/>
<circle class="c" cx="78" cy="36" r="1.6" fill="currentColor"/>
<rect x="92" y="34" width="72" height="4" rx="2" fill="currentColor" opacity=".7"/>
</svg>

Screenshots / recordings

The stacked goal pill PR shows the custom icon in use.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@arvsrn arvsrn mentioned this pull request Oct 28, 2026
3 of 6 tasks
Base automatically changed from client-done-check to dev October 29, 2026 00:05
A composer pill can now render its own icon, such as an animated status glyph, in place of
the view's named icon and tone. It renders once per mounted pill, so an animation keeps
running while show() runs again.

A pill's own popover (menu) no longer gets a host heading. The host keeps the popover's
frame, and everything inside belongs to the plugin; the view's title names the popover
for assistive technology.
A pill that starts with an icon now sits it 8px from the edge with a 6px gap to its
label, through one rule on the shared pill instead of the todo pill's own override.
Pills without an icon keep 10px on both sides. The activity menu's rows follow the
capsule's new inset and gap.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant