From ceab195ae1ea373492581e503b4f4bdca7cbf8cc Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 28 Jul 2026 22:54:15 +0200 Subject: [PATCH] =?UTF-8?q?feat(blocks):=20el=20arco=20sem=C3=A1ntico=20co?= =?UTF-8?q?mpleto=20=E2=80=94=20contacto=20al=20pulsar,=20commit=20al=20el?= =?UTF-8?q?egir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elegir un plan ya no era solo un click: ahora demuestra el momento `--event` entero, que es lo que ninguna referencia puede shippear. - Pulsar emite `contact-activate` — gratis, por composición: es el evento del morfo del propio `Button`. Eso ya funcionaba en todos los bloques. - Lo que faltaba es la CONSECUENCIA: la app emite `commit-select` con intent `affirm` (CANON §2: una elección activa es afirmativa) vía `uix.events`. Un block no puede emitir eventos propios —no tiene morfo, contrato B—, así que la consecuencia es necesariamente app-land; la demo la ejerce para que el arco se vea. El commit va DESPUÉS del resultado (`await tick()`), no en la pulsación. Es la doctrina (un commit es lo que quedó fijado, no el contacto) y además evita una colisión real que medí: emitidos a la vez sobre el mismo elemento, la proyección del contacto pisa la del commit. Verificado con MutationObserver usando `attributeOldValue` —sin él la medición MIENTE, porque el callback se agrupa en microtarea y `getAttribute` ya devuelve el valor sobrescrito—: la secuencia es `contact-activate` → `commit-select` (family `commit`, intent `affirm`), cero errores de página. `blocks:check` verde (7) · `svelte-check` sin errores propios. Co-Authored-By: Claude Opus 4.8 --- web/routes/blocks/pricing/PricingSite.svelte | 34 +++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/web/routes/blocks/pricing/PricingSite.svelte b/web/routes/blocks/pricing/PricingSite.svelte index aeb4e4ed4..04cb42202 100644 --- a/web/routes/blocks/pricing/PricingSite.svelte +++ b/web/routes/blocks/pricing/PricingSite.svelte @@ -13,6 +13,37 @@ import { Badge } from '$uix/eidos/components/badge'; import { Button } from '$uix/eidos/components/button'; import { Link } from '$uix/eidos/components/link'; + import { tick } from 'svelte'; + import { getActiveUix } from '$active-uix'; + + const uix = getActiveUix(); + + // The full semantic arc of choosing a plan. Pressing already emits + // `contact-activate` (the Button's own morfo event, for free by composition); + // what NO reference can express is the CONSEQUENCE — so the app emits the + // `commit.select` that says the choice was fixed. `affirm`, per CANON §2: an + // active choice is affirmative. This is app-land on purpose: a block has no + // morfo and cannot invent the outcome of the app's own action. + let chosen = $state(null); + + async function choose(plan: string, event: MouseEvent) { + const target = event.currentTarget as HTMLElement; + chosen = plan; + + // The commit is the CONSEQUENCE, not the press: pressing already emitted + // `contact-activate` (the Button's own morfo event). We wait for the + // outcome to land and only then say what was fixed — which is also what + // keeps the two signals from colliding on the same element (emitting both + // synchronously, the contact projection overwrites the commit's). + await tick(); + void uix.events?.emit({ + target, + name: 'commit-select', + family: 'commit', + intent: 'affirm', + message: plan + }); + } type Plan = { name: string; @@ -103,8 +134,9 @@ block variant={plan.featured ? 'solid' : 'surface'} intent={plan.featured ? 'fulfill' : 'affirm'} + onclick={(event) => choose(plan.name, event)} > - {plan.cta} + {chosen === plan.name ? 'Plan elegido' : plan.cta}