Files
fn_registry/.claude/rules/cpp_apps.md
T
egutierrez 750b7abcd5 chore: auto-commit (97 archivos)
- .claude/CLAUDE.md
- .claude/agents/fn-recopilador/SKILL.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- bash/functions/infra/build_cpp_windows.sh
- cpp/CMakeLists.txt
- cpp/PATTERNS.md
- cpp/framework/app_base.cpp
- cpp/framework/app_base.h
- dev/issues/README.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:11:24 +02:00

9.2 KiB

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.

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:

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++:

---
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:

# --- <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/):

# --- <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:

// 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).

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) y persiste el imgui.ini serializado por nombre.

  • 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/).