+{/if}
diff --git a/frontend/src/lib/components/MdiIcon.svelte b/frontend/src/lib/components/MdiIcon.svelte
new file mode 100644
index 0000000..79ee0ca
--- /dev/null
+++ b/frontend/src/lib/components/MdiIcon.svelte
@@ -0,0 +1,9 @@
+
+
+{#if name && getMdiPath(name)}
+
+{/if}
diff --git a/frontend/src/lib/mdi-lookup.ts b/frontend/src/lib/mdi-lookup.ts
new file mode 100644
index 0000000..277a795
--- /dev/null
+++ b/frontend/src/lib/mdi-lookup.ts
@@ -0,0 +1,36 @@
+/**
+ * Lazy MDI icon path lookup.
+ *
+ * Instead of `import * as mdi from '@mdi/js'` (which loads ~5MB of SVG paths
+ * into memory), this module loads the full set once on first use and caches it.
+ * Vite only processes the import once, reducing HMR memory pressure.
+ */
+
+let _cache: Record | null = null;
+
+async function _load(): Promise> {
+ if (_cache) return _cache;
+ const mod = await import('@mdi/js');
+ _cache = mod as unknown as Record;
+ return _cache;
+}
+
+// Eagerly load on module init (runs once)
+let _ready: Record | null = null;
+_load().then(m => { _ready = m; });
+
+/**
+ * Get SVG path for an icon name. Returns empty string if not found or not yet loaded.
+ */
+export function getMdiPath(name: string): string {
+ if (!name || !_ready) return '';
+ return (_ready as any)[name] || '';
+}
+
+/**
+ * Get all icon names (for IconPicker search). Returns empty array if not yet loaded.
+ */
+export function getAllMdiNames(): string[] {
+ if (!_ready) return [];
+ return Object.keys(_ready).filter(k => k.startsWith('mdi') && k !== 'default');
+}