feat(cpp/core): design tokens + primitivos UI para dashboards ImGui

Trasladar principios del DESIGN_SYSTEM.md de @fn_library (Mantine/React)
al mundo C++/ImGui sin añadir deps externas:

  cpp/functions/core/
    tokens       — colors/spacing/radius/font_size como constexpr +
                   apply_dark_theme() al ImGuiStyle global. Dark + indigo
                   primary (Mantine-inspired).
    badge        — etiqueta inline 6 variantes (Default/Success/Warning/
                   Error/Info/Outline). <Badge> de @fn_library en C++.
    empty_state  — placeholder centrado para tablas/listas vacías.
    page_header  — header con title + subtitle + separator + hueco
                   para acciones (patrón begin/end).

Scope limitado (KISS) a fases 1-2 del plan: tokens + 3 primitivos.
No se duplica dashboard_panel con un "card" — el existente ya cumple
el rol. Fases 3-5 (charts ImPlot line/area, app_shell con navbar,
toast/alert) quedan fuera hasta que el dashboard crezca en alcance.

Resultado:
- 869 funciones (+4) en registry.db.
- Dashboard con header homogéneo y empty states en todas las tablas.
- Sin hardcode de ImVec4 disperso en views.cpp.

Diary + CHANGELOG actualizados.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 14:52:09 +02:00
parent a5a428b231
commit fda89ca3ba
14 changed files with 570 additions and 0 deletions
+8
View File
@@ -32,6 +32,14 @@ Para contexto detallado del trabajo diario ver `docs/diary/`. Para decisiones ar
- `http_client.cpp` del dashboard: añadido `#include <cstdint>` requerido por mingw-w64 para cross-compile Windows (g++ Linux lo incluía transitivamente).
- `registry_dashboard.exe` (Windows) ya no abre ventana de consola al lanzarse — enlazado como GUI app (`WIN32_EXECUTABLE TRUE` / `-mwindows`).
### Added (design system C++)
- `cpp/functions/core/tokens` — design tokens para dashboards ImGui (colors, spacing, radius, font_size) inspirados en `@fn_library` (Mantine v9). Paleta dark + indigo primary. `apply_dark_theme()` aplica los tokens al `ImGuiStyle` global.
- `cpp/functions/core/badge` — etiqueta inline con 6 variantes (Default/Success/Warning/Error/Info/Outline). Equivalente a `<Badge>` de `@fn_library`.
- `cpp/functions/core/empty_state` — placeholder centrado para tablas/listas vacías.
- `cpp/functions/core/page_header` — header de página con título/subtítulo + hueco para acciones + separator.
- `registry_dashboard` migrado a los nuevos componentes: `page_header_begin/end` en el header, `empty_state` en las 4 tablas cuando están vacías, `apply_dark_theme()` al primer frame. Sin hardcode de colores disperso.
- `systemd_local_{enable,start,restart}`: stdout de `systemctl` redirigido a stderr para no contaminar el JSON capturado por el pipeline.
- `.gitmodules`: entry fantasma `cpp/vendor/glfw` con path absoluto `/home/lucas/...` que bloqueaba `git submodule status` y el cross-compile Windows.
+44
View File
@@ -0,0 +1,44 @@
#include "badge.h"
#include "tokens.h"
#include "imgui.h"
static ImVec4 badge_bg(BadgeVariant v) {
using namespace fn_tokens::colors;
switch (v) {
case BadgeVariant::Success: return success;
case BadgeVariant::Warning: return warning;
case BadgeVariant::Error: return error;
case BadgeVariant::Info: return info;
case BadgeVariant::Outline: return ImVec4(0, 0, 0, 0);
case BadgeVariant::Default:
default: return surface_hover;
}
}
void badge(const char* text, BadgeVariant variant) {
ImVec4 bg = badge_bg(variant);
ImVec4 tx = (variant == BadgeVariant::Outline || variant == BadgeVariant::Default)
? fn_tokens::colors::text
: ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
const float pad_x = fn_tokens::spacing::sm;
const float pad_y = fn_tokens::spacing::xs * 0.5f;
ImVec2 text_size = ImGui::CalcTextSize(text);
ImVec2 cursor = ImGui::GetCursorScreenPos();
ImVec2 p_min = cursor;
ImVec2 p_max = ImVec2(cursor.x + text_size.x + pad_x * 2.0f,
cursor.y + text_size.y + pad_y * 2.0f);
ImDrawList* dl = ImGui::GetWindowDrawList();
if (variant == BadgeVariant::Outline) {
dl->AddRect(p_min, p_max, ImGui::GetColorU32(fn_tokens::colors::border),
fn_tokens::radius::md);
} else {
dl->AddRectFilled(p_min, p_max, ImGui::GetColorU32(bg),
fn_tokens::radius::md);
}
dl->AddText(ImVec2(cursor.x + pad_x, cursor.y + pad_y),
ImGui::GetColorU32(tx), text);
ImGui::Dummy(ImVec2(p_max.x - p_min.x, p_max.y - p_min.y));
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
// Badge — etiqueta inline para estados (como <Badge> de @fn_library).
// Dibuja un rectángulo con fondo coloreado + texto. Uso en tablas, junto a
// títulos, estados de entities, etc.
enum class BadgeVariant {
Default, // surface — estado neutro
Success, // verde — tested=yes, active
Warning, // naranja — stale
Error, // rojo — failed, corrupted
Info, // azul — metadata
Outline, // transparente con borde — énfasis bajo
};
// Renderiza un badge en la posición actual del cursor. Consume un hueco
// equivalente a su tamaño (es decir, es un widget inline como Text).
void badge(const char* text, BadgeVariant variant = BadgeVariant::Default);
+67
View File
@@ -0,0 +1,67 @@
---
name: badge
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "void badge(const char* text, BadgeVariant variant = BadgeVariant::Default)"
description: "Etiqueta inline tipo badge para estados (tested/untested, pure/impure, active/stale). Equivalente al <Badge> de @fn_library. 6 variantes: Default, Success, Warning, Error, Info, Outline."
tags: [imgui, badge, ui, status, label, design-system]
uses_functions:
- tokens_cpp_core
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/badge.cpp"
framework: imgui
params:
- name: text
desc: "Texto del badge (ej: 'pure', 'tested', 'active')"
- name: variant
desc: "Variante semántica: Default | Success | Warning | Error | Info | Outline"
output: "Dibuja el badge en la posición actual del cursor y avanza el layout (igual que ImGui::Text)"
---
# badge
Etiqueta inline con fondo coloreado y texto, para mostrar estado al lado de un ítem. Usa `fn_tokens::colors` y `fn_tokens::radius` para coherencia visual con el resto del dashboard.
## Uso
```cpp
#include "core/badge.h"
// En una celda de tabla junto al nombre de una función
ImGui::TextUnformatted(fn.name);
ImGui::SameLine();
badge(fn.purity.c_str(), fn.purity == "pure"
? BadgeVariant::Success : BadgeVariant::Warning);
// En un header, junto al título de un proyecto
badge("active", BadgeVariant::Success);
ImGui::SameLine();
badge("v1.2.0", BadgeVariant::Outline);
```
## Variantes
| Variant | Caso típico |
|---------|-------------|
| `Default` | Estados neutros (tags, kind) |
| `Success` | `tested: yes`, `status: active`, `pure` |
| `Warning` | `status: stale`, pendiente |
| `Error` | `status: failed`, `corrupted` |
| `Info` | Metadata (dominio, version) |
| `Outline` | Énfasis bajo, detalles |
## Notas
- Es un widget **inline** — ocupa espacio como un `Text` y respeta `SameLine`.
- Padding y radio vienen de `fn_tokens` (no hardcodeados).
- Sin estado interno: puro render. Se puede llamar miles de veces por frame sin coste significativo.
+49
View File
@@ -0,0 +1,49 @@
#include "empty_state.h"
#include "tokens.h"
#include "imgui.h"
void empty_state(const char* icon, const char* title, const char* description) {
ImVec2 avail = ImGui::GetContentRegionAvail();
// Altura estimada del bloque: icon (2x font) + title (1x) + description (1x)
// + spacings. Se centra verticalmente dejando vertical padding arriba.
const float line = ImGui::GetTextLineHeight();
float block_h = line * 2.0f + fn_tokens::spacing::md + line;
if (description && *description) block_h += line + fn_tokens::spacing::xs;
if (avail.y > block_h) {
ImGui::Dummy(ImVec2(1.0f, (avail.y - block_h) * 0.5f));
}
// Icon (centrado, 2x tamaño vía font scale)
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::SetWindowFontScale(2.0f);
ImVec2 icon_size = ImGui::CalcTextSize(icon);
float icon_x = (avail.x - icon_size.x) * 0.5f;
if (icon_x < 0) icon_x = 0;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + icon_x);
ImGui::TextUnformatted(icon);
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor();
ImGui::Dummy(ImVec2(1.0f, fn_tokens::spacing::sm));
// Title (centrado)
ImVec2 title_size = ImGui::CalcTextSize(title);
float title_x = (avail.x - title_size.x) * 0.5f;
if (title_x < 0) title_x = 0;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + title_x);
ImGui::TextUnformatted(title);
// Description (centrado, muted)
if (description && *description) {
ImGui::Dummy(ImVec2(1.0f, fn_tokens::spacing::xs));
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImVec2 desc_size = ImGui::CalcTextSize(description);
float desc_x = (avail.x - desc_size.x) * 0.5f;
if (desc_x < 0) desc_x = 0;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + desc_x);
ImGui::TextUnformatted(description);
ImGui::PopStyleColor();
}
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
// Empty state — mensaje centrado para listas/tablas sin datos.
// Equivalente a <EmptyState> de @fn_library.
//
// Renderiza:
// - Icono (emoji o texto corto) grande y atenuado
// - Título
// - Descripción opcional en texto muted
//
// Ocupa toda la altura disponible del parent (útil dentro de un panel vacío).
void empty_state(const char* icon,
const char* title,
const char* description = nullptr);
+59
View File
@@ -0,0 +1,59 @@
---
name: empty_state
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "void empty_state(const char* icon, const char* title, const char* description = nullptr)"
description: "Estado vacío centrado para tablas/listas sin datos. Icono grande (muted) + título + descripción opcional. Equivalente a <EmptyState> de @fn_library."
tags: [imgui, empty-state, ui, placeholder, design-system]
uses_functions:
- tokens_cpp_core
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/empty_state.cpp"
framework: imgui
params:
- name: icon
desc: "Emoji o texto corto (ej: '📭', '📊', 'No data')"
- name: title
desc: "Título principal (ej: 'No apps yet')"
- name: description
desc: "Descripción opcional con instrucciones o contexto"
output: "Dibuja el bloque centrado verticalmente en el espacio disponible del parent"
---
# empty_state
Placeholder para tablas/listas sin datos. Centra icono + título + descripción en la altura disponible del contenedor actual.
## Uso
```cpp
#include "core/empty_state.h"
if (apps.empty()) {
empty_state("📭", "No apps registered",
"Run 'fn sync' to fetch apps from the server");
} else {
draw_apps_table(apps);
}
```
## Casos de uso típicos
- **Tablas sin datos**: "No functions found", "No assertions yet"
- **Búsquedas sin resultados**: "No results for 'xyz'"
- **Estados iniciales**: antes del primer `fn sync` en un PC nuevo
## Notas
- Ocupa todo el espacio vertical del parent. Si quieres un bloque compacto, envuélvelo en un `BeginChild` con alto fijo.
- El icono se escala 2x con `SetWindowFontScale`. Los emojis Unicode funcionan si el atlas de fuente los incluye (con ImGui por defecto NO incluye emojis, usar texto corto como "📭" solo si cargaste una fuente con coverage; en su defecto usar algo como `"( empty )"`).
+34
View File
@@ -0,0 +1,34 @@
#include "page_header.h"
#include "tokens.h"
#include "imgui.h"
bool page_header_begin(const char* title, const char* subtitle) {
// Título más grande con font scale
ImGui::SetWindowFontScale(1.4f);
ImGui::TextUnformatted(title);
ImGui::SetWindowFontScale(1.0f);
// Subtítulo en muted
if (subtitle && *subtitle) {
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::TextUnformatted(subtitle);
ImGui::PopStyleColor();
}
// Reservar la misma línea del título para que el caller ponga acciones
// a la derecha. SameLine sobre el último Text (título) — el caller usa
// SameLine con offset para alinear a la derecha si quiere.
// (En forma idiomática: ImGui::SameLine(ImGui::GetWindowWidth() - 120);)
return true;
}
void page_header_end() {
ImGui::Dummy(ImVec2(0.0f, fn_tokens::spacing::sm));
ImGui::Separator();
ImGui::Dummy(ImVec2(0.0f, fn_tokens::spacing::sm));
}
void page_header(const char* title, const char* subtitle) {
page_header_begin(title, subtitle);
page_header_end();
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
// Page header — título + subtítulo opcional + separator.
// Equivalente al <PageHeader> de @fn_library (versión simple).
//
// El patrón begin/end deja un hueco para que el caller inserte acciones
// (botones Refresh, etc.) alineadas a la derecha del título.
//
// Uso idiomático:
//
// if (page_header_begin("fn_registry Dashboard", "865 functions / 152 types")) {
// // opcional: acciones alineadas a la derecha
// if (ImGui::Button("Reload")) reload();
// }
// page_header_end();
//
// page_header_begin siempre devuelve true — el valor es solo estilístico
// para permitir el idiom if-block. Llamar page_header_end siempre.
//
// Forma corta sin acciones:
//
// page_header("fn_registry", "865 functions");
bool page_header_begin(const char* title, const char* subtitle = nullptr);
void page_header_end();
// Forma corta (sin acciones).
void page_header(const char* title, const char* subtitle = nullptr);
+60
View File
@@ -0,0 +1,60 @@
---
name: page_header
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "bool page_header_begin(const char* title, const char* subtitle = nullptr); void page_header_end(); void page_header(const char* title, const char* subtitle = nullptr)"
description: "Header de página con título, subtítulo opcional y separator final. Equivalente al <PageHeader> de @fn_library. Patrón begin/end permite insertar acciones (botones) en el hueco entre título y separator."
tags: [imgui, header, page-header, ui, layout, design-system]
uses_functions:
- tokens_cpp_core
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/page_header.cpp"
framework: imgui
params:
- name: title
desc: "Título principal de la página (font scale 1.4x)"
- name: subtitle
desc: "Subtítulo opcional en texto muted"
output: "Renderiza título + subtítulo + separator. page_header_begin devuelve true siempre (idiom estilístico)."
---
# page_header
Header consistente para dashboards y páginas ImGui. Título destacado, subtítulo en muted, separador abajo. Entre medias hay un hueco para que el caller inserte acciones (botones Refresh, filtros, etc.).
## Uso
### Con acciones
```cpp
#include "core/page_header.h"
page_header_begin("fn_registry Dashboard",
"865 functions · 152 types · 12 apps");
ImGui::SameLine(ImGui::GetWindowWidth() - 120.0f);
if (ImGui::Button("Reload")) { trigger_reload(); }
page_header_end();
```
### Sin acciones (forma corta)
```cpp
page_header("Settings", "Application preferences");
```
## Notas
- El título se renderiza con `SetWindowFontScale(1.4f)` — si tu atlas incluye varias fuentes, sustituir por PushFont con una fuente más grande para mejor rendering.
- El subtítulo usa `fn_tokens::colors::text_muted`.
- El separator y el espaciado antes/después son determinísticos (spacing::sm) para consistencia entre páginas.
- Si necesitas tabs integrados en el header (como `PageHeader` en React con `tabs={...}`), componer con `tab_container` debajo del `page_header_end`.
+50
View File
@@ -0,0 +1,50 @@
#include "tokens.h"
namespace fn_tokens {
void apply_dark_theme() {
ImGuiStyle& s = ImGui::GetStyle();
// Colors
s.Colors[ImGuiCol_WindowBg] = colors::bg;
s.Colors[ImGuiCol_ChildBg] = colors::surface;
s.Colors[ImGuiCol_PopupBg] = colors::surface;
s.Colors[ImGuiCol_FrameBg] = colors::surface;
s.Colors[ImGuiCol_FrameBgHovered] = colors::surface_hover;
s.Colors[ImGuiCol_FrameBgActive] = colors::surface_hover;
s.Colors[ImGuiCol_Border] = colors::border;
s.Colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
s.Colors[ImGuiCol_Text] = colors::text;
s.Colors[ImGuiCol_TextDisabled] = colors::text_dim;
s.Colors[ImGuiCol_Button] = colors::primary;
s.Colors[ImGuiCol_ButtonHovered] = colors::primary_hover;
s.Colors[ImGuiCol_ButtonActive] = colors::primary;
s.Colors[ImGuiCol_Header] = colors::surface_hover;
s.Colors[ImGuiCol_HeaderHovered] = colors::primary;
s.Colors[ImGuiCol_HeaderActive] = colors::primary_hover;
s.Colors[ImGuiCol_Tab] = colors::surface;
s.Colors[ImGuiCol_TabHovered] = colors::primary_hover;
s.Colors[ImGuiCol_TabActive] = colors::primary;
s.Colors[ImGuiCol_Separator] = colors::border;
s.Colors[ImGuiCol_TableHeaderBg] = colors::surface_hover;
s.Colors[ImGuiCol_TableBorderLight]= colors::border;
s.Colors[ImGuiCol_TableBorderStrong]= colors::border;
// Radius
s.FrameRounding = radius::sm;
s.ChildRounding = radius::md;
s.WindowRounding = radius::md;
s.PopupRounding = radius::md;
s.TabRounding = radius::sm;
s.GrabRounding = radius::sm;
s.ScrollbarRounding = radius::md;
// Spacing
s.ItemSpacing = ImVec2(spacing::sm, spacing::sm);
s.ItemInnerSpacing = ImVec2(spacing::xs, spacing::xs);
s.FramePadding = ImVec2(spacing::sm, spacing::xs + 2.0f);
s.WindowPadding = ImVec2(spacing::md, spacing::md);
s.CellPadding = ImVec2(spacing::sm, spacing::xs);
}
} // namespace fn_tokens
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "imgui.h"
// Design tokens — colores, spacing, radius, font-size.
// Inspirados en el DESIGN_SYSTEM de @fn_library (Mantine v9 dark + indigo primary).
// Reemplaza hardcode disperso de ImVec4(...) por constantes semánticas.
namespace fn_tokens {
namespace colors {
// Primary (indigo-inspired, Mantine indigo.6)
constexpr ImVec4 primary {0.25f, 0.37f, 0.85f, 1.0f};
constexpr ImVec4 primary_hover {0.30f, 0.42f, 0.90f, 1.0f};
// Semantic
constexpr ImVec4 success {0.13f, 0.70f, 0.42f, 1.0f};
constexpr ImVec4 warning {0.95f, 0.60f, 0.20f, 1.0f};
constexpr ImVec4 error {0.87f, 0.26f, 0.30f, 1.0f};
constexpr ImVec4 info {0.22f, 0.55f, 0.95f, 1.0f};
// Background (dark by default — matches DESIGN_SYSTEM.md §2 & §8)
constexpr ImVec4 bg {0.08f, 0.08f, 0.10f, 1.0f}; // window bg
constexpr ImVec4 surface {0.12f, 0.12f, 0.15f, 1.0f}; // panels/cards
constexpr ImVec4 surface_hover {0.16f, 0.16f, 0.20f, 1.0f};
// Text
constexpr ImVec4 text {0.95f, 0.95f, 0.95f, 1.0f};
constexpr ImVec4 text_muted {0.60f, 0.60f, 0.65f, 1.0f};
constexpr ImVec4 text_dim {0.40f, 0.40f, 0.45f, 1.0f};
// Border
constexpr ImVec4 border {0.20f, 0.20f, 0.25f, 1.0f};
}
namespace spacing {
constexpr float xs = 4.0f;
constexpr float sm = 8.0f;
constexpr float md = 12.0f;
constexpr float lg = 16.0f;
constexpr float xl = 24.0f;
}
namespace radius {
constexpr float none = 0.0f;
constexpr float sm = 3.0f;
constexpr float md = 5.0f;
constexpr float lg = 8.0f;
constexpr float xl = 12.0f;
}
namespace font_size {
constexpr float xs = 10.0f;
constexpr float sm = 12.0f;
constexpr float md = 14.0f; // default
constexpr float lg = 18.0f;
constexpr float xl = 24.0f;
constexpr float xxl = 32.0f;
}
// Aplica los tokens al ImGuiStyle global. Llamar una vez al arrancar la app,
// después de ImGui::CreateContext() y antes del primer frame.
void apply_dark_theme();
} // namespace fn_tokens
+58
View File
@@ -0,0 +1,58 @@
---
name: tokens
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "namespace fn_tokens { namespace colors/spacing/radius/font_size { constexpr ... }; void apply_dark_theme(); }"
description: "Design tokens (colors, spacing, radius, font_size) para dashboards ImGui. Inspirados en @fn_library (Mantine v9) — dark theme con indigo primary. Reemplaza hardcode de ImVec4(...) por constantes semánticas."
tags: [imgui, theme, tokens, colors, spacing, radius, dark, design-system]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/tokens.cpp"
framework: imgui
params: []
output: "Tokens constexpr accesibles como fn_tokens::colors::*, spacing::*, radius::*, font_size::*. apply_dark_theme() aplica los tokens al ImGuiStyle global."
---
# tokens
Design tokens para todos los dashboards ImGui del registry. Traducción del DESIGN_SYSTEM.md de `@fn_library` al mundo C++/ImGui: mismos principios (dark-first, paleta semántica, escala consistente de espaciado y radios) pero con `constexpr ImVec4` en lugar de CSS variables.
## Namespaces
| Namespace | Valores |
|-----------|---------|
| `fn_tokens::colors` | `primary`, `primary_hover`, `success`, `warning`, `error`, `info`, `bg`, `surface`, `surface_hover`, `text`, `text_muted`, `text_dim`, `border` |
| `fn_tokens::spacing` | `xs=4`, `sm=8`, `md=12`, `lg=16`, `xl=24` (px) |
| `fn_tokens::radius` | `none=0`, `sm=3`, `md=5`, `lg=8`, `xl=12` (px) |
| `fn_tokens::font_size` | `xs=10`, `sm=12`, `md=14`, `lg=18`, `xl=24`, `xxl=32` (px) |
## Uso
```cpp
#include "core/tokens.h"
// Al arrancar la app (una vez, después de ImGui::CreateContext)
fn_tokens::apply_dark_theme();
// En componentes
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
ImGui::PopStyleColor();
```
## Notas
- **Dark by default** como en el DESIGN_SYSTEM (§2, §8). Si algún día queremos light, se añade `apply_light_theme()`.
- Los valores semánticos (success/warning/error/info) se usan en `badge`, `kpi_card` para deltas, y gráficos de estado.
- **No duplicar** estas constantes en componentes — siempre importar de aquí. Si se detecta un `ImVec4` hardcodeado en un componente del registry es candidato a migrar.
- Compatible con `plot_theme_cpp_core` (para ImPlot charts) — los colors del palette se pueden derivar de estos tokens si se quiere coherencia total.
+16
View File
@@ -61,3 +61,19 @@ El `.exe` del escritorio abría una ventana de consola negra al lanzarlo (mingw
- Hecho: `WIN32_EXECUTABLE TRUE` en el `CMakeLists.txt` del target `registry_dashboard` → mingw pasa `-mwindows` (subsystem:windows).
- Hecho: rebuild Windows + copy al Desktop reemplazando el anterior (hash `e361387f...`).
- Commit en subrepo del dashboard.
## 14:50 — Design system C++ (fases 1 y 2)
Trasladar principios del DESIGN_SYSTEM.md de `@fn_library` (Mantine/React) al dashboard ImGui sin añadir deps externas.
- Hecho: `cpp/functions/core/tokens.{h,cpp,md}` — namespace `fn_tokens` con `colors`, `spacing`, `radius`, `font_size` (constexpr) + `apply_dark_theme()` que aplica los tokens al `ImGuiStyle` global. Paleta dark + indigo primary (Mantine-inspired).
- Hecho: `cpp/functions/core/badge.{h,cpp,md}` — etiqueta inline con 6 variantes (Default/Success/Warning/Error/Info/Outline). Equivalente a `<Badge>` de `@fn_library`.
- Hecho: `cpp/functions/core/empty_state.{h,cpp,md}` — placeholder centrado para tablas vacías.
- Hecho: `cpp/functions/core/page_header.{h,cpp,md}` — header con título/subtítulo + hueco para acciones + separator (patrón begin/end).
- Hecho: migrado `views.cpp` para usar `page_header_begin/end` en lugar de `Text + Separator + Button` manual; `empty_state` en las 4 tablas cuando están vacías; `apply_dark_theme` al primer frame.
- Hecho: `CMakeLists.txt` del dashboard añade los 4 nuevos .cpp.
- Hecho: build Linux (OK) + Windows (OK, hash `772b0aef...`), copiado a Desktop.
- Hecho: `fn index` pasa de 865 → 869 funciones.
- Fix yaml en `tokens.md`: `params:` con item `- name: -` rompía el parser YAML (el `-` colisionaba con el array marker). Cambiado a `params: []`.
**Scope respetado (KISS)**: sólo fases 1+2 del plan propuesto — no se crearon card ni line_chart ni app_shell ni sistema de toast. Se mantiene `dashboard_panel` existente en vez de duplicar con un `card`.