eleven / ocho Public
π€ Icons β one system across web and native
Three kinds of mark, three sources, one rule about emoji.
The three kinds
- 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.
- 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 needsfill-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. - 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 itslogo.
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 viainnerHTMLwith these constants β never user content.web/static/logos.jsβ the brand marks, same shape.- Apps carry
logo:besideglyph:indefineApp;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 asICON_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 | | Reply in thread | ICON_THREAD (messages-square) | bubble.left.and.bubble.right | | Threads list | ICON_THREAD (messages-square) | bubble.left.and.bubble.right (iOS kebab; the Mac kebab's rows are text-only β see below) | | Convert to thread | ICON_FOLD_IN (fold-vertical) | arrow.down.right.and.arrow.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.
One row of the table is ahead of native: Reply in thread. The web strip and menus now draw ICON_THREAD, but the native message menus still label a second arrowshape.turn.up.left (StockChatFeed.swift, MessageBubble.swift) β tolerable there because those rows carry text, whereas the web hover strip is icon-only and two arrows read as one control drawn twice. Swap them to bubble.left.and.bubble.right on the next native touch.
The Threads list follows the same platform split as every other kebab row: the web's β€ menu and the iOS β― menu draw the icon (iOS menu rows are Labels on one icon grid), while the Mac header kebab is text-only rows by construction β nothing else in it carries a glyph either.
Convert to thread carries a glyph on exactly two of its four surfaces: the web's picker (icon-only rows, so it needs one) and the iOS long-press sheet. The iOS context menu and the Mac NSMenu are text-only rows by construction, so there is nothing to map there.