|
|
|
|
|
# PLAN — Tier `blocks`: composición reutilizable (infraestructura + componentes base + catálogo)
|
|
|
|
|
|
|
|
|
|
|
|
> **Kickoff para sesión nueva**: *"Lee `docs/process/PLAN-blocks.md` y continúa
|
|
|
|
|
|
> la fase que toque."* Decisión de usuario (2026-07-21): existe un tier nuevo
|
|
|
|
|
|
> **`blocks`** — conjuntos de componentes desempeñando una función (cabecera
|
|
|
|
|
|
> sticky, hero, footer, app-shell…). Este plan es autosuficiente: cada fase
|
|
|
|
|
|
> lista QUÉ leer, QUÉ producir y CON QUÉ guard se verifica. Un agente no debe
|
|
|
|
|
|
> descubrir la doctrina por arqueología — este documento la enlaza toda.
|
|
|
|
|
|
>
|
|
|
|
|
|
> **Antes de escribir código de F0**: presentar al usuario las decisiones
|
|
|
|
|
|
> D-BLK de §2 (AskUserQuestion o tabla en chat) y obtener firma. Las
|
|
|
|
|
|
> propuestas de este plan son eso — propuestas razonadas, no decisiones
|
|
|
|
|
|
> tomadas.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 0. Contexto y estado
|
|
|
|
|
|
|
|
|
|
|
|
- **Origen**: análisis del ecosistema (sesión 2026-07-21). Diagnóstico: el
|
|
|
|
|
|
catálogo de primitivas es excepcional (~140 componentes eidos, ~95 con soma);
|
|
|
|
|
|
la brecha está en (a) piezas de contenido/estado de página y (b) el nivel de
|
|
|
|
|
|
composición. Iniciativa registrada en `docs/next-features.md` §8.
|
|
|
|
|
|
- **Precedentes en el repo**: la familia `chat-*` es un "bloque" construido
|
|
|
|
|
|
como componentes canónicos (siguió la ruta de 9 fases porque cada pieza
|
|
|
|
|
|
tiene contrato real); `picker-shell` es un chasis compartido; el tier
|
|
|
|
|
|
`packs` (`docs/architecture/packs.md`) ya resolvió la pregunta "¿cómo vive
|
|
|
|
|
|
un tier fuera del canon?" — este plan lo usa de espejo.
|
|
|
|
|
|
- **Estado**: F0 pendiente (nada construido). Actualizar esta tabla al cerrar
|
|
|
|
|
|
cada tanda, estilo `PLAN-component-coherence.md`.
|
|
|
|
|
|
|
|
|
|
|
|
| Fase | Contenido | Estado |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| **F0** | Infraestructura del tier: doctrina + alias + guard + rutas demo | **HECHA 2026-07-21** (F0.1–F0.7; cross-ref en `comparison.md` omitido a propósito — sin aporte hasta que exista catálogo) |
|
|
|
|
|
|
| **F1** | 7 componentes base del CANON que los blocks necesitan | pendiente |
|
|
|
|
|
|
| **F2** | Blocks de sitio (10) | pendiente (F2 solo requiere F1.1) |
|
|
|
|
|
|
| **F3** | Blocks de aplicación (10) | pendiente |
|
|
|
|
|
|
| **F4** | Blocks de docs (3) | pendiente |
|
|
|
|
|
|
| **F5** | Backlog condicionado (componentes media/mobile + blocks diferidos) | pendiente |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Qué es un block (doctrina propuesta — aterriza en `docs/architecture/blocks.md` en F0)
|
|
|
|
|
|
|
|
|
|
|
|
Un **block** es una composición nombrada de componentes del canon que
|
|
|
|
|
|
desempeña una función de página: no aporta primitivas nuevas, aporta
|
|
|
|
|
|
*ensamblaje correcto* (layout, landmarks, jerarquía de headings, responsive,
|
|
|
|
|
|
puntos de contenido). Consume el framework; el framework nunca lo referencia.
|
|
|
|
|
|
|
|
|
|
|
|
La tabla de tiers queda:
|
|
|
|
|
|
|
|
|
|
|
|
| Tier | Valor | Contrato | Entra por |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| **Canon** (`src/uix/`) | densidad de contrato (eventos, ARIA, teclado, tokens que otros consumen) | morfo + matriz de aceptación | ruta de 9 fases (`docs/building-a-component.md`) |
|
|
|
|
|
|
| **Packs** (`src/packs/`) | decoración parametrizada de hoja | contrato P | `packs-check` |
|
|
|
|
|
|
| **Blocks** (`src/uix/blocks/`) | composición de función de página | contrato B (§3) | `blocks-check` |
|
|
|
|
|
|
|
|
|
|
|
|
**Regla de admisión (espejo de la de packs)**: si al construir un block hace
|
|
|
|
|
|
falta comportamiento nuevo con superficie de contrato — un evento real, una
|
|
|
|
|
|
máquina de estados, un `data-attr` que el CSS necesita seleccionar, una
|
|
|
|
|
|
obligación a11y de widget — esa pieza se construye ANTES como componente
|
|
|
|
|
|
canónico por la ruta de 9 fases, y el block la compone. **Nunca se le crece
|
|
|
|
|
|
el privilegio al block**. (Es exactamente la regla "compose existing
|
|
|
|
|
|
components; flag gaps" de `docs/guides/component-guide.md` §4, elevada a
|
|
|
|
|
|
frontera de tier.) La F1 de este plan existe porque ese triage ya está hecho:
|
|
|
|
|
|
los 7 gaps detectados se construyen primero.
|
|
|
|
|
|
|
|
|
|
|
|
Lo que un block SÍ posee semánticamente: **landmarks y estructura de
|
|
|
|
|
|
documento** (`header/nav/main/aside/footer`, jerarquía h1–h6, skip-link,
|
|
|
|
|
|
`aria-label` de región). Los componentes no pueden saber el contexto de
|
|
|
|
|
|
página; los blocks sí. Es su única superficie a11y propia.
|
|
|
|
|
|
|
|
|
|
|
|
Un block PUEDE tener estado de vista local (pestaña activa, toggle
|
|
|
|
|
|
mensual/anual) usando los props/eventos públicos de los componentes que
|
|
|
|
|
|
compone. Lo que NO puede es materializar ese estado con DOM/CSS propio que
|
|
|
|
|
|
requiera contrato (→ regla de admisión).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Decisiones D-BLK — FIRMADAS 2026-07-21
|
|
|
|
|
|
|
|
|
|
|
|
Repasadas y firmadas por el usuario en el kickoff (F0.1 HECHA). **D-BLK.1
|
|
|
|
|
|
quedó enmendada** respecto a la propuesta original del plan (que proponía
|
|
|
|
|
|
`src/blocks/`); el resto se firmó tal como estaba propuesto. D-BLK.3/4/5
|
|
|
|
|
|
derivan de doctrina ya vigente y se firmaron por no-objeción.
|
|
|
|
|
|
|
|
|
|
|
|
| # | Decisión | FIRMADO |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| **D-BLK.1** | Ubicación y alias | **`src/uix/blocks/`** + alias `$blocks` (decisión de usuario: el tier es UI y vive junto a las capas). La prueba de encapsulación se conserva íntegra: borrar `src/uix/blocks/` deja `npm run check` verde y NADA del canon (`morfo/soma/sema/eidos/active-uix/langs`) lo importa — blocks es un tier bajo `src/uix/`, no una quinta capa. |
|
|
|
|
|
|
| **D-BLK.2** | Estilos | **Layout-components-first**: el layout se hace componiendo `Container/Section/Stack/Flex/Grid/AutoGrid/Wrap/Group/Separator/AspectRatio/Surface` y sus props. Un block NO trae `.css` propio; un `<style>` scoped puntual exige justificación en su README y nunca selecciona internals de componentes compuestos. |
|
|
|
|
|
|
| **D-BLK.3** | API | Componente compuesto con partes anidadas: `<SiteHeader>` / `<SiteHeader.Nav>` / `<SiteHeader.Actions>`; contenido SIEMPRE por children, nunca árboles de datos (`items={...}` solo donde el componente canónico compuesto ya es data-driven). (Derivada de la regla compositional-not-data-driven ya vigente.) |
|
|
|
|
|
|
| **D-BLK.4** | Demos | `web/routes/blocks/{kebab}/+page.svelte` + galería índice en `web/routes/blocks/`. (La ruta `web/routes/alpha/` sigue TERMINADA y prohibida — no tocarla.) |
|
|
|
|
|
|
| **D-BLK.5** | Idiomas/strings | Un block no posee NINGÚN string visible: todo texto llega del app como children/props. Si un string parece inevitable, es superficie de contrato → lo posee el componente canónico subyacente (vía `texts:` del morfo + langs). (Consecuencia mecánica de B-1: sin morfo no hay `texts:`.) |
|
|
|
|
|
|
| **D-BLK.6** | Servicios | **v1 sin servicios**: los blocks NO consumen `uix.prefs`/langs/eidos directamente; el cableado (tema/idioma, submit de auth, transporte) llega como handlers/props del app. Revisable si ≥2 blocks demuestran necesidad real (misma vara que la 2-de-3). |
|
|
|
|
|
|
| **D-BLK.7** | Naming F1 | `sticky` · `anchor-nav` · `empty-state` · `result` · `callout` · `prose` · `sidebar` — confirmados. (Los matices de naming siguen revisables en la fase 0 de cada uno, como toda fase 0.) |
|
|
|
|
|
|
|
|
|
|
|
|
Cualquier enmienda futura a una D-BLK se registra aquí con fecha ANTES de
|
|
|
|
|
|
seguir construyendo (regla dura: los desvíos de alcance se declaran, nunca en
|
|
|
|
|
|
silencio).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. El contrato B (suelo de calidad de un block — guard: `blocks-check`)
|
|
|
|
|
|
|
|
|
|
|
|
| B | Obligación |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| **B-1** | Sin morfo, sin pack sema, sin fila en `component:audit`. Un block es composición; el comportamiento con contrato se promociona al canon ANTES (regla de admisión §1). |
|
|
|
|
|
|
| **B-2** | Todo elemento interactivo es un componente eidos del catálogo (`Button`, `Link`, `Field`, …). Elementos nativos interactivos crudos (`button/input/select/textarea/a`) = **error** de `blocks-check`. (Excepción única: el HTML que `Prose` recibe ya renderizado — ese contenido es del app.) |
|
|
|
|
|
|
| **B-3** | Los componentes compuestos se consumen AS-IS por sus props públicos (`variant/size/color/…`). Prohibido re-estilizar sus internals desde el block (ni CSS ni `style=`). Los colores son siempre roles/tokens vía props — un block no decide color fuera del sistema. |
|
|
|
|
|
|
| **B-4** | Dependencia unidireccional: `src/uix/blocks/*` importa `$uix`, `$adom` y arts públicos; nada del canon (`src/uix/{morfo,soma,sema,eidos,active-uix,langs}`) ni de `src/{arts,libs,packs}` importa de `src/uix/blocks/`. Borrar el tier deja `check` verde — la prueba de encapsulación se mantiene aunque viva bajo `src/uix/`. Entre blocks tampoco se importa (B-10). |
|
|
|
|
|
|
| **B-5** | Contenido por composición (children/snippets). Nunca `root={tree}` ni props-árbol propias. |
|
|
|
|
|
|
| **B-6** | Responsive con los mecanismos del framework (props responsive de los componentes de layout, breakpoints canónicos). Cero `matchMedia`/listeners propios — si hiciera falta observar algo, es señal de componente canónico (→ admisión). |
|
|
|
|
|
|
| **B-7** | Cero strings propios (D-BLK.5). |
|
|
|
|
|
|
| **B-8** | Landmarks correctos: elemento sectioning + `aria-label`/`aria-labelledby` cuando hay más de un landmark del mismo tipo; jerarquía de headings coherente y documentada en el README del block (qué nivel emite y cómo se ajusta). |
|
|
|
|
|
|
| **B-9** | Cada block: `README.md` (secciones: **Función · Mapa de composición** — qué componentes canónicos usa y con qué props — **· Decisiones · Gaps-con-disposición**) + demo con profundidad de testbed (cada prop pública = control vivo; guía: `docs/guides/demo-authoring.md`, adaptada — sin las 9 tabs completas de componente, mínimo: escena realista + panel de props + código copiable). |
|
|
|
|
|
|
| **B-10** | Un block no importa otro block. Si dos blocks comparten estructura, la pieza compartida o es un componente canónico o se duplica conscientemente (anotado en Gaps). Excepción declarada: los shells (`app-shell`, `docs-shell`) SÍ componen blocks/componentes de F1 por diseño — se lista explícitamente en su README. |
|
|
|
|
|
|
| **B-11** | Motion: solo vía los props `motion`/presets de los componentes compuestos o `Cascade` para coreografía de entrada. Cero `@keyframes`/transitions propias (la regla R-4.5 del canon aplica moralmente aunque el audit no corra aquí). |
|
|
|
|
|
|
|
|
|
|
|
|
`blocks-check` (F0.5) verifica mecánicamente: B-2 (AST/regex de elementos
|
|
|
|
|
|
nativos interactivos), B-4 (dirección de imports), B-1 (no hay ficheros bajo
|
|
|
|
|
|
`src/uix/morfo/components/` reclamados por blocks; no imports de
|
|
|
|
|
|
`sema/components`), D-BLK.2 (no `.css` bajo `src/uix/blocks/`; `<style>` solo
|
|
|
|
|
|
con `/* justified: … */`), B-9 (README + ruta demo existen), B-10 (imports
|
|
|
|
|
|
entre blocks solo en la allowlist de shells).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Reglas de trabajo (TODAS las sesiones de este plan)
|
|
|
|
|
|
|
|
|
|
|
|
**Lectura obligatoria antes de tocar nada** (leer los docs directamente —
|
|
|
|
|
|
nunca delegar la lectura a agentes):
|
|
|
|
|
|
|
|
|
|
|
|
- Siempre: `CLAUDE.md` (llega solo) · este plan · `docs/README.md` (mapa).
|
|
|
|
|
|
- F1 (componentes canon): `docs/building-a-component.md` — LA puerta; cada
|
|
|
|
|
|
fase de la ruta nombra su doc y su guard. No saltarse la fase 0 (tabla
|
|
|
|
|
|
comparativa vs ≥3 referencias; cada ❌/⚠️ del scope recibe decisión del
|
|
|
|
|
|
usuario ANTES de construir).
|
|
|
|
|
|
- F2–F4 (blocks): `docs/architecture/blocks.md` (existirá tras F0) + los
|
|
|
|
|
|
README de CADA componente que el block compone (el mapa de composición se
|
|
|
|
|
|
escribe leyendo, no de memoria) + referencias de blocks equivalentes
|
|
|
|
|
|
(shadcn blocks · Tailwind UI/Plus · Flowbite blocks · PrimeBlocks · Relume)
|
|
|
|
|
|
— comparativa ANTES de diseñar y al declarar done.
|
|
|
|
|
|
|
|
|
|
|
|
**Proceso por tanda** (una tanda = un componente o un block):
|
|
|
|
|
|
|
|
|
|
|
|
1. `git reset -q` + verificar HEAD (sesiones concurrentes; NUNCA amend).
|
|
|
|
|
|
2. Fase 0 del ítem: comparativa + scope al usuario si hay decisiones.
|
|
|
|
|
|
3. Construir. UN fichero → verificar → resto (no-cascade). Componer, jamás
|
|
|
|
|
|
re-implementar; gap detectado = FLAG al usuario, no workaround inline.
|
|
|
|
|
|
4. Verificar: `npm run check` + scope vitest del ítem + guard del tier
|
|
|
|
|
|
(`component:audit --only {kebab}` para F1 · `blocks-check` para F2+) +
|
|
|
|
|
|
**navegador de verdad**: screenshot y MIRARLO, claro Y oscuro
|
|
|
|
|
|
(`colorScheme:'dark'`), móvil y desktop para blocks (resize 375/1280).
|
|
|
|
|
|
5. Demo con profundidad de testbed (B-9); docs del framework en el MISMO pase
|
|
|
|
|
|
(README del ítem + mapa si procede).
|
|
|
|
|
|
6. Commit (convención viva del repo): `uix({kebab}): …` para F1,
|
|
|
|
|
|
`blocks({kebab}): …` para F2+, `docs(blocks): …` para doctrina. Stage
|
|
|
|
|
|
SOLO los paths propios (nunca `git add -A`; excluir `words/`, `palabras/`,
|
|
|
|
|
|
`web/routes/alpha/`).
|
|
|
|
|
|
7. Actualizar la tabla de estado de este plan (y `next-features.md` §8 al
|
|
|
|
|
|
cerrar cada fase).
|
|
|
|
|
|
|
|
|
|
|
|
**Prohibiciones**: no tocar `palabras/`, `chronos/`, `media-player`
|
|
|
|
|
|
(foráneos/WIP — componerlos solo cuando estén landed; hoy chronos NO lo
|
|
|
|
|
|
está); no crear servicios/mocks falsos en tests (instancias reales vía
|
|
|
|
|
|
`createActiveUix`); no `--no-verify`; no borrar nada sin instrucción
|
|
|
|
|
|
explícita; responder en castellano, código y docs en inglés.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F0 — Infraestructura del tier
|
|
|
|
|
|
|
|
|
|
|
|
**Objetivo**: que exista el tier con doctrina, guard y sitio donde vivir —
|
|
|
|
|
|
vacío pero verde.
|
|
|
|
|
|
|
|
|
|
|
|
| Paso | Producir | Verificación |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| F0.1 | **Firma D-BLK** (§2) con el usuario; enmendar el plan si procede | **HECHA 2026-07-21** — firmas en §2 (D-BLK.1 enmendada: `src/uix/blocks/`) |
|
|
|
|
|
|
| F0.2 | `docs/architecture/blocks.md` — la doctrina de §1 + §3 en formato espejo de `packs.md` (frontmatter E1, regla de admisión, hard boundaries, contrato B, "promotion path" = la F1 como ejemplo vivido). Enlazar sin copiar: canon → `CANON.md`, ruta → `building-a-component.md` | `npm run docs:check` (links) |
|
|
|
|
|
|
| F0.3 | Alias `$blocks` → `src/uix/blocks` en el const `aliases` de `vite.config.ts` (fuente de verdad) + `svelte.config.js` en sync + fila en la tabla de aliases de `CLAUDE.md` | `npm run check` |
|
|
|
|
|
|
| F0.4 | `src/uix/blocks/README.md` — mapa del tier (inventario vivo = el árbol, como packs) + template de README de block (B-9) | — |
|
|
|
|
|
|
| F0.5 | `scripts/blocks-check.ts` + npm script `blocks:check` — los checks mecánicos listados en §3. Espejo estructural de `packs-check`. Test negativo: un fixture con `<button>` crudo debe fallar | `npm run blocks:check` verde en tier vacío + test negativo rojo |
|
|
|
|
|
|
| F0.6 | `web/routes/blocks/+page.svelte` — galería índice (dogfooding: componer `Container/Section/Card/…` del propio catálogo para la galería) | navegador claro/oscuro |
|
|
|
|
|
|
| F0.7 | Cablear docs: fila E1 en el mapa de `docs/README.md` (architecture/blocks.md) + nota del tier en el diagrama de arquitectura de `CLAUDE.md` (línea `blocks/ → …`) + cross-ref en `docs/comparison.md` si aporta | `npm run docs:check` |
|
|
|
|
|
|
|
|
|
|
|
|
**Cierre F0**: `check` + `docs:check` + `blocks:check` verdes; galería
|
|
|
|
|
|
renderiza vacía con mensaje de "en construcción" compuesto con el catálogo.
|
|
|
|
|
|
|
|
|
|
|
|
**CERRADA 2026-07-21** — evidencia: `docs:check` 0/0 (511 docs);
|
|
|
|
|
|
`svelte-check` 76E/51W = baseline exacto (los archivos nuevos compilan
|
|
|
|
|
|
limpios; los 76 son deuda foránea preexistente del árbol sin commitear);
|
|
|
|
|
|
`blocks:check` verde (self-test 10 fixtures OK — el `<button>` crudo SE
|
|
|
|
|
|
detecta — y 5 532 archivos de canon/arts/libs/packs escaneados sin
|
|
|
|
|
|
violaciones de dirección). Galería verificada en navegador vía árbol de
|
|
|
|
|
|
accesibilidad + estilos computados en AMBOS modos (light: `base-light`, h1
|
|
|
|
|
|
oklch(0.24…); dark: `base-dark`, h1 oklch(0.95…)); la captura de píxeles
|
|
|
|
|
|
del panel embebido expiró (renderer suspendido en segundo plano — clase
|
|
|
|
|
|
conocida) — pendiente de un vistazo humano o Playwright en la primera
|
|
|
|
|
|
sesión F1. El listener `prefers-color-scheme` del layout funciona al boot;
|
|
|
|
|
|
el evento `change` en vivo no dispara con el panel suspendido (peculiaridad
|
|
|
|
|
|
del entorno, mismo patrón raw-matchMedia que el layout de `/uix`).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F1 — Componentes base del CANON (7)
|
|
|
|
|
|
|
|
|
|
|
|
Estos NO son blocks: entran por `src/uix/{morfo,soma,sema,eidos}` siguiendo
|
|
|
|
|
|
**la ruta completa de 9 fases** de `docs/building-a-component.md` (fase 0
|
|
|
|
|
|
Decide → 8 Acceptance), con `component:audit --only {kebab}` como oráculo.
|
|
|
|
|
|
Las fichas siguientes NO sustituyen la ruta — la parametrizan: dan el gap, la
|
|
|
|
|
|
membresía esperada, el boceto de morfo, las referencias mínimas de la fase 0
|
|
|
|
|
|
y las trampas conocidas del ecosistema que aplican. El vocabulario cerrado
|
|
|
|
|
|
(archetypes, familias, verbos, intents, holds) se toma SIEMPRE de
|
|
|
|
|
|
`docs/canon/vocabularies.md` (generado del código) — las fichas nombran
|
|
|
|
|
|
candidatos, el builder valida contra la lista real.
|
|
|
|
|
|
|
|
|
|
|
|
**Orden recomendado**: F1.2 → F1.3 → F1.4 (display, baratos, establecen
|
|
|
|
|
|
ritmo) → F1.1 (desbloquea F2) → F1.5 → F1.6 → F1.7 (desbloquean F4/F3).
|
|
|
|
|
|
|
|
|
|
|
|
### F1.1 `sticky` — afijado con estado
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: wrapper `position:sticky` que SABE cuándo está afijado
|
|
|
|
|
|
(`data-stuck`) para que la cabecera cambie elevación/fondo al pegarse.
|
|
|
|
|
|
- **Membresía**: soma + eidos (comportamiento real: observación + estado).
|
|
|
|
|
|
- **Fase 0, comparar**: AntD `Affix` · Mantine `Affix` · la técnica sentinel
|
|
|
|
|
|
con IntersectionObserver (CSS-Tricks/web.dev) · shadcn (no lo tiene —
|
|
|
|
|
|
anotarlo como diferencial).
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` (el contenedor sticky) + `sentinel`
|
|
|
|
|
|
(1px observado, `aria-hidden`); data: `data-stuck` (presente/ausente),
|
|
|
|
|
|
`data-edge` (`top | bottom`). **Sin eventos v1** (afijarse es hecho de
|
|
|
|
|
|
layout, no ocurrencia perceptiva del usuario — si algún consumidor pide
|
|
|
|
|
|
señal sema, se revisa con el criterio D.4). Sin keyboard, sin `texts`.
|
|
|
|
|
|
Partes display → **sin `archetype`** (un archetype interactivo arrastra
|
|
|
|
|
|
estilos de item; y el archetype `content` pisa `position` — justo lo que
|
|
|
|
|
|
sticky no puede permitirse).
|
|
|
|
|
|
- **Soma**: provider observa el sentinel vía `dom.observe`
|
|
|
|
|
|
(IntersectionObserver por adom) — **JAMÁS** scroll listener +
|
|
|
|
|
|
`getBoundingClientRect` síncrono (regla de reflow: lecturas de layout solo
|
|
|
|
|
|
post-layout vía `dom.measure`/rAF). `data-stuck` lo escriben los effects
|
|
|
|
|
|
(`dom.apply`), único escritor.
|
|
|
|
|
|
- **Eidos**: wrapper fino; tokens `--sticky-top-offset` /
|
|
|
|
|
|
`--sticky-z-index` (alias de la escala `--z-index-*` semántica, no número
|
|
|
|
|
|
crudo). La receta NO decide sombra/fondo del contenido — eso lo hace el
|
|
|
|
|
|
consumidor seleccionando su propio estado visual con `data-stuck` presente
|
|
|
|
|
|
(documentar el patrón en el README).
|
|
|
|
|
|
- **Demo**: página larga con header/toolbar afijable, chip mostrando el
|
|
|
|
|
|
estado, edge top y bottom.
|
|
|
|
|
|
- **Trampas**: `mergeProps` clobberea stamps — attrs visuales del wrapper
|
|
|
|
|
|
fuera del morfo salvo que crucen a soma (aquí `data-stuck` SÍ es soma).
|
|
|
|
|
|
|
|
|
|
|
|
### F1.2 `empty-state` — estado vacío
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: patrón universal icono/título/descripción/acción para listas y
|
|
|
|
|
|
paneles sin datos. Hoy no existe nada.
|
|
|
|
|
|
- **Membresía**: morfo + eidos, **sin provider soma** (display puro; hay
|
|
|
|
|
|
precedente sancionado: `metrics` es `scope: ['sema','eidos']` sin soma).
|
|
|
|
|
|
Morfo-first aplica igual: hasta las hojas llevan morfo.
|
|
|
|
|
|
- **Fase 0, comparar**: Chakra `EmptyState` · AntD `Empty` · HeroUI ·
|
|
|
|
|
|
patrones de empty state de Material.
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` + `media` (icon o ilustración) +
|
|
|
|
|
|
`title` + `description` + `actions`. Sin eventos, sin keyboard, sin
|
|
|
|
|
|
archetype en partes display. `texts:` NO — los strings los trae el app
|
|
|
|
|
|
(es contenido, no chrome del componente).
|
|
|
|
|
|
- **Eidos**: receta pequeña espejo de la más cercana ya enviada (estudiar
|
|
|
|
|
|
`banner`/`card` antes de escribir una línea); centrado, spacing tokenizado
|
|
|
|
|
|
`--empty-state-*`, tamaño vía canon `size` si aporta (probablemente solo
|
|
|
|
|
|
`sm/md`). `actions` compone `Button` del catálogo vía children.
|
|
|
|
|
|
- **Demo**: en contexto real — un `table`/`grid-list` sin filas mostrando el
|
|
|
|
|
|
empty-state, más la escena aislada con controles.
|
|
|
|
|
|
|
|
|
|
|
|
### F1.3 `result` — página de resultado
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: estado terminal de página/flujo (éxito, error, 403/404/500).
|
|
|
|
|
|
Pareja de `empty-state`, distinto rol: cierra un flujo, no describe
|
|
|
|
|
|
ausencia de datos.
|
|
|
|
|
|
- **Membresía**: como F1.2 (morfo + eidos display).
|
|
|
|
|
|
- **Fase 0, comparar**: AntD `Result` · patrones de error page de
|
|
|
|
|
|
Tailwind UI · HeroUI.
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` + `media` + `title` + `description` +
|
|
|
|
|
|
`actions` + `extra`. Prop `status` → `data-status`
|
|
|
|
|
|
(`success | error | info | forbidden | not-found | server-error`).
|
|
|
|
|
|
**Cuidado doctrinal**: `data-status` NO es el intent perceptivo — si en
|
|
|
|
|
|
algún momento gana eventos con evaluación, el intent viaja por
|
|
|
|
|
|
`fromProp:intent` del morfo, no reciclando status (regla data-color ≠
|
|
|
|
|
|
perceptual-intent). v1 sin eventos.
|
|
|
|
|
|
- **Eidos**: color del media por rol canónico según status (roles, no
|
|
|
|
|
|
hex); iconografía por status con override por children.
|
|
|
|
|
|
- **Demo**: los 6 status + composición con `Button` home/back.
|
|
|
|
|
|
|
|
|
|
|
|
### F1.4 `callout` — admonición inline
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: aviso DENTRO del contenido (info/tip/aviso/peligro). `Banner` es
|
|
|
|
|
|
el anuncio de página; esto es la nota de documento — imprescindible para
|
|
|
|
|
|
`prose` y el docs-shell.
|
|
|
|
|
|
- **Membresía**: morfo + eidos display (sin soma; variante dismissible se
|
|
|
|
|
|
DIFIERE — si se pidiera, el dismiss es evento real → soma + sema en esa
|
|
|
|
|
|
pasada, no antes).
|
|
|
|
|
|
- **Fase 0, comparar**: Radix Themes `Callout` · shadcn `Alert` ·
|
|
|
|
|
|
admonitions de Docusaurus/Starlight · `banner` propio (leer su README:
|
|
|
|
|
|
qué decisiones ya están tomadas para avisos y cuáles NO trasladan).
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` (role `note`) + `icon` + `title` +
|
|
|
|
|
|
`content`. La evaluación ES semántica aquí: prop `intent` restringido a
|
|
|
|
|
|
un subset canónico (candidatos: `neutral | affirm | risk | threat`;
|
|
|
|
|
|
validar contra `vocabularies.md`) estampado vía `fromProp:intent` — así
|
|
|
|
|
|
eidos tiñe con la MISMA mecánica evaluativa del sistema, no con un enum
|
|
|
|
|
|
paralelo inventado.
|
|
|
|
|
|
- **Eidos**: tinte por intent usando roles/escalas (fondo suave + borde +
|
|
|
|
|
|
icono saturado — estudiar cómo tiñe `banner` e imitar la mecánica);
|
|
|
|
|
|
tipografía del canon; `--callout-*` para spacing/radius.
|
|
|
|
|
|
- **Demo**: los 4 intents × con/sin título × con contenido multilínea, e
|
|
|
|
|
|
incrustado en un texto largo (anticipo de `prose`).
|
|
|
|
|
|
|
|
|
|
|
|
### F1.5 `prose` — contenido largo estilizado
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: contenedor que estiliza HTML/markdown renderizado (h1–h6, p,
|
|
|
|
|
|
listas, blockquote, tabla, código, img, hr) con los tokens del tema. Sin
|
|
|
|
|
|
esto no hay docs ni blog.
|
|
|
|
|
|
- **Membresía**: morfo mínimo + eidos (sin soma). El morfo declara UNA part
|
|
|
|
|
|
`provider`; el trabajo vive en la receta.
|
|
|
|
|
|
- **Fase 0, comparar**: Tailwind Typography (`prose`) — el patrón de
|
|
|
|
|
|
referencia — · Mantine `TypographyStylesProvider` · Radix Themes.
|
|
|
|
|
|
- **Norma que lo hace legal**: los selectores de elemento descendientes
|
|
|
|
|
|
(`[data-prose] h2`, `[data-prose] ul`…) son **selectores estructurales
|
|
|
|
|
|
bajo una parte del morfo** — sancionados por la norma S1 del eidos-lint
|
|
|
|
|
|
(hook = data-attr del morfo o estructural bajo parte; clases NO).
|
|
|
|
|
|
- **Eidos**: LA receta grande del grupo. Reglas: tipografía SOLO vía
|
|
|
|
|
|
primitivos del canon (`--font-*`, escala tipográfica; cero literales — y
|
|
|
|
|
|
los proporcionales que hagan falta con `/* literal: */` justificado);
|
|
|
|
|
|
medida de lectura `--prose-max-width` (~65ch) tokenizada; `code`/`pre`
|
|
|
|
|
|
alineados con los tokens de `code`/`code-block` (leer sus recetas ANTES;
|
|
|
|
|
|
si hay que duplicar valores, es un gap a flag, no un copy-paste);
|
|
|
|
|
|
imágenes `max-width:100%`; tablas con overflow propio. El HTML interno es
|
|
|
|
|
|
del app — aquí la regla B-2 no aplica (es la excepción documentada).
|
|
|
|
|
|
- **Demo**: documento markdown real renderizado (headings, listas anidadas,
|
|
|
|
|
|
tabla, código, blockquote, callout incrustado), claro/oscuro, densidades.
|
|
|
|
|
|
|
|
|
|
|
|
### F1.6 `anchor-nav` — índice con scrollspy
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: TOC lateral con sección activa según scroll (docs, settings
|
|
|
|
|
|
largos, landing largas).
|
|
|
|
|
|
- **Membresía**: soma + eidos (observación + estado activo + navegación).
|
|
|
|
|
|
- **Fase 0, comparar**: AntD `Anchor` · Mantine `TableOfContents` ·
|
|
|
|
|
|
Starlight/Docusaurus TOC. APG: no hay patrón de widget — es un `nav`
|
|
|
|
|
|
landmark con `aria-current`; anotarlo en el README (válvula A-1.4 con
|
|
|
|
|
|
nota, como pagination/stepper).
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` (nav, `aria-label` vía `texts:` —
|
|
|
|
|
|
aquí SÍ hay string de chrome: "On this page"/"En esta página" → catálogo
|
|
|
|
|
|
langs) + `list` + `item` + `link` (¿archetype de item interactivo? —
|
|
|
|
|
|
validar contra vocabularies; el link compone `Link` del catálogo).
|
|
|
|
|
|
Data: `data-active` en item; `aria-current`. Eventos: click de link =
|
|
|
|
|
|
navegación (familia/verbo a decidir en fase 1 contra `SEMA_VERBS` —
|
|
|
|
|
|
candidato familia `shift`; validar). `expression:` según criterio D.4.
|
|
|
|
|
|
- **Soma**: registro de secciones objetivo (por id o por `attachPart`),
|
|
|
|
|
|
observación vía `dom.observe` (IntersectionObserver, umbrales al gusto
|
|
|
|
|
|
del provider) — mismas reglas anti-reflow que F1.1. El activo es estado
|
|
|
|
|
|
del provider; los effects estampan `data-active`. Scroll programático al
|
|
|
|
|
|
click vía `dom` (scrollIntoView por ActiveDom), no window crudo.
|
|
|
|
|
|
- **Eidos**: raíl vertical con indicador de activo (¡leer la memoria
|
|
|
|
|
|
NavMenu Indicator: soma posiciona, eidos da forma, nunca transition de
|
|
|
|
|
|
transform!); niveles h2/h3 con indentación tokenizada.
|
|
|
|
|
|
- **Demo**: página larga real con `prose` (F1.5) + anchor-nav vivo.
|
|
|
|
|
|
|
|
|
|
|
|
### F1.7 `sidebar` — navegación vertical de app
|
|
|
|
|
|
|
|
|
|
|
|
- **Gap**: columna de navegación con grupos, item activo, colapso a raíl y
|
|
|
|
|
|
variante móvil. `navigation-menu` es horizontal (patrón Radix); esto es
|
|
|
|
|
|
otra pieza.
|
|
|
|
|
|
- **Membresía**: soma + eidos. Es el componente más pesado de F1 —
|
|
|
|
|
|
reservarle tanda propia.
|
|
|
|
|
|
- **Fase 0, comparar**: shadcn `Sidebar` (el patrón de referencia actual) ·
|
|
|
|
|
|
Mantine `AppShell.Navbar` · Ark/Radix (no lo tienen — anotar). Scope al
|
|
|
|
|
|
usuario ANTES de construir: qué features de shadcn entran en v1
|
|
|
|
|
|
(propuesta v1: grupos + colapsable a raíl con tooltips + activo +
|
|
|
|
|
|
slots header/footer; FUERA v1: keyboard shortcut global, persistencia —
|
|
|
|
|
|
llega del app por D-BLK.6, submenús flotantes en raíl).
|
|
|
|
|
|
- **Morfo (boceto)**: parts `provider` + `header` + `content` + `group` +
|
|
|
|
|
|
`group-label` + `item` + `footer` + `trigger` (botón colapso — compone
|
|
|
|
|
|
`Button` con `asChild`/child pattern). Data: `data-collapsed`,
|
|
|
|
|
|
`data-rail`, `data-active` (item). Eventos: toggle de colapso (verbo
|
|
|
|
|
|
candidato del set real; evaluación neutral), activación de item si el
|
|
|
|
|
|
item es más que un `Link` — decidir en fase 1 (si item = `Link` puro, la
|
|
|
|
|
|
navegación no necesita evento propio del sidebar).
|
|
|
|
|
|
- **Soma**: estado collapsed/rail; grupos colapsables PUEDEN componer el
|
|
|
|
|
|
provider de `collapsible` (precedente compose-compound-in-component:
|
|
|
|
|
|
NumberField dentro de Knob, con aislamiento de eventos); variante móvil
|
|
|
|
|
|
COMPONE `Drawer` (no lo reimplementa) — el provider decide qué montar por
|
|
|
|
|
|
breakpoint responsive del sistema, no matchMedia propio.
|
|
|
|
|
|
- **Eidos**: raíl con `will-change` cuidado (memoria: `will-change:
|
|
|
|
|
|
transform` produce jitter en raíles finos con DPR≠1 — override a `auto`);
|
|
|
|
|
|
tooltips de item en modo raíl componen `Tooltip`; tokens `--sidebar-*`
|
|
|
|
|
|
(width, rail-width, paddings).
|
|
|
|
|
|
- **Demo**: shell de app simulada, toggle colapso, grupos, móvil (375px) con
|
|
|
|
|
|
drawer, RTL.
|
|
|
|
|
|
|
|
|
|
|
|
**Cierre F1**: los 7 con `component:audit --only` PASS (o NEEDS-WORK
|
|
|
|
|
|
únicamente por reglas D-* de demos v3 si esa fase global sigue abierta —
|
|
|
|
|
|
anotar en la tabla), `morfo:check`, `eidos-lint` por componente, suite
|
|
|
|
|
|
`npx vitest run src/uix/eidos` verde, `npm run check` sin regresión sobre
|
|
|
|
|
|
baseline.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F2 — Blocks de sitio (10)
|
|
|
|
|
|
|
|
|
|
|
|
Todos entran por el contrato B. Ficha = **Función · Compone · API (partes) ·
|
|
|
|
|
|
Layout/landmark · v1 · Demo**. Regla transversal: fase 0 ligera SIEMPRE
|
|
|
|
|
|
(mirar el block equivalente en ≥2 catálogos de referencia de §4 y anotar en
|
|
|
|
|
|
el README qué se adopta/descarta). Variantes: v1 = LA variante (una);
|
|
|
|
|
|
ampliaciones = Gaps con disposición, no código especulativo.
|
|
|
|
|
|
|
|
|
|
|
|
**Depende de**: F1.1 (`sticky`) para F2.1; el resto de F2 no depende de F1.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.1 `site-header`
|
|
|
|
|
|
- **Función**: cabecera de sitio con afijado y cambio de elevación al pegarse;
|
|
|
|
|
|
colapso a menú móvil.
|
|
|
|
|
|
- **Compone**: `Sticky` (F1.1) + `Container` + `NavigationMenu` + `Button` +
|
|
|
|
|
|
`Drawer` (móvil) + `Link` + `Separator`.
|
|
|
|
|
|
- **API**: `<SiteHeader>` (props: `sticky?`, `container?`) + `.Brand` +
|
|
|
|
|
|
`.Nav` + `.Actions` + `.MobileNav` (children del drawer).
|
|
|
|
|
|
- **Layout/landmark**: `<header>` + `<nav aria-label>`; skip-link como primer
|
|
|
|
|
|
foco (decidir en su fase 0 si el skip-link vive aquí o en los shells — una
|
|
|
|
|
|
sola respuesta, documentada).
|
|
|
|
|
|
- **v1**: brand izquierda · nav centro · actions derecha · drawer móvil;
|
|
|
|
|
|
estilización del estado pegado vía `data-stuck` (elevación/fondo con tokens
|
|
|
|
|
|
de los componentes compuestos, no CSS nuevo).
|
|
|
|
|
|
- **Demo**: página con scroll largo, claro/oscuro, 375/1280, RTL.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.2 `hero`
|
|
|
|
|
|
- **Función**: sección de apertura con titular, subtítulo, acciones y media.
|
|
|
|
|
|
- **Compone**: `Section` + `Container` + `Stack`/`Grid` + `Heading`
|
|
|
|
|
|
(nivel configurable, default h1) + `Text` + `Group` (acciones con
|
|
|
|
|
|
`Button`) + `Badge` (chip anuncio) + `Image`/`AspectRatio`.
|
|
|
|
|
|
- **API**: `<Hero>` (prop `layout: 'center' | 'split'`) + `.Eyebrow` +
|
|
|
|
|
|
`.Title` + `.Description` + `.Actions` + `.Media`.
|
|
|
|
|
|
- **Layout/landmark**: `<section aria-labelledby={title.id}>`; un solo h1
|
|
|
|
|
|
por página es responsabilidad del app — el README lo dice.
|
|
|
|
|
|
- **v1**: `center` y `split` (la segunda existe porque discrimina el layout,
|
|
|
|
|
|
no por lujo — es el criterio "casos que discriminen").
|
|
|
|
|
|
- **Demo**: ambos layouts, con/sin media, con badge, dark.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.3 `feature-grid`
|
|
|
|
|
|
- **Compone**: `Section` + `Container` + `AutoGrid` + `Stack` + `Icon` +
|
|
|
|
|
|
`Heading` + `Text`.
|
|
|
|
|
|
- **API**: `<FeatureGrid>` + `.Header` (title+description de sección) +
|
|
|
|
|
|
`.Item` (con `.ItemIcon`/`.ItemTitle`/`.ItemText` o children libres).
|
|
|
|
|
|
- **v1**: items planos (sin Card — la variante card es Gap).
|
|
|
|
|
|
- **Demo**: 3/6 items, columnas responsive del AutoGrid.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.4 `pricing`
|
|
|
|
|
|
- **Compone**: `Section` + `CardGroup`/`Card` + `Heading` + `Text` + `Badge`
|
|
|
|
|
|
(plan destacado) + `Button` + `ToggleGroup` (mensual/anual) + filas de
|
|
|
|
|
|
features (`Stack` + `Group` + `Icon` check + `Text`).
|
|
|
|
|
|
- **API**: `<Pricing>` + `.Switch` (billing toggle; estado de vista local
|
|
|
|
|
|
permitido §1) + `.Plan` (prop `featured?`) + `.PlanPrice` + `.PlanFeatures`
|
|
|
|
|
|
+ `.PlanAction`.
|
|
|
|
|
|
- **v1**: 2–4 planes en fila responsive; el precio mostrado por periodo lo
|
|
|
|
|
|
resuelve el app con el valor del toggle (block emite el cambio vía prop
|
|
|
|
|
|
callback del ToggleGroup — sin formatear moneda: eso es `FormatNumber`
|
|
|
|
|
|
del app).
|
|
|
|
|
|
- **Demo**: 3 planes, featured al centro, toggle vivo.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.5 `testimonials`
|
|
|
|
|
|
- **Compone**: `Section` + `AutoGrid` + `Card` + `Avatar` + `Text` +
|
|
|
|
|
|
`Group`.
|
|
|
|
|
|
- **API**: `<Testimonials>` + `.Header` + `.Item` (+`.ItemAuthor` con
|
|
|
|
|
|
Avatar/nombre/cargo).
|
|
|
|
|
|
- **v1**: grid; variante `Carousel` = Gap (el componente existe; entra
|
|
|
|
|
|
cuando una demo real la pida).
|
|
|
|
|
|
|
|
|
|
|
|
### F2.6 `faq`
|
|
|
|
|
|
- **Compone**: `Section` + `Container` (medida estrecha) + `Heading` +
|
|
|
|
|
|
`Accordion`.
|
|
|
|
|
|
- **API**: `<Faq>` + `.Header` + `.Item` (proxy fino de Accordion.Item con
|
|
|
|
|
|
children pregunta/respuesta — respetando el API real del Accordion, que
|
|
|
|
|
|
se lee antes).
|
|
|
|
|
|
- **v1**: una columna; `type` del accordion expuesto tal cual.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.7 `stats-band`
|
|
|
|
|
|
- **Compone**: `Section` + `Group`/`AutoGrid` + `Metrics` + `CountUp` +
|
|
|
|
|
|
`Text`.
|
|
|
|
|
|
- **API**: `<StatsBand>` + `.Stat` (valor + etiqueta; `CountUp` opt-in por
|
|
|
|
|
|
prop).
|
|
|
|
|
|
- **v1**: banda horizontal 2–4 stats; los formatos numéricos llegan ya
|
|
|
|
|
|
formateados o vía `FormatNumber` compuesto por el app.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.8 `cta`
|
|
|
|
|
|
- **Compone**: `Section` + `Surface` (tratamiento de fondo del sistema — un
|
|
|
|
|
|
CTA se distingue por acabado, y eso ya es vocabulario del framework) +
|
|
|
|
|
|
`Heading` + `Text` + `Group` (`Button`s).
|
|
|
|
|
|
- **API**: `<Cta>` + `.Title` + `.Description` + `.Actions`.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.9 `newsletter`
|
|
|
|
|
|
- **Compone**: como F2.8 + `Form` + `Field` (email) + `Button`.
|
|
|
|
|
|
- **API**: `<Newsletter>` + `.Title` + `.Description` + `.Form` (submit
|
|
|
|
|
|
handler del app; validación por el sistema de `Form` — leer su README:
|
|
|
|
|
|
el block no inventa validación).
|
|
|
|
|
|
- **Nota**: separado de `cta` porque el form introduce a11y y estados
|
|
|
|
|
|
(invalid/submitting) que el CTA puro no tiene.
|
|
|
|
|
|
|
|
|
|
|
|
### F2.10 `site-footer`
|
|
|
|
|
|
- **Compone**: `<footer>` + `Container` + `Grid` (columnas: `Heading` sm +
|
|
|
|
|
|
`Stack` de `Link`) + `Separator` + `Group` (`IconButton` sociales) +
|
|
|
|
|
|
`Text` legal.
|
|
|
|
|
|
- **API**: `<SiteFooter>` + `.Column` + `.Social` + `.Legal` + `.Extra`
|
|
|
|
|
|
(slot libre: theme/lang pickers del app — D-BLK.6).
|
|
|
|
|
|
- **v1**: 3–5 columnas responsive → apiladas en móvil.
|
|
|
|
|
|
|
|
|
|
|
|
*(`logo-cloud` se mueve a F5: su versión honesta pide `Marquee` o queda en un
|
|
|
|
|
|
`Wrap` trivial que no justifica block todavía — decisión anotada, revisable.)*
|
|
|
|
|
|
|
|
|
|
|
|
**Cierre F2**: `blocks-check` verde; galería `web/routes/blocks/` con los 10;
|
|
|
|
|
|
una página compuesta de PRUEBA (header + hero + features + pricing + faq +
|
|
|
|
|
|
cta + footer juntos) que valide que los blocks ensamblan sin fricción — esa
|
|
|
|
|
|
página ES el test de integración del tier y se mira en claro/oscuro/375/1280.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F3 — Blocks de aplicación (10)
|
|
|
|
|
|
|
|
|
|
|
|
Mismas reglas y ficha que F2. **Depende de**: F1.2/F1.3/F1.4 (estados),
|
|
|
|
|
|
F1.7 (`sidebar`) para F3.1.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.1 `app-shell`
|
|
|
|
|
|
- **Función**: esqueleto de aplicación: sidebar + topbar + contenido (+ aside
|
|
|
|
|
|
opcional).
|
|
|
|
|
|
- **Compone**: `Sidebar` (F1.7) + `Sticky` + `Container`/`Grid` +
|
|
|
|
|
|
`ScrollArea` + `Group` + slots.
|
|
|
|
|
|
- **API**: `<AppShell>` + `.Sidebar` + `.Topbar` + `.Content` + `.Aside`.
|
|
|
|
|
|
- **Landmark**: `header/nav/main/aside` + skip-link (según decisión F2.1);
|
|
|
|
|
|
jerarquía documentada.
|
|
|
|
|
|
- **v1**: sidebar colapsable + topbar afijada + main con scroll propio;
|
|
|
|
|
|
móvil = sidebar en drawer (lo trae F1.7).
|
|
|
|
|
|
- **Excepción B-10 declarada**: los shells componen otros elementos de F1 y
|
|
|
|
|
|
blocks — allowlist en `blocks-check`.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.2 `auth`
|
|
|
|
|
|
- **Función**: familia de formularios de identidad. Sub-blocks: `sign-in` ·
|
|
|
|
|
|
`sign-up` · `recover` · `otp`.
|
|
|
|
|
|
- **Compone**: `Card` + `Form` + `Field` + `PasswordField` + `PinInput`
|
|
|
|
|
|
(otp) + `ProofOfHuman` + `Button` + `Separator` + `Link` + slot de
|
|
|
|
|
|
proveedores sociales (`Button`s del app).
|
|
|
|
|
|
- **API**: `<AuthCard kind='sign-in'|…>` con partes (`.Title` · `.Fields` ·
|
|
|
|
|
|
`.Actions` · `.Footer`), o cuatro exports finos — decidir en su fase 0
|
|
|
|
|
|
mirando el API real de `Form` (lo que menos indirection genere gana:
|
|
|
|
|
|
no-premature-abstraction).
|
|
|
|
|
|
- **Regla dura**: TODO handler (submit, oauth, resend) llega del app; el
|
|
|
|
|
|
block no conoce transporte ni credenciales. La validación es del sistema
|
|
|
|
|
|
`Form` con schemas del app.
|
|
|
|
|
|
- **Demo**: los 4 kinds, estados invalid/submitting, claro/oscuro.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.3 `data-table`
|
|
|
|
|
|
- **Función**: la tabla de trabajo completa: toolbar (búsqueda + filtros +
|
|
|
|
|
|
visibilidad de columnas + bulk actions con selección) + `Table` +
|
|
|
|
|
|
`Pagination` + `EmptyState` integrado.
|
|
|
|
|
|
- **Compone**: `Table` + `SearchField` + `DropdownMenu` + `Button` +
|
|
|
|
|
|
`Pagination` + `EmptyState` (F1.2) + `Group`/`Toolbar`.
|
|
|
|
|
|
- **⚠️ Fase 0 OBLIGADA y más profunda**: leer `soma/components/table` +
|
|
|
|
|
|
`$libs/datagrid` ANTES de diseñar el API — el block debe montar sobre las
|
|
|
|
|
|
capacidades reales (sorting/selection/paging que ya existan). Cada
|
|
|
|
|
|
capacidad que falte = FLAG con disposición (candidata a canon), nunca
|
|
|
|
|
|
lógica de datos dentro del block. El estado de datos es del app; el block
|
|
|
|
|
|
orquesta la UI.
|
|
|
|
|
|
- **API**: `<DataTable>` + `.Toolbar` (+`.Search`/`.Filters`/`.Columns`) +
|
|
|
|
|
|
`.Content` (la Table compuesta por el app con su API real) + `.Selection`
|
|
|
|
|
|
(barra bulk) + `.Footer` (Pagination) + `.Empty`.
|
|
|
|
|
|
- **Demo**: dataset realista (≥100 filas), búsqueda+filtro+selección+bulk,
|
|
|
|
|
|
estado vacío tras filtrar.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.4 `dashboard`
|
|
|
|
|
|
- **Compone**: `AutoGrid`/`Grid` + `Card` + `Metrics`/`CountUp` + `Chart`
|
|
|
|
|
|
(tipos ya existentes) + `Feed` + `EmptyState`.
|
|
|
|
|
|
- **API**: `<Dashboard>` + `.Stat` + `.Panel` (card con `.PanelHeader` +
|
|
|
|
|
|
children libres para chart/feed).
|
|
|
|
|
|
- **v1**: composición fija de ejemplo con slots; nada de layout persistible
|
|
|
|
|
|
ni drag (Gap explícito; si un día entra, el drag es de `drag-drop`).
|
|
|
|
|
|
|
|
|
|
|
|
### F3.5 `settings`
|
|
|
|
|
|
- **Compone**: `Container` (medida estrecha) + `Section` + `Heading` +
|
|
|
|
|
|
`Separator` + filas `Field`/controles + `Callout` (F1.4, intent `threat`)
|
|
|
|
|
|
+ `AlertDialog` (confirmación destructiva) + `Button`.
|
|
|
|
|
|
- **API**: `<Settings>` + `.Section` (+`.SectionTitle`/`.SectionDescription`)
|
|
|
|
|
|
+ `.Row` (label+control+ayuda) + `.DangerZone`.
|
|
|
|
|
|
- **Regla**: cada control es el componente del ecosistema (nunca nativos —
|
|
|
|
|
|
B-2); el guardado (por fila o global) lo decide el app vía `Form`.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.6 `user-menu`
|
|
|
|
|
|
- **Compone**: `Avatar` + `DropdownMenu` (+ items con `Icon`, `Separator`,
|
|
|
|
|
|
`Kbd` opcional).
|
|
|
|
|
|
- **API**: `<UserMenu>` + `.Trigger` (Avatar + nombre) + `.Items` (children
|
|
|
|
|
|
= items del DropdownMenu real).
|
|
|
|
|
|
- **Nota D-BLK.6**: tema/idioma/logout = items que el APP cablea; el block
|
|
|
|
|
|
da la estructura y los puntos de montaje.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.7 `notifications`
|
|
|
|
|
|
- **Compone**: `Popover` (desktop) / `Drawer` (móvil) + `Badge` (contador en
|
|
|
|
|
|
el trigger `IconButton`) + `Feed` + `EmptyState` + `Button` («marcar
|
|
|
|
|
|
leídas» — string del app).
|
|
|
|
|
|
- **API**: `<Notifications>` + `.Trigger` + `.Panel` (+`.Header`/`.List`/
|
|
|
|
|
|
`.Empty`/`.Footer`).
|
|
|
|
|
|
- **Trampa conocida**: panel más ancho que el min-width del Popover →
|
|
|
|
|
|
recorte; pasar `width` por el prop tokenizado del `PopoverContent` (la
|
|
|
|
|
|
lección ya está aprendida — no re-descubrirla).
|
|
|
|
|
|
|
|
|
|
|
|
### F3.8 `wizard`
|
|
|
|
|
|
- **Compone**: `Stepper` + `Form` + `Group` de `Button`s (atrás/siguiente/
|
|
|
|
|
|
finalizar) + `Progress` opcional.
|
|
|
|
|
|
- **API**: `<Wizard>` + `.Steps` (Stepper compuesto) + `.Step` (contenido) +
|
|
|
|
|
|
`.Nav`.
|
|
|
|
|
|
- **Fase 0**: leer el API real de `Stepper` y de `Form` (validación por
|
|
|
|
|
|
paso) — el block solo coordina visibilidad de paso + navegación; el
|
|
|
|
|
|
estado de validez lo dicta `Form`.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.9 `error-page`
|
|
|
|
|
|
- **Compone**: `Result` (F1.3) + `Group` de `Button`/`Link` + `SearchField`
|
|
|
|
|
|
opcional (404).
|
|
|
|
|
|
- **API**: `<ErrorPage status=…>` + children de acciones.
|
|
|
|
|
|
- **v1**: 404 · 403 · 500 · offline.
|
|
|
|
|
|
|
|
|
|
|
|
### F3.10 `kanban`
|
|
|
|
|
|
- **Compone**: `DragDrop` + `Grid`/`Group` de columnas + `Card` +
|
|
|
|
|
|
`VirtualList` (columnas largas) + `Badge` + `EmptyState` por columna.
|
|
|
|
|
|
- **⚠️ Fase 0 OBLIGADA**: leer `soma/components/drag-drop` a fondo; el
|
|
|
|
|
|
block mapea sus primitivas reales (zonas, handles, anuncios a11y del
|
|
|
|
|
|
provider). Toda carencia = FLAG (candidata a canon), no workaround.
|
|
|
|
|
|
- **API**: `<Kanban>` + `.Column` (+`.ColumnHeader`) + `.Card` (children
|
|
|
|
|
|
libres). El modelo de datos y la persistencia del orden son del app.
|
|
|
|
|
|
- **Demo**: 3 columnas, drag entre columnas, columna vacía, teclado.
|
|
|
|
|
|
|
|
|
|
|
|
*(Diferidos de F3, registrados en F5: `scheduler` — bloqueado hasta que
|
|
|
|
|
|
`chronos` aterrice; `chat-room` — NO va aquí: la familia `chat-*` es canon y
|
|
|
|
|
|
su roadmap vive en `next-features.md` §7.)*
|
|
|
|
|
|
|
|
|
|
|
|
**Cierre F3**: `blocks-check` verde; demo de `app-shell` montando dentro
|
|
|
|
|
|
`data-table` + `dashboard` + `notifications` + `user-menu` como página de
|
|
|
|
|
|
integración; navegador claro/oscuro/375/1280 + teclado (tab por toda la
|
|
|
|
|
|
página de integración sin trampas de foco).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F4 — Blocks de docs (3)
|
|
|
|
|
|
|
|
|
|
|
|
**Depende de**: F1 completo (prose, anchor-nav, sidebar, callout) + F2.1.
|
|
|
|
|
|
Conecta con la iniciativa docs-corpus=site (el corpus es la semilla del
|
|
|
|
|
|
sitio); estos blocks son su vehículo de UI.
|
|
|
|
|
|
|
|
|
|
|
|
### F4.1 `docs-shell`
|
|
|
|
|
|
- **Compone**: `AppShell` (F3.1, excepción B-10) especializado: `Sidebar`
|
|
|
|
|
|
(árbol de docs) + `Prose` (main) + `AnchorNav` (TOC derecha) +
|
|
|
|
|
|
`Breadcrumb` + trigger de búsqueda (`Command` — ya existe) + prev/next
|
|
|
|
|
|
(`Group` de dos `Card`/`Link`).
|
|
|
|
|
|
- **API**: `<DocsShell>` + `.Nav` + `.Article` (prose) + `.Toc` + `.Search`
|
|
|
|
|
|
+ `.PrevNext`.
|
|
|
|
|
|
- **v1**: 3 columnas desktop → TOC colapsada y sidebar-drawer en móvil.
|
|
|
|
|
|
|
|
|
|
|
|
### F4.2 `code-showcase`
|
|
|
|
|
|
- **Compone**: `Tabs` (Preview/Code) + `Surface` (lienzo de preview) +
|
|
|
|
|
|
`CodeBlock` + `Clipboard` + `Toolbar` opcional.
|
|
|
|
|
|
- **API**: `<CodeShowcase>` + `.Preview` (children vivos) + `.Code`
|
|
|
|
|
|
(children CodeBlock).
|
|
|
|
|
|
- **v1**: preview + código + copiar. (Chips de tema/dirección/densidad del
|
|
|
|
|
|
lienzo = Gap, notado — pediría servicios, D-BLK.6.)
|
|
|
|
|
|
|
|
|
|
|
|
### F4.3 `props-table`
|
|
|
|
|
|
- **Función**: tabla de referencia de un componente **generada del morfo** —
|
|
|
|
|
|
parts, data-attrs, ARIA, keyboard y eventos salen de `compileMorfo`
|
|
|
|
|
|
(ventaja estructural única de este framework: el contrato ES
|
|
|
|
|
|
introspectable).
|
|
|
|
|
|
- **Compone**: `Table` + `Code`/`Kbd` + `Badge`.
|
|
|
|
|
|
- **API**: `<PropsTable morfo={…}>` (aquí el input data-driven es legítimo:
|
|
|
|
|
|
el "árbol" es el contrato canónico, no contenido inventado).
|
|
|
|
|
|
- **v1**: tablas morfo-backed (parts/attrs/keyboard/eventos con
|
|
|
|
|
|
familia·verbo·intent). Tabla de props TS = **Gap tracked** (requiere
|
|
|
|
|
|
extracción de tipos; no improvisar — flag para decidir tooling).
|
|
|
|
|
|
|
|
|
|
|
|
**Cierre F4**: una página de docs REAL montada con los 3 (un doc del corpus
|
|
|
|
|
|
renderizado en `docs-shell` con showcase y props-table de un componente
|
|
|
|
|
|
PASS), verificada en navegador.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## F5 — Backlog condicionado (no construir sin disparador)
|
|
|
|
|
|
|
|
|
|
|
|
Componentes canon candidatos (cada uno entraría por la ruta de 9 fases):
|
|
|
|
|
|
|
|
|
|
|
|
| Pieza | Disparador |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `lightbox` (visor imagen zoom/galería) | cuando un block de media/galería real lo pida |
|
|
|
|
|
|
| `tour` (onboarding spotlight) | cuando el app-shell tenga consumidor real con onboarding |
|
|
|
|
|
|
| `hover-card` genérico | cuando un tercer caso no-URL aparezca (hoy `link-preview` cubre) |
|
|
|
|
|
|
| `description-list` | primer detail-view real (pareja natural de `data-table`) |
|
|
|
|
|
|
| `loading-overlay` | primera pantalla con carga bloqueante real |
|
|
|
|
|
|
| `transfer-list` | primer admin real con asignación dual |
|
|
|
|
|
|
| `mention` | ya registrado en `next-features.md` §7 (chat) — no duplicar aquí |
|
|
|
|
|
|
| `masonry` · `bottom-nav` · `swipe-actions` · `pull-to-refresh` · `image-compare` · `marquee` (¿pack?) · `watermark` · `signature-pad` | demanda real; varios son candidatos a pack, no a canon — decidir con la regla de admisión de packs |
|
|
|
|
|
|
| art `shortcuts` (registro global de atajos + cheat-sheet con `Kbd`) | cuando ≥2 consumidores reales (command palette global + docs) lo pidan |
|
|
|
|
|
|
|
|
|
|
|
|
Blocks diferidos: `scheduler` (bloqueado por `chronos`) · `logo-cloud`
|
|
|
|
|
|
(bloqueado por decisión marquee) · `billing` · `file-manager` ·
|
|
|
|
|
|
`profile-card` (cuando haya consumidor).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Verificación de cierre del plan (definition of done global)
|
|
|
|
|
|
|
|
|
|
|
|
1. `npm run check` sin regresión; `npm run blocks:check` verde; `npm run
|
|
|
|
|
|
component:audit` → los 7 de F1 PASS (o NEEDS-WORK solo por D-* de la fase
|
|
|
|
|
|
demos global); `docs:check` verde.
|
|
|
|
|
|
2. Las tres páginas de integración (F2 landing · F3 app · F4 docs) renderizan
|
|
|
|
|
|
compuestas SOLO de canon+blocks, verificadas visualmente en claro/oscuro,
|
|
|
|
|
|
375/1280 y teclado.
|
|
|
|
|
|
3. Doctrina publicada y enlazada (architecture/blocks.md en el mapa; CLAUDE.md
|
|
|
|
|
|
al día); cada block con README B-9 completo.
|
|
|
|
|
|
4. Ningún gap silenciado: todo ❌/⚠️ de las fases 0 tiene decisión firmada o
|
|
|
|
|
|
fila en F5/next-features.
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Registro
|
|
|
|
|
|
|
|
|
|
|
|
- 2026-07-21 — Plan creado (análisis de ecosistema + decisión de usuario:
|
|
|
|
|
|
tier `blocks`). Iniciativa en `next-features.md` §8.
|
|
|
|
|
|
- 2026-07-21 — **F0.1 HECHA**: las 7 D-BLK firmadas (repaso en sesión).
|
|
|
|
|
|
D-BLK.1 **enmendada** frente a la propuesta: el tier vive en
|
|
|
|
|
|
`src/uix/blocks/` (no `src/blocks/`); referencias del plan actualizadas
|
|
|
|
|
|
(tabla de tiers, B-4, blocks-check, F0.3/F0.4). Resto firmado como
|
|
|
|
|
|
propuesto. Siguiente: F0.2 (doctrina en `architecture/blocks.md`).
|
|
|
|
|
|
- 2026-07-21 — **F0 CERRADA** (misma sesión): doctrina
|
|
|
|
|
|
`docs/architecture/blocks.md` · alias `$blocks` (vite + svelte.config +
|
|
|
|
|
|
CLAUDE.md) · `src/uix/blocks/README.md` (mapa + template B-9) ·
|
|
|
|
|
|
`scripts/blocks-check.ts` + `npm run blocks:check` (self-testing) ·
|
|
|
|
|
|
galería `web/routes/blocks/` con layout de bootstrap propio
|
|
|
|
|
|
(`+layout@.svelte`, espejo mínimo del de `/uix`, sin packs sema) · fila
|
|
|
|
|
|
E1 en `docs/README.md`. Nota de commit: `docs/README.md` y
|
|
|
|
|
|
`docs/next-features.md` quedaron FUERA del commit de F0 — ambos traían
|
|
|
|
|
|
diff foráneo de otra sesión imposible de separar por staging de archivo
|
|
|
|
|
|
completo; sus hunks propios (fila E1 + §8) viajan con el árbol de trabajo
|
|
|
|
|
|
hasta que su dueño commitee. Siguiente: **F1** (orden recomendado:
|
|
|
|
|
|
`empty-state` → `result` → `callout` → `sticky` → `prose` → `anchor-nav`
|
|
|
|
|
|
→ `sidebar`).
|