Let’s keep everything stable and fix this without touching the good icons.

Your symptoms = two different issues:

Some uploaded SVGs (like your guest icons) use <clipPath>/<mask>/<use> etc. Your sanitizer is stripping those → preview goes blank (gray broken-image icon).

When we force “fill=currentColor” on every shape, a few icons that rely on fill="none" + strokes collapse to a tiny blob. So broad recoloring breaks them.

Here’s a minimal, safe fix set:

A) Loosen the sanitizer (don’t remove legit SVG features)

Update utils/svg.ts (or wherever sanitizeSvgClient lives) to allow the common advanced tags/attrs. This alone will make the blank “Guest Icon” show up again.

// utils/svg.ts
export function sanitizeSvgClient(svg: string) {
  try {
    const doc = new DOMParser().parseFromString(svg, "image/svg+xml");

    const allowedTags = new Set([
      "svg","g","path","rect","circle","ellipse","polygon","polyline","line",
      "defs","clipPath","mask","use","symbol","title","desc"
    ]);

    const allowedAttrs = new Set([
      "viewBox","width","height","x","y","cx","cy","r","rx","ry",
      "d","points","transform","style","class","color","xmlns","version",
      "fill","fill-rule","stroke","stroke-width","stroke-linecap","stroke-linejoin","stroke-miterlimit","opacity",
      "clip-path","mask","id","href","xlink:href","role","aria-hidden","focusable"
    ]);

    const walk = (el: Element) => {
      if (!allowedTags.has(el.tagName)) { el.remove(); return; }
      Array.from(el.attributes).forEach(a => {
        const n = a.name;
        if (n.startsWith("on")) el.removeAttribute(n);
        else if (!allowedAttrs.has(n) && !n.startsWith("aria-")) el.removeAttribute(n);
      });
      Array.from(el.children).forEach(ch => walk(ch as Element));
    };

    walk(doc.documentElement);
    return new XMLSerializer().serializeToString(doc);
  } catch {
    return svg; // fail open: better to render than disappear
  }
}

B) Don’t aggress-colorize uploaded (storage) icons

Only colorize the demo/mock icons. For uploaded icons, render exactly as provided (but sanitized). This avoids the “tiny blob” issue.

In IconsPage (your IconRenderer), change the inline render block to this:

// inside IconRenderer, when inlineSvg is available:
try {
  const isStorage = icon.id.startsWith("storage-");
  const source = isStorage
    ? sanitizeSvgClient(inlineSvg)                      // raw for uploaded icons
    : sanitizeSvgClient(applySvgColor(inlineSvg, color)); // recolor only mock/demo

  return <div className={size} dangerouslySetInnerHTML={{ __html: source }} />;
} catch {
  return <div className={size} dangerouslySetInnerHTML={{ __html: inlineSvg }} />;
}


(Leave your applySvgColor as-is; we simply stop applying it to uploaded icons by default.)

C) Ultra-targeted visibility fallback (optional and safe)

If you still encounter a rare uploaded SVG that has no fill and no stroke anywhere (IcoMoon exports can do this), add a tiny helper used only when needed—it doesn’t touch normal files.

// utils/svg.ts
export function ensureVisibleIfUnpainted(svg: string) {
  try {
    const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
    const shapes = Array.from(doc.querySelectorAll("path,rect,circle,ellipse,polygon,polyline,line"));
    const painted = shapes.some(el => el.hasAttribute("fill") || el.hasAttribute("stroke"));
    if (painted) return svg; // do nothing if already visible

    shapes.forEach((el: any) => {
      el.setAttribute("stroke", "currentColor");
      if (!el.getAttribute("stroke-width")) el.setAttribute("stroke-width", "2");
      if (!el.hasAttribute("fill")) el.setAttribute("fill", "none");
    });
    return new XMLSerializer().serializeToString(doc);
  } catch {
    return svg;
  }
}


Use it only in the storage branch if you detect a blank render (e.g., when preview fails). But try A+B first—usually that’s enough.

Where to change (quick checklist)

utils/svg.ts

Replace sanitizeSvgClient with the version above (adds clipPath/mask/use/...).

(Optional) add ensureVisibleIfUnpainted.

IconsPage.tsx (your IconRenderer)

In the inline SVG path, use the storage gate: colorize only non-storage (mock) icons.

That’s it. This keeps everything that already works untouched, fixes the blank guest icon by not stripping its clipPath/mask, and prevents the tiny-dot regression by not force-recolorizing uploaded files.