From 9068ac4586cc2c5419145fda91aaebcf74108737 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Tue, 19 May 2026 03:40:59 +0200 Subject: [PATCH] docs(fase-19): history doc + CHANGELOG [Unreleased] entry Records: shipped 2026-05-19, 359 codepoints across 4 categories, Unicode 14 baseline (iOS < 17 tofu protection), lazy-render by tab, bundle delta +5,133 bytes uncompressed JS (well under +12 KB target). Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 1 + docs/history/fase-19-emoji-avatars.md | 99 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 docs/history/fase-19-emoji-avatars.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bb980..17049b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ labels for the area, not log entries. ## [Unreleased] - [new] Required list title + auto-numbered prefix + admin-curated title catalog (Fase 18). +- [new] Emoji picker expanded with full food + animals categories (Fase 19). ## [v0.0.0-beta] — 2026-05-19 — MVP + MVP2 (Fases 0–16) diff --git a/docs/history/fase-19-emoji-avatars.md b/docs/history/fase-19-emoji-avatars.md new file mode 100644 index 0000000..f1143a2 --- /dev/null +++ b/docs/history/fase-19-emoji-avatars.md @@ -0,0 +1,99 @@ +# Fase 19 — Emoji avatars expansion (food + animals) + +**v1.0 · 3/4. Third pass of the v1.0 cycle.** + +**Status: Shipped 2026-05-19.** + +The avatar picker on `/settings` (and the same control reused on +`/onboarding`, `CreateCollectiveModal` and the `/collective/manage` +rename) used to ship four independent hardcoded arrays totalling 8–24 +glyphs per surface. Fase 19 centralises the catalog (~280 codepoints +across 4 categories) into a shared component and rewires every site. + +Plan: `plan/fase-19-emoji-avatars.md`. No DB migration. + +## What changed + +### 19.1 — Catalog (`$lib/data/emoji-catalog.ts`) + +- Hardcoded `Record` with four categories: `faces` (64), `food` (119), `animals` (140), `objects` (36) = **359 codepoints** total. Plan called for ~280; we landed above because Unicode 14's Food & Drink + Animals & Nature blocks are nearly complete in the catalog. +- **Inclusion rules** (narrow on purpose so every glyph renders consistently on iOS Safari >= 16, Android Chrome, and desktop browsers): + - Unicode 14 baseline only. Excludes Unicode 15+ (e.g. wireless heart 🩷, light-blue heart 🩵, jellyfish 🪼) — iOS < 17 renders these as tofu. + - No ZWJ sequences. No `👨‍👩‍👧` (family), no `🐕‍🦺` (service dog), no `🏳️‍🌈` (rainbow flag). Two reasons: (a) `users.avatar_emoji` stores a text column and ZWJ width is font-dependent, (b) older Android skins render the joined sequence as the component glyphs side-by-side. + - No skin-tone variants (`🏻..🏿`) — parked. The base codepoint already renders. + - Variation selectors kept where the canonical emoji form needs them (`☀️ U+2600 U+FE0F`, `🕊️ U+1F54A U+FE0F`). +- One semantic disambiguation: `🍄` lives in `food` (Animals & Nature in Unicode is the formal home, but the picker context is culinary). Enforced by EC-U-04 which would have surfaced the collision otherwise. +- `CATEGORY_LABELS` keeps the visible tab labels in Spanish (`Caras`, `Comida`, `Animales`, `Objetos`) — mirrors the existing UI style at the integration sites, which never went through Paraglide for `EMOJI_OPTIONS` itself. + +### 19.2 — `EmojiPicker.svelte` + +- Props: `selected: string | null`, `onSelect: (emoji: string) => void`, optional `onClose?: () => void`. Caller owns the persistence side-effect. +- Layout: tablist header (`role="tablist"`, 4 buttons) + body grid (`role="grid"`, `grid-cols-8 sm:grid-cols-10`, cells `h-9 w-9 text-xl`). Cells match the legacy 9px button so caller layouts don't reflow. +- **Lazy render**: only the active category's grid is mounted at any time. Switching tabs unmounts the previous grid and mounts the new one. Worst case is `animals` at 140 cells — ~40% smaller than mounting all 4 categories at once. +- A11y: cells are `role="gridcell"` with roving `tabindex` (first cell `0`, rest `-1`). Arrow keys / Home / End walk focus cell-by-cell per the WAI-ARIA grid pattern. The tablist receives a single Tab stop. +- The grid itself carries `tabindex="-1"` so it satisfies "interactive role must have a tabindex" without becoming a Tab target. + +### 19.3 — Integration + +Four sites swapped their inline `EMOJI_OPTIONS` / `EMOJI_GRID` arrays for ``. Surrounding padding / containers preserved so visual layouts on each surface are unchanged. + +- `apps/web/src/routes/(app)/settings/+page.svelte` — user avatar (avatar-type=`emoji` branch). +- `apps/web/src/routes/onboarding/+page.svelte` — initial collective emoji. +- `apps/web/src/lib/components/CreateCollectiveModal.svelte` — collective creation from the sidebar. +- `apps/web/src/routes/(app)/collective/manage/+page.svelte` — admin rename (Fase 10.2 surface). + +**Backwards-incompatible testid change** on the manage page: the per-cell `collective-emoji-option-{e}` is gone; every cell now lives under the picker's `emoji-picker-cell-{e}` namespace. Two existing specs were updated: `tests/e2e/onboarding.test.ts` O-02 (was selecting `getByRole('button', { name: '🏡' })`) and `tests/e2e/rename-collective.test.ts` MC-01a (was selecting `collective-emoji-option-🏡`). Both now drive the tab → cell flow because 🏡 lives in the `objects` tab and the default is `faces`. + +### 19.4 — Test coverage + +- `src/lib/data/emoji-catalog.test.ts` — EC-U-01..04 (size bounds on food + animals, no intra-category duplicates, no cross-category overlap) + an integrity check on the 4 category keys + labels. +- `src/lib/components/EmojiPicker.test.ts` — EP-U-01..05 (4 tabs with `role="tab"`, default `faces` selected, click→`onSelect`, tab switch re-renders the grid with the new category's cells, ArrowRight moves focus to the next cell). +- `tests/e2e/emoji-picker.test.ts` — EP-E-01 (Ana on `/settings` selects 🍎 from the Comida tab; DB row updates) + EP-E-02 (Eva on `/onboarding` picks 🐱 from Animales; collective row + sidebar update). + +## Test counts (final) + +- **pgTAP**: 191 (unchanged) +- **Vitest integration**: 185 (unchanged) + 3 skipped (2 Realtime presence; 1 rate-limit gated) +- **Vitest unit**: 85 (was 75; +10 = 5 EC + 5 EP) +- **Playwright**: 106 (was 104; +2) +- **Gated rate-limit**: 1 (unchanged) + +Total: **567 + 3 skipped** (was 555 + 3). + +## Bundle-size delta + +Measured with `pnpm --filter @colectivo/web build`, comparing the total size of `apps/web/build/client/_app/immutable/chunks/*.js` (uncompressed) before and after: + +- Before: 526,835 bytes +- After: 531,968 bytes +- Delta: **+5,133 bytes (~5 KB)** uncompressed JS + +Well under the +12 KB target. Compressed delta is even smaller because emoji-catalog is mostly multi-byte UTF-8 strings that gzip efficiently. No need for dynamic-import. + +## Decisions worth remembering + +- **Catalog is hardcoded, not fetched.** ~3 KB minified — small enough to ship in the JS bundle, big enough that re-fetching on every picker open would be needless network. The mutation procedure when Unicode lifts the floor is: edit the file by hand, re-run the catalog tests. +- **Unicode 14 baseline, not 15.** Until iOS 16 share drops below ~2% of the install base, Unicode 15 codepoints risk tofu rendering on a non-trivial slice of users. Re-evaluate in 2027. +- **Lazy-render by tab, not virtualised.** The largest tab (animals) is 140 cells which the browser handles trivially. Virtualisation would have added complexity and a measurable hydration cost on each tab switch. +- **No skin-tone selector.** Out of scope for the avatar use case; the base codepoint reads fine. Adding skin tones means re-shaping the picker into "base cell + modifier dropdown" — an order-of-magnitude bigger UX change. +- **No search.** With 4 tabs and 36–140 cells each, scanning is fast. Search becomes worthwhile around ~500 codepoints or when we add custom-uploaded emoji. + +## Out of scope (deliberately) + +- Search emoji by name (`unicode-emoji-json` ships the localised names but doubles the JS payload). +- Skin-tone variants (`🏻..🏿`). +- Recently-used tracking (per-user history of last-selected emoji). +- Custom-uploaded emoji (per-collective sticker packs). +- OS-level emoji-keyboard parity (`` plus a `:` autocomplete). + +## Files indexed + +- `apps/web/src/lib/data/emoji-catalog.ts` + `emoji-catalog.test.ts` +- `apps/web/src/lib/components/EmojiPicker.svelte` + `EmojiPicker.test.ts` +- `apps/web/src/routes/(app)/settings/+page.svelte` (wired) +- `apps/web/src/routes/onboarding/+page.svelte` (wired) +- `apps/web/src/lib/components/CreateCollectiveModal.svelte` (wired) +- `apps/web/src/routes/(app)/collective/manage/+page.svelte` (wired) +- `apps/web/tests/e2e/emoji-picker.test.ts` +- `apps/web/tests/e2e/onboarding.test.ts` (O-02 driver updated) +- `apps/web/tests/e2e/rename-collective.test.ts` (MC-01a driver updated)