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/event.ts

198 lines
5.9 KiB

import {
SEMA_FAMILY_POLICY,
type SemaActionEvent,
type SemaEvent,
type SemaEventKey,
type SemaEventLabel,
type SemaFamily,
type IntentBinding,
type SemaTransitionalFamily,
type SemaValencedFamily
} from './types'
import { INTENTS, type Intent } from '../intent'
export const SEMA_VALENCED_FAMILIES = [
'contact',
'commit',
sema: align vocabulary with libro_semantica (signal, shift, loss) Updates the canonical semantic vocabulary to match the latest formulation in src/docs/libro_semantica_completo.txt: Families (7, was 6): contact, commit, signal, handle, emerge, shift, sustain - `alert` → `signal` (rename: same perceptual function — attention / salience — but the book's name reflects the broader scope including notify, warn, emphasize, announce). - `shift` (NEW, transitional): frame change. Modal mount, navigation, mode change. Differs from emerge in that it reorganizes planes (heavier backdrop, deeper shadow) — "cruce de umbral" not just "aparición". Per book rule, the frame doesn't carry intent on its own; evaluation belongs to a companion `signal` event. Intents (6, was 5): neutral, affirm, fulfill, risk, threat, loss - `loss` (NEW): consummated negative consequence (negative valence, low activation, posterior). Differs from `threat` (anterior, high activation, convokes action): loss registers, doesn't convoke. Per book §11.5 "la amenaza no es la pérdida". sema-map.ts perceptual values: - `signal` family entry: kept the prior `alert` numbers verbatim (semantics is the same — attention/salience) under the new name. - `shift` family entry: motion 260ms ease-in-out 0.98→1, sound pitch 500 ascending gain 0.18 (deliberately quieter than emerge's 0.20 to not compete with companion signal), presence with stronger backdrop (0.5) + shadow (blur 32 y 12 op 0.2) emphasizing plane reorganization. - `loss` intent deltas: descending sound contour (-150 pitch), contraction (scale.to -0.04, motion duration ×0.95 — brief but grave), color toward purple-grave (hue replace 280, lower saturation/intensity), withdrawn presence (backdrop 0.05, no glow). ARIA `role="alert"` (W3C live region) is unrelated and untouched in soma/announce, soma/toast and morfo announce/field/form. The toast morfo gains `loss → role:'status' aria-live:'polite'` mapping — loss is consummated so it shouldn't interrupt. Verbs added per the book's worked examples: signal {warn, notify, emphasize}, shift {enter-mode, exit-mode, navigate}, commit {save, discard, delete}, contact {press, click, hover}, handle {pick, carry, drop}, emerge {reveal, show, hide}. Verified: vitest 9 files / 99 tests, smoke 68/68, morfo-check 66/66.
5 months ago
'signal',
'handle'
] as const satisfies readonly SemaValencedFamily[]
export const SEMA_TRANSITIONAL_FAMILIES = [
'emerge',
sema: align vocabulary with libro_semantica (signal, shift, loss) Updates the canonical semantic vocabulary to match the latest formulation in src/docs/libro_semantica_completo.txt: Families (7, was 6): contact, commit, signal, handle, emerge, shift, sustain - `alert` → `signal` (rename: same perceptual function — attention / salience — but the book's name reflects the broader scope including notify, warn, emphasize, announce). - `shift` (NEW, transitional): frame change. Modal mount, navigation, mode change. Differs from emerge in that it reorganizes planes (heavier backdrop, deeper shadow) — "cruce de umbral" not just "aparición". Per book rule, the frame doesn't carry intent on its own; evaluation belongs to a companion `signal` event. Intents (6, was 5): neutral, affirm, fulfill, risk, threat, loss - `loss` (NEW): consummated negative consequence (negative valence, low activation, posterior). Differs from `threat` (anterior, high activation, convokes action): loss registers, doesn't convoke. Per book §11.5 "la amenaza no es la pérdida". sema-map.ts perceptual values: - `signal` family entry: kept the prior `alert` numbers verbatim (semantics is the same — attention/salience) under the new name. - `shift` family entry: motion 260ms ease-in-out 0.98→1, sound pitch 500 ascending gain 0.18 (deliberately quieter than emerge's 0.20 to not compete with companion signal), presence with stronger backdrop (0.5) + shadow (blur 32 y 12 op 0.2) emphasizing plane reorganization. - `loss` intent deltas: descending sound contour (-150 pitch), contraction (scale.to -0.04, motion duration ×0.95 — brief but grave), color toward purple-grave (hue replace 280, lower saturation/intensity), withdrawn presence (backdrop 0.05, no glow). ARIA `role="alert"` (W3C live region) is unrelated and untouched in soma/announce, soma/toast and morfo announce/field/form. The toast morfo gains `loss → role:'status' aria-live:'polite'` mapping — loss is consummated so it shouldn't interrupt. Verbs added per the book's worked examples: signal {warn, notify, emphasize}, shift {enter-mode, exit-mode, navigate}, commit {save, discard, delete}, contact {press, click, hover}, handle {pick, carry, drop}, emerge {reveal, show, hide}. Verified: vitest 9 files / 99 tests, smoke 68/68, morfo-check 66/66.
5 months ago
'shift',
'sustain'
] as const satisfies readonly SemaTransitionalFamily[]
export const SEMA_FAMILIES = [
...SEMA_VALENCED_FAMILIES,
...SEMA_TRANSITIONAL_FAMILIES
] as const satisfies readonly SemaFamily[]
/**
* All valid `SemaEventKey` values, generated from the family × intent
* product plus family-only forms. Open template-literal types can't be
* enumerated by the type system; we materialise the array at module load
* for runtime membership checks.
*/
export const SEMA_EVENT_LABELS: readonly SemaEventKey[] = [
...SEMA_FAMILIES,
...SEMA_FAMILIES.flatMap((family) =>
INTENTS.map((intent) => `${family}-${intent}` as SemaEventKey)
)
]
const FAMILY_SET = new Set<SemaFamily>(SEMA_FAMILIES)
const VALENCED_FAMILY_SET = new Set<SemaValencedFamily>(SEMA_VALENCED_FAMILIES)
const TRANSITIONAL_FAMILY_SET = new Set<SemaTransitionalFamily>(SEMA_TRANSITIONAL_FAMILIES)
const INTENT_SET = new Set<Intent>(INTENTS)
const LABEL_SET = new Set<SemaEventKey>(SEMA_EVENT_LABELS)
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
export function isSemaFamily(value: unknown): value is SemaFamily {
return typeof value === 'string' && FAMILY_SET.has(value as SemaFamily)
}
export function isSemaValencedFamily(value: unknown): value is SemaValencedFamily {
return typeof value === 'string' && VALENCED_FAMILY_SET.has(value as SemaValencedFamily)
}
export function isSemaTransitionalFamily(value: unknown): value is SemaTransitionalFamily {
return typeof value === 'string' && TRANSITIONAL_FAMILY_SET.has(value as SemaTransitionalFamily)
}
export function isIntent(value: unknown): value is Intent {
return typeof value === 'string' && INTENT_SET.has(value as Intent)
}
export function isSemaEventLabel(value: unknown): value is SemaEventLabel {
return typeof value === 'string' && LABEL_SET.has(value as SemaEventKey)
}
export function isIntentBinding(value: unknown): value is IntentBinding {
if (!isRecord(value)) return false
if (!isIntent(value.default)) return false
if ('fromProp' in value && value.fromProp !== undefined && typeof value.fromProp !== 'string') return false
if ('supported' in value && value.supported !== undefined) {
if (!Array.isArray(value.supported)) return false
if (!value.supported.every(isIntent)) return false
}
return true
}
/**
* Validate the runtime shape of a `SemaEvent` against the per-family
* `SEMA_FAMILY_POLICY`:
*
* - `expected` policy → intent MUST be a valid literal or binding.
* - `allowed` / `optional` policy → intent may be absent; if present,
* must still be a valid literal or binding.
*
* Replaces the previous valenced/transitional split (which forbade
* intent on transitional families). Post-canon doctrine: any family can
* carry intent unless its policy says otherwise.
*/
export function isSemaEvent(value: unknown): value is SemaEvent {
if (!isRecord(value) || !isSemaFamily(value.family)) return false
const policy = SEMA_FAMILY_POLICY[value.family].intentPolicy
const intentValue = 'intent' in value ? value.intent : undefined
if (intentValue === undefined) {
return policy !== 'expected'
}
return isIntent(intentValue) || isIntentBinding(intentValue)
}
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/**
* Parsed shape of a `SemaEventKey`. With the policy reform, ANY family
* may carry an intent (or none) — the parsed shape only distinguishes
* "intent present" from "intent absent" without locking the family kind.
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
*/
export type ParsedSemaEventLabel =
| { family: SemaFamily }
| { family: SemaFamily; intent: Intent }
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
export function parseSemaEventLabel(label: SemaEventLabel): ParsedSemaEventLabel {
const [family, rawIntent] = label.split('-')
if (rawIntent === undefined) {
return { family: family as SemaFamily }
}
return {
family: family as SemaFamily,
intent: rawIntent as Intent
}
}
export function resolveIntent(
intent: Intent | IntentBinding,
props?: Record<string, unknown>
): Intent {
if (isIntent(intent)) return intent
const supported = intent.supported?.filter(isIntent) ?? [...INTENTS]
const fallback = supported[0] ?? intent.default
const candidate = intent.fromProp ? props?.[intent.fromProp] : undefined
if (isIntent(candidate) && supported.includes(candidate)) {
return candidate
}
return supported.includes(intent.default) ? intent.default : fallback
}
/**
* Normalize a `SemaActionEvent` (label or structured event) into its
* canonical resolved form. Preserves intent on ANY family that carries
* one — the previous "transitional families never carry intent" rule
* was the bug Phase 2 of the codex refactor fixes.
*/
export function normalizeSemaEvent(
event: SemaActionEvent,
props?: Record<string, unknown>
): {
family: SemaFamily
intent: Intent | null
label: SemaEventLabel
} {
if (typeof event === 'string') {
const parsed = parseSemaEventLabel(event)
if ('intent' in parsed) {
return {
family: parsed.family,
intent: parsed.intent,
label: event
}
}
return {
family: parsed.family,
intent: null,
label: event
}
}
const intentValue = 'intent' in event ? event.intent : undefined
if (intentValue === undefined) {
return {
family: event.family,
intent: null,
label: event.family as SemaEventLabel
}
}
const intent = resolveIntent(intentValue, props)
return {
family: event.family,
intent,
label: `${event.family}-${intent}` as SemaEventLabel
}
}
export function toSemaEventLabel(event: SemaActionEvent, props?: Record<string, unknown>): SemaEventLabel {
return normalizeSemaEvent(event, props).label
}

Powered by TurnKey Linux.