feat(hub-ui): render explicit SVG mask icons - #382
Conversation
◈ PR Lens
Architecture 4 components touched across 4 lanes. Inside the changed components — 2 viewsComponent view — Hub dock icon resolution Client runtime URL resolution and hub UI Vue mask rendering. Component view — Terminals mask icon rendering Node session icon normalization and Svelte client CSS alpha masking. Data flow
The other flows — 1 sequence
View
Tip Untick Architecture lens or Data flow lens under View to hide a diagram, or tick Expand every detail to open every section. The comment redraws in a few seconds. 🪧 More tips
Thanks for using PR Lens! It's built by Coldtea, free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
|
@dvcolomban is attempting to deploy a commit to the NuxtLabs Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
🟡 Changes recommended
There are blocking packaging/security issues (subpath export types for ./icons, and inline SVG allowing <image>-based external loads) plus a brittle deep cross-workspace component import in the inspect UI.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends icon support across the devframe + hub ecosystem so dock entries, commands, and terminal sessions can carry inline icon sources (raw SVG strings, imported Iconify icon data, or theme pairs), reducing reliance on remote Iconify fetches and enabling currentColor inheritance for inline SVG rendering.
Changes:
- Introduces
DevframeIcon/DevframeIconSourceand propagates them through devframe definitions, hub types, commands, and terminal session metadata/RPC schemas. - Adds a browser helper entrypoint
@devframes/hub-ui/iconsplus hub-ui icon rendering updates (inline SVG sanitization, finite bundled Phosphor subset generation). - Updates tests, examples, docs, build tasks, and public API snapshots to reflect the new icon shapes and offline behavior.
File summaries
| File | Description |
|---|---|
| turbo.json | Adds a hub-ui icon generation task and wires it into typecheck/build outputs. |
| tsconfig.base.json | Adds a TS path alias for @devframes/hub-ui/icons. |
| pnpm-workspace.yaml | Adds catalog entries for Iconify utils/types and jsdom. |
| pnpm-lock.yaml | Locks new dependencies (@iconify/utils, @iconify/types, jsdom) and transitive graph changes. |
| package.json | Ensures hub-ui icon generation runs during postinstall. |
| alias.ts | Adds runtime/source alias for @devframes/hub-ui/icons. |
| packages/devframe/src/types/index.ts | Re-exports the new icons type module. |
| packages/devframe/src/types/icons.ts | Defines DevframeIconSource/DevframeIcon using @iconify/types. |
| packages/devframe/src/types/devframe.ts | Switches DevframeDefinition.icon and dock defaults to DevframeIcon. |
| packages/devframe/package.json | Adds @iconify/types dependency for type resolution. |
| packages/hub/src/types/docks.ts | Updates dock icon typing to DevframeIcon and re-exports DevframeIconSource. |
| packages/hub/src/types/commands.ts | Updates command icon docs to match expanded icon source forms. |
| packages/hub/src/client/dock-resources.ts | Preserves non-string icon sources while still resolving URL strings. |
| packages/hub/src/client/dock-resources.test.ts | Adds coverage for preserving transported icon objects and mixed theme pairs. |
| packages/hub-ui/package.json | Exposes ./icons entry and adds a build step for generated builtin icon data. |
| packages/hub-ui/tsdown.config.ts | Builds both node entry and browser icon helpers; bundles iconify/dompurify deps. |
| packages/hub-ui/vitest.config.ts | Adds a dedicated vitest project config for hub-ui. |
| packages/hub-ui/scripts/build-icons.ts | Generates a finite builtin Phosphor icon subset for offline dock defaults. |
| packages/hub-ui/src/client/utils/icons.ts | Adds icon resolution helpers (variant selection, image detection, inline SVG sanitization). |
| packages/hub-ui/src/client/utils/icons.test.ts | Adds jsdom tests for conversion/sanitization/offline builtin behavior and source switching. |
| packages/hub-ui/src/client/components/icons/IconifyIcon.vue | Switches to inline SVG rendering via the new icon helpers. |
| packages/hub-ui/src/client/components/dock/DockIcon.vue | Accepts DevframeDockEntryIcon and forwards sources to IconifyIcon for rendering. |
| packages/hub-ui/src/client/components/dock/DockIcon.stories.ts | Adds stories for raw SVG and imported Iconify data rendering. |
| packages/hub-ui/src/client/state/dock-settings.ts | Removes legacy command-icon collapsing helper (icons now carried through). |
| packages/hub-ui/src/client/state/context.ts | Projects dock entry icons into commands without collapsing theme/object forms. |
| plugins/terminals/src/node/types.ts | Updates terminal preset/session icon types to DevframeIcon. |
| plugins/terminals/src/node/rpc/schemas.ts | Expands terminal RPC schemas to accept object/iconify/raw SVG icon sources. |
| plugins/terminals/src/node/manager.ts | Preserves hub-provided icon sources rather than normalizing to UnoCSS classes. |
| plugins/terminals/test/_utils.ts | Updates fake hub terminal icon typing to DevframeIcon. |
| plugins/terminals/test/terminals.test.ts | Adds transport test for theme/raw/imported icons; updates expectations for preserved icon names. |
| plugins/terminals/app/client/SessionIcon.svelte | Adds a renderer for inline SVG/imported data/image URLs (and existing class icons) in the terminals UI. |
| plugins/terminals/app/client/App.svelte | Uses SessionIcon to render session/preset icons across the UI. |
| plugins/terminals/package.json | Adds hub-ui workspace dependency for consuming the icon helpers. |
| plugins/inspect/src/node/types.ts | Updates inspect command icon typing/documentation to DevframeIcon. |
| plugins/inspect/src/node/rpc/functions/_hub-commands.ts | Updates hub command-like icon typing to DevframeIcon. |
| plugins/inspect/src/node/index.ts | Updates inspect devframe options icon typing to DevframeIcon. |
| plugins/inspect/app/components/CommandRow.vue | Switches command row icon rendering to a dock icon component. |
| plugins/messages/src/node/index.ts | Updates messages devframe options icon typing to DevframeIcon. |
| plugins/og/src/node/index.ts | Updates og devframe options icon typing to DevframeIcon. |
| plugins/data-inspector/src/node/index.ts | Updates data-inspector devframe options icon typing to DevframeIcon. |
| plugins/assets/src/node/index.ts | Updates assets devframe options icon typing to DevframeIcon. |
| plugins/a11y/src/node/index.ts | Updates a11y devframe options icon typing to DevframeIcon. |
| plugins/code-server/test/_utils.ts | Updates fake hub session icon typing to DevframeIcon. |
| plugins/code-server/src/node/supervisor.ts | Updates hub terminal bridge typing to accept DevframeIcon icons. |
| examples/custom-hub-vite/src/client/main.ts | Updates example dock icon painting to work with non-string icon sources. |
| examples/custom-hub-next/src/client/app/page.tsx | Changes dock icon cache keying to account for object icon sources. |
| design/dock-icon.ts | Switches design helper to use the new icon helpers instead of direct Iconify fetch. |
| docs/content/1.guide/16.hub.md | Documents inline icon support and the @devframes/hub-ui/icons helpers for custom renderers. |
| tests/snapshots/tsnapi/devframe/types.snapshot.d.ts | Snapshot updates to include exported DevframeIcon/DevframeIconSource. |
| tests/snapshots/tsnapi/devframe/index.snapshot.d.ts | Snapshot updates for DevframeDefinition.icon and dock defaults using DevframeIcon. |
| tests/snapshots/tsnapi/@devframes/plugin-terminals/types.snapshot.d.ts | Snapshot updates for terminal icon types using DevframeIcon. |
| tests/snapshots/tsnapi/@devframes/plugin-terminals/rpc.snapshot.d.ts | Snapshot updates for expanded terminal RPC icon schema types. |
| tests/snapshots/tsnapi/@devframes/plugin-terminals/index.snapshot.d.ts | Snapshot updates for terminal icon types using DevframeIcon. |
| tests/snapshots/tsnapi/@devframes/plugin-og/index.snapshot.d.ts | Snapshot updates for og devframe option icon typing. |
| tests/snapshots/tsnapi/@devframes/plugin-messages/index.snapshot.d.ts | Snapshot updates for messages devframe option icon typing. |
| tests/snapshots/tsnapi/@devframes/plugin-data-inspector/index.snapshot.d.ts | Snapshot updates for data-inspector devframe option icon typing. |
| tests/snapshots/tsnapi/@devframes/plugin-assets/index.snapshot.d.ts | Snapshot updates for assets devframe option icon typing. |
| tests/snapshots/tsnapi/@devframes/plugin-a11y/index.snapshot.d.ts | Snapshot updates for a11y devframe option icon typing. |
| tests/snapshots/tsnapi/@devframes/hub/types.snapshot.d.ts | Snapshot updates to export DevframeIconSource from hub types. |
| tests/snapshots/tsnapi/@devframes/hub/index.snapshot.d.ts | Snapshot updates for dock entry icon typing and exports. |
| tests/snapshots/tsnapi/@devframes/hub-ui/icons.snapshot.d.ts | Adds public API snapshot for @devframes/hub-ui/icons types. |
| tests/snapshots/tsnapi/@devframes/hub-ui/icons.snapshot.js | Adds public API snapshot for @devframes/hub-ui/icons runtime exports. |
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 48/62 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
The inspect devframe UI imports a hub-ui SFC via a repo-relative path (bypassing declared dependencies), and this coupling should be resolved before approval.
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
packages/hub-ui/src/client/utils/icons.ts:23
- This browser helper module instantiates DOMPurify at import time and later relies on DOM globals. If it’s ever imported in a non-DOM context (SSR bundlers or accidental node-side import), it can crash during module evaluation. Consider guarding DOMPurify initialization and failing fast inside
getIconSvg()when DOM APIs are unavailable so the module remains safe to import.
storybook/src/client/icons.ts:4 - This Storybook helper reaches into
packages/hub-ui/src/...directly. Since@devframes/hub-ui/iconsis now an exported entry, importing from it keeps Storybook aligned with the public API and avoids coupling to the monorepo file layout.
plugins/inspect/app/components/CommandRow.vue:6
- This component imports a Vue SFC from another workspace via a repo-relative path, which bypasses the package dependency graph (the inspect devframe doesn’t declare any dependency on
@devframes/hub-ui) and makes the build fragile to repo layout changes. Prefer rendering icons locally using the public@devframes/hub-ui/iconshelpers (or add an explicit dependency and import from an exported entry point).
- Files reviewed: 50/64 changed files
- Comments generated: 0 new
- Review effort level: Lite
affd084 to
02b909e
Compare
02b909e to
13e57cf
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Mask URL handling should trim/guard the value consistently to avoid whitespace/empty-value edge cases that can break resolution or produce unexpected URLs.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 3
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Empty mask: values currently render as url("") in CSS masks, which can cause unintended fetches and should be treated as mask: none (and the docs inline code formatting needs correction).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
docs/content/8.references/4.node-api.md:33
- The inline code sample uses doubled backticks (
`...`), which will render literal backtick characters around the string and is confusing for readers. Use a normal inline code span for the string expression.
- Files reviewed: 7/7 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
The new mask icon renderers introduce inconsistent accessibility attributes (e.g., missing alt="" / aria-hidden) that should be made consistent before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
packages/hub-ui/src/client/components/icons/IconifyIcon.vue:45
IconifyIconnow rendersmask:icons witharia-hidden, but the other branches (Iconify SVG injection and<img>) still lackaria-hiddenand the<img>lacksalt. Since these dock icons are decorative (buttons already havearia-label/ adjacent text), addaria-hidden="true"consistently and setalt=""on the image branch to avoid screen readers announcing the URL.
}
catch {
// A failed icon fetch (offline / flaky CDN) should degrade to a blank icon,
// not throw out of the async effect and crash the surrounding panel.
iconifyLoaded.value = undefined
- Files reviewed: 7/7 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is cohesive across resolver, renderers, and terminal metadata, and the added tests cover the key new resolution and transport behaviors.
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The new hub-ui icon rendering branch introduces accessibility regressions/inconsistencies (decorative SVG/img not consistently hidden from assistive tech) and a small URL-resolution consistency issue for protocol-relative mask URLs.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
packages/hub-ui/src/client/components/icons/IconifyIcon.vue:60
- In the Iconify SVG branch, the icon is decorative (dock buttons already provide an aria-label), but this wrapper div isn’t marked
aria-hidden. That can cause redundant/verbose screen-reader output when the injected SVG includes accessible content. Mark this brancharia-hidden="true"like the mask branch.
This issue also appears on line 61 of the same file.
packages/hub/src/client/dock-resources.ts:34
resolveIconUrlpreserves protocol-relative URLs (//...) for non-mask icons, butmask://...currently gets normalized vianew URL(...)into an explicit scheme. Preserving//...for masks as well keeps behavior consistent and avoids unnecessarily hardcoding a scheme in serialized metadata.
packages/hub-ui/src/client/components/icons/IconifyIcon.vue:65
- The
<img>fallback is also decorative in the dock (the parent button provides anaria-label), but it currently has noaltand isn’taria-hidden. Addalt=""(and optionallyaria-hidden="true") to prevent screen readers from announcing the URL/filename.
<img
v-else :src="icon"
class="w-full h-full m-auto"
draggable="false"
>
- Files reviewed: 7/7 changed files
- Comments generated: 0 new
- Review effort level: Lite
Background (Why)
Locally bundled monochrome SVG icons need to follow the dock's foreground color, including selected states. SVGs rendered through an image element cannot inherit that color.
Changes (What)
Accept
mask:<image-url>in the existing string icon API. Render it as an alpha mask usingcurrentColor, resolve relative dock URLs against the supplying hub, and preserve mask strings through terminal session metadata. Ordinary images, Iconify names and dock theme pairs keep their existing behavior.Verification (Testing)
Existing Vitest tests cover mask URL resolution, theme pairs and terminal metadata serialization. After cleanup, 108 scoped tests, affected lint and type checks pass. Builds and all 288 affected tests passed during the earlier implementation validation.
Browser validation before removing the temporary harness covered data URLs, ordinary images, selected/dimmed states, launchers and the command palette with Iconify blocked, across embedded dock and standalone SPA modes and both color schemes. The screenshots remain separate from the source changes.
All light/dark, embedded and command-palette screenshots.