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.
svelte-kit-vice/docs/decisions/guia-semantica-historica.md

28 KiB

title type audience authority status source
Guía de implementación — Semántica perceptiva en UIX notes human + agent historical seed — superseded by docs/CANON.md + code; kept for the Spanish narrative historical 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 + el código (SEMA_MAP, holds.ts, los morfos); las desviaciones y extensiones registradas viven en 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 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.

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

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 §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

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.
<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 §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

[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:

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 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

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)

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 D.11). Del morfo real del Dialog:

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:

runtime.trigger('close', {
	semantic: { family: 'commit', verb: 'save', intent: 'fulfill' }
});

6. Sema: holds y persistencia

6.1. Separar hold expresivo de persistencia semántica

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):

  • 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)

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)

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:

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

a11ySemantic: {
  requiresPersistentTrace?: boolean;
  requiresLiveRegion?: boolean;
  requiresFocusMove?: boolean;
  keyboardEquivalent?: boolean;
  reducedMotionFallback?: 'state' | 'text' | 'focus' | 'none';
}

9.2. Ejemplos

// 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

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

<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:

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).

Powered by TurnKey Linux.