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 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ labels for the area, not log entries.
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- [new] Required list title + auto-numbered prefix + admin-curated title catalog (Fase 18).
|
- [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)
|
## [v0.0.0-beta] — 2026-05-19 — MVP + MVP2 (Fases 0–16)
|
||||||
|
|
||||||
|
|||||||
99
docs/history/fase-19-emoji-avatars.md
Normal file
99
docs/history/fase-19-emoji-avatars.md
Normal file
@@ -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<EmojiCategory, readonly string[]>` 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 `<EmojiPicker selected={…} onSelect={…} />`. 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 (`<input type="text">` 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)
|
||||||
Reference in New Issue
Block a user