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

89 lines
3.2 KiB

import type { DomApplier, DomAttrValue } from '$adom';
import type { SemanticSignal } from './signal';
/**
* Project a `SemanticSignal` onto its target as `data-event-*` attrs.
*
* These are the **semantic tokens** the cascade matches against
* (`[data-event-family="signal"]`, `[data-event-intent="threat"]`, …) and
* the same tokens that eidos CSS reads to tint borders / pulse focus
* during the hold. Same surface for sema's signal-time matching and
* eidos's CSS animation.
*
* Ownership: `VisualChannel.prepare()` projects these attrs BEFORE the
* cascade resolver, through a `SignalProjector`. The engine only invokes
* generic channel prepare hooks and cleans their handles AFTER the visual
* hold.
*
* Attrs written:
* - `data-event` — signal.name (always)
* - `data-event-id` — signal.id (always; the engine generates one
* if the caller didn't pass it)
* - `data-event-phase` — `'active'` (always)
* - `data-event-family` — signal.family (when present)
* - `data-event-intent` — signal.intent (when present)
*/
export function eventAttrs(signal: SemanticSignal): Record<string, DomAttrValue> {
const attrs: Record<string, DomAttrValue> = {
'data-event': signal.name,
'data-event-id': signal.id ?? '',
'data-event-phase': 'active',
'data-event-family': signal.family,
'data-event-intent': signal.intent
};
return attrs;
}
export function clearEventAttrs(): Record<string, DomAttrValue> {
return {
'data-event': undefined,
'data-event-id': undefined,
'data-event-phase': undefined,
'data-event-family': undefined,
'data-event-intent': undefined
};
}
export function stampEventAttrs(
target: HTMLElement,
signal: SemanticSignal,
dom: DomApplier
): void {
dom.apply({
target,
attrs: eventAttrs(signal)
});
}
/**
* Retire the projection — but ONLY if it is still the one this signal wrote.
*
* The surface is one slot per element (`data-event`, `-id`, `-phase`,
* `-family`, `-intent`), so a second occurrence on the same node overwrites
* the first. Until 2026-08-10 the cleanup took the signal and DISCARDED it
* (`_signal`), wiping whatever it found: the older signal, finishing its hold
* first, erased the projection of the newer one mid-hold. Three independent
* audits measured it — fable S1 (2026-07-01), sema S-17 (2026-08-05, knob:
* `commit-set` erased 24 ms after birth instead of 240) and blocks A-36/A-65
* (2026-08-01/09, reproduced in the browser) — and it survived all three.
*
* The id is already in the DOM, so ownership costs one attribute read. When it
* does not match, the stamp belongs to another occurrence and the right thing
* to do is nothing: whoever owns it will retire it when ITS hold ends.
*/
export function unstampEventAttrs(
target: HTMLElement,
signal: SemanticSignal,
dom: DomApplier
): void {
const owner = target.getAttribute('data-event-id');
// `owner === null` means nothing is stamped — cleaning is a harmless no-op
// and keeps the function idempotent, which the projection handle relies on.
if (owner !== null && signal.id !== undefined && owner !== signal.id) return;
dom.apply({
target,
attrs: clearEventAttrs()
});
}

Powered by TurnKey Linux.