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.
754 lines
25 KiB
754 lines
25 KiB
|
3 months ago
|
---
|
||
|
|
title: Guía de implementación — Semántica perceptiva en UIX
|
||
|
|
type: notes
|
||
|
|
audience: human + agent
|
||
|
|
authority: historical seed — superseded by docs/CANON.md + code; kept for the Spanish narrative
|
||
|
|
status: historical
|
||
|
|
source: migrated verbatim from src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md (2026-07-02, docs-book F7.6; historical seed — not translated)
|
||
|
|
---
|
||
|
|
|
||
|
|
# Guía de implementación — Semántica perceptiva en UIX
|
||
|
|
|
||
|
|
> **Estado: semilla histórica, NO autoritativa.** Este documento fue la guía
|
||
|
|
> fundacional de la migración semántica (2026-05). El canon vigente es
|
||
|
|
> [`docs/CANON.md`](../CANON.md) + el código (`SEMA_MAP`, `holds.ts`,
|
||
|
|
> los morfos); las desviaciones y extensiones registradas viven en
|
||
|
|
> [`book-deviations.md`](./book-deviations.md).
|
||
|
|
> Se conserva por su narrativa en castellano. Donde este texto discrepe del
|
||
|
|
> canon o del código, **el canon y el código ganan**. (CLAUDE.md aún lo cita
|
||
|
|
> como autoritativo; esa cita se actualizará en el pase diferido de CLAUDE.md.)
|
||
|
|
|
||
|
|
## De la teoría del libro a la arquitectura del framework
|
||
|
|
|
||
|
|
Este documento traduce las decisiones del libro *Semántica perceptiva de la interfaz* a la arquitectura UIX (Morfo/Soma/Sema/Eidos). No repite la teoría — la convierte en contratos, vocabularios, reglas de resolución y convenciones técnicas.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. Vocabulario canónico corregido
|
||
|
|
|
||
|
|
> **Canonical (EN):** [`docs/CANON.md`](../CANON.md) is now the single
|
||
|
|
> source of truth for families / intents / verbs, anchored to the book + code.
|
||
|
|
> This section is kept for the Spanish narrative; if it disagrees with the canon,
|
||
|
|
> the canon wins.
|
||
|
|
|
||
|
|
### 1.1. Familias
|
||
|
|
|
||
|
|
8 familias. Sin excepciones.
|
||
|
|
|
||
|
|
```ts
|
||
|
|
export const SEMA_FAMILIES = [
|
||
|
|
'contact', // ¿el sistema ha sentido mi acción?
|
||
|
|
'commit', // ¿algo quedó fijado o tuvo consecuencia?
|
||
|
|
'signal', // ¿algo reclama mi atención?
|
||
|
|
'handle', // ¿estoy manipulando directamente un objeto?
|
||
|
|
'emerge', // ¿algo entró o salió del campo perceptivo?
|
||
|
|
'shift', // ¿cambió el marco operativo?
|
||
|
|
'sustain', // ¿esto sigue ocurriendo?
|
||
|
|
'delegate' // ¿quién actúa ahora? (libro cap. 29)
|
||
|
|
] as const;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cambios respecto a la implementación actual:**
|
||
|
|
|
||
|
|
| Antes | Ahora | Razón |
|
||
|
|
|---|---|---|
|
||
|
|
| `alert` como familia | `signal` con verbo `alert` | alert es intensidad dentro de signal, no familia |
|
||
|
|
| sin `shift` | `shift` añadido | emerge ≠ shift — dropdown ≠ modal |
|
||
|
|
| sin `loss` | `loss` como intent | threat ≠ loss — amenaza ≠ pérdida consumada |
|
||
|
|
|
||
|
|
### 1.2. Intents
|
||
|
|
|
||
|
|
6 intents. Regiones evaluativas del espacio valencia/activación.
|
||
|
|
|
||
|
|
Fuente única: `src/uix/intent.ts` exporta `INTENTS` y `Intent`. Las capas no
|
||
|
|
redeclaran ni prefijan este vocabulario.
|
||
|
|
|
||
|
|
- `neutral`: sin carga evaluativa fuerte
|
||
|
|
- `affirm`: confirmación positiva, baja activación
|
||
|
|
- `fulfill`: resolución positiva, mayor activación
|
||
|
|
- `risk`: problema corregible, negativo moderado
|
||
|
|
- `threat`: amenaza activa, alta activación negativa
|
||
|
|
- `loss`: pérdida consumada, consecuencia ya ocurrida
|
||
|
|
|
||
|
|
### 1.3. Verbs por familia
|
||
|
|
|
||
|
|
```ts
|
||
|
|
export const SEMA_VERBS = {
|
||
|
|
contact: [
|
||
|
|
'press', 'tap', 'activate', 'focus', 'trigger', 'release'
|
||
|
|
],
|
||
|
|
commit: [
|
||
|
|
'select', 'unselect', 'toggle', 'save', 'submit', 'confirm',
|
||
|
|
'complete', 'fail', 'cancel', 'reset', 'discard', 'delete',
|
||
|
|
'restore', 'expire', 'acknowledge', 'apply', 'partial', 'block',
|
||
|
|
'move', 'set', 'remove', 'reorder', 'upload'
|
||
|
|
],
|
||
|
|
signal: [
|
||
|
|
'announce', 'notify', 'warn', 'alert', 'inform', 'emphasize', 'remind'
|
||
|
|
],
|
||
|
|
handle: [
|
||
|
|
'pick', 'carry', 'drop', 'drag', 'resize', 'reorder', 'rotate',
|
||
|
|
'scroll', 'zoom'
|
||
|
|
],
|
||
|
|
emerge: [
|
||
|
|
'present', 'dismiss', 'open', 'close', 'expand', 'collapse',
|
||
|
|
'reveal', 'hide'
|
||
|
|
],
|
||
|
|
shift: [
|
||
|
|
'enter-mode', 'exit-mode', 'navigate', 'route', 'step',
|
||
|
|
'return', 'context'
|
||
|
|
],
|
||
|
|
sustain: [
|
||
|
|
'start', 'progress', 'loading', 'waiting', 'syncing',
|
||
|
|
'processing', 'streaming', 'pending', 'retrying', 'upload', 'end'
|
||
|
|
],
|
||
|
|
delegate: [
|
||
|
|
'offer', 'plan', 'authorize', 'act', 'review', 'escalate', 'return'
|
||
|
|
]
|
||
|
|
} as const;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cambios de verbs:**
|
||
|
|
|
||
|
|
| Antes | Ahora | Razón |
|
||
|
|
|---|---|---|
|
||
|
|
| `contact.select` | `commit.select` | seleccionar fija estado = commit |
|
||
|
|
| `contact.toggle` | `commit.toggle` | alternar fija estado = commit |
|
||
|
|
| `handle.acknowledge` | `commit.acknowledge` | reconocer cierra algo = commit |
|
||
|
|
| `handle.edit` | `shift.enter-mode` | editar cambia régimen = shift |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Sistema de 8 tokens de color
|
||
|
|
|
||
|
|
> **Vigente**: el inventario real es de **9 roles** — se añadió `tertiary`
|
||
|
|
> (jerarquía) — y los slots CSS reales son `--color-{role}-{slot}`
|
||
|
|
> (`solid`, `text`, `bg`, `border`, …), no `--color-{role}-element`.
|
||
|
|
> Fuente: [`theming/reference.md`](../theming/reference.md) §4 +
|
||
|
|
> `THEME_BASE_COLOR_ROLES` (`src/uix/eidos/themes/base.ts`). La doctrina de
|
||
|
|
> dos ejes (jerarquía vs intent) que sigue es la vigente; los nombres
|
||
|
|
> concretos evolucionaron.
|
||
|
|
|
||
|
|
### 2.1. Los 8 valores
|
||
|
|
|
||
|
|
Dos ejes ortogonales:
|
||
|
|
|
||
|
|
**Jerarquía** (sin carga evaluativa):
|
||
|
|
- `primary` — acción principal
|
||
|
|
- `secondary` — acción secundaria
|
||
|
|
|
||
|
|
**Intent** (carga evaluativa):
|
||
|
|
- `neutral` — sin juicio fuerte
|
||
|
|
- `affirm` — confirmación suave
|
||
|
|
- `fulfill` — objetivo cumplido
|
||
|
|
- `risk` — problema corregible
|
||
|
|
- `threat` — amenaza activa
|
||
|
|
- `loss` — pérdida consumada
|
||
|
|
|
||
|
|
### 2.2. Regla de resolución intent ↔ color
|
||
|
|
|
||
|
|
```ts
|
||
|
|
visualColor =
|
||
|
|
intent !== 'neutral' → intent // la carga evaluativa gana
|
||
|
|
intent === 'neutral' → color ?? 'neutral' // jerarquía si se pasó, neutral si no
|
||
|
|
```
|
||
|
|
|
||
|
|
Un componente recibe dos props ortogonales con prioridad clara:
|
||
|
|
|
||
|
|
- `intent` (semántico) — siempre presente, default `neutral`. Decide firma sema y, si es evaluativo, también el color visual.
|
||
|
|
- `color` (jerárquico) — opcional, solo `primary | secondary`. Override visual cuando NO hay carga evaluativa. Si pasas `color='primary'` con `intent='threat'`, el intent gana.
|
||
|
|
|
||
|
|
```svelte
|
||
|
|
<Toggle /> <!-- data-color='neutral' -->
|
||
|
|
<Toggle color="primary" /> <!-- data-color='primary' -->
|
||
|
|
<Toggle intent="affirm" /> <!-- data-color='affirm' -->
|
||
|
|
<Toggle intent="threat" color="primary" /> <!-- data-color='threat' (intent gana) -->
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.3. Mapeo desde convención heredada
|
||
|
|
|
||
|
|
| Convención CSS | Token UIX | Nota |
|
||
|
|
|---|---|---|
|
||
|
|
| primary | `primary` | jerarquía, no intent |
|
||
|
|
| secondary | `secondary` | jerarquía, no intent |
|
||
|
|
| success | `affirm` o `fulfill` | affirm = confirmación suave, fulfill = objetivo cumplido |
|
||
|
|
| warning | `risk` | problema corregible |
|
||
|
|
| danger | `threat` o `loss` | threat = antes, loss = después |
|
||
|
|
| info | `signal.announce + neutral` | info no es intent, es función de atención |
|
||
|
|
|
||
|
|
### 2.4. Tokens CSS por theme
|
||
|
|
|
||
|
|
Cada theme define los roles; el inventario y los nombres de slot reales
|
||
|
|
(`--color-{role}-{slot}`) viven en [`theming/reference.md`](../theming/reference.md)
|
||
|
|
§4 y en el generador (`DEFAULT_COLOR_ROLE_SLOT_STEPS`,
|
||
|
|
`src/uix/eidos/lib/render-css.ts`). El naming `--color-{role}-element` de la
|
||
|
|
versión original de esta guía nunca llegó al código.
|
||
|
|
|
||
|
|
El theme decide hue/sat/lightness por marca. El componente solo declara qué token leer.
|
||
|
|
|
||
|
|
### 2.5. CSS en recipes
|
||
|
|
|
||
|
|
```css
|
||
|
|
[data-color='primary'] { ... }
|
||
|
|
[data-color='secondary'] { ... }
|
||
|
|
[data-color='neutral'] { ... }
|
||
|
|
[data-color='affirm'] { ... }
|
||
|
|
[data-color='fulfill'] { ... }
|
||
|
|
[data-color='risk'] { ... }
|
||
|
|
[data-color='threat'] { ... }
|
||
|
|
[data-color='loss'] { ... }
|
||
|
|
```
|
||
|
|
|
||
|
|
Los selectores legacy (`info`, `success`, `warning`, `danger`) se eliminan.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Subset por componente
|
||
|
|
|
||
|
|
No todo componente acepta los 8 valores. Cada componente declara su subset permitido.
|
||
|
|
|
||
|
|
### 3.1. Tabla de subsets
|
||
|
|
|
||
|
|
| Componente | color | intent permitidos |
|
||
|
|
|---|---|---|
|
||
|
|
| Button (acción) | primary, secondary | neutral, affirm, fulfill, risk, threat, loss |
|
||
|
|
| Button (nav) | primary, secondary | neutral |
|
||
|
|
| Toggle / Switch | primary, secondary | neutral, affirm, risk, threat |
|
||
|
|
| Checkbox / Radio | primary, secondary | neutral, affirm |
|
||
|
|
| Input | — | neutral, risk |
|
||
|
|
| Select / Combobox | primary, secondary | neutral, affirm |
|
||
|
|
| Slider | primary | neutral, affirm |
|
||
|
|
| Alert inline | — | risk |
|
||
|
|
| Alert crítica | — | threat |
|
||
|
|
| Toast / Snackbar | — | neutral, affirm, risk |
|
||
|
|
| Toast con undo | — | loss |
|
||
|
|
| Banner | — | neutral, risk |
|
||
|
|
| Badge | — | neutral, risk |
|
||
|
|
| Modal | — | neutral, threat, risk |
|
||
|
|
| Spinner / Skeleton | — | no acepta intent |
|
||
|
|
| Progress bar | — | no acepta intent |
|
||
|
|
|
||
|
|
### 3.2. Regla de subset
|
||
|
|
|
||
|
|
Si un componente no admite `loss`, no aparece `[data-color='loss']` en su recipe CSS. El intent se valida en TypeScript:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
type ToggleIntent = Extract<SemanticIntent, 'neutral' | 'affirm' | 'risk' | 'threat'>;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3. Regla: el intent no nace del componente
|
||
|
|
|
||
|
|
El intent no nace del componente. Pero el componente debe poder recibirlo para expresarlo. La diferencia con la convención heredada es el orden:
|
||
|
|
|
||
|
|
- **Convención:** diseñador pinta botón de rojo → botón "es" danger → intent nace del color
|
||
|
|
- **UIX:** evento es commit.delete + loss → componente recibe loss → aplica firma perceptiva
|
||
|
|
|
||
|
|
### 3.4. Regla: la estética no contradice la semántica
|
||
|
|
|
||
|
|
La estética tiene libertad dentro del rango que la semántica permite. Un affirm puede ser verde esmeralda o azul suave — eso es estética. Pero un affirm no puede ser rojo con icono de alerta — eso es contradicción semántica.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. Familias y componentes: quién expresa qué
|
||
|
|
|
||
|
|
### 4.1. emerge vs shift
|
||
|
|
|
||
|
|
> **Tabla orientativa (doctrina del libro), el morfo manda.** La
|
||
|
|
> implementación asignó `emerge` a los overlays Dialog / Drawer / Popover:
|
||
|
|
> sus morfos declaran `open`/`close` con `family: 'emerge'`, y el `close`
|
||
|
|
> polimórfico admite `allowedFamilies: ['emerge', 'commit', 'signal']` — sin
|
||
|
|
> `shift` (ver `src/uix/morfo/components/dialog.ts` y
|
||
|
|
> [`book-deviations.md`](./book-deviations.md) D.11). Ante
|
||
|
|
> cualquier duda, la familia real de un componente es la de su morfo.
|
||
|
|
|
||
|
|
| Componente | Familia | Razón |
|
||
|
|
|---|---|---|
|
||
|
|
| Dropdown | emerge.open | aparición local, no cambia marco |
|
||
|
|
| Popover | emerge.open | aparición anclada |
|
||
|
|
| Tooltip | emerge.present | información auxiliar |
|
||
|
|
| Accordion | emerge.expand | contenido contenido |
|
||
|
|
| Modal / Dialog | emerge.open (implementado) | el libro lo doctrina shift.enter-mode; el morfo declara emerge |
|
||
|
|
| Command Palette | shift.enter-mode | cambia régimen operativo |
|
||
|
|
| Edit mode | shift.enter-mode | cambia qué puede hacerse |
|
||
|
|
| Wizard step | shift.step | avanza en proceso |
|
||
|
|
| Route change | shift.navigate | nuevo contexto |
|
||
|
|
| Drawer (pesado) | shift.enter-mode | si bloquea fondo y captura foco |
|
||
|
|
| Drawer (ligero) | emerge.open | si no bloquea ni captura |
|
||
|
|
|
||
|
|
### 4.2. contact vs commit
|
||
|
|
|
||
|
|
```
|
||
|
|
contact = el sistema recibió mi gesto
|
||
|
|
commit = algo quedó fijado como consecuencia
|
||
|
|
```
|
||
|
|
|
||
|
|
Si la operación es instantánea (toggle, checkbox, select), el usuario percibe un solo evento: commit. El contact está implícito en el gesto. Se documenta como un solo evento semántico.
|
||
|
|
|
||
|
|
### 4.3. signal vs emerge
|
||
|
|
|
||
|
|
```
|
||
|
|
emerge = algo entra o sale del campo perceptivo
|
||
|
|
signal = algo reclama atención
|
||
|
|
```
|
||
|
|
|
||
|
|
Un toast que aparece es emerge.present. El mensaje dentro puede ser signal.notify + neutral o commit.save + affirm. La aparición no es la señal.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. Morfo: declaración semántica del componente
|
||
|
|
|
||
|
|
### 5.1. Estructura del evento en Morfo
|
||
|
|
|
||
|
|
```ts
|
||
|
|
events: [
|
||
|
|
{
|
||
|
|
name: 'commit-toggle',
|
||
|
|
semantic: {
|
||
|
|
family: 'commit',
|
||
|
|
verb: 'toggle',
|
||
|
|
intent: undefined, // lo decide el provider según contexto
|
||
|
|
target: v.partRef('root'),
|
||
|
|
sequence: 'post' // la señal ocurre después del cambio de estado
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2. Campo `sequence` (timing del evento)
|
||
|
|
|
||
|
|
```ts
|
||
|
|
sequence: 'pre' | 'coincident' | 'post'
|
||
|
|
```
|
||
|
|
|
||
|
|
| Valor | Cuándo usar | Ejemplo |
|
||
|
|
|---|---|---|
|
||
|
|
| `pre` | señal perceptiva antes del cambio estructural | emerge.dismiss (animar salida antes de cerrar) |
|
||
|
|
| `coincident` | señal durante el proceso | sustain.progress |
|
||
|
|
| `post` | señal después del resultado real | commit.save + affirm (confirmar después de guardar) |
|
||
|
|
|
||
|
|
No todo evento debe ser `pre` como el Toast dismiss. Contact debe ser `post` (inmediato, sin bloquear estado). Commit.save debe ser `post` (no celebrar antes de que exista resultado).
|
||
|
|
|
||
|
|
### 5.3. Capacidad semántica vs evento fijo
|
||
|
|
|
||
|
|
Morfo puede declarar capacidad semántica cuando el componente soporta varias
|
||
|
|
familias según contexto. El shape implementado es **aditivo** — el evento
|
||
|
|
declara su semántica concreta como default y `allowedFamilies` habilita el
|
||
|
|
override (el shape `defaultSemantic` de la versión original de esta guía se
|
||
|
|
descartó; ver [`book-deviations.md`](./book-deviations.md)
|
||
|
|
D.11). Del morfo real del Dialog:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
events: [
|
||
|
|
{
|
||
|
|
name: 'close',
|
||
|
|
semantic: {
|
||
|
|
family: 'emerge', // default
|
||
|
|
verb: 'close',
|
||
|
|
target: v.partRef('content'),
|
||
|
|
sequence: 'pre',
|
||
|
|
persistence: 'transient',
|
||
|
|
allowedFamilies: ['emerge', 'commit', 'signal']
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
El provider concreta con un override validado contra `allowedFamilies`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
runtime.trigger('close', {
|
||
|
|
semantic: { family: 'commit', verb: 'save', intent: 'fulfill' }
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Sema: holds y persistencia
|
||
|
|
|
||
|
|
### 6.1. Separar hold expresivo de persistencia semántica
|
||
|
|
|
||
|
|
```ts
|
||
|
|
hold: number // duración expresiva mínima del evento (ms)
|
||
|
|
persistence: 'transient' | 'untilAction' | 'untilFix' | 'stateBound'
|
||
|
|
```
|
||
|
|
|
||
|
|
| Tipo | Significado | Ejemplo |
|
||
|
|
|---|---|---|
|
||
|
|
| `transient` | desaparece tras hold | contact.press, commit.save + affirm |
|
||
|
|
| `untilAction` | persiste hasta que el usuario actúe | signal.alert + threat |
|
||
|
|
| `untilFix` | persiste hasta corrección | signal.warn + risk |
|
||
|
|
| `stateBound` | ligado al estado del proceso | sustain.progress |
|
||
|
|
|
||
|
|
### 6.2. Holds por familia e intent
|
||
|
|
|
||
|
|
Los valores canónicos viven en el código — no se copian aquí (regla
|
||
|
|
anti-drift de [`docs/authoring.md`](../authoring.md)):
|
||
|
|
|
||
|
|
- **Holds base por familia**: `SEMA_MAP.families[F].hold` en
|
||
|
|
`src/uix/sema/sema-map.ts`.
|
||
|
|
- **Tabla de referencia holds-por-intent**: `SEMA_HOLDS_BY_INTENT` en
|
||
|
|
`src/uix/sema/holds.ts` (referencia doctrinal, NO auto-aplicada — el
|
||
|
|
default conservador es `transient` y cada morfo declara su `persistence`).
|
||
|
|
|
||
|
|
La tabla numérica que ocupaba esta sección divergió del código en semanas;
|
||
|
|
consulta siempre las dos fuentes de arriba.
|
||
|
|
|
||
|
|
### 6.3. Sustain no tiene hold fijo
|
||
|
|
|
||
|
|
Sustain no es un evento transitorio. Es un estado que dura mientras dura el proceso. No se le asigna hold de 600ms ni de ningún valor fijo. Se gestiona como `stateBound`: el canal visual mantiene los atributos mientras el provider indique que el proceso sigue activo.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Canal visual: atributos DOM
|
||
|
|
|
||
|
|
### 7.1. Señales transitorias (Sema escribe, Eidos lee)
|
||
|
|
|
||
|
|
```html
|
||
|
|
data-event="commit-toggle"
|
||
|
|
data-event-id="sig-42"
|
||
|
|
data-event-phase="active"
|
||
|
|
data-event-family="commit"
|
||
|
|
data-event-intent="affirm"
|
||
|
|
```
|
||
|
|
|
||
|
|
Viven durante el hold. Se limpian antes de resolver la Promise.
|
||
|
|
|
||
|
|
### 7.2. Estado persistente (Soma/Effects escriben, Eidos lee)
|
||
|
|
|
||
|
|
```html
|
||
|
|
data-state="open"
|
||
|
|
data-color="primary"
|
||
|
|
data-intent="risk"
|
||
|
|
data-disabled
|
||
|
|
data-pressed
|
||
|
|
```
|
||
|
|
|
||
|
|
Viven mientras el estado sea verdadero. No son señales transitorias.
|
||
|
|
|
||
|
|
### 7.3. Regla: no mezclar transitorio y persistente
|
||
|
|
|
||
|
|
El canal visual nunca toca atributos de estado (`data-state`, `data-intent`, `data-disabled`). El estado lo gestiona el runtime. Razón: estado es persistente y señal es transitoria. Pisar el mismo nombre fuerza al canal a borrar estado al limpiar.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. Composiciones simultáneas
|
||
|
|
|
||
|
|
### 8.1. V1: un solo evento activo por target
|
||
|
|
|
||
|
|
Mantener un solo `data-event` activo. Suficiente para:
|
||
|
|
- dismiss, open, press, complete, toggle, select
|
||
|
|
|
||
|
|
### 8.2. V2 (futuro): slots semánticos
|
||
|
|
|
||
|
|
Para composiciones simultáneas como:
|
||
|
|
|
||
|
|
```
|
||
|
|
shift.enter-mode + signal.alert + threat
|
||
|
|
sustain.progress + signal.warn + risk
|
||
|
|
handle.carry + signal.warn + threat
|
||
|
|
```
|
||
|
|
|
||
|
|
Añadir slots:
|
||
|
|
|
||
|
|
```html
|
||
|
|
data-event-frame="shift.enter-mode"
|
||
|
|
data-event-signal="signal.alert"
|
||
|
|
data-event-intent="threat"
|
||
|
|
```
|
||
|
|
|
||
|
|
Así el modal (shift) no absorbe el intent de su contenido (signal).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Accesibilidad semántica en Morfo
|
||
|
|
|
||
|
|
### 9.1. Contrato a11y por evento
|
||
|
|
|
||
|
|
```ts
|
||
|
|
a11ySemantic: {
|
||
|
|
requiresPersistentTrace?: boolean;
|
||
|
|
requiresLiveRegion?: boolean;
|
||
|
|
requiresFocusMove?: boolean;
|
||
|
|
keyboardEquivalent?: boolean;
|
||
|
|
reducedMotionFallback?: 'state' | 'text' | 'focus' | 'none';
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 9.2. Ejemplos
|
||
|
|
|
||
|
|
```ts
|
||
|
|
// signal.warn + risk
|
||
|
|
a11ySemantic: {
|
||
|
|
requiresPersistentTrace: true,
|
||
|
|
reducedMotionFallback: 'text'
|
||
|
|
}
|
||
|
|
|
||
|
|
// signal.alert + threat
|
||
|
|
a11ySemantic: {
|
||
|
|
requiresPersistentTrace: true,
|
||
|
|
requiresLiveRegion: true,
|
||
|
|
requiresFocusMove: true
|
||
|
|
}
|
||
|
|
|
||
|
|
// handle (drag)
|
||
|
|
a11ySemantic: {
|
||
|
|
keyboardEquivalent: true
|
||
|
|
}
|
||
|
|
|
||
|
|
// commit.delete + loss
|
||
|
|
a11ySemantic: {
|
||
|
|
requiresPersistentTrace: true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. Sound y Haptic
|
||
|
|
|
||
|
|
### 10.1. Principios
|
||
|
|
|
||
|
|
- Sound y Haptic reciben el signal directamente del engine, no leen DOM.
|
||
|
|
- Son fire-and-forget: no bloquean al caller.
|
||
|
|
- Son opcionales, proporcionales y nunca únicos.
|
||
|
|
- La semántica debe poder vivir sin ellos.
|
||
|
|
|
||
|
|
### 10.2. Configuración por familia/intent
|
||
|
|
|
||
|
|
```ts
|
||
|
|
sound: {
|
||
|
|
enabled: false, // opt-in global
|
||
|
|
allowFamilies: ['signal', 'commit'], // solo estas familias pueden sonar
|
||
|
|
allowIntents: ['fulfill', 'threat', 'loss'], // solo estos intents
|
||
|
|
muteFrequentEvents: true // silenciar contact frecuente
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 10.3. Eager-init
|
||
|
|
|
||
|
|
AudioContext se crea/resume en el primer user gesture (click/touch/keydown en document, capture phase). Convención aplicable a cualquier canal con restricción de "primera vez en gesture": audio, vibration, fullscreen, clipboard write.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. Firma perceptiva por evento
|
||
|
|
|
||
|
|
Una firma perceptiva es el conjunto de decisiones de canal coordinadas alrededor de un evento.
|
||
|
|
|
||
|
|
**Las dimensiones perceptivas no son un conjunto cerrado — pero ojo a quién
|
||
|
|
las ejecuta.** A nivel del libro, una firma compone los canales de expresión
|
||
|
|
(tiempo · motion · presencia · profundidad · forma · color · sonido · háptica).
|
||
|
|
El framework los **REPARTE por dueño** y NO los ejecuta todos en sema:
|
||
|
|
|
||
|
|
- **sema ejecuta 2 canales runtime** — `sound` + `haptic` (los únicos
|
||
|
|
declarados en `SemaChannelSignatures`) — más el **meta-canal `visual`**, que
|
||
|
|
no realiza ninguna modalidad: solo **estampa los `data-event-*` y temporiza
|
||
|
|
el hold**.
|
||
|
|
- **eidos materializa los canales visuales** (motion · presencia · profundidad ·
|
||
|
|
forma · color) reaccionando en CSS a esos `data-event-*` + `data-state` /
|
||
|
|
`data-intent`. Eidos es el **único dueño de lo visual**.
|
||
|
|
|
||
|
|
`SemaChannelSignatures` se extiende vía TypeScript declaration merging para
|
||
|
|
añadir canales **de sema** con firma (`a11y`, `voice`, …) — NO para los
|
||
|
|
visuales, que viven en eidos. Una rule de cascade afina `sound`/`haptic`;
|
||
|
|
`motion`/`color` se ajustan en los recipes de eidos, no en la cascade. Los
|
||
|
|
ejemplos de abajo enumeran las dimensiones **perceptivas** de cada evento —
|
||
|
|
recuerda que motion/forma/color las realiza **eidos**, no sema.
|
||
|
|
|
||
|
|
### 11.1. Ejemplo: commit.save + affirm
|
||
|
|
|
||
|
|
```
|
||
|
|
tiempo: breve
|
||
|
|
motion: asentamiento mínimo
|
||
|
|
forma: marca persistente
|
||
|
|
color: affirm (positivo discreto)
|
||
|
|
sonido: no por defecto
|
||
|
|
háptica: no por defecto
|
||
|
|
accesibilidad: texto o estado visible
|
||
|
|
```
|
||
|
|
|
||
|
|
### 11.2. Ejemplo: signal.alert + threat
|
||
|
|
|
||
|
|
```
|
||
|
|
presencia: dominante
|
||
|
|
forma: bloque crítico
|
||
|
|
color: threat (alta saliencia)
|
||
|
|
texto: acción clara
|
||
|
|
sonido: opcional, urgente
|
||
|
|
motion: entrada saliente
|
||
|
|
persistencia: hasta acción
|
||
|
|
accesibilidad: foco + live region + persistente
|
||
|
|
```
|
||
|
|
|
||
|
|
### 11.3. Ejemplo: commit.delete + loss
|
||
|
|
|
||
|
|
```
|
||
|
|
presencia: retirada + huella
|
||
|
|
forma: undo si existe
|
||
|
|
color: loss (grave/desaturado)
|
||
|
|
sonido: seco/grave opcional
|
||
|
|
motion: retirada/descenso
|
||
|
|
persistencia: huella
|
||
|
|
accesibilidad: anuncio + undo persistente + foco no perdido
|
||
|
|
no usar: alarma sostenida de threat
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 12. Arquetipos y familias frecuentes
|
||
|
|
|
||
|
|
Relación orientativa entre archetipos de Morfo y familias:
|
||
|
|
|
||
|
|
| Archetype | Familias frecuentes |
|
||
|
|
|---|---|
|
||
|
|
| trigger | contact, emerge, shift, commit |
|
||
|
|
| content | emerge, shift, signal |
|
||
|
|
| item | commit, handle, signal |
|
||
|
|
| overlay | shift |
|
||
|
|
| indicator | sustain, commit |
|
||
|
|
| thumb | handle |
|
||
|
|
| track | handle, sustain |
|
||
|
|
|
||
|
|
No como regla rígida, sino como documentación que ayuda a decidir qué eventos puede expresar un componente.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 13. Doctrina del API: soma compound, eidos option C
|
||
|
|
|
||
|
|
> Actualizacion 2026-05-14: la forma flat con snippet slots queda retirada.
|
||
|
|
> Eidos usa la option C disciplinada: root visual directo con hijos atados como
|
||
|
|
> propiedades explicitas. La referencia operativa vive en
|
||
|
|
> `src/uix/eidos/components/README.md`.
|
||
|
|
|
||
|
|
### 13.1. Las dos capas exponen responsabilidades distintas
|
||
|
|
|
||
|
|
```
|
||
|
|
soma + morfo → composable universal — TODO caso (avanzado, raro, custom)
|
||
|
|
eidos → visual opinionado del design system sobre la misma anatomia
|
||
|
|
```
|
||
|
|
|
||
|
|
**Soma siempre es compound** (`Component.Provider`, `Component.Trigger`,
|
||
|
|
`Component.Content`, …) por simetría: el desarrollador aprende un
|
||
|
|
patrón único, todas las partes son visibles, el contrato cross-part
|
||
|
|
(IDs, ARIA refs) queda explícito.
|
||
|
|
|
||
|
|
**Eidos no inventa una API flat paralela.** La capa visual conserva la
|
||
|
|
anatomia declarada por Morfo y materializada por Soma, pero expone un root
|
||
|
|
visual directo:
|
||
|
|
|
||
|
|
- **Single-part** (Toggle, Switch): el `default` es el componente visual
|
||
|
|
completo.
|
||
|
|
- **Multi-part** (Collapsible, Dialog, Drawer, Popover, Toast): el `default`
|
||
|
|
es el root visual (`<Drawer>`, `<Dialog>`, etc.) y los hijos se acceden como
|
||
|
|
propiedades attached (`<Drawer.Trigger>`, `<Drawer.Content>`, etc.).
|
||
|
|
- No hay `Provider` publico en Eidos; `Provider` sigue siendo nombre de Soma.
|
||
|
|
|
||
|
|
### 13.2. Forma publica vigente
|
||
|
|
|
||
|
|
```svelte
|
||
|
|
<Drawer bind:open>
|
||
|
|
<Drawer.Trigger>Open</Drawer.Trigger>
|
||
|
|
<Drawer.Portal>
|
||
|
|
<Drawer.Overlay />
|
||
|
|
<Drawer.Content>
|
||
|
|
<Drawer.Title>Title</Drawer.Title>
|
||
|
|
<Drawer.Close>Close</Drawer.Close>
|
||
|
|
</Drawer.Content>
|
||
|
|
</Drawer.Portal>
|
||
|
|
</Drawer>
|
||
|
|
```
|
||
|
|
|
||
|
|
El `index.ts` de cada componente attached usa asignacion explicita, no
|
||
|
|
`Object.assign`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
const Drawer = DrawerRoot as DrawerNamespace;
|
||
|
|
Drawer.Trigger = Trigger;
|
||
|
|
Drawer.Portal = Portal;
|
||
|
|
Drawer.Content = Content;
|
||
|
|
|
||
|
|
export { Drawer };
|
||
|
|
export default Drawer;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 13.3. La virtud arquitectonica
|
||
|
|
|
||
|
|
La capa visual ya no mantiene dos verdades. Si una app necesita una composicion
|
||
|
|
que el wrapper visual no cubre, baja a `$soma/components/{x}` y compone la
|
||
|
|
primitiva headless directamente. Eidos no bloquea esa salida; simplemente no
|
||
|
|
mantiene un segundo API flat con snippets que duplique el compound.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 14. Plan de migración
|
||
|
|
|
||
|
|
> **Histórico — plan ya ejecutado.** Las cinco prioridades y la tabla de
|
||
|
|
> componentes de esta sección se completaron durante los sprints 2026-05
|
||
|
|
> (vocabulario, tokens, holds/persistence, sequence, a11ySemantic, y la
|
||
|
|
> migración eidos completa). Se conserva como registro del arco de la
|
||
|
|
> migración; el estado real de cada componente lo da
|
||
|
|
> `npm run component:audit`, no esta tabla.
|
||
|
|
|
||
|
|
### 14.1. Prioridad 1 — Vocabulario
|
||
|
|
|
||
|
|
1. Renombrar `alert` → `signal` en SEMA_FAMILIES
|
||
|
|
2. Añadir `shift` a SEMA_FAMILIES
|
||
|
|
3. Añadir `loss` a `INTENTS`
|
||
|
|
4. Mover verbs: select/toggle a commit, acknowledge a commit, edit a shift
|
||
|
|
5. Actualizar SEMA_VERBS con la tabla completa
|
||
|
|
|
||
|
|
### 14.2. Prioridad 2 — Tokens de color
|
||
|
|
|
||
|
|
1. Añadir tokens: `secondary`, `affirm`, `loss`
|
||
|
|
2. Renombrar: `success → fulfill`, `warning → risk`, `danger → threat`
|
||
|
|
3. Eliminar: `info`
|
||
|
|
4. Implementar regla de resolución intent ↔ color en providers
|
||
|
|
|
||
|
|
### 14.3. Prioridad 3 — Holds y persistencia
|
||
|
|
|
||
|
|
1. Separar `hold` expresivo de `persistence` semántica
|
||
|
|
2. Actualizar holds por familia/intent según tabla
|
||
|
|
3. Sustain como `stateBound`, no hold fijo
|
||
|
|
|
||
|
|
### 14.4. Prioridad 4 — Sequence timing
|
||
|
|
|
||
|
|
1. Añadir campo `sequence` a morfo events
|
||
|
|
2. Implementar `pre | coincident | post` en runtime.trigger
|
||
|
|
|
||
|
|
### 14.5. Prioridad 5 — a11ySemantic
|
||
|
|
|
||
|
|
1. Añadir contrato a11ySemantic a morfo events
|
||
|
|
2. Implementar reducedMotionFallback, liveRegion, focusMove
|
||
|
|
|
||
|
|
### 14.6. Componentes por migrar
|
||
|
|
|
||
|
|
| Componente | CSS legacy | Wrapper | Subset color |
|
||
|
|
|---|---|---|---|
|
||
|
|
| toggle | — | ✅ piloto | primary, secondary, neutral, affirm, risk, threat |
|
||
|
|
| switch | switch.css | ⏳ | primary, secondary, neutral, affirm, risk, threat |
|
||
|
|
| dialog | dialog.css | ⏳ | neutral, risk, threat |
|
||
|
|
| drawer | drawer.css | ⏳ | neutral |
|
||
|
|
| popover | popover.css | ⏳ | neutral |
|
||
|
|
| toast | toast.css | ⏳ | neutral, affirm, risk, loss |
|
||
|
|
| checkbox | checkbox.css | ⏳ | primary, secondary, neutral, affirm |
|
||
|
|
| accordion | accordion.css | ⏳ | neutral |
|
||
|
|
| tabs | tabs.css | ⏳ | neutral |
|
||
|
|
| tooltip | tooltip.css | ⏳ | neutral |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 15. Reglas invariantes
|
||
|
|
|
||
|
|
1. **El intent no nace del componente.** Pero el componente debe poder recibirlo.
|
||
|
|
2. **La estética no contradice la semántica.** Libertad dentro del rango que el intent permite.
|
||
|
|
3. **Color expresa intent, no lo define.** El intent viene de la evaluación del evento.
|
||
|
|
4. **Emerge ≠ shift.** Dropdown = emerge. Modal = shift.
|
||
|
|
5. **Threat ≠ loss.** Antes de la consecuencia ≠ después de la consecuencia.
|
||
|
|
6. **Affirm ≠ fulfill.** Confirmación suave ≠ objetivo cumplido.
|
||
|
|
7. **Info no es intent.** Es signal.announce + neutral.
|
||
|
|
8. **Sustain no tiene hold fijo.** Dura mientras dure el proceso.
|
||
|
|
9. **Los canales no-visuales no leen DOM.** Reciben signal del engine.
|
||
|
|
10. **La semántica debe sobrevivir sin color, sin motion, sin sonido y sin háptica.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Documento derivado de "Semántica perceptiva de la interfaz" (Navarro Leal) y la arquitectura UIX (Morfo/Soma/Sema/Eidos).*
|