# 🤖 Icons — one system across web and native

Three kinds of mark, three sources, one rule about emoji.

## The three kinds

1. **UI chrome** (carets, the kebab, message actions, app marks for
   first-party apps): **Lucide** on web (https://lucide.dev, ISC — vendored,
   never a CDN), **SF Symbols** on iOS/Mac (built into Apple platforms;
   their license keeps them off the web, which is why one literal library
   everywhere isn't possible). The table below keeps the two in step.
2. **Brand marks** (the integration apps): real logos, vendored monochrome in
   `web/static/logos.js` — Simple Icons (CC0) where available, an official
   asset otherwise (DNSimple's mask icon — note it needs
   `fill-rule="evenodd"` or the knockout fills solid). Brands have no SF
   Symbols; native gets the same SVGs as template images in the asset
   catalogs, tinted like SF.
3. **Emoji** — **content, never chrome.** An emoji is right where a person or
   a model *chose* it: a member bot's title ("RTE News Hourly 📺"), a member
   app's glyph, a view's glyph, reactions. Interface furniture never uses
   emoji. Every first-party app still declares an emoji `glyph` — it is the
   cross-surface fallback (native command palette, string contexts) — but
   web surfaces render its `logo`.

## How it's wired (web)

- `web/static/icons.js` — every chrome icon as a static in-code SVG string
  (24×24 viewBox, `currentColor`, stroke idiom). Only ever injected via
  `innerHTML` with these constants — never user content.
- `web/static/logos.js` — the brand marks, same shape.
- Apps carry `logo:` beside `glyph:` in `defineApp`; `appGlyphInto(el, app)`
  (apps.js) renders logo-else-glyph everywhere app identity draws.
- The Views button's caret is the one inline SVG in `web/pages/index.html`
  (static markup; same chevron as `ICON_CHEVRON_DOWN`).

**Adding an icon** = vendor the Lucide SVG into `icons.js` (keep the
`(Lucide: <slug>)` comment), add a row here with its SF Symbol, use it.

## The mapping

| Meaning | icons.js (Lucide slug) | SF Symbol (native) |
|---|---|---|
| Reply | `ICON_REPLY` (hand-drawn, pre-Lucide) | `arrowshape.turn.up.left` |
| Close / dismiss | `ICON_X` | `xmark` |
| Edit | `ICON_EDIT` | `pencil` |
| React | `ICON_REACT` | `face.smiling` |
| Delete | `ICON_TRASH` | `trash` |
| Play | `ICON_PLAY` | `play.fill` |
| Pinned chat | `ICON_PIN` | `pin.fill` |
| Muted chat | `ICON_MUTE` | `bell.slash` |
| Report | `ICON_REPORT` | `flag` |
| Copy | `ICON_COPY` | `doc.on.doc` |
| Dropdown caret | `ICON_CHEVRON_DOWN` (chevron-down) | `chevron.down` |
| Back | `ICON_CHEVRON_LEFT` (chevron-left) | `chevron.left` |
| Kebab | `ICON_MORE_VERTICAL` (ellipsis-vertical) | `ellipsis` |
| Memory | `ICON_BRAIN` (brain) | `brain` |
| To-do | `ICON_SQUARE_CHECK` (square-check) | `checkmark.square` |
| Reminders | `ICON_ALARM_CLOCK` (alarm-clock) | `alarm` |
| Bookmarks | `ICON_BOOKMARK` (bookmark) | `bookmark` |
| Poll | `ICON_CHART_COLUMN` (chart-column) | `chart.bar` |
| Board | `ICON_PRESENTATION` (presentation) | `rectangle.on.rectangle` |
| Vault | `ICON_LOCK` (lock) | `lock` |
| Emoji picker | `ICON_SMILE` (smile) | `face.smiling` |
| GIF | `ICON_FILM` (film) | `film` |
| Admin | `ICON_WRENCH` (wrench) | `wrench.adjustable` |
| App Studio | `ICON_PUZZLE` (puzzle) | `puzzlepiece` |

Brand marks (`logos.js`): GitHub, DNSimple, Stripe, HubSpot, Linear
(awaiting the linear app), plus Lucide's `plug` for the Integrations app
itself (a capability, not a brand).

## Native status

Built (`ios/LChatCore/Sources/LChatCore/AppIdentity.swift`): the appId →
SF-Symbol map above plus the brand marks as vendored template PNGs in
`LChatCore/Marks/` (the same SVGs as `logos.js`, rasterized
black-on-transparent at 96px, tinted at render — regenerate with resvg,
remembering the vendored strings need an `xmlns` added). Wired into both
platforms' avatar ladders (photo → mark → symbol → glyph → initials): the
iOS thread list/search/share rows, the Mac sidebar, rail, feed header,
search, ⌘K palette, and both New-chat pickers. The old "▦" placeholder is
gone — unmapped apps get `square.grid.2x2`. Device verify + the release
changelog ride the next native release mint.
