that screenshot screams “preview URL is 404/mismatch,” not an SVG coloring issue. Let’s hard-fix both sides so icons always render:

🔧 Replit prompt — make Icons display (no more broken image)
1) Backend: serve real preview + svg (local fallback too)

Edit server/routes/iconLibraryRoutes.js: add these endpoints (or replace your existing ones with this exact logic).

// --- add near the top ---
const express = require("express");
const fs = require("fs/promises");
const path = require("path");
const mime = require("mime-types");
const router = express.Router();

const ObjectStorageService = require("../objectStorage"); // keep if you have it

async function readLocal(file) {
  try { return await fs.readFile(file); } catch { return null; }
}
function guessType(p) { return mime.lookup(p) || "application/octet-stream"; }

// Try cloud first, then local
async function fetchFromStorage(key, localPath) {
  try {
    const obj = new ObjectStorageService();
    const buf = await obj.downloadObject(key); // may throw
    if (buf) return buf;
  } catch {}
  return await readLocal(localPath);
}

// ===== PREVIEW (PNG/JPG) =====
// GET /api/icons/:id/preview
router.get("/:id/preview", async (req, res) => {
  const id = String(req.params.id);

  // Expected locations (adjust to your uploader):
  // cloud key: public-objects/icons/preview/{id}/preview.png
  // local: uploads/icons/preview/{id}/preview.png (or .jpg/.jpeg)
  const cand = [
    { key: `public-objects/icons/preview/${id}/preview.png`, local: path.join(process.cwd(), "uploads", "icons", "preview", id, "preview.png") },
    { key: `public-objects/icons/preview/${id}/preview.jpg`, local: path.join(process.cwd(), "uploads", "icons", "preview", id, "preview.jpg") },
    { key: `public-objects/icons/preview/${id}/preview.jpeg`, local: path.join(process.cwd(), "uploads", "icons", "preview", id, "preview.jpeg") },
  ];

  for (const c of cand) {
    const buf = await fetchFromStorage(c.key, c.local);
    if (buf) {
      res.setHeader("Content-Type", guessType(c.local));
      res.setHeader("Cache-Control", "public, max-age=3600");
      return res.send(buf);
    }
  }
  return res.status(404).send("preview not found");
});

// ===== RAW SVG (sanitized) =====
// GET /api/icons/:id/svg
router.get("/:id/svg", async (req, res) => {
  const id = String(req.params.id);
  // cloud/local: icons/svg/{id}/icon.svg
  const key = `public-objects/icons/svg/${id}/icon.svg`;
  const local = path.join(process.cwd(), "uploads", "icons", "svg", id, "icon.svg");

  let svg = await fetchFromStorage(key, local);
  if (!svg) return res.status(404).send("svg not found");

  let s = svg.toString("utf8");
  // minimal sanitize/normalize
  s = s.replace(/<script[\s\S]*?<\/script>/gi, "")
       .replace(/<foreignObject[\s\S]*?<\/foreignObject>/gi, "")
       .replace(/\son\w+="[^"]*"/gi, "");
  if (!/viewBox=/.test(s)) s = s.replace(/<svg/i, `<svg viewBox="0 0 24 24"`);
  s = s.replace(/\swidth="[^"]*"/i, "").replace(/\sheight="[^"]*"/i, "");
  if (!/fill=/.test(s)) s = s.replace(/<svg/i, `<svg fill="currentColor"`);
  if (!/stroke=/.test(s)) s = s.replace(/<svg/i, `<svg stroke="currentColor"`);

  res.setHeader("Content-Type", "image/svg+xml; charset=utf-8");
  res.setHeader("Cache-Control", "public, max-age=3600");
  res.send(s);
});

module.exports = router;


If you already have /api/icons/list, keep it; these two routes are the missing pieces your UI is trying to hit.

2) Frontend: render SVG first, fallback to preview; never show broken <img>

In your Icons Library page (the user-facing one), ensure we fetch from /api/icons/list and render like this:

type IconApi = {
  id: string;
  name: string;
  style: string;
  tags: string[];
  svg?: string;     // inline if present
  svgUrl?: string;  // else fetch /api/icons/:id/svg
  previewUrl?: string; // else /api/icons/:id/preview
};

// …after fetching the list…

const [inlineSvg, setInlineSvg] = useState<Record<string, string>>({});

useEffect(() => {
  let cancel = false;
  (async () => {
    const needs = icons.filter(i => !i.svg && i.svgUrl);
    for (const i of needs) {
      try {
        const r = await fetch(i.svgUrl!);
        if (!r.ok) continue;
        const text = await r.text();
        if (cancel) return;
        setInlineSvg(prev => ({ ...prev, [i.id]: text }));
      } catch {}
    }
  })();
  return () => { cancel = true; };
}, [icons]);

// Inside each card:
const markup = icon.svg || inlineSvg[icon.id] || null;

<div className="aspect-square flex items-center justify-center rounded-lg bg-white border border-border">
  {markup ? (
    <div
      className="w-16 h-16 md:w-20 md:h-20"
      // your colorizer here:
      dangerouslySetInnerHTML={{ __html: applySvgColor(markup, color) }}
    />
  ) : (
    <img
      src={icon.previewUrl || `/api/icons/${icon.id}/preview`}
      alt={icon.name}
      className="w-16 h-16 md:w-20 md:h-20 object-contain"
      onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = "none"; }}
      loading="lazy"
    />
  )}
</div>


This guarantees:

If SVG is available → you see the real icon (recolorable).

If not → we show /api/icons/:id/preview.

If preview 404s → image hides (no broken icon).

3) Quick verification (1 minute)

Open your browser devtools → Network tab.

Click an icon card; you should see 200 for /api/icons/:id/svg (or /preview).

If any 404, check the actual on-disk path created by the importer:

uploads/icons/svg/{id}/icon.svg

uploads/icons/preview/{id}/preview.png
(adjust the names in the route if your importer used different filenames.)