Saltar al contenido
EspañolEnglish

Novedades

Lo que llegó en cada versión, sacado del registro de cambios (CHANGELOG.md) que viaja en el paquete de la 0.17.1. Una entrada marcada Rompe cambia algo que tu código tiene que tocar al actualizar: npx amset-ui migrate se encarga de casi todo.

0.17.1, del 19 de octubre de 2026

La 0.17.0 se publicó desde un pipeline de pre anterior al arreglo de Flag, y salió con ese fallo: con SSR, la portada y cualquier página con una bandera daban el error 418 al hidratar. Si usas Flag con un servidor, sube a esta.
  • Flag ya no rompe la hidratación cuando el servidor ya tenía su región (AMS-1894). Un build estático pinta cientos de páginas en el mismo proceso, y desde la segunda la región de banderas ya estaba en la caché del servidor: Flag la pintaba sin frontera de Suspense, el navegador (que empieza de cero) la pintaba con ella, y React daba el error 418 al hidratar. Pasaba en la portada de amset.tech y en cada página con el selector de idioma. Ahora la frontera va siempre y el navegador conserva lo del servidor hasta que llega la región; lo prueba una hidratación real con el servidor caliente y el navegador frío.
  • migrate y doctor ven las hojas de antes importadas desde CSS (AMS-1912). migrate reescribe también los @import '@amset/ui/estilos.css' y estilos/<hoja>.css de tus .css, y doctor da error si el CSS global importa una hoja que ya no existe (antes decía que no importabas ninguna). doctor cuenta ahora los mismos nombres que migrate cambia, también los de la 0.16, y señala data-tema, que la biblioteca ya no lee.
  • npx amset-ui create: una aplicación nueva con la biblioteca ya puesta (AMS-1786). Dos plantillas en starters/: Next con App Router (la de por defecto) y Vite con --template vite. TypeScript estricto y ESLint de serie, sin Tailwind, el armazón (AppShell, TopBar, ThemeButton y SideNav marcando la página actual) y cada página en su propio fichero: en Next, páginas de servidor; en Vite, React.lazy y la carga adelantada al pasar por el enlace. Después pasa init, así que salen con el .npmrc, el tema del color de --brand y el proveedor, y doctor no tiene nada que decir. No escribe en una carpeta con algo dentro. npm run consumers crea los dos con el CLI del paquete empaquetado y comprueba que se instalan, pasan sus tipos y su lint, compilan, navegan sin recargar y caben en su tope de JS (medido: 150 KB gzip en Next y 85 KB en Vite).
  • Bloques: siete pantallas enteras que npx amset-ui add copia al proyecto (AMS-1786). Panel de gestión (dashboard), listado de clientes (customer-list), alta de cliente (customer-form, con su esquema customerSchema para validar también en el servidor), ficha de cliente (customer-detail), emisión de factura (invoice-form), ajustes (settings) e inicio de sesión (sign-in). Hechos solo con componentes de la biblioteca: importan @amset/ui como tu proyecto y no copian código de ningún componente, así que no pesan nada. npx amset-ui list --blocks los enseña; add avisa de lo que necesitan y tu proyecto no tiene. En Storybook, en Bloques, con sus estados en claro, oscuro y móvil, y traducidos al inglés. scripts/blocks.test.ts vigila que solo importan la biblioteca, que no escriben ni un color ni una medida, y que los siete copiados compilan en estricto.
  • useLocale() se exporta: el idioma del proveedor ({ locale, direction }) para formatear una fecha o un importe propio igual que la biblioteca. Lo necesitaban los bloques y cualquier aplicación que escribe sus propias cifras.
  • Table con onRowPress: la fila se abre también con Intro. La fila ya se enfocaba con las flechas, pero solo respondía al clic: con el teclado no había forma de abrir una factura del panel. Intro sobre un control de dentro de la fila sigue siendo de ese control.

0.17.0, del 19 de octubre de 2026

La 0.15.0 y la 0.16.0 no llegaron a publicarse: esta es la primera versión publicada desde la 0.14.0, así que quien venga de la 0.14 recibe de golpe lo de las tres secciones (la 0.15.0, la 0.16.0 y esta).
  • Los componentes de IA, configurables para meterlos en el chat del asistente. Lo que el chat real de AMSET hace y las piezas no dejaban configurar: PromptInput admite busyModes (seguir escribiendo mientras responde -añadir al turno, parar y corregir, encolar-: radios de verdad sobre el campo y onSubmit recibe la forma elegida en { mode }; «Parar» pasa a estar junto a «Enviar»), inputRef, actions a la derecha, onPasteText (un texto enorme se adjunta como fichero en vez de pegarse), onInputKeyDown para atajos propios, inputProps (corrector, idioma, tope de longitud) y attachDisabled. ChatMessage gana actions (acciones propias junto a copiar, regenerar y editar), actionsVisibility, editor (editar en la misma burbuja) y highlighted (coincidencia de una búsqueda, con un aro fijo y no una animación). ActivityLog gana group por evento (un historial de varias tareas, con título por sección y sin juntar lo igual de dos secciones), renderText y loading. Y un componente nuevo, FindBar: la barra de buscar dentro de una conversación larga, con contador, siguiente, anterior y cerrar (Intro, Mayús+Intro, Escape). El segundo argumento de onSubmit es nuevo pero no rompe a nadie; 36 pruebas con teclado y foco reales.
  • Las piezas de IA igualan al chat real del asistente (AMS-1773). Se leyó el chat de AMSET pieza a pieza y se añadió lo que hacía y la biblioteca no: PromptInput gana pastedFileName, un freno contra el rebote del doble envío (el mismo texto dos veces en 0,3 s), busyModeScope, history (flechas arriba y abajo, con el cursor al final) y submitOn="auto"; Attachment gana icon, detail, onPreview, compact y el anuncio de «Subido»; Thumbnail gana onPress, loading, error y caption; nuevos ImageViewer (el visor a pantalla casi completa, un diálogo de verdad) y los ganchos useBlobUrl y useElapsed; ArtifactCard gana onRetry; ModelSelector, maxFixes; Conversation gana loading, loadingOlder, onReachStart (mantiene el sitio de lectura al llegar mensajes arriba), stickToBottom, revealKey, highlighted y empty como nodo; Reasoning y ActivityLog cuentan el tiempo en vivo (startedAt, endedAt, label, emptyIcon); Suggestions, allowPartial; UsageBadge, cachedTokens; FindBar, hasMore. Todo opcional: nadie que ya los use cambia. Dejado fuera a propósito: la vista previa de PDF (un iframe sin sandbox abriría la puerta a ejecutar HTML ajeno; falta una pieza que solo admita PDF) y el markdown (sin dependencias en el núcleo).
  • Dos fichas ya no comparten título (AMS-1783): el campo de importe pasa de «Importe» a «Campo de importe» y el de tarjeta de «Tarjeta» a «Campo de tarjeta», como «Campo de texto» o «Campo numérico». Los ids de las fichas no cambian, y scripts/docs.test.ts falla si dos fichas vuelven a repetir título.
  • Los umbrales de cobertura suben a lo medido (AMS-1763): de 80/80/75/80 a 95/94/86/92 (líneas, funciones, ramas, sentencias). Pruebas nuevas para el arrastre de filas, el tirador de ancho de columna, el teclado de la rejilla y los estados de interacción, que eran lo más flojo (entre un 12 % y un 42 %).
  • Rompe Rompe: tres clases raíz con el nombre del componente que de verdad las usa (AMS-814). .amset-code (el campo de código de un solo uso) pasa a .amset-code-field con todos sus hijos y modificadores, .amset-heading (el encabezado de página) a .amset-page-header, y .amset-list (la lista del panel de los desplegables) a .amset-listbox. Las variables --amset-code-* y --amset-heading-* no cambian. Quien estilara esas clases desde fuera tiene que usar los nombres nuevos.
  • El menú de acciones busca escribiendo con el buscador de la biblioteca (AMS-1200): useTypeahead ya acepta una función para leer las claves del DOM y el menú dejó de llevar su copia. Sigue encontrando «Éxito» con «e» y dejando el espacio como parte de lo escrito.
  • La campana de la bandeja y el botón de abrir del buscador y del selector múltiple usan amset-trigger-open (AMS-1293): se borran las dos copias de las reglas de «disparador con el panel abierto».
  • Flag ya no mete las 244 banderas en el JavaScript de la página (AMS-1893). Los dibujos van por regiones (Europa, América, África, Asia, Oceanía), cada una en un fichero que se pide cuando se pinta una de sus banderas: lo que cuenta de Flag pasa de unos 17,6 KB comprimidos de JS a unos 1,5 KB. Mientras llega el dibujo se enseñan las dos letras, y un servidor que espera a la suspensión (Next, renderToPipeableStream) ya trae la bandera en el HTML. Nuevo preloadFlags() para tenerla en el primer pintado de un cliente. El presupuesto del paquete gana el escenario Flag con staticOnly, que solo cuenta lo que se descarga por imports estáticos.
  • Table: la cabecera de fila (rowHeader) ya no sale centrada (AMS-1884). El <th> heredaba el centrado del navegador; ahora alinea al inicio y en negrita como el resto de celdas, y align="end" / "center" siguen mandando.
  • Pagination: un size que no está en la lista de tamaños ya se ve en el selector (AMS-1883). Antes el selector salía vacío o cortado; ahora el tamaño se añade a las opciones, sin duplicados y ordenado.
  • RadarChart: la marca del máximo ya no pisa el nombre del eje de arriba (AMS-1895). Cuelga por debajo del contorno exterior.
  • La prueba de hidratación de RelativeTime ya no depende de la zona horaria de la máquina (AMS-1896): fija el timeZone por defecto de Intl.DateTimeFormat, porque cambiar process.env.TZ no vale en los hilos de Vitest.
  • Todo el código está en inglés: ni una variable, función, parámetro, propiedad ni tipo en castellano (AMS-1887). Se renombraron unos 3.400 nombres distintos (más de 37.000 usos en 800 ficheros) con el LanguageService de TypeScript, no con sustituciones de texto: claveDeFila → rowKey, esClicDeEnrutador → isRouterClick, alPulsarFila → onPressRow, ordenable → sortable. Lo que un consumidor puede notar: bin/proyecto.mjs pasa a bin/project.mjs; dist/fuentes/ (lo que lee amset-ui add) pasa a dist/sources/; los ids de los hallazgos de amset-ui doctor van en inglés (registry, layer-order, legacy-names, provider, router, project, installed, theme-not-imported); el estado de cada fichero de add y de init es new/equal/overridden/kept/modified; AppShell usa content como id por defecto del <main>; el atributo data-photo de Avatar vale loading/loaded. amset-ui migrate sigue buscando los nombres antiguos (escritos como cadenas en bin/migrations.mjs); su lista tenía initialsValues en vez de valoresIniciales y no encontraba esa prop, ya corregido. Lo vigila scripts/identifiers-english.test.ts, que recorre el árbol de sintaxis de todo el repositorio.
  • LocaleProvider ya no arrastra el selector de fechas (AMS-1885). Estaba definido dentro de date-picker.tsx, así que cualquier aplicación que lo montara -que es todas, en todas sus páginas- se llevaba la hoja date.css (4,6 KB gzip, medido con Vite) y, en un build de Next con Turbopack, además el calendario, los trozos editables y el selector entero: ~45 KB sin comprimir en una portada que no pintaba ni una fecha. Pasa a su propio módulo, components/locale-provider.tsx. No cambia la API: LocaleProvider, LocaleProviderProps y DEFAULT_LOCALE salen del mismo índice y de las mismas subrutas. Medido en amset.tech: el JavaScript inicial baja 10 KB gzip y el de la biblioteca de 144 a 116 KB sin comprimir. Lo vigilan src/locale-provider-weight.test.ts (el grafo de imports del proveedor no puede tocar fechas ni capas flotantes) y un escenario nuevo del presupuesto de peso, LocaleProvider con 2,2 KB, que cuenta solo lo estático: el catálogo de claves que el proveedor pide con import() en desarrollo son 20 KB que nadie descarga en producción y taparían un cambio de unos pocos KB.
  • RichTextToolbar y ToggleButton: la barra de un editor de texto enriquecido, sin meter el editor en la biblioteca (AMS-1787). La barra habla con el editor por state (lo que hay puesto donde está el cursor) y onCommand, así que vale con Tiptap, con Lexical o con cualquier otro y @amset/ui sigue sin dependencias. Es una Toolbar (una parada del tabulador, flechas, memoria del foco) con un ToggleButton por formato (aria-pressed, atajo en aria-keyshortcuts y en la ayuda, Control o Comando según el sistema), grupos con nombre, enlace e imagen con paneles que solo se montan al abrirlos y que no son un <form> (vale dentro del formulario de la aplicación), y pulsar con el ratón no le quita el foco al editor. Las direcciones se comprueban con normalizeLinkHref y normalizeImageSrc, que se exportan: lista blanca de esquemas, un javascript: o un data: no pasan. La imagen pide texto alternativo o marcarla como decorativa (alt=""). Hojas nuevas toggle-button.css y rich-text.css, con el marco del editor (.amset-richtext__frame) y la tipografía de lo escrito (.amset-richtext__content). Guía en inglés en docs/rich-text-editor.md con las dos recetas enteras (sobre el núcleo de cada editor, sin @tiptap/react ni @lexical/react, que arrastran @floating-ui), comprobadas por el compilador y atadas a la guía por una prueba, y un checklist para la pasada manual con NVDA y VoiceOver de Table editable, Combobox, InvoiceLinesEditor y la propia barra. Tiptap y Lexical entran solo como dependencias de desarrollo del Storybook, donde dos historias los ejecutan de verdad.
  • WeekAgenda ya no rompe la hidratación en inglés (AMS-1894). La cabecera de la semana sale de formatRange («Oct 11 – 17, 2026»), y el ICU de Node escribe espacios finos (U+2009) alrededor de la raya donde Chrome pone espacios normales: pintada en el servidor, la agenda no casaba al hidratar y React tiraba el HTML de la página entera (error 418, medido en amset.tech, solo en inglés porque en castellano ese formato no lleva espacios). El calendario, los trozos de fecha y el cronograma ya lo normalizaban, cada uno con su copia; ahora los cuatro pasan por utils/plain-spaces.ts. Lo fija una prueba de week-agenda.test.tsx que pinta la agenda en en-US (comprobado que falla sin el arreglo).
  • Decidido: onPress sigue siendo el evento de pulsar, e isDateUnavailable e isLeaf conservan su «is» (AMS-1762). Primer registro de decisiones del repositorio, en docs/adr/ (en inglés): onPress es el nombre que ya escriben todos los fronts y une ratón, teclado y dedo en un solo evento; onClick se queda solo como puerta de migración en la familia del botón y en TextButton, siempre junto a onPress y con el MouseEvent nativo (AMS-735). isDateUnavailable es un predicado, no un estado, e isLeaf es la marca de tres estados de la carga diferida. No cambia ni una prop. prop-names.test.ts deja de tolerarlos en silencio: los lista como excepciones con enlace al registro y falla con cualquier otro «is» o con un onClick propio sin onPress.
  • npx amset-ui migrate cubre también lo que la 0.16 pasó al inglés (AMS-1894). Se escribió para lo que retiró la 0.14 y no sabía nada de la tabla de la 0.16; migrando amset.tech salieron los huecos. Ahora reescribe las 133 hojas por componente (@amset/ui/estilos/boton.css → @amset/ui/styles/button.css, también importadas sueltas), estilos.css, fichas.json y mandos.json, Tooltip para → trigger, PasswordField nueva → newPassword y errorDeImporte → amountError. La tabla de hojas se emparejó por CONTENIDO, comparando las reglas de cada hoja de las dos versiones publicadas: las 133 tienen destino y ninguna cae en la misma que otra. Arreglado de paso: el codemod solo miraba las props de Table y Form escritas en el código, así que una entrada nueva en PROP_RENAMES se ignoraba sin avisar; ahora manda la tabla. Lo que sigue siendo a mano: las claves linea1/linea2 de una dirección (el compilador ya dice «¿quisiste decir line1?») y los atributos data-tema/data-densidad del HTML, que pueden ser de la propia aplicación.
  • Storybook: las descripciones de los controles dejan de usar nombres viejos y la guía de tallas ya enseña la columna «Largo» (AMS-1667). SlotPicker decía hora donde la prop es time, WeeklySchedule y RangeFilter decían { desde, to } donde es { from, to } y closedSchedule() salía como horarioCerrado(); y las historias de SizeGuide declaraban la medida largo pero daban los valores bajo la clave long, así que la columna salía vacía. Solo documentación e historias: sin cambios de API.
  • BarChart: marcas de referencia y una segunda cifra por barra (AMS-1818). markers dibuja líneas que cruzan todas las barras a su valor (la media del grupo, un objetivo), en columnas y en filas: entran en el techo del eje, salen en la tabla con su nombre y su valor y no cuentan en el resumen del dibujo; la capa mide lo mismo que el sitio donde crecen las barras, así que la línea al 62 % cae exactamente donde acabaría una barra de 62 (medido a 0 px). DataBar.detail pone una segunda cifra visible detrás del número de la barra («+12 pts»), solo en filas, donde hay sitio; en columnas se queda en la tabla, junto a la nota. Nuevas variables --amset-bars-name y --amset-bars-row-gap. Lo que sigue pendiente de ese ticket (barra apilada acierto/blanco/fallo y el detalle por punto en LineChart) es API nueva y queda aparte.
  • MonthAgenda: los eventos se pueden pulsar (AMS-1817). AgendaEvent admite href (el evento de la lista del día es un enlace, que pasa por el RouterProvider igual que Link) y MonthAgenda admite onEventPress(key): sin href, el evento pasa a ser un botón que avisa de su clave; con href, avisa además al seguir el enlace. La tarjeta se ve igual y entera es pulsable (foco de 2 px, borde del color del evento al posar el ratón). Sin ninguno de los dos, el evento sigue siendo texto. Lo que sigue pendiente de ese ticket (rejilla de mes con títulos dentro de cada día y vista de agenda por rango) es API nueva y queda aparte.
  • Table: el selector de filas por página ya no se corta, y la columna rowHeader se alinea al inicio (AMS-1819). El selector medía 4,5 rem fijos y con una cifra más ancha salía «5…»: ahora es un mínimo y crece con su valor. Un th scope="row" lo centra el navegador, así que una columna rowHeader quedaba centrada bajo una cabecera a la izquierda; la biblioteca dice ahora text-align: start para todas las celdas th que no son end ni center. Y la barra libre del toolbar (.amset-table__bar-free) ya no se encoge sin suelo hasta cortar un toolbar propio en 390 px: tiene un mínimo y baja de línea.
  • PhoneField: el número cabe con prefijos largos en un campo estrecho (AMS-869). Con +351 en un campo lg de 224 px el último dígito se salía 7,4 px; el disparador del prefijo gasta ahora 12 px menos (menos aire entre la bandera, el prefijo y la flecha: 97,2 → 85,2 px medidos) y el número tiene min-width: 0.
  • Ficha propia para 14 componentes que solo salían dentro de la de su grupo (AMS-1898). AreaChart, StackedBarChart, Grid, Container, Center, ScrollArea, AspectRatio, Kbd, Mark, Highlight, Blockquote, ColorPanel, ToggleChipGroup y Uploader tenían API pública y no tenían página, ni entrada en el índice, ni búsqueda por su nombre: ahora cada uno tiene la suya en Storybook y en docs.json (202 fichas), con su historia «Interactivo», su texto en inglés y su teclado y su accesibilidad medidos en un navegador. Uploader no salía descrito en ningún sitio. Las fichas de Stack, Code (ahora «Código en línea»), LineChart, ColorPicker, ToggleChip y FileUpload dejan de contar lo de las otras y las enlazan. De paso: Blockquote con cite perdía los atributos que se le pasaban (ahora van al <figure>); en Uploader los atributos, className y style caían en la zona de dentro y no en la caja, y al quitar el último fichero el foco se iba al body y el aviso se perdía (ahora vuelve al botón de elegir y se anuncia); el tirador del área de color se colocaba contra el flotante y no contra el área (52 px desviado en ColorPicker, fuera del panel con ColorPanel suelto), y ahora cae donde dice su valor; en la tira de tono, Fin (o arrastrar hasta el borde) llevaba el tirador al principio porque 360° es 0°, y ahora acaba en 359°; ToggleChipGroup y Uploader estrenan --amset-chip-group-gap y --amset-uploader-gap; y el Storybook vuelve a construir, que importaba LocaleProvider de su sitio viejo.
  • Las capas flotantes se recolocan al cargar la tipografía (AMS-1176). Un reflujo que mueve el disparador sin cambiarle el tamaño (la fuente web que acaba de llegar) no disparaba ni el scroll, ni el resize, ni el observador del disparador, y la capa se quedaba con la medida vieja hasta el primer resize. Ahora se vuelve a colocar cuando resuelve document.fonts.ready y cuando cambia el tamaño del body.
  • Hidratar una pantalla con DatePicker cuesta un tercio: el panel cerrado ya no monta su calendario (AMS-1886). FloatingDialog -el panel del selector de fecha, del rango, del color, de los menús, del Dropdown y de los filtros de la tabla- pintaba su contenido también cerrado, así que cada DatePicker montaba un mes entero que nadie veía, y cada día creaba sus formateadores de Intl (calendarOf, la fecha larga, el número del día). Ahora el contenido se monta al abrir y se desmonta al acabar la salida, y los formateadores se crean una vez por idioma (utils/intl-cache.ts). Medido con el benchmark de apps (CPU ×4, mediana de 15): la pantalla de pedidos hidrata en 150 ms en vez de 264, a la par que Mantine (149) y por delante de MUI (242) y antd (352); el selector de fecha cerrado pasa de sumar ~250 ms a ~40. Aviso: quien buscase en una prueba algo de dentro de un panel cerrado ya no lo encuentra hasta abrirlo.
  • Rompe Rompe: TODA la API que se toca desde fuera pasa al inglés. AMS-550 tradujo componentes y props, pero quedaban en castellano funciones, atributos, ficheros, subrutas y la CLI, y los ejemplos de la referencia seguían con la API de antes (<Boton variante="solido">). Qué cambia al subir de versión:
    AntesAhora
    @amset/ui/estilos.css, @amset/ui/estilos/<hoja>.css@amset/ui/styles.css, @amset/ui/styles/<sheet>.css (las hojas, también en inglés: boton.css → button.css)
    @amset/ui/formulario, @amset/ui/esquemas@amset/ui/form, @amset/ui/schemas
    fichas.json, mandos.json, ejemplos.json, idiomas/*.jsondocs.json, knobs.json, examples.json, locales/*.json (y sus claves: component, module, title, family, description, stories, name, knobs, story, code)
    npx amset-ui tema, npx amset-ui mandos, npx amset-ui migrar, amset-tema.cssnpx amset-ui theme, npx amset-ui knobs, npx amset-ui migrate, amset-theme.css; add escribe styles/ y styles.css
    data-tema, data-densidaddata-theme, data-density
    data-cargando, data-seleccionada, data-deshabilitada (Card) y el resto de data-* en castellanodata-loading, data-selected, data-disabled… (data-actual → data-current, data-sin-leer → data-unread, data-nivel → data-level…)
    @layer amset-ui.componentes@layer amset-ui.components
    Clases amset-no-imprimir, amset-only-impresion, amset-for-imprimir, amset-card-pago*, amset-motion-esencial, amset-order-state__hitoamset-print-hidden, amset-print-only, amset-printable, amset-payment-card*, amset-motion-essential, amset-order-state__milestone (y --amset-order-state-milestone-gap)
    Animaciones amset-*-entra, -entrar, -salir, -aparece, amset-progress-va-y-viene, amset-rating-marcar-enter, -exit, -appear, amset-progress-back-and-forth, amset-rating-mark
    csvDeTabla, descargarCsv, separadorPorDefecto, filtrarFilas, ordenarFilas, paginarFilas, opcionesDeColumna, valorDeCeldatableToCsv, downloadCsv, defaultSeparator, filterRows, sortRows, paginateRows, columnOptions, cellValue
    calcularFactura, errorDeImportecalculateInvoice, amountError; y rejectionReason / FileLimits de FileUpload, que el README daba por exportados y no lo estaban
    Tooltip para, AddressField linea1 / linea2, parseHotkey(...).teclaTooltip trigger, line1 / line2, .key
    PasswordField nuevanewPassword
    Avatar state="inline" (era «en línea» mal traducido)state="online"
    TaxZone: peninsula, baleares, canariasmainland, balearic-islands, canary-islands (ceuta y melilla se quedan)
    TransferList moveText(..., 'elegidos' | 'disponibles'), ROOT_FIELD = 'raiz''chosen' | 'available', 'root'
    Claves de textos CabeceraDeAgenda.*, FilaDeFranja.*, TablaDeDias.*, NumerosDePagina.*, TiraDeDias.*, HorasDelDia.*AgendaHeader.*, SlotRow.*, DayTable.*, PageNumbers.*, DayStrip.*, DayHours.*
    Ids de Storybook componente-boton--jugar, componentes-*, sistema-*, fundamentos-*component-button--playground, review-*, system-*, foundations-*; los exports de las historias también en inglés (Cargando → Loading) y el nombre que se ve, en castellano

    También los ficheros y las carpetas del repositorio pasan al inglés (src/components, src/utils, src/styles, src/form, src/schemas, src/table, src/messages/texts, boton.tsx → button.tsx, DISENO.md → DESIGN.md...). Lo que se nota desde fuera: npx amset-ui add copia a components/, utils/, messages/texts/ y styles/ en vez de los nombres de antes; DESIGN.md y ACCESSIBILITY.md viajan en el paquete con su nombre nuevo; y los ficheros de dist/ cuelgan de las mismas carpetas (dist/components/button.js), aunque desde fuera solo se llega a ellos por el indice y las subrutas de exports.

    Los scripts de package.json también: build, build:css, build:css-components, build:docs, build:examples, build:sources, build:locales, build:storybook, typecheck, verify, test:watch, e2e:engines, e2e:engines:gate, consumers, budget, audit y export:figma (antes construir*, tipos, verificar, test:mirar, e2e:motores*, consumidores, presupuesto, auditar y exportar:figma). Quien tenga un npm run construir en su CI de una rama, lo cambia.

    Los parámetros de las funciones exportadas también se llaman en inglés, y scripts/docs-api.test.ts comprueba con el compilador cada ejemplo de README, REFERENCE y Primeros pasos: imports, componentes, props y valores.

  • Storybook sin ruido de accesibilidad y con el código en inglés. Pasado axe sobre las 1.036 páginas (historias y documentación, claro y oscuro): las reglas de PÁGINA (region, landmark-one-main, page-has-heading-one) saltaban en casi todas las historias por construcción y se apagan en el banco (src/bench/a11y.ts); las de orden de títulos y puntos de referencia repetidos se apagan solo en las páginas de repaso y de sistema, que apilan piezas sueltas. Lo que sí era del componente: la cabecera del calendario y de la agenda era un <header> que axe contaba como banner, ahora es un <div>; la columna vacía de Board y SortableList vacía no tenía fila válida dentro del grid (aria-required-children); el nombre accesible de cada columna de Board llevaba «tarjetas» escrito en castellano y ahora sale del paquete de textos (Board.column, con plural, también en inglés); RangeFilter deshabilitado lleva aria-disabled y deja de medirle el contraste a lo apagado; y la barra neutra de Timeline no llegaba a 4,5:1 en el tema oscuro. Por otro lado, 504 historias se llamaban en castellano (Cargando, ConError, SoloLectura…): ahora el export y el id de Storybook van en inglés, el nombre que se ve sigue en castellano, y los identificadores de las historias, el banco y .storybook/ también (fotoDeMuestra → samplePhoto).
  • El panel lateral y el diálogo vuelven a parar la página de detrás y a decir en su disparador si están abiertos (AMS-1284). Al pasar los dos a <dialog> se perdieron, sin un solo error, dos cosas que hacía React Aria: la página de detrás se seguía desplazando con el modal abierto (medido en el banco: la rueda encima del velo la llevaba de 200 a 1.000 px) y el disparador dejó de llevar aria-expanded y aria-controls, así que un lector no decía «expandido» ni se quedaba hundido con su capa abierta. Ahora, mientras hay un Dialog, un Drawer o una CommandPalette abiertos, la raíz lleva overflow: hidden y, si la barra de desplazamiento ocupa sitio, scrollbar-gutter: stable, para que nada salte de lado; lo que la página tenía en línea se devuelve al cerrar, y un diálogo abierto desde un panel no suelta la página hasta que se cierra también el panel. El disparador que se pasa por trigger recibe aria-expanded, aria-controls (solo con la capa abierta, apuntando al id del <dialog>, el tuyo si le das uno) y amset-trigger-open. Cuesta 0,34 KB comprimidos en cada uno; el presupuesto de peso suma un escenario Button, Drawer.
  • benchmark/: MUI y Mantine en las piezas sueltas, y tres apps equivalentes medidas en un navegador real (AMS-1784). Las piezas (Button; Button + Select + DatePicker; Table) ya comparan siete bibliotecas con versiones fijadas; Mantine con el CSS mínimo, solo las hojas de lo que queda en el bundle, comprobado al construir. Además, la misma pantalla (formulario con validación, tabla con orden y paginación, diálogo de confirmación) hecha con @amset/ui, MUI, Mantine y antd, pintada en el servidor e hidratada en Chromium: se mide lo que descarga la página, el JS que se parsea y la hidratación, y cada app pasa el mismo recorrido por roles y axe sin fallos. npm run benchmark construye, mide y reescribe benchmark/RESULTADOS.md con las versiones, la fecha y las salvedades. Medido hoy: la pantalla carga 127 KB gzip con @amset/ui, 176 con Mantine, 220 con MUI y 391 con antd.
  • El botón se queda con el foco al pulsarlo con el ratón también en Safari (AMS-1881). Safari no enfoca un <button> nativo al pulsarlo, ni con el ratón ni con el dedo, así que al abrir un Dialog, un Drawer o un menú con un clic y cerrarlo, el navegador no tenía a quién devolver el foco y caía en el body (quien seguía con el teclado volvía al principio de la página). Button e IconButton enfocan ahora en el click -no en el mousedown, donde Safari lo deshace y Firefox saca el aro de foco con el ratón- y antes de onPress, que es cuando la capa mira quién tiene el foco. Con el teclado no cambia nada. Cuesta 70 bytes comprimidos.

0.16.0, del 18 de octubre de 2026

  • npx amset-ui migrar: reescribe el código que usa los nombres que la 0.14 retiró (AMS-1831). Las props y las claves de columna de Table (enServidor, paginacion, ordenable, numerica...), el sentido del orden, Formulario con sus props (esquema, onEnviar...), los campos *Form, BotonEnviar, resolverDeEsquema, PropsCalendarRange, TableSortInput y la llamada a ariaGranularity. Lo hace con el árbol de sintaxis del TypeScript del propio proyecto, no con expresiones regulares: distingue una prop de la misma palabra en un comentario, una cadena u otro componente, respeta tu sangría y tus comentarios, sigue imports con alias y con espacio de nombres, y las columnas declaradas en una variable. Lo que no puede decidir solo (un ...props, un .sentido leído en un manejador, PropsForm, ERROR_NETWORK_COUPON) sale como aviso con fichero y línea. --dry-run cuenta antes, --check sale con 1 si queda algo (para la CI), y es idempotente. doctor avisa (error) cuando quedan esos nombres. Probado con 19 pruebas, entre ellas una que compila el código de antes (falla) y el migrado (compila) contra los tipos reales de la biblioteca, y ejecutado en seco contra cuatro fronts de la casa sin ningún falso positivo.

0.15.0, del 17 de octubre de 2026

  • InvoiceLinesEditor: la fila ya no se sale de su caja (AMS-1812). Con la columna de retención (la de serie) la fila de una sola línea suma unos 65 rem de columnas, pero el umbral para pasar de tarjeta a fila era 52 rem, el mismo con y sin retención: entre 832 y 1040 px de contenedor sobresalía hasta 210 px y el importe y el botón de quitar quedaban fuera, recortados (sin retención, 38 px a 832). Ahora hay dos umbrales, 55 rem sin retención y 66 con ella; por debajo, cada línea es la tarjeta de dos columnas. e2e/factura-ancho.spec.ts barre doce anchos y falla si la fila sobresale (comprobado que muerde). Lo destapó un barrido de axe sobre todas las historias del Storybook, que contaba el importe recortado como un contraste de 1,12:1.
  • Rompe ROMPE (arregla un fallo): NumberField, QuantityPicker y MoneyField mandan en un formulario el numero, no el texto que se ve. El name iba en el campo visible, que lleva el numero con el formato del idioma: en un formulario nativo, 10,5 llegaba como "10,5" (un servidor lo lee como NaN o como 10), 12.000 como "12.000" (un 12) y un importe como "1.234,56 €". Ahora el name va en un <input type="hidden"> con el numero canonico —10.5, 0.21, 12000, 1234.56—, siempre con punto, sin separador de miles ni simbolo, ya cuadrado a minimo, maximo y paso como lo deja el campo al salir, y vacio si no hay cifra o no se entiende. Un campo apagado no se envia, como un <input disabled>. Aviso: el campo visible ya no lleva name; si una prueba o un script lo buscaba por [name=...], ahora encuentra el oculto. Nuevo canonicalNumber() en utilidades/numero. 12 pruebas nuevas (numero-formulario.test.tsx); 9 fallan contra el codigo anterior.
  • Recargo de equivalencia en las facturas (AMS-1785). calcularFactura y InvoiceLinesEditor suman el recargo al IVA según su tipo -5,2 % con el 21 %, 1,4 % con el 10 %, 0,5 % con el 4 %, art. 161 de la Ley 37/1992- con su fila propia en el desglose (surchargeBreakdown) y en el total. Se pide para toda la factura (equivalenceSurcharge) o línea a línea (surchargePerLine trae una casilla por línea y la de la línea manda). Se redondea una vez por tipo, como el IVA, no línea a línea (diez líneas de 0,10 dan 0,05 y no 0,10). surchargeRates cambia los tipos; el 0,62 % transitorio del 5 % no va por defecto. Sin pedirlo, los totales de siempre no cambian. Sale por el índice SPANISH_SURCHARGE_RATES y los tipos nuevos.
  • Item, ItemGroup, InputGroup y RadarChart. La fila genérica (con href o onPress el TÍTULO es el enlace o el botón y su zona se estira por CSS hasta cubrir la fila: un solo elemento interactivo, y las acciones por encima como botones aparte); el campo con segmentos pegados dentro de una sola caja, con el anillo de foco, la ayuda y el error de TextField; y el gráfico de radar (escala desde cero, un null es un hueco y no un cero, resumen para el lector con lo más alto y lo más bajo de cada serie, y la tabla entera). radarPoint, ringPath y seriesPath salen de utilidades/grafico-radar.ts.
  • TextButton, la acción de texto (AMS-1792). Un <button> con el aspecto y los tonos de Link para una acción que va dentro de una frase (reintentar, dejarlo): el lector dice «botón», responde a Intro y Espacio, es type="button" por defecto y no envía un formulario dos veces mientras loading. Sin la caja ni el relleno de Button: hereda tamaño y altura de línea, y con el dedo la zona de pulsación crece a 44 px con un pseudo-elemento, sin mover la línea (un <button> es siempre inline-block: el relleno sí la estiraría). Sustituye el botón escrito a mano que cada front montaba para esto.
  • HoverCard, Menubar y NavigationMenu. La tarjeta que sale al pasar el ratón por un enlace (su contenido se puede tocar: closeDelay deja llegar hasta ella, Tab entra porque va justo después y Escape la descarta, WCAG 1.4.13); la barra de menús de escritorio (un solo tabulador, flechas laterales con vuelta y al revés en RTL, y con un menú abierto el ratón abre el vecino, que un ActionMenu modal no puede); y la navegación principal con despliegues de ARIA (enlaces de verdad, el panel justo tras su botón, aria-current, Escape y salir con Tab la cierran, y pulsar un botón que el ratón acaba de abrir lo FIJA en vez de cerrarlo). useMenuList sale de MenuPanel para compartir el teclado de la lista con la barra de menús.
  • El prefijo del teléfono sale completo en la lista (AMS-1703). Dominica, Bahamas, Jamaica y el resto del +1 se veían todos como «+1»; ahora la lista y el nombre accesible dicen «Dominica (+1 767)», y con varios rangos «+1 809 · 829 · 849» en el nombre accesible y el título, y «+1 809…» en la fila (el completo partía el nombre del país en dos líneas: 43 px la fila de República Dominicana frente a 36). El disparador sigue en «+1» (las cifras del reparto ya van en el número escrito, y repetirlas daría «+1 767 767 555…»); Estados Unidos, Canadá (demasiados rangos) y los repartos de una o dos cifras se quedan en el indicativo. El E.164 no cambia. dialLabel() sale por el índice.
  • ModelSelector y ArtifactCard (+ ArtifactFrame), los dos que faltaban para migrar el chat del asistente. El selector de motor y modelo es de presentación pura (el catálogo y la elección los pone quien lo usa): dos filas de radios nativos —las flechas saltan los motores apagados—, un motor apagado dice su motivo con texto, y si lo elegido se ha apagado el botón se marca y el panel avisa; fixLabel + onFix pone el enlace para arreglarlo. La tarjeta de artefacto trae la miniatura o el icono según el tipo, abrir y descargar como botones HERMANOS de la cabecera, y la vista previa solo se monta abierta; onVisible pide el contenido de lo que entra en pantalla. ArtifactFrame aísla el HTML con sandbox="allow-scripts" y nunca allow-same-origin, y no se deja quitar desde fuera. Salen sueltos selectionLabel y artifactKind.
  • npx amset-ui init y npx amset-ui doctor (AMS-1778). init deja un proyecto con la biblioteca puesta como toca: la línea @amset:registry del .npmrc (nunca un token), @amset/ui/estilos/base.css arriba del CSS global —lo que fija el orden de las capas antes que Tailwind 4 sin pagar el CSS dos veces—, tu tema generado con el comando tema (contraste comprobado), AmsetProvider y el enrutador de Next montados en el layout, _app o <App />. Es idempotente, no pisa un fichero tuyo (solo le añade líneas), --dry-run cuenta lo que haría y --install instala con tu gestor. doctor diagnostica un proyecto que ya la usa y sale con código 1 si hay errores: Tailwind 3 sin orden de capas, data-tema con otros valores, token como ${VARIABLE} en el .npmrc, Pages Router sin transpilePackages, y avisos por el tema sin generar, los enlaces que recargan la página, Jest sin mapa de CSS. Probado con proyectos de Next y de Vite sin configurar que pasan por init, compilan, se abren en Chromium y llevan el color de marca pedido (npm run consumidores, scripts/init-doctor.test.ts).
  • ref funciona con React 18 (AMS-1782). En React 18 un componente de función no recibe ref como prop y lo descarta: <Button ref={nodo}> lo dejaba a null y Dropdown no abría su panel desde un Button (medido con React 18 real). Button, IconButton y FloatingButton -los únicos tres que exponen ref en su tipo público- van ahora envueltos con withRef, un forwardRef que entrega el ref al render; en React 19 todo sigue igual. Nuevo npm run test:react18 (y su paso en la CI): instala react@18 aparte y pinta cada ficha y los botones con ref sin que salga ni una excepción ni un aviso de React, así que «React 18 y 19» deja de ser una promesa sin vigilar.

0.14.0, del 17 de octubre de 2026

  • Rompe Rompe: fuera los nombres de antes que quedaban como alias (AMS-1761). Un alias obsoleto es código muerto: se van los de Table (enServidor, seleccionadas, alSeleccionar, paginacion, columnasConfigurables, alCambiarColumnas, redimensionables, alPulsarFila, barraSuperior; en la columna numerica, ordenable, filtrable, ocultaPorDefecto, pegada, cabeceraDeFila; y sentido en el orden, con su tipo TableSortInput), los nombres en español del formulario (Formulario, CampoTextoForm, CampoAreaForm, SelectorForm, SelectorFechaForm, CasillaForm, InterruptorForm, GrupoRadioForm, BuscadorForm, BotonEnviar, resolverDeEsquema, PropsForm, EsquemaEstandar, IncidenciaEstandar, ShippingAnswer), PropsCalendarRange, ariaGranularity y ERROR_NETWORK_COUPON. La tabla de equivalencias está en el README. amset-ui-web y amset-assistant suben a esta versión a la vez.
  • El calendario del DatePicker ya no vuelve a aparecer al cerrarse (AMS-1766). El cierre de las capas esperaba a TODAS las animaciones y transiciones del panel, también a las que pone el CSS de quien usa la biblioteca: en amset-assistant, una transición de 200 ms en scrollbar-color hacía que la salida de 100 ms acabara, el panel volviera a verse entero unos 80 ms y solo entonces se cerrara. Ahora se esperan las animaciones de @keyframes y las transiciones que mueven la capa (opacity, transform, translate, scale, display, overlay), y la salida de las capas flotantes y del Tooltip se queda en su último fotograma (forwards) hasta el close().
  • README en inglés (AMS-1716). README.md pasa a ser la portada en inglés —lo que muestran npm y GitLab—: instalación, uso, estilos, temas, Next.js, idiomas bajo demanda, formularios, peso medido, pruebas, herramientas para asistentes de IA y licencia. La referencia completa por componente sigue en castellano en README.es.md, que ahora viaja en el paquete. src/readme-ingles.test.ts ata la portada a la realidad: cada npm run y cada subruta @amset/ui/... que cita tiene que existir, y la prueba de documentación vigila también README.es.md.
  • Referencia en inglés (AMS-1774). REFERENCE.md traduce al inglés la referencia completa por componente (README.es.md, 2.674 líneas) y viaja en el paquete, enlazada desde la portada. src/referencia-ingles.test.ts impide que se separen: mismos encabezados en el mismo orden, mismos bloques de código y mismas tablas, y las mismas variables --amset-* y componentes citados en las dos.
  • Componentes para interfaces de IA: PromptInput, ChatMessage, Reasoning, ToolCall, ActivityLog, Attachment, Suggestions y UsageBadge. Salen de lo que ya hacía el chat del asistente de AMSET, sin perder nada: la caja crece con el texto hasta un tope, envía con Intro y baja de línea con Mayús+Intro (sin enviar mientras se compone con un IME), recupera lo último con la flecha arriba, admite ficheros pegados, arrastrados o elegidos y pasa de «Enviar» a «Parar» mientras responde; el registro sigue el final solo si ya se estaba abajo (useStickToBottom, exportado) y junta los eventos repetidos; una llamada a herramienta que falla se abre sola. Todo dicho con texto además de con color, sin dependencias y con un CSS por componente. Salen sueltos formatDuration, formatBytes, collapseRuns y isNearBottom. Un @keyframes nuevo, amset-caret-blink, compartido por los tres cursores.
  • FormTreeSelect, el campo conectado que faltaba (AMS-1675). @amset/ui/formulario ya tiene un campo conectado por cada campo con valor: FormTreeSelect guarda la clave del nodo elegido, o la lista de claves con selection="multiple" (en el orden del árbol, no en el del clic), y enseña el error del esquema en el campo. FormCascader y FormTreeSelect entran también en las tablas de campos conectados de la referencia, de donde faltaba la primera.
  • El orden del CSS ya no decide como se ve un campo (AMS-1775). La caja y la entrada de todos los campos (.amset-field__control y .amset-field__entry) pesaban una clase, lo mismo que sus variantes (el area de texto, el campo de numero, el buscador...), y mandaba la que el empaquetador dejara la ultima: en la biblioteca sola el orden lo fija index.css, pero en un consumidor no. Medido en un navegador con las hojas en el orden malo: el TextArea salia con una caja de 36 px alrededor de un textarea de 56 px (en Next 16 con Turbopack, 46 alrededor de 67), y con el interlineado en normal en vez de 21 px. Ahora las dos bases van en :where() y pesan cero: cualquier variante, y cualquier CSS de quien usa la biblioteca, las gana siempre. En los dos ordenes la caja mide lo mismo (80,3 px) y no desborda. Una prueba nueva (orden-del-css.test.ts) impide volver a escribir la base con una clase suelta. Sin cambios en la API.
  • Firefox y WebKit en el e2e, y lo que encontraron (AMS-1716). npm run e2e:motores pasa el e2e de comportamiento en los dos motores (opt-in con AMSET_MOTORES=1, sin axe ni capturas), y la CI lleva una puerta rápida, e2e:motores. Dos fallos reales que Chromium no veía: en Safari, al cerrar un desplegable o una confirmación el foco se perdía en el <body> (Safari no da el foco a un botón al pulsarlo con el ratón, y el navegador devuelve el foco a «lo que lo tenía»); ahora la capa lo devuelve a su disparador si se ha perdido. Y en Firefox y Safari el diálogo, el panel lateral y la paleta de comandos desaparecían de golpe al cerrarse (solo Chromium anima display y overlay); ahora la salida va por data-exiting y close() espera a que acabe, igual en los tres motores. Sin cambios de API.
  • La marca ya admite un segundo color: secondary. Hasta ahora solo existía brand (el acento), así que un diseño con dos colores de marca no se podía expresar. Nueva familia de tokens --amset-secondary, -hover, -active, -text, -soft e -ink (violeta por defecto, en claro y en oscuro, con sus pares de contraste AA medidos), clave palette.secondary en amset-ui.config.js, y tone="secondary" en todo lo que ya aceptaba accent (botones, etiquetas, avisos, tarjetas, avatares, gráficos, texto, enlaces…). Sin cambios para quien no la use.
  • Y un tercero: tertiary. Misma receta que secondary: tokens --amset-tertiary* (verde azulado por defecto, claro y oscuro, contraste AA medido), palette.tertiary y tone="tertiary" en todo lo que aceptaba accent.
  • El segundo y el tercer color llegan a TODOS los componentes con data-tone. Casillas, interruptores, deslizadores, pestañas, pasos, el día elegido del calendario, el aro de foco… pintaban siempre accent y no tenían tone. Ahora data-tone="secondary" o data-tone="tertiary" en el propio componente o en cualquier contenedor repinta todo lo de dentro (remapea --amset-accent* y --amset-focus), sin tocar cada componente.
  • La documentación cuenta los tres colores de marca. Fundamentos → Color lista las familias secondary y tertiary (53 roles) y estrena «Los tres colores de marca», una demo en vivo de tone y data-tone; Primeros pasos explica las claves nuevas de palette y las fichas que hablaban de «seis tonos» ya nombran los ocho.
  • npx amset-ui add: copiar el código de un componente a tu proyecto (AMS-1771). Para quien no quiere depender de una versión publicada: add button iban-field (o --all) copia el código con todo lo que necesita, sus hojas de estilo y un catálogo de textos generado solo con lo copiado; list enseña los 190 que hay. Quita el .js de los imports (--keep-js lo deja), no pisa un fichero que ya habías cambiado (--overwrite sí) y apunta lo copiado en amset-ui.json. El paquete publica el código en dist/sources/ (npm run construir:fuentes). Medido: lo copiado compila en estricto y add --all también.
  • @amset/ui/esquemas: las comprobaciones de los campos españoles, sueltas (AMS-1771). documentSchema, ibanSchema, cnaeSchema, socialSecuritySchema, plateSchema, cardNumberSchema y postalCodeSchema: la letra del NIF, el módulo 97 del IBAN, la división del CNAE... como esquemas Standard Schema que además traen check y message para engancharlas a Zod o Valibot sin que la biblioteca dependa de ninguno. Mismos mensajes que el campo, en es y en. Subruta de servidor: sin 'use client'.
  • ExportButton y exportElement: gráficos y tablas a PNG, SVG y PDF (AMS-1771). En el navegador y sin librerías: el elemento se copia con los estilos ya calculados, se mete en un SVG con foreignObject y de ahí salen los tres formatos (elementToPng, elementToSvg, elementToPdf, downloadBlob). scale hasta 4 para imprimir; abarca lo que se sale de la caja; incrusta tipografías e imágenes. El PDF lleva la imagen, no texto. Un elemento oculto falla con ExportError. Medido en Chromium con LineChart, BarChart y DonutChart. Nuevos textos ExportButton.*.
  • Table con 100.000 filas, medido (AMS-1771). Con virtualized y maxHeight: primer pintado 121 ms con 16 filas en el DOM, 61 fotogramas por segundo al desplazar y 42 ms para ordenar. No cambia código: solo se documenta con la medida.
  • ACCESIBILIDAD.md: lo que se mide (axe con WCAG 2.0, 2.1 y 2.2 AA), lo que se vigila aparte y lo que no se ha medido todavía, sin declarar un nivel que no se ha comprobado (AMS-1771).

0.12.0, del 16 de octubre de 2026

  • La biblioteca se prueba en proyectos reales (AMS-1716). npm run consumidores empaqueta el paquete, lo instala en un proyecto Next (App Router) y en otro Vite, los compila, los sirve y los abre en Chromium: tipado de un proyecto ajeno, HTML de servidor traducido, hidratación sin errores, interacción, idiomas perezosos que no se descargan si nadie los pide y tope de JS de primera carga. Job consumidores en la CI. Sin cambios para quien usa la biblioteca.
  • Los avisos para quien programa ya no hablan ni cargan nada en producción (AMS-1716). Con process.env.NODE_ENV detrás de una guarda typeof process, en el navegador la producción se tomaba por desarrollo: con catalogs el proveedor descargaba el catálogo entero de la biblioteca (18 KB gzip, medido en un Vite real) para comprobar las claves, y los avisos de props obsoletas de la tabla salían por consola. Ahora lo decide isDevelopment(), que deja la expresión que sustituye el empaquetador sin guarda.
  • Elegir un día en un DatePicker (o cualquier capa) dentro de un Dialog ya no cierra el modal. El close y el cancel del <dialog> hijo subían por el árbol de React hasta el onClose/onCancel del modal que lo contiene y lo cerraban (también con Escape dentro del calendario). Dialog, Drawer, CommandPalette y FloatingDialog solo atienden el cierre que es suyo.
  • El texto sobre un relleno de color es claro en los dos temas (AMS-1737). En el tema oscuro los rellenos sólidos (accent, error-solid, success-solid, warning-solid, info-solid) eran claros y llevaban texto casi negro: con una marca roja o verde salía letra negra sobre rojo o verde. Ahora el relleno tiene la misma luminosidad oscura en los dos temas y su tinta (*-ink) es casi blanca, teñida del tono de la familia, no blanco ni negro puros. Medido: con cualquier tono y croma de marca la tinta llega a 4,5:1 sobre su relleno en claro y en oscuro (prueba nueva en paleta.test.ts). highlight pasa a negro también en oscuro, así que el pasar el ratón oscurece siempre. Aviso: en oscuro los botones primarios y de estado se ven más profundos que antes; si forzabas colors con un relleno claro, el generador se negará a escribir el tema.
  • El texto de un ToggleChip activado se lee con cualquier color (AMS-1737). Llevaba el 75 % del color del chip y el resto de la tinta del tema: un rosa intenso en claro o el acento en oscuro quedaban por debajo de 4,5:1. Ahora lleva el 30 % (--amset-chip-ink-share), y una prueba barre el espacio de color en los dos temas.
  • Mark deja de pintar texto oscuro sobre negro. Usaba el rol highlight (hacia dónde se oscurece un relleno) como fondo; ahora va sobre gold-soft con la tinta principal, y el par está en las pruebas de contraste.
  • Lo hecho dentro de una barra del cronograma oscurece la barra en vez de aclararla, para que las fechas sigan leyéndose sobre ella.
  • Los componentes de bloque ocupan el ancho de su hueco sin pedirlo. Campos y Select, tabla, acordeón, tarjetas, alertas, pestañas, paginación, gráficos, listas y el resto de componentes de bloque llevan inline-size: 100% desde base.css: metidos en una fila flexible se encogían hasta su contenido (en la web, 80 componentes; el Select cambiaba de ancho al elegir). Los que miden lo que su contenido (botones, etiquetas, avatares, interruptores, SegmentedGroup, texto) no cambian, y una prueba obliga a clasificar cada componente nuevo. Dentro de otros componentes se mantienen compactos el prefijo del teléfono, las horas del horario semanal y el selector de filas de la paginación.
  • Calendar suelto se mide contra su caja y sale centrado: los días encogen con el hueco en vez de con la ventana (en una tarjeta de 238 px el mes medía 274 y se salía) y el calendario ocupa su hueco hasta lo que mide el mes.
  • Grid iguala el alto de lo que va en cada celda (equalHeight, por defecto true): una tarjeta dentro de un envoltorio llena la fila igual que si fuera hija directa.
  • Link va subrayado por defecto (underline="always"): dentro de un párrafo el color no basta (axe link-in-text-block, 1,12:1 medido). underline="none" lo quita para menús y barras.
  • SegmentedGroup: el relleno ya no sale volando tras desplazar la página. El punto de partida se medía al elegir la opción anterior; con la página movida entre medias arrancaba a miles de píxeles y el marco enseñaba la barra de desplazamiento.
  • benchmark/: peso comparado con react-aria-components, shadcn/ui, HeroUI y antd (P1.6). npm run benchmark empaqueta con Vite lo mismo en cada una (Button; Button, Select y DatePicker; Table) y cuenta JS y CSS en gzip, con las versiones fijadas y las salvedades escritas en benchmark/README.md.
  • Pedir un componente ya no trae los textos de todos (AMS-1718). El catalogo es/en entero (65 KB sin comprimir) viajaba con cualquier componente que dijera algo: un Button pesaba 23,4 KB gzip y ahora 5,9; Table pasa de 54 a 38 KB y la agenda de semana de 35 a 18. Cada componente trae solo su paquete (src/mensajes/textos/); ES y EN siguen siendo el catalogo entero. translatorFor(locale, paquetes, mensajes?) y useMessages(paquetes) reciben ahora los paquetes del componente, y catalogFor se sustituye por catalogEntry. Un texto pedido a un paquete que no lo trae sale en la consola y hace fallar las pruebas.
  • Seguridad: Link y ButtonLink con target="_blank" llevan rel="noopener noreferrer" (AMS-1716). Antes no llevaban rel y la pagina de destino recibia la ventana de origen y la cabecera Referer. El rel que pasa quien lo usa se respeta y se completa. La direccion de seguimiento de OrderStatus tambien lleva noopener.
  • SECURITY.md, auditoria y presupuesto de peso en CI (AMS-1716). SECURITY.md con como informar y que hace y no hace la biblioteca; npm run auditar (altas y criticas de lo que viaja) y npm run presupuesto (peso gzip de lo que se importa, medido con Vite) en el job verificar; src/seguridad-urls.test.tsx fija que javascript: queda bloqueado.
  • axe pasa tambien wcag22aa (AMS-1716): en axe-core 4.13 es la regla target-size; 568 casos sin violaciones.
  • Todas las descripciones de props, también en inglés (AMS-1687). Las 300 que faltaban, de componentes antiguos y de los nuevos, ya tienen su versión en src/mensajes/props.en.json; una prueba nueva en scripts/api.test.ts falla si una prop nueva se queda con su descripción solo en castellano.
  • LineChart, AreaChart y StackedBarChart (AMS-1698). Gráficos de varias series con hueco (null), eje con números redondos, leyenda, valores al pasar el ratón, resumen y tabla para el lector de pantalla. Hoja series.css, textos SeriesChart.*.
  • El panel del Select se topa a 20 rem. Con muchas opciones el panel llegaba a medir toda la ventana, con una barra de desplazamiento kilométrica. Ahora mide como mucho 20 rem (unas ocho opciones y media) y el resto se recorre con scroll; se cambia con --amset-select-panel-max-height. Cualquier capa flotante puede pedir su propio tope con --amset-floating-cap; si la ventana es más baja, el panel sigue encogiéndose.
  • WorkCalendarView, createWorkCalendar y lectores de festivos (AMS-1700). Calendario laboral sin festivos incrustados: se cargan de la fuente oficial con parseIcsHolidays / parseCsvHolidays. Resumen de días laborables por mes, isDateUnavailable para los selectores de fecha. Hoja calendario-laboral.css, textos WorkCalendar.*.
  • WeekAgenda en móvil: en pantalla estrecha pasa a la lista de días con la franja de la semana arriba, como Teams (layout auto | full | compact). En táctil las citas se pulsan y no se arrastran.
  • WeekAgenda (AMS-1700). Agenda de semana y de día con citas que se arrastran y se estiran (puntero y teclado), reparto de las que coinciden, línea de «ahora» y avisos al lector de pantalla. Hoja propia, agenda-semana.css; textos WeekAgenda.* en español e inglés; placeBlocks, moveBlock y resizeBlock salen sueltos.
  • Rompe ROMPE: un solo nombre para cada prop, el del HTML y sin «is» (P0.1, AMS-1727). La biblioteca tenia dos dialectos: 44 controles decian isDisabled, isReadOnly, isRequired, isInvalid e isSelected y 24 piezas disabled, readOnly, selected y open, asi que quien montaba un formulario tenia que acordarse de cual iba en cada uno. Ahora son disabled, readOnly, required, invalid, selected, open, indeterminate, dismissable y keyboardDismissDisabled en todos, y no queda ningun alias de los nombres de antes: hay que cambiarlos al actualizar (en JSX, isDisabled pasa a disabled y asi con cada uno). MultiSelect se queda con disabled, readOnly y required como todos, y desaparecen sus alias de «is». Se quedan isDateUnavailable (es una función, no un estado), onPress y el onClick nativo, y useDisclosure sigue devolviendo isOpen junto a sus verbos open() y close(). Una prueba (nombres-de-props.test.ts) impide que vuelva un nombre con «is».
  • InvoiceLinesEditor (AMS-1700). Editor de las líneas de una factura con IVA y retención de IRPF por línea y el desglose de totales; calcularFactura y lineAmount salen sueltos. Hoja propia, factura.css. Textos InvoiceLinesEditor.* en español e inglés.
  • Table avanzada (AMS-1699). Agrupación por columna, filtros con varias condiciones, exportar a CSV, columnas guardadas entre visitas, edición en celda y virtualización de filas. Todo opcional: sin las props nuevas la tabla se comporta como antes. Nuevos textos Table.* en español e inglés; csvDeTabla y descargarCsv salen sueltos.
  • ContextMenu y submenús en ActionMenu (AMS-1698). El menú del clic derecho, con teclado, y MenuAction.children para listas anidadas.
  • CodeField: rellenar o vaciar una casilla del medio ya no corre las cifras de la derecha (AMS-1747). En modo controlado el hueco no sobrevivía a que el padre devolviera el valor sin huecos, y teclear en la casilla 5 con "123" puesto dejaba el 7 en la 4. Ahora las casillas conservan sus huecos mientras el padre devuelva lo que onChange emitió, y onChange/onComplete entregan siempre el código sin huecos (antes onComplete podía llegar con un espacio dentro). Escribir sobre una casilla que ya tiene cifra la sustituye en vez de repartir las dos, y una casilla vacía ya no se pinta con un espacio.
  • Firefox y WebKit en el e2e, y lo que encontraron (AMS-1716). npm run e2e:motores pasa el e2e de comportamiento en los dos motores (opt-in con AMSET_MOTORES=1, sin axe ni capturas), y la CI lleva una puerta rápida, e2e:motores. Dos fallos reales que Chromium no veía: en Safari, al cerrar un desplegable o una confirmación el foco se perdía en el <body> (Safari no da el foco a un botón al pulsarlo con el ratón, y el navegador devuelve el foco a «lo que lo tenía»); ahora la capa lo devuelve a su disparador si se ha perdido. Y en Firefox y Safari el diálogo, el panel lateral y la paleta de comandos desaparecían de golpe al cerrarse (solo Chromium anima display y overlay); ahora la salida va por data-exiting y close() espera a que acabe, igual en los tres motores. Sin cambios de API.
  • Campos sin caja: variant="bare" y --amset-field-touch-height. Un campo que va dentro de otra caja (la barra de un chat, un buscador dentro de una barra de herramientas) tenia que quitarse la suya a base de CSS propio, y al quitarla perdia tambien el relleno y el alto, asi que el texto quedaba pegado al borde y el campo medía mas o menos que los botones de al lado. bare en TextField, TextArea y el resto de campos no pinta borde, fondo ni raya, conserva el alto y el relleno del tamaño y marca el foco con un anillo (y el error con la raya de abajo). Y --amset-field-touch-height fija el alto de la caja con el dedo (por defecto 48 px, como siempre) para el front que usa controles mas bajos en el movil. Aditivo.

0.11.0, del 16 de octubre de 2026

  • Los enlaces precargan el destino (como next/link). RouterProvider acepta prefetch (router.prefetch de Next): Link y ButtonLink lo llaman una vez por destino al posar el raton o recibir el foco. No lo hacen con un destino de fuera, con target propio ni apagados. Opcional: sin prefetch no cambia nada. Next sin cablear nada: @amset/ui/next exporta NextRouterProvider (App Router) y @amset/ui/next/pages exporta NextPagesRouterProvider (Pages Router); se ponen una vez arriba del arbol y todos los enlaces navegan y precargan por el enrutador de Next. next es peer opcional: quien no importe esos subpaths no lo necesita. Nueva ficha Enrutado en Storybook (y, con ella, en la web) que explica el proveedor genérico y los dos de Next.
  • PageHeader, ButtonGroup y Combobox ya no llevan 'use client'. No usan estado, efectos ni manejadores propios: en Next se renderizan como componentes de servidor y su codigo no viaja al navegador. Medido en una app de Next 16 con una pagina de servidor que los usa: PageHeader y ButtonGroup desaparecen del JS de cliente (-1,4 kB, -469 B gzip). El resto de componentes con la directiva si usan hooks, contexto o manejadores.
  • Textos que la biblioteca seguía escribiendo a mano en castellano (AMS-1720). Con el idioma en inglés salían en castellano: la calculadora de cuotas («al mes durante», «La última…», el mínimo y «TIN / TAE»), el selector de envío («Te faltan… para que salga gratis»), el aviso del tablero al mover una tarjeta, el «de 5» de la valoración, el contador de la visita guiada, la pregunta de después de la encuesta NPS (su función por defecto se llamaba sin traductor), los errores de dirección y de tarjeta y los nombres de color y de canal del selector de color. Ahora salen del catálogo de mensajes. addressErrors, postalCodeError, cardNumberError, expiryError, cvvError, cardErrors, moveAnnouncement y colorName aceptan el traductor como último argumento opcional y, sin él, devuelven lo mismo que antes; nuevo channelName. La prueba del catálogo ya no se conforma con buscar tildes: recorre el JSX y las plantillas de cada componente.
  • Storybook: las demos, la documentación y los controles, de verdad en el idioma elegido (AMS-1720).
    • Los textos de las demos (botones, tablas, avisos, importes) salen en inglés: los args llegan traducidos al componente y el resto se cambia en la vista previa con src/banco/demos.en.json (3.400 textos; los nombres propios y los códigos se quedan). Los importes y fechas que las historias formateaban con 'es-ES' fijo usan src/banco/formatos.ts. En castellano todo queda igual que antes, también lo que fotografían las capturas de referencia.
    • Veinticinco historias de Repaso y Sistema envolvían la demo en un LocaleProvider sin idioma que forzaba el castellano: fuera.
    • Las fichas cuyas historias van en su propio marco (inline: false) salían en castellano: cada marco recibe el idioma y la página lo lee de su propio estado.
    • «Jugar» pasa a llamarse «Interactivo» (Playground en inglés). Los ids no cambian.
    • Pulsar un enlace de una demo ya no navega la ventana entera (Storybook pone <base target="_parent">): un ancla desplaza, uno de fuera abre otra pestaña.
    • Los controles que el componente solo lee al montarse (default…, initial…, el estado de la historia) ya funcionan: la demo se vuelve a montar al cambiarlos, salvo en Capas, donde cambiar el título de un diálogo abierto no lo cierra. currency y locale se eligen de una lista: escritos a mano tumbaban la historia.
    • La documentación de 198 ficheros usaba los nombres de la API anteriores a AMS-550 (alCambiar, valorInicial, plana, fantasma, clave…): ahora usa los reales (onChange, initialValue, flat, ghost, key…), comprobado contra las props y tipos de cada componente.
    • Siete historias pedían iconos con su nombre viejo («Subir», «Carrito») y pintaban la palabra: arreglado, con una prueba.
  • Render en el servidor con idiomas perezosos (AMS-1721). loadMessages(locale, catalogs) resuelve el catálogo del idioma antes de renderizar (componente de servidor, loader, getServerSideProps) y su resultado viaja como messages en AmsetProvider/LocaleProvider: el HTML sale traducido y se hidrata sin avisos. Comparte la caché de preloadCatalog, no lleva 'use client' y rechaza si la carga falla. Un AmsetProvider recibe ahora también messages. Sin cambios para quien no lo usa.
  • Los huecos que quedaban para migrar un front (AMS-1730). Select gana native (un <select> de verdad: el selector del sistema en el movil, con onChangeEvent) y group en la opcion, que agrupa bajo un titulo tanto en el <select> (optgroup) como en el panel propio. Button gana loadingText, que sustituye al contenido mientras envia, sin girador. IconButton gana fullWidth. NumberField gana onChangeEvent. Y --amset-field-required-display: none esconde el asterisco de «obligatorio» (el lector lo sigue anunciando). Todo es aditivo.
  • Los idiomas de catalogs se cargan bajo demanda (AMS-1721). Cada etiqueta puede ser el JSON o una función (fr: () => import('./fr.json')): solo se pide el idioma del locale activo, una sola vez, sin repetir la carga aunque la función se escriba en línea, con StrictMode ni al volver a un idioma ya visto. Mientras llega salen los textos de la biblioteca; si falla se avisa por consola, se llama a onCatalogError y se reintenta al volver a pedirlo; si se cambia de idioma con otro en camino, el tardío no pisa al nuevo. preloadCatalog(cargador) precarga (selector de idioma, servidor). Nuevos tipos CatalogLoader, CatalogSource y CatalogErrorHandler. Compatible: un objeto sigue saliendo en el primer pintado.
  • API para migrar un front que ya existe (AMS-735). Tres cosas que antes obligaban a reescribir cientos de manejadores: onClick de Button, IconButton y ButtonLink entrega ahora el MouseEvent nativo (con preventDefault, stopPropagation y currentTarget; antes recibia la pulsacion unificada); onChangeEvent entrega el evento del <input> en TextField, PasswordField, TextArea, Checkbox y Switch (e.target.value, e.target.checked), sin quitar onChange; e inputRef llega a NumberField y FileUpload, asi que inputRef.current?.click() abre el selector de ficheros desde un boton propio. Aviso: si pasabas onClick leyendo pointerType, usa onPress, que sigue dando la pulsacion unificada.
  • Idiomas enteros desde el proveedor. AmsetProvider y LocaleProvider aceptan catalogs ({ fr, 'pt-BR': ptBR }, un JSON de Componente.clave -> texto por etiqueta BCP-47) y AmsetProvider acepta ademas messages. Se usa el catalogo del locale activo (exacto, idioma o regional) y lo que falte cae en ingles clave a clave; messages manda sobre catalogs. La plantilla con todas las claves se publica en @amset/ui/idiomas/es.json y en.json. En desarrollo el proveedor avisa una vez de un idioma sin catalogo y de claves que no existen, y validateCatalog(json) (con los tipos MessageCatalogs, MessageOverrides, MessageKey y CatalogReport) lo comprueba en una prueba. Sin cambios para quien no pasa catalogs.
  • La raya de color de cada evento de la agenda vuelve a medir 3 px (AMS-1665). --amset-agenda-slot estaba declarada dos veces en la misma regla y la segunda (el hueco entre el mes y la lista, 20 px) pisaba el grosor de la raya. El grosor pasa a su propia variable, --amset-agenda-stripe; --amset-agenda-slot queda solo para el hueco.
  • Los campos agrupados dicen que estan apagados (AMS-1268). NumberField, QuantityPicker, CodeField y SignatureField ponen aria-disabled en su role="group" cuando estan desactivados: antes solo se apagaban los botones de dentro y un lector de pantalla no decia que el campo entero no se podia usar.
  • Licencia MIT (AMS-1688). El paquete declara "license": "MIT" y trae el fichero LICENSE (antes UNLICENSED). Sin cambios de código.
  • Textos que la biblioteca escribia en castellano aunque el idioma fuera ingles (AMS-1714). Ahora salen del catalogo de mensajes: «Antes» y «Ahorras un» del importe, «sube» y «baja» de la variacion del dato, «de 5» del resumen de valoraciones, el contador de la galeria, «con subniveles» del selector en cascada, «sin leer» de la bandeja, «Eventos del...» de la agenda, «Trabajando» de confirmar accion y «(se abre en otra pestaña)» del estado del pedido. 12 claves nuevas; en castellano no cambia nada.
  • El Select anuncia que es obligatorio y que no es valido (AMS-1155). El disparador pasa de role="button" a role="combobox" (patron «select-only combobox» de la APG) y lleva aria-required y aria-invalid cuando toca. Un lector de pantalla dice ahora «cuadro combinado» en vez de «boton». Afecta a cada campo construido sobre el: telefono, selector de idioma y moneda, tipo de IVA, mes y filas por pagina. Si una prueba tuya busca ese control con getByRole('button'), pasa a getByRole('combobox'). Visualmente no cambia nada.
  • El Storybook entero sigue el idioma de la barra (AMS-1720). Antes iba a medias: con English elegido la barra lateral, la explicación de cada historia, *Primeros pasos* y los rótulos de Repaso y Fundamentos seguían en castellano, y la interfaz de Storybook (pestañas, ajustes, tabla de props, atajos) salía en inglés aunque se eligiera castellano. Ahora: la barra lateral usa .storybook/menus.en.json (con los nombres que Storybook saca del export sin acentos corregidos en menus.es.json); las 672 explicaciones de historias van en .storybook/historias.en.json; *Primeros pasos* son dos ficheros markdown; los rótulos del banco (Canvas, Matrix y las tablas de Fundamentos) van en src/banco/banco.en.json y Canvas pasa a seguir el idioma elegido (sin barra sigue siendo el castellano, así que las capturas de referencia no cambian); y lo que escribe Storybook por su cuenta se pone en castellano con .storybook/traductor-de-interfaz.ts. Una prueba (scripts/historias.test.ts) obliga a que cada nombre, explicación y rótulo tenga su versión en inglés y a que no sobre ninguna. Se apaga la guía de bienvenida y el aviso de novedades de Storybook, que son su publicidad y no se pueden traducir. Las descripciones de la pestaña *Controles*, que Storybook fija al preparar la historia y no repinta, se traducen en el propio DOM al elegir inglés (solo si la traducción lleva el mismo código en el mismo orden).
  • SiteFooter y @amset/ui/seo (AMS-1750). SiteFooter es el pie de página semántico: un <footer> (contentinfo dentro de AppShell) con marca, columnas de enlaces —cada una un <nav> con su título como nombre—, redes solo con icono, franja legal y controles; los enlaces externos se abren en otra pestaña con rel="noopener noreferrer" y avisándolo al lector de pantalla. Hoja pie-sitio.css, textos SiteFooter.*. La subruta nueva @amset/ui/seo (servidor, sin dependencias ni CSS) trae pageMetadata (compatible con la Metadata API de Next y con validación que lanza SeoError), checkSeo, los constructores de JSON-LD (organization, webSite, webPage, breadcrumbList, article, product, faqPage, localBusiness), <JsonLd> con escape seguro dentro de <script>, sitemapEntries/sitemapXml y robotsRules/robotsTxt (blockAll para pre). Aditivo.
  • Tabs admite pestañas sin panel (AMS-1634). Una pestaña sin content no pinta panel ni lleva aria-controls: es un filtro o un modo de la misma pantalla. Sigue siendo una pestaña (flechas, foco, elegida) y se puede mezclar con las que si tienen panel. Tab.content pasa a ser opcional; es aditivo. El Dialog ya cubria el resto de huecos que el ticket media en la 0.8.0 (icono y color de cabecera, acciones en cabecera, incrustado, arriba, ancho numerico, sin relleno y en columna).
  • Un ejemplo de uso real por cada historia de Storybook, publicado en @amset/ui/ejemplos.json. Sacado del propio codigo: si la historia trae render, es su cuerpo tal cual; si no, se sintetiza la etiqueta JSX con los args reales de meta y de la historia. Un ejemplo al que le falte una prop obligatoria (porque su valor dependia de algo externo al fichero, o del mapping de un control de Storybook como iconControl) se descarta en vez de publicarse incompleto. scripts/ejemplos.test.ts compila cada ejemplo sintetizado contra los tipos reales del componente.

0.10.2, del 15 de octubre de 2026

  • ToggleChip, ToggleChipGroup y OptionCard (AMS-1534). Los dos componentes que faltaban para migrar el asistente sin perder diseño. ToggleChip es un chip que se activa y se apaga (<button aria-pressed>) con color propio por chip (color o --amset-chip-color), count, icon, tres tamaños y marca de verificación al activarse; ToggleChipGroup los agrupa con nombre y salto de línea. OptionCard es una opción de RadioGroup pintada como tarjeta, con media, description y la marca de la elegida: dentro del grupo se reparten en una rejilla (--amset-option-card-min). Teclado y foco son los de un radio nativo. Hojas propias chip-alternable.css y tarjeta-opcion.css, ficha en Storybook en español e inglés y pruebas de comportamiento.

0.10.1, del 15 de octubre de 2026

  • El campo de hora ya hidrata igual en inglés (AMS-1687). Con un reloj de 12 horas, el servidor (ICU de Node) escribía un espacio fino antes del «AM»/«PM» y Chrome un espacio normal: React tiraba el HTML y daba el error 418 en las fichas TimeField y DateTimePicker de amset.tech/en/. Los separadores del campo se normalizan al espacio normal, y una prueba lo fija.

0.10.0, del 15 de octubre de 2026

  • La documentación también en inglés (AMS-1687). @amset/ui/fichas.json lleva en cada ficha en: { titulo, descripcion } y nuevo export @amset/ui/props.en.json (descripción de cada prop en inglés, por el texto en español). El extractor rompe el build si una ficha no tiene su versión en src/mensajes/fichas.en.json, y una prueba vigila la cobertura y que el castellano no haya cambiado desde que se tradujo (cada traducción guarda la huella de su origen). En Storybook, las páginas de documentación siguen el selector de idioma: con English, título, texto y descripciones de las props salen en inglés (1.758 descripciones, todas traducidas). Las historias sueltas (sus nombres y comentarios) y las páginas de Empezar siguen en castellano.
  • TagsInput y Combobox (AMS-1698). Campo de etiquetas que se crean al escribir (Intro, coma, pegado o al salir), sin repetidas, con max, validate, transform y name; y Combobox, el buscador con texto libre por defecto. Textos TagsInput.* en es y en.
  • DateTimePicker y MonthPicker (AMS-1698). La fecha y la hora en un solo campo (2026-10-15T09:30, con min, max, isDateUnavailable y name) y el selector de mes y año (2026-10, con los meses en el idioma activo y minYear / maxYear). Solo avisan con el valor entero. Hoja propia, fecha-hora.css, con --amset-datetime-gap y --amset-monthpicker-gap.
  • Cinco campos de gestión y cinco ganchos (AMS-1677). Nuevos: MoneyField (dinero que entiende coma decimal, separador de miles y símbolo según el idioma y entrega un número), SocialSecurityField (NUSS con los dos dígitos de control comprobados), LicensePlateField (matrícula actual, provincial y de ciclomotor), CnaeField (CNAE-2009, con catálogo opcional) y VatRateSelect (21, 10, 4 y 0 %). Salen sueltos los validadores (validateSocialSecurity, validatePlate, validateCnae...) y las funciones de dinero (parseMoney, formatMoney, formatMoneyForEdit). Ganchos: useMediaQuery, useDisclosure, useClipboard, useHotkeys y useAnnouncer. Textos en español e inglés, ficha en Storybook y pruebas de comportamiento (teclado y errores al salir del campo) para cada uno. Ver el roadmap en docs/roadmap-amset-ui.md.
  • Los textos de la biblioteca, en español e inglés (AMS-1687). Cada texto que un componente decía por su cuenta (etiquetas accesibles, avisos, errores, anuncios, resúmenes, plurales...) sale ahora de un catálogo por idioma (unas 560 claves, cada una en es y en en), según el LocaleProvider. Un idioma sin catálogo cae en inglés; sin proveedor sigue el español, así que nada cambia para quien ya usa la biblioteca. Nuevos: la prop messages del proveedor, translatorFor, useMessages, y un último parámetro opcional msg en las funciones que escriben un texto. Los textos con cantidades usan Intl.PluralRules. Storybook trae un selector de idioma (es, en, ar). Tres pruebas vigilan que no haya claves sin usar, que el inglés no lleve acentos y que ningún componente vuelva a escribir un texto en español a mano.
  • El CSS entra por componente, no entero (AMS-1677). Cada módulo de dist/ importa ahora SU hoja (y base.css, con los tokens y el reset), así que el empaquetador se lleva solo el CSS de lo que usas: un Button en Vite pasa de 392 kB a 23 kB de CSS (4 kB gzip), y una pantalla de 20 componentes de 395 a 133 kB, con el mismo resultado al píxel que la hoja única. Nuevos exports: @amset/ui/estilos/base.css y @amset/ui/estilos/<hoja>.css. Cambia al actualizar: (1) si sigues importando @amset/ui/estilos.css pagas el CSS dos veces: quítalo; (2) con Next Pages Router hace falta transpilePackages: ['@amset/ui'] (sin él, ERR_UNKNOWN_FILE_EXTENSION al construir); (3) en Jest, un moduleNameMapper para .css$. Probado en Vite 8, webpack 5, y Next 16 con App Router y Pages Router. Los imports van en la misma línea que "use client" para no mover los sourcemaps.
  • Once componentes se pueden usar desde un Server Component (AMS-1677). Antes TODOS los componentes llevaban "use client", así que en Next un layout de solo servidor se llevaba igualmente el JS de Text, Title, Badge, Separator, EmptyState, VisuallyHidden, CompactGroup, DataList, List, Stack/Grid/Center/Container y Blockquote. Ya no lo llevan: no usan hooks, manejadores de eventos ni APIs del navegador. Comprobado importándolos y llamándolos con la condición react-server de Node, y una prueba nueva (componentes-servidor.test.ts) impide que un fichero sin la directiva empiece a necesitar el navegador. Sin cambios de API. Los componentes que dependen de un proveedor, de texto por idioma o de otro componente de cliente (PageHeader, ButtonGroup, Result) siguen marcados, a propósito.

0.9.6, del 15 de octubre de 2026

  • Un tema claro dentro de una página oscura ya se ve claro. Los valores claros solo existían en :root: un <div data-tema="light"> dentro de una página en oscuro seguía oscuro, sin ningún error (la muestra de tema de amset.tech salía oscura con «Claro» marcado). Ahora [data-tema='light'] existe en cualquier elemento, igual que el oscuro, y donde se anidan gana el de más dentro. Para que el texto y el fondo de la zona cambien, la zona lleva además la clase amset-canvas (o su propio color y fondo).
  • Los enlaces ya no van subrayados. Link sale sin subrayar, en el color de acento y en negrita (el peso es la pista que no depende del color: axe sigue limpio con el enlace dentro de un párrafo). underline="always" lo subraya siempre y underline="hover" al posar el ratón. Tampoco se subrayan ya al posar el ratón los títulos de Card, las migas, la descarga de FileList, el enlace de Stat, el de los filtros, el de ProductCard ni los de GlobalAlert. bare sigue existiendo y ahora solo le quita el peso.

0.9.5, del 15 de octubre de 2026

Repaso de calidad de doce componentes que Aitor señaló en amset.tech (AMS-1701). Todo medido en el navegador antes y después, con prueba que falla sin el arreglo.

  • DateRangePicker ya deja elegir el segundo extremo en el otro mes. Con dos meses a la vista, el primer mes seguía al día con el foco, y un día recibe el foco al pulsar el ratón, antes de soltarlo: las rejillas se corrían un mes bajo el puntero y el clic caía en otro sitio. Ahora la vista solo se mueve cuando el foco sale de ella. De paso: cada mes lleva su nombre encima, «Mes siguiente» mira el último mes visible y DatePicker/DateRangePicker siguen su value (un atajo de QuickRange ya escribe en el campo).
  • Countdown con variantes: variant="roll" (por defecto, las cifras que cambian ruedan), "ring" (un anillo que se vacía de forma continua) y "text" (el aspecto de antes). Los anuncios para lector no cambian.
  • ActivityMap cuadrado: el título de la tabla se metía como celda de la primera columna; ahora la columna de días mide lo que mide, las casillas son cuadradas, los meses van encima y la leyenda cuadra con la rejilla.
  • LocalePicker con banderas (withFlags, LocaleOption.flag, localeFlag), y el hueco de un DatePicker vacío sale con las letras de cada idioma (yyyy, jj/mm/aaaa, tt.mm.jjjj).
  • PhoneField con los 244 países y territorios de la UIT, cada uno con su bandera (SVG propios, geometría real) y su nombre en el idioma activo; se busca escribiendo el nombre («Fra»), el espacio sigue buscando, Re Pág/Av Pág saltan diez y la rueda del ratón ya baja la lista. Nueva prop initialCountry. Las opciones se anuncian «España (+34)».
    • La rueda no bajaba en NINGUNA lista flotante larga: el panel escuchaba también el desplazamiento de su propia lista y, al volver a medirse, la devolvía arriba. Arreglado en flotante.tsx, para todas.
    • ListOption.textValue (texto de búsqueda aparte de la etiqueta), CountryPrefix.areaCodes.
  • Amount alineado: altura de línea propia, «/mes» pegado al precio, la insignia de descuento centrada con las cifras; tres tamaños en fila comparten línea base.
  • Table: ordenar y filtrar son dos cajas iguales de 22 px; el filtro activo se marca con el color del acento y el embudo relleno, sin fondo de bloque. La cabecera de fila pegada ya no coge el gris de la cabecera.
  • Tabs sin barra de desplazamiento: un píxel de margen negativo desbordaba la lista y Windows enseñaba una barra con flechas. Si las pestañas no caben, la rueda las mueve de lado sin barra. orientation="vertical" ya se pinta en columna.
  • SideNav con el diseño del menú de AMSET (el del assistant): baldosa de icono, nombre y descripción, marca de la página actual, grupos desplegables con cuenta, plegado a carril con tooltip. API nueva retrocompatible: description, grupos (NavGroup, NavItem), onCollapsedChange, header/footer como función de { collapsed }. Anchos 13/16/19 rem. AppShell ya no apila el menú encima del contenido en una pantalla estrecha: queda un carril al lado.
  • Collapsible y Accordion cerrados sin hueco fantasma: el panel cerrado seguía teniendo caja y se comía el gap.

0.9.4, del 15 de octubre de 2026

  • Dos hidrataciones mas que se rompian con una web de verdad (AMS-1666). Salieron al barrer amset.tech en produccion, donde el HTML lo pinta un runner de CI en UTC y el navegador esta en otra zona y otro dia:
    • QuickRange con un periodo puesto marcaba el atajo que cuadra («este mes») segun el dia que fuese al pintar: el servidor decia uno y el navegador otro. Ahora no marca ninguno hasta que se sabe el dia.
    • RelativeTime con withFullDate escribia la fecha completa en la zona del servidor («15:45») y el navegador en la suya («17:45»). Ahora la primera vez se acepta lo del servidor y en cuanto se hidrata el nodo se vuelve a montar con la hora de quien mira (useInBrowser): suppressHydrationWarning calla el aviso pero deja el texto del servidor.
    • hidratacion.test.tsx pinta ahora el servidor en UTC y el navegador en Europe/Madrid, y comprueba que el texto acaba bien.
  • La API de Table pasa a inglés, con los nombres de antes funcionando (AMS-1677). Era el único componente que mezclaba idiomas (onSort junto a alSeleccionar, paginacion, enServidor...). Ahora: serverSide, selectedKeys, onSelectionChange, pagination, configurableColumns, onVisibleColumnsChange, resizableColumns, onRowPress, toolbar; en las columnas sortable, filterable, numeric, hiddenByDefault, sticky, rowHeader; y el orden es { column, direction }. Los nombres antiguos siguen funcionando con un aviso en la consola (una vez por nombre, nunca en producción) y se retiran en la próxima versión mayor. onSort entrega direction y también sentido mientras tanto, para no romper a quien lo lea. Tabla de equivalencias en el README, que además tenía la sección de Tabla escrita con una API anterior que ya no existía.
  • La API de la biblioteca, legible por agentes de IA (AMS-1677). El build escribe ahora dist/api.json con las props reales de cada componente (tipo, obligatoria o no, comentario), sacadas del compilador y no de una lista a mano; los atributos HTML nativos no se enumeran, se anota que se aceptan. De ahí salen dist/AGENTS.md, dist/llms.txt y dist/llms-full.txt, y el paquete lleva una skill, skills/amset-ui/SKILL.md, para quien construye con la biblioteca desde Claude Code, OpenCode u otro agente. Nuevos exports: @amset/ui/api.json, /AGENTS.md, /llms.txt y /llms-full.txt. Sin cambios en la API de los componentes.
  • El job verificar construye antes de probar. Los 8 tests del CLI de temas se saltaban siempre en CI porque dist/ aún no existía; ahora corren.

0.9.3, del 14 de octubre de 2026

  • Timeline ya no rompe la hidratacion por los espacios de la fecha (AMS-1666). El tramo de una barra («24 oct – 4 nov») lo escribe Intl.DateTimeFormat.formatRange, y con ICU 78 -el de Node 22.23- pone espacios finos (U+2009) a cada lado de la raya, mientras que Chrome pone espacios normales. Un HTML pintado en Node y hidratado en el navegador no casaba y React lanzaba el error 418. Ahora se normalizan al espacio normal. Se vio al quitar de la web de amset.tech el parche que esquivaba el cronograma: la prueba de hidratacion de la biblioteca no lo cogia porque corre en un solo ICU.

0.9.2, del 14 de octubre de 2026

  • useTabs sale por el índice (AMS-1670). Es la lógica de las pestañas sin su ropa: para una pantalla que ya pinta su panel por su cuenta y solo necesita la lista con su teclado (las flechas dan la vuelta, Inicio y Fin, solo la activa entra en el tabulador, las apagadas se saltan, activación automática o manual). Salen también TabSlot, TabsSetup y TabsPlumbing. Antes solo lo usaban Tabs y la galería. Para filtrar una misma lista no son pestañas: es SegmentedGroup.
  • Ya no se rompe la hidratacion en una web con SSR o exportada a estatico (AMS-1666). Un componente que lee «hoy» o «ahora» mientras se pinta sale distinto en el servidor y en el navegador, y React tira el HTML y repinta (error 418 en la consola, medido en produccion en amset.tech). Pasaba en Calendar, DatePicker, DateRangePicker, QuickRange, MonthAgenda, ShippingPicker y Timeline. Ahora el primer pintado -el del servidor y el de la hidratacion- no sabe el dia, y el navegador lo conoce justo despues, sin un fotograma de mas en una app que solo pinta en cliente:
    • Calendar y MonthAgenda sin un dia que los ancle (ni value, ni defaultFocusedValue, ni initialDay) pintan solo su sitio, con aria-busy, hasta que llega «hoy»; con un dia dado se pintan enteros desde el servidor.
    • La marca del dia de hoy (data-today) y la linea de hoy de Timeline salen al llegar al navegador.
    • ShippingPicker escribe el plazo de entrega al llegar al navegador, salvo que se le pase now.
    • Hay una prueba que lo vigila en TODAS las fichas (hidratacion.test.tsx): pinta en servidor un dia, hidrata otro y cuenta los desajustes. Una ficha nueva entra sola.

0.9.1, del 14 de octubre de 2026

  • El texto de las 149 fichas, al día con el código (AMS-1659). Las fichas del banco (y desde ahora las de amset.tech) seguían hablando de React Aria, que ya no está, y usaban los nombres en castellano de la API de antes (etiqueta, tamano, alNavegar, ProveedorIdioma…). Revisadas una a una contra el código: props y valores reales, teclado y accesibilidad como funcionan hoy, y fuera lo que ya no existe.
  • @amset/ui/fichas.json: la ficha de cada componente, como dato (AMS-1659). Una entrada por componente con su nombre de importación, su módulo, su título en español, su familia, el texto de su ficha (qué es, cuándo usarlo y cuándo no, teclado, accesibilidad, aspecto fino; en markdown) y los ids de sus historias en el banco. Sale de las historias de Storybook al construir, así que la web de amset.tech deja de llevar una copia a mano que se quedaba vieja.
  • La description del Dialog admite un nodo (una etiqueta de estado junto a la frase) y se sigue anunciando como descripción. Sin descripción ya no apunta con aria-describedby a un elemento que no existe.
  • Dialog cubre ya lo que pedía un modal con cabecera de marca (AMS-1534, a petición de amset-assistant, que tenía 42 modales propios). Todo es opcional y lo que ya existía no cambia:
    • title puede ser un nodo; icon pone un icono propio en lugar del del tono y accent le da color (--amset-dialog-accent); headerActions lleva botones entre el título y el aspa.
    • width (píxeles si es número, medida CSS si es texto) manda sobre size; placement="top" lo pega arriba, para una paleta que crece hacia abajo; hideHeader no pinta la cabecera pero el título sigue siendo el nombre accesible.
    • flush quita el relleno del cuerpo y bodyColumn lo hace columna flex, para contenido con su propio desplazamiento.
    • inline: la misma caja EN la página, como una región con su título. Sin <dialog>, fondo, Escape ni trampa de foco, sin aspa y sin inventar un «Cerrar»: sirve para reutilizar como panel de pantalla lo que en otra es un diálogo.

0.9.0, del 14 de octubre de 2026

  • QRCode (AMS-1638). Un código QR en SVG sin dependencias: <QRCode value label level size margin onError />. Detrás hay un codificador propio (encodeQr, ISO 18004, modo bytes/UTF-8, versiones 1–40, niveles L/M/Q/H) comprobado módulo a módulo contra el paquete qrcode (2.032 matrices idénticas con máscara forzada) y el SVG renderizado se decodifica con un lector independiente. Sale siempre negro sobre blanco, también en el tema oscuro: es lo que lee un escáner. No se hacen Mentions, Watermark, Masonry ni el visor de imágenes de antd: sin demanda medida.
  • TreeSelect: un desplegable con el árbol dentro (AMS-1636). El campo es el del Select y el panel es el Tree: elegir una categoría o un departamento de una jerarquía sin tener el árbol siempre a la vista. Una (selection="single", cierra al elegir) o varias ("multiple", se queda abierto y lo elegido sale en el orden del árbol); cualquier nodo se puede elegir, rama u hoja, salvo los apagados. Las ramas que llevan a lo elegido se abren solas y el foco entra en la fila elegida. displayRender, name (un campo oculto por clave). Teclado el del Tree más Abajo/Arriba para abrir y Escape para cerrar. Sin búsqueda dentro del panel (el Tree no la tiene) y sin versión conectada propia: va con FormField.
  • Tree: carga diferida y marcado en cascada (AMS-1637).
    • loadData(clave) pide los hijos de una rama isLeaf: false la primera vez que se abre; el chevron deja paso a un anillo (aria-busy) y se anuncia «Cargando…». Si falla, la rama se queda como estaba y el chevron reintenta.
    • cascade (con selection="multiple"): marcar una rama marca todo lo de dentro y una rama con solo una parte marcada se ve «a medias» (y el lector dice «elegida en parte»). El valor sale en el orden del árbol; lo apagado no se toca. Con carga diferida, una rama ya marcada hereda la marca en lo que llega.
    • Medido contra el ticket: el Carousel ya tenía autoplay (automatic, con botón de pausa, pausa al pasar el ratón o enfocar y respeto a «menos movimiento») y control desde fuera (index + onChange); no se tocó. Arrastrar nodos para moverlos queda fuera (pide una alternativa de teclado diseñada aparte).
  • Cascader: selector en cascada (AMS-1635). País > provincia > ciudad en un solo campo, con una columna por nivel, en vez de varios Select encadenados que no comparten estado ni teclado. El valor es la ruta (['es', 'madrid', 'alcala']), controlada o no. changeOnSelect deja elegir una rama sin bajar a una hoja; loadData(ruta) pide los hijos de una opción isLeaf: false la primera vez que se abre (la columna dice «Cargando…»); displayRender, levelLabels (el nombre de cada nivel para el lector de pantalla), name (viaja la clave de la hoja). Teclado: flechas arriba/abajo por la columna, derecha entra, izquierda vuelve, Intro elige, Escape cierra. Las ramas se anuncian «con subniveles». FormCascader lo conecta al formulario. Sin búsqueda ni selección múltiple todavía.
  • Subir ficheros de verdad (AMS-1555). FileUpload elegía y validaba, pero no subía nada: el progreso, el fallo y el reintento los reescribía cada proyecto. Ahora:
    • Uploader: la zona de soltar, una barra por cada fichero que sube, el motivo y «Reintentar» si falla, y quitar uno que sube cancela la petición. Sube por HTTP con http={{ action, method, headers, data, fieldName, withCredentials }} o por donde digas con customRequest({ file, onProgress, signal }) (una URL firmada, un SDK). Cuántos suben a la vez: concurrency (3). Avisa con onChange, onUploaded y onFailed, y anuncia «factura.pdf subido» o el fallo.
    • useFileUploads: el motor sin pintar nada (cola, progreso, fallo, reintento, cancelar, pending, y autoStart: false + start() para subir con un botón de «Enviar»), para montar otra pantalla.
    • httpUpload, UploadError, isAbort: la petición por defecto, con XMLHttpRequest (es lo único que da progreso de subida; fetch no).
    • FileUpload gana maxCount (los que no caben se rechazan con su motivo), directory (elegir una carpeta; soltar una carpeta encima no la recorre), pegar un fichero o una captura con Ctrl+V y hideList.
    • Sigue siendo cierto: el filtro de tipo y tamaño es comodidad, el servidor tiene que volver a comprobarlo.
  • Campos coherentes entre sí y NumberField vaciable (AMS-1625, AMS-1626).
    • NumberField se puede vaciar: borrar el texto y salir del campo avisa con onChange(NaN) —el «sin valor» que ya escribían los consumidores— y deja el campo en blanco. Antes volvía el número anterior y se enviaba. Lo que no es un número (abc) sigue descartándose. QuantityPicker no cambia: una cantidad no puede estar vacía. FormNumberField lo traduce a undefined.
    • DocumentField e IbanField admiten onBlur.
    • PhoneField admite name (envía el número entero en E.164, no el trozo visible), QuantityPicker admite name y Slider admite name (cada tirador lo lleva); ya viajan en un formulario nativo.
    • El error de MultiSelect lleva el icono de aviso, como el de los demás campos.
    • Slider ya aceptaba onBlur (es un atributo de su <div>); el ticket decía que no.
  • Armazón, pegado al desplazar y controles pegados (AMS-1558).
    • AppShell: cabecera, columna lateral, contenido y pie en una rejilla de CSS (el Layout, Header, Sider y Content de antd en uno). El contenido es un <main id="contenido" tabindex="-1">, el destino de «saltar al contenido» del TopBar; la cabecera se queda fija y la columna se queda fija justo debajo de ella (mide su alto) con su propio desplazamiento. Por debajo de 48 rem la columna pasa encima del contenido y deja de ser fija. siderWidth, mainId, stickyHeader.
    • Affix: un bloque que se queda pegado al desplazar, por arriba (offsetTop) o por abajo (offsetBottom), con onChange(pegado). Es position: sticky: el contenedor tiene que ser más alto que el bloque y ningún ancestro puede tener overflow: hidden ni auto.
    • TableOfContents gana offset: con una cabecera fija de 64 px, pulsar una entrada deja la sección debajo de ella y la raya de lectura se cuenta desde su borde de abajo. Sin offset es lo de siempre.
    • CompactGroup: un campo con su botón (o varios campos) pegados sin huecos y con las esquinas de unión cuadradas; el Space.Compact de antd. Los campos de dentro llevan hiddenLabel. Vale con cualquier campo (texto, número, selector...) y con Button, IconButton y ButtonLink.
    • No hecho, con su ticket: Cascader (AMS-1635), TreeSelect (AMS-1636), carga diferida y arrastre del Tree y autoplay del Carousel (AMS-1637), y las piezas menores (AMS-1638).
  • @amset/ui/formulario: todos los campos conectados y la API en inglés (AMS-1557). Antes conectaba 8 campos y tenía los nombres en español (Formulario, CampoTextoForm, BotonEnviar, resolverDeEsquema), lo que dejaba dos idiomas en una misma importación.
    • Nombres en inglés: Form (props schema, initialValues, onSubmit, validateOn), SubmitButton, schemaResolver y los tipos FormProps, SubmitResponse, StandardSchema, StandardIssue. Los nombres de siempre siguen funcionando con sus mismas props —son alias obsoletos y se retirarán en una versión mayor—: no hay que migrar nada hoy.
    • Campos nuevos: FormPasswordField, FormPhoneField, FormDocumentField, FormIbanField, FormCodeField, FormNumberField, FormQuantityPicker, FormSlider (un valor), FormRating, FormMultiSelect, FormSegmentedGroup (una opción), FormDateRangePicker, FormTimeField, FormColorPicker, FormCardField, FormAddressField, FormSignatureField y FormFileUpload. Los de valor compuesto (tarjeta, dirección) reparten el error del esquema a cada subcampo: tarjeta.number falla en el número, no en el bloque.
    • FormField: conecta CUALQUIER otro campo con una función que recibe value, onChange, onBlur y error. Es el equivalente del Form.Item de antd, y cubre lo que no tenga versión propia o un componente tuyo.
    • Los campos conectados antiguos se llaman ahora FormTextField, FormTextArea, FormSelect, FormSearchField, FormDatePicker, FormCheckbox, FormSwitch y FormRadioGroup.
    • Aviso: ROOT_FIELD sigue valiendo 'raiz' y no 'root'; es la clave que ya leen los proyectos en formState.errors.
  • Tono de la barra, forma de los botones, tamaño del grupo y aviso de foto rota (AMS-1556).
    • Progress gana tone (success, error, warning, info, neutral; sin tono, acento): una subida que ha fallado se pinta en rojo y una terminada, en verde. El color no es lo único que lo dice: ponlo también en el texto con valueLabel.
    • Button, ButtonLink e IconButton ganan shape: 'default' o 'pill' (esquinas del todo redondeadas) y, solo en IconButton, 'circle'. Dentro de un ButtonGroup solo se redondean las esquinas de fuera del grupo. Se declara el vocabulario Shape y SHAPES en variantes.
    • ButtonGroup gana size: se escribe una vez y lo heredan los Button, IconButton y ButtonLink de dentro que no dicen el suyo (uno con su propio size lo conserva).
    • Avatar gana onError(src): avisa de que la foto no ha cargado. El avatar ya se quedaba en iniciales por su cuenta; esto es para reintentar con otra dirección o dejar constancia.
    • No hacía falta: ButtonGroup ya tenía orientation="vertical".
  • Tabla: filas desplegables, clase por fila, alto máximo y más (AMS-1554).
    • renderExpandedRow(row) convierte cada fila en desplegable: gana un botón con aria-expanded (que apunta a su detalle con aria-controls cuando está abierto) y el detalle sale debajo, de ancho completo. rowExpandable(row) deja sin botón a las filas que no tienen detalle. Se llega con el teclado (flecha derecha desde la fila, Intro) y desplegar no cuenta como pulsar la fila (alPulsarFila). Controlable con expandedKeys / onExpandedChange, o initialExpandedKeys.
    • rowClassName(row, index) suma una clase a una fila concreta (una factura vencida en rojo).
    • maxHeight ('24rem', '60vh'): el cuerpo se desplaza y la cabecera y el pie se quedan fijos. Era la variable --amset-table-height; ahora es prop.
    • TableColumn.pegada admite 'end' (la columna de acciones se queda al final al desplazar en horizontal); true y 'start' siguen pegando al principio.
    • TableColumn.ellipsis corta el texto largo con «…» en una línea y deja el texto entero en el title.
    • Arreglado: con la tabla desplazada, la cabecera de fila pegada (th) se pintaba encima de la cabecera de columna «Número», y el pie fijo quedaba por debajo de las columnas pegadas.
    • No hacía falta: los totales al pie (TableColumn.footer) y la cabecera pegada ya existían.
    • Sigue sin existir: colSpan por celda, bordered, tableLayout, showHeader, caption.
  • Funciones de pintado (AMS-1550). Hasta ahora una opción, una celda o un día eran texto más un icono: en cuanto hacía falta un avatar, dos líneas o un precio, el componente se descartaba. Ahora se puede pintar la pieza. La regla es la de Chip: label es lo que se lee y la función pinta lo que se ve, así que el nombre accesible no cambia y la pieza sigue siendo elegible con ratón y teclado.
    • renderOption(option, { selected, disabled }) en Select, MultiSelect, SearchField, SelectableList y VirtualList (esta última solo la llama para las filas pintadas).
    • renderValue(option) en Select: lo elegido dentro del campo cerrado.
    • renderTag(option) en MultiSelect: cada ficha de lo elegido.
    • renderItem(item, { selected, disabled }) en TransferList.
    • renderDay(date, number) en Calendar: lo que va dentro de cada día. El nombre del día no cambia.
    • renderPage(page, { current }) en Pagination: el contenido del botón de cada página (solo con shape="full").
    • renderCrumb(crumb, { current }) en Breadcrumbs.
    • TableColumn.render recibe ahora también index, la posición de la fila en la página que se ve.
    • Se exportan los tipos OptionRender y OptionRenderState.
    • Todo es aditivo. Falta lo que antd llama expandedRowRender y summary en la tabla: son de AMS-1554.

0.8.0, del 13 de octubre de 2026

  • Quién cierra una capa y con qué se abre (AMS-1553).
    • Dialog gana isDismissable (pulsar fuera cierra; en false, solo se va por sus botones, Escape o open) e isKeyboardDismissDisabled, las mismas dos que ya tenía Drawer. Es el maskClosable de antd. kind="alert" sigue sin cerrarse al pulsar fuera.
    • Tooltip gana openOn ('hover' | 'focus' | 'press', por defecto ['hover', 'focus']). Con 'press' se alterna al pulsar y se cierra al pulsar fuera: es lo que la hace visible en una pantalla táctil. Un onClick propio del elemento se respeta.
    • Dropdown gana openOn ('press' | 'contextmenu'). Con 'contextmenu' se abre con el clic derecho y el menú del navegador no sale. No hay 'hover' a propósito: el panel es modal.
    • No hay zIndex ni contenedor de portal, y no es un olvido: las capas van en la capa superior del navegador, que queda por encima de cualquier z-index (medido con 2147483647) y funciona dentro de un shadow DOM. Un panel que se abre desde dentro del modal de otra librería vive en ese modal, así que sigue siendo pulsable.
  • Estado de carga (AMS-1552). loading (y loadingText, para lo que dice) llega a los componentes que no lo tenian: Switch, Select, MultiSelect, SearchField, SelectableList, VirtualList, Dialog y Drawer.
    • Switch: mientras espera la confirmacion NO cambia de valor ni avisa, pero sigue en el orden del tabulador (apagarlo sacaria el foco), con un girador en el tirador.
    • Select, MultiSelect, SearchField: el campo se marca ocupado (aria-busy), lleva un anillo y el panel enseña una fila Cargando…. Las opciones que ya hubiera se quedan.
    • SelectableList, VirtualList: ocupadas, con la fila debajo (scroll infinito).
    • Dialog, Drawer: el contenido se cambia por un esqueleto y se anuncia; el titulo y los botones se quedan.
  • Spinner gana delay (no sale hasta pasado ese tiempo, para que una carga de 100 ms no parpadee) y puede envolver un bloque con children: lo deja en gris, inerte y con el girador encima.
  • inputRef en TextField, PasswordField y TextArea: el nodo del <input> o del <textarea> de verdad, no el de la caja. Para enfocar un campo al abrir una edicion en linea, seleccionar un token que se va a copiar o devolver el foco a un buscador. Acepta objeto y funcion, y convive con autoFocus (el atributo sigue saliendo al DOM). Era lo que impedia migrar los campos de amset-assistant que se enfocan a mano.
  • El id de un campo llega a su control (AMS-1488). TextArea, TimeField, DatePicker y DateRangePicker lo aceptaban y lo tiraban; MultiSelect y SearchField lo dejaban en el <div> de fuera. Ahora, en un campo con <input> el for de su etiqueta lo apunta, y en uno cuyo control es una fila de segmentos (fecha, hora, rango) cae en el propio grupo.
  • PhoneField, QuantityPicker, TimeField, DatePicker y DateRangePicker admiten id en el tipo: en ejecucion ya lo reenviaban, pero el compilador no dejaba escribirlo.

0.7.0, del 13 de octubre de 2026

Todos los componentes dejan pasar los atributos de su elemento (AMS-1549). Antes lo hacían 14 de 158; ahora lo hacen todos los que pintan DOM. Es un cambio aditivo: medido contra la 0.6.2, no se ha quitado ni cambiado el tipo de ninguna prop, así que actualizar no debería romper la compilación de nadie.

### Qué se puede hacer ahora

<Tag id="estado-pago" title="Pagada el 12/10" data-testid="etiqueta" style={{ marginInlineStart: 8 }}>
  Pagada
</Tag>

Cualquier atributo del elemento que el componente pinta en su raíz: id, title, lang, data-*, aria-*, style y los manejadores de ratón y teclado. Dos reglas que conviene saber:

  • className se suma a las clases de la biblioteca y style se mezcla con el del componente. Ninguno de los dos lo sustituye.
  • En un campo (TextField, Select, NumberField…) los atributos van al control —ahí los espera quien escribe un data-testid o un autoComplete—, y className y style van a la caja, que es lo que se ve.

También se exporta DomProps<'div'>, para tipar un envoltorio propio sin enumerar atributos a mano.

### Lo que NO se deja pasar, y por qué

Hay props del elemento que un componente pone él mismo, y una de fuera se aceptaría y se descartaría en silencio. En esos casos el tipo no las admite, así que te enteras al compilar en vez de comprobar que no pasa nada:

ComponenteNo admiteUsa
Dialog, Drawer, IdleWarning, CommandPaletteonCancel, onClose, onClick, role, aria-labelledby, aria-describedbyonOpenChange, title, description
Dropdown, ConfirmAction, ShareButton, NotificationTray, ActionMenuid, role, tabIndex, onKeyDown, onClick, aria-label, aria-labelledby, aria-describedbytitle, onOpenChange
Tooltipid, role—
OverlayCloseButtononClickonPress
Checkbox, SwitchonKeyDown, onKeyUp, onPointerEnter, onPointerLeave, onPointerDownenvolver en tu propio elemento
CarouselonFocus, onBlur—
Tree, SelectableList, VirtualList, SortableListonKeyDown, onFocus, onBlur, role, tabIndex—
DocumentField, IbanFieldonBlur—

### Otros cambios

  • En un campo de texto, un aria-describedby de fuera se suma a la ayuda y al error en vez de pisarlos (TextField, TextArea, PasswordField).
  • Card separa en el tipo lo que gobierna cada forma (estática, pulsable, enlace); no cambia lo que aceptaba.
  • FieldRootPassThrough y FieldInputPassThrough aceptan ahora style.
  • Tres pruebas vigilan el resultado en la propia biblioteca: que el atributo llegue, que caiga en la raíz y no en una pieza interna, y que un manejador de fuera se llame.

Antes de 0.7.0

No había registro de cambios. Las versiones anteriores están en el historial de GitLab.