feat: add provider logo overlays to workflow template thumbnails#8365
feat: add provider logo overlays to workflow template thumbnails#8365christian-byrne merged 19 commits intomainfrom
Conversation
- Add LogoInfo interface and logos field to TemplateInfo type - Create LogoOverlay.vue component for rendering positioned logos - Fetch logo index from templates/index_logo.json in store - Add getLogoUrl helper to useTemplateWorkflows composable - Integrate LogoOverlay into WorkflowTemplateSelectorDialog Supports multiple logos per template with configurable: - Position (Tailwind classes) - Size (sm/md/lg) - Opacity (0-1) Templates can declare logos explicitly. Logo assets are fetched from the workflow_templates repo (templates/logo/*.png). Amp-Thread-ID: https://ampcode.com/threads/T-019c03a1-9f05-70bd-b0a3-2848e54e04af Co-authored-by: Amp <amp@ampcode.com>
📝 WalkthroughWalkthroughThis PR introduces a logo overlay feature for workflow templates. It adds a new LogoOverlay component to display provider logos on template cards, integrates logo management into the template store with URL resolution, and defines necessary types and schemas to support logo data throughout the system. Changes
Sequence DiagramsequenceDiagram
participant TS as Template Selector
participant Store as Templates Store
participant HTTP as HTTP Client
participant LO as LogoOverlay
participant IMG as Image Load
TS->>Store: loadWorkflowTemplates()
Store->>HTTP: GET /templates/index_logo.json
HTTP-->>Store: logoIndex data
Store->>Store: validate with zLogoIndex
Store-->>TS: templates with logos[]
TS->>TS: render template cards
TS->>LO: pass logos[] + getLogoUrl
LO->>LO: compute validLogos (resolve URLs)
LO->>LO: render logo pills
LO->>IMG: load provider image
IMG-->>LO: onImageError
LO->>LO: update failedLogos set
LO->>LO: hasAllFailed checks visibility
LO-->>TS: render UI with overlay
Suggested reviewers
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎨 Storybook Build Status✅ Build completed successfully! ⏰ Completed at: 02/01/2026, 01:39:13 AM UTC 🔗 Links🎉 Your Storybook is ready for review! |
🎭 Playwright Tests:
|
Bundle Size ReportSummary
Category Glance Per-category breakdownApp Entry Points — 26 kB (baseline 26 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 978 kB (baseline 974 kB) • 🔴 +4.23 kBGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 80.7 kB (baseline 80.7 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 471 kB (baseline 471 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 12 added / 12 removed User & Accounts — 3.94 kB (baseline 3.94 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 3 added / 3 removed Editors & Dialogs — 2.89 kB (baseline 2.89 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 33.7 kB (baseline 33.7 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 2.71 MB (baseline 2.71 MB) • 🔴 +976 BStores, services, APIs, and repositories
Status: 8 added / 8 removed Utilities & Hooks — 25.3 kB (baseline 25.3 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 7 added / 7 removed Vendor & Third-Party — 10.7 MB (baseline 10.7 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 7.12 MB (baseline 7.12 MB) • 🔴 +73 BBundles that do not match a named category
Status: 38 added / 38 removed |
Amp-Thread-ID: https://ampcode.com/threads/T-019c07c8-7f01-71bf-a642-c3f612ee4a4a Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c07c8-7f01-71bf-a642-c3f612ee4a4a Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Around line 41-88: Update the tests in LogoOverlay.test.ts to avoid asserting
Tailwind classes and inline styles: instead of checking container.classes() or
img.classes() and container.attributes('style'), assert semantic attributes or
behavior (e.g., check for data-position, data-size, data-opacity or the rendered
alt/src and any layout/interaction effects). Specifically, modify the
mountOverlay usage and subsequent assertions (the tests that call mountOverlay
and query wrapper.find('div') and wrapper.find('img')) to expect data-position,
data-size, and data-opacity attributes (or other behavior-driven outputs)
produced by the LogoOverlay component; if those attributes don't exist, add them
in the LogoOverlay implementation so tests can assert stable, semantic
attributes rather than Tailwind classes or inline style strings.
In `@src/components/templates/thumbnails/LogoOverlay.vue`:
- Around line 11-23: Update LogoOverlay.vue to respect the LogoInfo contract by
using logo.size to set the img dimensions, honoring logo.opacity with default 1,
and defaulting the overlay position to top-left when logo.position is missing:
bind the image size via :style or explicit width/height using logo.size
(fallback to the component/test default), keep :style="{ opacity: logo.opacity
?? 1 }" for opacity, and ensure the container CSS classes reflect top-left
alignment unless logo.position specifies another value; adjust any uses of
onImageError and the container class string accordingly so size, opacity, and
position are applied consistently (also apply the same fixes in the other block
referenced at lines 37-57).
- Around line 3-4: Replace the unstable index key on the v-for in
LogoOverlay.vue (v-for="(logo, index) in validLogos") with a stable identifier
from each logo object—e.g. use :key="logo.provider" or :key="logo.id" (prefer an
immutable unique id if available); update any code that constructs validLogos to
guarantee the chosen field exists and is unique so Vue can track logo items
reliably instead of reusing DOM by index.
In `@src/platform/workflow/templates/repositories/workflowTemplatesStore.ts`:
- Around line 506-520: The logo index may contain untrusted paths; update
fetchLogoIndex/getLogoUrl to validate entries from logoIndex (the
logoIndex.value lookup used by getLogoUrl) before composing a file URL: ensure
the resolved logoPath is a simple relative filename or allowed subpath (reject
any segments containing ".." or leading "/" ), and enforce an allowed filename
pattern/extension (e.g., only png|svg|jpg|jpeg and safe characters) or a
whitelist of expected prefixes; if validation fails, return an empty string (or
skip the entry) instead of calling api.fileURL. Use the existing functions
fetchLogoIndex and getLogoUrl and the logoIndex variable to locate and apply
this validation.
| class="flex items-center gap-1.5 rounded-full bg-black/20 px-2 py-1" | ||
| :style="{ opacity: logo.opacity ?? 1 }" | ||
| > | ||
| <img | ||
| :src="logo.url" | ||
| :alt="logo.provider" | ||
| class="h-5 w-5 rounded-[50%]" | ||
| draggable="false" | ||
| @error="onImageError(logo.provider)" | ||
| /> | ||
| <span class="text-sm font-medium text-white"> | ||
| {{ logo.provider }} | ||
| </span> |
There was a problem hiding this comment.
Honor size/opacity defaults and align default position.
size is currently ignored, default opacity is 1, and the default position is top-left; this diverges from the LogoInfo contract/tests and will break expected rendering.
🛠️ Proposed fix
const {
logos,
getLogoUrl,
- defaultPosition = 'top-2 left-2'
+ defaultPosition = 'bottom-2 right-2'
} = defineProps<{
logos: LogoInfo[]
getLogoUrl: (provider: string) => string
defaultPosition?: string
}>()
+function getLogoSizeClass(size?: 'sm' | 'md' | 'lg') {
+ switch (size) {
+ case 'sm':
+ return 'h-6 w-6'
+ case 'lg':
+ return 'h-12 w-12'
+ default:
+ return 'h-8 w-8'
+ }
+}
+
const failedLogos = ref(new Set<string>())- :style="{ opacity: logo.opacity ?? 1 }"
+ :style="{ opacity: logo.opacity ?? 0.9 }"- class="h-5 w-5 rounded-[50%]"
+ :class="cn('rounded-full', getLogoSizeClass(logo.size))"Also applies to: 37-57
🤖 Prompt for AI Agents
In `@src/components/templates/thumbnails/LogoOverlay.vue` around lines 11 - 23,
Update LogoOverlay.vue to respect the LogoInfo contract by using logo.size to
set the img dimensions, honoring logo.opacity with default 1, and defaulting the
overlay position to top-left when logo.position is missing: bind the image size
via :style or explicit width/height using logo.size (fallback to the
component/test default), keep :style="{ opacity: logo.opacity ?? 1 }" for
opacity, and ensure the container CSS classes reflect top-left alignment unless
logo.position specifies another value; adjust any uses of onImageError and the
container class string accordingly so size, opacity, and position are applied
consistently (also apply the same fixes in the other block referenced at lines
37-57).
Replace unstable index-based key with logo.provider for reliable DOM tracking when logo order changes. Amp-Thread-ID: https://ampcode.com/threads/T-019c083e-8ba0-7699-a5ff-63fd03e24391 Co-authored-by: Amp <amp@ampcode.com>
Guard against path traversal and unexpected file types by validating logo paths start with 'logo/', have allowed image extensions, and contain no '..' or leading '/' segments. Amp-Thread-ID: https://ampcode.com/threads/T-019c083e-8ba0-7699-a5ff-63fd03e24391 Co-authored-by: Amp <amp@ampcode.com>
Remove tests that assert Tailwind utility classes and inline styles. Focus on semantic attributes (src, alt, draggable) and behavioral outputs (filtering, rendering counts) per testing guidelines. Amp-Thread-ID: https://ampcode.com/threads/T-019c083e-8ba0-7699-a5ff-63fd03e24391 Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Line 8: Replace the helper function expression mockGetLogoUrl with a function
declaration; locate the constant mockGetLogoUrl in LogoOverlay.test (the pure
helper used to return `/logos/${provider}.png`) and refactor it to a named
function declaration (function mockGetLogoUrl(provider: string) { ... }) so it
follows the repository guideline preferring declarations for pure helpers.
- Around line 65-72: The test currently couples to Tailwind by using the CSS
selector '[class*="absolute"]'; change it to assert structural/behavioral output
instead: update the test using mountOverlay to query by a stable marker (e.g.,
data-testid or the logo child component) and assert the count/unique keys, for
example use wrapper.findAll('[data-testid="logo-container"]') or
wrapper.findAllComponents(LogoComponent) and expect length 2, and/or verify that
each rendered item's key/prop contains the provider value (check vnode.props.key
or props.provider) instead of relying on the "absolute" class.
This reverts commit 85a7d19.
Amp-Thread-ID: https://ampcode.com/threads/T-019c0c7b-2984-758f-aa2e-d28e2c29580d Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c0c7b-2984-758f-aa2e-d28e2c29580d Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c0c7b-2984-758f-aa2e-d28e2c29580d Co-authored-by: Amp <amp@ampcode.com>
…wTemplates Amp-Thread-ID: https://ampcode.com/threads/T-019c0c7b-2984-758f-aa2e-d28e2c29580d Co-authored-by: Amp <amp@ampcode.com>
- Update LogoInfo to accept provider as string | string[] for stacked logos - Add label and gap fields for custom text and overlap control - Redesign LogoOverlay with overlapping circular logos (white border, -6px gap) - Apply style updates: h-6 w-6 size, pr-2 padding, 0.85 default opacity - Add path validation in getLogoUrl to prevent directory traversal - Expand tests for stacked logos, custom labels, and styling Amp-Thread-ID: https://ampcode.com/threads/T-019c0ca3-c517-7310-9b34-6d21a20ca7e2 Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Around line 128-146: Tests in LogoOverlay.test.ts are asserting Tailwind
utility classes (selectors '.rounded-full' and classes
'border-2'/'border-white') which couples tests to styling; update tests (which
use mountOverlay) to assert behavior/semantics instead: add semantic data
attributes in the component (e.g., data-testid="logo-pill" on the pill and
data-testid="logo-img" on the image) or expose a prop-driven flag, then change
the tests to find by those attributes (e.g.,
wrapper.find('[data-testid="logo-pill"]') and
wrapper.find('[data-testid="logo-img"]') ) and assert on relevant
attributes/inline style values (opacity via attributes('style') or presence of a
visible border via computed style string) instead of checking Tailwind class
names '.rounded-full', 'border-2', or 'border-white'; keep mountOverlay
unchanged except for rendering these data attributes.
In `@src/platform/workflow/templates/repositories/workflowTemplatesStore.ts`:
- Around line 507-517: The fetchLogoIndex function returns response.data without
validating its shape; update axios.get to use a typed response (e.g.,
axios.get<LogoIndex>) and add runtime validation of the returned payload before
returning (use an existing schema validator or add a lightweight guard that
checks required keys/types for LogoIndex) so malformed JSON is rejected and {}
is returned or an error is handled; reference fetchLogoIndex, axios.get,
LogoIndex and the content-type check to locate where to add the typed generic
and validator.
src/platform/workflow/templates/composables/useTemplateWorkflows.ts
Outdated
Show resolved
Hide resolved
- Add data-testid attributes (logo-pill, logo-img) to decouple tests from styling - Add typed axios.get<LogoIndex> and runtime validation for fetchLogoIndex Amp-Thread-ID: https://ampcode.com/threads/T-019c1215-0fc3-752a-aa20-514c5c4804aa Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Around line 7-147: Add tests exercising the onImageError/hasAllFailed logic in
LogoOverlay: use the existing mountOverlay helper to mount LogoOverlay, trigger
an 'error' event on img elements to simulate failed loads, and assert the pill
(data-testid="logo-pill") visibility (isVisible or v-show) — one test where all
provider images error and the pill becomes hidden, and one test where only some
stacked provider images error and the pill remains visible; reference the
onImageError/hasAllFailed behavior in LogoOverlay and the mountOverlay helper to
locate where to add these new specs.
In `@src/components/templates/thumbnails/LogoOverlay.vue`:
- Around line 14-16: Replace the inline style marginLeft: '2px' on the div with
the Tailwind spacing class by removing :style and adding ml-0.5 to the existing
class list on the div with class "flex items-center" in LogoOverlay.vue; ensure
the element's attributes become class="flex items-center ml-0.5" (or equivalent
merged classes) and remove the :style binding.
- Use Tailwind ml-0.5 instead of inline marginLeft style - Add error handling test coverage for onImageError/hasAllFailed Amp-Thread-ID: https://ampcode.com/threads/T-019c1215-0fc3-752a-aa20-514c5c4804aa Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Around line 13-20: The test helper mountOverlay should type its props
parameter as Partial<ComponentProps<typeof LogoOverlay>> to keep it aligned with
the actual component props; update the signature of mountOverlay to accept
props: Partial<ComponentProps<typeof LogoOverlay>> (and import ComponentProps
from 'vue' if not already imported) so TypeScript will validate test prop shapes
and prevent drift as LogoOverlay's props evolve.
- Line 1: ESLint flags the import of `@vue/test-utils` as unresolved in
LogoOverlay.test.ts (the `import { mount } from '@vue/test-utils'` line);
install the correct package (e.g., add `@vue/test-utils` to devDependencies for
the project's Vue version) and ensure your import resolver is configured (update
ESLint settings such as import/resolver in .eslintrc or tsconfig/paths so the
workspace resolver recognizes node_modules and any path aliases); after
installing, run the provided verification script to confirm package.json entries
include "@vue/test-utils" and that import/resolver or tsconfig alias settings
are present.
In `@src/components/templates/thumbnails/LogoOverlay.vue`:
- Around line 74-92: The label in the computed validLogos uses a hard-coded
separator (" & ") which bypasses localization; update the code in the validLogos
computed (where label is assigned for each ValidatedLogo) to format providers
using Intl.ListFormat with the current locale instead of string-joining with " &
" so user-facing provider names respect locale-specific conjunctions; locate the
label assignment in the validLogos computed that currently uses logo.label ??
validProviders.join(' & ') and replace the join with a localized formatting call
(using Intl.ListFormat or a locale-aware helper) so labels fall back to
logo.label but otherwise use the localized provider list.
| @@ -0,0 +1,168 @@ | |||
| import { mount } from '@vue/test-utils' | |||
There was a problem hiding this comment.
Resolve the ESLint no-unresolved error for @vue/test-utils.
ESLint reports import-x/no-unresolved; ensure the dependency is installed and the resolver is configured for the workspace.
#!/bin/bash
set -euo pipefail
# Check package.json files for `@vue/test-utils` dependency
fd package.json -t f -E node_modules -E dist -E build | while read -r f; do
echo "== $f =="
rg -n '"@vue/test-utils"' "$f" || true
done
# Check eslint resolver configuration
rg -n "import/resolver|alias|tsconfig" .eslintrc* package.json🧰 Tools
🪛 ESLint
[error] 1-1: Unable to resolve path to module '@vue/test-utils'.
(import-x/no-unresolved)
🤖 Prompt for AI Agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts` at line 1, ESLint
flags the import of `@vue/test-utils` as unresolved in LogoOverlay.test.ts (the
`import { mount } from '@vue/test-utils'` line); install the correct package
(e.g., add `@vue/test-utils` to devDependencies for the project's Vue version) and
ensure your import resolver is configured (update ESLint settings such as
import/resolver in .eslintrc or tsconfig/paths so the workspace resolver
recognizes node_modules and any path aliases); after installing, run the
provided verification script to confirm package.json entries include
"@vue/test-utils" and that import/resolver or tsconfig alias settings are
present.
…helper - Use Intl.ListFormat for locale-aware provider name conjunction - Type mountOverlay helper with Partial<ComponentProps<typeof LogoOverlay>> - Update test to expect 'and' (English locale) instead of '&' Amp-Thread-ID: https://ampcode.com/threads/T-019c1614-d5f6-74f3-bfff-431a70f89040 Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/components/templates/thumbnails/LogoOverlay.test.ts`:
- Around line 135-172: Replace brittle inline-style assertions in
LogoOverlay.test.ts: update the "hides logo pill when all provider images fail
to load" and "keeps logo visible when only some images fail in stacked logos"
tests to use behavioral checks (e.g., use pill.isVisible() or
expect(pill.exists()).toBe(false/true)) instead of inspecting style strings;
change the "applies default opacity" and "applies custom opacity" tests to
assert a semantic signal emitted by the component (e.g., data-opacity attribute
or a computed class) rather than "style" — reference mountOverlay,
[data-testid="logo-pill"], and [data-testid="logo-img"] to locate the tests; and
remove the redundant "logos have border styling for stacking visibility" test
entirely since it does not assert meaningful behavior.
In `@src/components/templates/thumbnails/LogoOverlay.vue`:
- Around line 87-108: The computed validLogos currently builds objects with keys
based only on providers which can collide; update the key generation inside the
validLogos computed (where result.push is called) to include layout/identity
attributes (e.g., position, label, gap and/or opacity) or a stable id from
LogoInfo so each entry is unique and stable across renders; ensure the key
construction uses validProviders.join('-') plus those attributes (and still
falls back to `logo-${index}`) so Vue won't reuse DOM for distinct logos.
- Around line 45-55: The fallback hard-coded " & " in formatProviderList should
be localized: import and call useI18n() inside the same scope as
formatProviderList to get t() and the locale, use new Intl.ListFormat(locale,
...) as before, and in the catch return
providers.join(t('thumbnails.andSeparator')); then add the key
"thumbnails.andSeparator": " & " to your main locale JSON so the separator is
provided via vue-i18n.
Amp-Thread-ID: https://ampcode.com/threads/T-019c1614-d5f6-74f3-bfff-431a70f89040 Co-authored-by: Amp <amp@ampcode.com>
- Create templateSchema.ts with zLogoIndex schema - Replace manual isValidLogoIndex() with zod safeParse - Re-export LogoIndex type from schema for consistency Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019c1614-d5f6-74f3-bfff-431a70f89040
Amp-Thread-ID: https://ampcode.com/threads/T-019c1614-d5f6-74f3-bfff-431a70f89040 Co-authored-by: Amp <amp@ampcode.com>
- Use api.fetchApi() instead of raw axios for fetchLogoIndex - Localize fallback separator via vue-i18n - Include layout attributes in logo keys to avoid collisions - Remove style-based tests (change detectors) - Keep behavioral error handling test Amp-Thread-ID: https://ampcode.com/threads/T-019c16c9-30a5-7024-9c89-aa58b490d37c Co-authored-by: Amp <amp@ampcode.com>
- Update index.schema.json with logos field and logoInfo definition - Convert index_logo.json to key-value format for frontend compatibility - Add example logos field to 4 templates (OpenAI, Stability, Google, WaveSpeed) Depends on: Comfy-Org/ComfyUI_frontend#8365





Summary
Add support for overlaying provider logos on workflow template thumbnails at runtime.
Changes
LogoInfointerface andlogosfield toTemplateInfotypeLogoOverlay.vuecomponent for rendering positioned logostemplates/index_logo.jsonin storegetLogoUrlhelper touseTemplateWorkflowscomposableLogoOverlayintoWorkflowTemplateSelectorDialogReview Focus
absolute bottom-2 right-2)Dependencies
Requires separate PR in workflow_templates repo to:
index.schema.jsonwith logos definitionlogosfield to templates inindex.jsonScreenshots (if applicable)
┆Issue is synchronized with this Notion page by Unito