Cambios de tokens:
- Light --background: 0.988 -> 0.962 (slate cool, hace pop el card white)
- Dark --background: 0.135 -> 0.130 (mas oscuro)
- Dark --card: 0.180 -> 0.220 (salto +0.090 vs bg, antes solo +0.045)
- Dark --popover: 0.200 -> 0.245 (popovers/dropdowns aun mas elevados)
- Dark --secondary/muted/accent/input: bumpeados al nivel correspondiente
para que la jerarquia visual mantenga proporciones
Card variants:
- default: shadow-sm -> shadow-md (mas elevacion default)
- nuevo variant 'flat' (sin shadow) para cuando se necesite
Nueva utility .surface:
- Mismo treatment visual que <Card variant='default'> pero como clase
CSS — para contenedores que no usan el componente Card (ej: tablas,
listas custom). bg-card + border + radius + shadow-md.
UsersTable refactorizado para usar .surface en lugar de border manual.
Cualquier futura tabla/lista usa .surface por consistencia.
Tests 136/136 verde.
Cambios:
- index.css: fix de browser autofill (Chrome/Safari forzaba bg amarillo +
texto blanco que rompia contraste). Override -webkit-text-fill-color
+ box-shadow inset para mantener tokens del DS. Esta era la causa real
de las 'letras blancas en gris' que se veian en login.
- index.css: utility .grid-bg global (7% opacity light, 10% dark) — para
usar como fondo cuadriculado en todos los layouts.
- PublicLayout: agrego .grid-bg layer + bg-background explicito + glow
blobs mas intensos (25%/20% en light vs 10% antes). Light ahora
tiene la misma profundidad visual que dark.
- ProtectedLayout: agrego .grid-bg + glow blobs sutiles en corners para
dar profundidad al dashboard y todas las secciones internas. Resalta
futuros componentes glass.
Tests: 136/136 verde.
El ThemeToggle solo vivia en AppHeader (ProtectedLayout), por lo que
desde /login era imposible cambiar el tema. Movido a esquina superior
derecha con z-index 20 sobre el gradient mesh.
useTheme defaultea a system preference, pero el usuario tiene que poder
override desde cualquier pantalla — incluido el login.
Cambios principales:
- Agregado violet accent (oklch 0.62 0.20 280) para combo tech con brand cyan
- Neutrals con shift sutil hacia hue 250 (slate-violet)
- Dark mode con bg oklch(0.135 0.018 252) — no pure black, feel mas tech
- Inputs con token --input propio (white en light, elevado en dark) y --input-border mas prominente. Fixea problema de input gris feo
- Card soporta variant glass/elevated/default
- Multi-layer shadows reales (shadow-sm/md/lg/xl/glow)
- Gradient mesh utility (.gradient-mesh + token --gradient-mesh)
- Clase .glass para glassmorphism (backdrop-blur 20px + saturate 180%)
- Border radius default 10px (era 8px) — mas moderno
- Headings con tracking-tight -0.015em
LoginPage redesigned:
- PublicLayout con gradient mesh + 2 glow blobs (brand+violet) + grid sutil
- Card variant glass para el form
- Logo mark con bg-gradient-to-br from-brand-500 to-violet-500
- Inputs con bg propio + ring brand glow al focus
Tests: 136/136 verde.
Doc Obsidian 2.14 actualizado v2.0. Engram sig-cm2/design-system actualizado.
- Reemplazo de tokens HSL por OKLCH (Tailwind 4 native)
- Brand color #008fbe escalado a brand-50..950
- Neutral cool slate (complementa brand)
- Semantic: success/warning/destructive como tokens
- Background tinted off-white (no pure white) para warmth
- Dark mode usa neutral-950 (no pure black)
- Brand utilities expuestos via @theme inline (bg-brand-500, etc)
- Focus rings con ring brand color
- Selection con brand-200/800
Skill registry actualizado con compact rules de design system para auto-inyeccion en sub-agents.
Source of truth: Obsidian/02-ARQUITECTURA-y-TECH-STACK/2.14 Design System.md
Engram: sig-cm2/design-system
Sonner estaba como dependencia pero el componente Toaster nunca se monto
en el arbol de la app. ChangeMyPasswordPage ya usaba toast() pero no
mostraba nada visualmente. Agregado <Toaster richColors closeButton /> en
App.tsx (top-right) y toasts de exito/error en PermisosEditor.handleSave
para confirmar al usuario que el cambio se persistio.
El backend devuelve { rolPermisos, overrides: {grant, deny}, effective }
(nested) segun spec, pero el frontend lo lee como {grant, deny} planos.
Causaba TypeError: permisoData.grant is not iterable al abrir tab Permisos.
Tests del frontend actualizados con el shape correcto.
El row click de UsersListPage navega directo a /usuarios/:id/editar,
por lo que el modal montado solo en UserDetailPage no era alcanzable
desde el flujo real. Ahora tambien esta en el header del EditPage,
al lado del boton Volver, oculto cuando el target es el user logueado.
La seccion Mi cuenta en el sidebar quedaba desprolija con un unico item.
Se movio Cambiar contraseña al dropdown del avatar en AppHeader donde
pertenece semanticamente.
El componente ResetPasswordModal estaba implementado pero nunca montado en una pagina.
Ahora se renderiza en UserDetailPage, oculto cuando el target es el usuario logueado
(evita hit de cannot-self-reset en backend).
JwtBearerOptions.MapInboundClaims defaulted to true, which mapped the
'sub' claim to ClaimTypes.NameIdentifier in HttpContext.User. Logout
endpoint read User.FindFirst("sub") and got null, returning 401 for
any authenticated caller.
Fix: set MapInboundClaims=false and pin NameClaimType="name" so the
JWT claims land in the principal with their original names, aligning
with how JwtService.GetPrincipalFromExpiredToken (used by refresh)
already consumes them.
Unblocks Login_Refresh_Logout_FullFlow integration test (15/15 green).