23 KiB
CONTINUE — Familia commerce del tier blocks
Kickoff para sesión nueva: «Lee
docs/process/CONTINUE-blocks-commerce.mdy continúa la fase que toque.»Estado 2026-08-10: DISEÑO CERRADO, CERO CÓDIGO ESCRITO. Las decisiones D-COM.1…5 están firmadas por el usuario. La fase C0 (dossier + plan de ejecución + grupo en el catálogo + entrada en
next-features.md) está pendiente: este handoff es lo único que existe todavía.
0. Qué es esto y por qué existe
Una familia de 26 blocks de ecommerce más 8 componentes de canon y un
art nuevo $commerce, diseñada con el mismo método con que nació el tier:
analizar lo hecho → comparativa contra las referencias reales de cada
ecosistema → fijar suelo de paridad → producir la lista.
El punto de partida, en tres hechos:
- No existía una sola línea de dominio commerce en el repo. Barrido de
cart · checkout · product · sku · order · basket · storefront · paymentsobresrc/ · web/ · scripts/: nada. Lo único adyacente es el blockpricing, que es comparativa de planes SaaS y cuyo README declara explícitamente que no formatea moneda.billingfiguraba diferido en F5 dePLAN-blocks.md. - El canon ya resuelve casi todo lo que un catálogo commerce exige (§3), y por eso la lista de huecos es corta y cada uno tiene motivo escrito.
- La doctrina de coordinación (2026-07-31) es justo lo que commerce pide. Hoy sólo 3 de 15 blocks coordinan y sólo 2 tienen máquina de estado. Commerce invierte esa proporción — 14 de 26 coordinan — porque casi todo lo que se vende puede estar bloqueado. El diseño arranca por el estado, nunca por el layout.
1. Puerta de lectura — antes de tocar nada
Precondición dura, no sugerencia. Leer directo, nunca delegando la lectura a agentes.
- Siempre:
CLAUDE.md(llega solo) · este handoff. - Contrato del tier:
architecture/blocks.md(B-1…B-11, la regla de admisión, la sección «Coordination»). - Para un componente de canon:
building-a-component.md(la ruta de 9 fases, LA puerta) +guides/component-guide.md(§Build contract + reglas A1–A37) +guides/completion-checklist.md(la matriz de aceptación). - Vocabulario cerrado:
canon/vocabularies.md(generado del código — archetypes, familias, verbos, intents, holds). - Contratos transversales:
canon/tsc.md·canon/recipe-contract.md·canon/direction-contract.md. - Para el ala agéntica:
architecture/agent.md(§5 el primitivo de actor, §7 la superficie de revisión). - Y el README de CADA componente que el block compone. El mapa de composición se escribe leyendo, no de memoria.
2. Las decisiones firmadas (D-COM.1…5, 2026-08-10)
| # | Decisión | Consecuencia operativa |
|---|---|---|
| D-COM.1 | El estado del carrito vive en un art nuevo $commerce (EngineCommerce/ActiveCommerce + port de transporte que suministra el app), espejo de $agent/AgentTransport |
Un carrito vive en cabecera, PDP, carrito y checkout: es estado inter-block, y B-10 prohíbe que un block importe otro. B-4 deriva $<art> de src/arts/*, así que la carpeta lo allowlista sola |
| D-COM.2 | Alcance: alas A+B+C+D+E (26 blocks). Admin FUERA. B2B diferida con disparador | Admin lo cubre F3 (data-table, dashboard, settings), y ningún design system serio shippea commerce admin. B2B se registra en next-features.md |
| D-COM.3 | La ley europea entra en el TIPO, más un token jurisdiction en el contexto comercial de $commerce |
priorPrice{amount, basis:'lowest-30d'} obligatorio en rebaja · taxIncluded no opcional · unit cuando se vende por medida. No en $prefs: no es preferencia del usuario |
| D-COM.4 | La procedencia por actor es hilo, no apéndice | actor nace en la línea de carrito en C2; el ala E la renderiza al final sin reabrir nada |
| D-COM.5 | Grupo «Commerce» en _lib/catalog.ts; slugs sin prefijo salvo colisión |
cart, no commerce-cart; pero commerce-header, igual que site-header |
Dos cabos abiertos que resuelve la fase 0 de C2, no antes:
- ⚠️
$commercesería el primer art de DOMINIO. Los 25 actuales son infraestructura transversal (adom,color,motion,prefs…). O se acepta explícitamente y se anota ensrc/arts/README.md, o se enmienda la doctrina de arts. No se cuela en silencio.src/svrs/commerceno sirve: B-4 no admite$svrsdesde un block. - El objeto de valor de dinero vive en una hoja por debajo del art y de
eidos, para evitar el ciclo — mismo patrón que el
ActorToken, cuyo tipo vive bajo orca y agent mientras la autoridad de acuñación queda enEngineAgent. Candidato:src/libs/money, junto alsrc/libs/currencyque ya existe.
3. Lo que el canon YA resuelve (no lo reinventes)
Medido sobre los 167 slugs de src/uix/eidos/components/, 100 con pila
completa morfo+soma+eidos:
| Necesidad | Se compone con |
|---|---|
| Rejilla de producto | AutoGrid / CardGroup (ya es rejilla seleccionable) + Card + AspectRatio + Image + Badge |
| Estrellas | RatingGroup (allowHalf · hoverPreview · clearable · readonly · valueText) |
| Cantidad | NumberField (min/max/step/largeStep · clampOnBlur · Increment/DecrementTrigger · Scrubber) |
| Dinero | FormatNumber (formatStyle="currency") + Text numeric="tabular" |
| Divisa y conversión | $format.currency: setCurrency · format · formatAs · convert/convertAs + createRates({ fetchRate }). Ninguna referencia del mundo tiene esto |
| Galería | Carousel + Image + AspectRatio (falta zoom/lightbox → §4) |
| Variantes | RadioCards · ToggleGroup · ColorSwatch (falta el eje de disponibilidad → §4) |
| Checkout | Stepper (linear) + Form + Field + Form.AutoFields + esquema SIUM |
| Carrito | Drawer + Table/GridList + NumberField + EmptyState |
| Facetas | Accordion + CheckboxGroup + Slider type="multiple" (ES el rango de precio) + TagGroup + Combobox |
| Pedidos | Table + $libs/datagrid — sorting multi/server · filtering · selection · pagination · visibility · pinning · row-detail. Sin virtualización |
| Seguimiento | Timeline (align="alternate", Marker, Connector) |
| Stock | Meter (zonas below/above/optimum) + Badge |
| Estados | EmptyState · Result · Callout · Skeleton · Toast · Announce — el cuarteto completo, que ningún catálogo del mercado tiene |
| Envío/peso | $format.units (length · mass · volume + convert) |
| Retail | Barcode (EAN-13/UPC-A/ITF-14…) + QrCode |
| Búsqueda | SearchField (debounceMs, loading) · Combobox · Command |
4. Los 8 huecos de canon (fase C1)
Criterio único: superficie de contrato (evento real, máquina, data-* que
el CSS selecciona, obligación a11y de widget). Todo lo demás es composición y
va al block. Cada uno entra por la ruta de 9 fases.
| # | Componente | Por qué es canon |
|---|---|---|
| C1.1 | price |
La ley vive en el tipo. Obligación a11y real: el tachado debe leerse «antes X, ahora Y», no «X Y» |
| C1.2 | variant-picker |
El ítem de mayor valor de la comparativa. Tres ejes booleanos por opción (exists/available/selected, nomenclatura de Hydrogen) → eidos pinta las cuatro celdas de la matriz desde CSS sin ramas JS |
| C1.3 | quantity-field |
Especialización fina componiendo NumberField (precedente sancionado: NumberField dentro de Knob). Aporta mínimo 1, quitar al bajar de mínimo, tope = stock, estado en vuelo, nombre accesible por producto |
| C1.4 | facets |
La máquina, no las vistas. Data-driven (precedente: Menubar, NavTree). Con la live region que Algolia nunca shippeó — su stats renderiza el conteo como texto plano |
| C1.5 | lightbox |
Ya era candidato de canon en F5 con disparador «cuando un block real de media/galería lo pida». Este es el disparador |
| C1.6 | address-field |
Data-driven sobre metadatos públicos de país: conjunto, orden, etiquetas, regex postal y token autocomplete (WCAG 1.3.5). Requiere $libs/address |
| C1.7 | selection-set |
Wishlist, comparar y lista de requisición B2B son el MISMO proveedor: conjunto sincronizado a URL, con tope, optimista. Construirlo una vez |
| C1.8 | embedded-field |
Envoltorio de iframe de proveedor. Nunca un input de tarjeta — el PAN no entra en nuestro DOM. Aporta la resolución de tokens del tema al otro lado del límite vía eidos.resolveToken() |
Enmiendas de canon, no componentes nuevos: RatingGroup (hoy readonly
conserva role="radio" y cada estrella es parada de tabulación — diez ratings
en un listado son 50 paradas) · Timeline/Stepper (¿estado error? el
seguimiento de un pedido no es camino feliz) · Card (elevación + el
invariante del enlace de tarjeta entera) · Badge (tone que no dependa del
color) · Table (virtualización, gap conocido).
5. Los 26 blocks
Ala A · descubrimiento (7) — commerce-header · product-grid ·
product-listing◆ · category-preview · promo-section · incentives ·
recommendation-slot.
Ala B · ficha (6) — product-gallery · buy-box◆ · product-info ·
reviews◆ · product-qa◆ · product-compare◆.
Ala C · carrito y pago (4) — cart◆ · order-summary◆ · checkout◆ ·
payment-methods◆.
Ala D · post-venta (6) — order-confirmation · order-tracking ·
order-history · order-detail · returns◆ · guest-order-lookup◆.
Ala E · agéntica (3) — cart-review◆ · cart-handoff◆ ·
purchase-authorization◆.
◆ = coordina (máquina de estado + palabras). Son 14 de 26.
Ala F · B2B: DIFERIDA con disparador — quote-request ·
requisition-list · quick-order-pad · aprobación de PO · pago a crédito ·
conmutador de cuenta/sede. selection-set (C1.7) ya la deja medio construida.
6. El patrón de coordinación — cópialo literal
contact y newsletter son los dos ejemplares vivos. La forma exacta:
state.ts— unión exhaustiva de estados (cada miembro con su línea de doc) · interfaz…StateInputcon camposreadonly·resolve…State(input)como escalera de guardas cuyo ORDEN es la prioridad ·can…(state): boolean· dosRecord<State, …>exhaustivos:…_REASON: Record<S, string | null>y…_ACTION: Record<S, string>. Como la clave es el tipo, añadir un estado es un error de tipos hasta que escribes su frase y su etiqueta: un estado que bloquea sin decir por qué no se puede escribir.context.ts— interfaz con getters (para que las lecturas sigan la fuente reactiva),reasonId,t(ref);Symbolprivado del módulo;set…Context/get…Context(el getter devuelve| undefinedy cada parte guarda).…-reason.svelte— pinta la frase y la anuncia poruix.announce(el segundo servicio que el contrato B sanciona), sólo al cambiar, con unlet announced = ''que NO es$state(es un libro de efectos, no estado reactivo).aria-describedbyes mecanismo aparte y aditivo: asociación, no anuncio, y se quieren los dos.state.test.ts— asserts estructurales, no caso a caso:ALL_STATES.filter(can…)· «para todo estado bloqueante,…_REASONes truthy» · el regex del idlangref/^#\?blocks\.<slug>\.[a-z.]+\|.+/.- El estado se deriva UNA vez en la raíz y las partes lo leen. Ninguna
parte recalcula; ninguna inventa su propio
disabled.
Regla de forma del tier — no la re-decidas: una parte compound se gana sólo si se repite (el app mapea sobre N) o coordina (lee el contexto). Si no hace ninguna de las dos, es un slot de snippet en la raíz.
Las palabras se registran en web/routes/blocks/_lib/blocks-langs.ts bajo
blocks.{slug}.* con es/en; el arnés las inyecta en langs.schema desde
_lib/BootUix.svelte. Sin registrar, sale el fallback inglés y no se rompe
nada.
7. Anatomía de fichero
src/uix/blocks/{kebab}/
├── README.md # Function · Composition map · Decisions · Gaps
│ # + el párrafo **Landmark + headings** (B-8)
├── index.ts # export compound + tipos
├── types.ts # props; contenido por snippets
├── {kebab}.svelte # composición: sólo canon, layout por componentes, sin .css
├── {kebab}-{parte}.svelte
└── (si coordina) state.ts · state.test.ts · context.ts
web/routes/blocks/{kebab}/
├── +page.svelte # BlockDemo + controles vivos + pestañas de doc
├── {Name}Site.svelte # el block dentro de una página de contenido REAL
└── preview/
├── +layout@.svelte # `@` resetea el layout; BootUix; lee mode/dir/lang de la URL
└── +page.svelte # sirve {Name}Site desde searchParams
Y flipar shipped: true en web/routes/blocks/_lib/catalog.ts, dentro del
grupo «Commerce». El guard parsea ese fichero por regex
({ slug: '…', … shipped: true }) y comprueba las dos direcciones: un
block sin ficha es invisible para el raíl; un slug publicado sin block es un
enlace muerto.
Los tipos: la raíz extiende Omit<HTMLAttributes<HTMLElement>, 'children'>;
toda otra parte extiende los props del componente de canon que envuelve,
nunca HTMLAttributes (los atributos crudos chocan con el style/class
refinados del canon). Cuando un slot comparte nombre con un atributo HTML
(title, media, width), se hace Omit — «la lección del hero».
8. Verificación
Por componente de canon: npm run component:audit --only {kebab} PASS ·
npm run morfo:check · npm run morfo:vocabulary · eidos-lint ·
npx vitest run src/uix/eidos · npm run perm:check · npm run rtl:check.
Por block: npm run blocks:check verde · npx vitest run src/uix/blocks ·
npm run check sin regresión sobre baseline (medir antes y después: el
baseline se mueve) · npm run docs:check · prettier.
Navegador de verdad, y mirado: claro y oscuro (colorScheme: 'dark'), 375
y 1280, y RTL. Sonda de audio + MutationObserver de data-event* tras la
puerta de hidratación honesta, con un control en el mismo entorno — sin
control, una latencia no es un veredicto.
Acceptance propia de la familia, además del contrato B:
- Cero cadenas horneadas. La lista de ~20 props traducibles que
ProductDetailde BigCommerce Catalyst exige es la checklist: ninguna de ellas existe aquí. - El cuarteto de estados en todo block con datos: poblado · cargando · vacío · error.
- Ningún estado bloqueante mudo — la propiedad ya está fijada por test en
contact/newsletter; se hereda. - Tabla de cumplimiento por block, generada del morfo: qué criterios WCAG y qué artículos satisface.
- Página compuesta de integración por ala (listado → PDP → carrito → checkout → confirmación).
9. Lo que muerde específicamente en commerce
- RTL es EL eje donde commerce rompe, y es un foso real: de doce catálogos
estudiados, exactamente uno anuncia RTL y no tiene ecommerce. Duele en el
orden antes/ahora del precio, la colocación del símbolo de divisa, los
−/+del stepper que no deben espejarse, elscrollLeftnegativo del carrusel y los chips de filtro. - Todo evento con consecuencia monetaria es
commitcon intent real: añadir = positivo, quitar = negativo con deshacer, seleccionar agotado =signal-threat. Y el stepper suena por repetición (steppor emisión), así que la velocidad del gesto ES la velocidad del trinquete. Form.Submitestampa su propioaria-labelgenérico, que gana la computación del nombre accesible: un botón con palabra de estado DEBE pasar el suyo explícito o incumple WCAG 2.5.3. Medido encontact(A-04).- Bajo validación
progressivese pregunta al ESQUEMA (validateSync), nunca aform.isValid— ahí significa «aún no se ha encontrado nada mal», y un formulario vacío se declara válido. Y se deja pasarinvalidal submit, o el camino de error por campo es inalcanzable por construcción. - Deuda heredada que commerce va a encontrar otra vez: las 27 filas todavía
CONFIRMADAS de
AUDIT-blocks-ledger.mdy los 8 hallazgos de canon congelados (F15–F22 enPLAN-blocks-quality.md). Muerden aquí en concreto:Cardsin prop de elevación, la ranuracontrastblanca sobre todo escalón sólido, y elaria-labelincondicional del canon. - Solape con F3:
data-table(F3.3) ywizard(F3.8) son parientes deorder-historyycheckout. No bloquea — commerce componeTable+$libs/datagridyStepperdirectamente, igual que haría F3 — pero cuando F3 llegue habrá que decidir siorder-historyesdata-tableconfigurado o su hermano.
10. Higiene de la comparativa
El dossier de referencia lleva marcas de verificación y hay que respetarlas, por la misma razón que existe el ledger:
- Verificado: el inventario de Tailwind Plus Ecommerce (transcrito categoría a categoría) · la ausencia total de commerce en Untitled UI, en los headless y en los design systems · el desglose de Storefront UI (26 componentes npm / 24 bloques copy-paste) · la ausencia de blocks de ecommerce en todo el ecosistema Svelte · las obligaciones legales de la UE.
- NO verificado, y por tanto no citable: las cifras por categoría de Flowbite (su web es una SPA que devuelve cuerpo vacío) · todas las estadísticas de Baymard (corpus de pago) · la penalización de conversión del checkout en chat · los nombres de campo concretos de los esquemas de AP2.
Regla: una cifra sin fuente leída no entra en una tabla comparativa ni justifica alcance por sí sola. Y nunca se cita el total auto-declarado de un catálogo — se publica la enumeración y el número se deriva de ella. Se observó dos veces que el total declarado y el enumerado no coinciden, y aplica también a nuestras propias cifras.
11. Lo siguiente
C0, en este orden:
- Escribir el dossier
docs/process/RESEARCH-commerce-references.mdcon las 7 pistas y las marcas de §10. - Escribir el plan de ejecución
docs/process/PLAN-blocks-commerce.mdcon la ficha por ítem (Función · Compone · API · Layout/landmark · v1 · Demo). - Añadir el grupo «Commerce» a
web/routes/blocks/_lib/catalog.tscon los 26 slugs enshipped: false. - Registrar la iniciativa en
docs/next-features.md(+ el ala B2B diferida con su disparador).
Luego C2 (el art $commerce, cuya fase 0 resuelve los dos cabos de §2),
y sólo entonces C1 — sus tipos los consume todo lo demás.