docs(plan): add Fase 16 — loading spinner (MVP2 reopened to 8 fases)
Adds a tiny planned-phase doc for visual feedback during async operations. - Single SVG-circle Spinner.svelte component, three sizes (sm/md/lg = 16 / 24 / 40 px), color via currentColor (theme-aware), motion-reduce-aware, role="status" + sr-only m.loading() for a11y. - Integration at 5 existing sites that already render m.loading() text or own a `loading` flag: auth splash, /search, /notes/archive, CreateCollectiveModal "creating" state, /collective/manage sub-sections. - 5 unit + 3 e2e tests planned. No DB migration. No new Paraglide strings. Rebrand: MVP2 reopens from 7/7 ✅ to in-progress 7/8 ✅. Headers in fases 9–15 bump "·N/7" + "fases 9 → 15" to "·N/8" + "fases 9 → 16". README + CLAUDE.md updated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
### Fase 10 — Spec completion ("cerrar la laguna del `analisis-funcional`")
|
||||
|
||||
**MVP2 · 2/7.** Segunda fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 2/8.** Segunda fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: ✅ Completa (2026-05-18). Cierra las 8 piezas de "completion debt" del audit 2026-04-22. Migraciones 017–021, pgTAP 010–013, 6 specs E2E + 2 integración + 12 unit. Ver `docs/history/fase-10-spec-completion.md`.**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 11 — Item tags + importance UX
|
||||
|
||||
**MVP2 · 3/7.** Tercera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 3/8.** Tercera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: 📋 Planificada. Cierra dos huecos en la experiencia del item de compra: clasificación libre por tags (scope colectivo) y reordenación explícita por arrastrar (importance).**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 12 — Section visibility (feature toggles user + colectivo)
|
||||
|
||||
**MVP2 · 4/7.** Cuarta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 4/8.** Cuarta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: 📋 Planificada. Permite a cada usuario ocultar secciones top-level que no usa, y a admins de colectivo ocultarlas para todo el colectivo. Pre-requisito para Fase 13 (server admin reusa el mismo modelo a nivel servidor).**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 13 — Server administration
|
||||
|
||||
**MVP2 · 5/7.** Quinta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 5/8.** Quinta fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: 📋 Planificada. Mayor salto de scope hasta ahora — introduce un rol global ortogonal a los roles de colectivo, un área `/admin` nueva, RPCs con privilegios elevados, y un audit log. Requiere Fase 12 para los toggles de visibilidad a nivel servidor.**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 14 — PWA hardening (auto-update + offline polish + version chip)
|
||||
|
||||
**MVP2 · 6/7.** Última fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 6/8.** Última fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: 📋 Planificada. Pulido del PWA que Fase 6 dejó funcional pero crudo. Sin migraciones de DB. Depende de Fase 9.3 (wiring de `sync_conflicts`) para el badge de conflictos.**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 15 — Common items management (weighted suggestions + exclude-on-list)
|
||||
|
||||
**MVP2 · 7/7.** Añadida después del cierre de MVP2 (2026-05-18) para integrar dos peticiones concretas sobre items frecuentes; reabre el ciclo con una fase extra.
|
||||
**MVP2 · 7/8.** Añadida después del cierre de MVP2 (2026-05-18) para integrar dos peticiones concretas sobre items frecuentes; reabre el ciclo con una fase extra.
|
||||
|
||||
**Estado: 📋 Planificada.** Atiende dos peticiones concretas sobre el flujo "añadir item a una lista":
|
||||
|
||||
|
||||
122
plan/fase-16-spinner.md
Normal file
122
plan/fase-16-spinner.md
Normal file
@@ -0,0 +1,122 @@
|
||||
### Fase 16 — Loading spinner (visual feedback)
|
||||
|
||||
**MVP2 · 8/8.** Añadida después del cierre de Fase 15 para cubrir la falta de feedback visual durante operaciones asíncronas.
|
||||
|
||||
**Estado: 📋 Planificada.** Atiende una petición concreta: cuando algo carga, hoy se muestra solo texto `Loading…`. Reemplazar por un círculo animado simple y reutilizable.
|
||||
|
||||
**Objetivo: un componente `Spinner` minimal (SVG circle + animación CSS), integrado en los 5 sitios actuales que ya muestran `m.loading()` o un estado `loading`. Nada de skeletons, nada de transiciones complejas. La UX es: si tarda >100ms, hay un círculo girando. Punto.**
|
||||
|
||||
---
|
||||
|
||||
**Estado actual (auditado 2026-05-18):**
|
||||
- `apps/web/src/routes/(app)/+layout.svelte:151` — splash de auth con `<span>{m.loading()}</span>`, sin indicador visual.
|
||||
- `apps/web/src/routes/(app)/search/+page.svelte:122` — texto plano `m.loading()` durante búsqueda.
|
||||
- `apps/web/src/routes/(app)/notes/archive/+page.svelte` — `let loading = $state(true)`, sin spinner.
|
||||
- `apps/web/src/lib/components/CreateCollectiveModal.svelte:109` — botón muestra `m.loading()` cuando `creating=true`, sin spinner.
|
||||
- Stores con flag dedicado: `authLoading`, `notesLoading`, `tagsLoading`, `commonItemsLoading`. Buena base para integración futura.
|
||||
|
||||
**Decisiones clave:**
|
||||
- **SVG + CSS keyframes**, no dependencias. Stroke-dasharray con animación `rotate(360deg)` infinita.
|
||||
- Props: `size = 'sm' | 'md' | 'lg'` (16 / 24 / 40 px), `class = ''` para Tailwind overrides. Color hereda `currentColor` para integrar con el theme system (Fase 9).
|
||||
- Render bloque: `<div role="status" aria-live="polite">` con `<span class="sr-only">{m.loading()}</span>` para a11y.
|
||||
- Sin `prefers-reduced-motion`: la animación queda pausada (animation: none) para usuarios que lo prefieran.
|
||||
- Sin delay de aparición ("solo si >100ms"); el componente aparece cuando lo monta el llamador. Si UX se queja, añadir prop `delay` después.
|
||||
|
||||
---
|
||||
|
||||
#### 16.1 Componente
|
||||
|
||||
**Fichero:** nuevo `apps/web/src/lib/components/Spinner.svelte`.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import * as m from '$lib/paraglide/messages';
|
||||
|
||||
interface Props {
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
class?: string;
|
||||
}
|
||||
let { size = 'md', class: extra = '' }: Props = $props();
|
||||
|
||||
const px = size === 'sm' ? 16 : size === 'lg' ? 40 : 24;
|
||||
</script>
|
||||
|
||||
<div role="status" aria-live="polite" class={extra}>
|
||||
<svg
|
||||
data-testid="spinner"
|
||||
width={px}
|
||||
height={px}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
class="animate-spin motion-reduce:animate-none"
|
||||
>
|
||||
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"
|
||||
stroke-linecap="round" stroke-dasharray="42 14" opacity="0.75" />
|
||||
</svg>
|
||||
<span class="sr-only">{m.loading()}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Tareas:**
|
||||
|
||||
- [ ] **16.1.1** Crear `apps/web/src/lib/components/Spinner.svelte`.
|
||||
- [ ] **16.1.2** Test unit `apps/web/src/lib/components/Spinner.test.ts`:
|
||||
- SP-U-01 default size renders 24x24 SVG.
|
||||
- SP-U-02 `size='sm'` → 16x16; `size='lg'` → 40x40.
|
||||
- SP-U-03 `role="status"` + `aria-live="polite"` presentes.
|
||||
- SP-U-04 sr-only message reads from Paraglide `m.loading()`.
|
||||
- SP-U-05 extra class prop forwarded a `<div>` raíz.
|
||||
|
||||
---
|
||||
|
||||
#### 16.2 Integraciones
|
||||
|
||||
**Ficheros:**
|
||||
- `apps/web/src/routes/(app)/+layout.svelte` (splash de auth)
|
||||
- `apps/web/src/routes/(app)/search/+page.svelte` (búsqueda)
|
||||
- `apps/web/src/routes/(app)/notes/archive/+page.svelte` (carga inicial)
|
||||
- `apps/web/src/lib/components/CreateCollectiveModal.svelte` (botón creating)
|
||||
- `apps/web/src/routes/(app)/collective/manage/+page.svelte` (sub-secciones "Items frecuentes" + "Etiquetas" cuando `commonItemsLoading` o `tagsLoading`)
|
||||
|
||||
**Tareas:**
|
||||
|
||||
- [ ] **16.2.1** En cada uno, reemplazar el texto `m.loading()` por `<Spinner size="sm" /> <span>{m.loading()}</span>` (mantener el texto como fallback para SR + tooltip).
|
||||
- [ ] **16.2.2** Splash de auth: `<Spinner size="lg" />` centrado, con label opcional.
|
||||
- [ ] **16.2.3** Botón modal `creating`: spinner inline (`size="sm"`) antes del texto.
|
||||
- [ ] **16.2.4** No tocar otros sitios sin estado de loading (no añadir loading donde antes no había).
|
||||
|
||||
---
|
||||
|
||||
#### 16.3 Tests E2E
|
||||
|
||||
**Fichero:** nuevo `apps/web/tests/e2e/spinner.test.ts`.
|
||||
|
||||
- [ ] **16.3.1** **SP-E-01** En `/search`, escribir consulta lenta (throttle red en `route.fulfill` con delay 500ms) → `[data-testid=spinner]` visible mientras carga.
|
||||
- [ ] **16.3.2** **SP-E-02** En `/collective/manage` (admin), abrir modal "Add common item", clickar `Save` con red throttleada → spinner visible en botón hasta resolución.
|
||||
- [ ] **16.3.3** **SP-E-03** Auth splash: navegar a `/lists` sin sesión → spinner visible mientras Supabase resuelve la sesión.
|
||||
|
||||
---
|
||||
|
||||
#### 16.Z Verificación + cierre
|
||||
|
||||
- [ ] **16.Z.1** `just test-all` verde. Suma esperada: +5 unit + 3 e2e = +8.
|
||||
- [ ] **16.Z.2** A11y manual: VoiceOver / NVDA leen el sr-only `m.loading()`. Motion reduce respetada.
|
||||
- [ ] **16.Z.3** Smoke visual en dark mode (Fase 9) — `currentColor` hereda bien.
|
||||
- [ ] **16.Z.4** Documentar en `docs/history/fase-16-spinner.md`.
|
||||
- [ ] **16.Z.5** No nuevas strings Paraglide (reutiliza `m.loading()` existente).
|
||||
|
||||
---
|
||||
|
||||
### Riesgos / notas
|
||||
|
||||
- **Animación a 60fps en mobile bajo carga** — un `transform: rotate` con `will-change: transform` es barato; no debería ser un problema. Si lo es, bajar a 30fps con `animation-duration: 2s` en lugar de `1s` (Tailwind default `animate-spin` es 1s).
|
||||
- **CLS (Cumulative Layout Shift)** — al reemplazar texto por spinner+texto, el ancho cambia. Si afecta a Lighthouse, fijar `min-width` del contenedor.
|
||||
- **`motion-reduce:animate-none`** requiere Tailwind v3+. Repo ya lo usa.
|
||||
|
||||
### Scope explícito fuera
|
||||
|
||||
- Skeletons (placeholder con shape del contenido). Solo spinner.
|
||||
- Progress bars determinísticas. Solo indeterminado.
|
||||
- Toasts/notifications de "loading". Solo inline.
|
||||
- Spinner overlay full-screen. Solo inline en el sitio del trigger.
|
||||
- Integración en stores que aún no tienen flag dedicado (lists, tasks). Fuera de scope hasta que se pida.
|
||||
@@ -1,6 +1,6 @@
|
||||
### Fase 9 — Polish + Dark Mode
|
||||
|
||||
**MVP2 · 1/7.** Primera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 15).
|
||||
**MVP2 · 1/8.** Primera fase del ciclo MVP2 (rama `mvp2`, fases 9 → 16).
|
||||
|
||||
**Estado: ✅ Completada (2026-05-18). Ver `docs/history/fase-9-polish-dark-mode.md` para detalles + paleta definitiva. 41 tests nuevos: 8 user_theme pgTAP, 12 sync_conflicts pgTAP, 4 sync-conflicts integración, 3 SC unit, 8 theme unit, 3 theme E2E, 3 reading-width E2E. WebKit specs (2 nuevos) gated en `RUN_WEBKIT=1`.**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user