@solu30/ui-kit v7.0.0 · registry privado · leído del paquete instalado
Cómo se usa el kit
Para el que va a escribir la próxima pantalla: qué hay adentro del paquete, cómo se instala en un repo nuevo, de qué subpath sale cada símbolo y con qué reglas se le agrega una pieza. Nada de esto está escrito a mano: se lee del paquete instalado en este mismo sitio cada vez que la página se construye.
- Versión instalada
- 7.0.0
- Docset declara
- 5.13.0
- Subpaths
- 19
- Símbolos distintos
- 1191
Qué es
Lo que sigue es docs/SCOPE.md, tal como viaja adentro del paquete publicado: qué entra a la librería, qué no, y dónde está el límite con un vertical. Sirve para decidir si la pieza que estás por escribir va acá o va en tu repo. No está resumido ni reescrito.
SCOPE — @solu30/ui-kit
Package: @solu30/ui-kit
Maintainer: Luna (design systems) / Forge (executor)
Ningún número de este doc se escribe a mano. Decía «4.13.0» y «80+ components» con el paquete en 5.14.0 y 334 componentes: dos versiones mayores de atraso, y nadie se enteró porque un doc no falla. El valor de hoy sale de
node ~/GitHub/ui-kit/scripts/estado-kit.mjs(superficie) ynode ~/GitHub/ui-kit/scripts/medir-uso.mjs(cuánto se importa cada pieza).
What this package IS
Cross-vertical UI component library: componentes, design tokens, utils de imagen
de Cloudflare y el theming de Tailwind. La consumen los 13 verticales y 39
packages @solu30/*.
What this package is NOT
- Not a vertical-specific component. Nothing in here can import from a vertical.
- Not a data layer. No DB calls, no API routes, no Drizzle schemas.
- Not a layout framework. It provides primitives; the vertical composes them.
Design token contract
Los estilos DEBEN usar tokens definidos por el cmpPreset (src/tailwind-preset.ts), que es el source of truth. Tokens válidos:
- Fondos:
bg-bkg,bg-container,bg-inner,bg-upper - Texto:
text-content(+ opacity, ej.text-content/70) - Bordes:
border-content,border(default--ui-border) - Accent / status:
text-primary,bg-primary,text-green/red/yellow/purple/orange - Namespace interno
ui-*(sólidos horneados, dirección 0.72) — VERIFICADOS en el preset:bg-ui-bg-secondary,text-ui-content-secondary,text-ui-content-muted,border-ui-border,text-ui-success/warning/error/info,text-ui-accent. - NUNCA hex inline ni
hsl(var(--*)).
Deuda conocida (token migration — tarea trackeada): clases no definidas por el preset (clases muertas que no renderizan):
text-foreground-muted/-subtle/-secondary,bg-background-secondary/-raised,border-border-subtle/-strong(~124 componentes), y los sufijos de statusbg-ui-success-bg/border-ui-success-border(el preset defineui.successcomo color sólido, no las variantes-bg/-border, usadas hoy entv.tsSTATUS_COLORS success/warning/error/info y entokens/colors.ts). Migración full = esfuerzo separado, con decisión de vocabulario pendiente (text-content/70opacity vs sólidostext-ui-content-muted) y posible necesidad de DEFINIR tokens faltantes en el preset. Ver tareas del plan.
See docs/INSTRUCTIONS.md for the full authoring rules.
Scope boundaries
| In scope | Out of scope |
|---|---|
| Generic UI primitives (Card, Button, Badge, …) | Vertical business logic |
| Design tokens (semantic classes only) | Inline alpha colors (/N) |
| Section-level pre-armed blocks | backdrop-blur |
| Cloudflare image utilities | Vertical-specific API calls |
@solu30/ui-kit/geo → movido a @solu30/ui-geo (ADR-219)
El subpath ./geo (GeoMap, Leaflet + react-leaflet) se extrajo del foundation
al package 2-domain @solu30/ui-geo como parte del split del god-package
(ADR-219). Migración de import: @solu30/ui-kit/geo → @solu30/ui-geo (nombres
de símbolos idénticos). Guía completa: docs/migration/ui-kit-3.0.md.
Los peers se limpian con el código, en el mismo commit
El split de ADR-219 se llevó el código de geo y de los charts, y dejó los
peers atrás: leaflet, react-leaflet, recharts y date-fns siguieron
declarados durante dos versiones mayores con cero archivos que los importaran
(recharts sobrevivía en un comentario). Se sacaron el 2026-08-26 a pedido de
Martín — «elimina leaflet del kit, eso no va acá».
Un peer huérfano no rompe nada, y por eso nadie lo mira: son optional, así que
ningún consumidor los resolvía. Lo que hace es mentir sobre lo que el paquete
necesita, y en un kit que consumen 13 verticales y 39 packages esa mentira se
lee como «el ui-kit quiere Leaflet». Un extract no termina hasta que el
manifiesto dice la verdad.
La regla, entonces: peerDependencies es superficie, no plomería. Al mover o
borrar un componente, se verifica en el mismo commit si su peer quedó sin dueño:
for dep in $(node -e "console.log(Object.keys(require('./package.json').peerDependencies).join(' '))"); do
echo "$dep: $(grep -rl "$dep" src | grep -v '\.test\.' | wc -l) archivos"
done
Hoy quedan 8 peers y los únicos NO opcionales son react y react-dom, que es
lo que un ui-kit debería pedir y nada más.
Cómo se instala
Tres pasos, una vez por repo y una vez por máquina. El paquete se publica en GitHub Packages bajo el scope @solu30, y ese registry pide credenciales hasta para leer.
1 · El .npmrc en la raíz del repositorio, para que el scope apunte al registry privado. Una vez por repo, va commiteado.
# GitHub Packages, para todo el scope @solu30
@solu30:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}2 · El token, una vez por máquina. La instalación lo toma de la variable de entorno; no queda escrito en ningún archivo del repo. En CI y en Vercel es el mismo nombre de variable, con un token de alcance read:packages.
gh auth refresh --hostname github.com --scopes read:packages
NODE_AUTH_TOKEN=$(gh auth token) pnpm install3 · Recién ahí, el paquete.
pnpm add @solu30/ui-kitSi falla
Sin credenciales no hay camino alternativo: no existe copia pública en npm, ni tarball descargable, ni una versión recortada del paquete. Un error de autenticación del registry en @solu30/* casi siempre es el token —vencido, sin read:packages, o sin la variable exportada en esa terminal—, no el paquete: se rehace el paso 2 y se reinstala.
Los @solu30/* se consumen siempre pre-compilados, como dependencia externa. Si estás por agregar el paquete a transpilePackages para resolver algo, ese es el síntoma de otro problema.
Cómo se importa
La regla es una sola: se importa del subpath, no de la raíz. La raíz junta la mayoría de los barriles en un único especificador, así que entrar por ahí arrastra vocabulario que no pediste. El caso caro está documentado en el propio paquete: el mapa completo de iconos de Lucide son unos 665 KB, así que la raíz reexporta una versión sin el mapa y lo que lo necesita entra por @solu30/ui-kit/dynamic-icon, a propósito.
import { Button } from '@solu30/ui-kit/input' // sí
import { Button } from '@solu30/ui-kit' // noLa tabla sale del exports map del paquete instalado y de las declaraciones compiladas que ese mapa apunta. El orden es el del mapa, no uno elegido acá. Los nombres de cada subpath están en el listado de componentes; acá está de dónde sale cada uno y qué tamaño tiene esa superficie.
| Se importa de | Símbolos | De los cuales tipos |
|---|---|---|
@solu30/ui-kit raíz | 1145 | 0 |
@solu30/ui-kit/preset | 2 | 0 |
@solu30/ui-kit/hooks | 115 | 2 |
@solu30/ui-kit/images | 6 | 0 |
@solu30/ui-kit/forms | 19 | 10 |
@solu30/ui-kit/editor | 12 | 2 |
@solu30/ui-kit/icons | 73 | 1 |
@solu30/ui-kit/batch-processor | 2 | 0 |
@solu30/ui-kit/markdown | 2 | 1 |
@solu30/ui-kit/tokens | 65 | 5 |
@solu30/ui-kit/utils | 94 | 8 |
@solu30/ui-kit/display | 240 | 90 |
@solu30/ui-kit/feedback | 140 | 65 |
@solu30/ui-kit/navigation | 98 | 66 |
@solu30/ui-kit/data-display | 62 | 32 |
@solu30/ui-kit/input | 239 | 121 |
@solu30/ui-kit/providers | 10 | 4 |
@solu30/ui-kit/animation | 12 | 5 |
@solu30/ui-kit/dynamic-icon | 9 | 2 |
La columna no se suma: la raíz reexporta casi todos los barriles, así que sumarla con ellos cuenta cada símbolo dos veces. Nombres distintos en todo el paquete: 1191.
Convenciones
Las reglas de autoría que viajan adentro del paquete, en docs/INSTRUCTIONS.md, sin editar. Son las que aplican al agregar o modificar un componente del kit. La primera viene anulada por su propio autor: la tabla de tokens que proponía apunta a clases que el preset nunca definió, así que no renderizan. El aviso está en el documento y se conserva — el vocabulario de reemplazo todavía no está decidido, y hasta que lo esté esa tabla no es contrato.
INSTRUCTIONS — @solu30/ui-kit
Authoring rules (MANDATORY)
1. Tokens del cmpPreset — DECISIÓN DE VOCABULARIO PENDIENTE
⚠️ Esta sección estaba ROTA y se neutralizó (task 3159). Mapeaba
text-content/70→text-foreground-secondary,bg-content/5→bg-background-secondary,border-content/10→border-border-subtle, etc. Esos tokens target NO están definidos en el cmpPreset (src/tailwind-preset.ts) — son clases muertas que no renderizan. La migración "solid colors" (dietaly/docs/plans/ui-kit-solid-colors-spec.md) se aplicó a ~124 componentes pero su vocabulario destino nunca se agregó al preset. NO sigas esta tabla.
Tokens VERIFICADOS (definidos en el preset, usar estos):
- Fondos:
bg-bkg,bg-container,bg-inner,bg-upper - Texto:
text-content(+ opacity:text-content/70) - Bordes:
border-content,border(default) - Accent/status:
text-primary,text-green/red/yellow/purple/orange - Namespace
ui-*(definidos):bg-ui-bg-secondary,text-ui-content-secondary,text-ui-content-muted,border-ui-border,text-ui-success/warning/error/info,bg-ui-{success,warning,error,info}-bg,border-ui-{success,warning,error,info}-border,text-ui-accent - NUNCA hex inline ni
hsl(var(--*)).
No verificados / probablemente muertos (no usar hasta confirmar en el preset):
text-foreground-*,bg-background-*,border-border-*.
Decisión pendiente (task trackeada): elegir el vocabulario canónico — opacity (text-content/70, permitido por CLAUDE.md global) vs sólidos horneados (text-ui-content-*, dirección 0.72 del kit) — definir en el preset los tokens que falten, y migrar los ~124 componentes. Hasta entonces, esta tab NO es contrato autoritativo.
2. No hex inline, no CSS vars con /N arbitrario
text-[var(--ui-content)]/60 ← PROHIBIDO
bg-[var(--ui-primary)]/10 ← PROHIBIDO (usar token semántico o clase utilitaria)
3. tsc limpio antes de cualquier PR
pnpm --filter @solu30/ui-kit tsc --noEmit debe pasar en cero errores.
4. Modificar un componente existente
- Leer el componente completo antes de editar.
- Usar SOLO tokens verificados del cmpPreset (sección 1). NO migrar a
text-foreground-*/bg-background-*(muertos). - Si encontrás clases con esos tokens muertos → es la deuda trackeada (task migración tokens), NO improvisar el reemplazo hasta que se decida el vocabulario.
5. Agregar un componente nuevo
- Sólo clases semánticas desde el inicio.
- Exportar desde el subpath-barrel que corresponda (
src/subpath-barrels/display.ts,feedback.ts, …); el barrel raízsrc/index.tslos re-exporta a todos (ADR-227), no se edita por componente. - Test co-locado
*.test.tsxconrenderKit(ver README § Tests). - Crear un changeset (
.changeset/<slug>.md) en la misma unidad de trabajo.CHANGELOG.mdse GENERA (pnpm changeset version) — nunca se edita a mano ni lleva sección[Unreleased].
6. Clases con gaps conocidos (pendientes de resolución por Natura)
Las siguientes clases con /N NO tienen mapeo determinístico en la tabla aún y requieren decisión de Natura antes de convertir:
bg-ui-success/10,bg-ui-error/10(trend badges en StatCard)bg-primary/20(default propiconBgen StatCard — es un prop caller-controlled)hover:bg-upper/50—bg-upperno figura en la tabla semántica[var(--ui-*)]/Nen SectionCard — patrón CSS-var-con-alpha, fuera del mapeo actual
7. RSC boundary — pasar iconos a componentes "use client" (ADR-192)
EmptyState es un Client Component. Pasar icon={LucideComponent} desde un Server Component
causa crash ("Only plain objects can be passed…"). Usar iconName en su lugar:
// Server Component (RSC) — CORRECTO
import { EmptyState } from "@solu30/ui-kit";
<EmptyState iconName="package" title="Sin paquetes" />
// Client Component — sigue funcionando igual
import { PackageIcon } from "lucide-react";
<EmptyState icon={PackageIcon} title="Sin paquetes" />
iconName acepta cualquier LucideIconName (re-exportado desde @solu30/ui-kit).
El icono se resuelve lazy con lucide-react/dynamic — no bundlea el set completo.
EmptyStateAction.iconName funciona igual para botones de acción.
Otros componentes con icon?: ComponentType (StatCard, MetricCard, SectionHeader) son
también "use client" pero sus callers típicos son client — no requieren migración aún.
Evaluar caso por caso cuando aparezca necesidad concreta desde RSC.
Versión
Un paquete y tres archivos que declaran una versión. Los tres se leen de la misma instalación: la que sirve esta página.
| Qué dice | Versión | Sale de |
|---|---|---|
| La que corre | 7.0.0 | package.json del paquete instalado |
| La del índice de exports | 5.13.0 | docs/EXPORTS.md, generado el 2026-08-24 |
Cuál es la fuente confiable
Confiable: las declaraciones compiladas de la versión instalada. De ahí sale la tabla de la sección 03 y de ahí sale el índice de componentes. No confiable hasta que se regenere: docs/EXPORTS.md, que viaja adentro del paquete 7.0.0 declarando ser de la 5.13.0. Si abriste ese archivo para buscar un símbolo, verificá contra la sección 03 antes de escribir el import.
El desfasaje no está repartido parejo, que es lo que lo hace traicionero: de 19 subpaths, 16 coinciden exactamente con el índice y 3 no: @solu30/ui-kit figura con 1174 y exporta 1145; @solu30/ui-kit/navigation figura con 94 y exporta 98; @solu30/ui-kit/animation figura con 45 y exporta 12. Un índice que acierta en casi todo es el que más fácil se cree.
Ninguno de los tres archivos está mal escrito: los tres eran ciertos el día que se generaron. Lo que falta es que se muevan juntos. El changelog sí tiene quien lo vigile —un control corre antes de cada commit de este repositorio, compara su release más nuevo contra la versión instalada y no deja pasar el commit si se separaron—; el índice de exports no tiene el suyo, y por eso pudo quedarse atrás sin que nada avisara.
Prosa leída de docs/SCOPE.md y docs/INSTRUCTIONS.md del paquete instalado · superficie leída de los .d.ts compilados que declara su exports map · versión 7.0.0.