19 KiB
Aura
The agent presence surface — the eidos face of the canonical materializer
of the delegate family (book ch. 29: ¿quién actúa ahora?). Soma owns the
custody-of-control state, the axis's one polite live region and the global
exit; Eidos adds the living orb (a $scene aurora modulated by state) and the
size axis. Doctrine:
docs/architecture/agent.md §7.
Baseline
- Classification: interactive (a
Canceltrigger). Composes the soma headless provider ($soma/components/aura) over a structural agent port; with no agent the surface is a valididle(§0 degradation). - Anatomy:
Aura(root,data-state) →.Ring(the custody ring — it WRAPS the Indicator) →.Indicator(the orb,aria-hidden; the$sceneeffect paints inside) +.Status(state + detail, two lines) +.Region(the axis's ONErole="status"polite live region) +.Cancel(the global exit, composes<Button>via asChild). The recipe's radial gradient is the static truth ONLY where no canvas mounted (:has(canvas)drops it): with the live render up, a filled disc behind a contour just muddies its silhouette. - Two axes:
size(sm | md | lg — orb + label scale, orb derived from the Avatar primitive--avatar-size-*) and the semanticdata-statetint (soma-owned; the recipe maps it to role slots — reviewing = risk, escalated = threat, acting/offered/planned = primary, idle = neutral). Aura has nocolorprop by design: the tint is evaluative, not decorative; a theme re-tints via the--aura-orb-color-*role tokens. - The orb is the D4 consumer:
.Indicatorprefers the shareduix.sceneengine (global concurrent-scene budget) with a per-dom fallback outside a uix tree; under reduced motion the engine's mandatorystatic-framepolicy freezes a painted frame — presence never disappears. - The orb is a CONTOUR (
thickness: 0.9): the core is hollow at every value and what shows through it is the page. The effect used to blend with abackgroundColorand derive alpha from the max channel, which painted an opaque disc of surface colour in the middle (a light theme got a filled circle, a dark one the seed's deep blue). Nowdraw()returns the rim's own alpha, so the hole is real on ANY ground and the light/dark branch is gone. - The orb is PAINTED in the role's colours (
colorA=solid,colorB=solid-hover,colorDeep=element), never hue-rotated: the shader rotates in YIQ, so a delta measured in OKLCH lands somewhere else entirely —riskused to read GREEN next to its own orange ring. Tokens resolve througheidos.resolveTokenand travel as concrete hex, which is exactly the seam$scene/effects/color.tsdocuments.
Talla y tema
37 clave(s) pública(s) en lib/recipes/base.ts (bloque aura), con sus pasos por talla y el nombre RESUELTO que consume la receta.
Es el contrato vivo: cada clave es un --{componente}-{ranura} que un tema
puede escribir, y el guard R-5.4 comprueba que ninguna miente.
Su demo todavía no tiene pestaña
Tokens(el paso 11 del protocolo §7 llegó después de que este componente cerrara): la tabla de aquí es la fuente hasta que la tenga.
| Grupo | Claves |
|---|---|
escalas |
status-font-size-md · status-line-height-md · status-letter-spacing-md · status-font-size-lg · status-line-height-lg · status-letter-spacing-lg · status-detail-font-size-md · status-detail-line-height-md · status-detail-letter-spacing-md · status-detail-font-size-lg · status-detail-line-height-lg · status-detail-letter-spacing-lg · orb-size-sm · orb-size-md · orb-size-lg |
raíz |
idle-opacity · orb-radius · gap · orb-color-idle · orb-color-offered · orb-color-planned · orb-color-reviewing · orb-color-acting · orb-color-escalated · orb-halo-alpha · ring-scale · ring-track-fg · ring-track-width · ring-arc-width · ring-halo-width · ring-halo-opacity · status-fg · status-weight · status-gap · status-detail-fg · transition-duration · transition-ease |
Lo que el guard R-5.4 da por silencioso, con su razón medida
(scripts/theming-sentinel-exceptions.ts):
orb-color-idle— the canvas covers the disc by design; removed in place -> reaches (oklch(0.6434 0 0) -> rgb(1,2,3) inside the radial-gradient)orb-color-offered— same disc, same passorb-color-planned— same disc, same passorb-color-reviewing— same disc, same passorb-color-acting— same disc, same passorb-color-escalated— same disc, same passorb-halo-alpha— the outer stop of the same gradient; removed the canvas -> the gradient changesring-arc-width— the arc slot is not mounted in the idle demo; mounted in place -> reaches (2px -> 1234px)ring-halo-width— the halo slot is not mounted; mounted in place -> reaches (1px -> 1234px)ring-halo-opacity— same slot; mounted in place -> reaches (0.5 -> 0.123)transition-duration— the guard freezes transitions on purpose; the orb tint and the arc stroke transition on it
Comparativa
Agent-presence-with-a11y is a near-empty landscape — no shipping framework has a perceptual surface for the delegation cycle. The refs are adjacent, not equivalent; the comparativa maps what each does and what Aura adds.
| Ref | Equivalente | Qué adoptamos | Qué no |
|---|---|---|---|
| ChatGPT / Claude "thinking" indicator | pulsing dot while the model works | la continuidad pensando → actuando como estado perceptible | indicador SOLO visual, sin región viva ni máquina de estados — un usuario de lector de pantalla no sabe que el agente actúa |
| CopilotKit activity / generative-UI | render de actividad de tool en el transcript | la idea de superficie de presencia del agente | acoplado al chat (no hay presencia sin conversación); sin semántica de «quién actúa» ni a11y dedicada |
Vercel AI SDK useChat.status |
submitted / streaming / ready |
mapear el estado del run a una superficie | un string de estado, no un canal perceptivo (sin sonido/motion/hold ni anuncio atribuido) |
| Presencia colaborativa (Figma / Google Docs) | «X está editando» (cursores/avatares) | el precedente a11y de anunciar a otro actor (live region, «quién») | modela HUMANOS concurrentes, no el ciclo de delegación (offer→review→act→return) ni la autoridad/autonomía |
| assistant-ui | estado running por mensaje |
— | estado por-mensaje, no una presencia del agente con salida global y ciclo tipado |
Lo que Aura añade y nadie tiene (informe §8.3): una semántica perceptiva
de la delegación (delegate + sustain con sonido/motion/hold), anuncios
atribuidos y coalescidos por plan (WCAG 4.1.3), reduced-motion que conserva
la presencia (static-frame, nunca hide), y una salida global por teclado.
Decisiones
-
Sin prop
color: el tinte es la valoración del estado (evaluativo), no decoración — cargarcolorinvitaría al antipatrón «marco rojo». El tema re-tinta los slots de rol--aura-orb-color-*. -
expression: 'family-default': el perfil sonoro bajo dedelegatees decisión firmada de capa 1 (los canales fuertes aparecen en composición con signal/commit); la modulación por intent reservada en scene §F6 (velocidad/amplitud/hue ≈ pitch/gain/contour) es VISUAL → recipe +$scene, no un pack de sonido. -
Orb = Avatar: el tamaño deriva de la primitiva canónica del disco de presencia/identidad (
--avatar-size-{sm,md,lg}= 32/40/48); un tema que retoca avatares retoca el orb. (Los 14/20/28 iniciales eran magic numbers inventados — corregido.) -
Anillo de custodia (D1, 2026-07-22): el estado se lee como FORMA, no solo como tinte — raíl punteado en reposo, un arco que respira al ofrecerse, segmentos = pasos del plan (late), dos arcos enfrentados y QUIETOS esperando tu revisión (la quietud lo separa de
acting), un arco que gira actuando, y anillo cerrado con pulso saliente cuando te necesita. Ningún estado depende del color. El movimiento continuo son loops registrados (data-animation-style="spin|pulse|ping"), nunca@keyframesde receta (R-4.5): vienen con su puerta de reduced-motion, donde el dash sigue distinguiendo el estado. El atributo va en los hijos del SVG, jamás en la parte —motionAttrs()estampa ademásdata-state, que en ese subárbol es del morfo. -
Variante
portal(2026-07-28, encargo del usuario): el mismo ciclo leído como una ESFERA a contraluz — estelas de luz que la envuelven y giran, cada capa sobre su eje inclinado y con su ritmo, con motion blur a lo largo del barrido. La elige el desarrollador: no sustituye alorb, convive con él.orbestá afinado para 32–48px (silueta simple, movimiento perceptible a ese tamaño);portaltiene mucho más detalle y luce en superficies grandes — a tamaño de componente pierde la irregularidad que lo define. La demo trae un switch studio (220px) precisamente para poder juzgar eso.Implementación propia (
$scene/effects/portal), no un port: la referencia del look es un componente de pago cuya fuente no está disponible, y la regla de la casa son implementaciones propias de todos modos.Los tres colores salen de los MISMOS slots de rol que el orb —
soliden el filo,solid-hoveren la cara cercana,elementen la que se aleja — así que un tema los retinta a la vez. El interior es TRANSPARENTE: no se pinta nada dentro, así que lo que se ve a través es la página. Eso es lo que hace la variante agnóstica al tema sin una sola rama claro/oscuro — un interior relleno tendría que elegir bando, y elegirlo fue lo que en el primer intento la convirtió en una bola de cristal.Modulación §F6 propia:
speed= ritmo del barrido (pitch),layers= cuántas capas vuelan,density= cuán apretadas,brightness= gain;escalatedgana capas Y corre caliente — forma y energía juntas, nunca color solo (AG-44).Tres lecciones que costaron render, anotadas porque son del FORMATO y no de este efecto: (1) las estelas no pueden salir de senos — un
sin()da anillos de latitud perfectamente regulares que se leen como jaula de alambre por mucho que se afinen; la irregularidad exige ruido anisótropo (aquí 1 : 9 en longitud/latitud). (2)sin(q.y * freq)sobre la esfera unidad dafreq/πciclos: ~9 son tres trazos finos por capa, 22 es malla y 2 es un borrón — el número sale de la cuenta, no del tanteo. (3) el motion blur promedia: 5 muestras × 0.16 rad son 46° de barrido, que borra las bandas en vez de estirarlas. -
Variante
orbpor defecto (2026-07-22, decisión del usuario): el render vivo del indicator es el efectoorbde$scene— presencia esférica con anillo respirando (ruido) + luz orbitando; movimiento de silueta perceptible a 32–48px, donde el campoaurora(banda horizontal recortada al círculo) leía estático.auroraqueda como variante (<Aura.Indicator variant="aurora">) para superficies grandes. Modulación §F6 del orb: el estado fija la ENERGÍA (forceHoverStatesostiene la distorsión,hoverIntensityes el gain,rotateOnHoveracumula rotación en acting/escalated) y el rol da el matiz como DELTA de hue OKLCH sobre la identidad del shader (primary; reviewing=risk, escalated=threat). El puntero deforma el orb (el hover machine del efecto) — la presencia es táctil además de visible. -
Cancel compone
<Button>vía asChild (nunca el<button>nativo del soma);variant/size/colordiferidos a los defaults del Button. -
La Region es sr-only en el recipe (present para AT, silenciosa a la vista); el orb es la presencia visible.
Status: dos líneas
El estado lleva el peso (--aura-status-fg = content-primary,
--aura-status-weight = medium) y el detalle recede
(--aura-status-detail-fg = content-subtle, un escalón menos de
tamaño). Es la mitad de D1 que faltaba: el anillo dice el estado como FORMA,
pero la respuesta a «¿quién actúa?» es texto, y una palabra gris de 13px junto
a un orbe vivo no la daba. Los dos renglones escalan con data-size.
R-1.5 exception: the only focusable part is Cancel, which composes
<Button> — the focus-ring is owned by the Button recipe (THM §32). The orb,
status and region are non-interactive, so the aura recipe declares no focus
treatment of its own.
Patrón: Aura al centro de un OnionMenu (medio anillo, luminoso)
Composición APP-LAND canónica (doctrina agent.md §7 — el framework da
estados + afordancias, el app da forma a la superficie de review): el orb es
el trigger central de un <OnionMenu placement="center"> y las
afordancias de custodia aparecen como sectores CONTEXTUALES por estado; el
menú se abre solo cuando la custodia espera a un humano. Vivo en
/active/docs/agnt (§Real model).
El anillo abre 180° (placement="bottom-center") en vez de 360°: dos
sectores de 90° hacia arriba, donde el ojo ya está y sin tapar lo que se
revisa. El trigger va sin chrome (data-chrome="bare": sin disco, sin
sombra FAB, sin salto en hover) porque el orbe ES el trigger, no su contenido.
Y los sectores usan look="luminous" — filo de luz en vez de relleno — para
que el menú hable el idioma del orbe y no le pegue dos cuñas planas al lado.
<Aura {agent}>
<OnionMenu bind:open placement="bottom-center" look="luminous"
innerRadius={44} ringWidth={40}>
{#snippet trigger()}
<Aura.Ring><Aura.Indicator /></Aura.Ring>
{/snippet}
<OnionLayer>
{#if run?.state === 'reviewing'}
<OnionOption label="Authorize" color="affirm" onSelect={authorize} />
<OnionOption label="Reject" color="loss" onSelect={reject} />
{:else if run?.state === 'escalated'}
<OnionOption label="Approve" color="affirm" onSelect={approve} />
<OnionOption label="Deny" color="loss" onSelect={deny} />
{:else if active}
<OnionOption label="Stop" color="neutral" onSelect={stop} />
{/if}
</OnionLayer>
</OnionMenu>
<Aura.Status />
<Aura.Region />
</Aura>
<!-- custody waits => the ring opens itself; it collapses when control
RETURNS (a transition), not whenever there is no run — at rest the ring
stays available, because opening it is how you delegate. -->
$effect(() => {
const s = run?.state;
if ((s === 'reviewing' || s === 'escalated') && !open) open = true;
if (wasActive && !active) open = false;
wasActive = active;
});
Never toggle open from onTriggerClick: the root already owns open and
has toggled it before that callback fires — doing it again cancels it out and
the ring never shows.
Colour is the axis's vocabulary, one hue = one meaning (a hue that means
two things on the same surface destroys self-perception): gray = no agency ·
orange = your review · purple = the agent acts · red = the standing
escalated claim, exclusive · teal = your yes. So the authority ring reuses
the custody colours with the SAME meaning (Review = risk because you will live
in reviewing; Auto = primary because it acts), and declines go neutral —
canon-aligned too, since a cancel's intent is absent, not loss.
Sema events
Six — the delegate cycle (first eidos component to react to the family):
sustain-processing (stateBound; orb tint + scene energy while acting),
delegate-offer, delegate-escalate (untilAction), and three terminals
picked by the run outcome: delegate-return (fulfill) ·
delegate-return-aborted (no intent) · delegate-return-failed (loss).
The terminals target the Ring and are the first delegate firmas in the
motion registry (BUILTIN_SIGNATURES): fulfilled pulses like a fulfilled
commit, failed plays return-recoil — a dip + dim + recovery. The loss firma
is motion, not tint (GUIA §11: loss exits by withdrawal): no new hue
appears, because red belongs exclusively to the standing escalated claim and
loss's plum sits 16° from primary in the default theme. aborted stays
silent on purpose. The Ring carries border-radius: full so the box-shadow
pulses land circular. Full table in the soma README
($soma/components/aura).
Gaps
| Gap | Disposition |
|---|---|
| Presets de escena por estado (carácter de aurora distinto más allá de speed/amplitude/hue) | diferir — la modulación actual cubre el ciclo; se especializa cuando el diseño lo pida |
Superficie de elicitación en escalated (pregunta tipada vía Form.AutoFields) |
construible ya (desbloqueada 2026-07-23: no depende de superficie conversacional — PLAN-agent §5b). Falta la costura en el motor: la escalada no lleva la pregunta y resolveEscalation no lleva la respuesta |
| Variante compacta/inline (solo orb, sin status/cancel) para toolbars | diferir — hasta que un host real lo pida (la barra de admisión) |
Prop color decorativo |
descartar — el tinte es evaluativo (estado del run); un color invitaría al antipatrón «marco rojo». El tema re-tinta los slots de rol |