# 2026-04-25

## 15:32 — shaders_lab fase 7 (UX node editor + correcciones DAG)

Pulido de la edición visual del DAG y corrección de fugas en el render. Coexiste con la Fase 6 (menubar View + Layouts) que añadió otro agente en paralelo.

### Hecho — UX nodos

- Hecho: nodos más grandes para conectar más rápido. `PIN_RADIUS` 9 → 14, `CABLE_THICK` 2.5 → 3.5, `CONTROL_WIDTH` 150 → 220, `COL_GAP` 8 → 14, `NodePadding` vertical 8 → 12, espaciado inicial entre nodos 220 → 320.
- Hecho: bug fix `solid` "sin nombre ni parámetro". El control `Color` se renderizaba con `ImGuiColorEditFlags_NoLabel`. Ahora `TextUnformatted(label) + SameLine` antes del swatch (aplica a todos los Color, no solo a `solid`).

### Hecho — Drop comportamiento (paleta + nodos del canvas)

- Hecho: drop sobre nodo del **mismo `DagKind`** (no Output) → replace. Conserva `id`, `editor_uid`, posición, `source_ids[]`, `preview_open`. Limpia slots de input que sobran.
- Hecho: drop sobre cable → splice. `new.source_ids[0] = src.id`, `dst.source_ids[slot] = new.id`. Hit-test = distancia punto-segmento entre cursor y línea (`src.right_mid → dst.left_at_slot_k`). Threshold 18 px canvas-space.
- Hecho: arrastrar **nodo Op/Blend ya colocado** sobre cable también splica. Tracking via `s_drag_existing_uid` (set en `IsMouseClicked(0)` con node hovered y sin pin hovered). Al soltar, refs hacia el nodo movido se limpian antes del rewire (queda exclusivamente en la nueva posición).
- Hecho: prioridad cable-hit > node-hit > add-vacío.

### Hecho — Splice highlight (preview visual)

- Hecho: cable candidato se pinta `SPLICE_COLOR (1.00, 0.82, 0.18, 1)` + `CABLE_THICK + 2` en `ed::Link()`.
- Hecho: además se redibuja como bezier dorado en `ImGui::GetForegroundDrawList()` (canvas → screen via `ed::CanvasToScreen`) para no depender del compositing interno de imgui-node-editor.
- Hecho: detección sin gates. La versión inicial gateaba con `IsMouseDown` + `window_hovered` y silenciaba el highlight. Basta payload `DAG_NODE_TYPE` (paleta) o `s_drag_existing_uid` (nodo del canvas).

### Hecho — Correctness DAG

- Hecho: **strict output** en `compile_dag_to_glsl`. Eliminado el fallback `last_valid_out` que filtraba el output del último nodo cuando `Output` no tenía source o no existía. Ahora regla: solo se emite lo conectado al `Output`; en cualquier otro caso `seed()` (gris oscuro). El `resolve()` interno de inputs también devuelve `vec4(0,0,0,1)` para slots vacíos (antes caía a `last_valid_out`).
- Hecho: `dag_uniforms_apply` resetea `u_preview_target = -1` al final, para que la rama de preview quede desactivada en el render principal del Canvas DAG.
- Hecho: nuevo `compile_dag_to_glsl_baked(pipeline)` en `dag_compile.{h,cpp}`. Sustituye `uniform vec4 u_params[64]` por `const vec4 u_params[N] = vec4[N](...)` con valores actuales empaquetados, y `uniform int u_preview_target` por `const int u_preview_target = -1`. El panel `Generated GLSL` muestra esta variante: pegarla en el editor `Code` reproduce el render del DAG en el momento del copy, sin depender de uniforms externos. Edits posteriores al DAG no afectan al Code.
- Hecho: `Canvas Code` ya NO recibe `dag_uniforms_apply` (versión anterior lo acoplaba). Es totalmente independiente del DAG.

### Hecho — Tests + builds

- Hecho: `dag_compile` 6/6 → **8/8** (test 4b strict output sin Output, test 7 baked variant).
- Hecho: `dag_catalog` 8/8 (19 nodos), `code_to_generator` 7/7, `uniform_parser` 6/6, `shaderlab_db` 7/7. Total **35 asserts** en gfx tras la fase.
- Hecho: build linux + windows OK. `./fn index` pasa a 899 funciones.
- Hecho: sync de `shaders_lab.exe` a `apps/shaders_lab/` (in-repo) y a `/mnt/c/Users/lucas/Desktop/`. MD5 final `33fa56c60584adc074679d39f307e19e`.

### Hecho — Operativo

- Hecho: regla nueva en memoria persistente — `feedback_no_adminlocal.md`. **Nunca** copiar binarios Windows a `/mnt/c/Users/AdminLocal/`. Solo `/mnt/c/Users/lucas/Desktop/`. Borrado el `shaders_lab.exe` que quedaba allí.
- Hecho: `cpp/CMakeLists.txt` centraliza `sqlite3_vendored` (top-level) para que `shaders_lab` y `registry_dashboard` lo compartan sin colisión de target. Ambos CMakeLists internos usan `find_package(SQLite3 QUIET)` + `if (NOT SQLite3_FOUND AND NOT TARGET sqlite3_vendored)`.

### Pendiente

- Pendiente: panel `View` con listado / borrado de generators custom desde la UI (hoy solo via DB directa).
- Pendiente: botón `Push to registry` que extrae un generator custom a `cpp/functions/gfx/<name>.{cpp,md}` con tag `shaders_lab` y dispara `fn index`.
- Pendiente: persistencia de pipelines con nombre.
- Pendiente: `Save as Op` (1 input `a`) y `Save as Blend` (2 inputs).

### Referencias

- App: `apps/shaders_lab/app.md` — Fase 7 documentada en `## Estado actual`.
- Funciones tocadas: `dag_node_editor_cpp_gfx`, `dag_compile_cpp_gfx`, `dag_uniforms_cpp_gfx`, `dag_catalog_cpp_gfx`. Notas appended en cada `.md`.
- Funciones nuevas (Fase 5, ya documentadas): `code_to_generator_cpp_gfx`, `shaderlab_db_cpp_gfx`.
- Memoria: `feedback_no_adminlocal.md`.

## 15:33 — Projects view + mutaciones desde el dashboard, gallery de primitivos

Sesion larga repartida en tres fases (plan acordado al principio): primitivos UI nuevos en `cpp/functions/core/`, endpoints de mutacion en `sqlite_api`, e integracion en el dashboard. Cierra con la primera capa del plan de tests (gallery interactiva).

### Hecho — Fase A (9 primitivos UI nuevos)

Todos con `.cpp + .h + .md`, compilando Linux + MinGW posix:

- `button_cpp_core`, `icon_button_cpp_core`, `toolbar_cpp_core` — interaccion basica con tokens.
- `modal_dialog_cpp_core`, `text_input_cpp_core`, `select_cpp_core` — formularios.
- `toast_cpp_core` v1.1 — push thread-safe + render por frame + `toast_inbox_button` con badge no-leidos + popover con historial 50 entradas.
- `process_runner_cpp_core` — `std::thread` + `std::atomic<int> state` + widget `runner_status` con spinner.
- `tree_view_cpp_core` — low-level (`tree_leaf` / `tree_branch_begin/end` / `tree_node_clicked`); el caller gestiona la seleccion.

**Toolchain MinGW**: switch de `win32` a `posix` (`x86_64-w64-mingw32-g++-posix`) para que `std::mutex/std::thread` compilen. Linker: `-static-libgcc -static-libstdc++ -static -lwinpthread`. Sin esto, toast/process_runner no compilan en Windows. Configurado en `cpp/toolchains/mingw-w64.cmake`.

### Hecho — Fase B (sqlite_api v0.2)

Split de handlers en 3 ficheros: `handlers.go` (read-only previo), `handlers_projects.go` (nuevo), `handlers_mutations.go` (nuevo).

- `GET /api/projects` — proyectos con conteos `apps_count/analyses_count/vaults_count` (subqueries) + bloque `orphans`.
- `GET /api/projects/{id}` — detalle apps/analyses/vaults; `id="orphans"` para huerfanas. Helper `scanAll(ctx, db, query, arg)`.
- `POST /api/reindex` — `exec.CommandContext` de `{registryRoot}/fn index` con timeout 60 s.
- `POST /api/add/app` — body `{name, lang, domain, project, description}` → crea dir + `app.md` minimo + reindex.
- `POST /api/add/analysis` — invoca pipeline `init_jupyter_analysis` via `fn run`.
- `POST /api/add/vault` — solo dentro de proyecto. Crea dir/symlink en `projects/{p}/vaults/` + entry append en `vault.yaml`.

`Server` gana campo `registryRoot string`. `NewServer(pool, root)`. Tests adaptados con `sed -i 's/NewServer(pool)/NewServer(pool, "")/g'`. Reiniciado: `systemctl --user restart sqlite_api`. Smoke OK.

### Hecho — Fase C (dashboard integration)

- **Actions bar** en page header (`fn_ui::toolbar`): `Reindex` (Primary) → `process_runner` con `http_post_reindex`; `+ Add` → modal; `Reload`; `toast_inbox_button("##inbox")`.
- **Modal Add** con kind selector (App/Analysis/Vault), `select` de proyecto, `text_input` Name + Description, campos especificos por kind. Submit dispara endpoint con `process_runner` + toast + reload.
- **Tab Projects** (`tree_view` izquierda + tabs derecha): proyectos + entrada "(orphans)"; click → `load_project_detail_http`.

`RegistryData` gana `projects[]`, `orphan_apps`, `orphan_analyses`, `orphan_vaults`. Tipos nuevos `ProjectRow`, `VaultRow`, `ProjectDetail`. `data_http.cpp` gana `load_projects_http`, `load_project_detail_http`, `http_post_{reindex,add_app,add_analysis,add_vault}`. `AnalysisRow` gana `lang` (query SQL pasa de 4 cols a 5). `views_set_api_url(url)` invocado desde main.

### Bug fix — vibracion al redimensionar

Tres causas combinadas:

- `fullscreen_window` v0.2: `NoScrollbar | NoScrollWithMouse` para evitar scrollbar fugaz cuando el contenido excede la ventana por 1-2 px (reflow del ancho ~14 px).
- `views.cpp::draw_dashboard`: altura de charts pasa de `GetContentRegionAvail().y * 0.35` a constante 260 px. La proporcion relativa propagaba el resize a todos los plots.
- `kpi_card` v1.2: 78 px fijo + scale 1.4 + padding sm + `NoScrollbar`. Antes `AutoResizeY` × 8 cards × cada frame era el lag.

### Bug fix — Reindex POST con timeout 5 ms en Windows

`http_client.cpp::request()` pasaba `struct timeval` (8 bytes en x86_64) a `setsockopt(SO_RCVTIMEO)` en Windows, que MSDN especifica como `DWORD` ms. Resultado: 5 ms efectivos en lugar de 5 s. Critico en POST desde threads de background. Fix: rama `_WIN32` con `DWORD timeout_ms = timeout_sec * 1000`. Tambien `wsa_init` envuelto en `std::call_once` para evitar race entre main thread + runners.

### Bug fix — toast vacio + popup en otra pantalla

- `post_json` ahora siempre escribe en `out_body`: extrae `output` del JSON en exito; sintetiza `"connect() failed to host:port (err=N)"` con codigo Winsock real en error de conexion. Em dash `—` (U+2014) sustituido por `:` ASCII (la fuente default no tenia el codepoint y rendia como `?`).
- `toast_inbox_button` antes usaba `btn_pos.x - 332` que podia caer fuera del `WorkRect`; con `viewports = true` ImGui lo enviaba a otra ventana del OS. Fix: clamp al `WorkRect`, `ImGuiCond_Appearing` (no `Always`), `SetNextWindowViewport(vp->ID)`.

### Hecho — Gallery + plan de tests

Implementada Capa 1 del plan de tests (Gallery + doctest + TestEngine + snapshot PNG):

- **`cpp/apps/primitives_gallery/`** — app dev con sidebar de 19 primitivos + panel con demo + snippet por cada uno. Linkea todos los `core/*.cpp` y `viz/*.cpp` relevantes; al estar en `cpp/CMakeLists.txt` actua como build gate. 3 ficheros de demos: `demos_core.cpp` (13 primitivos), `demos_viz.cpp` (6 charts), `demos_graph.cpp` (graph_viewport, fichero aparte).
- **Demo `graph_viewport`** con sliders `Nodes` (100–20 000), `Clusters` (2–16), `Repulsion` (100–20 000), `Attraction` (0.001–0.5), `Gravity` (0.0–0.05), aplicados en vivo a `ForceLayoutConfig`. Stats en una sola linea fija (`hover` y `sel` siempre presentes con `-`, sin filas condicionales que vibren).
- **`README.md`** propio en `cpp/apps/primitives_gallery/` (primer .md en `cpp/apps/*/`; `chart_demo` y `shaders_lab` no tienen).
- `gl_loader_cpp_gfx` v1.1: ampliado con 11 funciones (`glBufferData`, `glDrawArraysInstanced`, `glEnableVertexAttribArray`, `glVertexAttribDivisor`, `glVertexAttribPointer`, `glBindRenderbuffer`, `glDeleteRenderbuffers`, `glFramebufferRenderbuffer`, `glGenRenderbuffers`, `glRenderbufferStorage`, `glFramebufferTexture`) — necesarias para que `graph_renderer` compile en cross-compile MinGW.
- `graph_renderer_cpp_viz` v1.1: cambia raw `<GL/glext.h>` por `gfx/gl_loader.h`. Sin cambios funcionales (Linux bit-equivalente). Cualquier app que use `graph_renderer` debe linkear `gl_loader.cpp` y llamar `fn::gfx::gl_loader_init()` una vez tras crear el contexto GL.

### Investigacion paralela

Pregunta del usuario: motor de grafos eficiente en C++ con miles de circulos compatible con ImGui. Recomendacion: hasta 50 k nodos el stack del registry (`graph_renderer` instanced + `graph_force_layout` Barnes-Hut + `graph_spatial_hash` + `graph_viewport`) es la opcion correcta. `imgui-node-editor` (vendoreado) NO es para visualizacion masiva sino para edicion de DAGs (caso del shaderlab); ImPlot scatter es alternativa rapida si no necesitas aristas; OGDF si Barnes-Hut se queda corto. La gallery sirve como benchmark rapido para validar rendimiento real.

### Pendiente

- Vendora `doctest` (single-header, MIT) + 6 tests de logica + `ctest` (Capa 2 del plan): `label_stride` (bar_chart), `slice_at` (pie_chart), `process_runner` transitions, `toast` queue, `tokens` sanity, `parse_url` (http_client).
- Exponer `label_stride` y `slice_at` fuera de `namespace { ... }` para que sean testables.
- `loginctl enable-linger lucas` para que `sqlite_api.service` (user systemd) sobreviva al logout (decision pendiente).
- Filtros interactivos en sidebar (filtrar tablas Apps/Functions/Types por proyecto).
- Permisos/auth en mutaciones de `sqlite_api` si se considera deploy a VPS.

### Referencias

- `cpp/functions/gfx/gl_loader.md` v1.1 (tabla de cobertura).
- `cpp/functions/viz/graph_renderer.md` v1.1.
- `cpp/functions/core/toast.md` v1.1.
- `projects/fn_monitoring/project.md` — seccion Estado actual con Fase projects view, bug fixes, gallery sibling.
- `projects/fn_monitoring/apps/sqlite_api/app.md` — endpoints `[v0.2]` + Estado actual.
- `projects/fn_monitoring/apps/registry_dashboard/app.md` — Fase actions bar + bug fixes + toolchain MinGW.
- `cpp/apps/primitives_gallery/README.md` — nuevo.
- Commits en sesiones previas (mismo dia): `3f622561` (cpp/viz static-plot + tooltips), `b828fd6a` (kpi v1.2 + bar v1.1), `d6bdab8` en `dataforge/registry_dashboard`. Cambios de esta sesion en working tree, sin commit todavia.
- Reindex final: 899 funciones, 153 types, 13 apps, 2 analysis, 3 projects, 1 vaults, 1293 unit_tests.

## 15:51 — cpp icons via Tabler (TI_* macros, atlas auto-cargado)

Glyphs en apps C++ no renderizaban (cuadritos vacios) porque ImGui usa ProggyClean default sin codepoints simbolicos. Integrado set Tabler v3.41.1 (espejo del frontend `@tabler/icons-react`).

- Hecho: vendoreado `cpp/vendor/tabler-icons/tabler-icons.ttf` (2.7 MB, MIT) + LICENSE + README + `gen_header.py` para regenerar.
- Hecho: generado `cpp/functions/core/icons_tabler.h` con 5093 macros `TI_*` (TI_PLUS, TI_TRASH, TI_REFRESH, TI_DEVICE_FLOPPY, TI_SETTINGS, ...) en formato `#define TI_NAME "\xee\xac\x8b"`.
- Hecho: nueva funcion del registry `icon_font_cpp_core` (impure, error_go_core) con `fn_ui::load_default_fonts(text_px=15, icon_px=14)` — carga texto default + mergea Tabler en el mismo `ImFont` con `MergeMode + GlyphMinAdvanceX = icon_px + GlyphOffset.y = 1` (range U+E000..U+FCFF).
- Hecho: hook en `cpp/framework/app_base.cpp` — llama `fn_ui::load_default_fonts()` justo despues de `ImGui::CreateContext()`. Cualquier app que use `fn::run_app` obtiene los iconos sin tocar nada.
- Hecho: `cpp/CMakeLists.txt` — capturado `FN_CPP_ROOT_DIR` a nivel root, define `FN_CPP_ROOT` en `fn_framework`, y `add_imgui_app` añade post-build copy de la TTF junto al exe (no depende de FN_CPP_ROOT en runtime, portable).
- Hecho: path resolver en runtime busca la TTF en orden: `./tabler-icons.ttf` → `./assets/` → `$FN_ASSETS_DIR` → `${FN_CPP_ROOT}/vendor/tabler-icons/`. Fallback a stderr + solo texto si no la encuentra.
- Hecho: `demos_core.cpp` (gallery) — `demo_icon_button` y `demo_toolbar` migrados de hex UTF-8 a `TI_*` (12 iconos en la fila: TI_REFRESH, TI_PLUS, TI_TRASH, TI_CHEVRON_DOWN, TI_SETTINGS, TI_CHECK, TI_X, TI_PENCIL, TI_DEVICE_FLOPPY, TI_SEARCH, TI_HELP, TI_HOME). Botones text+icon: `button(TI_PLUS " New", V::Primary)`.
- Hecho: regla en `cpp/DESIGN_SYSTEM.md` seccion 11 — uso obligatorio de `TI_*`, anti-patrones (hex UTF-8, emojis, otras icon fonts), upgrade procedure, checklist.
- Hecho: notas en `cpp/functions/core/icon_button.md` (la tabla hex original queda como historica) y `cpp/apps/primitives_gallery/README.md` (`## Iconos en los demos`).
- Hecho: memoria persistente `feedback_cpp_icons.md` para futuras sesiones.

Bug + fix:
- `add_imgui_app` resolvia `${CMAKE_CURRENT_SOURCE_DIR}/vendor/tabler-icons/...` desde el dir del caller (no del root). → Capturar `FN_CPP_ROOT_DIR` con `set(... CACHE INTERNAL)` a nivel root y usarlo dentro de la funcion.
- Indexer rechazo `icon_font` como impuro sin error_type → añadido `error_type: error_go_core` (convencion cpp del registry).

Build verificado:
```bash
cd cpp/build/linux && cmake --build . --target primitives_gallery
# 100% Built target primitives_gallery
ls apps/primitives_gallery/tabler-icons.ttf  # copiado post-build
```

Pendiente:
- Recompilar Windows cross (`cpp/build/windows/`) para apps que se distribuyan a /mnt/c/Users/lucas/Desktop — el `add_imgui_app` ya copia la TTF, basta `cmake --build cpp/build/windows --target <app>`.
- Auditar el resto de apps (`cpp/apps/shaders_lab/main.cpp`, `dataforge/*`) por hex UTF-8 inline o emojis y migrar a `TI_*`.
- Si el atlas completo (5093 glyphs a 14 px) resulta pesado en VRAM, recortar `tabler_ranges[]` en `icon_font.cpp` a los rangos efectivamente usados (U+EA5E..U+EC00 cubre la mayoria comun).

Reindex: 900 funciones (+1 icon_font_cpp_core), 153 types, 13 apps. Working tree con cambios sin commit.

## 16:05 — Cross-build Windows + sync a Desktop/apps

Recompilados los 3 .exe afectados por el cambio de fuentes en `app_base.cpp` y desplegados al escritorio Windows del usuario (donde ejecuta las apps nativas, ya que en WSL no hay GUI).

- Hecho: `cmake --build cpp/build/windows --target primitives_gallery registry_dashboard shaders_lab -j$(nproc)` — los 3 binarios MinGW listos en `cpp/build/windows/apps/<app>/`.
- Hecho: `add_imgui_app` ya copia `tabler-icons.ttf` junto al exe post-build (verificado, 2.7 MB en cada dir).
- Hecho: sync a `/mnt/c/Users/lucas/Desktop/apps/<app>/` para los tres apps. Estado final por dir:
  - `primitives_gallery/`: `primitives_gallery.exe` (15.2 MB) + `tabler-icons.ttf`
  - `registry_dashboard/`: `registry_dashboard.exe` (20.6 MB) + `tabler-icons.ttf` + `imgui.ini` (preservado)
  - `shaders_lab/`: `shaders_lab.exe` (23.0 MB) + `tabler-icons.ttf` + `imgui.ini` + `shaders_lab.db` (preservados)
- Hecho: actualizada memoria persistente `feedback_no_adminlocal.md` con la convencion **WSL → Desktop/apps**: el destino canonico es `/mnt/c/Users/lucas/Desktop/apps/<app>/` (no la raiz del Desktop), y SIEMPRE hay que copiar exe + assets adyacentes (TTF). Sin la TTF los `TI_*` salen como cuadritos.

Convencion documentada (memoria + diario):
```bash
APP=shaders_lab
cmake --build cpp/build/windows --target $APP -j$(nproc)
cp cpp/build/windows/apps/$APP/{$APP.exe,tabler-icons.ttf} /mnt/c/Users/lucas/Desktop/apps/$APP/
```

Pendiente:
- Probar cada app en Windows y confirmar que los iconos `TI_*` rinden correctamente (usuario).
- Si algun app C++ futuro mete assets adicionales al lado del exe (texturas, shaders, .db seed), hay que añadirlos al cp tambien — el patron post-build de `add_imgui_app` solo cubre la TTF de Tabler.

## 16:25 — Fix regresion de tamaño + tipografia vectorial

El cambio de las 15:51 metio una regresion: `load_default_fonts(text_px=15, icon_px=14)` cargaba ProggyClean default a 15 px. ProggyClean es bitmap (13 px nativo), asi que en todas las apps el texto salia mas grande Y borroso/escalado. Ademas firma con dos parametros de tamaño distintos rompia line-height al mezclar texto+icono.

- Hecho: `icon_font.h` — firma simplificada a `void load_default_fonts(float size_px = 13.0f)`. Un solo tamaño compartido por texto e iconos = line-height uniforme.
- Hecho: `icon_font.cpp` reescrita — carga **Karla-Regular** (vectorial, 17 KB, vendoreado por ImGui en `vendor/imgui/misc/fonts/`) en lugar de ProggyClean. OversampleH=2 para nitidez. Mergea Tabler al mismo tamaño. Si Karla no se encuentra, fallback a `AddFontDefault()` (ProggyClean) para no romper la app.
- Hecho: helper `find_asset(filename, repo_subpath)` factorizado — mismo path resolver para los dos TTFs (./, ./assets/, $FN_ASSETS_DIR, ${FN_CPP_ROOT}/<subpath>).
- Hecho: nueva API `fn_ui::text_font_loaded()` ademas de `tabler_font_loaded()` para diagnostico.
- Hecho: `cpp/CMakeLists.txt` `add_imgui_app` — el post-build copy ahora copia AMBAS TTFs (Karla y Tabler) junto al exe.
- Hecho: actualizadas `cpp/functions/core/icon_font.md`, `cpp/DESIGN_SYSTEM.md` seccion 11 (path resolution), y `feedback_no_adminlocal.md` (lista completa de assets a sincronizar).
- Hecho: recompilados los 3 binarios Linux + Windows. Sincronizados a `/mnt/c/Users/lucas/Desktop/apps/<app>/` con `Karla-Regular.ttf` + `tabler-icons.ttf` al lado.

Estado final binarios Windows en Desktop/apps:
- `primitives_gallery/`: exe (15.2 MB) + Karla (17 KB) + Tabler (2.7 MB)
- `registry_dashboard/`: exe (20.6 MB) + Karla + Tabler + imgui.ini preservado
- `shaders_lab/`: exe (23.0 MB) + Karla + Tabler + imgui.ini + shaders_lab.db preservados

Pendiente:
- Probar en Windows: texto debe verse igual o mejor que antes (Karla 13 px vectorial vs ProggyClean 13 px bitmap), iconos `TI_*` renderizan.
- Si Karla no encaja (estilo poco "Geist"-like), evaluar bajar Geist Sans desde `vercel/geist-font` y vendoreara en `cpp/vendor/fonts/`. Karla es la opcion KISS (cero descarga) pero no es exactamente el espejo del frontend.

## 17:30 — Settings window con FPS toggle + font picker (todas las apps)

Texto a 13 px se sentia pequeño, y queriamos que el usuario pudiera elegir fuente/tamaño y togglear el FPS overlay sin recompilar. Implementado un sistema de settings reutilizable por todas las apps C++ del registry.

- Hecho: nueva funcion `app_settings_cpp_core` (impure, error_go_core) — estado global `AppSettings { show_fps, font_id, font_size_px }`, persistencia en `<cwd>/app_settings.ini`, ventana flotante con `CollapsingHeader` para Display + Typography + secciones extra registrables, auto-save al cambiar.
- Hecho: API publica:
  - `settings()` → ref mutable
  - `settings_load() / settings_save()` → INI I/O
  - `settings_window_set_open(bool) / toggle()`
  - `settings_window_menu_item("Settings...")` → MenuItem componible
  - `settings_window_render()` → render no-op si cerrada
  - `settings_window_add_section(id, title, callback)` → extender desde apps
  - `settings_mark_font_dirty() / consume_font_dirty()` → coordinacion con icon_font
- Hecho: `icon_font` reescrita — `load_fonts_from_settings()` lee `font_id` del settings y carga la TTF correspondiente (Karla / Roboto / DroidSans / Cousine / ProggyClean). Default global `font_size_px = 15` (antes 13, se sentia pequeño).
- Hecho: `app_base.cpp` orquesta:
  1. `settings_load()` antes de cargar fuentes
  2. `load_fonts_from_settings()` aplica el font del .ini
  3. Cada frame: si `consume_font_dirty()`, `io.Fonts->Clear()` + reload (ImGui 1.92+ refresca GPU texture solo via `UpdateTexture`)
  4. Despues de `render_fn()`: `settings_window_render()` (no-op si cerrada)
  5. Si `settings().show_fps`: `fps_overlay()`
  6. Al exit: `settings_save()`
- Hecho: `app_menubar` añade siempre el MenuItem "Settings..." como tercer item (junto a View / Layouts).
- Hecho: `fn_framework` (libreria static) ahora incluye `tokens.cpp + icon_font.cpp + app_settings.cpp + fps_overlay.cpp + panel_menu.cpp + layouts_menu.cpp + app_menubar.cpp`. Cualquier app que use `add_imgui_app` los enlaza automaticamente. Removidos de los CMakeLists individuales para evitar multiple-definition (shaders_lab, registry_dashboard, gallery, chart_demo).
- Hecho: gallery + dashboard ahora llaman `fn_ui::app_menubar(nullptr, 0, nullptr)` para exponer Settings (no tienen paneles ni layouts propios).
- Hecho: shaders_lab — removida llamada explicita a `fps_overlay()` en panel Controls. Ahora siempre se respeta el toggle de Settings.
- Hecho: `add_imgui_app` post-build copia las 4 TTFs vectoriales (Karla 17 KB, Roboto 163 KB, DroidSans 190 KB, Cousine 44 KB) + Tabler (2.7 MB) junto a cada exe.
- Hecho: `cpp/DESIGN_SYSTEM.md` seccion 12 nueva — Settings (UI, persistencia, extension, anti-patrones).
- Hecho: `app_settings.md` con docs completas en el registry.

Bug + fix:
- Build inicial fallo: `ImGui_ImplOpenGL3_DestroyFontsTexture/CreateFontsTexture` no existen en ImGui 1.92+ (renombrados/removidos). Fix: dejar que la atlas se recree y el backend la actualice solo via `UpdateTexture` en `NewFrame()`. Mas simple ademas.

Pendiente:
- Cerrar `primitives_gallery.exe` en Windows: el .exe esta locked y no se sincronizo (TTFs si). Reintentar `cp` cuando la app este cerrada.
- Probar en Windows que el menu Settings... abre la ventana, que cambiar fuente/tamaño rebuildea atlas en el siguiente frame, que toggle FPS funciona, que `app_settings.ini` se crea junto al exe con los valores correctos.
- Si una app quiere settings propios (shaders_lab podria querer "auto-compile on save"), ejemplo de uso en `app_settings.md`.

Reindex: 901 funciones (+1 app_settings_cpp_core), 153 types, 13 apps. Working tree con cambios sin commit.

## 17:50 — Settings menubar en chart_demo + gallery resync

- Hecho: `chart_demo/main.cpp` — removida llamada explicita a `fps_overlay()`, añadida `fn_ui::app_menubar(nullptr, 0, nullptr)` para exponer Settings (mismo patron que gallery y dashboard).
- Hecho: removido `#include "core/fps_overlay.h"` (ya no necesario en la app — vive en fn_framework).
- Hecho: build Linux + Windows OK. Gallery resynced (`primitives_gallery.exe` 15.2 MB, timestamp 20:06).
- Hecho: nueva carpeta `Desktop/apps/chart_demo/` con `chart_demo.exe` (14.9 MB) + 5 TTFs.

Estado final apps deployed con Settings menubar + multi-font:
- `Desktop/apps/primitives_gallery/` ✓
- `Desktop/apps/registry_dashboard/` ✓
- `Desktop/apps/shaders_lab/` ✓
- `Desktop/apps/chart_demo/` ✓ (nueva)

Las 4 apps comparten la misma menubar pattern (`View` opcional, `Layouts` opcional, `Settings...` siempre) y la misma seleccion de fuente/tamaño + toggle FPS via Settings.

## 18:10 — Fix: cambio de tamaño en runtime ahora es instantaneo

Usuario reportaba que el slider de tamaño no aplicaba inmediatamente. Causa: rebuild de atlas (Clear + AddFont + UpdateTexture) tarda 1+ frame y se notaba lag/flicker.

ImGui 1.92+ (junio 2025) introdujo el sistema **escalable**: `style.FontSizeBase` cambia el tamaño de render dinamicamente sin rebuild de atlas — el atlas se rasteriza una vez y se escala via vector at draw time (cuando `BackendFlags_RendererHasTextures` esta activo, que lo esta con el backend OpenGL3 nuevo).

- Hecho: `app_base.cpp` cada frame: `style.FontSizeBase = settings().font_size_px` + `style._NextFrameFontSizeBase = ...` (FIXME-hack documentado en imgui_demo.cpp:8520). Tamaño cambia al instante sin rebuild.
- Hecho: `settings_window`: el slider/combo de Size ya NO marca font dirty. Cambio puro de `font_size_px` (sin cambio de `font_id`) NO rebuildea atlas.
- Hecho: cambio de `font_id` (TTF distinta) sigue rebuildeando atlas — necesario porque las metricas de glyph difieren entre TTFs.
- Hecho: inicializacion en `run_app`: `style.FontSizeBase = settings().font_size_px` justo despues de `load_fonts_from_settings()`, asi el primer frame ya respeta el .ini.
- Hecho: footer de la ventana actualizado: "Tamaño aplica al instante. Cambio de fuente = 1 frame.".
- Hecho: rebuild Linux + Windows OK. Las 4 .exe sincronizadas a `Desktop/apps/` (timestamp 20:12).

Referencias en imgui:
- `imgui.cpp:439` — changelog 1.92.6 sobre escalado dinamico
- `imgui.h:2370` — comment de `FontSizeBase`
- `imgui_demo.cpp:8519-8520` — patron de slider sobre `style.FontSizeBase` + `_NextFrameFontSizeBase`

## 18:30 — Defaults DroidSans 15, kpi_card v1.3 con icono, shader_canvas en gallery

Tres cambios en un sweep:

### Defaults globales

- Hecho: `AppSettings::font_id = FontId::DroidSans` (antes Karla). DroidSans tiene mejor legibilidad solida a 15 px en ImGui sin hinting.
- Hecho: `font_size_px = 15` y `show_fps = false` confirmados como defaults.
- Comentario actualizado en `app_settings.cpp` para que el .ini deje claro cual es el default.

### kpi_card v1.3.0 — icono + Tabler arrows

- Hecho: nueva firma con parametro opcional al final: `kpi_card(label, value, delta, history, count, format, icon = nullptr)`. Icono se renderiza inline antes del label en text_muted.
- Hecho: arrows del delta migradas de UTF-8 hex (`\xe2\x96\xb2` / `\xe2\x96\xbc`) a `TI_TRENDING_UP` / `TI_TRENDING_DOWN` — los anteriores no estan en el atlas de DroidSans/Karla y salian como cuadritos. Mismo fix en el placeholder de "no data": `\xe2\x80\x94` (em dash) → `TI_MINUS`.
- Hecho: card altura 78 → 86 px para acomodar el row icono+label sin apretar el resto.
- Hecho: gallery demo actualizado con iconos: `TI_CASH` (Revenue), `TI_USERS` (Users), `TI_CHART_BAR` (Churn), `TI_ALERT_CIRCLE` (Errors).
- Hecho: `kpi_card.md` bumped a v1.3, frontmatter con nuevo param `icon`.

### shader_canvas en primitives_gallery

shaders_lab tiene como pieza distintiva el preview live de fragment shaders. Añadido como demo en la gallery para documentar/showcase.

- Hecho: nuevo dominio "Gfx" en el sidebar, primer item: `shader_canvas`.
- Hecho: `demos_gfx.cpp` con un fragment shader sintetico (gradiente animado de celdas con focus en mouse), compilado en el primer frame con `compile_fragment` y renderizado a 480x300 px via `canvas_render(canvas, time)`.
- Hecho: `demos.h` registra `demo_shader_canvas`. `main.cpp` añade entry al array `k_demos[]` con categoria "Gfx".
- Hecho: `gallery/CMakeLists.txt` añade el shader stack: `gl_shader.cpp + gl_framebuffer.cpp + fullscreen_quad.cpp + shader_canvas.cpp + demos_gfx.cpp`.
- Estado del binario: gallery crece de 15.2 → 18.1 MB (shader stack).

### Build + sync

- Hecho: rebuild Linux + Windows OK.
- Hecho: `Desktop/apps/` resynced — los 4 exes a las 20:20 con la nueva default config (DroidSans 15) y los iconos renderizando en kpi_card.

Pendiente:
- Probar en Windows que defaults aplican en la primera apertura (sin `app_settings.ini` previo): DroidSans 15, FPS off.
- Probar shader_canvas demo en gallery: el shader debe compilar y animarse.
- Considerar mas demos gfx: `dag_palette` / `dag_panel` (node editor) — mas trabajo, lo dejo para otra sesion si hace falta.
- Añadir `app_menubar` / `panel_menu` / `layouts_menu` / `app_settings` como demos explicitos en la gallery (ahora se ven via la propia menubar de la gallery, pero un demo dedicado documentaria mejor el API). Skip por ahora.

Reindex: 901 funciones (kpi_card y app_settings ya estaban en sesiones previas), 153 types.

## 20:43 — /documentar — cierre de sesion C++ icons + settings + fuentes

Wrap-up: distribucion de la conversacion a los `.md` que faltaban tras todos los cambios C++ del dia.

- Hecho: `cpp/functions/core/app_menubar.md` — bloque "Notas — Settings menu" describiendo la inclusion automatica del item `Settings...` y la dependencia implicita en `app_settings_cpp_core`.
- Hecho: `cpp/functions/core/fps_overlay.md` — bloque "Notas — Auto-render via app_settings": `fn::run_app` lo invoca segun `settings().show_fps`; anti-patron de llamarlo directo desde la app.
- Hecho: `cpp/functions/viz/kpi_card.md` — bumped a v1.3, nuevo param `icon`, glyphs Tabler en delta/placeholder, comentario obsoleto sobre rangos UTF-8 marcado.
- Hecho: `apps/shaders_lab/app.md` — bloque "Notas — Settings + iconos" + "Lo siguiente que pega" (auditar hex inline en uniform_panel/dag_panel; ejemplo de seccion extra de settings).
- Hecho: `projects/fn_monitoring/apps/registry_dashboard/app.md` — bloque "Notas — Settings menubar" con el cambio de `render()` (ahora llama `app_menubar(nullptr,0,nullptr)`) y limpieza de CMake.
- Hecho: reindex final → 901 funciones, 153 types, 13 apps.

Pendiente para proxima sesion:
- Migrar a `TI_*` cualquier hex UTF-8 (`"\x..\x.."`) o emoji Unicode hardcoded en uniform_panel, dag_panel, dag_node_editor (apps en uso ya tienen Tabler en atlas, no estamos aprovechando).
- Demos extra en gallery: `app_menubar`, `app_settings` y/o un par de items del DAG (`dag_palette` mostrando la paleta drag-source). Skip esta sesion porque requieren state pesado.
- Ejemplo concreto de `settings_window_add_section` en `shaders_lab` (auto-compile + debounce_ms persistido en `shaders_lab.db`).

Estado final del working tree: cambios sin commit. Las 4 .exe en `Desktop/apps/` con build de las 20:20 (DroidSans 15 default, kpi cards con icono, gallery con shader_canvas demo).
