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

395 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Continue tomorrow
Fecha de corte: **2026-05-24**. Rama: `active-uix` (worktree:
`.claude/worktrees/keen-mcnulty-383812` → branch `claude/keen-mcnulty-383812`,
pero los cambios reales viven en el árbol principal).
## TL;DR de la sesión
Sesión de saneamiento + ampliación:
1. **NumberField demo**: re-construido para exponer `Intl.NumberFormatOptions`
completo (locale, style, currency, currencyDisplay, unit, unitDisplay,
decimales, useGrouping, notation, signDisplay) con 9 presets canónicos.
2. **Listas hardcoded eliminadas**: el demo ahora deriva locales, monedas y
unidades del ecosistema (`$libs/langs`, `$libs/currency`, `$format/units`).
3. **Bug crítico de locale propagation**: descubierto y arreglado en
**soma/NumberField + soma/Calendar + soma/RangeCalendar**. El prop `locale`
era ignorado cuando UIX tenía `format` service activo.
4. **TextArea** construido de cero — full stack (morfo + soma + eidos + sema +
demo canónico 6-tabs).
5. **Decisión arquitectónica**: NO se crean `CurrencyField` / `UnitField`
separados. Se confirma el patrón canónico headless mainstream (React Aria,
Ark, Bits, Chakra v3, Radix Vue) — un solo `NumberField` con `formatOptions`.
## Actualización final — Words cerrado para retomar mañana
Después del corte anterior se completó el componente **Words** hasta demo
funcional:
- `src/uix/morfo/components/words.ts` — contrato Morfo con 8 partes y 6
eventos.
- `src/uix/soma/components/words/` — motor JSON, selection, input pipeline,
commands, history, render HTML seguro, hidden input y tests.
- `src/uix/eidos/components/words/` — wrapper compound `<Words>` +
`<Words.Toolbar>`, `<Words.Content>`, `<Words.CommandButton>`, etc. y receta
CSS.
- `src/uix/sema/components/words.ts` — pack Sema para focus, commit, format,
history, clear e invalid input.
- `src/uix/langs/components/words.ts` — catálogo de strings para eliminar
warnings de fallback.
- `web/routes/uix/components/words/+page.svelte` — demo canónica 6 tabs con
stage permanente, snippets Soma/Eidos, Morfo/Sema/Recipe/A11y.
### Verificación específica de Words
Comandos ejecutados:
```bash
npx vitest run src/uix/soma/components/words/engine/engine.test.ts src/uix/soma/components/words/words-provider.svelte.test.ts
node --import tsx/esm scripts/component-audit.ts --only words
npx prettier --check src/uix/eidos/components/words src/uix/sema/components/words.ts src/uix/sema/components/words/README.md web/routes/uix/components/words src/uix/eidos/lib/recipes/base.ts src/uix/eidos/index.css web/routes/uix/+layout@.svelte src/uix/sema/components/index.ts src/uix/soma/components/words/README.md src/uix/words/README.md src/uix/langs/components/words.ts src/uix/langs/components/index.ts
npm run check 2>&1 | Select-String -Pattern 'words|Words'
```
Resultado:
- Tests Words: **23 passed**.
- Component audit `--only words`: **PASS**.
- Prettier: **PASS**.
- `npm run check` filtrado por `words`: sin errores ni warnings de Words.
- Visual en navegador: demo abierta en `http://localhost:5173/uix/components/words`.
Se verificó render inicial, presets `empty`/`long`, placeholder, escritura,
commit button, trace de Sema y tab Sema con 6 botones `Play`.
- Browser logs después de añadir `wordsLangs`: sin warnings nuevos.
Captura temporal local:
```text
tmp/words-demo-empty.png
```
### Problemas detectados y corregidos durante Words
- El wrapper Eidos de `Words.Content` pasaba un snippet vacío y bloqueaba el
render HTML por defecto de Soma. Corregido: solo se pasa snippet cuando el
consumidor aporta children.
- Mismo patrón corregido para `Placeholder`, `CommandButton` y `Status`, para
no pisar defaults Soma.
- Faltaba catálogo de traducciones `components.words.*`, lo que generaba
warnings de fallback en navegador. Corregido en `src/uix/langs/components`.
### Check global pendiente
`npm run check` global sigue fallando por deuda ajena a Words:
- `web/routes/uix/components/tree-grid/+page.svelte`: `bind:expanded` y
`bind:value` sobre props no bindables.
- `web/routes/uix/components/tree-view/+page.svelte`: `bind:expandedValue` y
`bind:selectedValue` sobre props no bindables.
- `src/uix/eidos/components/virtual-grid/virtual-grid.svelte`: tipo de
`style` permite objeto, pero el markup espera string.
- `src/uix/eidos/components/virtual-list/virtual-list.svelte`: mismo problema
de `style`.
- `src/uix/active-uix/active-uix.svelte.test.ts`: `announceMorfo` usa evento
`absent-clear` con `family: 'absent'` / `intent: 'absent'`, incompatible con
el vocabulario actual de Morfo.
- Además hay warnings no bloqueantes en `navigation-menu` (`href="#"`) y
`table` (`state_referenced_locally`).
## Punto exacto para continuar mañana
1. **Primero limpiar `npm run check` global**, empezando por los errores
anteriores. Es mejor cerrar eso antes de seguir creando componentes, porque
ahora Words ya pasa su auditoría aislada.
2. **Revisar visualmente Words en responsive y dark/light**: desktop ya fue
comprobado; falta una pasada móvil/estrecha, vertical toolbar y tabs
Morfo/Recipe/A11y con scroll largo.
3. **Si se sigue en Words V1.1**, el orden recomendado es:
- sincronizar cambios externos de `value` sin remount en la demo;
- import/export HTML seguro;
- Markdown import/export;
- BubbleMenu + LinkPopover como partes Soma reales;
- agrupar input/composition en batches de history menos ruidosos.
4. **Si se sigue con el ecosistema UIX**, no abrir más componentes hasta que
`component-audit --only words` siga en PASS y `npm run check` global esté
limpio.
## Decisiones tomadas hoy
### 1. Patrón canónico para number/currency/unit inputs
**Decisión final**: un solo `<NumberField>` con `formatOptions:
Intl.NumberFormatOptions`. NO se crean componentes separados.
**Razones** (validadas contra 12 librerías):
- React Aria, Ark UI, Bits UI, Chakra v3, Radix Vue, shadcn-vue → todos usan
`NumberField` + `formatOptions` con los modos W3C estándar
(`style: 'decimal' | 'currency' | 'unit' | 'percent'`).
- PrimeReact es el único outlier: usa `mode="currency"` enum, pero sigue
siendo UN solo componente.
- Mantine y Ant Design usan `prefix` / `suffix` strings (lo más débil).
- En la rama histórica `morfo-driven-soma` el `air/number-field` tampoco
tenía Currency/Unit — solo skin sobre `terra.NumberField`.
**Implicación**: para usar como moneda o unidad, el consumidor pasa
`formatOptions={{ style: 'currency', currency: 'EUR' }}` o
`formatOptions={{ style: 'unit', unit: 'kilogram' }}`. CLDR-aligned.
### 2. Bug de locale prop ignorado (soma)
**Origen**: `soma/NumberFieldProvider` delegaba todas las operaciones a
`soma.nums` (servicio activo UIX) cuando estaba disponible.
`soma.nums.format(value, opts)` solo acepta `Intl.NumberFormatOptions` —
**no acepta locale como argumento**. El locale interno viene de
`prefs.locale`, no del prop del componente.
Mismo bug en `soma/Calendar` y `soma/RangeCalendar`: `dateOrder` leía
directamente `soma.dates.getDateOrder()` ignorando el prop locale.
`DateField` ya lo hacía bien (precedencia explícita prop > service > locale
default); las dos restantes no.
**Patrón de fix aplicado** (en los 3 archivos):
```ts
// Bypass del servicio activo cuando el consumidor pasa locale explícito.
private get hasOwnLocale(): boolean {
return this.opts.locale.current !== undefined;
}
readonly decimalSeparator = $derived.by(() => {
if (this.hasOwnLocale) return resolveDecimalSeparator(this.locale.current);
return this.soma.nums?.getDecimalSeparator() ?? resolveDecimalSeparator(this.locale.current);
});
```
5 puntos parcheados en number-field-provider: `decimalSeparator`,
`groupSeparator`, `formatValue`, `formatEditValue`, `parseValue`. En calendar
y range-calendar: solo `dateOrder` (la única ruta que ignoraba el prop).
**Semántica resultante** (consistente en NumberField + DateField + Calendar +
RangeCalendar):
| Caso | Comportamiento |
| ------------------------------- | ----------------------------------------------- |
| `<NumberField>` sin prop locale | Respeta `prefs.locale` del app (sin cambio) |
| `<NumberField locale="en-US">` | Bypassa `soma.nums` → formato en-US garantizado |
### 3. Listas hardcoded en demo → ecosistema centralizado
**Antes**: el demo de NumberField tenía 3 arrays inventados (7 locales, 8
currencies, 12 units).
**Después**:
```ts
import { REGION_CURRENCY } from '$libs/currency';
import { UNIT_DEFINITIONS } from '$format/units';
import type { LangBase } from '$libs/langs';
```
- **Languages**: 10 base langs de `LangBase` × tabla regional curada →
30 BCP47 locales reales (`en-US`, `es-AR`, `pt-BR`, `ar-EG`, etc).
- **Currencies**: `[...new Set(Object.values(REGION_CURRENCY))].sort()` →
~155 monedas ISO 4217.
- **Units**: `Object.entries(UNIT_DEFINITIONS)` agrupadas por `UnitKind`
en `<optgroup>` por tipo (length / distance / mass / volume / temperature /
speed) → 25 unidades CLDR.
### 4. TextArea — full stack
**API** (sigue Mantine + Radix + PrimeReact, no inventa nada):
```svelte
<TextArea
bind:value
autosize minRows={3} maxRows={8}
resize="vertical" {/* none | vertical | horizontal | both */}
submitOn="mod+enter" {/* false | enter | shift+enter | mod+enter */}
maxLength={280}
size="md" variant="surface" color="primary"
onSubmit={(v) => …}
>
<TextArea.Input placeholder="…" />
<TextArea.Count /> {/* default: count / max */}
</TextArea>
```
**Morfo**: 3 parts (`provider`, `input`, `count`), 3 events
(`commit-input`, `commit-submit`, `shift-count-overflow`).
**Autosize**: scrollHeight measurement por evento de input. Reset
`height=auto` → mide → clamp entre `minRows*lineHeight` y `maxRows*lineHeight`.
Respeta padding/border. Cuando `autosize=true`, la handle de resize se
suprime (suppresses CSS `resize`) porque lucharían entre sí.
**Submit shortcut**: intercepta `Enter` selectivamente. Por defecto OFF
(deja que Enter inserte newline). Las 3 modalidades:
- `'enter'` — Enter (sin modifier) → `onSubmit`, prevent newline.
- `'shift+enter'` — Shift+Enter → `onSubmit`, Enter sigue insertando newline.
- `'mod+enter'` — Ctrl/Cmd+Enter → `onSubmit`.
**maxLength**: doble bloqueo (atributo nativo + soft clamp en `commit()`
para paste events que excedan). Emite `shift-count-overflow` con intent
`threat` cuando el usuario intenta pasarse.
**Field integration**: OR-merge de `disabled/readonly/required/invalid` con
el `<Field>` padre. `Field.Label` gana sobre `aria-label` propio.
### 5. NO crear más componentes ahora
Catálogo actual (97 morfos) ya excede shadcn (~50), Radix (~30). Las únicas
ausencias notables vs mainstream:
| Faltante | Equivalente parcial | Prioridad |
| --------------------------------- | ------------------------ | ------------------ |
| Skeleton (loader placeholder) | — | media |
| Spinner (determinate loader) | progress indeterminate | media |
| PasswordField | editable + custom toggle | baja |
| HoverCard | link-preview (similar) | baja |
| DataTable (sort/filter compuesto) | table | media-alta |
| Sidebar (app shell pattern) | — | baja (composición) |
## Archivos modificados/creados hoy
### Fix soma (3 providers)
- `src/uix/soma/components/number-field/number-field-provider.svelte.ts`
— getter `hasOwnLocale` + 5 puntos bypass.
- `src/uix/soma/components/calendar/calendar-provider.svelte.ts`
— `dateOrder` derivado respeta prop locale.
- `src/uix/soma/components/range-calendar/range-calendar-provider.svelte.ts`
— idem `dateOrder`.
### TextArea nuevo (12 archivos)
- `src/uix/morfo/components/textarea.ts`
- `src/uix/soma/components/textarea/`
- `types.ts`, `langs.ts`, `textarea-provider.svelte.ts`, `exports.ts`,
`index.ts`
- `components/textarea.svelte`, `textarea-input.svelte`,
`textarea-count.svelte`
- `src/uix/eidos/components/textarea/`
- `textarea.svelte`, `textarea-input.svelte`, `textarea-count.svelte`,
`textarea.css`, `types.ts`, `index.ts`
- `src/uix/sema/components/textarea.ts`
- `src/uix/soma/types/html.ts` — añadido `PrimitiveTextareaAttributes`
- `src/uix/soma/types/index.ts` — export añadido
- `src/uix/soma/components/index.ts` — barrel
- `src/uix/sema/components/index.ts` — barrel
### Demo NumberField
- `web/routes/uix/components/number-field/+page.svelte` — imports del
ecosistema, locale del `format` service, presets canónicos.
### Demo TextArea + layout
- `web/routes/uix/components/textarea/+page.svelte` — 410 líneas, 6 tabs.
- `web/routes/uix/+layout@.svelte` — TextArea añadido al sidebar Forms +
sema registrada.
### Trabajo pre-sesión (uncommitted previo)
El working tree también contiene trabajo de sesiones anteriores no
commiteado todavía:
- Ports a eidos: `announce`, `clipboard`, `drag-drop`, `feed`, `table`,
`tree-grid`, `tree-view`, `virtual-grid`, `virtual-list`, `words`
- Sus demos en `web/routes/uix/components/`
- Sema events de utility morfos
- Tree indent guides via `::before` (TreeView) y stacked gradients (TreeGrid)
- Sidebar reorganizado con grupos Data + Utilities
## Verificación
`svelte-check`: 0 errores en archivos modificados/creados.
**NO verificado visualmente en navegador** — el preview server vivía en
worktree, hubo confusión entre worktree y main project durante la sesión.
Para mañana: arrancar `npm run dev` desde el árbol principal
(`G:/dev/svelte/vicen`, NO desde el worktree) y comprobar:
1. **NumberField**: cambiar el `<select>` de locale de `es-ES` a `en-US`
— el separador decimal debe pasar de coma a punto inmediatamente.
2. **Compact preset** con locale en-US debe mostrar `1.2M` (no `1,2 M`).
3. **TextArea**: probar autosize escribiendo texto largo, ver el counter
ponerse rojo al pasar maxLength, probar `submitOn="mod+enter"`.
4. **Calendar**: con prop `locale="en-US"` los segmentos del header deben
ordenarse MDY, no DMY.
## Próximos pasos (orden recomendado)
1. **Verificación visual** (1h) — los 4 puntos de arriba.
2. **Skeleton + Spinner** (2-3h) — primitivos eidos puros, bajo coste, alto
retorno. Skeleton genérico con `shape` (rect / circle / text), Spinner
con `size` + `color`. NO morfo nuevo (son layout primitives, scope
`['eidos']` solo).
3. **PasswordField** (1-2h) — wrapper sobre `editable` con `type="password"`,
toggle de visibility y eidos chrome consistente con SearchField. Reusa
el morfo de editable (mismo APG textbox); solo añade un eidos wrapper.
4. **Audit visual completo del demo de TextArea** — comprobar tabs Live /
API / Morfo / Sema / Recipe / A11y, todas las combinaciones de variant
- color + size.
## Patrones canon establecidos hoy
**Locale prop precedence en componentes que consumen format/dates service:**
> Si el componente expone un prop `locale`, debe puentear el servicio activo
> de UIX (`soma.nums`, `soma.dates`) cuando el prop es defined. El patrón
> es un getter `hasOwnLocale` que toggea entre Intl directo (locale del
> prop) y el servicio (locale del app `prefs.locale`). NUNCA delegar
> ciegamente al servicio si el componente expone el prop.
**Catálogo de listas dependientes de locale**:
| Lista | Fuente canónica |
| ----------- | ----------------------------------------------------------- |
| Locales | `LangBase` (`$libs/langs`) + tabla regional curada |
| Currencies | `REGION_CURRENCY` (`$libs/currency`) → `Set.values` |
| Units | `UNIT_DEFINITIONS` (`$format/units`) → group por `UnitKind` |
| Date orders | `resolveDateOrder()` de `$libs/days` |
| Hour cycles | `getDefaultHourCycle()` de `$libs/days` |
Cualquier demo que enumere alguna de estas listas DEBE importarlas; nunca
hardcodear.
\*\*TextArea = referencia para nuevos form controls de una sola parte
- count + Field integration.\*\* Si construyes un componente con:
* input/control único
* value bindable
* Field integration (OR-merge)
* counter o indicator opcional
* shortcut keys configurable
Clónalo. La estructura `provider + input + count` con sus 3 wrappers svelte
es replicable.
## Comandos útiles
```bash
cd G:/dev/svelte/vicen
npm run check # svelte-check
npm run dev # dev server (DESDE el árbol principal, NO worktree)
npx vitest run src/uix/soma/components/number-field
```
## Estado del working tree
Sucio. ~17 archivos modificados + 32 paths nuevos (incluye trabajo de
sesiones anteriores aún no commiteado). Hoy se debería hacer:
1. `git add` selectivo de TODO el trabajo acumulado.
2. Commit unificado o split por dominio.
3. Push a `gita/active-uix` (34 commits locales pendientes de push).
Branch principal: `active-uix`. Worktree de trabajo de la sesión:
`.claude/worktrees/keen-mcnulty-383812` — los archivos del worktree NO
están en sync con los del árbol principal; los edits de la sesión fueron al
árbol principal (correcto, era lo que el preview server necesitaba).

Powered by TurnKey Linux.