Skip to content

ui: render extension-contributed detail-rail tab icons - #5

Draft
alextomas955 wants to merge 3 commits into
mainfrom
ext-tab-icon-resolution
Draft

ui: render extension-contributed detail-rail tab icons#5
alextomas955 wants to merge 3 commits into
mainfrom
ext-tab-icon-resolution

Conversation

@alextomas955

Copy link
Copy Markdown
Owner

Summary

Extension-contributed detail-rail tabs can now render their own icon. The tab's icon value resolves in three forms:

  • component:<Name> — the extension's own registered component (e.g. a brand logo), rendered inline so it inherits the tab's currentColor like a native icon;
  • an allowlisted lucide name — that lucide glyph;
  • anything else / missing — falls back to the layout's default tab icon.

Previously the extension tab's icon was dropped when the rail was assembled, so every extension tab showed the default puzzle glyph.

Safe against a misbehaving or hostile extension: the lucide set is an explicit allowlist (no dynamic lucide-react barrel import, and no way to render an arbitrary component by name); a component: icon is sandboxed in an error boundary and hard-clamped to icon size, so a crashing or oversized icon component falls back to the default and cannot break the rail.

Linked issue

Closes #

Type of change

  • New feature
  • Bug fix
  • Refactor / tech debt
  • Docs
  • Other:

AI usage

  • No AI was used for this PR.
  • AI was used for this PR.
    • Model(s): Claude Opus 4.8
    • Where / how: implementation and the added test
  • A human (me) has reviewed, understands, and takes full responsibility for every change here including that the design and architecture are sound. (required)

Testing done & evidence

  • tsc -b clean; vite build succeeds.
  • Added ui/src/test/extensionTabIcon.test.tsx (5 cases): a component: icon renders the resolved component; an allowlisted lucide name renders that glyph; an unknown component, unknown name, and missing name each resolve to undefined so the default applies.
  • Ran the full ui vitest suite — no new failures.
  • Verified live in a dev instance running an extension that contributes a video detail-rail tab: the tab renders the extension's own logo (below), inheriting the tab color. An induced bad icon name fell back to the default puzzle glyph with the tab still present and no page error.

Extension detail-rail tab rendering the extension's own logo icon

Checklist

  • I have read and followed the Contribution Guide.
  • Builds and existing tests pass.
  • I added or updated tests where it makes sense.
  • I updated docs where needed.
  • This PR is focused and does not bundle unrelated changes.

@alextomas955
alextomas955 force-pushed the ext-tab-icon-resolution branch 3 times, most recently from e4ec9d5 to e8a7163 Compare July 19, 2026 02:08
A detail-rail tab contributed by an extension can now render an icon, set via
its manifest `icon` string as a bare name (no prefix). The name resolves in
order: a host built-in named icon (resolveIcon), then a component the extension
registered under that name (e.g. its own brand logo); an unresolved name falls
back to the default glyph and warns in dev. Built-in-first, so a shared icon
name can never be shadowed by an extension's content component. The extension
component is sandboxed in an error boundary and hard-clamped to icon size, so a
crashing or oversized icon can neither break the rail nor blow out its layout.
Previously an extension tab's icon was dropped, so every extension tab showed
the default puzzle glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alextomas955
alextomas955 force-pushed the ext-tab-icon-resolution branch from e8a7163 to 68853bd Compare July 19, 2026 14:36
…installed-extensions list

Extends the renderExtensionTabIcon resolution (built-in name → extension-registered component →
default) introduced for the detail-rail tab to three more host surfaces, so an extension's icon
renders everywhere it is represented, not only on its detail-rail tab:

- Settings-nav: resolveExtensionSettingsTabIcon returns a registered extension component when the
  tab's icon name matches one (else the built-in, else the default Plug glyph).
- Bulk-action button: ExtensionSelectionActions renders renderExtensionTabIcon(action.icon, …)
  instead of a hardcoded Puzzle glyph.
- Installed-Extensions list: renders the extension's iconUrl next to its name.

All backward-compatible (unknown/absent icon → the existing default glyph). Live-verified on a dev
instance with an extension registering its own logo component + iconUrl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alextomas955
alextomas955 force-pushed the ext-tab-icon-resolution branch from 6cf3d74 to 21db9a5 Compare July 21, 2026 07:03
…ss all surfaces

Fold the previously-split icon paths — a registered component for the tab/nav/bulk-action, but a raw
<img iconUrl> for the installed-extensions list — into ONE resolver with a single precedence: host
built-in name → extension-registered component → an <img> for a URL / data-URI / path asset → the
caller's fallback. Every ReactNode surface (detail-rail tab, bulk-action bar, installed-extensions
list) now resolves icons the same way; the settings-nav shares the same precedence (its render site
takes a component, so it keeps resolveExtensionSettingsTabIcon).

- Generalizes renderExtensionTabIcon → renderExtensionIcon with {sizeClass, fallback} options and an
  image-source branch.
- The installed-extensions list routes its iconUrl through the resolver instead of a bare <img>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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