Migrate AI SVG Logo Generator → Logo Composer (Logo tool)

Stack: React + TypeScript + Tailwind + shadcn/ui.
Do not change global styles. Keep existing theming.

Goal

Move the “Generate AI-powered SVG logo” feature from Brand Kit Generator into the Logo Composer under the Logo tool. The user can:

Describe an icon, click Generate Logo, view Preview, and then Insert the SVG into the canvas (replace existing icon or keep both).

Later use Download (multi-format ZIP) or Send to Brand Kit.

UI (Logo tool panel)

Create a collapsible card titled Generate AI-powered SVG logo with:

Textarea (placeholder: “Describe your logo (minimal icon, geometric, vector, no text)”)

Generate Logo button (primary)

Preview area (checkerboard bg, dashed border, empty state message)

Footer actions (appear only if preview is ready):

Insert into Canvas (primary)

Replace Existing Icon (secondary) – only shown if an icon layer exists

Keep Both (ghost) – adds as a new icon layer

Discard (link button)

Match the layout and spacing from the screenshot (textarea → button → preview card).

File moves / new files

Move (or recreate) generator panel in:
components/logo/LogoAIGenerator.tsx

API client:
client/src/services/logo-ai.ts → generateLogoSvg(input): Promise<{ svg: string }>

SVG sanitizer & normalizer (client):
client/src/lib/svg-sanitize.ts

Composer integration helpers:
client/src/lib/composer/icon-layer.ts (insert/replace/position)

If the Brand Kit had older versions of these files, port logic but scope names to “logo-ai” to avoid collisions.

Component behavior

LogoAIGenerator.tsx

Local state: { prompt: string; svg?: string; loading: boolean; error?: string }

onGenerate():

Validate prompt (non-empty, < 400 chars).

Call generateLogoSvg({ prompt, brandName, colorHints }).

Receive { svg }; run sanitizeAndNormalize(svg) (see below).

Show in Preview (render raw SVG safely).

Insert into Canvas:

If no existing icon layer → create a new icon layer using addIconLayer(svg).

If an icon layer exists and user clicks Replace Existing Icon → replaceIconLayer(svg).

Keep Both → addIconLayer(svg) with incremental name “Icon 2/3…”.

After insert/replace, fit to artboard and center. Maintain aspect ratio.

API (reuse, but rename route)

Create/confirm backend route:

POST /api/logo-ai/generate

body: { prompt: string; brandName?: string; colorHex?: string }

returns: { svg: string }

If an existing Brand Kit route exists, keep it (backward compatible) but have the composer call the new route.

SVG sanitize & normalize (client)

In svg-sanitize.ts:

Strip: <script>, on*= attrs, external refs, <foreignObject>, xlink:href to remote.

Ensure viewBox exists; if missing, derive from width/height and then remove width/height so it scales.

Remove text nodes if detected (we want icon-only). If text exists, show toast: “This generator produces icon-only SVGs; text was removed.”

Inline fills/strokes; collapse groups where safe.

Return clean string.

Canvas integration (icon layer helpers)

icon-layer.ts:

addIconLayer(svg: string): create new layer { id, type: 'icon', svg }, set default transform (scale to 60% of artboard min dimension), center.

replaceIconLayer(svg: string): swap .svg on existing icon layer, preserve its transform (position/scale/rotation), re-fit if dimensions changed wildly.

Both must set data-role="icon-layer" on the root <g> for easy targeting.

State & metadata

Store inserted SVG in composer state (source of truth).

Update brandMeta.assets.iconSvg = svg.

Do NOT modify brand text; the icon is independent.

If color syncing is on, allow an optional Recolor to Brand Primary toggle (default ON): replace non-neutral fills with brandMeta.colors[0]. Keep neutrals (black/white/gray) intact.

Hooks to other features

Download modal: use the current composed SVG (icon + text) when exporting. No changes needed here.

Send to Brand Kit button (already present):

POST /api/brand-kit/queue with { svgMarkup, brandName, colors, fonts } – the svgMarkup includes the inserted/updated icon layer.

Empty state & errors

Preview empty state text (as in screenshot):

“No logo generated yet — Click ‘Generate Logo’ to create a professional SVG logo with transparent background.”

On API error: toast “Couldn’t generate a logo right now. Try again.”

On sanitize failure: toast “SVG was invalid and was discarded.”

Accessibility & UX polish

Disable Generate when prompt empty or loading.

Keyboard focus ring on cards and buttons.

Preview region is scrollable if the SVG is tall; constrain height to ~260–300px with checkerboard bg.

Debounce prompt typing 200ms if any live “suggestions” are added later (not required now).

Telemetry (optional)

logo_ai_generate_clicked

logo_ai_generate_succeeded with { ms, bytes }

logo_ai_insert_action: "insert" | "replace" | "keep_both"

Acceptance criteria

The Logo tool contains the new AI generator card exactly as described.

Entering a prompt and clicking Generate Logo shows a vector Preview.

Insert into Canvas adds the icon as a layer and centers/fits it.

Replace Existing Icon swaps the current icon while keeping transform.

Generated SVGs are sanitized (no scripts, remote refs) and have a valid viewBox.

Brand text is untouched; no duplication issues occur.

The composed logo (icon + chosen text/font from Text tool) exports correctly and can be Sent to Brand Kit.

Notes / Non-goals

No favicon generation in this flow.

No text generation inside the icon; text is handled by the Text tool.

Keep Brand Kit’s legacy generator route for backwards compatibility, but primary UI for generation now lives in Logo Composer.