You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/CONTINUE-blocks-commerce.md

23 KiB

CONTINUE — Familia commerce del tier blocks

Kickoff para sesión nueva: «Lee docs/process/CONTINUE-blocks-commerce.md y 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:

  1. No existía una sola línea de dominio commerce en el repo. Barrido de cart · checkout · product · sku · order · basket · storefront · payment sobre src/ · web/ · scripts/: nada. Lo único adyacente es el block pricing, que es comparativa de planes SaaS y cuyo README declara explícitamente que no formatea moneda. billing figuraba diferido en F5 de PLAN-blocks.md.
  2. 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.
  3. 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.


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:

  1. ⚠️ $commerce sería el primer art de DOMINIO. Los 25 actuales son infraestructura transversal (adom, color, motion, prefs…). O se acepta explícitamente y se anota en src/arts/README.md, o se enmienda la doctrina de arts. No se cuela en silencio. src/svrs/commerce no sirve: B-4 no admite $svrs desde un block.
  2. 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 en EngineAgent. Candidato: src/libs/money, junto al src/libs/currency que 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 …StateInput con campos readonly · resolve…State(input) como escalera de guardas cuyo ORDEN es la prioridad · can…(state): boolean · dos Record<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); Symbol privado del módulo; set…Context / get…Context (el getter devuelve | undefined y cada parte guarda).
  • …-reason.svelte — pinta la frase y la anuncia por uix.announce (el segundo servicio que el contrato B sanciona), sólo al cambiar, con un let announced = '' que NO es $state (es un libro de efectos, no estado reactivo). aria-describedby es 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, …_REASON es 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:

  1. Cero cadenas horneadas. La lista de ~20 props traducibles que ProductDetail de BigCommerce Catalyst exige es la checklist: ninguna de ellas existe aquí.
  2. El cuarteto de estados en todo block con datos: poblado · cargando · vacío · error.
  3. Ningún estado bloqueante mudo — la propiedad ya está fijada por test en contact/newsletter; se hereda.
  4. Tabla de cumplimiento por block, generada del morfo: qué criterios WCAG y qué artículos satisface.
  5. 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, el scrollLeft negativo del carrusel y los chips de filtro.
  • Todo evento con consecuencia monetaria es commit con intent real: añadir = positivo, quitar = negativo con deshacer, seleccionar agotado = signal-threat. Y el stepper suena por repetición (step por emisión), así que la velocidad del gesto ES la velocidad del trinquete.
  • Form.Submit estampa su propio aria-label gené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 en contact (A-04).
  • Bajo validación progressive se pregunta al ESQUEMA (validateSync), nunca a form.isValid — ahí significa «aún no se ha encontrado nada mal», y un formulario vacío se declara válido. Y se deja pasar invalid al 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.md y los 8 hallazgos de canon congelados (F15–F22 en PLAN-blocks-quality.md). Muerden aquí en concreto: Card sin prop de elevación, la ranura contrast blanca sobre todo escalón sólido, y el aria-label incondicional del canon.
  • Solape con F3: data-table (F3.3) y wizard (F3.8) son parientes de order-history y checkout. No bloquea — commerce compone Table + $libs/datagrid y Stepper directamente, igual que haría F3 — pero cuando F3 llegue habrá que decidir si order-history es data-table configurado 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:

  1. Escribir el dossier docs/process/RESEARCH-commerce-references.md con las 7 pistas y las marcas de §10.
  2. Escribir el plan de ejecución docs/process/PLAN-blocks-commerce.md con la ficha por ítem (Función · Compone · API · Layout/landmark · v1 · Demo).
  3. Añadir el grupo «Commerce» a web/routes/blocks/_lib/catalog.ts con los 26 slugs en shipped: false.
  4. 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.

Powered by TurnKey Linux.