icons: restore inline-SVG link/gear icons, share them across all three pages

Fixes a regression + a longer-standing inconsistency (mara: "agent
page link icons is different from swarm ui, settings icon looks weird
since component extract").

- The `SettingsMenu` shared-component extraction swapped swarm-ui's
  original inline-SVG gear trigger for a plain `⚙` text glyph (matching
  agent's `MetaNav`, which was itself still on the `🔗` emoji at the
  time). An emoji/text glyph is rendered by the OS/browser's own font
  at that font's fixed metrics — it can never match a neighbouring
  icon in size or weight, and some codepoints (the gear included)
  aren't even reliably covered by every font.
- New `@hive/shared/icons.js` (`GearIcon`, `LinkIcon`) — the exact SVG
  markup that used to live only in swarm-ui's `SettingsMenu`/
  `LinksMenu` as two separate inline copies, now the one shared source.
- `SettingsMenu` (shared) uses `GearIcon`; agent's `MetaNav` and
  swarm-ui's `LinksMenu` both use `LinkIcon` — three consumers, one
  rendering path, matching stroke/viewBox/size everywhere.

Verified with real screenshots on both agent and swarm-ui — both
trigger icons render as the same crisp line-icon style now.
This commit is contained in:
iris 2026-08-29 11:50:00 +02:00 committed by mara
commit 4175ae86e3
5 changed files with 84 additions and 27 deletions

View file

@ -14,6 +14,14 @@
// visual language for that package either), motion included
// unconditionally rather than agent-only.
//
// That first merge swapped the trigger's icon from swarm-ui's original
// inline SVG gear to a plain `⚙` text glyph (matching `MetaNav`'s own
// `value="🔗"` emoji at the time) — mara caught it: "settings icon
// looks weird since component extract". `../icons.js`'s `GearIcon` is
// swarm-ui's original SVG, restored here so both pages get the crisp
// version now instead of just one — see that file's comment for why a
// text/emoji glyph is the wrong tool for a trigger's own icon.
//
// Storage keys are the caller's responsibility (`themeKey`/`motionKey`
// props), not derived here — a page mounts `useApplyThemeOverride`/
// `useApplyMotionOverride` (`./theme-apply.js`/`./motion-apply.js`)
@ -22,6 +30,7 @@
// page-specific naming decision.
import { useEffect, useRef, useState } from 'preact/hooks';
import { Badge } from '../badge/Badge.js';
import { GearIcon } from '../icons.js';
import { useThemeOverride, type ThemeOverride } from './theme-apply.js';
import { useMotionOverride, type MotionOverride } from './motion-apply.js';
import './SettingsMenu.css';
@ -59,7 +68,7 @@ export function SettingsMenu({ themeKey, motionKey }: SettingsMenuProps) {
return (
<div class="settings-menu-anchor" ref={rootRef}>
<Badge value="⚙" title="settings" onClick={() => setOpen((v) => !v)} expanded={open} />
<Badge value={<GearIcon />} title="settings" onClick={() => setOpen((v) => !v)} expanded={open} />
{open ? (
<div class="settings-menu-popover" role="menu" aria-label="settings">
<label class="settings-menu-row">