docs/backend-migration/plans/2026-04-30-backend-logo-assets-implementation-notes.md
Companion to 2026-04-30-backend-logo-assets-design.md. Records how the
implementation landed, including the places it deliberately diverged from
the spec. The spec records what we decided; this file records what we
actually shipped.
2026-04-30-backend-logo-assets-design.mdaionui-backend@main and
AionUi@feat/backend-migration (uncommitted as of this note).The spec focused on agent logos. The implementation also migrated provider logos (OpenAI / Anthropic / Bedrock / DeepSeek / … used in settings platform pickers):
modelPlatforms.ts now builds every logo via
buildLogoAssetUrl('ai-cloud/…') / buildLogoAssetUrl('ai-major/…').EditModeModal.tsx's duplicated PROVIDER_CONFIGS array was deleted
outright; it now derives provider entries from MODEL_PLATFORMS.
Eliminates a DRY violation that logo imports had been hiding.AgentSetupCard.tsx lost its local AGENT_LOGOS map (11 entries) and
calls getAgentLogo(result.backend) directly.resolveBackendAssetUrl helperIntroduced in src/renderer/utils/platform.ts. The renderer runs from
file:// inside Electron, so a backend-relative path like
/api/assets/logos/... must be prefixed with
http://127.0.0.1:${backendPort}. The helper:
getBaseUrl()) to any
path starting with /,/api/... paths alone — the same-origin
proxy handles them.resolveExtensionAssetUrl was collapsed to a thin wrapper around this
helper (both flows had identical needs). Used by agentLogo.ts,
modelPlatforms.ts, TeamAgentIdentity.tsx, TeamChatEmptyState.tsx,
and agentSelectUtils.tsx.
Not in the spec but present in aionui-assets/src/service.rs:
normalize_logo_path rejects .., absolute roots, and Windows-style
drive prefixes. The route returns 403 Forbidden on rejection (spec
only mentioned 404). Covered by get_logo_asset_rejects_traversal test.
Spec mentioned the swap lives in getAgentLogo. In practice
resolveAgentLogo also applies it, so a backend-served opts.icon URL
that ends in opencode-light.svg gets rewritten to the dark variant
when the renderer is in dark mode. Implemented via a shared
normalizeLogoUrl → applyThemeVariant helper in agentLogo.ts.
Spec specified the backend field; the frontend plumbing ended up touching four layers:
crates/aionui-api-types/src/team.rs: TeamAgentResponse.icon.crates/aionui-team/src/service.rs: TeamSessionService gained an
agent_metadata_repo dependency and a build_team_response /
build_agent_response pair that hydrates icon from
agent_metadata keyed on backend.src/common/adapter/teamMapper.ts: fromBackendAgent reads
r.icon.src/common/types/teamTypes.ts: TeamAgent.icon?: string.TeamAgentIdentity.tsx, TeamChatEmptyState.tsx,
TeamChatView.tsx, TeamTabs.tsx, TeamPage.tsx) pass icon
through and prefer it over the string-fallback getAgentLogo(backend).AgentOptionLabel in-line URL detectionagentSelectUtils.tsx:AgentOptionLabel has to cope with the icon
field carrying three shapes (preset emoji, custom avatar key, or a
backend asset URL). It now branches on a regex — URL-ish strings go
through resolveBackendAssetUrl, everything else falls back to the
existing emoji / avatar-map paths.
Backend (aionui-backend@main):
crates/aionui-assets/ (Cargo.toml, src/{lib,routes,service,state}.rs).crates/aionui-db/migrations/008_agent_metadata_icon_backfill.sql.crates/aionui-app/src/lib.rs, state_builders.rs, Cargo.toml.crates/aionui-app/tests/assets_e2e.rs.crates/aionui-api-types/src/team.rs (icon field +
serde tests).crates/aionui-team/src/{service,session,types}.rs,
src/mcp/server.rs, related test files.crates/aionui-db/src/repository/sqlite_agent_metadata.rs.Frontend (AionUi@feat/backend-migration):
src/renderer/utils/model/agentLogo.ts — rewritten.src/renderer/utils/model/modelPlatforms.ts — provider logos migrated.src/renderer/utils/platform.ts — resolveBackendAssetUrl added.src/renderer/components/agent/AgentSetupCard.tsx — uses
getAgentLogo.src/renderer/pages/settings/components/EditModeModal.tsx —
PROVIDER_CONFIGS removed, derived from MODEL_PLATFORMS.src/renderer/pages/settings/AgentSettings/AgentCard.tsx — passes
icon through resolveAgentLogo.src/renderer/pages/team/components/* — threaded icon through
identity/empty-state/view/tabs.src/common/types/teamTypes.ts + src/common/adapter/teamMapper.ts —
icon?: string on TeamAgent.src/renderer/assets/logos/ (40+ files, keeping only
brand/app.png).tests/unit/agentLogo.test.ts,
tests/unit/EditModeModal.dom.test.tsx,
tests/unit/platform/resolveExtensionAssetUrl.test.ts,
tests/unit/renderer/team/TeamAgentIdentity.dom.test.tsx./api/agents and
/api/assets/logos/... succeed).bun run test passes in the renderer workspace.icon = NULL by design (no asset shipped).