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/continue.md

168 lines
7.3 KiB

# Continue tomorrow
Fecha de corte: 2026-05-23 (segunda mitad). Rama: `active-uix`.
## Resumen del día
Segundo sprint sobre la rama: **Tasks #118 + #120 + #127 completadas**.
Patrón canonical para consumers de `<Button>` establecido y aplicado en 5
consumers. svelte-check: 0 errores en cada commit.
**Commits del día (orden cronológico):**
| Commit | Scope |
|---|---|
| `0e9628b8` | refactor(eidos): extract shared menu-item indicator partial (#118) |
| `4b5bd9f3` | refactor(dropdown-menu): remove `modal`; Trigger→Button; canon vocab fixes (#120 + #127) |
| `a2709476` | style(menu-indicator): align with Checkbox / RadioGroup visual |
| `c6f063dc` | refactor(alert-dialog): Action/Cancel render `<Button>`; intent flows via snippet |
| `c98510af` | demo(alert-dialog): expose variant/size chips for Action and Cancel |
| `32598d7c` | fix(alert-dialog): narrow Action.variant to canonical SelectionVariant |
| `0b55be5c` | refactor(combobox + search-field): ClearButton/Clear render `<Button>` |
**Hecho:**
- **#118 — Shared menu-indicator partial.** `src/uix/eidos/lib/menu-indicator.css`
unifica los `::before` indicators de `[data-{dropdown,context}-menu-{checkbox,radio}-item]`.
Geometría compartida, tokens privados (`--_menu-indicator-{size,radius,color,glyph,border,inset}`).
Estilo final igual que `<Checkbox>` / `<RadioGroup>` standalone:
- off → caja/círculo vacío con borde gris
- on → relleno púrpura primary + ✓ blanco (mask-image con el mismo path
SVG que `<SvgCheck>`) / punto blanco interior (radio)
- padding-inline-start reservado 26px → no shift al togglear
**Crítico:** el partial debe importarse DESPUÉS de los recipes de menu
(en `eidos/index.css`) para que su `padding-inline-start` calc gane al
`padding-inline` shorthand del row a igual specificity. Documentado en el
propio partial.
- **#127 — `modal` prop eliminado de DropdownMenu.** El prop estaba declarado
en soma pero nunca cableado (sin overlay, sin scroll lock, sin pointer-events
blocker). ark-ui / Headless-UI / Chakra Menu no exponen `modal` por convención
— un menú no es un Dialog. Eliminado de soma (types + provider + component
+ test fixture + menubar consumer), eidos doc, demo (state + control + snippet
+ API row + parity row), COMPONENT_GUIDE actualizado.
- **#120 — 5 consumers refactored a `<Button>`** vía soma `child` snippet:
| Consumer | Variant set narrowed | Default | Color | Notas |
|---|---|---|---|---|
| `DropdownMenu.Trigger` | `ControlVariant` (3) | surface | inherits | — |
| `AlertDialog.Cancel` | `ControlVariant` (3) | outline | neutral | hardcoded neutral para contrastar con Action destructive |
| `AlertDialog.Action` | `SelectionVariant` (3) | solid | — | intent forwarded del Dialog vía child snippet props (sin CSS ancestor cascade) |
| `Combobox.ClearButton` | `ControlVariant` (3) | ghost | neutral | iconOnly X via `icon` snippet |
| `SearchField.ClearTrigger` | `ControlVariant` (3) | ghost | neutral | iconOnly X via `icon` snippet |
En cada caso: per-recipe CSS chrome eliminada (border, padding, focus ring,
hover, dimensions). Solo permanece CSS específico del comportamiento del
componente (`[data-empty]` para esconder clear buttons).
AlertDialog.Action expone un mecanismo nuevo del soma: el provider
(`AlertDialogActionProvider.dialogIntent`) lee `DialogProvider.opts.intent`
y lo pasa por `child({ props, intent })`. El eidos wrapper lo forwardea
a Button: `intent={intentOverride ?? dialogIntent}`. Cero CSS ancestor cascade.
## Patrón canonical para consumers de `<Button>` (aplicar a futuros)
**Regla 1 — Variant siempre narrowed a un set canónico** en
`src/uix/eidos/lib/types.ts` (4 sets, todos 3-4 valores):
```ts
export type ControlVariant = 'surface' | 'outline' | 'ghost'; // form controls
export type SelectionVariant = 'solid' | 'outline' | 'ghost'; // primary CTAs
export type ChipVariant = 'soft' | 'solid' | 'outline' | 'ghost';
export type MarkerVariant = 'solid' | 'soft' | 'outline';
```
Nunca redeclarar; siempre `import type { ... } from '$uix/eidos/lib/types'`.
Si necesitas otro set canónico, defínelo ahí. Si necesitas restringir aún
más, usa `Extract<ButtonVariant, CanonSet>`.
**Regla 2 — Chip parity §12.7.** El demo muestra TODOS los valores del set
narrowed. No truncar. No mezclar.
**Regla 3 — Composición vía soma `child` snippet.** El wrapper eidos no
crea `<button>`; usa el `child` snippet del componente headless de soma:
```svelte
<Soma.Part {...rest}>
{#snippet child({ props })}
{#if outerChild}
{@render outerChild({ props })}
{:else}
<Button {...props} {variant} {size} {intent} {color} {iconOnly}>
{@render children?.()}
</Button>
{/if}
{/snippet}
</Soma.Part>
```
Nota: si el wrapper tiene un prop llamado `child`, **renombra en destructure
a `outerChild`** — el `{#snippet child}` shadow infinite-loops si no.
**Regla 4 — Contracts cross-layer.** Si el dialog/popover/menu padre tiene
estado evaluativo (intent, color, status) que el Button hijo debe heredar,
el soma provider del Button-like part lo expone via `child({ props, ... })`
mediante `WithChildNoChildrenSnippetProps` para que `children` siga siendo
un Snippet plano:
```ts
// soma types.ts
export type FooButtonProps = WithChildNoChildrenSnippetProps<
{ id?: string },
{ intent: 'neutral' | 'risk' | 'threat' } // extra snippet props
>;
// soma provider.svelte.ts
readonly dialogIntent = $derived.by(() => this.dialog.opts.intent.current);
// soma component
{@render child({ props: mergedProps, intent: state.dialogIntent })}
```
**Regla 5 — CSS recipe local solo para comportamiento.** Después del
refactor, la recipe local solo debe contener selectores específicos del
componente (`[data-foo][data-empty] [data-foo-clear]`, `data-state='open'`
transitions). Chrome general (border, padding, focus ring, hover) es de
Button.
**Regla 6 — Verificación obligatoria:** svelte-check 0 errores + browser
visual confirm (`mcp__Claude_Preview__preview_eval` con `getComputedStyle`).
## Pendiente
### Sin tareas explícitas
Sprint cerrado limpio. Próximos posibles candidatos (no urgentes):
- **NumberField stepper buttons** → decisión consciente de NO refactorizar
(mantienen comportamiento bespoke `hold-to-repeat`). Re-evaluar si se
necesita variant/size unification.
- **Dialog.Trigger / Popover.Trigger / Drawer.Trigger** → mismo refactor
que DropdownMenu.Trigger podría aplicar. No estaban en el scope #120.
- **Select.Trigger / Combobox.Trigger** → form controls; podrían adoptar
`<Button variant="surface">` con `ControlVariant`.
### Audit walk
Considerar pasar `npm run component:audit` para detectar drift en otros
recipes después de los cambios (CSS recipes que apuntaban a tokens
Toggle-derived ahora pueden tener selectors orphan).
## Comandos útiles
```bash
cd G:/dev/svelte/vicen
npm run check # svelte-check (0 errors expected)
npm run dev # via .claude/launch.json "dev"
npm run generate:eidos-css # if recipe tokens change
npm run component:audit # static contract audit
```
## Estado del working tree
Limpio. Branch `active-uix` con 7 commits nuevos sobre el HEAD anterior de
hoy (`dc03773c`). Sin push pendiente (decisión del usuario cuando quiera
empujar a `gita/active-uix`).

Powered by TurnKey Linux.