docs: add icon migration reference

Record the Tabler pack choice, icon conventions, and the per-file
glyph-to-icon mapping for the unicode-to-solid-icons migration.
This commit is contained in:
2026-09-13 14:01:10 +08:00
parent 40b6fbe157
commit 49bed7d279
+191
View File
@@ -0,0 +1,191 @@
# Icon migration — unicode/emoji → `solid-icons`
**Status: implemented.** This document is both the audit and the record of the
migration. It lists the pack/conventions used and the per-file mapping.
## Goal
Replace ad-hoc unicode symbols and emoji in **UI chrome** with real SVG icon
components from [`solid-icons`](https://solid-icons.vercel.app), so icons are
consistent, themeable (`currentColor`, `size`), and render identically across
platforms (emoji fonts vary wildly between OSes).
## Scope
**In scope — UI chrome** (`src/components/**`, `src/App.tsx`, doc-entry
frontmatter, `RevealManager` inline SVGs, the editor-panel align PNGs).
**Out of scope — left as-is:**
| Thing | Why |
|---|---|
| `:[icon-name]` content directive (`src/markdown/icon.ts`, `assets/`) | Game-content icons resolved from user `assets/` dirs via CSS `background-image`. Not UI chrome. |
| CLI console output (`✅ ❌ 📦` in `src/cli/**`) | Terminal output, not a browser. `solid-icons` is a SolidJS/browser library. |
| Emoji in `README.md`, `docs/*.md` prose | Documentation text, not rendered UI. |
| Dimension text `×` in `PltPreview` / `PrintPreviewHeader` | A multiplication sign in "100mm × 50mm", not an icon. |
| `content/**` sample content | User-authored data. |
| `PltPreview.tsx` / `PrintPreview.tsx` `<svg>` | Rendered card/page artwork, not icons. |
## Pack
**Tabler** (`solid-icons/tb`). `solid-icons@1.2.0` does **not** ship Lucide; the
available packs are `ai, bi, bs, cg, fa, fi, hi, im, io, md, oc, ri, si, tb, ti,
vs, wi`. Tabler was chosen because it is stroke-based, uses `currentColor`, and
covers everything needed (dice, ruler, grip, alignment, arrows, …).
> Exports are prefixed `TbOutline*` (outline) and `TbFill*` (filled). Always use
> `TbOutline*`.
## Conventions
```tsx
import { TbOutlineFolderOpen, TbOutlineX } from "solid-icons/tb";
<TbOutlineFolderOpen size={16} class="text-gray-400" />
```
- `size?: string | number` defaults to `"1em"`; `color` defaults to
`currentColor`. Icons therefore inherit the surrounding font-size and text
color, so existing Tailwind text-color/size classes keep working.
- **Inline alignment:** an inline SVG sits on the text baseline, so icons placed
inline next to text get `class="inline-block align-[-0.125em]"`. Icons inside
a `flex`/`inline-flex` container don't need it.
- **Fixed-size buttons** get an explicit `size` (`16` for `w-7 h-7`/`w-8 h-8`,
`14` for `w-5 h-5`/`w-6 h-6`).
- Text labels next to a glyph (e.g. `📥 导出 PDF`) are preserved; only the glyph
is replaced.
## Shared modules
- **`src/components/doc-icons.ts`** — maps doc-entry frontmatter `icon:` keys
(e.g. `dice`, `table`) to Tabler components. Frontmatter is YAML, so it can't
hold a component; it stores a semantic key instead. `docIcon(key)` falls back
to `TbOutlineFile`.
- **`CompletionItem.icon`** (`src/components/journal/command-parser.ts`) — an
optional `IconTypes` rendered by `CompletionsDropdown.tsx` before the label,
so completion entries carry real icons instead of emoji in the label string.
## Inventory
### Shell / navigation
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/App.tsx` | `☰` | mobile menu | `TbOutlineMenu2` |
| `src/App.tsx` | `📋` | journal panel toggle | `TbOutlineClipboardList` |
| `src/App.tsx` | `?` | docs/help | `TbOutlineHelpCircle` |
| `src/components/Sidebar.tsx` | `📂` | content source | `TbOutlineFolderOpen` |
| `src/components/Sidebar.tsx` | `✕` | close sidebar | `TbOutlineX` |
| `src/components/FileTree.tsx` | `📂` / `📁` | folder open / closed | `TbOutlineFolderOpen` / `TbOutlineFolder` |
| `src/components/FileTree.tsx` | `📄` | file | `TbOutlineFile` |
| `src/components/FileTree.tsx` | `▶` | expand chevron | `TbOutlineChevronRight` |
| `src/components/DocDialog.tsx` | `▾` | dropdown caret | `TbOutlineChevronDown` |
| `src/components/DocDialog.tsx` | `📖` | directives doc set | `TbOutlineBook2` |
| `src/components/DocDialog.tsx` | `✓` | selected doc set | `TbOutlineCheck` |
| `src/components/DocDialog.tsx` | `📋` | journal doc set | `TbOutlineClipboardList` |
| `src/components/DocDialog.tsx` | `✕` | close dialog | `TbOutlineX` |
| `src/components/DocDialog.tsx` | entry `icon:` | doc entry icon | `<Dynamic component={docIcon(entry.icon)} />` |
### md-* components
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/md-dice.tsx` | `🎲` | dice reset | `TbOutlineDice5` |
| `src/components/md-table.tsx` | `🎲` | random row | `TbOutlineDice5` |
| `src/components/md-pins.tsx` | `📋` | copy pin coords | `TbOutlineCopy` |
| `src/components/md-yarn-spinner.tsx` | `🔄` | restart | `TbOutlineRotate` |
| `src/components/md-yarn-spinner.tsx` | `⏩` | advance | `TbOutlinePlayerTrackNext` |
| `src/components/md-yarn-spinner.tsx` | `→` | option bullet | `TbOutlineArrowRight` |
### Journal
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/journal/ConnectDialog.tsx` | `🗑` | delete session | `TbOutlineTrash` |
| `src/components/journal/CreateSessionDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/DeleteSessionDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/ErrorPopup.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/InviteDialog.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/journal/JournalHeader.tsx` | `←` | back to session list | `TbOutlineArrowLeft` |
| `src/components/journal/JournalHeader.tsx` | `▸` | hide panel | `TbOutlineLayoutSidebarRightCollapse` |
| `src/components/journal/SessionDropdown.tsx` | `▾` | dropdown caret | `TbOutlineChevronDown` |
| `src/components/journal/SessionDropdown.tsx` | `✓` | active session | `TbOutlineCheck` |
| `src/components/journal/SessionDropdown.tsx` | `🗑` | delete session | `TbOutlineTrash` |
| `src/components/journal/StreamMessage.tsx` | `×` | revert message | `TbOutlineArrowBackUp` |
| `src/components/journal/types/intent.tsx` | `🙋` | intent | `TbOutlineHandStop` |
| `src/components/journal/types/intent.tsx` | `✅` | verdict | `TbOutlineCircleCheck` |
| `src/components/journal/types/link.tsx` | `📄` | link | `TbOutlineFileText` |
| `src/components/journal/types/roll.tsx` | `🎲` | roll | `TbOutlineDice5` |
| `src/components/journal/types/roll.tsx` | `→` | notation → total | `TbOutlineArrowRight` |
| `src/components/journal/types/var.tsx` | `🔢` | variable | `TbOutlineHash` |
| `src/components/journal/command-completions.ts` | `🎲` | dice completion | `TbOutlineDice5` (via `item.icon`) |
| `src/components/journal/command-completions.ts` | `✨` | spark completion | `TbOutlineSparkles` (via `item.icon`) |
### md-deck
| File | Glyph | Purpose | Icon |
|---|---|---|---|
| `src/components/md-deck/DeckHeader.tsx` | `✓` / `✏️` | editing toggle | `TbOutlineCheck` / `TbOutlinePencil` |
| `src/components/md-deck/DeckHeader.tsx` | `📥` | export PDF | `TbOutlineDownload` |
| `src/components/md-deck/DeckHeader.tsx` | `📋` | copy code | `TbOutlineCopy` |
| `src/components/md-deck/PltPreview.tsx` | `📥` | download PLT | `TbOutlineDownload` |
| `src/components/md-deck/PrintPreviewFooter.tsx` | `❌` | export error | `TbOutlineCircleX` |
| `src/components/md-deck/PrintPreviewFooter.tsx` | `✕` | close | `TbOutlineX` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `➕` | add deck | `TbOutlinePlus` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `📐` | PLT preview | `TbOutlineRuler` |
| `src/components/md-deck/PrintPreviewHeader.tsx` | `📥` | export PDF | `TbOutlineDownload` |
| `src/components/md-deck/CardList.tsx` | `‹` | previous page | `TbOutlineChevronLeft` |
| `src/components/md-deck/CardList.tsx` | `›` | next page | `TbOutlineChevronRight` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `⠿` | drag handle | `TbOutlineGripVertical` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `👁` / `👁‍🗨` | visible / hidden | `TbOutlineEye` / `TbOutlineEyeOff` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `↑ → ↓ ←` | orientation | `TbOutlineArrowUp` / `TbOutlineArrowRight` / `TbOutlineArrowDown` / `TbOutlineArrowLeft` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `↖ ↑ ↗ ↙ ↓ ↘` | alignment | `TbOutlineArrowUpLeft` / `TbOutlineArrowUp` / `TbOutlineArrowUpRight` / `TbOutlineArrowDownLeft` / `TbOutlineArrowDown` / `TbOutlineArrowDownRight` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `✕` | delete layer | `TbOutlineTrash` |
| `src/components/md-deck/editor-panel/LayerRow.tsx` | `align-*.png` | align presets | `TbOutlineAlignLeft` / `TbOutlineAlignCenter` / `TbOutlineAlignRight` |
The three `align-*.png` assets were deleted; the `icons/` directory is gone.
### `RevealManager.tsx` (formerly inline SVG strings)
| Former | Purpose | Icon |
|---|---|---|
| `LINK_SVG` | floating `/link` action | `TbOutlineLink` |
| `SPARK_SVG` | floating `/roll` spark action | `TbOutlineSparkles` |
`FloatingButton.svg: string` (rendered via `innerHTML`) became
`FloatingButton.icon: IconTypes`, rendered as `<Dynamic component={icon} />`
children.
### `src/doc-entries/*.md` frontmatter `icon:`
| File / tag | Key |
|---|---|
| `journal-gm.md` | `gm` |
| `journal-link.md` | `link` |
| `journal-player.md` | `player` |
| `journal-roll.md` | `roll` |
| `journal-set.md` | `set` |
| `journal-spark.md` | `spark` |
| `md-bg.md` | `background` |
| `md-border.md` | `border` |
| `md-deck.md` | `deck` |
| `md-dice.md` | `dice` |
| `md-embed.md` | `embed` |
| `md-font.md` | `font` |
| `md-link.md` | `link` |
| `md-pins.md` | `pins` |
| `md-table.md` | `table` |
| `md-yarn-spinner.md` | `yarn` |
Keys are resolved through `docIcon()` in `src/components/doc-icons.ts`.
## Follow-ups / not migrated (by design)
- Doc-entry **body prose** that referenced a replaced button glyph was reworded
to name the button (e.g. `md-dice.md` now says "点击骰子图标"). Remaining emoji
in `src/doc-entries/*.md` are illustrative — coloured status dots
(`🟢🟡🔴⚪` in `journal-gm.md`) — not UI icons.
- CLI console / MCP output (`✅ ❌ 📦` in `src/cli/**`) keeps its emoji: it is
terminal text, not rendered UI.
- `command-completions.ts` `CompletionItem` now carries an optional `icon`; any
future completion source can use it.