# 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`. ## Decisiones tomadas hoy ### 1. Patrón canónico para number/currency/unit inputs **Decisión final**: un solo `` 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 | |---|---| | `` sin prop locale | Respeta `prefs.locale` del app (sin cambio) | | `` | 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 `` por tipo (length / distance / mass / volume / temperature / speed) → 25 unidades CLDR. ### 4. TextArea — full stack **API** (sigue Mantine + Radix + PrimeReact, no inventa nada): ```svelte ``` **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 `` 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 `