uix(aura): el Status en dos líneas — cierra D1

La otra mitad de la dirección firmada: 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. Ahora el Status son dos renglones:

  Esperando tu revisión     ← el estado, content-primary, peso medium
  la custodia es tuya       ← qué significa para ti, secondary, un escalón menos

Sin parte nueva: el Status ya existía y añadir un `Detail` habría sido
abstracción prematura (y una pelea de layout — dos hermanos no apilan sin
envoltorio). El soma renderiza las dos líneas por defecto y expone
`detailText` en los snippet props, así que un consumidor que traiga sus
children sigue mandando.

La copia del detalle es FIJA por estado y localizada como la etiqueta
(es/en): los contadores reales del run (turnos / actos) NO están en el puerto
estructural, y ensancharlo para un subtítulo más bonito es una decisión, no un
defecto. Queda dicho en el README por si algún día se firma.

Tokens: `--aura-status-color` pasa a `content-primary` (era secondary: el peso
informativo estaba en el sitio equivocado), + `-weight`, `-gap` y
`-detail-color`.

Verificado en Chrome real, claro y oscuro, con un run en `reviewing`:
peso 500, primary sobre secondary, y las dos líneas escalando con `data-size`.

Tests 478, eidos-lint 0 invalid / 0 class-hooks, morfo:vocabulary 0 errores,
check en los 73 ajenos preexistentes.

Con esto D1 queda cerrada entera (anillo + jerarquía). Handoff al día.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent e10eed98cc
commit 59f20e2b1f

@ -97,9 +97,6 @@ npm run check # 73 errores = AJENOS preexistentes; 0 propios
ejes F-B. Al abrirse: capacidad tipada contra su API + OCC (\_version) +
write-scope + `allowedFamilies` del primer actuado (reubicada aquí).
3. **Flecos de la superficie** (ninguno bloqueante):
- **Jerarquía tipográfica del Status**: el mockup D1 llevaba DOS líneas
(estado + sub-línea de contexto) y solo se implemento el anillo. Hoy
sigue siendo una línea gris de 13px — la mitad de D1 sin hacer.
- **El pulso de `commit-select` del onion se corta** si el consumidor
cierra el anillo al elegir: el sello dura su hold, pero el nodo ya se
desmontó. Arreglo: retención tipo `Presence` en el cierre.

@ -14,7 +14,7 @@ size axis. Doctrine:
with no agent the surface is a valid `idle` (§0 degradation).
- **Anatomy**: `Aura` (root, `data-state`) → `.Ring` (the custody ring — it
WRAPS the Indicator) → `.Indicator` (the orb, `aria-hidden`; the `$scene`
effect paints inside) + `.Status` (localized state label) + `.Region` (the
effect paints inside) + `.Status` (state + detail, two lines) + `.Region` (the
axis's ONE `role="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
@ -103,6 +103,15 @@ la presencia (`static-frame`, nunca `hide`), y una salida global por teclado.
- **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-color` = `content-primary`,
`--aura-status-weight` = medium) y el detalle recede
(`--aura-status-detail-color` = `content-secondary`, 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

@ -153,19 +153,41 @@
/* ── Status label ───────────────────────────────────────────────────────── */
/* Two lines: the state answers «¿quién actúa?» and carries the weight; the
detail says what that means for you right now and recedes. One grey word
for both made the surface a whisper next to its own orb. */
[data-aura-status] {
display: grid;
gap: var(--aura-status-gap);
color: var(--aura-status-color);
font-size: var(--size-sm-font-size);
line-height: var(--size-sm-font-line-height);
letter-spacing: var(--size-sm-font-letter-spacing);
}
[data-aura-status-label] {
font-weight: var(--aura-status-weight);
}
[data-aura-status-detail] {
color: var(--aura-status-detail-color);
font-size: var(--size-xs-font-size);
line-height: var(--size-xs-font-line-height);
letter-spacing: var(--size-xs-font-letter-spacing);
}
[data-aura][data-size='lg'] [data-aura-status] {
font-size: var(--size-md-font-size);
line-height: var(--size-md-font-line-height);
letter-spacing: var(--size-md-font-letter-spacing);
}
[data-aura][data-size='lg'] [data-aura-status-detail] {
font-size: var(--size-sm-font-size);
line-height: var(--size-sm-font-line-height);
letter-spacing: var(--size-sm-font-letter-spacing);
}
/* ── Live region: present for AT, visually silent ───────────────────────── */
[data-aura-region] {

@ -668,7 +668,10 @@
--aura-ring-arc-width: var(--border-width-medium);
--aura-ring-halo-width: var(--border-width-thin);
--aura-ring-halo-opacity: var(--opacity-50);
--aura-status-color: var(--color-content-secondary);
--aura-status-color: var(--color-content-primary);
--aura-status-weight: var(--font-weight-medium);
--aura-status-gap: var(--space-0-5);
--aura-status-detail-color: var(--color-content-secondary);
--aura-transition-duration: var(--duration-fast);
--aura-transition-ease: var(--ease-default);
--badge-padding-inline-xs: var(--space-1-5);

@ -342,7 +342,12 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'ring-arc-width': 'var(--border-width-medium)',
'ring-halo-width': 'var(--border-width-thin)',
'ring-halo-opacity': 'var(--opacity-50)',
'status-color': 'var(--color-content-secondary)',
// The Status is two lines: the state carries the weight (it is the
// answer to «¿quién actúa?»), the detail recedes.
'status-color': 'var(--color-content-primary)',
'status-weight': 'var(--font-weight-medium)',
'status-gap': 'var(--space-0-5)',
'status-detail-color': 'var(--color-content-secondary)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)'
},

@ -39,6 +39,33 @@ export const auraLangs = {
en: 'Needs your attention'
}
},
// Second line of the Status: what the state means for the user right now.
detail: {
idle: {
es: 'sin delegación',
en: 'no delegation'
},
offered: {
es: 'iniciativa del sistema',
en: 'system initiative'
},
planned: {
es: 'nada ejecutado todavía',
en: 'nothing done yet'
},
reviewing: {
es: 'la custodia es tuya',
en: 'control is yours'
},
acting: {
es: 'en tu nombre',
en: 'on your behalf'
},
escalated: {
es: 'parado hasta que decidas',
en: 'stopped until you decide'
}
},
announce: {
offer: {
es: 'Agente: ofrece encargarse de esto',

@ -48,6 +48,15 @@ export const auraMorfo = {
statusReviewing: '#?components.aura.status.reviewing|Awaiting your review',
statusActing: '#?components.aura.status.acting|Acting on your behalf',
statusEscalated: '#?components.aura.status.escalated|Needs your attention',
// The Status is TWO lines: the state, and what that state means for the
// user right now (who holds control). One word alone made the surface a
// whisper — the detail is where «¿quién actúa?» is actually answered.
detailIdle: '#?components.aura.detail.idle|no delegation',
detailOffered: '#?components.aura.detail.offered|system initiative',
detailPlanned: '#?components.aura.detail.planned|nothing done yet',
detailReviewing: '#?components.aura.detail.reviewing|control is yours',
detailActing: '#?components.aura.detail.acting|on your behalf',
detailEscalated: '#?components.aura.detail.escalated|stopped until you decide',
announceOffer: '#?components.aura.announce.offer|Agent: offering to help',
announceEscalated: '#?components.aura.announce.escalated|Agent: needs your attention',
announceReturned: '#?components.aura.announce.returned|Agent finished — control returned',

@ -50,7 +50,7 @@ generic — degradation, never a guess.
| `Provider` | `<div>` | Root context; projects `data-state` (idle · offered · planned · reviewing · acting · escalated). |
| `Ring` | `<div>` | Custody ring (`aria-hidden`, optional). WRAPS the Indicator — it is the orb's frame of reference, not a sibling positioned by hand; mirrors `data-state` so eidos can draw the cycle as SHAPE. |
| `Indicator` | `<div>` | Decorative orb (`aria-hidden`); mirrors `data-state` for the eidos/scene visual. |
| `Status` | `<span>` | Localized state label; snippet props `{ state, statusText }`. |
| `Status` | `<span>` | Localized state in TWO lines: the state itself + what it means for the user right now (who holds control). Snippet props `{ state, statusText, detailText }`. |
| `Region` | `<div>` | `role=status` + `aria-live=polite` + `aria-atomic` — the axis's ONE live region; renders the attributed announcement. |
| `Cancel` | `<button>` | The global exit; disabled while idle; `aria-label` from langs. |
@ -95,6 +95,12 @@ DETAIL is app content, in the app's own domain.
## Decisions
- **The Status is two lines, not a label.** One grey word next to a live orb
is a whisper: the state answers «¿quién actúa?» and the detail answers what
that means for you (`la custodia es tuya`, `parado hasta que decidas`). The
copy is FIXED per state and localized like the label — the run's real
counters (turns / acts) are not in the structural port, and widening it for
a nicer subtitle would be a decision, not a default.
- **Announcements live in Aura's own Region**, not the shared runtime
announce path (no double announcement); attributed («Agent: …») and
coalesced per plan — WCAG 4.1.3 doctrine.

@ -129,6 +129,24 @@ export class AuraProvider {
return this.soma.langs.ts(key) ?? '';
});
/**
* Second line of the Status: what the current state means for the user —
* who holds control right now. Fixed copy per state, localized like the
* label: the run's real counters (turns / acts) are NOT in the structural
* port, and widening it is a decision, not a default.
*/
readonly detailText: string = $derived.by(() => {
const key = {
idle: AURA_LANGS.DETAIL_IDLE,
offered: AURA_LANGS.DETAIL_OFFERED,
planned: AURA_LANGS.DETAIL_PLANNED,
reviewing: AURA_LANGS.DETAIL_REVIEWING,
acting: AURA_LANGS.DETAIL_ACTING,
escalated: AURA_LANGS.DETAIL_ESCALATED
}[this.state];
return this.soma.langs.ts(key) ?? '';
});
/** The global exit (doctrine: cancel reachable without a mouse). */
cancel(): void {
const agent = this.opts.agent.current;
@ -308,7 +326,8 @@ export class AuraStatusProvider {
readonly snippetProps = $derived.by(() => ({
state: this.provider.state,
statusText: this.provider.statusText
statusText: this.provider.statusText,
detailText: this.provider.detailText
}));
readonly props = $derived.by(() =>

@ -32,7 +32,8 @@
<span {...mergedProps}>
{@render children?.(state.snippetProps)}
{#if !children}
{state.snippetProps.statusText}
<span data-aura-status-label>{state.snippetProps.statusText}</span>
<span data-aura-status-detail>{state.snippetProps.detailText}</span>
{/if}
</span>
{/if}

@ -7,6 +7,12 @@ export const AURA_LANGS = {
STATUS_REVIEWING: '#?components.aura.status.reviewing|Awaiting your review',
STATUS_ACTING: '#?components.aura.status.acting|Acting on your behalf',
STATUS_ESCALATED: '#?components.aura.status.escalated|Needs your attention',
DETAIL_IDLE: '#?components.aura.detail.idle|no delegation',
DETAIL_OFFERED: '#?components.aura.detail.offered|system initiative',
DETAIL_PLANNED: '#?components.aura.detail.planned|nothing done yet',
DETAIL_REVIEWING: '#?components.aura.detail.reviewing|control is yours',
DETAIL_ACTING: '#?components.aura.detail.acting|on your behalf',
DETAIL_ESCALATED: '#?components.aura.detail.escalated|stopped until you decide',
ANNOUNCE_OFFER: '#?components.aura.announce.offer|Agent: offering to help',
ANNOUNCE_ESCALATED: '#?components.aura.announce.escalated|Agent: needs your attention',
ANNOUNCE_RETURNED: '#?components.aura.announce.returned|Agent finished — control returned',

@ -65,6 +65,8 @@ export type AuraRingProps = WithChild<{
export type AuraStatusSnippetProps = {
state: AuraState;
statusText: string;
/** Second line: what this state means for the user (who holds control). */
detailText: string;
};
export type AuraStatusProps = WithChild<

Loading…
Cancel
Save

Powered by TurnKey Linux.