feat(blocks): el arco semántico completo — contacto al pulsar, commit al elegir

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 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 2 months ago
parent 19e8dbfb63
commit ceab195ae1

@ -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<string | null>(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}
</Button>
</Pricing.PlanAction>
</Pricing.Plan>

Loading…
Cancel
Save

Powered by TurnKey Linux.