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

17 KiB

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:

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:

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):

// 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:

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):

<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

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.