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/soma/components/gradient-builder/gradient-builder-provider.s...

468 lines
15 KiB

feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
import { context, type WithRefOpts } from '../../provider';
import { type Active, type ActiveProps, type StateProps } from '$libs/reactive';
import { boolToEmptyStrOrUndef } from '$adom';
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
import type { OnChangeFn } from '../../types';
import { KEYS } from '../../keyboard';
import type { Gradient, GradientStop, StopColor, GradientKind } from '$libs/gradient';
import { gradientBuilderMorfo } from '../../../morfo/components/gradient-builder';
import { Soma } from '../../core/soma.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
const clamp01 = (n: number): number => Math.min(1, Math.max(0, n));
const wrap360 = (deg: number): number => ((deg % 360) + 360) % 360;
/** Position step for a keyboard nudge (1%); Shift uses the coarse step. */
const STEP = 0.01;
const COARSE_STEP = 0.1;
// ── Root ─────────────────────────────────────────────────────────────────────
interface GradientBuilderOpts
extends
WithRefOpts,
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
StateProps<{ value: Gradient }>,
ActiveProps<{
disabled: boolean;
onValueCommit: OnChangeFn<Gradient>;
}> {}
/**
* Headless GradientBuilder — the state machine over the canonical `Gradient`
* model (`$libs/gradient`). Owns stop add / move / remove / recolor, the kind
* (linear/radial/conic), and the angle. Every mutation flows through here so the
* Stop slider thumbs, the track, and the controls all read one source of truth.
* v1 edits the `stops` of a linear/radial/conic gradient (mesh editing is the
* fast-follow).
*/
export class GradientBuilderProvider {
readonly opts: GradientBuilderOpts;
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo El defecto que tres auditorías encontraron y ninguna cerró no era que N eventos redirigieran sin declararlo: era que la API de emisión aceptaba un HTMLElement suelto, compensando que el registro de partes perdía la identidad de instancia. Censar y arreglar a mano reproduce el modo de fallo. Esto lo hace inexpresable. F1 — SomaRuntime<M> genérico, SIN default. Nombres de evento, kebabs de parte y claves de events/actions tipados contra la declaración (EventNameOf / PartKebabOf / ActionNameOf, los extractores que semaSelector ya usaba del otro lado). 104 anotaciones migradas + 3 wrappers que devolvían tipado pero tomaban sources ANCHO. SomaRuntime<Morfo> queda sólo en la tabla de contratos. F2 — registro por instancia: Map<part, PartRegistration[]>, pertenencia mantenida por el stream del attachment (poda al desmontar, re-entrada al remontar), resolución liveReg = la instancia VIVA más nueva. Mata de paso la clase "la más nueva muerta ensombrece a una viva". F3 — tres formas legales de destino: el target declarado; el anclaje por instancia (SomaRuntimePart.trigger, tipado por EventNameTargeting, + runtime.partInstance por identidad de referencia); y targetFallback, la cadena por estado de montaje declarada en el morfo y resuelta por resolveEmitTarget — LA resolución única para validación, emit y foco a11y, que ya no pueden discrepar. ~30 componentes migrados; censo de targetOverride 148 -> 89. F4 — A-36 tenía un superviviente. Medido en dropdown-menu: bajo `pre` el `open` no colisionaba con el contact-activate del Button compuesto, se PERDÍA entero porque su content no había montado. Aplicada la receta de float-panel a dropdown-menu Y context-menu. Ahora: contact-activate en trigger, open en content — dos superficies, ambas expresando. Submenús mudos, tres componentes. No era cableado: el morfo no declaraba el evento. onion-menu recibe emerge-expand/emerge-collapse (la forma de TreeView: un nivel revela sus hijos en el sitio); dropdown-menu y context-menu reciben sub-open/sub-close sobre sub-content, distintos del open raíz a propósito porque el sub es su propia superficie flotante. Nacen con la receta A-36 para no repetirla en código nuevo. chronos — declaraba 26 partes y registraba UNA; 17 role= a mano y el ARIA declarado no llegaba nunca al DOM. Se anula la mitad sobre-extendida de la decisión de 29ffe902a: la reutilización sigue viajando por el motor puro, pero la ESTRUCTURA se compone como en Table y Calendar. C1 (las 6 partes que emiten, chronos con cero targetOverride) + C2 (8 singletons) + C3 8/9. Registrar una parte DESTAPA declaraciones falsas, tres veces en un pase: el chip declaraba defaultElement button y ponía type= sobre un div (anida el asa de resize); grid-row declaraba archetype item y pintó la semana entera como fila de lista; y un aria por propRef desaparece en SILENCIO si el provider no publica la fuente. En los tres el arreglo fue la DECLARACIÓN, no el DOM. Verificado: check 74 = línea base medida con stash, DELTA CERO sostenido en cada lote · 458 pasan (chronos+morfo+sema) · navegador limpio en las tres vistas de chronos, con el contrato ARIA vivo por primera vez (aria-labelledby resolviendo a «junio de 2026», aria-readonly/disabled que no se habían emitido nunca). Handoff en docs/process/CONTINUE-perceptual-surface.md. Aparte, y decidido por el autor: docs/process/PLAN-event-name-normalization.md — prefijo de familia siempre; 36 de 252 sin prefijo, cero ambiguos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
readonly runtime: SomaRuntime<typeof gradientBuilderMorfo>;
readonly runtimePart: SomaRuntimePart<typeof gradientBuilderMorfo, 'provider'>;
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
readonly soma: Soma;
static readonly ctx = context<GradientBuilderProvider>('GradientBuilder');
static get(): GradientBuilderProvider | undefined {
return this.ctx.getOr(undefined) as GradientBuilderProvider | undefined;
}
static require(): GradientBuilderProvider {
return this.ctx.get();
}
static create(opts: GradientBuilderOpts) {
return new GradientBuilderProvider(opts);
}
/** The stop the controls edit (its color, etc.). */
selectedIndex = $state(0);
/** The stop currently being dragged, or null. */
activeDragIndex = $state<number | null>(null);
/** Whether the color editor for the selected stop is open (opened by clicking a stop). */
colorEditOpen = $state(false);
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
/** The track element (the drag surface) — registered by the Track part. */
trackRef = $state<HTMLElement | null>(null);
openColorEdit(i: number): void {
this.selectStop(i);
this.colorEditOpen = true;
}
closeColorEdit(): void {
this.colorEditOpen = false;
}
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
get isDisabled(): boolean {
return this.opts.disabled.current;
}
/** The current gradient's stops (linear/radial/conic). Mesh has none → empty. */
readonly stops = $derived.by<readonly GradientStop[]>(() => {
const v = this.opts.value.current;
return v.kind === 'mesh' ? [] : v.stops;
});
readonly kind = $derived.by<GradientKind>(() => this.opts.value.current.kind);
/** The live gradient being edited — read by the eidos preview / track background. */
get value(): Gradient {
return this.opts.value.current;
}
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
private constructor(opts: GradientBuilderOpts) {
this.opts = opts;
this.soma = Soma.require();
this.runtime = this.soma.runtime(gradientBuilderMorfo, {
props: {
disabled: () => this.isDisabled
},
events: {
'commit-set': () => this.opts.onValueCommit.current(this.opts.value.current)
}
});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
feat(soma)!: fase C2c — el efecto syncAttrs muere; una tubería, cero ambigüedad Cierre de la fase C del P0: tras C2a (la bolsa resuelve el contrato) y C2b (una sola computación), el efecto era doble-escritura idempotente sin función. Se retira entero: `syncPartAttrs`, el flag del tipo y las ~71 registraciones que lo pasaban (tabs-provider y radio-group-provider viajaron en `8ac23047f` del eje theming con la retirada declarada en su cuerpo — coordinación por canal entre sesiones, historia completa desde ambos lados). El único escritor imperativo sancionado que queda es el prewrite de `trigger` (un commit que el morfo declara). La PRIMERA pasada de la suite completa de la historia (nadie la corría — el hallazgo original del informe demostrándose solo) destapó la cola: ~258 asserts en 34 ficheros de test de provider (jsdom, elementos sueltos) ejercitaban las escrituras DOM del efecto retirado, no el contrato. Todos re-firmados a asserts de bolsa, con tres leyes que salieron del trabajo: - la bolsa entrega valores CRUDOS (número 20, no '20'; booleans donde el getter los produce) — Svelte stringifica al render, el DOM no cambia; - una AUSENCIA se aserta contra la bolsa del PART (`'x' in props === false`), no contra un getter de provider que añada la clave explícita `undefined`; - el prewrite (`data-last-action`) SIGUE siendo assert de DOM — es el escritor imperativo que sobrevive. De la misma pasada: `skin-media-player` entra en `COMPETING_ROOTS` de lint.test (viste el contrato de media-player, clase audio-player — nació sin registrarse y nada lo corría), y los ~10 comentarios que describían el efecto (dialog-001, sticky, knob, field, media-player, editable, anchor-nav) pasan a describir la bolsa, verificados spread a spread antes de tocar nada. Verificación: runtime 49/49 · ssr-contract 6/6 · morfo:check 161/161 post-strip («All routed morfos validate» — el detector definitivo) · los 34 ficheros re-firmados en verde · suite completa con únicos rojos residuales los flaky de carga ya adjudicados (timeouts que pasan en solitario) · svelte-check src/ a 0 (71 = ledger exacto de web/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
context: GradientBuilderProvider.ctx
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
});
}
setTrackRef(el: HTMLElement | null): void {
this.trackRef = el;
}
/** Position 0..1 of stop `i` — its explicit position or the even distribution. */
stopPosition(i: number): number {
const s = this.stops;
const p = s[i]?.position;
if (p != null) return p;
return s.length > 1 ? i / (s.length - 1) : 0;
}
selectStop(i: number): void {
if (i >= 0 && i < this.stops.length) this.selectedIndex = i;
}
private writeStops(next: GradientStop[]): void {
const v = this.opts.value.current;
if (v.kind === 'mesh') return;
this.opts.value.current = { ...v, stops: next };
}
/** Set stop `i`'s position (0..1), no commit (the live drag / keyboard step). */
moveStop(i: number, position01: number): void {
if (this.isDisabled || i < 0 || i >= this.stops.length) return;
const stops = [...this.stops];
stops[i] = { ...stops[i], position: clamp01(position01) };
this.writeStops(stops);
}
/** Keyboard nudge — move + commit. */
nudgeStop(i: number, delta: number): void {
this.moveStop(i, this.stopPosition(i) + delta);
this.commit();
}
/**
* Midpoint between the SELECTED stop and the next one (by position). When the
* selected stop is the last (rightmost), the midpoint of the penultimate and
* the last — so the "+" button always inserts right next to the selection.
*/
private betweenSelectedAndNext(): number {
const sel = this.stopPosition(this.selectedIndex);
const ps = this.stops.map((_, i) => this.stopPosition(i)).sort((a, b) => a - b);
const next = ps.find((p) => p > sel);
if (next !== undefined) return (sel + next) / 2;
// The selection is the last stop — insert between the penultimate and it.
const last = ps[ps.length - 1] ?? 1;
const penult = ps[ps.length - 2] ?? 0;
return (penult + last) / 2;
}
/**
* Insert a stop, colored from the nearest neighbour. With no position, the
* stop lands BETWEEN the selected stop and the next one — or, if the selected
* is the last, between the penultimate and the last. The explicit position is
* used only for click-on-track.
*/
addStop(position01?: number): void {
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
if (this.isDisabled) return;
const pos = clamp01(position01 ?? this.betweenSelectedAndNext());
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
const stops = [...this.stops];
let nearest = 0;
let best = Infinity;
stops.forEach((_, idx) => {
const d = Math.abs(this.stopPosition(idx) - pos);
if (d < best) {
best = d;
nearest = idx;
}
});
const color: StopColor = stops[nearest]?.color ?? { kind: 'css', value: 'currentColor' };
stops.push({ color, position: pos });
stops.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
this.writeStops(stops);
this.selectedIndex = stops.findIndex((s) => s.position === pos);
this.commit();
}
/** Remove stop `i` (a gradient needs ≥ 2 stops). */
removeStop(i: number): void {
if (this.isDisabled || this.stops.length <= 2 || i < 0 || i >= this.stops.length) return;
const stops = this.stops.filter((_, idx) => idx !== i);
this.writeStops(stops);
this.selectedIndex = Math.min(this.selectedIndex, stops.length - 1);
this.commit();
}
/**
* Set stop `i`'s color. Live update only — does NOT commit (the inline color
* editor calls this on every change and commits on release via `commit()`).
* Committing per-change would fire the `commit-set` firma — a primary ring
* pulse on the provider card — on every dragged pixel.
*/
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
setStopColor(i: number, color: StopColor): void {
if (this.isDisabled || i < 0 || i >= this.stops.length) return;
const stops = [...this.stops];
stops[i] = { ...stops[i], color };
this.writeStops(stops);
}
/** Set stop `i`'s alpha (0..1). Live update only — see {@link setStopColor}. */
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
setStopAlpha(i: number, alpha: number): void {
if (this.isDisabled || i < 0 || i >= this.stops.length) return;
const stops = [...this.stops];
stops[i] = { ...stops[i], alpha: clamp01(alpha) };
this.writeStops(stops);
}
/** Switch geometry, preserving the stops. */
setKind(kind: 'linear' | 'radial' | 'conic'): void {
if (this.isDisabled) return;
const v = this.opts.value.current;
if (v.kind === kind || v.kind === 'mesh') return;
this.opts.value.current = {
kind,
stops: [...this.stops],
interpolation: v.interpolation,
hints: v.hints
} as Gradient;
this.commit();
}
/**
* Set the linear angle / conic from-angle (degrees). Live update only — the
* angle dial commits on release (see {@link setStopColor} for why).
*/
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
setAngle(deg: number): void {
if (this.isDisabled) return;
const v = this.opts.value.current;
if (v.kind === 'linear') this.opts.value.current = { ...v, angle: wrap360(deg) };
else if (v.kind === 'conic') this.opts.value.current = { ...v, from: wrap360(deg) };
}
/** The current angle for the dial (linear `angle` / conic `from`, else 0). */
get angle(): number {
const v = this.opts.value.current;
if (v.kind === 'linear') return v.angle ?? 0;
if (v.kind === 'conic') return v.from ?? 0;
return 0;
}
commit(): void {
void this.runtime.trigger('commit-set');
}
reset(value: Gradient): void {
this.opts.value.current = value;
void this.runtime.trigger('commit-reset');
}
/** Provider element attrs — morfo-resolved + `data-kind` (set imperatively). */
get props() {
fix(uix): fase A cierre — los seis rojos del censo morfo:check, adjudicados Cada uno leído antes de tocarlo; tres clases distintas: - combobox.input y gradient-builder.track: el morfo declara el data-* SIN value (soma posee el valor — el idioma que el propio trigger de combobox documenta) y nadie lo escribía. El comentario del input además afirmaba que la bolsa lo resolvía, cosa que una declaración sin fuente no puede hacer. Escritura manual, como sus hermanos. - menu-dial.trigger: componente de scope eidos (contrato espejado a mano); el trigger estampaba marker y aria pero olvidó data-state — la lista de al lado sí lo lleva. Espejo del idioma existente. - calendar (data-calendar-surface) y fab (data-fab-size): attrs visuales de wrapper / hooks compartidos de eidos, sancionados FUERA del morfo por la doctrina firmada (precedente: data-combobox-control). El instrumento no conocía la doctrina: nace scripts/eidos-only-attrs.ts como lista ÚNICA que consumen morfo-check y eidos-lint-all, para que los dos instrumentos no puedan divergir. - palabras.handle: el grip porta el escape del focus-scope a propósito (comentario in situ con la razón); la declaración alcanza a la implementación sancionada. Los otros cuatro rojos del censo previo no eran contrato: accordion, background y link eran timeouts de compilación fría (pasan en caliente) y color-field lo sanó la unificación de la bolsa por sí sola. Verificación: morfo:check 160/161 con los seis en PASS — el único rojo restante (natural-time-picker) es la clase flaky del instrumento «demo que navega bajo el eval» (no falló en ninguna de las dos pasadas previas; la ruta verificada a mano en navegador con su contrato completo) · schema sweep 89/89 · eidos:lint exit 0 · combobox 11/11 · palabras 84/84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
return { ...this.runtimePart.props, 'data-kind': this.kind };
}
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
/** Map a pointer x to a position 0..1 along the registered track. */
positionFromPointer(clientX: number): number {
const el = this.trackRef;
if (!el) return 0;
const rect = el.getBoundingClientRect();
if (rect.width === 0) return 0;
return clamp01((clientX - rect.left) / rect.width);
}
// ── Drag (driven by a Stop via pointer capture) ──────────────────────────
startStopDrag(i: number): void {
this.selectStop(i);
this.activeDragIndex = i;
void this.runtime.trigger('handle-pick');
}
dragStopTo(clientX: number): void {
const i = this.activeDragIndex;
if (i == null) return;
this.moveStop(i, this.positionFromPointer(clientX));
void this.runtime.trigger('handle-drag');
}
endStopDrag(): void {
if (this.activeDragIndex == null) return;
this.activeDragIndex = null;
this.commit();
}
}
// ── Stop (one slider thumb — the keyboard-accessible color stop) ──────────────
interface GradientStopOpts extends WithRefOpts {
index: Active<number>;
}
export class GradientStopProvider {
readonly opts: GradientStopOpts;
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo El defecto que tres auditorías encontraron y ninguna cerró no era que N eventos redirigieran sin declararlo: era que la API de emisión aceptaba un HTMLElement suelto, compensando que el registro de partes perdía la identidad de instancia. Censar y arreglar a mano reproduce el modo de fallo. Esto lo hace inexpresable. F1 — SomaRuntime<M> genérico, SIN default. Nombres de evento, kebabs de parte y claves de events/actions tipados contra la declaración (EventNameOf / PartKebabOf / ActionNameOf, los extractores que semaSelector ya usaba del otro lado). 104 anotaciones migradas + 3 wrappers que devolvían tipado pero tomaban sources ANCHO. SomaRuntime<Morfo> queda sólo en la tabla de contratos. F2 — registro por instancia: Map<part, PartRegistration[]>, pertenencia mantenida por el stream del attachment (poda al desmontar, re-entrada al remontar), resolución liveReg = la instancia VIVA más nueva. Mata de paso la clase "la más nueva muerta ensombrece a una viva". F3 — tres formas legales de destino: el target declarado; el anclaje por instancia (SomaRuntimePart.trigger, tipado por EventNameTargeting, + runtime.partInstance por identidad de referencia); y targetFallback, la cadena por estado de montaje declarada en el morfo y resuelta por resolveEmitTarget — LA resolución única para validación, emit y foco a11y, que ya no pueden discrepar. ~30 componentes migrados; censo de targetOverride 148 -> 89. F4 — A-36 tenía un superviviente. Medido en dropdown-menu: bajo `pre` el `open` no colisionaba con el contact-activate del Button compuesto, se PERDÍA entero porque su content no había montado. Aplicada la receta de float-panel a dropdown-menu Y context-menu. Ahora: contact-activate en trigger, open en content — dos superficies, ambas expresando. Submenús mudos, tres componentes. No era cableado: el morfo no declaraba el evento. onion-menu recibe emerge-expand/emerge-collapse (la forma de TreeView: un nivel revela sus hijos en el sitio); dropdown-menu y context-menu reciben sub-open/sub-close sobre sub-content, distintos del open raíz a propósito porque el sub es su propia superficie flotante. Nacen con la receta A-36 para no repetirla en código nuevo. chronos — declaraba 26 partes y registraba UNA; 17 role= a mano y el ARIA declarado no llegaba nunca al DOM. Se anula la mitad sobre-extendida de la decisión de 29ffe902a: la reutilización sigue viajando por el motor puro, pero la ESTRUCTURA se compone como en Table y Calendar. C1 (las 6 partes que emiten, chronos con cero targetOverride) + C2 (8 singletons) + C3 8/9. Registrar una parte DESTAPA declaraciones falsas, tres veces en un pase: el chip declaraba defaultElement button y ponía type= sobre un div (anida el asa de resize); grid-row declaraba archetype item y pintó la semana entera como fila de lista; y un aria por propRef desaparece en SILENCIO si el provider no publica la fuente. En los tres el arreglo fue la DECLARACIÓN, no el DOM. Verificado: check 74 = línea base medida con stash, DELTA CERO sostenido en cada lote · 458 pasan (chronos+morfo+sema) · navegador limpio en las tres vistas de chronos, con el contrato ARIA vivo por primera vez (aria-labelledby resolviendo a «junio de 2026», aria-readonly/disabled que no se habían emitido nunca). Handoff en docs/process/CONTINUE-perceptual-surface.md. Aparte, y decidido por el autor: docs/process/PLAN-event-name-normalization.md — prefijo de familia siempre; 36 de 252 sin prefijo, cero ambiguos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
readonly runtimePart: SomaRuntimePart<typeof gradientBuilderMorfo, 'stop'>;
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
readonly provider = GradientBuilderProvider.require();
static readonly ctx = context<GradientStopProvider>('GradientStop');
static create(opts: GradientStopOpts) {
return new GradientStopProvider(opts);
}
private constructor(opts: GradientStopOpts) {
this.opts = opts;
this.runtimePart = this.provider.runtime.part('stop', {
id: opts.id,
ref: opts.ref,
owner: this,
context: GradientStopProvider.ctx,
feat(soma)!: fase C2c — el efecto syncAttrs muere; una tubería, cero ambigüedad Cierre de la fase C del P0: tras C2a (la bolsa resuelve el contrato) y C2b (una sola computación), el efecto era doble-escritura idempotente sin función. Se retira entero: `syncPartAttrs`, el flag del tipo y las ~71 registraciones que lo pasaban (tabs-provider y radio-group-provider viajaron en `8ac23047f` del eje theming con la retirada declarada en su cuerpo — coordinación por canal entre sesiones, historia completa desde ambos lados). El único escritor imperativo sancionado que queda es el prewrite de `trigger` (un commit que el morfo declara). La PRIMERA pasada de la suite completa de la historia (nadie la corría — el hallazgo original del informe demostrándose solo) destapó la cola: ~258 asserts en 34 ficheros de test de provider (jsdom, elementos sueltos) ejercitaban las escrituras DOM del efecto retirado, no el contrato. Todos re-firmados a asserts de bolsa, con tres leyes que salieron del trabajo: - la bolsa entrega valores CRUDOS (número 20, no '20'; booleans donde el getter los produce) — Svelte stringifica al render, el DOM no cambia; - una AUSENCIA se aserta contra la bolsa del PART (`'x' in props === false`), no contra un getter de provider que añada la clave explícita `undefined`; - el prewrite (`data-last-action`) SIGUE siendo assert de DOM — es el escritor imperativo que sobrevive. De la misma pasada: `skin-media-player` entra en `COMPETING_ROOTS` de lint.test (viste el contrato de media-player, clase audio-player — nació sin registrarse y nada lo corría), y los ~10 comentarios que describían el efecto (dialog-001, sticky, knob, field, media-player, editable, anchor-nav) pasan a describir la bolsa, verificados spread a spread antes de tocar nada. Verificación: runtime 49/49 · ssr-contract 6/6 · morfo:check 161/161 post-strip («All routed morfos validate» — el detector definitivo) · los 34 ficheros re-firmados en verde · suite completa con únicos rojos residuales los flaky de carga ya adjudicados (timeouts que pasan en solitario) · svelte-check src/ a 0 (71 = ledger exacto de web/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// Per-stop sources for the morfo's value-bound aria (aria-valuenow /
fix(uix): fase A cierre — los seis rojos del censo morfo:check, adjudicados Cada uno leído antes de tocarlo; tres clases distintas: - combobox.input y gradient-builder.track: el morfo declara el data-* SIN value (soma posee el valor — el idioma que el propio trigger de combobox documenta) y nadie lo escribía. El comentario del input además afirmaba que la bolsa lo resolvía, cosa que una declaración sin fuente no puede hacer. Escritura manual, como sus hermanos. - menu-dial.trigger: componente de scope eidos (contrato espejado a mano); el trigger estampaba marker y aria pero olvidó data-state — la lista de al lado sí lo lleva. Espejo del idioma existente. - calendar (data-calendar-surface) y fab (data-fab-size): attrs visuales de wrapper / hooks compartidos de eidos, sancionados FUERA del morfo por la doctrina firmada (precedente: data-combobox-control). El instrumento no conocía la doctrina: nace scripts/eidos-only-attrs.ts como lista ÚNICA que consumen morfo-check y eidos-lint-all, para que los dos instrumentos no puedan divergir. - palabras.handle: el grip porta el escape del focus-scope a propósito (comentario in situ con la razón); la declaración alcanza a la implementación sancionada. Los otros cuatro rojos del censo previo no eran contrato: accordion, background y link eran timeouts de compilación fría (pasan en caliente) y color-field lo sanó la unificación de la bolsa por sí sola. Verificación: morfo:check 160/161 con los seis en PASS — el único rojo restante (natural-time-picker) es la clase flaky del instrumento «demo que navega bajo el eval» (no falló en ninguna de las dos pasadas previas; la ruta verificada a mano en navegador con su contrato completo) · schema sweep 89/89 · eidos:lint exit 0 · combobox 11/11 · palabras 84/84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
// aria-valuetext / aria-label) — resolved via `the render bag`.
props: {
position: () => this.position,
valueText: () => this.valueText,
stopLabel: () => this.stopLabel
}
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
});
}
get index(): number {
return this.opts.index.current;
}
/** Position % for `aria-valuenow`. */
get position(): number {
return Math.round(this.provider.stopPosition(this.index) * 100);
}
get isSelected(): boolean {
return this.provider.selectedIndex === this.index;
}
/** Live-announced description: "Stop 2 of 4, 40%". */
get valueText(): string {
return `Stop ${this.index + 1} of ${this.provider.stops.length}, ${this.position}%`;
}
get stopLabel(): string {
return `Color stop ${this.index + 1}`;
}
/** Element attrs — morfo-resolved aria + state attrs + the drag/keyboard handlers. */
get props() {
return {
fix(uix): fase A cierre — los seis rojos del censo morfo:check, adjudicados Cada uno leído antes de tocarlo; tres clases distintas: - combobox.input y gradient-builder.track: el morfo declara el data-* SIN value (soma posee el valor — el idioma que el propio trigger de combobox documenta) y nadie lo escribía. El comentario del input además afirmaba que la bolsa lo resolvía, cosa que una declaración sin fuente no puede hacer. Escritura manual, como sus hermanos. - menu-dial.trigger: componente de scope eidos (contrato espejado a mano); el trigger estampaba marker y aria pero olvidó data-state — la lista de al lado sí lo lleva. Espejo del idioma existente. - calendar (data-calendar-surface) y fab (data-fab-size): attrs visuales de wrapper / hooks compartidos de eidos, sancionados FUERA del morfo por la doctrina firmada (precedente: data-combobox-control). El instrumento no conocía la doctrina: nace scripts/eidos-only-attrs.ts como lista ÚNICA que consumen morfo-check y eidos-lint-all, para que los dos instrumentos no puedan divergir. - palabras.handle: el grip porta el escape del focus-scope a propósito (comentario in situ con la razón); la declaración alcanza a la implementación sancionada. Los otros cuatro rojos del censo previo no eran contrato: accordion, background y link eran timeouts de compilación fría (pasan en caliente) y color-field lo sanó la unificación de la bolsa por sí sola. Verificación: morfo:check 160/161 con los seis en PASS — el único rojo restante (natural-time-picker) es la clase flaky del instrumento «demo que navega bajo el eval» (no falló en ninguna de las dos pasadas previas; la ruta verificada a mano en navegador con su contrato completo) · schema sweep 89/89 · eidos:lint exit 0 · combobox 11/11 · palabras 84/84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
...this.runtimePart.props,
tabindex: 0,
'data-selected': boolToEmptyStrOrUndef(this.isSelected),
'data-dragging': boolToEmptyStrOrUndef(this.provider.activeDragIndex === this.index),
onkeydown: this.onkeydown,
onpointerdown: this.onpointerdown,
onpointermove: this.onpointermove,
onpointerup: this.onpointerup
};
}
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
/** The a11y differentiator: every stop is a real slider with arrow/Home/End/Delete. */
readonly onkeydown = (e: KeyboardEvent): void => {
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
const i = this.index;
const step = e.shiftKey ? COARSE_STEP : STEP;
switch (e.key) {
case KEYS.ARROW_LEFT:
case KEYS.ARROW_DOWN:
e.preventDefault();
this.provider.nudgeStop(i, -step);
break;
case KEYS.ARROW_RIGHT:
case KEYS.ARROW_UP:
e.preventDefault();
this.provider.nudgeStop(i, step);
break;
case KEYS.HOME:
e.preventDefault();
this.provider.moveStop(i, 0);
this.provider.commit();
break;
case KEYS.END:
e.preventDefault();
this.provider.moveStop(i, 1);
this.provider.commit();
break;
case KEYS.DELETE:
case KEYS.BACKSPACE:
e.preventDefault();
this.provider.removeStop(i);
break;
case KEYS.ENTER:
case KEYS.SPACE:
e.preventDefault();
this.provider.selectStop(i);
break;
}
};
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
/** Pointer x at press — distinguishes a click (opens the color picker) from a
* drag (moves the stop). */
private pressX = 0;
private didDrag = false;
readonly onpointerdown = (e: PointerEvent): void => {
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
const el = e.currentTarget as HTMLElement | null;
el?.setPointerCapture?.(e.pointerId);
this.pressX = e.clientX;
this.didDrag = false;
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
this.provider.startStopDrag(this.index);
};
readonly onpointermove = (e: PointerEvent): void => {
if (this.provider.activeDragIndex == null) return;
if (Math.abs(e.clientX - this.pressX) > 3) this.didDrag = true;
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
this.provider.dragStopTo(e.clientX);
};
readonly onpointerup = (e: PointerEvent): void => {
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
const el = e.currentTarget as HTMLElement | null;
el?.releasePointerCapture?.(e.pointerId);
this.provider.endStopDrag();
// A click (no drag) on the stop opens its color picker; a drag just moved it.
if (!this.didDrag) this.provider.openColorEdit(this.index);
};
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
}
// ── Track (the stop rail / drag surface — click empty space to add a stop) ────
interface GradientTrackOpts extends WithRefOpts {}
export class GradientTrackProvider {
readonly opts: GradientTrackOpts;
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo El defecto que tres auditorías encontraron y ninguna cerró no era que N eventos redirigieran sin declararlo: era que la API de emisión aceptaba un HTMLElement suelto, compensando que el registro de partes perdía la identidad de instancia. Censar y arreglar a mano reproduce el modo de fallo. Esto lo hace inexpresable. F1 — SomaRuntime<M> genérico, SIN default. Nombres de evento, kebabs de parte y claves de events/actions tipados contra la declaración (EventNameOf / PartKebabOf / ActionNameOf, los extractores que semaSelector ya usaba del otro lado). 104 anotaciones migradas + 3 wrappers que devolvían tipado pero tomaban sources ANCHO. SomaRuntime<Morfo> queda sólo en la tabla de contratos. F2 — registro por instancia: Map<part, PartRegistration[]>, pertenencia mantenida por el stream del attachment (poda al desmontar, re-entrada al remontar), resolución liveReg = la instancia VIVA más nueva. Mata de paso la clase "la más nueva muerta ensombrece a una viva". F3 — tres formas legales de destino: el target declarado; el anclaje por instancia (SomaRuntimePart.trigger, tipado por EventNameTargeting, + runtime.partInstance por identidad de referencia); y targetFallback, la cadena por estado de montaje declarada en el morfo y resuelta por resolveEmitTarget — LA resolución única para validación, emit y foco a11y, que ya no pueden discrepar. ~30 componentes migrados; censo de targetOverride 148 -> 89. F4 — A-36 tenía un superviviente. Medido en dropdown-menu: bajo `pre` el `open` no colisionaba con el contact-activate del Button compuesto, se PERDÍA entero porque su content no había montado. Aplicada la receta de float-panel a dropdown-menu Y context-menu. Ahora: contact-activate en trigger, open en content — dos superficies, ambas expresando. Submenús mudos, tres componentes. No era cableado: el morfo no declaraba el evento. onion-menu recibe emerge-expand/emerge-collapse (la forma de TreeView: un nivel revela sus hijos en el sitio); dropdown-menu y context-menu reciben sub-open/sub-close sobre sub-content, distintos del open raíz a propósito porque el sub es su propia superficie flotante. Nacen con la receta A-36 para no repetirla en código nuevo. chronos — declaraba 26 partes y registraba UNA; 17 role= a mano y el ARIA declarado no llegaba nunca al DOM. Se anula la mitad sobre-extendida de la decisión de 29ffe902a: la reutilización sigue viajando por el motor puro, pero la ESTRUCTURA se compone como en Table y Calendar. C1 (las 6 partes que emiten, chronos con cero targetOverride) + C2 (8 singletons) + C3 8/9. Registrar una parte DESTAPA declaraciones falsas, tres veces en un pase: el chip declaraba defaultElement button y ponía type= sobre un div (anida el asa de resize); grid-row declaraba archetype item y pintó la semana entera como fila de lista; y un aria por propRef desaparece en SILENCIO si el provider no publica la fuente. En los tres el arreglo fue la DECLARACIÓN, no el DOM. Verificado: check 74 = línea base medida con stash, DELTA CERO sostenido en cada lote · 458 pasan (chronos+morfo+sema) · navegador limpio en las tres vistas de chronos, con el contrato ARIA vivo por primera vez (aria-labelledby resolviendo a «junio de 2026», aria-readonly/disabled que no se habían emitido nunca). Handoff en docs/process/CONTINUE-perceptual-surface.md. Aparte, y decidido por el autor: docs/process/PLAN-event-name-normalization.md — prefijo de familia siempre; 36 de 252 sin prefijo, cero ambiguos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
readonly runtimePart: SomaRuntimePart<typeof gradientBuilderMorfo, 'track'>;
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
readonly provider = GradientBuilderProvider.require();
static readonly ctx = context<GradientTrackProvider>('GradientTrack');
static create(opts: GradientTrackOpts) {
return new GradientTrackProvider(opts);
}
private constructor(opts: GradientTrackOpts) {
this.opts = opts;
this.runtimePart = this.provider.runtime.part('track', {
id: opts.id,
ref: opts.ref,
owner: this,
feat(soma)!: fase C2c — el efecto syncAttrs muere; una tubería, cero ambigüedad Cierre de la fase C del P0: tras C2a (la bolsa resuelve el contrato) y C2b (una sola computación), el efecto era doble-escritura idempotente sin función. Se retira entero: `syncPartAttrs`, el flag del tipo y las ~71 registraciones que lo pasaban (tabs-provider y radio-group-provider viajaron en `8ac23047f` del eje theming con la retirada declarada en su cuerpo — coordinación por canal entre sesiones, historia completa desde ambos lados). El único escritor imperativo sancionado que queda es el prewrite de `trigger` (un commit que el morfo declara). La PRIMERA pasada de la suite completa de la historia (nadie la corría — el hallazgo original del informe demostrándose solo) destapó la cola: ~258 asserts en 34 ficheros de test de provider (jsdom, elementos sueltos) ejercitaban las escrituras DOM del efecto retirado, no el contrato. Todos re-firmados a asserts de bolsa, con tres leyes que salieron del trabajo: - la bolsa entrega valores CRUDOS (número 20, no '20'; booleans donde el getter los produce) — Svelte stringifica al render, el DOM no cambia; - una AUSENCIA se aserta contra la bolsa del PART (`'x' in props === false`), no contra un getter de provider que añada la clave explícita `undefined`; - el prewrite (`data-last-action`) SIGUE siendo assert de DOM — es el escritor imperativo que sobrevive. De la misma pasada: `skin-media-player` entra en `COMPETING_ROOTS` de lint.test (viste el contrato de media-player, clase audio-player — nació sin registrarse y nada lo corría), y los ~10 comentarios que describían el efecto (dialog-001, sticky, knob, field, media-player, editable, anchor-nav) pasan a describir la bolsa, verificados spread a spread antes de tocar nada. Verificación: runtime 49/49 · ssr-contract 6/6 · morfo:check 161/161 post-strip («All routed morfos validate» — el detector definitivo) · los 34 ficheros re-firmados en verde · suite completa con únicos rojos residuales los flaky de carga ya adjudicados (timeouts que pasan en solitario) · svelte-check src/ a 0 (71 = ledger exacto de web/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
context: GradientTrackProvider.ctx
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
});
// The track IS the drag surface the provider measures against.
$effect(() => {
this.provider.setTrackRef(this.opts.ref?.current ?? null);
});
}
get props() {
// Click-to-add on the rail — the reference-standard PRIMARY add path: a
// click on the bare track inserts a stop at the pointer position. Clicks
// that land on a Stop are ignored here (the Stop owns select / edit / drag);
// the coordinate-less Add button stays as the fallback.
fix(uix): fase A cierre — los seis rojos del censo morfo:check, adjudicados Cada uno leído antes de tocarlo; tres clases distintas: - combobox.input y gradient-builder.track: el morfo declara el data-* SIN value (soma posee el valor — el idioma que el propio trigger de combobox documenta) y nadie lo escribía. El comentario del input además afirmaba que la bolsa lo resolvía, cosa que una declaración sin fuente no puede hacer. Escritura manual, como sus hermanos. - menu-dial.trigger: componente de scope eidos (contrato espejado a mano); el trigger estampaba marker y aria pero olvidó data-state — la lista de al lado sí lo lleva. Espejo del idioma existente. - calendar (data-calendar-surface) y fab (data-fab-size): attrs visuales de wrapper / hooks compartidos de eidos, sancionados FUERA del morfo por la doctrina firmada (precedente: data-combobox-control). El instrumento no conocía la doctrina: nace scripts/eidos-only-attrs.ts como lista ÚNICA que consumen morfo-check y eidos-lint-all, para que los dos instrumentos no puedan divergir. - palabras.handle: el grip porta el escape del focus-scope a propósito (comentario in situ con la razón); la declaración alcanza a la implementación sancionada. Los otros cuatro rojos del censo previo no eran contrato: accordion, background y link eran timeouts de compilación fría (pasan en caliente) y color-field lo sanó la unificación de la bolsa por sí sola. Verificación: morfo:check 160/161 con los seis en PASS — el único rojo restante (natural-time-picker) es la clase flaky del instrumento «demo que navega bajo el eval» (no falló en ninguna de las dos pasadas previas; la ruta verificada a mano en navegador con su contrato completo) · schema sweep 89/89 · eidos:lint exit 0 · combobox 11/11 · palabras 84/84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month ago
return {
...this.runtimePart.props,
// The morfo declares `data-kind` WITHOUT a value — soma owns it, same
// idiom as preview's hand stamp. It was never written; morfo:check
// caught the required-attr gap (P0 fase A, audit 2026-08-26).
'data-kind': this.provider.kind,
onclick: this.onclick
};
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
}
private onclick = (e: MouseEvent): void => {
if (this.provider.isDisabled) return;
const target = e.target as Element | null;
if (target?.closest('[data-gradient-builder-stop]')) return;
this.provider.addStop(this.provider.positionFromPointer(e.clientX));
};
feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold Gradient axis — Phase 1 (engine + dogfood): - $libs/gradient: canonical Gradient model + gradientToCss serializer, pure and zero-dep, shared by the theming axis and the (WIP) GradientBuilder. Stops reference color ROLES → a --gradient-{name} re-tints with the seed and flips light/dark for free. Default `in oklch` interpolation. - build-gradient: role-derived factories (deepen/sheen/halo/aurora mesh) + ActiveEidos.applyGradients() (the 6th runtime builder, in ThemeSeed/applyTheme). - shimmer migrated to `in oklch`. Dogfood: /demos/cristal replaces its ~12 hardcoded gradients with applyGradients role tokens. Opacity = function of elevation (depth cue `translucency`): - New translucency depth-plane cue, sibling of shadow/blur: frost opacity now scales with elevation (foundation overlay 68% / modal 80%; cristal 52→66→80) via --depth-{plane}-translucency, consumed by the frost rule. base.css regenerated. GradientBuilder component — Phase 2 (scaffold, WIP): - morfo (contract) + soma provider state machine over the Gradient model: stops add/move/remove/recolor, kind, angle, pointer drag; every stop a keyboard- accessible role=slider thumb. Marked ACTIVE_DEV_TRACK until eidos/picker/demo land. Docs: $libs/gradient README + THEMING §gradient-axis / §translucency + token table. Demo cristal: scroll-reveal via uix.motion spring + hover glow; Select z-index ladder; aurora/title → role-derived tokens. contracts.test: ACTIVE_DEV_TRACK now filtered uniformly in both soma collectors. check: 0 new errors. Tests: $libs/gradient + build-gradient + base.css sync green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
}

Powered by TurnKey Linux.