# 2026-05-16

## 02:00 — Framework C++ anti-jitter v2 + floating panel survival + Alt drag/resize + bulk redeploy

Extensiones al anti-jitter del framework C++ (`cpp/framework/app_base.cpp` + `.h`):

- **Multi-HWND subclass.** `g_subclassed` ahora `unordered_map<HWND, WNDPROC>`. Scan per-frame en `pio.Viewports` instala subclass en cada HWND nuevo (cubre main + cada viewport flotante). `prune_dead_subclassed()` con `IsWindow` limpia stale. `uninstall_sizemove_subclass_all()` al exit restaura cada HWND. Fix del temblor en paneles arrastrados fuera del main.
- **Iconified main + flotantes vivos.** El legacy `glfwWaitEvents+continue` paraba todo el frame loop. Con multi-viewport activo eso congelaba los flotantes. Ahora detecta secondary viewports y, si existen, fall-through al frame normal (main GL clear en HWND minimizado es harmless, contexts GL secundarios siguen pintando).
- **Alt + RMB resize anywhere.** WndProc detecta `WM_RBUTTONDOWN` + `GetAsyncKeyState(VK_MENU) & 0x8000`, calcula direccion por cuadrante del cursor (WMSZ_TOPLEFT/TOPRIGHT/BOTTOMLEFT/BOTTOMRIGHT relativo al centro del client rect), `ReleaseCapture()` + `PostMessage(WM_SYSCOMMAND, SC_SIZE|dir)`. Modal nativo, gate sizemove existente pausa render automaticamente.
- **Alt + LMB move anywhere.** Misma estructura para `WM_LBUTTONDOWN`. Posta `WM_SYSCOMMAND, SC_MOVE | HTCAPTION`. Modal MOVE nativo.
- **`io.ConfigWindowsMoveFromTitleBarOnly = true`** en `fn::run_app`. Floating panels (OS window borderless + UNA ventana ImGui rellenandolo) ahora respetan "solo header arrastra". Fix del drag-anywhere-sin-alt en flotantes.
- **`fn::internal::*` test observability.** `sizemove_enter_count()`, `alt_rmb_resize_count()`, `alt_lmb_move_count()`, `rbuttondown_seen_count()`, `set_force_alt_for_test(bool)`. Counters monotonicos zero-cost. En test mode los handlers de Alt cuentan pero NO postean SC_SIZE/SC_MOVE.

Tests en `apps/altsnap_jitter_test/main.cpp` extendidos a **6 phases** (p1 sync, p2 main HWND modal, p3 secondary HWND modal, p4 iconify+restore preserva flotante, p5 Alt+RMB consumed, p6 Alt+LMB consumed). Todas PASS en Windows.

Pipeline nuevo: **`redeploy_all_cpp_apps_bash_pipelines`** (`bash/functions/pipelines/redeploy_all_cpp_apps.sh` + `.md`). Cross-compila TODO el arbol `cpp/` en un solo cmake pass + redeploy de cada `.exe` al Desktop. Filtro opcional por substring. Tolerante a fallos (build best-effort, summary OK/SKIPPED/FAILED). Composicion: `build_cpp_windows_bash_infra` + loop `taskkill.exe` + `deploy_cpp_exe_to_windows_bash_infra`. Primera corrida: **12 OK / 1 SKIP / 0 FAILED**.

Fix `resolve_cpp_app_dir_bash_infra` **v1.1.0**: ahora busca apps en `apps/<X>/` (canonical issue 0096), luego `cpp/apps/<X>/` (legacy), luego `projects/*/apps/<X>/`. Antes solo veia los dos ultimos → `./fn run compile_cpp_app dag_engine_ui` fallaba. Deduccion desde CWD tambien cubre los tres layouts.

### Pitfalls / gotchas registrados

- `ImGui_ImplGlfw` subclassea el HWND DESPUES que nuestro framework. Captura nuestro WndProc como `bd->PrevWndProc` y chainea via `CallWindowProc` → todos los mensajes nos llegan en orden correcto. **NO re-subclassear** despues de ImGui init (genera recursion infinita por cycle).
- `keybd_event(VK_MENU)` no es fiable para drivear `GetAsyncKeyState` desde tests headless cross-compilados (sesion de input no foreground). Usar `set_force_alt_for_test(true)` + `SendMessageW` sincrono mismo-hilo. `PostMessage(WM_RBUTTONDOWN)` sintetizado tambien es dropeado por el kernel input filter.
- Pre-existing build break en `cpp/tests/test_llm_anthropic.cpp` + `cpp/tests/test_graph_icons.cpp` por `setenv()` no disponible en mingw-w64. NO bloquea `redeploy_all_cpp_apps` (build best-effort). Candidato a guard `#ifdef _WIN32` + `_putenv_s` o skip cross-compile.

### Lo siguiente que pega

- Resolver el build break de `test_llm_anthropic.cpp` + `test_graph_icons.cpp` con `_putenv_s` bajo `#ifdef _WIN32`. Quitar el SKIP del log de `redeploy_all_cpp_apps`.
- Considerar regla nueva sobre **ImGui io flags criticos** (`ConfigWindowsMoveFromTitleBarOnly`, `ConfigViewportsNoAutoMerge`, `ConfigDragClickToInputText`, etc.) que cambian comportamiento de viewports. Sub-seccion en `cpp_apps.md` o archivo separado en `.claude/rules/imgui_io_flags.md`.
- Documentar en `cpp/PATTERNS.md` que `add_imgui_app` ya embebe el icono `appicon.ico` si esta en el `<app_dir>` (verificado en sesion; el usuario lo mantuvo entre redeploys).

### Archivos tocados

- `cpp/framework/app_base.cpp` (multi-HWND, iconified-gate, Alt+RMB, Alt+LMB, ConfigWindowsMoveFromTitleBarOnly, fn::internal::*)
- `cpp/framework/app_base.h` (declaracion de fn::internal::*)
- `apps/altsnap_jitter_test/main.cpp` (phases 3-6)
- `apps/altsnap_jitter_test/app.md` (6 phases doc)
- `bash/functions/pipelines/redeploy_all_cpp_apps.sh` (nuevo)
- `bash/functions/pipelines/redeploy_all_cpp_apps.md` (nuevo, growth log v1.0.0)
- `bash/functions/infra/resolve_cpp_app_dir.sh` (v1.1.0)
- `bash/functions/infra/resolve_cpp_app_dir.md` (v1.1.0 + growth log)
- `.claude/rules/cpp_apps.md` (seccion 7.1 ampliada con v2)
- `docs/capabilities/cpp-windows.md` (tabla + seccion bulk redeploy + layouts)
- `CHANGELOG.md` (entrada 2026-05-16)

---

## 01:05 — Iconos .ico para apps C++ + funcion del registry

Las 11 apps C++ desplegadas a Windows no tenian icono → imposible distinguirlas en Desktop/taskbar (todas con el icono generico de exe).

### Pipeline build-time end-to-end

1. **Fuente de glyphs**: clonado `phosphor-icons/core` (MIT) en `sources/phosphor-core/` con `git clone --depth=1`. 1512 SVGs en 6 weights. Registrado en `sources/sources.yaml`.

2. **Mapping inicial** en `dev/gen_app_icons.py` (script reproducible, NO es del registry — vive en `dev/`). Tabla `APPS = [(app_id, dir, phosphor_icon, accent_hex), ...]` con un Tailwind color 500-700 por app:
   - chart_demo → chart-bar / sky-500
   - dag_engine_ui → tree-structure / violet-600
   - data_factory → factory / orange-500
   - engine_smoke (no aplicado: usa raw add_executable, no `add_imgui_app`)
   - graph_explorer → graph / cyan-600
   - navegator_dashboard → compass / blue-600
   - odr_console → terminal-window / zinc-600
   - primitives_gallery → shapes / pink-600
   - registry_dashboard → gauge / emerald-600
   - runtime_test (no aplicado: raw add_executable)
   - shaders_lab → palette / orange-600
   - text_editor_smoke → note-pencil / teal-600
   - altsnap_jitter_test → arrows-clockwise / red-600

3. **Funcion del registry**: `generate_app_icon_py_infra` (`python/functions/infra/generate_app_icon.py`). Lo escribe `fn-constructor`. Signature `generate_app_icon(phosphor_icon_name, accent_hex, out_ico_path, *, weight='fill', sizes=None, phosphor_root=None) -> str`. Tags `cpp-windows, icon, phosphor`. Deps `cairosvg + Pillow` ya en `python/.venv` (`uv pip install cairosvg` en `python/`). Validado con `./fn show` + smoke test `/tmp/test_icon2.ico`.

4. **Wiring CMake** (`cpp/CMakeLists.txt`):
   - Linea 1-5: `project(... LANGUAGES C CXX RC)` solo en WIN32. Linux ignora.
   - Macro `add_imgui_app` (linea ~241): si `WIN32 AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/appicon.ico`, genera `${target}_appicon.rc` en build dir con `IDI_ICON1 ICON "<full_path>"` y lo anade a `add_executable`. `x86_64-w64-mingw32-windres` (ya en toolchain) lo compila a `.res` y se enlaza al `.exe` como seccion `.rsrc`.

5. **Rebuild + redeploy**: `cmake --build cpp/build/windows -- -j$(nproc)` + `deploy_cpp_exe_to_windows` para las 11. Verificacion: `x86_64-w64-mingw32-objdump -h <exe> | grep rsrc` muestra la seccion en todas.

### Decision: build-time vs post-build

Existia `set_exe_icon_go_infra` (Go puro, embebe `.ico` post-build modificando PE). Descartado porque:
- Falla si el `.exe` ya tiene seccion `.rsrc` (limita robustez).
- Anade paso extra al pipeline redeploy.
- Build-time es atomico: el `.exe` recien linkeado YA tiene el icono.

### Gotchas

- **engine_smoke** y **runtime_test** usan `add_executable` raw, no `add_imgui_app` — no reciben icono automatico. Son smoke tests, no apps user-facing. Skip aceptable.
- **shaders_lab** vive en `apps/shaders_lab/`, no en `cpp/apps/shaders_lab/` (confusion inicial del mapping; corregido).
- **Pillow `save(format='ICO')` con `append_images`**: si pasas la imagen 16x16 como base, el `.ico` final solo tiene 16x16. Hay que pasar la imagen MAS GRANDE (256x256) como base + lista de variants per-size. Variants per-size dan crispness en 16/24 mejor que dejar Pillow downscalear.
- **Cache iconos Windows** (`iconcache.db`): tras desplegar nuevo `.ico`, Explorer puede seguir mostrando el viejo. Refresh: `ie4uinit.exe -show` o restart explorer.

### Para anadir icono a una app nueva

```bash
# 1. Elegir nombre Phosphor desde sources/phosphor-core/assets/fill/
ls sources/phosphor-core/assets/fill/ | grep <keyword>

# 2. Generar el .ico
./fn run generate_app_icon "<phosphor-name>" "#<accent_hex>" "<app_dir>/appicon.ico"

# 3. Rebuild + redeploy
./fn run redeploy_cpp_app_windows <app_name> <app_dir> --build
```

### Archivos tocados

- `sources/phosphor-core/` (nuevo, clonado)
- `sources/sources.yaml` (entry phosphor-icons/core)
- `dev/gen_app_icons.py` (nuevo, script reproducible con mapping inicial)
- `python/functions/infra/generate_app_icon.py` (nueva funcion registro)
- `python/functions/infra/generate_app_icon.md` (idem)
- `python/functions/infra/__init__.py` (export)
- `cpp/CMakeLists.txt` (project LANGUAGES + add_imgui_app icon wiring)
- 11 x `<app_dir>/appicon.ico` (nuevos, multi-res)
- `.claude/rules/cpp_apps.md` (§11 Icono Windows)
- `CHANGELOG.md` (entrada 2026-05-16 Added: iconos .ico)
- 11 x `.exe` rebuildeadas + redesplegadas a `/mnt/c/Users/lucas/Desktop/apps/`

### Lo siguiente que pega

- Anadir icono tambien a `engine_smoke` y `runtime_test` si se promueven a apps user-facing (hoy son smoke tests).
- Considerar `fn doctor cpp-apps` check: app C++ sin `appicon.ico` → warning.
- Si aparecen iconos antiguos en Explorer tras redeploy, anadir `ie4uinit.exe -show` al final de `deploy_cpp_exe_to_windows`.

## 20:54 — dag_engine — fix function-not-found nocturno + panel Logs en RunDetail

- Hecho: diagnostico de 3 fallos nocturnos (`fn_backup` x2 2026-05-15/16, `daily-registry-audit` 2026-05-16) que reportaban `error: function "<id>" not found (tried as ID and name)` aunque `audit_capability_groups_go_infra`, `backup_all_bash_pipelines` y `cdp_extract_recipe_py_pipelines` existen en `registry.db` raiz.
- Hecho: raiz identificada en `cmd/fn/ops.go:1597-1628 tryOpenRegistryDB` — sin `FN_REGISTRY_ROOT` el resolver cae al walk-up `go.mod` y devuelve `apps/dag_engine/` donde habia una copia stale `apps/dag_engine/registry.db` (262 KB, May 15) que violaba `.claude/rules/db_locations.md` (registry.db SOLO en raiz).
- Hecho: stale db borrada.
- Hecho: `~/.config/systemd/user/dag_engine.service` ampliado con `Environment=FN_REGISTRY_ROOT=/home/lucas/fn_registry`, `FN_BIN`, `PATH=/usr/local/go/bin:...` (sin PATH `go vet` fallaba con `exec: "go": executable file not found`), `HOME`. `daemon-reload` + `restart`.
- Hecho: `apps/dag_engine/executor.go` belt-and-suspenders — steps `function:` exportan `FN_REGISTRY_ROOT` en env del spawn y default `dir = fnRegistryRoot` si `step.Dir`/`dag.WorkingDir` vacios; rebuild `CGO_ENABLED=1 go build -tags fts5 -o dag_engine .`.
- Hecho: smoke test `POST /api/dags/daily-registry-audit/run` -> step `audit_capabilities` SUCCESS 387 ms (era el step que fallaba con not-found). `audit_unused` SUCCESS, `audit_artefacts` falla con exit 1 (bug aparte) y `fn_backup` `run_backup_all` exit 4 sin respetar `continue_on.exit_code` (bug aparte).
- Hecho: panel "Logs" anadido a `apps/dag_engine/frontend/src/pages/RunDetail.tsx` — `<Paper>` final con `<Code block>` (max-h 480 scrollable) + `CopyButton` Mantine (icono toggle copy/check teal 1.5s). Helper `buildLogText(run, steps)` compone texto plano con metadata del run (dag/path/status/trigger/started/finished/duration/error) + por-step (`[status] name exit=N Nms`, stdout/stderr indentado 4 espacios). Type-checkea limpio.
- Hecho: documentadas BBDDs canonicas — `dag_engine.db` en `apps/dag_engine/`, `data_factory.db` en `apps/data_factory/` (tablas nodes/connections/runs/databases), `navegator_dashboard` NO tiene BD propia (solo `layouts.db` del framework via `fn::local_path`).
- Hecho: append en `apps/dag_engine/app.md` (seccion fechada + "Lo siguiente que pega"), `apps/dag_engine/README.md` (Function steps: aviso sobre `FN_REGISTRY_ROOT` y `PATH` en systemd), `CHANGELOG.md` (Added panel Logs + Fixed function-not-found bug).
- Pendiente: investigar exit 1 real de `audit_artefacts` en `daily-registry-audit` (probable artefacto huerfano o git drift).
- Pendiente: bug en executor — `continue_on.exit_code: [4]` no se respeta; solo se mira `step.ContinueOn.Failure` (bool). Parsear `ContinueOn.ExitCode []int` y comparar con `result.ExitCode` antes de marcar `runFailed=true`.
- Pendiente: frontend `pnpm build` roto por API drift de Mantine (`<Collapse in={opened}>` en `StepTimeline.tsx:49`) y CSS type import en `main.tsx:1`. Bloquea ver el panel Logs en vivo; type-check ya pasa.
