Record the Tabler pack choice, icon conventions, and the per-file glyph-to-icon mapping for the unicode-to-solid-icons migration.
192 lines
10 KiB
Markdown
192 lines
10 KiB
Markdown
# 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.
|