Propósito
Este runbook registra el proceso iterativo de eliminación de estilos inline en los componentes React del Workspace (Astro + React + Tailwind v4 sobre Cloudflare Pages) para reemplazarlos por las utility classes globales definidas en globals.css (bloque D.1 del design system).
El objetivo es un código más mantenible, coherencia visual garantizada por tokens CSS y facilidad para cambios de tema global.
Utility classes de referencia (D.1)
Las clases centralizadas en globals.css que sirven de destino de la migración:
| Clase | Propósito |
|---|---|
.empty-state | Estados vacíos / carga / sin datos. Centrado, color-muted-foreground, font-size 0.875rem |
.section-eyebrow | Encabezados de sección secundaria. Uppercase, 0.875rem, font-weight 600, letter-spacing 0.05em, color-muted-foreground |
.badge | Etiqueta/chip genérica. 0.6875rem, padding 1px 6px, border-radius 4px, font-weight 600 |
.badge.success | Badge verde éxito. background rgba(197,245,74,0.2), color var(--color-success) |
.list-row | Fila horizontal de lista (no apta para elementos con borderLeft color dinámico) |
.dash-card | Tarjeta del dashboard (ya centralizada, no migrar) |
.dash-card-title | Título de tarjeta dashboard (ya centralizado) |
.dash-entry | Entrada de lista en dashboard (ya centralizada) |
Nota: Los patrones que requieren valores CSS dinámicos (color por variable de JavaScript) o layout único no replicado se excluyen conscientemente de la migración — se mantienen como
style={}inline explícito con override mínimo sobre la utility class base.
Estado de bloques
| Bloque | Descripción | PR | Estado |
|---|---|---|---|
| D.1 | Cimientos: tokens (--space-*, --radius-xs/md, --fw-*, --leading-*, --t-*, --scrim) + utility classes (.badge, .assignee-badge, .list-row, .empty-state, .section-eyebrow, .modal-overlay/dialog) en globals.css | PR#12 (2026-05-11) | ✅ Completado |
| D.3 | Tareas: chips assignee + priority + count + empty-state + modal-overlay/dialog en TaskKanban, TaskModal, TaskTableView, TaskAttachmentsModal | PR#13 (2026-05-11) | ✅ Completado |
| D.4 | /notes, /alerts/admin, AlertBanner dashboard + widget | PR#14 (2026-05-11) | ✅ Completado |
| D.5 | .tools-modal-* (QuickLinkEditModal, DirectionEditModal, ImportCsvModal, ExportModal, ThemeConfigModal) refactor a tokens D.1 en tools.css — los componentes ya estaban centralizados | PR#15 (2026-05-11) | ✅ Completado |
| D.6 | Dashboard widgets: 8 empty states inline → .empty-state en 8 archivos | PR#16 (2026-05-11) | ✅ Completado |
| D.7 | Biblioteca/Pulse: elimina PRIORITY_COLOR y TYPE_COLOR locales (duplicaban constants.ts), 2 empty states → .empty-state | PR#17 (2026-05-11) | ✅ Completado |
| D.8 | Reports section eyebrows (5 headings idénticos → .section-eyebrow). Mobile V1HomeVivo queda como deuda separada (64 inline, refactor mayor fuera de scope) | PR#18 (2026-05-11) | ✅ Completado |
| D.9 | Limpieza final: hex semánticos (#ef4444, #f59e0b, #3b82f6, #a855f7, #06b6d4, #c5f54a, #f97316, #fbbf24) → var(--danger/warning/accent/violet/accent-2/success/orange/amber) en 20 archivos | PR#19 (2026-05-11) | ✅ Completado |
| D.10 | Corrección crítica del toggle de tamaño: 100 fontSize: 'X.Yrem' → var(--text-*) en 21 archivos. Los rounds 1-4 anteriores solo migraron literales numéricos en píxeles, dejando 104 rem-strings sin convertir (toggle afectaba <50% del texto). Cobertura post-D.10: ~95% | PR#21 (2026-05-11) | ✅ Completado |
| D.11 | Formato masivo: 74 archivos TS/TSX reformateados con prettier --write + nuevo .prettierrc.json (plugin astro+tailwind, printWidth 100, single quotes, semi, trailingComma all) + .prettierignore. Sólo whitespace, sin tocar lógica (tsc reporta los mismos 361 errores preexistentes) | PR#22 (2026-05-11) | ✅ Completado |
| D.12 | Unificación CI con CreaRack-Pro: .github/workflows/ci.yml con job Prettier check bloqueante en PRs hacia main (Node 20 + pnpm). Primer intento falló por usar npm ci con pnpm-lockfile, corregido a pnpm/action-setup@v4 + pnpm install --frozen-lockfile | PR#23 (2026-05-11) | ✅ Completado |
| D.13 | Consolidación post-auditoría de diseño (s211): tokens tipográficos declarados de verdad (--font-heading Oswald / --font-body / --font-mono — se USABAN sin existir) + --text-2xl/3xl en las 4 densidades + --accent-ink (azul-texto legible, en sync con kb.css) + :focus-visible global (a11y) + tema claro/auto RETIRADO (~200 líneas; ThemeMode solo dark, prefs viejas migran solas) + dedup (.chip-mini separado de .chip, DisplayAlertBanner renombrado, stubs Header/Sidebar.astro vacíos borrados de 27 páginas) + .page-wrap/.page-head/.list-stack/a.list-row nuevas (informes/worklog sin estilos inline) + fondo de lectura near-black --kb-bg en DocsLayout | PRs #146/#147 (2026-07-09) | ✅ Completado |
Nota: el bloque D.2 no existe — la numeración salta de D.1 a D.3 intencionadamente (D.2 quedó como prefijo histórico no usado). Relación con el sistema kb (s211): los valores semánticos de
public/_kb/kb.cssson los mismos que los tokens deglobals.cssy se mantienen en sync a mano (kb.css es autocontenido — lo cargan informes estáticos). Detalle del sistema: [[ia-tech—metodo—antesala-y-voz]] §4.
Detalle D.4 — Bloque migrado en PR#14 (2026-05-11)
Archivos modificados
| Archivo | Patrón migrado | Antes | Ahora |
|---|---|---|---|
NotesGrid.tsx | Empty state “Notas disponibles en produccion” | 5 props inline | .empty-state |
NotesGrid.tsx | Empty state “Cargando…” | 2 props inline | .empty-state |
NotesGrid.tsx | Empty state “No hay notas. Crea la primera.” | 3 props inline | .empty-state |
AlertAdmin.tsx | Heading “Historial” | 5 props inline | .section-eyebrow |
AlertAdmin.tsx | Empty “No hay alertas” | 3 props inline | .empty-state |
AlertAdmin.tsx | Type badge (Info / Aviso / Critico) | 6 props inline | .badge + override color dinámico |
AlertAdmin.tsx | Badge “Activa” | 5 props inline | .badge.success |
AlertAdmin.tsx | Badge “Inactiva” | 3 props inline | .badge + background: transparent |
widgets/AlertBanner.tsx | Chip severity (INFO/AVISO/URGENTE) | 7 props inline | .badge + override fondo sólido |
dashboard/AlertBanner.tsx | Chip severity | 6 props inline | .badge + override fondo |
Tamaño: 4 archivos · +26 / −14 LOC (refactor neto +12 por overrides explícitos necesarios)
Excluido conscientemente (decisión D.4)
| Componente | Motivo de exclusión |
|---|---|
Cards de nota (NotesGrid.tsx líneas 113-124) | borderLeft con color dinámico por nota — incompatible con .list-row (layout horizontal) |
| Banner del AlertBanner | padding/border patrón único, no replicado en otros componentes |
.dash-card, .dash-card-title, .dash-entry | Ya son clases centralizadas en globals.css — no necesitan migración |
Tests del runbook
| Test | Descripción | Estado |
|---|---|---|
| Test 1 | Cambiar --space-4 re-espacia uniformemente todos los empty states y badges | ⏳ Pendiente (paddings calibrados preservados intencionalmente) |
| Test 2 | Cambiar --radius-md redondea todos los badges | ⏳ Pendiente (radii calibrados preservados) |
| Test 3 ampliado | Cambios en --scrim y propagación de severity colors son consistentes entre AlertBanner dashboard, widget y AlertAdmin | ✅ Validado en PR#14 |
| Test 4 | Toggle body[data-font-size="lg"] escala el texto de todas las páginas con cards (Dashboard, Tasks, Notes, Reports, Servers, Notebook, Maintenance, Pulse) | ✅ Validado en PR#21 (~95% del texto responde) |
Detalle D.10 — Corrección del toggle (PR#21, 2026-05-11)
Diagnóstico
Tras los rounds 1-4 anteriores el toggle de tamaño de texto en Preferencias solo afectaba ~50% del workspace. Los conteos previos:
| Métrica | Antes D.10 | Después D.10 |
|---|---|---|
fontSize: 'var(--text-*)' | 108 | 207 |
fontSize: 'X.Yrem' hardcoded | 104 | 10 |
| Cobertura del toggle | ~50% | ~95% |
La raíz: los rounds previos solo convirtieron literales numéricos (fontSize: 13) a las variables. Los rem-strings quedaron olvidados (fontSize: '0.8125rem', fontSize: '0.875rem'…).
Mapping aplicado
| Origen rem-string | Destino |
|---|---|
'0.6875rem' · '0.7rem' · '0.75rem' · '0.8rem' | var(--text-xs) |
'0.8125rem' · '0.821rem' · '0.825rem' · '0.85rem' · '0.857rem' · '0.875rem' · '0.893rem' · '0.9rem' · '0.925rem' · '0.929rem' | var(--text-sm) |
'0.9375rem' · '0.95rem' · '0.964rem' · '1.05rem' · '1.075rem' · '1.1rem' | var(--text-base) |
'1.125rem' | var(--text-lg) |
'1.25rem' | var(--text-xl) |
100 reemplazos en 21 archivos (99 vía script + 1 huérfano '0.825rem' en TaskKanban.tsx:277).
Excluidos intencionalmente (decorativos micro)
Los 10 rem hardcoded restantes están por debajo del rango calibrado del toggle:
- 8×
0.625rem(10px) — micro-chips y labels secundarios - 1×
0.5rem(8px) — separadores decorativos - 1×
0.5625rem(9px) — texto auxiliar muy pequeño
Estos NO escalan con el toggle por diseño: forman parte del set tipográfico secundario.
Detalle D.11 + D.12 — Unificación CI con CreaRack-Pro (PR#22 + PR#23, 2026-05-11)
Contexto
Tras D.10 detectamos que el repo CreaRackSL-workspace no tenía CI pre-merge (todos los workflows estaban configurados con on: push: branches: [main] o on: pull_request: types: [closed]). Las PRs nunca tenían “verde” que esperar — incidente en PR#21 D.10 donde esperamos ~25 min un CI inexistente.
Edu pidió unificar el patrón con CreaRack-Pro (que sí tiene Backend pytest+ruff, Frontend biome+vite, Docker Build pre-merge).
Análisis de estado inicial
| Validador | Resultado inicial | Decisión |
|---|---|---|
tsc --noEmit | 361 errores TS preexistentes (mocks Task sin resolution/started_at/completed_at, JSX namespace en ReportsPanel, noteColors no encontrado en NotesGrid) | Fuera de scope: requiere plan de saneamiento separado |
prettier --check src/**/*.{ts,tsx} | 87 archivos rotos (no había .prettierrc.json siquiera) | Reformatear masivo antes de activar gate |
prettier --check src/**/*.astro | Falla: “No parser could be inferred” (plugin astro no se cargaba) | Excluido por ahora; añadir tras estabilizar |
D.11 — PR#22: formato masivo
Cambios:
.prettierrc.jsonnuevo:plugins: [prettier-plugin-astro, prettier-plugin-tailwindcss],printWidth: 100,singleQuote: true,semi: true,trailingComma: 'all',arrowParens: 'always',endOfLine: 'auto'(necesario en Windows concore.autocrlf=true).prettierignorenuevo: excluyenode_modules,dist,.astro,.wrangler,public,pagefind,src/content/{wiki,help,news,changelog}/(MD auto-sincronizado intacto)- 74 archivos TS/TSX reformateados vía
pnpm exec prettier --write src/**/*.{ts,tsx}. +5699 / −1994 LOC, solo whitespace y comillas
Verificación: tsc --noEmit sigue reportando los mismos 361 errores → reformat no toca lógica.
D.12 — PR#23: workflow CI
.github/workflows/ci.yml:
name: CI
on:
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
format:
name: Prettier check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm exec prettier --check "src/**/*.{ts,tsx}"
Footgun durante D.12: primer commit usó npm ci y cache: 'npm'. Falla inmediato en CI por “Dependencies lock file is not found” — el repo usa pnpm. Fix: pnpm/action-setup@v4 + pnpm install --frozen-lockfile + cache: 'pnpm'.
Estado tras D.12
| Repo | CI pre-merge |
|---|---|
| CreaRack-Pro | Backend (pytest + ruff) · Frontend (biome + vite) · Docker Build · agent-pip-audit |
| CreaRackSL-workspace | CI / Prettier check (~30s) |
Workspace tiene CI mínimo pero unificado conceptualmente: ningún PR puede romper formato. Asimetría restante (typecheck, tests) requiere plan de saneamiento de los 361 errores TS preexistentes.
Lockfile policy workspace
- Workspace usa pnpm (
pnpm-lock.yaml), no npm. - Cualquier workflow nuevo o script CI debe usar
pnpm/action-setup@v4+pnpm install --frozen-lockfile. - Cualquier devDependency nueva:
pnpm add -D <pkg>(nonpm install).
Patrón de override aprobado
Cuando un elemento necesita un valor CSS dinámico (calculado en JS) sobre una utility class, el patrón aprobado es:
// ✅ Correcto: utility class base + override mínimo en style={}
<span
className="badge"
style={{
backgroundColor: `color-mix(in srgb, ${typeColors[alert.type]} 20%, transparent)`,
color: typeColors[alert.type],
}}
>
{typeLabels[alert.type]}
</span>
// ❌ Incorrecto: todos los props inline, sin reutilizar utility class
<span style={{ fontSize: '0.6875rem', padding: '1px 6px', borderRadius: '4px', fontWeight: 600, backgroundColor: '...', color: '...' }}>
{typeLabels[alert.type]}
</span>
El style={} residual solo debe contener los valores que no puede proveer la utility class (valores dinámicos de JS o variantes únicas no contempladas en D.1).
Véase también
- [[feature—workspace—refresh-ui-2026]]
- [[feature—workspace—tasks-s56-improvements]]
- [[feature—styles—design-tokens-d1]]