feat(gradient): S9 gradient-* — graduate from ACTIVE_DEV_TRACK + apg + dossiers (initiative close)

The initiative's real remaining scope (verified 2026-07-10 against the repo;
the "uncommitted work" memory was stale — builder landed in b3d0e290, picker
in f5eb8acb):

- contracts.test: remove gradient-builder/gradient-picker from
  ACTIVE_DEV_TRACK. The catalogue guards found exactly ONE violation — the
  missing soma barrel exports — now added (GradientBuilder, GradientPicker).
  Post-graduation: same 13 pre-existing failures (knob/menu-dial/metrics/
  onion-menu debris from other WIP), zero gradient mentions.
- apg declared on both morfos: builder cites slider (every stop + the angle
  dial IS a slider thumb — the keyboard-a11y differentiator); picker cites
  dialog-modal via the composed Popover (same rationale as date-picker/ntp).
- Both eidos READMEs upgraded to dossier form (Baseline / Comparativa /
  Decisiones / Gaps) transplanting the CONTINUE.md competitive research
  (OKLCH model shared with the token engine; keyboard-editable stops — the
  field is mouse-only RGBA exporters). Stale "active dev track" recipe notes
  corrected.
- gradient-builder demo: D-1.2 was a false negative — the tab union was the
  correct v2 9-tab set, just formatted multi-line; joined to one line
  (canary format). SSR smoke: 200 + 40 data-gradient-builder attrs.

Remaining on both: D-1.5/D-3.1/D-4.3 — the catalog-wide demo-census (fails
on 10/7 components INCLUDING the button canary), tracked in the dossiers'
Gaps, not gradient-specific.

Gates: component-audit 102 PASS · contracts 24 pass/13 pre-existing ·
vocab exit 0 · type baseline 57.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
menubar-v4-safe
dev 3 months ago
parent e8f12aa1dc
commit 45ae36919a

@ -170,9 +170,7 @@ function collectMorfoComponentNamesByScope(scope: string): string[] {
const ACTIVE_DEV_TRACK = new Set([
'words',
'palabras',
'chronos',
'gradient-builder',
'gradient-picker'
'chronos'
]);
// SYS-1 scope-drift guard. `scope` enumerates the layers that IMPLEMENT a

@ -1,5 +1,7 @@
# Eidos GradientBuilder
Fecha de revisión: 2026-07-10 (dossier S9 — graduado de `ACTIVE_DEV_TRACK`).
The visual layer over the headless [`GradientBuilder`](../../../soma/components/gradient-builder/README.md)
(soma). A framed editor card that composes the framework's existing components —
never re-implementing a primitive: the embedded **ColorPicker** (the selected
@ -7,6 +9,23 @@ stop's color, inline), **Slider** (the angle), **ToggleGroup** (the kind),
**Button** (add stop). Soma owns the state machine + the keyboard-accessible
stops; eidos positions, paints and frames.
## Baseline
Sin baseline air y sin análogo real en el campo: el survey de editores
(documentado en el header del morfo + el research de
`soma/components/gradient-builder/CONTINUE.md`) encontró que TODOS los
incumbentes (Figma, Photoshop, los pickers web, gradient.style) almacenan
RGBA + geometría y exportan un string muerto, y todos son mouse-only. El
diferencial UIX es doble:
1. **El modelo es el token**: edita el `Gradient` canónico de
`$libs/gradient` — el MISMO modelo que deriva el eje de theming
(`buildGradient`) — así que un gradiente construido aquí es también un
token tematizable (OKLCH, stops role-ref | oklch | css).
2. **Editable desde el teclado**: cada stop y el dial de ángulo son
`role="slider"` reales con `aria-valuetext`, roving focus y anuncios —
nadie más lo publica (apg: slider).
## Surface
```svelte
@ -61,14 +80,58 @@ selected stop reads as one surface. The seed effect tracks only `selectedIndex`
## Recipe
Self-contained `--_gradient-builder-*` tokens (gradient-builder is on the active
dev track, not yet registered in `recipes/base.ts`). The stop handle centers with
the `translate` property (not `transform`) so a sema press-squeeze can't fight
the centering. Selectors target only morfo-promised attrs + the eidos-only
composition wrappers stamped by the wrappers.
Self-contained `--_gradient-builder-*` tokens — private by choice (graduated
from the dev track 2026-07-10; the R-rules pass with the private set, and a
public `--gradient-builder-*` API waits for real theming demand, see Gaps).
The stop handle centers with the `translate` property (not `transform`) so a
sema press-squeeze can't fight the centering. Selectors target only
morfo-promised attrs + the eidos-only composition wrappers stamped by the
wrappers.
## Comparativa
| Capacidad | UIX | Figma | Photoshop | Pickers web / gradient.style |
| --- | --- | --- | --- | --- |
| Modelo compartido con el token engine (OKLCH) | **✓** (`$libs/gradient`) | No (RGBA interno) | No | No (string CSS) |
| Stops editables por teclado (`role=slider`) | **✓** | No | No | No |
| Geometría explícita per-kind (angle / shape+size+at / from+at) | ✓ | Matriz afín | Matriz | Parcial |
| Alpha per-stop | ✓ | ✓ | ✓ | ✓ |
| Round-trip sin pérdida a CSS | ✓ | Export muerto | Export muerto | ✓ (string) |
| Semántica perceptiva del gesto (pack handle+commit) | ✓ | — | — | — |
Referencias: research competitivo en
[`CONTINUE.md`](../../../soma/components/gradient-builder/CONTINUE.md) (no
re-derivar) · [APG Slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) ·
[gradient.style](https://gradient.style/).
## Decisiones
- **Geometría = campos explícitos per-kind**, no matriz afín: round-trip sin
pérdida a CSS y mapeo trivial a teclado (decisión del research; el morfo la
hereda).
- **Alpha per-stop en v1**; el rail de opacidad separado queda reservado, no
shipped.
- **ColorPicker INLINE** (Area + sliders + hex, sin popover anidado): editar
el stop seleccionado se lee como una sola superficie; el seed effect trackea
solo `selectedIndex` (untrack del color) para no pelear con el picker.
- **Compose-first cumplido**: ColorPicker / Slider / ToggleGroup / Button son
los del sistema; lo único bespoke es el track de stops (que ES el patrón
slider propio).
- **Pack sema**: handle pick/drag sobre el track (la firma del slider) +
commit-set/reset.
- **Sin prop `color`** — GradientBuilder pinta el gradiente del usuario, no un
rol de paleta; el palette-picker de la demo está ausente por diseño.
## Gaps
| Gap | Disposición | Detalle |
| --- | --- | --- |
| Mesh editor (puntos 2D) | **diferir** (fast-follow declarado) | El eje ya publica `aurora()`; el editor de mesh reutilizará el patrón stop=slider. Diamond descartado en el research. |
| Midpoints (color hints) + `parseGradient` (paste-to-edit) | **diferir** (Phase-2.5 declarada) | Con el research ya hecho en CONTINUE.md. |
| Gizmo on-canvas (superficie dual barra + lienzo) | **diferir** | v1 = la barra precisa; el gizmo espera el caso mesh. |
| API pública de tokens (`--gradient-builder-*` en recipes/base.ts) | **diferir** | Los privados pasan las R-rules; registrar cuando haya demanda real de theming del editor. |
| Demo v2 (D-1.2/D-3.1 de la máquina) | **implementar** | Pase de demos de esta iniciativa (ver sesión S9). |
## Notes
- **No `color` prop** — GradientBuilder paints the user's gradient, not a palette
role. The demo's palette picker is therefore absent by design.
- Demo: `web/routes/uix/components/gradient-builder/+page.svelte`.

@ -1,11 +1,24 @@
# Eidos GradientPicker
Fecha de revisión: 2026-07-10 (dossier S9 — graduado de `ACTIVE_DEV_TRACK`).
The visual layer over the headless
[`GradientPicker`](../../../soma/components/gradient-picker/README.md) — a
field-shaped trigger carrying a live gradient chip that opens the
[`GradientBuilder`](../gradient-builder/README.md) in a Popover, with a
Clear / Cancel / Save footer. Identity wrapper over the composed Popover.
## Baseline
Sin baseline air y sin análogo publicado: los editores de gradiente del campo
viven DENTRO de herramientas de diseño (el fill-popover de Figma), no como
form control con valor comprometido. La forma sigue dos patrones internos ya
canónicos: la **identidad-sobre-Popover §2** (el Popover ES el diálogo — mismo
criterio que date-picker/ntp; apg: dialog-modal) y el **host transaccional del
Picker genérico** (footer Clear/Cancel/Save compartido vía PickerShell —
gradient-picker fue reconstruido sobre él en `f5eb8acb` y es, con ntp, uno de
sus dos consumidores reales).
## Surface
```svelte
@ -53,12 +66,46 @@ no loop.
## Recipe
Self-contained (gradient-picker is on the active dev track). The trigger is a
field-shaped pill; the chip + standalone swatch paint `--gp-current-gradient`
(stamped inline by soma). The popover content + editor reuse the Popover +
PickerShell + GradientBuilder recipes.
Self-contained (graduated from the dev track 2026-07-10; private tokens by
choice — the chrome is mostly the composed Popover + PickerShell +
GradientBuilder recipes). The trigger is a field-shaped pill; the chip +
standalone swatch paint `--gp-current-gradient` (stamped inline by soma).
## Comparativa
| Capacidad | UIX | Figma (fill popover) | Libs web de color con tab gradiente |
| --- | --- | --- | --- |
| Form control con valor comprometido (`bind:value`) | **✓** | No (edición in-place) | Parcial (sin transacción) |
| Footer transaccional (Clear/Cancel/Save + revert) | ✓ (PickerShell) | No | No |
| Editor accesible por teclado dentro | ✓ (builder apg slider) | No | No |
| Chip de valor vivo en el trigger | ✓ (`--gp-current-gradient`) | ✓ | Parcial |
| Overlay delegado al sistema | ✓ (Popover §2) | Propio | Propio |
Referencias: [`../gradient-builder/README.md`](../gradient-builder/README.md)
(el editor y su research) · patrón §2 en [`../date-picker/`](../date-picker/).
## Decisiones
- **Identity wrapper over the composed Popover (§2)**: este morfo no declara
semántica de diálogo (role/trap/open-close) — pertenece al Popover; el
footer es chrome compartido de `picker-shell`.
- **Eventos propios = el desenlace transaccional**: `commit-reset` (Clear) /
`commit-save` (Save) / `commit-remove`, con pack propio; los eventos de
EDICIÓN suenan por el contrato del GradientBuilder embebido. Dos voces, dos
dueños.
- **El value bridge es commit-only + ref guard** (sección arriba): el builder
edita un `draft` local; solo los valores asentados suben al provider; los
resets externos bajan por `$effect` con guard — sin bucle.
- **Sin prop `color`** (como el builder).
## Gaps
| Gap | Disposición | Detalle |
| --- | --- | --- |
| Persistencia de presets guardados | **descartar** (nivel componente) | Como ColorPicker: los presets/save son del CONSUMIDOR; el panel de presets ya compone. |
| Migración a `PickerProvider<TValue>` (deferValue genérico) | **diferir** | Task #31 de la memoria — pasada conjunta de los pickers POST audit-fix. |
| Demo v2 (D-1.5/D-3.1 de la máquina) | **implementar** | Pase de demos de esta iniciativa (ver sesión S9). |
## Notes
- **No `color` prop** (like GradientBuilder). Demo:
`web/routes/uix/components/gradient-picker/+page.svelte`.
- Demo: `web/routes/uix/components/gradient-picker/+page.svelte`.

@ -19,6 +19,10 @@ export const gradientBuilderMorfo = {
name: 'GradientBuilder',
kebab: 'gradient-builder',
scope: ['soma', 'sema', 'eidos'],
// Every stop (and the angle dial) is a real slider thumb — the pattern the
// keyboard interaction implements; composed pieces (ColorPicker, ToggleGroup,
// Button) carry their own patterns.
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/slider/',
expression: 'pack',
events: [
// Grabbing / dragging a stop along the track — same `handle` family as the

@ -16,6 +16,10 @@ export const gradientPickerMorfo = {
name: 'GradientPicker',
kebab: 'gradient-picker',
scope: ['soma', 'sema', 'eidos'],
// The composed Popover owns the dialog pattern (same as date-picker /
// natural-time-picker §2); the editing surface inside cites slider via
// the gradient-builder morfo.
apg: 'https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/',
// Own commit events (reset / save / remove) tuned by the pack at
// sema/components/gradient-picker.ts — the embedded GradientBuilder
// additionally sounds through its own contract.

@ -30,6 +30,8 @@ export * as FieldLangs from './field-langs';
export * as FileUpload from './file-upload';
export * as FloatPanel from './float-panel';
export * as Form from './form';
export * as GradientBuilder from './gradient-builder';
export * as GradientPicker from './gradient-picker';
export * as GridList from './grid-list';
export * as ImageAdjustments from './image-adjustments';
export * as ImagePicker from './image-picker';

@ -24,16 +24,7 @@
const uix = getActiveUix();
type Tab =
| 'live'
| 'system'
| 'motion'
| 'sema'
| 'services'
| 'api'
| 'morfo'
| 'recipe'
| 'a11y';
type Tab = 'live' | 'system' | 'motion' | 'sema' | 'services' | 'api' | 'morfo' | 'recipe' | 'a11y';
let tab = $state<Tab>('live');
const sizes: GradientBuilderSize[] = ['sm', 'md', 'lg'];

Loading…
Cancel
Save

Powered by TurnKey Linux.