# 0063 β€” kanban: sistema de stickers (emojis) sobre cards ## APP Metadata | Campo | Valor | |-------|-------| | **ID** | 0063 | | **Estado** | pendiente | | **Prioridad** | media | | **Tipo** | feature β€” `apps/kanban/` | ## Dependencias - Ninguna. Aplica TBD obligatorio (`.claude/rules/apps_tbd.md`): trabajar en `issue/0063-stickers`, merge `--no-ff` a master. ## Objetivo Permitir que el usuario "decore" visualmente las cards del tablero pegandoles emojis (stickers) con transparencia. Util para marcar estados visualmente sin abusar de tags ni colores: πŸ”₯ (urgente), βœ… (revisado), ⚠️ (cuidado), πŸš€ (lanzado), ❀️ (importante), πŸ’€ (bloqueado), etc. ## Contexto `apps/kanban/` es una app Go que embebe un frontend React + Mantine v9 + dnd-kit. El tablero esta en `frontend/src/App.tsx` y cada card se renderiza en `frontend/src/components/KanbanCard.tsx`. Las cards ya tienen color (`color`), tags, asignado, bloqueo, historial y descripcion. Faltan **stickers** como capa decorativa libre, separada del modelo de tags. UX deseada: 1. Usuario hace clic en boton "Stickers" de la toolbar del board (`App.tsx:920`). 2. Aparece picker con lista de emojis predefinidos. 3. Usuario selecciona uno β†’ entra en "modo pegar sticker" (cursor cambia, indicador visible). 4. Hace clic sobre cualquier card β†’ el emoji se pinta encima de la card con opacidad (~0.6). 5. ESC sale del modo. Click derecho sobre un sticker existente lo borra. 6. Stickers persisten en BD para que sobrevivan recargas y todos los usuarios los vean. ## Decisiones de diseno (a confirmar antes de implementar) | Decision | Recomendacion default | Alternativas | |----------|-----------------------|--------------| | Persistencia | BD (campo `stickers JSON` en cards) | localStorage por usuario | | Cantidad por card | Multiples apilados | Uno solo | | Posicion | Arrastrable dentro de la card | Esquina/centrado fijo | | TamaΓ±o / opacidad | Fijos: 48px, 0.6 | Configurables por sticker | | Fuente emojis | Lista hardcoded ~20 | `emoji-mart`, picker nativo | | UX modo activo | Cursor especial + ESC sale | Toggle persistente | | Borrado | Click derecho sobre sticker | Modo "quitar sticker" | Confirmar con usuario antes de Fase 1. Si elige defaults, proceder. ## Arquitectura ### Archivos afectados **Backend Go (`apps/kanban/`):** - `migrations/00X_add_stickers.sql` (NEW) β€” `ALTER TABLE cards ADD COLUMN stickers TEXT NOT NULL DEFAULT '[]'`. - `db.go` β€” leer/escribir columna `stickers` en `Card`. - `handlers.go` β€” endpoint `PATCH /api/cards/:id/stickers` (body: `{"stickers":[{emoji,x,y}]}`). Reusar el patron de `updateCard`. - `tools.go` β€” exponer mutacion al chat agent si aplica (opcional). **Frontend (`apps/kanban/frontend/src/`):** - `types.ts` β€” aΓ±adir `Sticker { emoji: string; x: number; y: number; }` y `Card.stickers: Sticker[]`. - `api.ts` (NEW endpoint helper) β€” `updateCardStickers(id, stickers)`. - `App.tsx` β€” boton "Stickers" en toolbar (`:920`), estado `pickerOpen`, `activeSticker` (emoji seleccionado en modo pegar), handler global ESC, handler `onCardSticker(cardId, x, y)`. - `components/KanbanCard.tsx` β€” render overlay absoluto de `card.stickers` (z-index alto, pointer-events: none salvo en modo borrado), cuando hay `activeSticker` cambiar cursor + capturar onClick para aΓ±adir sticker en coords relativas. - `components/StickerPicker.tsx` (NEW) β€” Popover con grid de emojis predefinidos. **Pure / impure split:** - Pure: util `relativeCoords(event, cardEl) β†’ {x,y}` (frontend, en `components/format.ts` o nuevo `stickers.ts`). - Impure: handler de PATCH HTTP, mutacion de BD, broadcast de cambio si hay realtime. ### Funciones del registry a reutilizar Auditar antes de escribir: ```bash sqlite3 registry.db "SELECT id, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:emoji OR description:emoji OR name:sticker OR description:sticker');" ``` Probable: nada existe. Si `relativeCoords` resulta reusable β†’ delegar a `fn-constructor` y crear `dom_relative_coords_ts_core` o similar (evaluar tras decision UX). ## Tareas ### Fase 1 β€” confirmar diseno 1.1 Confirmar con usuario las 7 decisiones de la tabla. Si elige defaults, seguir. 1.2 Auditar registry: `fn search "emoji"`, `fn search "sticker"`. Documentar reutilizacion en este issue. ### Fase 2 β€” backend 2.1 Crear migracion `migrations/00X_add_stickers.sql`. 2.2 Tipo Go `Sticker { Emoji string; X, Y float64 }` en `db.go`. `Card.Stickers []Sticker` con custom marshalling JSON ↔ TEXT. 2.3 Endpoint `PATCH /api/cards/:id/stickers` en `handlers.go`. Validar payload (emoji no vacio, x/y en [0,1]). 2.4 Aplicar migracion al arrancar (mecanismo existente de `apps/kanban/migrations`). ### Fase 3 β€” frontend tipos + api 3.1 AΓ±adir `Sticker` y `Card.stickers` en `types.ts`. 3.2 AΓ±adir `updateCardStickers(id, stickers)` en `api.ts`. ### Fase 4 β€” UI: picker + modo activo 4.1 Crear `components/StickerPicker.tsx` con lista hardcoded (β‰ˆ20 emojis: πŸ”₯β­βœ…βš οΈπŸš€πŸ’€πŸŽ―β€οΈπŸ‘€πŸ’‘πŸ“ŒπŸ›βœ¨πŸŽ‰πŸ™πŸ€”πŸ˜…πŸš§πŸŸ’πŸ”΄). 4.2 En `App.tsx`: nuevo state `activeSticker: string | null`. Boton "Stickers" con `IconMoodSmile` o similar de `@tabler/icons-react`. Popover con `StickerPicker`. Listener global `keydown` ESC β†’ `setActiveSticker(null)`. 4.3 Indicador visual del modo activo (banner o cursor custom). ### Fase 5 β€” render + interaccion en cards 5.1 En `KanbanCard.tsx`: render overlay absoluto de `card.stickers` (cada uno como `{emoji}`). 5.2 Cuando `activeSticker !== null`, en `onClick` de la Paper de la card calcular coords relativas (`event.offsetX / cardEl.offsetWidth`, idem Y, clamp [0,1]), pushear nuevo sticker a `card.stickers`, llamar `updateCardStickers`, optimistic update. 5.3 Click derecho sobre un sticker β†’ quitar de la lista + persistir. ### Fase 6 β€” tests + verificacion 6.1 `tools_test.go`: extender (o nuevo `stickers_test.go`) con test de PATCH `/api/cards/:id/stickers` (insertar 2, borrar 1, leer card). 6.2 Manual smoke test: boton aparece, picker funciona, sticker se pega, persiste tras F5, click derecho borra, ESC sale del modo. 6.3 `fn doctor uses-functions` no debe regresionar para `kanban_go_tools`. ### Fase 7 β€” docs y cleanup 7.1 Si se reutilizo o creo funcion del registry β†’ declararla en `apps/kanban/app.md` (`uses_functions`). 7.2 Una linea en `apps/kanban/app.md` describiendo la feature. 7.3 `fn index` y verificar `fn show kanban_go_tools`. ## Ejemplo de uso ``` 1. Usuario clica "Stickers" en toolbar del board 2. Picker abre, usuario clica πŸ”₯ 3. Cursor pasa a "modo pegar", banner: "Modo sticker: πŸ”₯ β€” ESC para salir" 4. Usuario clica en card "Migrar usuarios" β†’ πŸ”₯ aparece donde clico, opacidad 0.6, persistido 5. Usuario sigue clicando β†’ mas πŸ”₯ se acumulan 6. Pulsa ESC β†’ modo off, cursor normal 7. F5 β†’ stickers siguen ahi 8. Click derecho sobre πŸ”₯ β†’ desaparece ``` ## Riesgos - **Hit-test confuso**: si los stickers absorben pointer events, rompen drag-and-drop de la card. Mitigacion: `pointerEvents: 'none'` salvo cuando estamos en modo borrado. - **Coords absolutas vs %**: si guardamos px y la card cambia de ancho (resize de columna), el sticker se sale. Guardar como % (`x,y ∈ [0,1]`). - **Coleccion en chat tool agent**: si el chat agent expone tools que hacen `updateCard` con payload completo, debe respetar el campo `stickers` y no sobrescribirlo a null. - **Migracion de BD en prod (laptop + casa)**: la migracion corre en cada arranque idempotente, OK. Pero confirmar antes en prod local del usuario. - **WIP en kanban (issue 0058)**: este issue toca `app.md` igualmente β€” coordinar para no pisar el sync pendiente de `uses_functions`. ## Prerequisitos - Confirmar las 7 decisiones de diseno con el usuario (Fase 1). - TBD: rama `issue/0063-stickers` desde `master` actualizado.