## Estandarizacion de apps C++ del registry

**Fuentes autoritativas:**
- `cpp/PATTERNS.md` — checklist y esqueleto del app shell (`fn::run_app`, AppConfig, panels, layouts, Settings, About).
- `cpp/DESIGN_SYSTEM.md` — identidad visual (`fn_tokens`, ThemeMode, equivalencias `@fn_library` ↔ C++).

Esta regla NO duplica esos documentos — los señala como obligatorios y añade convenciones estructurales que no aparecen alli.

### Scaffolder canonico — OBLIGATORIO

**REGLA DURA:** crear apps C++ nuevas SIEMPRE con `fn run init_cpp_app <name> [--project <p>] [--desc "..."]`. NUNCA escribir `main.cpp` + `CMakeLists.txt` + `app.md` desde cero a mano en `cpp/apps/` ni `projects/*/apps/`. Tampoco copiar otra app y renombrar — la deriva entre patrones es lo que estamos eliminando.

Si el scaffolder no cubre un caso (ej. necesitas plantilla diferente, layout custom desde el primer dia), **modificas el scaffolder**, no escribes la app a mano. La plantilla canonica es codigo, no decoracion.

Razones:
- Garantiza `cfg.about` + `cfg.log` + `cfg.panels` + framework defaults aplicados.
- Genera frontmatter `app.md` valido (framework, dir_path, repo_url) para `fn index`.
- Registra `add_subdirectory` en `cpp/CMakeLists.txt` (raiz o bloque `_DIR` para projects).
- Crea repo Gitea `dataforge/<name>` con master + commit inicial.

Pipeline: `init_cpp_app_bash_pipelines`. Slash command equivalente: `/new-cpp-app`. Auditoria: `fn doctor cpp-apps`.

### 1. Ubicacion

| Caso | Donde vive |
|---|---|
| App independiente | `cpp/apps/<nombre>/` |
| App de un proyecto | `projects/<proyecto>/apps/<nombre>/` |

NUNCA en `cpp/apps/<nombre>/` si pertenece a un proyecto, NUNCA fuera de `apps/` directamente. Ver `apps_location` en memoria + regla `apps_vs_functions.md`.

### 2. Estructura minima

```
<app_dir>/
  CMakeLists.txt        # usa add_imgui_app(target ...)
  app.md                # frontmatter de registro (ver §4)
  main.cpp              # entry: parseo de args + fn::run_app + render()
  [data.{h,cpp}]        # opcional: capa de datos (DB / HTTP / archivos)
  [views.{h,cpp}]       # opcional: composicion de paneles
  [<modulo>.{h,cpp}]    # opcional: dominio especifico
  [vendor/]             # opcional: deps no comunes (se prefieren las globales en cpp/vendor/)
  [.git/]               # cada app es su propio repo Gitea (ver §6)
```

**Reglas de split:**
- `main.cpp` SIEMPRE — punto de entrada con `int main()` + `fn::run_app(...)` + funcion `render()`.
- Si la app supera ~400 lineas en `main.cpp`, partir en `data.{h,cpp}` (carga/persistencia) + `views.{h,cpp}` (UI por panel).
- Modulos especificos del dominio en archivos propios (`compiler.cpp` en `shaders_lab`, `data_http.cpp` en `registry_dashboard`).
- NO crear archivos de "utilidades genericas" dentro de la app — eso va al registry como funcion (`cpp/functions/...`).

### 3. CMakeLists.txt

Patron canonico:

```cmake
add_imgui_app(<target>
    main.cpp
    [extra_modules.cpp]
    # Funciones del registry usadas (paths absolutos):
    ${CMAKE_SOURCE_DIR}/functions/<dominio>/<funcion>.cpp
    ...
)
target_include_directories(<target> PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(<target> PRIVATE [SQLite::SQLite3] [imgui_node_editor] ...)

if(WIN32)
    set_target_properties(<target> PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
```

Reglas:
- Usar SIEMPRE la macro `add_imgui_app(target ...)` — gestiona enlace con `fn_framework` y copia de TTFs.
- Listar explicitamente cada `.cpp` del registry usado (no glob). Hace visible el grafo de dependencias.
- NO listar `tokens.cpp`, `icon_font.cpp`, `app_settings.cpp`, `app_about.cpp`, `fps_overlay.cpp`, `panel_menu.cpp`, `app_menubar.cpp`, `layouts_menu.cpp`, `gl_loader.cpp`, `layout_storage.cpp` — viven en `fn_framework` y dan multiple-definition si se duplican.
- En `WIN32`, marcar `WIN32_EXECUTABLE TRUE` para apps GUI (sin consola).

### 4. app.md (frontmatter)

Plantilla minima para apps C++:

```yaml
---
name: <name>
lang: cpp
domain: <gfx|tui|tools|infra|...>
description: "Frase corta — lo que hace y por que existe."
tags: [imgui, ...]                    # si es service, anadir 'service'
uses_functions:                       # IDs del registry — el indexer NO deduce C++
  - <nombre>_cpp_<dominio>
  - ...
uses_types: []
framework: "imgui"
entry_point: "main.cpp"
dir_path: "cpp/apps/<name>" o "projects/<proyecto>/apps/<name>"
repo_url: "https://gitea-.../dataforge/<name>"
---
```

Reglas:
- `uses_functions` se rellena a mano con los IDs de las funciones del registry usadas en `CMakeLists.txt`. Auditar con: `sqlite3 registry.db "SELECT id FROM apps WHERE id='<id>';"` + revisar diffs.
- `framework: "imgui"` siempre que use `fn::run_app`. Otros valores solo si la app NO usa el shell (raro).
- `tags`: incluir `service` si es daemon de larga duracion (ver `function_tags.md`).
- `repo_url` apunta al sub-repo en Gitea (ver §6).

### 5. Registro en `cpp/CMakeLists.txt`

Cada app nueva se registra al final de `cpp/CMakeLists.txt`:

```cmake
# --- <app_name> ---
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/apps/<name>/CMakeLists.txt)
    add_subdirectory(apps/<name>)
endif()
```

Para apps en proyectos (fuera del arbol `cpp/`):

```cmake
# --- <app_name> (lives in projects/<proj>/apps/) ---
set(_<NAME>_DIR ${CMAKE_SOURCE_DIR}/../projects/<proj>/apps/<name>)
if(EXISTS ${_<NAME>_DIR}/CMakeLists.txt)
    add_subdirectory(${_<NAME>_DIR} ${CMAKE_BINARY_DIR}/apps/<name>)
endif()
```

El `if(EXISTS ...)` hace el registro tolerante a apps no clonadas (cada app es sub-repo separado).

### 6. Sub-repo Gitea (TBD obligatorio)

Cada app C++ es su propio repo en `dataforge/<name>` con branch `master`. Esto significa:
- El directorio `<app_dir>/` esta en el `.gitignore` de `fn_registry` (excepto `app.md`).
- El propio directorio tiene `.git/` apuntando al sub-repo.
- TBD obligatorio mientras se desarrolla la app: ver `apps_tbd.md`. Trabajar en `issue/<NNNN>-<slug>` o `quick/<slug>`, mergear a `master` con `--no-ff`.
- Sync entre PCs y push/pull se gestionan con `/full-git-push` y `/full-git-pull`.

### 7. Convencion `local_files/` — separacion de distribuible vs estado local

**OBLIGATORIO**: TODA app coloca sus archivos escribibles bajo
`<exe_dir>/local_files/`. Los archivos distribuibles (`.exe`, `.dll`,
`.ttf`, `enrichers/`, `runtime/`) viven directos en `<exe_dir>/`.

```
<exe_dir>/
├── <app>.exe
├── duckdb.dll, *.ttf, runtime/, enrichers/    ← read-only, ships con el zip
└── local_files/                                ← writable, per-PC
    ├── imgui.ini                               ← gestionado por fn::run_app
    ├── app_settings.ini                        ← gestionado por fn_ui::settings_*
    └── <lo que la app escriba>                 ← usar fn::local_path("nombre")
```

`fn::run_app` lo gestiona automaticamente para `imgui.ini` y
`app_settings.ini` y migra desde `<exe_dir>/` o `cwd` si vienen de
una version previa.

Apps que escriban archivos extra (DBs, caches, proyectos del
usuario) **DEBEN** usar `fn::local_path("nombre")` al construir
sus paths. Ejemplo:

```cpp
// MAL
sqlite3_open("graph_explorer.db", &db);
fopen("graph_explorer.ini", "r");

// BIEN
sqlite3_open(fn::local_path("graph_explorer.db"), &db);
fopen(fn::local_path("graph_explorer.ini"), "r");
```

API en `cpp/framework/app_base.h`:
- `fn::exe_dir()` — directorio del ejecutable.
- `fn::local_dir()` — `<exe_dir>/local_files/`, creado on-demand.
- `fn::local_path(name)` — `<local_dir>/<name>`.
- `fn::migrate_to_local_files(names, n)` — mueve archivos viejos.

Beneficios:
- Carpeta del .exe limpia para distribuir (zip portable).
- Reset trivial (basta borrar `local_files/`).
- Separacion clara para backup/sync (solo `local_files/` es propio del PC).

### 7.1 Anti-jitter automatico (AltSnap, tiling WMs)

`fn::run_app` aplica tres capas de proteccion contra jitter al mover la
ventana con herramientas externas (AltSnap en Windows, snap-assist, tiling
WMs). Activado por defecto, sin opt-in:

1. **GLFW pos/size callbacks** — `vp->Pos/Size` se sincronizan al instante
   con `glfwSetWindowPos/Size` (no espera al siguiente NewFrame).
2. **Per-frame viewport sync** al inicio del main loop — cubre viewports
   secundarios (paneles drag-out) que la backend crea dinamicamente.
3. **Win32 WndProc subclass** (`#ifdef _WIN32`) — observa `WM_ENTERSIZEMOVE`
   / `WM_EXITSIZEMOVE` que AltSnap fakea alrededor de cada drag. Mientras
   el bracket esta abierto el main loop SKIPEA `render_fn` + `glfwSwapBuffers`,
   replicando el contrato del title-bar drag native (DefWindowProc bloquea
   el hilo, DWM compositor mueve el framebuffer existente).

Tests: `cpp/apps/altsnap_jitter_test/` corre dos fases:
- `p1.sync` (cross-platform): drives `glfwSetWindowPos` cada frame, asserta
  `vp->Pos` sigue OS dentro de 1px.
- `p2.altsnap` (Windows): worker thread fakea `WM_ENTERSIZEMOVE` +
  burst de `SetWindowPos(SWP_ASYNCWINDOWPOS)` + `WM_EXITSIZEMOVE`, asserta
  que `render()` no se llama durante el bracket.

Lanzar con `e2e_run_cpp_windows altsnap_jitter_test`.

NO hace falta nada en cada app — toda `fn::run_app` lo hereda. Si una app
necesita renderizar incluso durante external move (caso raro: telemetria
en vivo, video stream), tendria que evitar el bypass — actualmente no hay
flag para desactivarlo (anadir `cfg.pause_on_external_sizemove = true` por
default si surge necesidad).

### 8. Convenciones de runtime

Cumplir el checklist completo de `cpp/PATTERNS.md`. Resumen de lo que NUNCA debe aparecer en una app:

| Anti-patron | Sustituir por |
|---|---|
| `glfwInit()` en `main` | `fn::run_app(cfg, render)` |
| `ImGui::StyleColorsDark()` | `cfg.theme = ThemeMode::FnDark` (default) |
| `ImVec4(0.5,0.5,0.5,1)` | `fn_tokens::colors::*` |
| `ImGui::Begin(u8"\xEF...")` | `ImGui::Begin(TI_HOME " ...")` |
| Menubar inline cada frame | `cfg.panels` + `cfg.layouts_cb` |
| About hardcoded en un panel | `cfg.about = {...}` |
| `gl*` directo sin loader | `cfg.init_gl_loader = true` |
| Tabla SQLite en la raiz del repo | `<app_dir>/<app>.db` (operations.db es solo para entities/relations/executions) |
| `fopen("foo.ini", ...)` con path relativo | `fopen(fn::local_path("foo.ini"), ...)` (ver §7) |

### 8. Tests visuales (recomendado, no obligatorio)

Si la app tiene componentes que se quieren proteger contra regresiones visuales, anadir un demo en `cpp/apps/primitives_gallery/demos_<dominio>.cpp` que use los mismos componentes/funciones del registry. El sistema de capture-and-compare de `primitives_gallery --capture` funciona como golden-image gate (ver final de `cpp/PATTERNS.md`).

### 9. Decisiones que cada app debe tomar y documentar en su `app.md`

- `viewports`: `true` (default) si las ventanas pueden arrastrarse fuera del main; `false` si la app necesita estar siempre embebida.
- `init_gl_loader`: `true` si llama `gl*` directo (renderers GPU custom como `graph_renderer`); `false` si solo usa ImGui/ImPlot.
- `about` info: nombre, version (semver), descripcion 1 frase.
- Persistencia: `<app>.db` SQLite junto al exe; nunca tocar `registry.db` ni `operations.db` salvo lectura.
- Modo CLI: si la app acepta args, documentarlos en el `app.md` con ejemplos.

### 10. Layouts persistentes (default)

`fn::run_app` provee menu Layouts (Save current as.../Apply/Delete/Reset) sin
codigo. Crea `<exe_dir>/local_files/layouts.db` (tabla `imgui_layouts` +
`layout_meta`) y persiste el `imgui.ini` serializado por nombre.

**Restore-on-open / save-on-close (1.1.0+):** al cerrar la app, el slot del
layout activo se reescribe con el `imgui.ini` actual (los retoques de
docking sobreviven). Al abrir, si habia un layout activo persistido en
`layout_meta.last_active`, se carga en el primer frame. Si la app no usa
named layouts (nunca clico Save/Apply), el comportamiento sigue siendo el
de antes: `imgui.ini` es la unica fuente.

- App nueva: nada que tocar — Layouts viene activo.
- App quiere personalizar `on_reset` (ej. re-mostrar paneles especificos como
  `shaders_lab`): abre su propio `LayoutStorage`, llama
  `layout_storage_make_callbacks`, override `on_reset`, y pasa
  `cfg.layouts_cb = &cb`. Cuando se pasa `layouts_cb`, el auto-storage se
  desactiva y la app es responsable de `layout_storage_apply_pending` al
  inicio de su `render`.
- App headless / capture mode: `cfg.auto_layouts = false`.
- Cambiar nombre del archivo: `cfg.auto_layouts_db = "<algo>.db"` (relativo a
  `local_files/`).
