17 KiB
Pendientes UIX
Backlog vivo. Cada entrada lleva una disposición: implementar / diferir / descartar. Mantener priorizado por capa afectada y dependencias.
Pickers — estado consolidado 2026-05-21
El bloque de trabajo sobre date-picker y date-range-picker cerró en
una pasada larga el mismo día. El contrato canónico es:
mode: 'inline' | 'modal'— modal bloquea outside-click + Escape.kind: 'date' | 'month' | 'year'(Chakra-style) — controla segments del input + view del popover. Derivación vive en elDateFieldProvider(soma), no en el snippet del consumer.- Footer = composición pura —
<Picker.Footer>contiene las parts que tú compongas (<Clear/>,<Cancel/>,<Close/>). SinclearButton/cancelButton/closeButtonprops en root. - Provider helpers:
commit()/cancel()(revierte al snapshot capturado en el OPEN edge víawatch(open)) /clear().
| Item | Disposición |
|---|---|
Aplicar mode + Footer a date-picker (single) |
hecho |
Aplicar kind (Chakra date/month/year) a date-picker |
hecho |
<DatePicker.YearView> + <DatePicker.MonthView> |
hecho |
Propagar kind a date-range-picker (ambos endpoints filtran segments) |
hecho |
<DateRangePicker.YearView> + <DateRangePicker.MonthView> (range state machine: start → end → reset, swap automático) |
hecho |
Promover Footer/Clear/Cancel/Close a parts canónicos Eidos (ambos pickers) |
hecho |
Drop clearButton/cancelButton/closeButton root props → composición pura |
hecho |
Morfo date-picker + date-range-picker: declarar parts Footer/Clear/Cancel/Close + arquetipo footer añadido al vocabulario |
hecho |
MonthRangePicker / YearRangePicker como componentes nuevos |
descartar — cubierto por <DateRangePicker kind='month'> / kind='year'. No vale la pena duplicar superficie cuando un prop captura la variante. |
MonthPicker / YearPicker (single) |
descartar — idem, <DatePicker kind='month'> / kind='year'. |
Promover <DatePicker.YearView> / <DatePicker.MonthView> a componentes independientes <YearCalendar> / <MonthCalendar> con demos aislados |
diferir — actualmente acoplados al DatePickerProvider. Independizarlos requiere refactor del context-lookup. Mantener acoplados hasta que aparezca un caso de uso real fuera del picker. |
Propagar el patrón modal+Footer a time-picker, time-range-picker, color-picker, combobox, select, popover cuando proceda |
implementar — decidir por componente; muchos no necesitan footer. |
| Tests browser del flujo completo (Playwright): inline/modal, kind chip, range state machine, snapshot/cancel, click-outside, Escape | implementar — alta cobertura para flujos que ahora dependen de state machine. |
Sema event al bloquear click cuando mode='modal' |
diferir — útil para screen reader feedback. No bloquea. |
Soma / Popover — bloqueo detectado durante Words
| Item | Disposición |
|---|---|
Auditar Popover como componente propio antes de usarlo para Words.ToolPopover |
implementar — el trigger emite present en la traza, pero el estado open no queda reflejado de forma fiable en DOM y Content puede no montarse. En la demo de Words, abrir el editor de link desde el BubbleMenu produce TypeError: Cannot read properties of undefined (reading 'wrapperProps') en src/uix/soma/components/popover/popover-provider.svelte.ts. No es una adaptación específica de Words; es deuda de Soma/Popover y debe resolverse con su propia demo, tests y audit visual. |
Revisar Portal sólo si la auditoría de Popover lo confirma |
diferir — no modificar internal/Portal como efecto lateral de Words; aislar primero si el fallo está en trigger/provider/presence/portal. |
Añadir test browser o harness Svelte para click real de Popover.Trigger → Content visible |
implementar — los tests unitarios del provider no cubren el flujo renderizado con Trigger, Portal, Content, bind:open y atributos morfo en DOM. |
Tooltip (warnings pendientes del audit)
| Item | Disposición |
|---|---|
A-3.6 evento open/close sin variante {verb}-X o {family}-X |
diferir — disclosure puro, el naming actual es semánticamente válido. |
D-4.3 demo no emite uix.events.emit(...) para wirear Play buttons |
implementar cuando se haga la próxima pasada de demos. |
Eidos theming — limpieza diferida
| Item | Disposición |
|---|---|
20 tokens de color muertos (rol tertiary completo + {intent}-active × 8 + fulfill-hover/loss-hover/loss-element) en themes/base.ts + generated/base.css |
diferir — no rompen nada. Validar que tertiary no es un hook futuro reservado antes de borrarlo. |
| Afinar valores de los tokens xs/xl extrapolados durante el batch de tamaños | diferir — la extrapolación proporcional (xs ≈ 75% de sm, xl ≈ 125% de lg) puede no proporcionarse bien en componentes concretos; revisar visualmente y reajustar caso por caso. |
Range view-cell: differentiate visualmente start/end vs in-range (actualmente todos se ven solid primary) — pendiente afinar tinte de data-in-range para que sea más sutil que start/end |
implementar cuando se haga el browser-walk del range-picker. |
Audit one-by-one (post-sesión)
Prioridad media
- dialog (verificar visual)
Prioridad baja — sólo si aparece síntoma visual
Lista barrida en el audit script (component:audit 67/67 PASS). Cuando aparezca un disparate visual, se walka el componente con browser y se remedia. No se hace barrido proactivo de nada que ya esté PASS.
Audit infra
| Item | Disposición |
|---|---|
Audit A-3.6 falla con eventos open/close válidos de emerge |
diferir — el regex pide {verb}-X pero muchos verbs son legítimos solos. Refinar regex para aceptar verbs canónicos. |
| Audit no verifica que el demo cierre el popover al hacer click-outside (cuando aplica) | diferir — verificación visual queda al browser-walk. |
Translations check: añadir warning cuando un componente declara texts.label pero su catálogo no tiene label |
implementar — pequeño, evita drift. |
Audit R-2.6 — var(--color-X) referenciado sin estar declarado en generated/base.css |
hecho (scripts/component-audit.ts). Set declarado se calcula al inicio desde generated/base.css; cada componente CSS se chequea contra él. |
Audit D-7.4 — el array de chips de cada control (size, variant, color) enumera el tipo completo declarado en types.ts |
hecho (scripts/component-audit.ts). Parsea {PascalName}{Prop} exacto, resuelve alias canónicos (ControlVariant, SelectionVariant, ChipVariant, MarkerVariant, TabsVariant, ColorRole+narrowings) vía SHARED_VARIANT_VOCAB. |
Nuevo: audit que verifique que ninguna Part Eidos consulta provider.opts.{boolean}Button.current (composición pura) |
diferir — chequeo defensivo, low value. |
Web · performance del docs site
Diagnóstico (2026-05-24): el usuario reporta carga lenta + Chrome marca
[Violation] 'setTimeout' handler took 78ms. Profiling identifica 4
cuellos de botella reales, ordenados por impacto. Como UIX está en fase
de desarrollo del framework, todos quedan como diferir hasta entrar
en pasada de polish; documentados aquí para no perder el rastro.
| Item | Disposición |
|---|---|
CSS layout 744 KB render-blocking: src/uix/eidos/index.css @import-ea 70+ recipes + generated/base.css (213 KB) — total 944 KB. Cada /uix/* descarga + parsea todo antes del first paint, la mayoría para componentes que la página no usa. |
diferir — fix arquitectónico: dejar foundation global (~250 KB: base.css + archetypes.css + events.css) y mover cada recipe XComponent.css a un import dentro del .svelte correspondiente para que Vite lo code-splittee con el JS. Esto requiere migrar la convención del foundation eidos. |
ActiveEidos.create({ applyDom: true }) re-genera todo en runtime: apply() (src/uix/eidos/active-eidos.svelte.ts:361-389) corre renderStaticCss() + renderThemeCss() sincrónicamente, itera THEME_BASE_RECIPE_TOKENS (2.884 líneas) + color×scale×role×alpha (>1k declaraciones), inyecta 3 <style> tags con textContent = css. Es la causa más probable de la violación setTimeout 78ms (el $effect envolvente se programa vía la cola que Chrome reporta como setTimeout). Además duplica el CSS bundleado vía import '@/uix/eidos/index.css'. |
diferir + investigar trade-off — fix conservador (1 línea): applyDom: false en web/routes/uix/+layout@.svelte:302 y depender del CSS bundleado. Pero hay que confirmar primero si runtime theme switching depende de la inyección (theme editors live). Si depende, la solución es hacer apply() lazy: cachear el render por themeId y solo inyectar overrides cuando setCssVariables() se llame. |
28 sema modules + 71 lang files eagerly imported en el layout: web/routes/uix/+layout@.svelte:10-38 (sema) + src/uix/langs/components/index.ts (langs). Añade ~30 KB al chunk del layout, bloquea tree-shaking, cada /uix/* registra todos aunque el usuario no abra esos componentes. |
diferir — fix arquitectónico: registración lazy por componente. Cada componente al montar registra su propio sema/langs pack en lugar de el central en boot. Requiere API nueva (uix.events.registerPack/uix.langs.lazy) + refactor del patrón de boot. |
Sidebar con 100+ links data-sveltekit-preload-{code,data}="hover": web/routes/uix/+layout@.svelte:746-769. Mover el cursor por el rail dispara fetches masivos. Las rutas docs son prerenderizadas — el preload-data no aporta nada. |
diferir — fix barato (5 líneas): quitar data-sveltekit-preload-data de los links del rail; cambiar data-sveltekit-preload-code="hover" a viewport para que precargue al entrar en pantalla, no al hover. Mantener hover en topbar/brand. |
Cuándo abordarlo: cuando la lib entre en pasada de optimización
pre-release. El orden de ataque sugerido es el inverso del coste:
quitar preload-data del rail (10 minutos, ganancia inmediata) → fijar
applyDom: false con cache lazy (medio día, gana 50-80ms) → split CSS
por componente (1-2 días, gana el grueso del transfer) → sema/langs
lazy registration (sprint propio).
Doc debt
| Item | Disposición |
|---|---|
READMEs/architecture docs siguen citando morfo.translations en muchos .md |
diferir — el código está migrado a texts, los docs lagging. Sweep separado. |
src/uix/COMPONENT_COMPLETION_CHECKLIST.md actualizado con C-2.6 + D-7.4 + D-7.5 |
hecho |
src/uix/eidos/README.md + web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md actualizados con N-6 (kind) + N-7 (composition) |
hecho |
Normas implantadas
Resumen accionable de las decisiones adoptadas. Forman parte del
contrato del componente y están reflejadas en los .md doctrinales.
N-1 · Cobertura de tamaños
Cada componente expone los valores de Size que su recipe declara. El
sistema admite xxs · xs · sm · md · lg · xl · xxl · full. La elección
por categoría:
| Categoría | Sizes | Componentes afectados |
|---|---|---|
| Form controls táctiles | xs · sm · md · lg · xl |
checkbox, switch, toggle, radio-group, rating-group, slider |
| Inputs de texto | xs · sm · md · lg · xl |
search-field, number-field, date-field, editable, tags-input, combobox, select |
| Progress/meter (barra escalable) | xs · sm · md · lg · xl |
progress, meter |
| Field / Form (layout) | xs · sm · md · lg · xl |
field, form |
| Nav controls | xs · sm · md · lg |
breadcrumb, pagination, tag-group, toolbar |
| Sin cambio (paneles compuestos donde sm/md/lg cubre) | sm · md · lg |
calendar, date-picker, date-range-picker, file-upload, stepper, tooltip |
| Ya en rango ampliado | xs · sm · md · lg · xl[+full] |
avatar, dialog, drawer, popover, tabs, icon |
N-2 · Paridad de chips en demos
Toda variante / tamaño / color declarado en el tipo del componente
debe aparecer como chip seleccionable en el demo. Las Extract<…>
arbitrarias que truncaban la unión están eliminadas (field,
toolbar ahora exponen los 3 valores de ControlVariant).
Enforced por audit D-7.4.
N-3 · Tokens del contrato deben estar declarados
Cualquier var(--color-X) referenciado en recipes o en *.css debe
estar declarado en generated/base.css. La sesión añadió
content.muted y surface.muted al contrato y corrigió el typo
--color-neutral-element-hover en form.css.
Enforced por audit R-2.6.
N-4 · No raw colors en CSS de Eidos
hex / rgb / hsl / named están prohibidos en src/uix/eidos/**/*.css
y en recipes/base.ts. Todo color va por color-mix(in srgb, var(--color-X) N%, transparent)
o por token directo. Corregidos archetypes.css y events.css
(eidos-commit-settle indigo → primary-solid).
N-5 · Scrollbars portaled
Las reglas ::-webkit-scrollbar* viven sin scope en web/routes/uix/uix.css
para alcanzar overlays portaleados (Combobox listbox, Popover content,
Dialog, Drawer). --uix-line está duplicado en :root + override en
:root[data-mode='dark'] (atributo escrito por ActiveEidos.apply()).
N-6 · Picker kind = single source for input + view
Para los pickers, kind: 'date' | 'month' | 'year' (Chakra-style) es
el único punto de configuración para variantes de granularidad.
Drives:
- Segments del input: filtrado a nivel del
DateFieldProvider(soma). El consumer renderizasegmentstal cual viene — no filtra en el snippet.kind='year'→YYYY,kind='month'→MM/YYYY,kind='date'→MM/DD/YYYY. Literales colapsados automáticamente. - View del popover: el consumer hace branching estructural
(
{#if kind === 'year'} <YearView/> {:else if 'month'} <MonthView/> {:else} <Calendar/> {/if}). Las views son partes propias:<DatePicker.YearView>,<DatePicker.MonthView>, equivalentes enDateRangePicker.
Implicación: no hay componentes separados MonthPicker,
YearPicker, MonthRangePicker, YearRangePicker. Esos casos son
<DatePicker kind='X'> / <DateRangePicker kind='X'>.
N-7 · Composición sobre props para visibilidad de parts
Las parts opcionales (Footer, Clear, Cancel, Close, etc.) NO
exponen props *Button booleanos en el root del componente. La
visibilidad se controla por composición: si quieres Clear,
compones <X.Clear/> dentro de <X.Footer/>. Si no lo compones, no
aparece. Si no quieres footer entero, omites <X.Footer/>.
Las parts renderizan siempre que se monten — sin checks internos contra props del provider. Esto se aplica a derivados: el patrón escala a cualquier componente con sub-parts opcionales (Header, Footer, ToolbarItem, etc.).
Demos: las parts se envuelven en {#if showX} con state local del
demo para dar el control UI, pero las parts en sí no leen ese state.
N-8 · Reutilizar componentes ya definidos en componentes complejos
Cuando un componente complejo (picker, combobox, color-picker, etc.) necesita un sub-control que ya existe como componente independiente (Slider, Popover, Calendar, Field, etc.), debe componerlo en vez de reimplementarlo. Sólo se justifica un fork si reusarlo causaría una pérdida de características concreta y demostrable.
Beneficios:
- Una sola fuente de eventos sema. El sub-control emite sus
eventos canónicos (
slider:handle-pick,slider:commit-set,popover:close-dismiss, etc.) que el composite hereda automáticamente — sin reimplementar la dimensión perceptiva. - Una sola implementación de pointer/keyboard/ARIA. Bugs corregidos en el componente raíz se propagan a todos los composites que lo usan. Sin duplicación de drag, focus trap, etc.
- Tokens del recipe unificados.
--slider-track-size,--popover-content-padding, etc. se setean en un único recipe; todos los composites consumen los mismos. - Superficie pública estable. La API del composite (parts + eventos) se reduce a su lógica diferenciadora; el resto cae del sub-control.
Caso aplicado 2026-05-21: ColorPicker.ChannelSlider deja de tener su
propio ColorPickerChannelSliderProvider con pointer handlers
duplicados; ahora compone SliderProvider por debajo. Channel-aware
behavior queda en una sola función bridge (setChannel + gradient
inline en --cp-channel-gradient). Resultado: −200 líneas en soma,
eventos slider:handle-pick/drag/commit-set propagados, tokens
unificados con time-picker / standalone Slider.
Segundo caso 2026-05-21 — PickerShell. El Footer + Clear/Cancel/
Close de los pickers eran cuatro archivos .svelte casi idénticos
por picker (date-picker, time-picker, color-picker). Extracción a:
src/uix/soma/components/picker-shell/—pickerShellContext+PickerShellHandleinterface (getMode / commit / cancel / clear).src/uix/eidos/components/picker-shell/— parts compartidos<PickerShell.Footer/Clear/Cancel/Close>que leen el handle.src/uix/eidos/components/picker-shell/picker-shell.css— CSS canónica[data-picker-footer/clear/cancel/close].
Cada provider registra su handle en el context via
pickerShellContext.set(this.pickerShellHandle). El namespace de cada
picker eidos re-exporta X.Footer/Clear/Cancel/Close apuntando a los
componentes compartidos — la API pública no cambia. Aplicado al
ColorPicker en este commit. Migración pendiente para date-picker y
time-picker (issue separado).