Updates CONTINUE.md to reflect the segunda-mitad sprint of 2026-05-23: the 7 commits betweenactive-uixdc03773cand0b55be5cthat closed the menu indicator partial (#118), removed the unused modal prop (#127), and refactored 5 consumers to render `<Button>` internally (#120). Documents the canonical Button-consumer pattern in 6 rules — variant narrowing to canonical sets in eidos/lib/types.ts, chip parity §12.7, composition via soma `child` snippet (with the `child`/`outerChild` rename gotcha), cross-layer state forwarding via WithChildNoChildrenSnippetProps, local recipe boundary, and the verification protocol (svelte-check + browser visual confirm). Lists candidate follow-ups out of #120 scope (NumberField stepper kept bespoke; Dialog/Popover/Drawer triggers and Select/Combobox field triggers could adopt the same pattern when prioritized). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parent
0b55be5c36
commit
b414ec2b85
@ -1,96 +1,167 @@
|
||||
# Continue tomorrow
|
||||
|
||||
Fecha de corte: 2026-05-23. Rama: `active-uix`.
|
||||
Fecha de corte: 2026-05-23 (segunda mitad). Rama: `active-uix`.
|
||||
|
||||
## Resumen del día
|
||||
|
||||
Sprint de demos canonical-depth + Button component + picker recipe polish.
|
||||
Commit clave: `a804c392` (49 archivos, +11034/-1365). `svelte-check`: 0/0.
|
||||
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.
|
||||
|
||||
**Hecho:**
|
||||
|
||||
- **Demos rebuilt a canonical depth (9):** context-menu, dropdown-menu, pin-input,
|
||||
alert-dialog, toggle-group, color-field, time-field, time-picker, time-range-picker,
|
||||
color-picker, month-grid, year-grid. Cada demo con per-part Morfo contracts iterando
|
||||
raw morfo, reference parity tables (Radix/bits-ui/ark-ui/Chakra/Mantine/react-aria),
|
||||
keyboard + ARIA contract tables, composition controls.
|
||||
|
||||
- **Nuevo `<Button>` (eidos):** morfo + soma + sema + eidos + demo canonical.
|
||||
6 variants (solid/soft/surface/outline/ghost/plain), 8 colors, 5 sizes,
|
||||
loading state + Spinner, icon/endIcon snippets, asChild Slot pattern.
|
||||
Sema `commit-action` con intent fromProp.
|
||||
|
||||
- **PickerShell refactor:** Clear/Cancel/Close ahora usan `<Button size="xs">`.
|
||||
Afecta 5 pickers (date / date-range / time / time-range / color). CSS chrome
|
||||
redundante eliminada de picker-shell.css; layout mantenido (gap, margin auto).
|
||||
**Commits del día (orden cronológico):**
|
||||
|
||||
- **Time picker polish:** popover condensado (min 18→14 / 32→28rem), per-channel
|
||||
accent (hour=primary, minute=secondary, second=neutral, end variant muted),
|
||||
ruler OFF por defecto, hour ruler limpio (0/6/12/18/23 sin "21/23" pegados).
|
||||
| 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>` |
|
||||
|
||||
- **Color picker polish:** typography token `--_color-picker-trigger-font-size`
|
||||
escala con `data-size` (10/12/14px); `autoFormat` default true (popover
|
||||
FormatSelect driving Trigger ValueText). Composition toggles para Hue / Alpha /
|
||||
ChannelInput / FormatSelect / EyeDropper / Swatches / Label / ValueText.
|
||||
Area recipe `[data-x-channel='hue']` → rainbow horizontal con overlays
|
||||
adaptados al y-channel; auto-switch a `{x: hue, y: saturation}` cuando el
|
||||
Hue slider está oculto.
|
||||
**Hecho:**
|
||||
|
||||
- **Bug fix:** ValueText eidos wrapper creaba un children snippet implícito
|
||||
vacío que bloqueaba el fallback de soma. Fix con split condicional
|
||||
`{#if children}` que aplica a cualquier wrapper con soma fallback.
|
||||
- **#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';
|
||||
```
|
||||
|
||||
- **Date field segment affordance:** cursor + hover bg + active ring via
|
||||
`--_date-field-accent-element` token añadido a las 8 color cascades.
|
||||
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>
|
||||
```
|
||||
|
||||
## Pendiente / en curso
|
||||
Nota: si el wrapper tiene un prop llamado `child`, **renombra en destructure
|
||||
a `outerChild`** — el `{#snippet child}` shadow infinite-loops si no.
|
||||
|
||||
### 1. Task #118 — Shared check/radio indicator recipe
|
||||
**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:
|
||||
|
||||
**Problema:** los recipes `[data-checkbox][data-state='checked']`,
|
||||
`[data-context-menu-checkbox-item][data-state='checked']::before` y
|
||||
`[data-dropdown-menu-checkbox-item][data-state='checked']::before` redibujan
|
||||
los indicadores (palomita rounded square / círculo radio) en bloques separados.
|
||||
```ts
|
||||
// soma types.ts
|
||||
export type FooButtonProps = WithChildNoChildrenSnippetProps<
|
||||
{ id?: string },
|
||||
{ intent: 'neutral' | 'risk' | 'threat' } // extra snippet props
|
||||
>;
|
||||
|
||||
**Plan:**
|
||||
- Crear un partial / sub-recipe compartido (e.g. `src/uix/eidos/lib/recipes/indicator.css` o sub-mixin en base.ts) con la geometría del check / radio dot.
|
||||
- Importar desde checkbox.css, context-menu.css, dropdown-menu.css.
|
||||
- Verificar también `Select.OptionIndicator` y `RadioGroup.Indicator` si aplican.
|
||||
// soma provider.svelte.ts
|
||||
readonly dialogIntent = $derived.by(() => this.dialog.opts.intent.current);
|
||||
|
||||
**No fusionar los componentes** — Checkbox / MenuCheckboxItem / MenuRadioItem son primitivas ARIA distintas (`role="checkbox"` vs `role="menuitemcheckbox"` vs `role="menuitemradio"`) con keyboard models distintos. Solo unificar el indicador visual.
|
||||
// soma component
|
||||
{@render child({ props: mergedProps, intent: state.dialogIntent })}
|
||||
```
|
||||
|
||||
### 2. Task #120 — Refactor restantes consumidores a `<Button>`
|
||||
**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.
|
||||
|
||||
**Hecho:** PickerShell.Clear/Cancel/Close (5 pickers).
|
||||
**Regla 6 — Verificación obligatoria:** svelte-check 0 errores + browser
|
||||
visual confirm (`mcp__Claude_Preview__preview_eval` con `getComputedStyle`).
|
||||
|
||||
**Pendiente:**
|
||||
- `DropdownMenu.Trigger` — actualmente raw `<button>` reset.
|
||||
- `AlertDialog.Action` + `AlertDialog.Cancel` — recipe bespoke con cascade `[data-color='risk'|'threat']` auto-coloring. Verificar que el cambio a `<Button intent="...">` mantenga el cascade visual.
|
||||
- `Combobox.ClearButton` — pequeño icon button.
|
||||
- `SearchField.Clear` — pequeño icon button.
|
||||
## Pendiente
|
||||
|
||||
**Decisión consciente NO refactorizar:**
|
||||
- `NumberField` stepper buttons → mantienen comportamiento bespoke (hold-to-repeat increment).
|
||||
### Sin tareas explícitas
|
||||
|
||||
### 3. Indicator refactor + Button consumer refactor son independientes
|
||||
Sprint cerrado limpio. Próximos posibles candidatos (no urgentes):
|
||||
|
||||
Pueden hacerse en cualquier orden. Task #118 toca recipe (CSS only). Task #120 toca svelte (cambia consumidor + drop CSS chrome redundante).
|
||||
- **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`.
|
||||
|
||||
### 4. Notas de scope / verificación
|
||||
### Audit walk
|
||||
|
||||
- Cada refactor: verificar visual en cada consumidor (date-picker, alert-dialog, etc.) — no asumir que el cambio no impacta layout.
|
||||
- `svelte-check --threshold error` debe quedar 0/0 después de cada commit.
|
||||
- Demo de cada consumidor debe re-verificarse después del refactor.
|
||||
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
|
||||
npx svelte-check --threshold error # 0/0 expected
|
||||
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` +17 ahead de `gita/active-uix` antes del push de hoy.
|
||||
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`).
|
||||
|
||||
Loading…
Reference in new issue