# 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 `` + ``, ``, ``, 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 `` 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 `