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/src/uix/sema/holds.ts

162 lines
7.0 KiB

/**
* SEMA_HOLDS_BY_INTENT — canonical lookup of perceptual hold + persistence
* by family + intent.
*
* PROVENANCE (corrected 2026-07-06; the earlier header cited a book section
* — "cap. 24 §6.2 (Holds por familia e intent)" — that DOES NOT EXIST, and
* claimed the tables were "verbatim"). The book *Diseñando lo que ocurre*
* gives NO milliseconds and no per-family hold table: its temporal doctrine
* is QUALITATIVE — cap. 12 (Tiempo) §4 duración · §6 "Persistencia y huella"
* ("no todo evento termina cuando termina su animación") · §8 tiempo e
* intent · §9 accesibilidad ("si un evento importante solo existe durante un
* instante, muchos usuarios no lo recibirán"), and cap. 32 TABLA 32.1/32.2
* ("regiones de diseño, no números sagrados" — e.g. commit.save+affirm
* "breve"; commit.complete+fulfill "breve-media, más resolutivo";
* signal.warn+risk "persistente hasta corrección"; signal.alert+threat
* "entrada rápida + persistencia hasta acción"; commit.delete+loss
* "breve + huella"). THIS TABLE is the framework's MATERIALIZATION of those
* regions onto the `SEMA_DURATIONS` anchors — authorship, not transcription.
*
* Two orthogonal concepts (book cap. 12 §6 distinguishes four — duración
* expresiva · persistencia · huella · caducidad; the runtime encodes two):
*
* - **hold** — perceptual MINIMUM display time. Independent of motion or
* animation timing; this is the duration the signal MUST be visible to
* register as a signal. (The expression itself is never cut short by the
* hold — see `VisualChannel.awaitExpression`.)
* - **persistence** — lifecycle policy. `transient` = auto-cleared after
* hold; `untilAction` / `untilFix` / `stateBound` = caller-managed
* lifecycle (the book's "hasta corrección/acción" is the EMITTER's
* responsibility — hence caller-managed).
*
* Used by:
* - the resolver (`resolveEffectiveSignature`) — THE single hold source per
* family + intent (the duplicated `SEMA_MAP.families[*].hold` table was
* removed 2026-07-06; it had drifted — signal 600 vs the book's "breve")
* - documentation tooling (lookup canonical defaults per family+intent)
* - authors as a reference when declaring `MorfoEvent.persistence`
*
* Persistence is NOT auto-applied by the runtime — `SomaRuntime.trigger`
* defaults to `'transient'` when the morfo doesn't declare it. This avoids
* silent behavior changes for components that don't yet opt in. Authors
* who want the canonical policy must declare it explicitly on the morfo
* event; `resolveHoldsByIntent(family, intent)` is provided so they don't
* have to guess.
*/
import type { Intent } from '../intent';
import type { SemaDurationSpec } from './durations';
import type { SemaFamily, SignalPersistence } from './types';
/**
* Canonical hold + persistence policy per family + intent.
*
* Encoded as a sparse map: each family's `_default` is the policy for
* absent / neutral intent; per-intent entries override. Intents NOT
* present fall back to `_default`.
*
* Reading: `SEMA_HOLDS_BY_INTENT.signal.threat = { hold: 'brief', persistence: 'untilAction' }`
* — a `signal.alert + threat` signal must be perceptible for at LEAST
* `brief` (240 ms) and persists in the DOM until the user acknowledges
* (caller clears it).
*
* Values are labels on the perceptual scale (never raw ms) so any future
* scale adjustment propagates automatically.
*/
export const SEMA_HOLDS_BY_INTENT = {
contact: {
// TABLA 32.1: "dentro de la ventana de atribución causal".
_default: { hold: 'glimpse', persistence: 'transient' }
},
emerge: {
// TABLA 32.1: dropdown "breve" · tooltip "breve-media" (per-event
// morfo `hold` override covers the longer cases).
_default: { hold: 'brief', persistence: 'transient' }
},
shift: {
// TABLA 32.1: "media, con orientación".
_default: { hold: 'noticed', persistence: 'transient' }
},
commit: {
// TABLA 32.1: commit.save + affirm "breve, sin interrumpir flujo".
_default: { hold: 'brief', persistence: 'transient' },
// TABLA 32.1: commit.complete + fulfill "breve-media, más
// resolutivo" → the settled (400) step (was `noticed` 600 — too
// long for "breve-media"; corrected 2026-07-06 with the new step).
fulfill: { hold: 'settled', persistence: 'transient' }
},
signal: {
// TABLA 32.2: signal.announce + neutral "breve o contextual".
_default: { hold: 'brief', persistence: 'transient' },
// TABLA 32.2: signal.warn + risk "hasta corrección/cierre" →
// untilFix. Hold = minimum display so a quickly-fixed warning
// still flashes for perceptibility.
risk: { hold: 'brief', persistence: 'untilFix' },
// TABLA 32.2: signal.alert + threat "hasta acción — alta, pero no
// infinita" → untilAction.
threat: { hold: 'brief', persistence: 'untilAction' },
// TABLA 32.1: commit.delete + loss "breve + huella" — loss is
// consumed grief: it registers and goes (`brief`, was `noticed`
// 600 against the book's "breve"; corrected 2026-07-06). The
// huella (trace) lives in undo/state, owned by the caller — not
// in a longer signal.
loss: { hold: 'brief', persistence: 'transient' }
},
handle: {
_default: { hold: 'brief', persistence: 'transient' }
},
sustain: {
// TABLA 32.1: "mientras dure el proceso" — state-bound, no fixed
// hold. The `hold` here is the minimum display for the perceptual
// transition into sustain mode.
_default: { hold: 'noticed', persistence: 'stateBound' }
},
delegate: {
// Cap. 29 — delegate is structural, signal weight similar to
// sustain. Transient by default; specific compositions
// (delegate + signal.warn, etc.) get their persistence from the
// signal partner.
_default: { hold: 'noticed', persistence: 'transient' }
}
} as const satisfies Record<
SemaFamily,
{ _default: HoldsPolicy } & Partial<Record<Intent, HoldsPolicy>>
>;
/**
* Hold + persistence pair resolved for a given family + intent. Authors
* declare this verbatim on `MorfoEvent` to opt into the canonical policy
* (the framework's materialization of the book's regions — see the header);
* otherwise `SomaRuntime` defaults to `'transient'`.
*/
export interface HoldsPolicy {
hold: SemaDurationSpec;
persistence: SignalPersistence;
}
/**
* Lookup the canonical hold + persistence for a given family + intent.
*
* - Family + specific intent declared → uses the intent's override.
* - Family + neutral / unspecified intent → uses the family's `_default`.
* - Family unknown → returns `undefined` (caller should fall back to the
* engine's default behavior).
*
* Pure function — no caching needed, lookup is constant time.
*/
export function resolveHoldsByIntent(
family: SemaFamily | undefined,
intent?: Intent | undefined
): HoldsPolicy | undefined {
if (!family) return undefined;
const entry = SEMA_HOLDS_BY_INTENT[family] as
| ({ _default: HoldsPolicy } & Partial<Record<Intent, HoldsPolicy>>)
| undefined;
if (!entry) return undefined;
if (intent && intent in entry) {
const perIntent = entry[intent];
if (perIntent) return perIntent;
}
return entry._default;
}

Powered by TurnKey Linux.