CreaRack-SL

Workspace · Migración a estilos centralizados (s56)

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:

ClasePropósito
.empty-stateEstados vacíos / carga / sin datos. Centrado, color-muted-foreground, font-size 0.875rem
.section-eyebrowEncabezados de sección secundaria. Uppercase, 0.875rem, font-weight 600, letter-spacing 0.05em, color-muted-foreground
.badgeEtiqueta/chip genérica. 0.6875rem, padding 1px 6px, border-radius 4px, font-weight 600
.badge.successBadge verde éxito. background rgba(197,245,74,0.2), color var(--color-success)
.list-rowFila horizontal de lista (no apta para elementos con borderLeft color dinámico)
.dash-cardTarjeta del dashboard (ya centralizada, no migrar)
.dash-card-titleTítulo de tarjeta dashboard (ya centralizado)
.dash-entryEntrada 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

BloqueDescripciónPREstado
D.1Cimientos: 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.cssPR#12 (2026-05-11)✅ Completado
D.3Tareas: chips assignee + priority + count + empty-state + modal-overlay/dialog en TaskKanban, TaskModal, TaskTableView, TaskAttachmentsModalPR#13 (2026-05-11)✅ Completado
D.4/notes, /alerts/admin, AlertBanner dashboard + widgetPR#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 centralizadosPR#15 (2026-05-11)✅ Completado
D.6Dashboard widgets: 8 empty states inline → .empty-state en 8 archivosPR#16 (2026-05-11)✅ Completado
D.7Biblioteca/Pulse: elimina PRIORITY_COLOR y TYPE_COLOR locales (duplicaban constants.ts), 2 empty states → .empty-statePR#17 (2026-05-11)✅ Completado
D.8Reports 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.9Limpieza final: hex semánticos (#ef4444, #f59e0b, #3b82f6, #a855f7, #06b6d4, #c5f54a, #f97316, #fbbf24) → var(--danger/warning/accent/violet/accent-2/success/orange/amber) en 20 archivosPR#19 (2026-05-11)✅ Completado
D.10Correcció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.11Formato 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.12Unificació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-lockfilePR#23 (2026-05-11)✅ Completado
D.13Consolidació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 DocsLayoutPRs #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.css son los mismos que los tokens de globals.css y 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

ArchivoPatrón migradoAntesAhora
NotesGrid.tsxEmpty state “Notas disponibles en produccion”5 props inline.empty-state
NotesGrid.tsxEmpty state “Cargando…”2 props inline.empty-state
NotesGrid.tsxEmpty state “No hay notas. Crea la primera.”3 props inline.empty-state
AlertAdmin.tsxHeading “Historial”5 props inline.section-eyebrow
AlertAdmin.tsxEmpty “No hay alertas”3 props inline.empty-state
AlertAdmin.tsxType badge (Info / Aviso / Critico)6 props inline.badge + override color dinámico
AlertAdmin.tsxBadge “Activa”5 props inline.badge.success
AlertAdmin.tsxBadge “Inactiva”3 props inline.badge + background: transparent
widgets/AlertBanner.tsxChip severity (INFO/AVISO/URGENTE)7 props inline.badge + override fondo sólido
dashboard/AlertBanner.tsxChip severity6 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)

ComponenteMotivo 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 AlertBannerpadding/border patrón único, no replicado en otros componentes
.dash-card, .dash-card-title, .dash-entryYa son clases centralizadas en globals.css — no necesitan migración

Tests del runbook

TestDescripciónEstado
Test 1Cambiar --space-4 re-espacia uniformemente todos los empty states y badges⏳ Pendiente (paddings calibrados preservados intencionalmente)
Test 2Cambiar --radius-md redondea todos los badges⏳ Pendiente (radii calibrados preservados)
Test 3 ampliadoCambios en --scrim y propagación de severity colors son consistentes entre AlertBanner dashboard, widget y AlertAdmin✅ Validado en PR#14
Test 4Toggle 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étricaAntes D.10Después D.10
fontSize: 'var(--text-*)'108207
fontSize: 'X.Yrem' hardcoded10410
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-stringDestino
'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

ValidadorResultado inicialDecisión
tsc --noEmit361 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/**/*.astroFalla: “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.json nuevo: plugins: [prettier-plugin-astro, prettier-plugin-tailwindcss], printWidth: 100, singleQuote: true, semi: true, trailingComma: 'all', arrowParens: 'always', endOfLine: 'auto' (necesario en Windows con core.autocrlf=true)
  • .prettierignore nuevo: excluye node_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

RepoCI pre-merge
CreaRack-ProBackend (pytest + ruff) · Frontend (biome + vite) · Docker Build · agent-pip-audit
CreaRackSL-workspaceCI / 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> (no npm 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]]