amalgm 0.1.143 → 0.1.144

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/runtime/lib/local/amalgmStore.js +0 -7
  3. package/runtime/scripts/amalgm-mcp/apps/rest.js +24 -1
  4. package/runtime/scripts/amalgm-mcp/apps/supervisor.js +16 -0
  5. package/runtime/scripts/amalgm-mcp/browser/cli.js +1 -5
  6. package/runtime/scripts/amalgm-mcp/browser/engine.js +18 -3
  7. package/runtime/scripts/amalgm-mcp/browser/paths.js +6 -1
  8. package/runtime/scripts/amalgm-mcp/browser/rest.js +12 -5
  9. package/runtime/scripts/amalgm-mcp/computer-use/index.js +1038 -0
  10. package/runtime/scripts/amalgm-mcp/lib/email-embeds.js +53 -23
  11. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-aa-57534e-ffffff.png +0 -0
  12. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-nb-f37626-ffffff.png +0 -0
  13. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-letter-p-d24726-ffffff.png +0 -0
  14. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-atsign-ffffff.png +0 -0
  15. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-box-d6d3d1.png +0 -0
  16. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-calendarclock-d6d3d1.png +0 -0
  17. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-file-a8a29e.png +0 -0
  18. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filearchive-facc15.png +0 -0
  19. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-2dd4bf.png +0 -0
  20. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-fileimage-c084fc.png +0 -0
  21. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filespreadsheet-4ade80.png +0 -0
  22. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-60a5fa.png +0 -0
  23. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-a8a29e.png +0 -0
  24. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-filetext-f87171.png +0 -0
  25. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-folder-d6d3d1.png +0 -0
  26. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-globe-d6d3d1.png +0 -0
  27. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-video-f472b6.png +0 -0
  28. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/amalgm-icon-lucide-zap-fbbf24.png +0 -0
  29. package/runtime/scripts/amalgm-mcp/lib/email-icon-assets/manifest.json +56 -0
  30. package/runtime/scripts/amalgm-mcp/lib/email-icons.js +34 -1
  31. package/runtime/scripts/amalgm-mcp/lib/email-md.js +95 -51
  32. package/runtime/scripts/amalgm-mcp/lib/file-icon-registry.json +146 -3
  33. package/runtime/scripts/amalgm-mcp/lib/generated/email-render.json +153 -0
  34. package/runtime/scripts/amalgm-mcp/notify/index.js +5 -2
  35. package/runtime/scripts/amalgm-mcp/server/core-tools.js +6 -0
  36. package/runtime/scripts/amalgm-mcp/server/routes/apps.js +4 -0
  37. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-push.test.js +60 -0
  38. package/runtime/scripts/amalgm-mcp/tests/bundle-entries.test.js +54 -0
  39. package/runtime/scripts/amalgm-mcp/tests/computer-use.test.js +151 -0
  40. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +4 -0
  41. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/citations.md +9 -0
  42. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/entities.md +15 -0
  43. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/links.md +5 -0
  44. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/media.md +21 -0
  45. package/runtime/scripts/amalgm-mcp/tests/email-render-fixtures/text.md +30 -0
  46. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/citations.html +5 -0
  47. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/entities.html +8 -0
  48. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/links.html +3 -0
  49. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/media.html +11 -0
  50. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/notification-shell.html +11 -0
  51. package/runtime/scripts/amalgm-mcp/tests/email-render-goldens/text.html +12 -0
  52. package/runtime/scripts/amalgm-mcp/tests/email-render.test.js +82 -0
  53. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +2 -0
  54. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +5 -0
  55. package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +1 -0
  56. package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +1 -0
  57. package/runtime/scripts/amalgm-mcp/toolbox/store.js +18 -0
@@ -15,14 +15,21 @@
15
15
  * three in sync.
16
16
  */
17
17
 
18
+ // GENERATED tokens/chrome — serialized from lib/rendering/tokens.ts and
19
+ // chromeTokens.ts by scripts/amalgm-mcp/generate-email-render-assets.mts.
20
+ // No hand-mirrored values here: change the canonical file and regenerate.
21
+ const RENDER_ASSETS = require('./generated/email-render.json');
22
+ const TOKENS = RENDER_ASSETS.tokens;
23
+ const CHROME = RENDER_ASSETS.chrome;
24
+
18
25
  const COLOR = {
19
- text: '#ffffff',
20
- textMuted: '#a8a29e', // stone-400
21
- border: '#1c1917', // stone-900 — container borders
22
- pillBorder: '#292524', // stone-800
23
- bg: '#000000',
24
- bgChip: '#0c0a09', // stone-950
25
- link: '#60a5fa', // blue-400
26
+ text: TOKENS.color.text,
27
+ textMuted: TOKENS.color.textMuted,
28
+ border: TOKENS.color.border, // stone-900 — container borders
29
+ pillBorder: TOKENS.color.tableBorder, // stone-800
30
+ bg: TOKENS.color.bg,
31
+ bgChip: TOKENS.color.bgChip, // stone-950
32
+ link: TOKENS.color.link, // blue-400
26
33
  };
27
34
 
28
35
  const FILE_ICON_REGISTRY = require('./file-icon-registry.json');
@@ -182,7 +189,10 @@ function matchesSpecialFilename(rule, normalizedName) {
182
189
  );
183
190
  }
184
191
 
185
- function resolveFileIconDescriptor(name) {
192
+ function resolveFileIconDescriptor(name, options = {}) {
193
+ // Mirrors lib/rendering/fileIconRegistry.ts resolveFileIconDescriptor.
194
+ if (options.isSkill) return FILE_ICON_REGISTRY.defaults.skill;
195
+ if (options.isDirectory) return FILE_ICON_REGISTRY.defaults.folder;
186
196
  const normalizedName = normalizedBasename(name);
187
197
  const special = (FILE_ICON_REGISTRY.specialFilenameIcons || []).find((rule) => matchesSpecialFilename(rule, normalizedName));
188
198
  if (special) return special.icon;
@@ -195,10 +205,21 @@ function resolveEntityIconDescriptor(type) {
195
205
  return FILE_ICON_REGISTRY.entityIcons[normalized] || FILE_ICON_REGISTRY.entityIcons.entity || FILE_ICON_REGISTRY.defaults.file;
196
206
  }
197
207
 
198
- function iconHtml(descriptor) {
208
+ function iconHtml(descriptor, size = CHROME.pillIconSize) {
199
209
  if (!descriptor) descriptor = FILE_ICON_REGISTRY.defaults.file;
200
210
  const contentId = iconContentId(descriptor);
201
- return `<img src="cid:${contentId}" alt="" width="14" height="14" style="display:block;width:14px;height:14px;border:0;outline:none;text-decoration:none">`;
211
+ return `<img src="cid:${contentId}" alt="" width="${size}" height="${size}" style="display:block;width:${size}px;height:${size}px;border:0;outline:none;text-decoration:none">`;
212
+ }
213
+
214
+ /**
215
+ * Inline citation icon — the email form of Citation's 12px icon slot.
216
+ * display:inline-block (not block) so it rides in the text line.
217
+ */
218
+ function citationIconHtml(descriptor) {
219
+ const size = CHROME.citationIconSize;
220
+ if (!descriptor) descriptor = FILE_ICON_REGISTRY.defaults.file;
221
+ const contentId = iconContentId(descriptor);
222
+ return `<img src="cid:${contentId}" alt="" width="${size}" height="${size}" style="display:inline-block;width:${size}px;height:${size}px;border:0;vertical-align:-1px">`;
202
223
  }
203
224
 
204
225
  function openIconHtml() {
@@ -223,11 +244,14 @@ function chromeLabelParts(name) {
223
244
  */
224
245
  function chromePill(cells, extra = '') {
225
246
  return (
226
- `<table role="presentation" cellpadding="0" cellspacing="0" style="display:inline-table;height:32px;background:${COLOR.bgChip};border:1px solid ${COLOR.pillBorder};border-radius:12px;border-collapse:separate;box-shadow:0 14px 35px rgba(0,0,0,0.45);${extra}">` +
247
+ `<table role="presentation" cellpadding="0" cellspacing="0" style="${CHROME.pillTableCss};${extra}">` +
227
248
  `<tr>${cells}</tr></table>`
228
249
  );
229
250
  }
230
251
 
252
+ // Text centers vertically via line-height = pill height minus the 2px of border.
253
+ const PILL_TEXT_CSS = `font-size:${CHROME.pillFontSize}px;line-height:${CHROME.pillHeight - 2}px;color:${CHROME.pillText};font-weight:400`;
254
+
231
255
  /** `[glyph] name` — title text fades after the tab-width budget, matching the UI treatment. */
232
256
  function namePill(iconDescriptor, name) {
233
257
  const { solid, fade } = chromeLabelParts(name);
@@ -240,9 +264,9 @@ function namePill(iconDescriptor, name) {
240
264
  .join(''));
241
265
  const labelStyle = fade ? 'width:122px;max-width:122px;' : 'max-width:224px;';
242
266
  return chromePill(
243
- `<td style="padding:0 0 0 12px;vertical-align:middle;line-height:0;width:18px">${iconHtml(iconDescriptor)}</td>` +
244
- `<td style="padding:0 12px 0 8px;vertical-align:middle">` +
245
- `<div title="${escapeAttr(name)}" style="display:block;${labelStyle}overflow:hidden;white-space:nowrap;word-break:normal;overflow-wrap:normal;font-size:13px;line-height:30px;color:${COLOR.text};font-weight:400">${label}</div>` +
267
+ `<td style="padding:0 0 0 ${CHROME.pillPaddingX}px;vertical-align:middle;line-height:0;width:${CHROME.pillIconSize}px">${iconHtml(iconDescriptor)}</td>` +
268
+ `<td style="padding:0 ${CHROME.pillPaddingX}px 0 ${CHROME.pillGap}px;vertical-align:middle">` +
269
+ `<div title="${escapeAttr(name)}" style="display:block;${labelStyle}overflow:hidden;white-space:nowrap;word-break:normal;overflow-wrap:normal;${PILL_TEXT_CSS}">${label}</div>` +
246
270
  `</td>`
247
271
  );
248
272
  }
@@ -250,8 +274,8 @@ function namePill(iconDescriptor, name) {
250
274
  /** The single "Open ↗" affordance — a link when href is given. */
251
275
  function openPill(href) {
252
276
  const pill = chromePill(
253
- `<td style="padding:0 0 0 12px;vertical-align:middle;line-height:0;width:14px">${openIconHtml()}</td>` +
254
- `<td style="padding:0 12px 0 7px;vertical-align:middle;font-size:13px;line-height:30px;color:${COLOR.text};font-weight:400;white-space:nowrap">Open</td>`
277
+ `<td style="padding:0 0 0 ${CHROME.pillPaddingX}px;vertical-align:middle;line-height:0;width:${CHROME.pillIconSize}px">${openIconHtml()}</td>` +
278
+ `<td style="padding:0 ${CHROME.pillPaddingX}px 0 ${CHROME.pillGap - 2}px;vertical-align:middle;${PILL_TEXT_CSS};white-space:nowrap">Open</td>`
255
279
  );
256
280
  return href
257
281
  ? `<a href="${escapeAttr(href)}" target="_blank" style="text-decoration:none;display:inline-block">${pill}</a>`
@@ -269,7 +293,7 @@ function embedShell({ iconDescriptor, name, action, body = '', minHeight = 96 })
269
293
  const right = action ? openPill(action.href) : '';
270
294
  const content = body || `<div style="height:${minHeight}px;line-height:${minHeight}px;font-size:0;background:${COLOR.bg}">&nbsp;</div>`;
271
295
  return (
272
- `<div style="border:1px solid ${COLOR.border};border-radius:16px;background:${COLOR.bg};margin:16px 0;overflow:hidden">` +
296
+ `<div style="${CHROME.frameCss}">` +
273
297
  `<div style="padding:12px 12px 10px;background:${COLOR.bg}">` +
274
298
  `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse"><tr>` +
275
299
  `<td align="left" style="padding:0;vertical-align:top;line-height:0">${left}</td>` +
@@ -291,7 +315,7 @@ function embedShell({ iconDescriptor, name, action, body = '', minHeight = 96 })
291
315
  * images take the passed href (the "open in amalgm" target).
292
316
  */
293
317
  function renderImageEmbed(alt, url, href) {
294
- const img = `<img src="${escapeAttr(url)}" alt="${escapeAttr(alt || '')}" style="display:block;max-width:100%;height:auto;border-radius:16px;border:1px solid ${COLOR.border};margin:16px 0">`;
318
+ const img = `<img src="${escapeAttr(url)}" alt="${escapeAttr(alt || '')}" style="display:block;max-width:100%;height:auto;border-radius:16px;border:1px solid ${COLOR.border};margin:0">`;
295
319
  const link = href || (isHttpUrl(url) ? url : null);
296
320
  return link
297
321
  ? `<a href="${escapeAttr(link)}" target="_blank" style="text-decoration:none;display:block">${img}</a>`
@@ -445,12 +469,15 @@ function renderEntityTile(label, type, id, renderContext = {}) {
445
469
  const safeLabel = escapeText(label || entityTypeLabel(type));
446
470
  const iconDescriptor = resolveEntityIconDescriptor(type);
447
471
  const href = buildEntityDeepLink(type, id, renderContext);
472
+ const size = CHROME.entityTileIconSize;
473
+ const contentId = iconContentId(iconDescriptor);
474
+ // Pure inline markup — a table here is block-level, so Gmail would break a
475
+ // mid-sentence tile onto its own line and leave an empty anchor stub behind.
476
+ const icon = `<img src="cid:${contentId}" alt="" width="${size}" height="${size}" style="display:inline-block;width:${size}px;height:${size}px;border:0;vertical-align:-3px;margin:0 10px 0 0">`;
448
477
  return (
449
- `<a href="${escapeAttr(href)}" target="_blank" style="text-decoration:none;display:inline-block;background:${COLOR.bg};border:1px solid ${COLOR.border};border-radius:12px;padding:8px 12px;margin:2px 0;vertical-align:middle">` +
450
- `<table role="presentation" cellpadding="0" cellspacing="0" style="border-collapse:collapse"><tr>` +
451
- `<td style="padding:0 10px 0 0;vertical-align:middle;line-height:0">${iconHtml(iconDescriptor)}</td>` +
452
- `<td style="padding:0;vertical-align:middle;font-size:14px;line-height:18px;color:${COLOR.text};font-weight:650">${safeLabel}</td>` +
453
- `</tr></table>` +
478
+ `<a href="${escapeAttr(href)}" target="_blank" style="${CHROME.entityTileCss};white-space:nowrap">` +
479
+ icon +
480
+ `<span style="font-size:${CHROME.entityTileFontSize}px;line-height:18px;color:${CHROME.entityTileText};font-weight:${CHROME.entityTileFontWeight}">${safeLabel}</span>` +
454
481
  `</a>`
455
482
  );
456
483
  }
@@ -482,6 +509,9 @@ function renderEntityEmbed(mode, type, label, idOrRenderContext, maybeRenderCont
482
509
  }
483
510
 
484
511
  module.exports = {
512
+ resolveFileIconDescriptor,
513
+ resolveEntityIconDescriptor,
514
+ citationIconHtml,
485
515
  mediaExtensionOf,
486
516
  mediaMimeOf,
487
517
  mediaKindOf,
@@ -0,0 +1,56 @@
1
+ {
2
+ "//": "GENERATED — SVG source per CID icon; the .png files are rasters of exactly this markup.",
3
+ "icons": {
4
+ "amalgm-icon-letter-aa-57534e-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#57534E\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">Aa</text></svg>",
5
+ "amalgm-icon-letter-c-00599c-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#00599C\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"6.4\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">C++</text></svg>",
6
+ "amalgm-icon-letter-c-5c6bc0-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#5C6BC0\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">C</text></svg>",
7
+ "amalgm-icon-letter-c-68217a-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#68217A\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">C#</text></svg>",
8
+ "amalgm-icon-letter-go-00add8-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#00ADD8\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">GO</text></svg>",
9
+ "amalgm-icon-letter-j-e76f00-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#E76F00\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">J</text></svg>",
10
+ "amalgm-icon-letter-js-f7df1e-1c1917": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#F7DF1E\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#1c1917\">JS</text></svg>",
11
+ "amalgm-icon-letter-kt-7f52ff-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#7F52FF\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">KT</text></svg>",
12
+ "amalgm-icon-letter-nb-f37626-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#F37626\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">Nb</text></svg>",
13
+ "amalgm-icon-letter-p-d24726-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#D24726\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">P</text></svg>",
14
+ "amalgm-icon-letter-php-777bb4-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#777BB4\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"6.4\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">PHP</text></svg>",
15
+ "amalgm-icon-letter-rb-cc342d-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#CC342D\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">RB</text></svg>",
16
+ "amalgm-icon-letter-rs-ce4a1f-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#CE4A1F\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">RS</text></svg>",
17
+ "amalgm-icon-letter-s-cd6799-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#CD6799\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">S</text></svg>",
18
+ "amalgm-icon-letter-sw-f05138-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#F05138\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">SW</text></svg>",
19
+ "amalgm-icon-letter-ts-3178c6-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#3178C6\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">TS</text></svg>",
20
+ "amalgm-icon-letter-v-42b883-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"3.5\" fill=\"#42B883\"></rect><text x=\"8\" y=\"11.6\" text-anchor=\"middle\" font-size=\"8\" font-weight=\"700\" font-family=\"-apple-system, BlinkMacSystemFont, &#x27;Segoe UI&#x27;, sans-serif\" fill=\"#ffffff\">V</text></svg>",
21
+ "amalgm-icon-lucide-atsign-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-white\" style=\"width:28px;height:28px\"><circle cx=\"12\" cy=\"12\" r=\"4\"></circle><path d=\"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8\"></path></svg>",
22
+ "amalgm-icon-lucide-box-d6d3d1": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d6d3d1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-300\" style=\"width:28px;height:28px\"><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"></path><path d=\"m3.3 7 8.7 5 8.7-5\"></path><path d=\"M12 22V12\"></path></svg>",
23
+ "amalgm-icon-lucide-box-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-white\" style=\"width:28px;height:28px\"><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"></path><path d=\"m3.3 7 8.7 5 8.7-5\"></path><path d=\"M12 22V12\"></path></svg>",
24
+ "amalgm-icon-lucide-braces-facc15": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#facc15\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-yellow-400\" style=\"width:28px;height:28px\"><path d=\"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1\"></path><path d=\"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1\"></path></svg>",
25
+ "amalgm-icon-lucide-braces-fb923c": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fb923c\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-orange-400\" style=\"width:28px;height:28px\"><path d=\"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1\"></path><path d=\"M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1\"></path></svg>",
26
+ "amalgm-icon-lucide-calendarclock-d6d3d1": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d6d3d1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-300\" style=\"width:28px;height:28px\"><path d=\"M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5\"></path><path d=\"M16 2v4\"></path><path d=\"M8 2v4\"></path><path d=\"M3 10h5\"></path><path d=\"M17.5 17.5 16 16.25V14\"></path><path d=\"M22 16a6 6 0 1 1-12 0 6 6 0 0 1 12 0Z\"></path></svg>",
27
+ "amalgm-icon-lucide-database-60a5fa": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#60a5fa\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-blue-400\" style=\"width:28px;height:28px\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"></ellipse><path d=\"M3 5V19A9 3 0 0 0 21 19V5\"></path><path d=\"M3 12A9 3 0 0 0 21 12\"></path></svg>",
28
+ "amalgm-icon-lucide-externallink-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-white\" style=\"width:28px;height:28px\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path><polyline points=\"15 3 21 3 21 9\"></polyline><line x1=\"10\" x2=\"21\" y1=\"14\" y2=\"3\"></line></svg>",
29
+ "amalgm-icon-lucide-file-a8a29e": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#a8a29e\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline></svg>",
30
+ "amalgm-icon-lucide-filearchive-facc15": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#facc15\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-yellow-400\" style=\"width:28px;height:28px\"><path d=\"M4 22V4c0-.5.2-1 .6-1.4C5 2.2 5.5 2 6 2h8.5L20 7.5V20c0 .5-.2 1-.6 1.4-.4.4-.9.6-1.4.6h-2\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><circle cx=\"10\" cy=\"20\" r=\"2\"></circle><path d=\"M10 7V6\"></path><path d=\"M10 12v-1\"></path><path d=\"M10 18v-2\"></path></svg>",
31
+ "amalgm-icon-lucide-fileimage-2dd4bf": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#2dd4bf\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-teal-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><circle cx=\"10\" cy=\"13\" r=\"2\"></circle><path d=\"m20 17-1.09-1.09a2 2 0 0 0-2.82 0L10 22\"></path></svg>",
32
+ "amalgm-icon-lucide-fileimage-c084fc": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#c084fc\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-purple-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><circle cx=\"10\" cy=\"13\" r=\"2\"></circle><path d=\"m20 17-1.09-1.09a2 2 0 0 0-2.82 0L10 22\"></path></svg>",
33
+ "amalgm-icon-lucide-filespreadsheet-4ade80": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4ade80\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-green-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><path d=\"M8 13h2\"></path><path d=\"M8 17h2\"></path><path d=\"M14 13h2\"></path><path d=\"M14 17h2\"></path></svg>",
34
+ "amalgm-icon-lucide-filetext-60a5fa": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#60a5fa\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-blue-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><line x1=\"16\" x2=\"8\" y1=\"13\" y2=\"13\"></line><line x1=\"16\" x2=\"8\" y1=\"17\" y2=\"17\"></line><line x1=\"10\" x2=\"8\" y1=\"9\" y2=\"9\"></line></svg>",
35
+ "amalgm-icon-lucide-filetext-a8a29e": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#a8a29e\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><line x1=\"16\" x2=\"8\" y1=\"13\" y2=\"13\"></line><line x1=\"16\" x2=\"8\" y1=\"17\" y2=\"17\"></line><line x1=\"10\" x2=\"8\" y1=\"9\" y2=\"9\"></line></svg>",
36
+ "amalgm-icon-lucide-filetext-f87171": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f87171\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-red-400\" style=\"width:28px;height:28px\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"></path><polyline points=\"14 2 14 8 20 8\"></polyline><line x1=\"16\" x2=\"8\" y1=\"13\" y2=\"13\"></line><line x1=\"16\" x2=\"8\" y1=\"17\" y2=\"17\"></line><line x1=\"10\" x2=\"8\" y1=\"9\" y2=\"9\"></line></svg>",
37
+ "amalgm-icon-lucide-folder-d6d3d1": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d6d3d1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-300\" style=\"width:28px;height:28px\"><path d=\"M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z\"></path></svg>",
38
+ "amalgm-icon-lucide-globe-d6d3d1": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d6d3d1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-300\" style=\"width:28px;height:28px\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"2\" x2=\"22\" y1=\"12\" y2=\"12\"></line><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"></path></svg>",
39
+ "amalgm-icon-lucide-lock-facc15": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#facc15\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-yellow-400\" style=\"width:28px;height:28px\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg>",
40
+ "amalgm-icon-lucide-messagesquare-ffffff": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-white\" style=\"width:28px;height:28px\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"></path></svg>",
41
+ "amalgm-icon-lucide-music-f472b6": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f472b6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-pink-400\" style=\"width:28px;height:28px\"><path d=\"M9 18V5l12-2v13\"></path><circle cx=\"6\" cy=\"18\" r=\"3\"></circle><circle cx=\"18\" cy=\"16\" r=\"3\"></circle></svg>",
42
+ "amalgm-icon-lucide-package-38bdf8": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#38bdf8\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-sky-400\" style=\"width:28px;height:28px\"><path d=\"m7.5 4.27 9 5.15\"></path><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"></path><path d=\"m3.3 7 8.7 5 8.7-5\"></path><path d=\"M12 22V12\"></path></svg>",
43
+ "amalgm-icon-lucide-package-facc15": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#facc15\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-yellow-400\" style=\"width:28px;height:28px\"><path d=\"m7.5 4.27 9 5.15\"></path><path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"></path><path d=\"m3.3 7 8.7 5 8.7-5\"></path><path d=\"M12 22V12\"></path></svg>",
44
+ "amalgm-icon-lucide-settings-a8a29e": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#a8a29e\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-400\" style=\"width:28px;height:28px\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path><circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg>",
45
+ "amalgm-icon-lucide-settings-d6d3d1": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#d6d3d1\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-stone-300\" style=\"width:28px;height:28px\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path><circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg>",
46
+ "amalgm-icon-lucide-settings-f87171": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f87171\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-red-400\" style=\"width:28px;height:28px\"><path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"></path><circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg>",
47
+ "amalgm-icon-lucide-terminal-4ade80": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4ade80\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-green-400\" style=\"width:28px;height:28px\"><polyline points=\"4 17 10 11 4 5\"></polyline><line x1=\"12\" x2=\"20\" y1=\"19\" y2=\"19\"></line></svg>",
48
+ "amalgm-icon-lucide-video-f472b6": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f472b6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-pink-400\" style=\"width:28px;height:28px\"><path d=\"m22 8-6 4 6 4V8Z\"></path><rect width=\"14\" height=\"12\" x=\"2\" y=\"6\" rx=\"2\" ry=\"2\"></rect></svg>",
49
+ "amalgm-icon-lucide-zap-fbbf24": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#fbbf24\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"text-amber-400\" style=\"width:28px;height:28px\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"></polygon></svg>",
50
+ "amalgm-icon-path-css3-1572b6": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><path fill=\"#1572B6\" d=\"M1.5 0h21l-1.91 21.563L11.977 24l-8.565-2.438L1.5 0zm17.09 4.413L5.41 4.41l.213 2.622 10.125.002-.255 2.716h-6.64l.24 2.573h6.182l-.366 3.523-2.91.804-2.956-.81-.188-2.11h-2.61l.29 3.855L12 19.288l5.373-1.53L18.59 4.414z\"></path></svg>",
51
+ "amalgm-icon-path-html5-e34f26": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><path fill=\"#E34F26\" d=\"M1.5 0h21l-1.91 21.563L11.977 24l-8.564-2.438L1.5 0zm7.031 9.75l-.232-2.718 10.059.003.23-2.622L5.412 4.41l.698 8.01h9.126l-.326 3.426-2.91.804-2.955-.81-.188-2.11H6.248l.33 4.171L12 19.351l5.379-1.443.744-8.157H8.531z\"></path></svg>",
52
+ "amalgm-icon-path-markdown-a8a29e": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><path fill=\"#a8a29e\" d=\"M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.732zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z\"></path></svg>",
53
+ "amalgm-icon-path-python-4b8bbe": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><path fill=\"#4B8BBE\" d=\"M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01z\"></path></svg>",
54
+ "amalgm-icon-react": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"-11.5 -10.23 23 20.46\" style=\"width:28px;height:28px\" aria-hidden=\"true\"><circle r=\"2.05\" fill=\"#61dafb\"></circle><g stroke=\"#61dafb\" stroke-width=\"1\" fill=\"none\"><ellipse rx=\"11\" ry=\"4.2\"></ellipse><ellipse rx=\"11\" ry=\"4.2\" transform=\"rotate(60)\"></ellipse><ellipse rx=\"11\" ry=\"4.2\" transform=\"rotate(120)\"></ellipse></g></svg>"
55
+ }
56
+ }
@@ -2,6 +2,10 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
4
  const ICON_ASSET_DIR = path.join(__dirname, 'email-icon-assets');
5
+ // The notify proxy rejects payloads with more than 8 attachments, so media +
6
+ // icons share that budget. Icons that don't fit are stripped from the HTML
7
+ // (stripUnattachedIconImages) and degrade to plain text — never broken-image
8
+ // boxes. Raising this requires raising the cap in api-proxy-v2 /notify first.
5
9
  const MAX_TOTAL_ATTACHMENTS = 8;
6
10
 
7
11
  function sanitizeId(value) {
@@ -50,4 +54,33 @@ function prepareEmailIconAttachments(html, existingAttachments = []) {
50
54
  return attachments;
51
55
  }
52
56
 
53
- module.exports = { iconContentId, prepareEmailIconAttachments };
57
+ /**
58
+ * Remove icon <img> tags whose cid never made it into the attachment list
59
+ * (budget overflow or missing asset) — a missing icon must degrade to plain
60
+ * text, never a broken-image box.
61
+ */
62
+ function stripUnattachedIconImages(html, attachments) {
63
+ const attached = new Set(attachments.map((attachment) => attachment.contentId));
64
+ return (
65
+ html
66
+ // Icon slot span wrapping only the cid image — drop the whole slot so
67
+ // no empty 12px gap survives.
68
+ .replace(
69
+ /<span style="[^"]*"><img [^>]*src="cid:(amalgm-icon-[a-z0-9-]+)"[^>]*><\/span>/gi,
70
+ (tag, contentId) => (attached.has(contentId) ? tag : ''),
71
+ )
72
+ // Pill/tile icon cell wrapping only the cid image — collapse the cell
73
+ // (keeping the pill's left padding) so no empty icon slot survives.
74
+ .replace(
75
+ /<td style="([^"]*)"><img [^>]*src="cid:(amalgm-icon-[a-z0-9-]+)"[^>]*><\/td>/gi,
76
+ (tag, tdStyle, contentId) =>
77
+ attached.has(contentId) ? tag : '<td style="padding:0 0 0 4px;width:0;font-size:0;line-height:0"></td>',
78
+ )
79
+ // Bare cid icon images (pills, tiles).
80
+ .replace(/<img [^>]*src="cid:(amalgm-icon-[a-z0-9-]+)"[^>]*>/gi, (tag, contentId) =>
81
+ attached.has(contentId) ? tag : '',
82
+ )
83
+ );
84
+ }
85
+
86
+ module.exports = { iconContentId, prepareEmailIconAttachments, stripUnattachedIconImages };