**Red commit:** `851cc8c3a024b1675558092d772444bf4f1ec625` — failing test on a stub branch (will link CI run after PR opens). Partial fix for #1648 (M5 of 6). **Do NOT close the tracking issue** — M6 (server-side residual emoji sweep + lint gate) still pending. ## Per-file swap counts | File | Phosphor `<use>` refs | Notes | |---|---|---| | `public/customize.js` | 20 | DEFAULTS → `ph:<name>` tokens; render path keeps legacy emoji branch (back-compat) | | `public/customize-v2.js` | 26 | same as v1; cv2 overrides path unchanged | | `public/home.js` | (helpers added) | `_renderHomeGlyph` / `_renderHomeLabel` accept both `ph:<name>` and legacy emoji | | `public/geofilter-builder.html` | 5 | clear / undo / save / load buttons (+inline `.ph-icon` CSS) | | `public/audio.js` | 1 | audio unlock prompt | | `public/filter-ux.js` | 5 (3 new) | help popover star + close, saved-filter delete | | `public/style.css` | 0 | `#chList .ch-share-btn::before { content: '📤' }` removed; JS now renders an inline sprite | | `cmd/server/routes.go` | (6 `ph:` tokens) | onboarding home defaults updated in lockstep with customize-v2.js | ## Operator config back-compat — PROMINENT Per design call #1 (user-locked): existing operator-stored emoji values in `config.json` / `localStorage` are **NOT** touched. The render path supports both: ```js function renderConfigGlyph(value) { var m = String(value || '').match(/^ph:([a-z][a-z0-9-]+)$/); if (m) return '<svg class="ph-icon"><use href="/icons/phosphor-sprite.svg#ph-' + m[1] + '"/></svg>'; return esc(value); // EMOJI-OK-LEGACY-RENDER — operator-stored emoji/text path } ``` Defaults flipped to `ph:<name>` tokens, so new operators (and operators who hit "Reset to Defaults") see Phosphor sprites. Operators with stored emoji values continue to see their emoji exactly as before. Verified end-to-end (see E2E (b) below). ## cmd/server/routes.go — changed in lockstep Per design call #2: the home-defaults `steps` / `footerLinks` mirror the JS DEFAULTS, so they MUST update together. routes.go now emits `ph:<name>` tokens; the frontend home-render path resolves them. Existing tests (`TestConfigThemeHomeDefaults`) still pass — they assert structure, not glyph values. ## E2E assertions added - `test-issue-1648-m5-emoji-scan.js` — per-file zero-emoji + ph-token DEFAULTS + sprite presence - `test-issue-1648-m5-icons-e2e.js`: - (a) customize chrome — tabs/header rendered as sprites; chrome text icon-free - **(b) back-compat — injects fake `🐙` operator step into localStorage, reloads, opens customize, asserts the emoji renders verbatim in both the input value AND the live preview span; asserts the ph-token step renders as a sprite** (design call #1 in action) - (c) `/channels` modal sprite count - (d) `/audio-lab` sprite presence - (e) `geofilter-builder.html` control buttons sprite-driven - (f) every `<use>` resolves to a defined symbol id ## Out of scope (M6 cleanup) - cmd/server/routes.go residual server-rendered emoji **not** tied to customize defaults (none found by my grep — file already audited) - `make lint-no-emoji` CI grep gate (M6 owns it) - `public/icons/README.md` workflow doc cross-stack: justified — design call #2 requires Go + JS update together. --------- Co-authored-by: openclaw-bot <bot@openclaw.local>
Phosphor Icon Sprite
This directory holds the vendored Phosphor Icons sprite used by the CoreScope frontend. We do not ship the Phosphor webfont (~150 KB) or fetch icons from a CDN at runtime — every icon used by the UI is bundled here.
File layout
phosphor-sprite.svg— single SVG sprite, one<symbol id="ph-NAME">per icon (regular weight,viewBox="0 0 256 256"to match Phosphor's native grid).
Markup pattern
<svg class="ph-icon" aria-hidden="true" focusable="false">
<use href="/icons/phosphor-sprite.svg#ph-magnifying-glass"></use>
</svg>
CSS helper (defined in public/style.css):
.ph-icon { width: 1em; height: 1em; vertical-align: -0.125em; fill: currentColor; }
Icons inherit color via currentColor and size via the surrounding font-size,
so they re-theme automatically with light/dark mode and CSS variables.
Adding a new icon
- Pull the regular-weight SVG from
https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2.1.1/assets/regular/NAME.svg
(or
assets/fill/NAME.svgfor the rare filled-circle / star-fill cases). - Append a
<symbol id="ph-NAME" viewBox="0 0 256 256">…</symbol>tophosphor-sprite.svg. Strip the outer<svg>wrapper and anyfill=attrs on the inner<path>(we wantcurrentColorfrom the parent). - Reference it with
<use href="/icons/phosphor-sprite.svg#ph-NAME"></use>.
Weight policy
Regular weight only, with two filled exceptions allowed for status dots and
star-favorite (circle-fill, star-fill, square-fill). Bold/duotone are
reserved for a future design pass — do not introduce them ad hoc.
Lint plan (M6)
A make lint-no-emoji target will grep public/** for codepoints in
U+1F300–U+1FAFF, U+2600–U+27BF, U+2700–U+27BF and the misc-symbols set
(◆●■▲★☆○✓✗⚠✉) outside an allowlist (channel-name strings, log/error text,
test fixtures). Until that lands, run the audit script in
scripts/audit-emoji.py (added in #1648).