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.35.0. 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.35.0, del 3 de noviembre de 2026

  • Table: varios rangos de celdas sueltos a la vez (AMS-2351). Con cellSelection, Ctrl/Cmd+clic añade otro rango sin soltar los que hay, y sobre una celda ya elegida la quita (su rango queda partido en lo que la rodeaba); Mayús+F8 empieza otro rango desde la celda enfocada, y Mayús+flechas o Mayús+clic estiran el último. Los rangos que se tocan se ven como uno, con el contorno por fuera. Ctrl/Cmd+C copia varios rangos solo si comparten filas (van lado a lado) o columnas (uno debajo de otro), como en Excel; si no cuadran, no se toca el portapapeles y se dice por qué. Un valor suelto pegado rellena todas las celdas de todos los rangos, y varias celdas no se pegan sobre varios rangos. onCellRangeChange sigue avisando del rango activo (el último) y onCellRangesChange da la lista entera. Sin Ctrl ni F8, todo queda como antes. Y el contorno del rango se ve por fin: el borde a cero de la regla general ganaba por especificidad a los 2 px de cada lado y solo se pintaba el fondo (medido en Chromium; una prueba de navegador lo fija). Y se ve bien también en oscuro: el relleno es ahora --amset-accent-soft, el mismo de la fila marcada (antes, un 12 % de acento que en oscuro casi no se distinguía de la fila), y el contorno --amset-accent-text (el acento a secas daba 2,9:1 frente a la fila en oscuro; ahora 7,1:1 en claro y 8,6:1 en oscuro).
  • Table: salir de una edición que no se puede guardar, rango sin grupos plegados, cuenta de filas paginadas, error de carga, elegir todo lo filtrado, pegado por lotes y ajustar el ancho al contenido (AMS-2351). Si se sale de una celda en edición con algo que no vale (o el guardado falla), la edición se descarta y la tabla dice por qué; antes el campo se quedaba abierto con el error y el foco en otro sitio, donde ni Intro ni Escape le llegaban. Con el foco dentro, el error se sigue enseñando para corregirlo. Mayús+flechas ya no cae en las filas de un grupo plegado. Paginada, la rejilla lleva aria-rowcount con el total y cada fila su aria-rowindex real (con pagination.total, el del servidor). error (con true, el texto de serie) sustituye a las filas por un aviso y onRetry le pone «Reintentar». Con la página entera marcada y más filas en otras páginas sale «Elegir las N filas», como en el correo. onCellsEdit recibe en una sola llamada todo lo que se pega. Doble clic en el tirador de una columna la ajusta a lo más ancho de su contenido.
  • DataImportWizard: importar por tandas con avance y cancelar, CSV en UTF-16, hojas ocultas, celdas combinadas y errores de fórmula (AMS-2352). Con batchSize, onImport se llama una vez por tanda, con una barra de avance y «Cancelar la importación»; su segundo argumento trae batch, batches y una signal que se dispara al cancelar. Si falla o se cancela una tanda que no es la primera, el resultado dice cuántas filas entraron y cuántas quedan. Cambio: onImport recibe siempre ese segundo argumento, también sin tandas ({ batch: 1, batches: 1, signal }); una prueba que compare la llamada con un solo argumento tiene que añadirlo. Los CSV en UTF-16 (el «Texto Unicode» de Excel), con o sin marca, se leen bien. Una hoja oculta del libro se marca «(oculta)» y no es la que se abre. El valor de una celda combinada se copia a las filas con datos que cubre. Una celda con #DIV/0!, #N/A y demás errores de Excel es un error de la fila, no un dato.
  • FileManager: subir de uno en uno con cancelar y reintentar, resultado por elemento al borrar o mover, tamaño máximo y nombres repetidos (AMS-2353). onUploadFile sube cada fichero por separado: sale en una lista con su barra, se puede cancelar (se dispara la signal) y, si falla, se queda con el motivo para reintentarlo o quitarlo. onDelete y onMove pueden devolver { failed: [{ id, message }] }: lo que falló se nombra y se queda elegido, y lo demás se da por hecho. maxSize (en bytes) y rejectDuplicates dejan fuera lo que pasa del tamaño o se llama igual que algo de la carpeta, también con el botón, y se dice cuántos y por qué. screenUploads hace ese filtro fuera del componente.
  • SchemaForm: listas de posiciones fijas de draft-07, objetos sin campos, valores que llegan tarde, «sin cambios» tras guardar, claves con punto o numéricas, y error y foco de una lista (AMS-2354). items: [a, b] con additionalItems (draft-07, OpenAPI 3.0) se lee como prefixItems y se pinta un campo por posición. Un objeto sin nada que pedir ya no pinta un grupo vacío. Si initialValues cambia (datos que llegan de una petición), el formulario los toma sin pisar lo que ya se cambió a mano. Un envío que sale bien cuenta como guardado: onChange vuelve a dirty: false. Una propiedad llamada version.mayor o 2024 se edita y se envía en su sitio; antes react-hook-form la leía como ruta. El error de una lista describe su grupo (aria-describedby) y al quitar una fila el foco pasa a la que ocupa su sitio, o a «Añadir» si no queda ninguna.
  • DataImportWizard: la fila de cada error es la del fichero, tope de descompresión y avisos que dicen el motivo (AMS-2352). Los errores citan la línea real del CSV (contando las líneas en blanco y las de dentro de un campo entre comillas) y la fila de Excel, y una corrección se escribe en la fila que toca aunque haya huecos antes. Un .xlsx con una celda en la última fila de Excel ya no crea un millón de filas vacías en memoria, el ZIP tiene un tope de 256 MB descomprimidos y un fichero de más de 64 MB dentro del libro rompe la lectura en vez de saltarse en silencio (antes un sharedStrings enorme dejaba las celdas vacías). Si onImport falla, el aviso dice su motivo; la revisión deja ver más filas, de 50 en 50; avisa si una misma columna alimenta a dos campos; con dos ficheros elegidos seguidos gana el último, y al terminar el foco pasa al resultado.
  • FileManager y MediaManager: lo soltado pasa por accept, barra de varias subidas, atajos y «Cargar más» (AMS-2353). Lo que se suelta encima pasa el mismo filtro que lo que se elige con el botón y se dice cuántos ficheros se quedan fuera. Con dos subidas a la vez, la primera que acaba ya no esconde la barra de la otra: la barra da la media de las que siguen. nameProblem rechaza los nombres reservados de Windows (CON, NUL, COM1…), los acabados en punto y los de más de 255 caracteres. En FileManager, Ctrl+A elige lo que se ve, Supr pide borrar con su confirmación, F2 renombra la entrada con el foco y Mayús+clic elige un rango; la confirmación dice qué se borra, no solo cuántos, y hasMore con onLoadMore pone «Cargar más». El onKeyDown de fuera se sigue llamando.
  • Table: vacíos al final en los dos sentidos, selección por página y sin filas ocultas, búsqueda en árbol y fechas locales al exportar (AMS-2351). Los vacíos (y un NaN) van al final también en orden descendente, en la tabla y en el árbol. La casilla de la cabecera marca o desmarca solo la página, sin borrar lo elegido en otras, y lo que el filtro, la búsqueda o los datos esconden deja de contar como elegido: las acciones nunca reciben filas que no se ven (con datos del servidor no se recorta). searchable busca también en las filas de un árbol, y con serverSide o la paginación del servidor el cuadro solo sale con onSearchChange, que avisa del texto. Una columna que llega después de montar sale a la vista (salvo hiddenByDefault) y pagination.size se aplica también después de montar. Un guardado de celda que rechaza con algo que no es un Error ya no deja el editor ocupado. Cambio de formato: el CSV exporta las fechas con hora en la hora local y con su desfase (2026-02-01T11:30:00+01:00), como se ven en pantalla, en vez de en UTC.
  • SchemaForm y su validador: patrones que cuelgan, multipleOf con importes, palabras que se ignoraban y envíos que fallaban en silencio (AMS-2354). Un pattern con una repetición anidada sin separador ((a+)+, (\w+\s?)*) se rechaza como SchemaFormError: con 26 letras tardaba 27 segundos en cada tecla. multipleOf 0,01 vale ya para importes grandes. Una propiedad heredada (constructor) no cuenta como presente. Se validan patternProperties (y additionalProperties: false ya no rechaza lo que casa), propertyNames, minProperties, maxProperties, prefixItems, contains, minContains, maxContains y nullable. Un $ref con properties o required al lado suma los dos. Cambio de clave: los errores generales del esquema van a root (ROOT_FIELD), la misma que los del servidor; antes iban a raiz. Un envío que lanza se dice en el formulario, cambiar de rama de un oneOf conserva los campos comunes y una fila nueva de una lista de objetos llega con sus default.

0.34.0, del 2 de noviembre de 2026

  • MediaManager: soltar ficheros encima, «Cargar más» y barra de avance; FileManager con barra de avance (AMS-2328). onUpload recibe ahora un segundo argumento, onProgress: llámalo con un valor de 0 a 100 y el gestor enseña una barra con el avance hasta que la subida acabe o falle (las funciones que no lo usan siguen funcionando igual). MediaManager acepta ficheros soltados sobre la biblioteca, con el borde marcado mientras se arrastra, y con hasMore y onLoadMore pone el botón «Cargar más» al final de la cuadrícula para pedir la siguiente tanda al servidor.
  • SchemaForm: elegir la rama de un oneOf o anyOf de objetos sin campo que las distinga (AMS-2327). Cuando las ramas son objetos con campos propios y ninguna se reconoce por un valor const, el formulario pone un desplegable «Tipo» (con el title de cada rama) y enseña solo los campos de la elegida; al cambiar de rama se vacían los de la anterior para que no choquen al validar ni se envíen.

0.33.0, del 2 de noviembre de 2026

  • Selectores con miles de opciones: solo se pintan las que se ven, y la barra sigue a la opción activa con el teclado (AMS-2326). Select, MultiSelect y ComboBox comparten la lista, que con más de 300 opciones de una sola línea (sin grupos, sin segunda línea y sin renderOption) pinta unas veinte filas y deja el sitio de las demás con el relleno de la propia lista. Las flechas, Re Pag, Av Pag, Inicio, Fin y escribir para saltar llevan la barra hasta la opción activa aunque no esté pintada, y al reabrir la elegida se ve. Cada fila lleva aria-posinset y aria-setsize para que el lector diga «1.234 de 5.000». Con grupos, descripciones o renderOption no se recorta nada, porque el hueco supone filas del mismo alto. Nueva historia «Lista larga» y e2e/select-long-list.spec.ts, medido en Chromium con 5.000 opciones: de 15 a 22 filas pintadas y la activa siempre a la vista.

0.32.0, del 1 de noviembre de 2026

  • Calendar: saltar de mes y de año desde el título con monthJump (AMS-2325). Con monthJump el título del mes es un botón que abre una vista con los doce meses del año; pulsando el año se ve un bloque de doce años. Los botones de los lados cambian de año o de bloque, los meses fuera de minValue y maxValue salen apagados y elegir uno devuelve la rejilla en ese mes (y avisa con onFocusChange). Solo con un mes a la vista y en calendario gregoriano; en otro, o con varios meses, el título sigue siendo texto.
  • MultiSelect: modo casillas y tope de opciones pintadas; TreeSelect: búsqueda (AMS-2326). checklist deja la lista abierta mientras se marca, enseña lo elegido con su marca (aria-multiselectable), quita una ya elegida al pulsarla y, al llegar a max, apaga las que faltan en vez de esconderlas. maxVisibleOptions pinta como mucho esas opciones en listas de miles y avisa de cuántas se ven. TreeSelect con searchable pone un cuadro de búsqueda que deja lo que coincide con el camino hasta ello abierto. El texto libre del ComboBox ya existía (allowsCustomValue). VirtualList no se ha metido dentro de las listas desplegables: el recorte opcional de maxVisibleOptions es lo que hay.
  • Table: editores de celda de fecha, casilla y propio, y validación con el servidor (AMS-2324). editor.type suma date (un selector de fecha que guarda AAAA-MM-DD), checkbox (guarda un booleano) y custom, con un render que recibe commit, cancel, error y saving. validateAsync comprueba lo escrito contra el servidor después de validate, con la celda ocupada y «Comprobando…», y si devuelve un motivo la edición se queda abierta con él; también se aplica a cada celda que se pega. CellEdit.value ahora puede ser un booleano (antes string | number | null): quien lo asigne a una variable de ese tipo tiene que ampliarla. Pegar entiende «sí», «no», «true» y «false» en una casilla, AAAA-MM-DD en una fecha y parse en un editor propio.
  • Board táctil, Timeline editable y DockLayout con el centro dividido (AMS-2333). En Board, el asa de cada tarjeta se arrastra con el dedo (eventos de puntero, touch-action: none, 44 px en pantallas táctiles) y suelta sobre otra tarjeta o sobre el hueco de una columna; el arrastre con ratón y el teclado no cambian. Timeline con onTaskChange hace las barras editables: se arrastra el cuerpo para mover la tarea, sus extremos para cambiar el principio o el final, y con el teclado las flechas mueven un día (Mayús, el final; Alt, el inicio), con aviso de lo que quedó; entrega las fechas nuevas y quien lo usa decide si las acepta. DockLayout suma una zona opcional split junto al centro (con su separador, plegado y la opción «Dividir el centro» en el menú del panel); una disposición escrita sin ella sigue valiendo. Un DockLayout dentro de un panel ya funcionaba y queda probado.
  • Button con preventFocusOnPress y Breadcrumbs con disabled (AMS-1401). preventFocusOnPress hace que pulsar con el ratón no le quite el foco al campo o a la selección que lo tuviera, para los botones de una barra de formato; el teclado no cambia. Breadcrumbs con disabled apaga toda la ruta: las migas dejan de ser enlaces, salen del tabulador y se leen como no disponibles.

0.31.0, del 1 de noviembre de 2026

  • WeekAgenda: citas de todo el día, de varios días y que cruzan la medianoche, y elegir un tramo arrastrando (AMS-2333). Una cita con allDay sale en una franja fija sobre la cuadrícula (no en la de horas) y, con endDate, ocupa varias columnas; las que se pisan se apilan en filas, y en la lista del móvil salen con «Todo el día». Una cita con horas y endDate (por ejemplo una guardia de 22:00 a 06:00 del día siguiente) sale partida en un trozo por día, bloqueados, y las que se repiten conservan su duración. onRangeSelect entrega el día y las horas de inicio y fin de un tramo elegido arrastrando por los huecos vacíos de una columna (con ratón o lápiz; con el dedo, arrastrar sigue desplazando la página) o con el teclado: Mayús y las flechas de arriba y abajo estiran el cursor de hueco, Intro entrega y Escape suelta. Un clic suelto sigue yendo a onSlotSelect. Todo es opcional y, sin las props nuevas, el DOM es el de antes.
  • Gráficas: leyenda que oculta series, valor pulsable, barras agrupadas y líneas de referencia (AMS-2330). En LineChart, AreaChart y StackedBarChart: toggleSeries convierte la leyenda en botones que ocultan o devuelven cada serie (el eje y los totales se recalculan; la tabla para el lector de pantalla conserva todas), con hiddenSeries y onHiddenSeriesChange para llevar el estado desde fuera; onPointPress hace pulsable cada valor del globo de una categoría, con teclado y dedo, y entrega serie, categoría y valor; referenceLines y referenceBands pintan objetivos, medias y rangos aceptables (el eje los incluye y el resumen para el lector los dice); y StackedBarChart grouped pone las barras una al lado de otra desde el cero. Todo es opcional y, sin las props nuevas, el dibujo y el DOM son los de antes.
  • Menús y pestañas: opciones con casilla y de opción única, y paneles que conservan su estado (AMS-2332). MenuAction suma type (checkbox o radio, con papel menuitemcheckbox o menuitemradio), checked y closeOnSelect (con false elegirla no cierra el menú, ni el submenú ni el de fuera); valen en ActionMenu, Menubar, ContextMenu y el desplegable de SplitButton, y el estado lo lleva quien los usa (para «Mostrar columnas» y «Ordenar por»). Tabs suma keepMounted: los paneles de las pestañas no elegidas siguen montados y ocultos (hidden), así que lo escrito en un formulario no se pierde al cambiar de pestaña; useTabs().panelProps(key, keepMounted) devuelve entonces los atributos del panel oculto. Por defecto no cambia nada. Pruebas en menu-checkable.test.tsx y tabs-keep-mounted.test.tsx. Queda fuera: NavigationMenu sin versión móvil.
  • Avisos y paleta de comandos: aviso ligado a una promesa, búsqueda aproximada, primer resultado marcado y búsqueda en el servidor (AMS-2331). useNotifications() suma promise(promise, { loading, success, error }), que enseña «cargando» y lo convierte en éxito o en error (el error interrumpe como cualquiera) y devuelve la misma promesa, y update(id, changes) para cambiar un aviso que sigue a la vista. CommandPalette suma searchMode (contains por defecto, fuzzy con letras salteadas y orden por parecido, none para no filtrar), onSearchChange (lo escrito en cada pulsación y '' al cerrar) y loading (lista ocupada y «Buscando…»). Cambio de comportamiento: al escribir queda marcado el primer resultado, así que Intro lo ejecuta sin pulsar antes una flecha; con el campo vacío no se marca nada. Pruebas en command-palette-search.test.tsx y notifications-promise.test.tsx.
  • ImageCropper y FileManager: zoom, giro, imagen recortada, árbol de carpetas y panel de detalles (AMS-2334). ImageCropper con onCrop añade «Recortar», que dibuja el rectángulo en un <canvas> y entrega la imagen recortada (un Blob, con outputType y quality; crossOrigin para una imagen de otro origen, y si el lienzo no se puede leer lo dice en vez de fallar callado); rotatable gira la imagen un cuarto de vuelta a cada lado y recorta sobre la imagen girada (entrega también la rotación); zoomable acerca hasta cuatro veces con un visor que se desplaza y el arrastre sigue contando en píxeles de la imagen. FileManager con tree pinta el árbol de carpetas a la izquierda (elegir una navega a ella; loadFolders para cargarlo a demanda) y con details el panel de detalles a la derecha con nombre, tipo, tamaño, fecha y miniatura de lo elegido (renderPreview pone tu propio visor); en una caja estrecha se apilan. Sin estas props, ambos son los de antes.
  • QueryBuilder y campos: valor según el tipo, exportar a SQL, MongoDB y texto, paginación sin total, céntimos y requisitos de contraseña (AMS-2329). En QueryBuilder, un campo con options elige el valor de un desplegable con «es» y «no es» y un campo de fecha usa el selector de fecha; exports enseña la consulta escrita, y queryToSql (con parámetros, ? o $1, y los nombres de columna solo salen de fields, rechazando lo que no sea un identificador), queryToMongo y queryToText la dan para usarla en el servidor, ignorando lo que matchesQuery ignora. Pagination con hasNext sirve a una API con cursor que no sabe cuántas páginas hay: solo «Página N» entre las flechas. MoneyField con unit="cents" recibe y entrega céntimos enteros (según los decimales de la moneda). PasswordField con rules enseña la lista de requisitos con su estado escrito («cumplido» / «pendiente»); common trae los de siempre. No entra todavía la multiselección de valores en QueryBuilder.
  • Subida e importación: PUT a URL firmada, miniaturas, ficheros ya subidos, corregir celdas al importar y listas de valores (AMS-2328). httpUpload con PUT y sin data ni fieldName manda ya el fichero tal cual como cuerpo (antes mandaba un formulario multipart, que una URL firmada de S3 rechaza); body fija multipart o raw a mano. Uploader gana previews (miniatura de cada imagen elegida, liberada al quitarla) y existingFiles con onRemoveExisting para enseñar los ficheros que ya estaban subidos, con su descarga; FileList admite preview y removable por fila. En DataImportWizard, las celdas con error se corrigen en la propia revisión y la fila se valida de nuevo, las filas con error se descargan en un CSV con su línea y el motivo, y el nuevo tipo list con options traduce el texto del fichero («Alta», «activo») al valor de la aplicación. No entran todavía la subida por partes con progreso en FileManager y MediaManager, soltar ficheros ni paginación en MediaManager.
  • SchemaForm: widgets propios, onChange con estado de cambios, validación propia y asíncrona por campo y listas reordenables (AMS-2327). widgets registra tus propios campos por nombre (ui={{ color: { widget: 'swatch' } }}); el widget recibe su valor, su error, el esquema y los datos de todo el formulario con setValue para escribir en otro campo. onChange(values, { dirty }) avisa en cada cambio (no al montar) y dice si hay cambios sin guardar, comparando con los valores de partida. validate añade una regla propia, también asíncrona (comprobar en el servidor que un código está libre), que solo se llama cuando el esquema ya da el campo por bueno y que admite lines.*.sku para cada elemento de una lista; si lanza, el error sale en el campo. En una lista, reorderable añade «Subir» y «Bajar» y duplicable añade «Duplicar» (respeta maxItems). Todo es opcional: sin estas props el formulario es el de antes. No entra todavía elegir la rama de un oneOf/anyOf sin discriminante.

0.30.0, del 30 de octubre de 2026

  • Cuatro bloques nuevos: sign-up, invoice-list, team-members y pricing (AMS-2301). El alta de cuenta (nombre, correo, contraseña escrita dos veces con su fuerza, condiciones de uso opcionales y el error del servidor), el listado de facturas (lo pendiente y lo vencido arriba, buscar, filtrar por estado, ordenar, paginar, marcar varias y exportar), el equipo (roles, quitar acceso con confirmación e invitar) y la comparativa de planes (pagar cada mes o cada año, ahorro, plan recomendado y plan actual). Se copian con npx amset-ui add. Con ellos son once; el tope de la prueba de bloques pasa de diez a catorce.
  • MultiSelect y PeoplePicker ya marcan el campo como no válido con invalid solo (AMS-2301). Calculaban invalid pero después miraban solo error, así que invalid sin mensaje no ponía ni data-invalid ni aria-invalid. invalid={false} ahora también gana a un error, como en Select y SearchField.
  • El style de fuera ya llega a ImageCompare, ReadingProgress, Thumbnail, WeekAgenda, LineChart, AreaChart y StackedBarChart (AMS-2301). Los tipos lo admitían, pero cada uno ponía su propio style después del reenvío y lo borraba. Se mezcla con el del componente; en WeekAgenda y los gráficos de series las variables propias del componente ganan, porque sin ellas se descuadra el dibujo.
  • Un Calendar de solo lectura se anuncia como tal (AMS-2301). La rejilla lleva aria-readonly cuando readOnly está puesto; antes solo dejaba de dejar elegir y un lector de pantalla no lo sabía.
  • src/components/props-contract.test.tsx: el contrato de las props sobre las 200 y pico fichas (AMS-2301). Enumera con el compilador cada prop cerrada de cada componente (unas 1.100 props y 3.100 valores), las pinta una a una y exige que ninguna haga caer al componente; que las del vocabulario común (size, tone, variant, invalid, loading...) cambien algo de lo pintado o estén justificadas con su motivo; y que className se una a las clases del componente y style llegue al DOM. Comprobado que muerde: revertido el arreglo de invalid, falla señalando multi-select.invalid.

0.29.0, del 29 de octubre de 2026

  • Table: elegir las columnas al exportar y exportar por tandas del servidor (AMS-2274). En exportCsv y exportXlsx, columns fija qué columnas salen y en qué orden, y chooseColumns abre un panel con una casilla por columna antes de bajar el fichero. Con loadRows la tabla pide las filas al servidor tanda a tanda (empezando en 0, hasta que hasMore sea falso), enseña cuántas lleva cargadas con un botón para cancelar y baja todas juntas; si el servidor falla o se cancela, se dice y no se baja nada. Salen sueltos los tipos ExportPage y LoadExportPage.

0.28.0, del 29 de octubre de 2026

  • DockLayout: espacio de trabajo con paneles acoplables (AMS-2259). Cuatro zonas con pestañas; cada panel se mueve a otra zona desde su menú o arrastrando su pestaña y se cierra desde el mismo menú; los lados y la zona de abajo cambian de tamaño con un separador que también se mueve con las flechas, y se pliegan. La disposición se crea con createDockLayout, se cambia con funciones puras y se puede controlar para guardarla.
  • MediaManager e ImageCropper: biblioteca multimedia y recorte de imágenes (AMS-2259). La biblioteca busca, filtra por tipo, sube, elige uno o varios, marca las imágenes sin texto alternativo y, del elegido, edita título, alternativo, pie y etiquetas, recorta y borra con confirmación. El recortador mueve y redimensiona la caja con ratón o teclado, con proporción libre o fija, y entrega el recorte en píxeles de la imagen original.
  • FileManager: explorador de ficheros (AMS-2258). Ruta con migas, búsqueda sin acentos, lista ordenable o cuadrícula con las carpetas delante, elegir varios, crear carpeta y renombrar con el nombre comprobado, mover con «Mover a» o arrastrando a una carpeta, borrar con confirmación y subir con el botón o soltando ficheros encima. Cada acción es una función de la aplicación; si falla, se dice y no se pierde lo elegido.
  • DashboardBuilder: panel de mando que cada persona ordena (AMS-2258). Piezas en una rejilla de 12 columnas que, en modo edición, se mueven arrastrando su asa o con las flechas, cambian de tamaño desde la esquina o con Mayúsculas y las flechas, se quitan y se añaden del catálogo; al mover una, las demás se apartan y todo sube. Cada movimiento anuncia dónde queda la pieza y cuánto mide.
  • WorkflowBuilder y AgentWorkflowEditor: editor de flujos de nodos y conexiones (AMS-2258, AMS-2259). Un lienzo con desplazamiento y zoom, nodos que se arrastran o se mueven con las flechas, conexiones que se validan (tipos de dato, entradas ocupadas, círculos), problemas del flujo escritos, «Ordenar» por capas, el estado de cada nodo en una ejecución y un panel para conectar y quitar conexiones sin ratón. AgentWorkflowEditor trae los pasos de un agente de IA (mensaje, modelo, herramienta, condición, aprobación humana y respuesta) traducidos.
  • Subidas por partes, reanudables, con reintentos y pausa (AMS-2273). chunkedUpload sube un fichero por partes (5 MiB por defecto), pregunta al servidor por dónde iba para reanudar tras un corte o una pausa y reintenta cada parte con espera creciente; retrying añade reintentos automáticos a cualquier subida. useFileUploads gana pause, resume, pauseAll, resumeAll, el estado paused, retries y el avance total (progress), y Uploader tiene pausable con la barra de todos juntos y los botones de pausar y reanudar.
  • NotificationCenter, useNotificationCenter y onShow en NotificationProvider (AMS-2275). El centro de avisos con el historial agrupado por día o por tipo, filtro de no leídos, marcar leído o no leído, borrar uno o todos con confirmación y acciones propias de cada aviso; el gancho que lo guarda en el navegador; y onShow para que los avisos efímeros queden en el historial.
  • AddressField propone direcciones mientras se escribe (AMS-2276). Con suggest (el buscador de tu proveedor de mapas o de tu servidor), la calle pide propuestas a partir de tres letras y tras 300 ms sin escribir, cancela la petición vieja al seguir escribiendo y, al elegir una, rellena todos los campos. Si el buscador falla, se dice y se sigue a mano. Sin suggest, la calle es la de siempre.
  • RecentSearchList y useRecentSearches: búsquedas recientes y ámbitos (AMS-2278). Las últimas búsquedas guardadas en el navegador y compartidas entre componentes, una lista para repetirlas, quitarlas o borrarlas bajo un buscador vacío, y parseScopedQuery para buscar en un ámbito con #pedidos 1042.
  • ShortcutsProvider, useShortcut y KeyCombo: atajos de teclado de toda la aplicación (AMS-2277). Un solo escuchador con registro de atajos, ámbitos que ganan a los globales, detección de choques, ayuda con Mayús+? y atajos que cada persona cambia desde la ayuda pulsando la combinación nueva; KeyCombo pinta un atajo como teclas de Mac o de PC.
  • AgentApprovalDialog, AIUsageDashboard y Excel en Table (AMS-2279, AMS-2280, AMS-2274). El diálogo para aprobar o rechazar la acción de un agente de IA (herramienta, datos, cambios, riesgo, comentario al rechazar, datos editables, «permitir siempre» y caducidad); el panel de consumo de IA (coste, tokens de entrada y salida, coste por día y modelo, desglose por modelo y topes con aviso); y exportXlsx en Table, que baja un .xlsx de verdad con números y fechas con su tipo, más selectedOnly para exportar solo las filas elegidas en CSV o en Excel.
  • PromptTemplateEditor, DragSelectArea, UserRoleEditor, OrganizationTree y DataMappingEditor (AMS-2259). Editor de plantillas con variables {{name}} (insertar, valores de prueba, vista previa y estimación de tokens); selección de varios elementos arrastrando un rectángulo, con Control+A y Escape; personas y roles (cambiar, buscar, quitar con confirmación); organigrama en cajas o en lista con teclado de árbol; y emparejado de campos entre un origen y un destino con transformaciones, avisos y applyMapping.
  • SmartFilterBar, SavedViewsManager y BulkEditPanel (AMS-2259). La barra de filtros de un listado (búsqueda, filtros rápidos, consulta avanzada con el QueryBuilder en un panel y quitar todo), con smartFilter para aplicarla en el cliente; las vistas guardadas (elegir, guardar nueva o encima de la activa, renombrar, por defecto y borrar con confirmación); y la edición masiva de los registros elegidos (no cambiar, poner un valor o vaciar, con resumen antes de aplicar).
  • SLAIndicator, useAutosave y ApprovalFlow (AMS-2259). SLAIndicator dice cuánto queda de un plazo de servicio con la barra de lo gastado y la fase escrita (en plazo, por vencer, vencido, en pausa, cumplido, incumplido). useAutosave guarda un valor cuando deja de cambiar, de uno en uno y sin reintentos en bucle, y AutosaveIndicator cuenta en qué punto está. ApprovalFlow pinta una cadena de aprobación con sus pasos, sus aprobadores y lo que decidieron, y recoge aprobar o rechazar con motivo de quien le toca.
  • DataImportWizard: importar un CSV o un Excel (AMS-2256). Un asistente de tres pasos (fichero, columnas y revisión) que lee el fichero en el navegador, sin subirlo, y emparejado con los campos de la aplicación entrega las filas ya convertidas y validadas. Detecta el separador, la codificación y las fechas de Excel; lee .xlsx con un lector propio (un ZIP de XML) que solo se carga al elegir un libro. Valida tipo, obligatorios, duplicados (unique) y reglas propias (validate); las filas con errores se saltan o impiden importar (allowPartial), y el resultado cuenta las importadas, las saltadas y las que rechazó el servidor. La validación es de formato: el servidor vuelve a validar.
  • QueryBuilder: consultas con grupos anidados (AMS-2255). Condiciones «campo, operador, valor» unidas por «y» o por «o», con grupos dentro de grupos (hasta cinco niveles) y la posibilidad de negar un grupo. Entrega un árbol de datos (QueryGroup) y filterByQuery / matchesQuery lo aplican a una lista de registros; una condición a medias no filtra nada. La lógica de cada condición (operadores por tipo, comparación de texto sin acentos, números y fechas) se sacó de los filtros avanzados de Table a un módulo compartido, así que los dos se comportan igual.
  • DiffViewer y AuditLogViewer: comparar y auditar (AMS-2254). DiffViewer compara dos textos o dos JSON, uno tras otro o lado a lado, línea a línea y palabra a palabra; las líneas iguales lejos de un cambio se pliegan en un botón y cada línea cambiada lleva un signo y un texto escondido, así que el cambio no depende del color. AuditLogViewer es el historial de cambios de algo: quién hizo qué, sobre qué y cuándo, agrupado por día, con búsqueda, carga por tandas y el cambio de cada campo desplegable con su comparación. Con onRevert cada entrada lleva un botón de revertir con confirmación; la biblioteca no deshace nada, el servidor tiene que comprobarlo de nuevo.
  • PermissionGate y PermissionMatrix: permisos en la interfaz (AMS-2253). PermissionProvider reparte lo concedido (una lista con comodines como invoices.*, o una función con tu política) y PermissionGate muestra, oculta o apaga un trozo de interfaz según ello; apagar es un <fieldset disabled> de verdad, así que el navegador desactiva cada control de dentro, y el motivo se lee. usePermission y usePermissions dan la misma respuesta en código. Mientras los permisos cargan no decide nada, y sin proveedor no concede nada y lo avisa en consola. PermissionMatrix es la matriz de un rol: módulos en filas, acciones en columnas, casillas de fila, columna y esquina con estado intermedio, celdas bloqueadas que no cambian ni al marcar su fila, una sola parada del tabulador con flechas y cada cambio anunciado. Ocultar algo en pantalla no lo protege: el servidor sigue comprobando.

0.27.0, del 28 de octubre de 2026

  • Cinco componentes que los fronts de la casa se habían tenido que escribir (AMS-736). AnimatedNumber, una cifra que recorre el camino de la anterior a la nueva (el lector recibe solo la final; con prefers-reduced-motion cambia de golpe). LiveClock, la hora en vivo de la zona que se pida, que avanza en punto leyendo el reloj del sistema. PeoplePicker, elegir a varias personas con avatar, presencia y una línea de debajo; los conectados primero y sin ofrecer a quien ya está (exclude). VideoPlayer, un vídeo con los controles propios del reproductor de audio más sonido, subtítulos WebVTT, velocidad, pantalla completa y teclado (Espacio, flechas, M, C, F). Y EmojiPicker, con buscador, categorías, recientes y tono de piel: el catálogo (casi dos mil emoji con nombre y palabras clave en español e inglés) no viaja con el componente, se pide al abrirlo (unos 37 KB comprimidos) o antes con preloadEmoji(); EmojiPanel va suelto para un panel propio.
  • SideNav dentro de un Drawer conserva sus etiquetas en pantalla estrecha (AMS-2022). Por debajo de 40 rem el menú se plegaba a baldosas de iconos aunque fuera dentro de un panel desplegable, donde hay sitio de sobra, y el menú móvil se quedaba sin nombres. Dentro de un Drawer ya no se pliega, y responsive={false} lo pide a mano en cualquier otro sitio.
  • El bloque SignIn puede abrirse con un error (AMS-2217). Nueva prop error: el motivo con el que empieza la pantalla (la sesión caducó, el enlace de recuperar ya se usó), arriba del formulario, que se quita al volver a enviar. La historia «Credenciales que no valen» lo usa y ya enseña el error en su captura.
  • amset-ui theme repara el contraste que rompe un color forzado (AMS-2154). Un token escrito tal cual con colors (el rojo exacto del asistente como error en oscuro) dejaba su derivado de al lado (error-soft) calculado para el valor de la biblioteca, y el texto se quedaba en 3,48:1 con solo un aviso. Ahora el derivado de la misma familia se mueve hasta que el par llega a su mínimo y la CLI dice de dónde a dónde; lo que no se puede arreglar sin tocar otra familia sigue avisando. Nueva función repairContrast en @amset/ui/styles/palette.
  • El reproductor de audio entra en el editor con un sonido que existe. Su valor de partida apuntaba a un fichero de ejemplo (ejemplo.mp3) que no existe en ningún sitio y daba 404, y el reproductor entraba roto. Ahora apunta a ejemplo.wav, un tintineo corto sin derechos de nadie que aloja la web de la biblioteca. Una dirección data: no vale para esto: el editor descarta cualquier src que empiece por data: (salvo en imágenes). scripts/audio-default.test.ts fija la dirección.
  • DateTimePicker controlado ya no pierde la fecha si empieza vacío. Con value={null} (o "", como empieza un formulario de react-hook-form), al elegir el día se juntaba con una hora que aún no había, salía «sin valor» y, como el campo estaba controlado, no se guardaba en ningún sitio: la fecha se perdía antes de poner la hora y no avisaba nunca, así que el formulario se quedaba vacío. Ahora la mitad que ya se ha elegido se guarda dentro mientras falta la otra y avisa en cuanto están las dos. Si desde fuera lo vacían después de completarlo (un reset), se queda vacío. src/components/date-time-picker.test.tsx lo fija.

0.26.0, del 27 de octubre de 2026

  • La barra que sale sobre un texto elegido ya no tapa la barra de formato. Con la primera línea elegida salía encima de la selección, porque cabía dentro del marco del editor y el marco incluye la barra de formato: en un móvil tapaba justo la negrita y no se podía pulsar. Ahora solo sale encima si cabe dentro de la zona del texto; si no, debajo. e2e/rich-text-mobile.spec.ts lo fija.
  • El editor ya no se rinde al escribir deprisa. Teclear más de 50 teclas seguidas de golpe (una expansión de texto, un dictado, una tecla repetida) hacía que React lanzara «Maximum update depth exceeded»: lo que quedaba por hacer con esa tecla, entre ello colocar el cursor, no se hacía y el resto del texto salía por el principio del párrafo. Venía de dos efectos que programaban un setState en cada cambio del documento (la limpieza de bloques propios y el cierre del menú de ortografía) aunque no hubiera nada que cambiar; ahora solo lo programan si hay algo. Medido: 1.000 letras de golpe, sin errores y todas en su sitio. e2e/rich-text-ux.spec.ts lo fija.
  • @amset/ui/rich-text: los componentes con contenido (tarjeta, aviso, rejilla, pila…) se ven de verdad dentro del editor. Hasta ahora eran un marco punteado con su nombre y el texto de dentro: el título, el tono, el icono o el borde que se ponían en la ficha no se veían hasta leer el documento, y parecía que el componente no hacía caso. Ahora el editor pinta el componente real, con su aspecto, y su contenido es texto del documento escrito dentro de él, con párrafos, listas, el menú «/», borrar y deshacer como en cualquier otro sitio. Cambiar una propiedad se ve al momento. Un clic en su aspecto (no en el texto) lo elige entero para moverlo, borrarlo o abrir sus propiedades, y el doble clic o el botón derecho abren la ficha. En solo lectura el contenido no se puede escribir. Una rejilla o una pila reparten lo que se escribe en columnas al editar igual que al leer, y si el componente acaba de descargarse cuando ya se estaba escribiendo dentro, el cursor se queda donde estaba. Lo que se escribe en el contenido sigue siendo un documento, igual que antes: lo único que cambia es cómo se ve al editar. e2e/rich-text-components.spec.ts y src/rich-text/view/framed-component.test.ts lo fijan.
  • La ficha de propiedades de un componente ya no tiene campos en inglés ni los que solo sirven programando. Medido sobre los 157 componentes: 81 propiedades y 146 valores de listas cerradas salían con el nombre en inglés («Close label», «Raised», «Min height», «Secondary») y también salían las opciones de formato de Intl, las funciones y los nombres de campo de un formulario. Ahora todo lo que se enseña tiene su texto en castellano y en inglés, y lo técnico se esconde. Una prueba (src/rich-text/amset/prop-labels.test.ts) recorre todos los esquemas y falla si una propiedad o un valor nuevos salen sin nombre.
  • SchemaForm: un grupo opcional que nadie ha rellenado ya no pide sus campos obligatorios. Un objeto opcional con campos obligatorios (la fecha mínima de un selector de fechas: año, mes y día) se trataba como un objeto al que le faltaban tres campos, así que el formulario no se podía enviar sin inventarse una fecha. Ahora un grupo opcional con todos sus campos en blanco (o hecho solo de grupos vacíos y listas sin filas, como el filtro avanzado de una tabla) es «no hay valor» y no sale en el resultado; uno a medias sigue pidiendo lo que falta y uno obligatorio vacío sigue diciendo que faltan sus campos. Así «Aplicar» funciona en la ficha de propiedades de DatePicker, Calendar, DateRangePicker, TimeField, Rating, Steps, Timeline, QuickRange y Table.
  • Una función obligatoria que el documento no trae ya no hace fallar al componente. ColorPanel llama a su onChange al montarse y en un documento no hay quien lo escuche: salía t is not a function. Una función obligatoria sin acción elegida es ahora una que no hace nada, y la ficha tampoco la exige (al pintar ya no contaba como falta). VisuallyHidden sale del catálogo de componentes (157): no se ve, así que en un documento no se podría ni elegir.
  • El menú «/» del editor (y el de menciones y el de ortografía) ya no se queda fuera de la pantalla. Con el cursor cerca del borde de la ventana, o fuera de ella (la página se desplazó con la lista abierta), la lista se colocaba por debajo del borde: la opción activa bajaba con las flechas, pero dentro de un trozo de lista que no se veía, y al llegar al final no se veía nada. Ahora la lista cabe siempre entera en la ventana: se pega al borde más cercano y, si la ventana es más baja que ella, se encoge. Medido en el resto de listas con foco virtual (selector, combo, selector múltiple, buscador, prefijo de teléfono, selector en árbol, cascada, paleta de comandos): siguen bien la opción activa. e2e/rich-text-ux.spec.ts lo fija, con la ventana baja y con el cursor fuera.
  • CookieBanner ya no roba el foco a quien está escribiendo. Al aparecer mandaba el foco dentro del aviso aunque el foco estuviera en un campo: en una tienda que lo pinta tras hidratar, quien ya escribía en el buscador perdía las teclas siguientes (escribía «zzz» y quedaba «z»). Ahora, si el foco está en un campo de texto, un área, un selector o un contenido editable, se queda ahí y el aviso se anuncia con aria-live; en cualquier otro caso entra como antes. isTyping() en utils/focus es lo que lo decide.

0.25.0, del 25 de octubre de 2026

  • Modos nuevos: radio de las esquinas, relieve y densidad amplia. data-radius (none, sm, md o lg) mueve los cuatro radios a la vez, también las píldoras de un interruptor, una barra de progreso o una ficha, que llevaban 999px escrito a mano y ningún tema alcanzaba: ahora salen de un token nuevo, --amset-radius-full. data-elevation="flat" quita la sombra de reposo de tarjetas, campos y botones y deja la de lo que flota. data-density="spacious" sube los controles a 36, 44 y 52 px para pantallas que se usan con el dedo, y la tabla, la lista de datos y el historial abren sus huecos con ella. Los tres van en la raíz o en un contenedor, AmsetProvider los acepta como radius, elevation y density, y la barra de Storybook tiene los dos selectores nuevos. Lo que es redondo por lo que significa (el punto de un botón de opción, un avatar, un girador) no cambia, y un shape="pill" pedido en la prop sigue siendo píldora en cualquier modo.
  • Las 693 variables de los componentes, probadas una a una en un navegador. Cada una se puso como la pondría un proyecto y se miró si cambiaba algo: todas lo hacen. 674 se vieron solas recorriendo las 1.052 historias; el resto solo actúa en un estado concreto (arrastrando, pulsando, al cerrar un panel, en pantalla estrecha) y se comprobó montando ese estado. Una prueba nueva falla si una hoja declara una variable que no lee nadie.
  • Tabs y AppliedFilters alineados con el resto. Las pestañas medían 30, 38 y 46 px y no seguían a la densidad; ahora miden lo que un control de su tamaño (30, 36 y 44 px, y lo que pida la densidad), con --amset-tabs-height, y --amset-tabs-padding-block pasa a 0. Las fichas de los filtros aplicados salían de 23,2, 26,8 y 32,4 px y ahora miden 24, 28 y 36, como ToggleChip, con --amset-filters-height. Medido en los 119 componentes con tamaño, en sus tres tamaños y sus tres densidades: el resto de controles ya casaba. e2e/modes.spec.ts lo fija.

0.24.0, del 25 de octubre de 2026

  • Repaso de móvil y teclado de todas las fichas. ButtonGroup pegado que no cabe en una fila ya se apila a todo el ancho (antes pasaba a una segunda fila desalineada); lo decide el componente midiendo, no un ancho fijo. La leyenda de DonutChart deja de pisar el nombre con el valor, Pagination esconde los números intermedios por debajo de 30 rem y la barra de RichTextEditor pasa a una sola fila que se desplaza. En el editor, Escape sobre un menú de la barra lo cierra y deja el foco en su botón en vez de saltar al texto. Las páginas de demostración de Affix y TableOfContents ya no desbordan en un móvil. Medido con una sonda de teclado sobre las 208 fichas; los avisos restantes eran de la propia sonda (radios nativos, acordeones que no se cierran con Escape).
  • WeekAgenda: el hueco para crear una cita se elige también con el teclado. Lo encontró amset-ui a11y lint al pasarlo sobre la propia biblioteca: con onSlotSelect el hueco solo se podía elegir con el ratón (WCAG 2.1.1). Ahora la cuadrícula es una parada del tabulador con un cuadro de hueco que se mueve con las flechas (Re Pág y Av Pág de hora en hora, Inicio y Fin al primer y al último hueco del día), anuncia el día y la hora y se elige con Intro o Espacio, igual que un clic. El cuadro solo se pinta con el foco del teclado, no al llegar con un clic, y en un idioma de derecha a izquierda las flechas laterales van al revés. Sin onSlotSelect la cuadrícula sigue sin meterse en el tabulador. Prueba en navegador en e2e/week-agenda-keyboard.spec.ts.
  • Menubar y ButtonGroup ya no empujan la página a 320 px de ancho (WCAG 1.4.10). Medido por amset-ui a11y audit: la barra de menús llegaba a 338 px y el grupo de botones a 323 px de 320, y las tarjetas del ejemplo de AppShell a 325 px porque la rejilla pedía 14 rem de mínimo. Las entradas de la barra y los botones del grupo pasan a otra línea cuando no caben (no se desplazan dentro de su caja: el aro de foco de un botón se sale 4 px y se recortaría), y la rejilla del ejemplo se encoge. e2e/reflow-320.spec.ts lo fija.
  • amset-ui a11y audit no da por inalcanzable lo que desaparece mientras se tabula. Un botón que solo existe tras bajar la página (como BackToTop) se desmonta al subir el foco al primer enlace, y se contaba como un control que el tabulador no alcanzaba. Ahora se vuelve a mirar la página al acabar el recorrido y lo que ya no está no cuenta. La pagina limpia de e2e/a11y-cli.spec.ts lleva un botón que se quita al enfocar otro, para que no vuelva.
  • amset-ui a11y: pasado contra las 208 fichas del propio Storybook, ya no da falsos fallos. La primera pasada daba 112 hallazgos propios y casi todos eran de la herramienta, no de la biblioteca. a11y audit ya no cuenta como inalcanzables los controles de un widget compuesto (toolbar, tablist, listbox...), que tiene una parada de tabulador y se recorre con las flechas; no tabula los controles con tabindex="-1"; mide un <input> escondido por la etiqueta que se ve; compara el foco por elemento y no por selector (dos filas de una tabla daban el mismo); no da por cerrada la vuelta en la primera salida del documento, porque una capa superior (un banner) la cruza antes del final; mide el tamaño de un objetivo con su caja entera aunque el scroll de su lista lo recorte; y suelta el foco previo (un autofocus) antes de medir, o un campo con el aro puesto de salida parecía no tenerlo. a11y lint reconoce el foco pintado con [data-focus-visible] y da por apagado el movimiento infinito si cualquier hoja respeta prefers-reduced-motion. Resultado: 202 de 208 fichas sin hallazgos propios; el resto son candidatos a revisar, no fallos confirmados.

0.23.0, del 24 de octubre de 2026

  • amset-ui a11y: la accesibilidad de tu proyecto, fuera de la biblioteca. Tres comandos nuevos que comparten una tabla de reglas, cada una con su criterio de WCAG. a11y audit <url> abre las páginas en un navegador con el Playwright del proyecto y mide lo que un escáner estático no ve porque exige pulsar teclas o cambiar la página: el foco visible al tabular, las trampas de teclado, los controles que el teclado no alcanza, el foco tapado por una cabecera fija (2.4.11), los objetivos de menos de 24 px (2.5.8), la página a 320 px de ancho (1.4.10), el espaciado del texto (1.4.12) y el movimiento reducido. Si el proyecto tiene axe-core, pasa además sus reglas de WCAG 2.2 sin contarlas dos veces; si no lo tiene, lo dice en el informe y no lo da por pasado. Solo lee: tabula y mide, nunca hace clic. Escribe a11y-report/index.html y report.json, y sale con código 1 si hay errores. a11y lint lee el código con el TypeScript del proyecto y avisa de controles hechos con div, imágenes sin alt, IconButton sin nombre, campos sin etiqueta, el foco quitado, colores sin contraste y componentes hechos a mano donde la biblioteca ya trae uno; un hallazgo concreto se calla con // amset-a11y-ignore image-without-alt. a11y statement redacta el borrador de la declaración de accesibilidad (Ley 11/2023, Anexo V de la Directiva europea) con lo medido y las fichas de los componentes que usa el proyecto: sin auditoría el estado es «no evaluado», con una auditoría limpia es «sin incumplimientos detectados» y nunca «conforme», lo que nadie ha comprobado sale como pendiente, y lo que no puede saber (empresa, contacto, vía de reclamación) queda como un hueco marcado para completar. Es un borrador, no asesoría jurídica. Las medidas del navegador se prueban contra una página limpia y otra con cada fallo (e2e/a11y-cli.spec.ts), y npm run consumers pasa los tres comandos contra cada starter recién creado y compilado.

0.22.0, del 24 de octubre de 2026

  • @amset/ui/rich-text: repaso entero del editor (AMS-2072). Botón Insertar en la barra con todo lo que se puede meter, por apartados y con buscador (antes los componentes solo salían escribiendo «/»). Un componente recién insertado sale con sus propiedades abiertas, que se vuelven a abrir con doble clic, con el botón derecho o desde la barra; la ficha va en castellano, por orden y sin lo técnico (el selector pasaba de 86 campos en inglés a los suyos). El botón derecho da un menú según lo de debajo: componente (propiedades, duplicar, subir, bajar, eliminar), imagen, enlace, tabla, y cortar, copiar, pegar y formato. Los 158 componentes entran con contenido de verdad, sacado de sus fichas (antes unos 80 entraban con «Etiqueta» y listas vacías). La pantalla completa es la del navegador o la capa superior: con un antepasado con transform (la documentación de Storybook) se quedaba en la barra sin el texto. El menú «/» va en la capa superior y se abre hacia arriba si abajo no cabe. Pegar, soltar o elegir del equipo una imagen funciona sin onImageUpload (se incrusta en base64; también en Firefox, que la deja en items). El panel del enlace pide el texto, la dirección y una descripción (title, que se guarda y se exporta), y avisa de textos como «aquí».
  • CommandPalette: con las flechas, la opción activa se queda a la vista. El foco de la paleta no sale del campo de búsqueda (la activa se dicta con aria-activedescendant), así que el navegador no desplazaba nada y, en cuanto la lista no cabía, se bajaba a ciegas por debajo del borde y se subía sin que la lista volviera. Ahora la fila activa se lleva a la vista cuando la mueve el teclado (no con el ratón, para que la lista no dé un tirón bajo el puntero) y la primera sube hasta el título de su grupo. Los selectores ya lo hacían bien; e2e/keyboard-scroll.spec.ts lo fija en los dos y mide la paleta antes y después del arreglo (22 filas fuera de la vista bajando, 0 ahora).

0.21.0, del 23 de octubre de 2026

  • Se retiran las recetas con editores de terceros. Con el editor propio de @amset/ui/rich-text ya no hacen falta: se quitan las historias de recetas con otros editores de RichTextToolbar, sus recetas y demos, la prueba e2e que las ejercitaba y las once dependencias de desarrollo de esos editores (el package-lock.json baja unas 900 líneas). RichTextToolbar sigue siendo API pública y se puede usar con cualquier motor: la guía docs/rich-text-editor.md se centra ahora en el editor propio y en el contrato de la barra, y conserva la lista de comprobación con lector de pantalla, que pasa a hacerse sobre RichTextEditor.
  • llms.txt y AGENTS.md cuentan lo que un asistente de IA necesita saber: que el código se copia, el servidor MCP y Figma. npx amset-ui add ya copiaba el código fuente de los componentes, @amset/mcp ya daba ocho herramientas a un asistente de código y los tokens ya salían como Variables de Figma, pero esos ficheros, que son lo que lee un asistente, no decían nada de eso y la biblioteca parecía solo instalable y sin integraciones. Ahora tienen las secciones «Instalar o copiar el código» y «Servidor MCP y Figma», y llms.txt enlaza a las guías. El editor de texto enriquecido (RichTextEditor, RichTextView) y el visor de HTML (HtmlViewer) son de la subruta @amset/ui/rich-text y no se pueden copiar con add: se instalan, y una prueba lo vigila.
  • llms.txt lleva enlaces. Google avisaba de que el fichero no los traía. Ahora tiene una sección «Documentación» con enlaces en Markdown a llms-full.txt, al catálogo de componentes, a «Empezar», a la página de agentes y a la del editor, y una prueba comprueba que haya un H1 y al menos tres enlaces.
  • @amset/ui/rich-text: corrector ortográfico propio, comentarios y sugerencias, historial de versiones, columnas y fórmulas (AMS-2055). spellchecker (con createWordSpellchecker y createLanguageToolSpellchecker): los fallos se subrayan con una onda, el botón derecho o la tecla de menú da sugerencias, ignorar y añadir al diccionario, y la palabra que se escribe no se marca; el editor no trae diccionario, lo pones tú. review añade comentarios (hilos que guardas fuera del documento, con respuesta, resolver y eliminar) y el modo «Sugerir cambios» (lo escrito se subraya, lo borrado se tacha, y se acepta o rechaza uno a uno o todo); RichTextView gana suggestions="show | accepted | rejected", y Word recibe las sugerencias como revisiones y los comentarios como comentarios, y los lee de vuelta. versions añade el historial: lista, comparación con el documento de ahora (lo añadido subrayado, lo quitado tachado) y restaurar con deshacer; diffDocuments(before, after) lo da fuera del editor. Nuevos bloques de serie: columnas (de 2 a 4, el texto fluye de una a otra, una sola en pantalla estrecha) y fórmulas en TeX pintadas como MathML con un conversor propio sin dependencias (se guardan como $$ ... $$ en Markdown). Además, el resaltado de buscar y el de ortografía ya no se pisan entre dos editores de la misma página. Sin cambios de API en lo que ya existía.

0.20.0, del 22 de octubre de 2026

  • Markdown: los dos espacios al final de una línea vuelven a ser un salto de línea, y nada se pierde por anidar de más. fromMarkdown recortaba cada línea del párrafo y el salto de CommonMark desaparecía. Un enlace entre <> con espacios ahora se lee (los espacios pasan a %20), y una cita anidada más allá de 20 niveles se queda como texto en vez de vaciarse al guardarla.
  • @amset/ui/rich-text: visor de HTML seguro y los componentes de AMSET UI dentro de un documento (AMS-2028). HtmlViewer pinta HTML ajeno: en modo documento lo vuelve a escribir desde el árbol con una lista de lo permitido (etiquetas, atributos, esquemas de URL y propiedades de CSS), y en modo página lo aísla en un <iframe sandbox> sin allow-same-origin y con una CSP que no deja red, formularios ni scripts ajenos. amsetBlocks({ actions, data }) registra 159 componentes de la biblioteca como bloques: el catálogo sale del compilador de TypeScript, cada uno se descarga la primera vez que se usa, las props se validan con su esquema antes de pintarlo, y las acciones y los datos son identificadores que pone la aplicación (nunca viaja una función). Tarjeta, Alerta, Rejilla, Pila y compañía llevan bloques del documento dentro (component), con marco y nombre en el editor, ficha de propiedades y «Quitar y dejar el contenido». toTsx(doc) lo lleva a un componente de React, y listComponents, describeComponent y createComponent dejan a un agente montar pantallas validadas. Además, en Firefox la primera pulsación en un editor sin foco ponía el cursor al principio del documento en vez de donde se pulsaba, y npm run e2e:engines no activaba Firefox ni WebKit (el nombre de la variable no coincidía con el de playwright.config.ts). Sin cambios de API en lo que ya existía.
  • @amset/ui/rich-text: Markdown y Word, pegado fiel, bloques nuevos, avisos de accesibilidad y circuito de IA (AMS-2011). toMarkdown/fromMarkdown (CommonMark y GFM, lo que Markdown no dice va como HTML y vuelve) y toDocx/fromDocx (un .docx con titulos, formato, listas con niveles, tablas con celdas combinadas, enlaces e imagenes); el pegado de Word, Google Docs, Excel y LibreOffice se prueba con un corpus. Celdas combinadas, menciones con @, cuadros destacados (> [!NOTE]), desplegables y bloques propios (blocks: tu componente en el texto, con su render y edit). Barra flotante sobre el texto seleccionado, asa para arrastrar bloques y Alt+Mayus+flechas. Avisos de accesibilidad mientras se escribe (checkAccessibility). Un circuito de IA sin proveedor: aiCommands, createRichTextAgent (leer, buscar, proponer cambios que una persona acepta o rechaza, escribir en directo, herramientas para un modelo).
  • @amset/ui/rich-text: un editor de texto enriquecido propio, sin dependencias (AMS-2011). RichTextEditor es un campo completo (etiqueta, ayuda, error, contador, solo lectura) con barra de herramientas -formato, títulos, listas y tareas, citas, código, alineación, color y resaltado, índices, enlaces, imágenes con sus propiedades, tablas, línea horizontal, buscar y reemplazar, código HTML y pantalla completa-, atajos de Markdown al escribir, pegado desde Word y Google Docs sin su basura, arrastrar y soltar, y deshacer agrupado. El valor es el documento en JSON (format="json", lo exacto) o HTML saneado (format="html"). Por dentro hay un modelo inmutable con pasos invertibles, un esquema que descarta todo lo que no conoce (el HTML exportado se genera desde el modelo: ni <script>, ni manejadores, ni javascript:) y una vista sobre contenteditable que cancela beforeinput, y cuando el navegador no deja (una composición de IME, un teclado de Android) lee el DOM y aplica la diferencia. RichTextView pinta lo guardado en el servidor, sin dangerouslySetInnerHTML. RichTextToolbar admite children para grupos propios. Vive en su subruta y el índice no lo importa: no pesa nada en quien no lo usa.

0.19.0, del 22 de octubre de 2026

  • Form: el clic en Guardar ya no se pierde al corregir el último error (AMS-2036). Un campo que valida al salir (el número confirma su valor en el blur) corregía su error justo al apretar Guardar: desaparecían el resumen y el mensaje del campo, el formulario subía hasta 111 px entre el pointerdown y el pointerup y el clic caía fuera del botón, así que había que pulsar dos veces. Ahora lo corregido se queda tachado en el resumen hasta el siguiente envío (con «Todo corregido: ya puedes enviar» cuando no queda nada y el tono de éxito), y mientras se aprieta dentro del formulario ningún error cambia de aspecto: se pone al día en cuanto llega el clic. Además, al enviar con errores el foco va a la cifra del número y no a su botón «−». Prueba nueva e2e/form-summary.spec.ts en Chromium, Firefox y WebKit, con axe en claro y oscuro.
  • El mensaje de chat separa lo que lleva dentro. Un razonamiento, una llamada a herramienta y la respuesta dentro de la misma burbuja de ChatMessage se pegaban sin un píxel de aire (medido: 0 px entre la herramienta y el texto). Ahora cada bloque que no es el último deja --amset-chat-message-block-gap debajo (--amset-space-2 de serie), también con un párrafo de margin: 0 en línea. Historia nueva «Con razonamiento y herramienta».
  • Dialog, Drawer y la paleta de comandos ya no se cierran con un clic que empezó dentro (AMS-2027). Pulsar fuera se detectaba por el destino del click, y el navegador lo manda al ancestro común de donde se pulsa y donde se suelta. Si el contenido cambia de tamaño entre las dos cosas (un aviso de error que desaparece al salir del campo) el botón se mueve, se suelta sobre el fondo y el diálogo se cerraba sin guardar: medido en Facturación al imputar horas tras un primer error. Ahora solo cierra si el gesto empezó también en el fondo. Lo mismo al arrastrar seleccionando el texto de un campo y soltar fuera.
  • npm run figma lo hace todo, y en cada subida a pre se genera solo. Construye el Storybook si falta, lo sirve en local y mide las historias con varias páginas a la vez, y deja figma-export/ con los componentes, los tokens y el plugin. Un job nuevo del CI (figma, no bloquea la web) lo ejecuta en cada subida a pre y ui.amset.tech lo publica en /figma/, igual que el resto de lo que se genera en cada versión.
  • Una app de gestión sale bien maquetada sin CSS propio (AMS-1972). Cinco huecos medidos en una app de gestión real con getComputedStyle, que la app tapaba con reglas suyas: una Table dentro de una Card con withoutPadding ya no pinta un segundo marco y alinea sus celdas extremas y la paginación con el título de la tarjeta; las celdas numéricas no parten un importe en dos líneas; PageHeader centra media con el título y estrena mediaTile para el icono de sección en su baldosa; un SideNav dentro de un Drawer ocupa el cajón entero, sin la raya lateral ni la doble barra de desplazamiento; AppShell quita el margen de 8 px del body, SiteFooter con solo la línea legal no duplica la raya, y una Card pulsable sin contenido ya no corta el subtítulo.
  • Un campo de texto ya no trae el CSS de color, fecha, hora, selector ni teléfono (AMS-2014). El generador del CSS por componente importaba cualquier hoja que tuviera una regla empezando por una clase que el módulo mencionaba, y casi todas las hojas de campos tienen unas pocas reglas de .amset-field (cómo se pinta un campo que contiene un color o una fecha). TextField y TextArea arrastraban así 64 KB de CSS sin comprimir cuando usaban 29. Ahora una hoja solo se importa por las clases que le pertenecen: las que dominan en ella o aquella donde la clase tiene más reglas. TextField baja a 29 KB, Select de 71 a 40 y los imports de todo el paquete de 710 a 489. Una prueba fija los componentes que no pueden quedarse sin su propia hoja.
  • MultiSelect y TagsInput: lo elegido va dentro de la caja (AMS-1971). Las fichas iban en un bloque aparte encima del campo y cada valor nuevo empujaba hacia abajo todo el formulario. Ahora fluyen dentro de la caja, delante de donde se escribe: con una línea mide lo mismo que cualquier campo y solo crece si no caben. La etiqueta se queda siempre en su sitio. Pulsar el hueco libre de la caja lleva al campo, y limitTags deja la caja en una línea aunque se elijan muchas: en reposo enseña ese número de fichas y un «+N», y al entrar salen todas.
  • La exportación a Figma trae también el estado pulsado y el deshabilitado, y la portada del Storybook la explica. En la historia «Interactivo» de cada componente se mide el pulsado (puntero pulsado sobre el primer elemento interactivo) y el deshabilitado (el atributo se pone en el elemento vivo y dibuja las reglas :disabled de la biblioteca). La portada gana un apartado «En Figma», en castellano y en inglés.
  • La exportación a Figma trae también el hover y el foco. Cada componente con historia «Interactivo» añade las variantes Interactivo · hover e Interactivo · foco, medidas con un puntero y un Tab reales; el anillo de foco se dibuja como un trazo por fuera. Solo cuentan si cambian el aspecto en reposo.
  • doctor ya no da error con data-theme={variable} (AMS-1962). El valor de una expresión no se puede leer sin ejecutar el código y se tomaba la propia variable como si fuera el tema, así que data-theme={tema} salía como error. Ahora es un aviso (data-theme-dynamic) que pide comprobar que solo devuelva "light" o "dark". Los literales ({'oscuro'}) siguen siendo error, y lo que dice un comentario (/* */, //, <!-- -->) ya no cuenta como código.
  • La página de documentación de Notificaciones ya no repite el demo cuatro veces. Con las historias en línea, cada una montaba su propia cola de posición fija y las cuatro se apilaban en la misma esquina; ahora cada historia va en su marco, como en AmsetProvider. Además, el error de useNotifications y el de useConfirmation fuera de su proveedor nombraban useNotificaciones y ProveedorNotificaciones, que ya no existen: ahora dicen el nombre real y en inglés.
  • @amset/ui/icons.json: la geometría de cada icono como dato (AMS-1373). Para pintar los iconos sin importar los componentes (la página de iconos de amset.tech se llevaría 900 KB de JS si los importara). Sale de llamar a cada icono y recorrer el elemento que devuelve, no de leer el código generado, así que no se rompe cuando cambia el formato del build. Cada entrada dice si es de la casa, si trae su propio <svg> (la estrella rellena), lo que le pasa a su envoltorio (el strokeWidth propio de 35 iconos) y sus elementos con sus atributos.

0.18.0, del 20 de octubre de 2026

  • VatField: el IVA intracomunitario, validado país por país. DocumentField solo entiende NIF, NIE y CIF, y una tienda que vende a empresas de la UE no podía validar un IVA alemán o francés sin una expresión regular que deja pasar el dígito cambiado, que es el error real. El campo cubre los 27 Estados de la UE y Irlanda del Norte (XI): cada uno con su forma y, en los 22 donde el algoritmo es público, su cifra de control (AT, BE, CZ, DE, DK, EE, EL, ES, FI, FR, HR, HU, IE, IT, LU, NL, PL, PT, RO, SE, SI, SK); Bulgaria, Chipre, Lituania, Letonia, Malta e Irlanda del Norte se validan por su forma y el campo lo dice. Se escribe con espacios, puntos o guiones y sale por onChange limpio, en mayúsculas y con EL para Grecia (GR se acepta). La ayuda dice de qué país es y si la cifra se ha comprobado; countries limita los Estados admitidos. No consulta VIES: dice si el número puede existir, no si está dado de alta (la consulta necesita un servidor). Nuevas utilidades validateVat, vatProblem, vatIsChecked, vatCountry, clearVat y VAT_COUNTRIES, y vatSchema para los esquemas. 88 pruebas, con números reales de cada país publicados como válidos y el mismo número con un dígito cambiado, que tiene que fallar en cada país con algoritmo.
  • Fieldset: un grupo de campos con su rótulo, un <fieldset> de verdad. La biblioteca no tenía cómo agrupar controles que van juntos para que un lector de pantalla diga el grupo antes de cada uno. legend es la <legend>; disabled apaga los campos de dentro a la vez; columns los reparte en 2, 3 o 4 columnas (una por debajo de 40 rem); hint pone ayuda bajo el rótulo y hiddenLegend lo esconde a la vista. Quita el min-width: min-content del navegador, que hacía desbordar una página estrecha. Se puede usar desde un Server Component.

0.17.2, del 20 de octubre de 2026

  • Table: filas jerárquicas, rango de celdas y totales por grupo. tree (getChildren, expanded, onExpandedChange) pasa la rejilla a treegrid con nivel, posición y estado de cada fila y teclado de árbol; cellSelection selecciona un rango con el ratón o con Mayús y flechas, lo copia como TSV y HTML y lo pega; groupTotals suma una fila de totales por grupo. Todo se anuncia en una región viva. Tipos TableTree, CellRange y CellRef.
  • SchemaForm (@amset/ui/form): un formulario entero desde un JSON Schema. Campos por tipo y format, objetos como fieldset, listas que se añaden y se quitan, pasos con Wizard y condiciones (if/then/else, dependentRequired, dependentSchemas, oneOf por const). Validador propio, sin dependencias, que dice lo que no entiende en vez de ignorarlo; los campos apagados por una condición no se envían.
  • WeekAgenda: citas que se repiten, zonas horarias y columnas por recurso. repeat con las reglas de iCalendar (freq, interval, weekdays, until, count, except), timeZone por cita y por agenda con el horario de verano de cada fecha (lo que cruza la medianoche sale partido y bloqueado) y resources para repartir la vista de día por sala o persona. onEventChange entrega además series, occurrence, timeZone y resource. Salen occurrencesBetween, occurrenceKey y parseOccurrenceKey.
  • Ficha de conformidad de accesibilidad por componente (ACCESSIBILITY-CONFORMANCE.md, npm run a11y:sheet). Generada desde las fichas: qué documenta cada componente, si pasa axe en el navegador, si tiene guion de prueba con lector de pantalla y el resultado de cada lector. Los guiones y los resultados manuales viven en docs/accessibility/protocol.json; lo que nadie ha ejecutado figura como «pendiente», no como «cumple». Una prueba exige la sección de accesibilidad en cada ficha.
  • npx amset-ui generate: una aplicación entera desde su plano JSON. Valida el plano entero antes de escribir (bin/blueprint.mjs), parte del starter de create, pone una página por entrada (vacía, o con dashboard, customer-list, settings o sign-in), rehace el menú, copia los bloques y pasa doctor con un solo intento de reparación; --check añade el compilador. Las pantallas salen con datos vacíos y el servicio sin conectar fallando a las claras. La skill amset-ui explica el plano a un asistente.
  • Cobertura de las capturas de referencia (npm run visual:coverage). Dice qué historias de repaso y sistema no tienen sus cuatro capturas, cuáles las tienen a medias y qué capturas sobran; una prueba falla con las huérfanas y los juegos a medias. Las que faltan las genera el job e2e:capturas. Límites de peso de la tabla y de la agenda subidos (44,6 y 20,8 KB) por lo anterior.
  • Lo que amset.tech copiaba a mano viaja ahora en el paquete (AMS-1922). El CHANGELOG en inglés sale como @amset/ui/changelog.en.json (el texto castellano normalizado como clave, igual que props.en.json) y una prueba falla si una entrada nueva no lo trae; SECURITY.md, benchmark/RESULTADOS.md, docs/rich-text-editor.md y docs/adr/ entran en el paquete; api.json da los tipos enteros (antes recortaba con «…» los de messages de AmsetProvider y trigger de Tooltip); EditableColor y ColorFormat se exportan, y ToneIcon cuenta como componente y no entre los iconos.
  • Los mandos de la etiqueta del campo llegan también a la valoración, la subida de ficheros y el grupo de radios (AMS-737). components.field['label-weight'] y el resto de label-* se escribían solo sobre .amset-field, así que esas tres piezas, que reutilizan la etiqueta del campo, se quedaban con otra tipografía. amset-ui theme los emite ahora también sobre ellas (el tamaño no: cada una lo ajusta con su size).
  • Las pruebas del comando theme volvían a ejecutarse: se saltaban en silencio desde el pase a inglés porque buscaban dist/styles/paleta.js.
  • amset-ui theme --format hex: el tema con los colores en hexadecimal. Por defecto el CSS sigue saliendo en oklch(...), que es como se deriva el tema; con --format hex se convierte a #rrggbb (#rrggbbaa con transparencia). Un formato que no existe da un error claro.
  • ImageViewer: «ajustada a la ventana» ya ajusta (AMS-1947). El escenario del visor era una rejilla con una fila auto, que crecía hasta el alto natural de la imagen, y el max-height: 100% de la imagen no se resolvía contra nada: una captura de 1440 × 900 en una ventana de 1280 × 900 salía 41 px más alta que su escenario, y una vertical de 600 × 1600 en un móvil, 225 px. Medido en un navegador de verdad. Ahora la rejilla tiene una fila y una columna definidas (minmax(0, 1fr)) y a tamaño real vuelve a crecer con la imagen, para que el desplazamiento llegue hasta el final. Prueba nueva e2e/image-viewer.spec.ts: apaisada, cuadrada y vertical en tres tamaños de ventana, por los cuatro lados del escenario.
  • Los componentes se exportan a Figma (npm run export:figma-components). Cada historia de Componentes/ se renderiza en un navegador real y se vuelca lo que mide (cajas, rellenos, bordes, radios, sombras, textos, SVG); el plugin de figma/ dibuja un conjunto de componentes por componente, con una variante por historia, y engancha a las variables de AMSET · Color los colores que coinciden con una sola. Las cajas son fijas (sin auto layout) y los estados solo salen si una historia los muestra.
  • knobs.json vuelve a traer los mandos del campo de código y del encabezado de página (AMS-1903). Al renombrar sus clases (AMS-814) el catálogo dejó de verlos, y con ellos el probador de tema, el MCP y la tabla de variables de sus fichas. Los grupos siguen siendo code y heading, y el selector es el nuevo. Una prueba nueva (scripts/knobs-groups.test.ts) falla si se pierden.
  • La tabla de datos de los gráficos de series ya tiene estilo (AMS-1902). Con tableVisible, LineChart, AreaChart y StackedBarChart la enseñaban sin una sola regla: cifras pegadas y sin bordes. Ahora lleva las mismas que la de BarChart, con los encabezados alineados a sus cifras.
  • Las dos primeras series de un gráfico ya no salen del mismo color con una marca azul (AMS-1909). El orden de tonos por defecto era acento, información, éxito...; con una marca azul, acento e información eran dos azules. Ahora es acento, aviso, éxito, error, información, neutro, en LineChart, AreaChart, StackedBarChart y RadarChart. Quien no pasaba tone verá la segunda serie en ámbar en vez de azul.
  • Los bloques salen enteros en inglés en el Storybook, y el manifiesto lleva su inglés (AMS-1913, AMS-1922). Los textos que un bloque arma con un dato dentro («8 en total», «Factura F-…», «¿Emitir la factura …?», «Facturas de …», «Historial de …») tienen su traducción, y la prueba de las demos ya ve los trozos fijos de esas plantillas, así que el siguiente no se escapa. El mensaje de longitud del alta de cliente pasa a «Máximo de caracteres: 500». dist/sources/manifest.json publica el en de cada bloque.

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 cualquier motor de edición 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 recetas enteras, 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.
  • 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; 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 la biblioteca anterior: 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/: la pantalla de referencia, medida en un navegador real (AMS-1784). Una pantalla (formulario con validación, tabla con orden y paginación, diálogo de confirmación) 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 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.
  • 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 de las piezas más usadas (P1.6). npm run benchmark empaqueta con Vite 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 una biblioteca 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 un visor de imágenes: 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 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. 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. 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 el detalle desplegable de fila y el pie de resumen de 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. 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.