a03675113a
- .claude/agents/fn-orquestador/SKILL.md - .claude/commands/fn_claude.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - .claude/rules/ids_naming.md - CHANGELOG.md - apps/dag_engine/README.md - apps/dag_engine/api.go - apps/dag_engine/dags_migrated/example.yaml - apps/dag_engine/dags_migrated/example_lineage_tracking.yaml - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
414 lines
20 KiB
Markdown
414 lines
20 KiB
Markdown
## 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 (issue 0096 estandarizada)
|
|
|
|
| Caso | Donde vive |
|
|
|---|---|
|
|
| App independiente | `apps/<nombre>/` |
|
|
| App de un proyecto | `projects/<proyecto>/apps/<nombre>/` |
|
|
|
|
NUNCA en `cpp/apps/<nombre>/` (deprecado tras issue 0096) ni en cualquier otra carpeta nombrada por lenguaje (`python/apps/`, `bash/apps/`, etc.). Las carpetas por lenguaje son solo para codigo del registry (`cpp/functions/`, `python/functions/`, etc.), nunca para artefactos. 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 per HWND** (`#ifdef _WIN32`) — observa
|
|
`WM_ENTERSIZEMOVE` / `WM_EXITSIZEMOVE` que AltSnap fakea alrededor de cada
|
|
drag. El subclass se instala en la ventana principal Y en cada HWND
|
|
secundario que el backend de ImGui crea cuando un panel se arrastra fuera
|
|
del main (escaneo per-frame de `pio.Viewports`). Mientras el bracket esta
|
|
abierto en CUALQUIER HWND propio, el main loop SKIPEA `render_fn` +
|
|
`glfwSwapBuffers` globalmente, replicando el contrato del title-bar drag
|
|
native (DefWindowProc bloquea el hilo, DWM compositor mueve el framebuffer
|
|
existente). El flag `g_in_sizemove` es global a proposito: una sola
|
|
sesion de sizemove externo pausa todo el render para que ninguna ventana
|
|
compita con el OS.
|
|
|
|
Estado del subclass:
|
|
- `g_subclassed` = `unordered_map<HWND, WNDPROC>`. Chain a la proc
|
|
original via `CallWindowProcW`.
|
|
- `install_sizemove_subclass_hwnd(HWND)` idempotente (skip si ya en mapa).
|
|
- Per-frame: `prune_dead_subclassed()` con `IsWindow` + install en cada
|
|
`pio.Viewports[i]->PlatformHandle` nuevo.
|
|
- `uninstall_sizemove_subclass_all()` restaura cada HWND al exit.
|
|
|
|
#### Iconified main no pierde paneles flotantes (2026-05-16)
|
|
|
|
El legacy `glfwWaitEvents + continue` al detectar `GLFW_ICONIFIED` paraba TODO
|
|
el frame loop. Con multi-viewport activo eso significa que
|
|
`ImGui::UpdatePlatformWindows + RenderPlatformWindowsDefault` dejan de
|
|
refrescar los viewports secundarios — los floating panels aparecen congelados
|
|
o son agrupados/ocultados por el WM. Fix actual: el iconified-gate cuenta
|
|
viewports secundarios primero; si hay alguno, fall-through al frame normal
|
|
(la swap del main HWND minimizado es harmless, los contexts GL secundarios
|
|
siguen pintando). Solo cuando NO hay flotantes dormimos en `glfwWaitEvents`.
|
|
|
|
#### Alt + RMB / Alt + LMB anywhere → modal nativo (2026-05-16)
|
|
|
|
WndProc del subclass tambien intercepta clicks con Alt held (`GetAsyncKeyState(VK_MENU) & 0x8000`):
|
|
|
|
- `WM_LBUTTONDOWN` + Alt → `ReleaseCapture()` +
|
|
`PostMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION)`. Modal MOVE nativo.
|
|
- `WM_RBUTTONDOWN` + Alt → calcula direccion por cuadrante (TOPLEFT/TOPRIGHT/
|
|
BOTTOMLEFT/BOTTOMRIGHT relativo al centro del client rect) y emite
|
|
`PostMessage(WM_SYSCOMMAND, SC_SIZE | dir)`. Modal RESIZE nativo.
|
|
|
|
Ambos retornan 0 (consumen el click — ImGui NO lo ve). Aplica a main y a
|
|
cada viewport flotante porque el subclass per-frame ya cubre todos los HWND.
|
|
El modal nativo dispara `WM_ENTERSIZEMOVE`, que el gate existente pausa
|
|
render → cero jitter automatico, mismo contrato que el title-bar drag.
|
|
|
|
**Caveat**: cualquier Alt+click se consume — perdes Alt+click como shortcut
|
|
UI. Aceptable porque Alt-modifier en clicks UI es muy raro.
|
|
|
|
#### Title-bar-only move para ImGui windows (2026-05-16)
|
|
|
|
`fn::run_app` setea `io.ConfigWindowsMoveFromTitleBarOnly = true`. Critico
|
|
para viewports secundarios: un viewport flotante = OS window borderless con
|
|
UNA ventana ImGui rellenandolo. Sin el flag, ImGui mueve sus ventanas
|
|
arrastrando cualquier client-pixel — como la ventana ImGui ES el viewport
|
|
entero, el OS window sigue al cursor sin modifier. Con el flag, floating
|
|
panels obedecen el contrato "solo header arrastra" (igual que main que tiene
|
|
title bar nativo de Windows). Alt+LMB anywhere sigue funcionando (consumido
|
|
antes por el subclass).
|
|
|
|
#### Test observability — `fn::internal::*` (2026-05-16)
|
|
|
|
Counters monotonicos para validar el subclass desde tests headless,
|
|
zero-cost en prod:
|
|
|
|
```cpp
|
|
namespace fn::internal {
|
|
int sizemove_enter_count(); // ++ en cada WM_ENTERSIZEMOVE
|
|
int alt_rmb_resize_count(); // ++ en cada Alt+RMB consumido
|
|
int alt_lmb_move_count(); // ++ en cada Alt+LMB consumido
|
|
int rbuttondown_seen_count(); // diagnostico — todo WM_RBUTTONDOWN
|
|
void set_force_alt_for_test(bool); // bypass GetAsyncKeyState para tests
|
|
}
|
|
```
|
|
|
|
En test mode (`set_force_alt_for_test(true)`), los handlers de Alt cuentan
|
|
pero NO postean `SC_SIZE`/`SC_MOVE` — el harness no se queda atrapado en el
|
|
modal de Windows. Path real en prod sigue posteandolos.
|
|
|
|
Tests: `apps/altsnap_jitter_test/` corre seis 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` sobre el
|
|
HWND principal, asserta que `render()` no se llama durante el bracket.
|
|
- `p3.secondary` (Windows): fuerza viewport secundario
|
|
(`ConfigViewportsNoAutoMerge=true`), localiza su HWND y repite el bracket
|
|
sobre el. Valida que el subclass per-viewport tambien pausa el render.
|
|
- `p4.minimize` (Windows): state machine 4 steps — captura
|
|
`IsWindow(secondary_hwnd)` antes/durante/despues de `glfwIconifyWindow +
|
|
glfwRestoreWindow`. Asserta los 3 estados vivos y `renders_iconified > 0`.
|
|
- `p5.alt_rmb` (Windows): `set_force_alt_for_test(true)` +
|
|
`SendMessage(WM_RBUTTONDOWN)` sincrono mismo-hilo. Asserta
|
|
`alt_rmb_resize_count` incrementa.
|
|
- `p6.alt_lmb` (Windows): mismo patron para `WM_LBUTTONDOWN`. Asserta
|
|
`alt_lmb_move_count` incrementa.
|
|
|
|
Lanzar con `source bash/functions/infra/e2e_run_cpp_windows.sh &&
|
|
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/`).
|
|
|
|
### 11. Icono Windows (.ico embebido en el .exe) — 2026-05-16
|
|
|
|
Cada app C++ desplegada a Windows tiene su propio icono. El icono vive en
|
|
`<app_dir>/appicon.ico` (multi-resolucion: 16/24/32/48/64/128/256). El macro
|
|
`add_imgui_app` de `cpp/CMakeLists.txt` lo detecta automaticamente: si
|
|
`WIN32` + existe `<CMAKE_CURRENT_SOURCE_DIR>/appicon.ico`, genera un
|
|
`<target>_appicon.rc` en `CMAKE_CURRENT_BINARY_DIR` apuntando al `.ico` con
|
|
`IDI_ICON1 ICON "<path>"` y lo anade a `add_executable`. El compilador RC
|
|
(`x86_64-w64-mingw32-windres` configurado en `cpp/toolchains/mingw-w64.cmake`)
|
|
lo enlaza al `.exe` como recurso `.rsrc`.
|
|
|
|
Verificar: `x86_64-w64-mingw32-objdump -h <app>.exe | grep rsrc` debe
|
|
mostrar la seccion. El project line en `cpp/CMakeLists.txt` declara
|
|
`LANGUAGES C CXX RC` solo en WIN32 (Linux ignora la `.rc`).
|
|
|
|
#### Crear `.ico` para una app nueva
|
|
|
|
Fuente de glyphs: **Phosphor Icons** (`sources/phosphor-core/`, clonado de
|
|
`https://github.com/phosphor-icons/core.git`). 1512 SVGs en weight `regular`,
|
|
`bold`, `fill`, `light`, `thin`, `duotone`. Usamos `fill` por defecto — mejor
|
|
legibilidad a 16/24px.
|
|
|
|
Funcion del registry: `generate_app_icon_py_infra` rasteriza un SVG Phosphor
|
|
sobre fondo redondeado del color accent y exporta `.ico` multi-res. Una
|
|
linea por app:
|
|
|
|
```python
|
|
from infra import generate_app_icon
|
|
generate_app_icon(
|
|
phosphor_icon_name="chart-bar",
|
|
accent_hex="#0ea5e9",
|
|
out_ico_path="apps/chart_demo/appicon.ico",
|
|
)
|
|
```
|
|
|
|
Mapping inicial (2026-05-16) en `dev/gen_app_icons.py` — script reproducible
|
|
que regenera los 11 `.ico` de un golpe leyendo la tabla `APPS`. Anadir app
|
|
nueva: una fila `(app_id, dir, phosphor_icon, accent_hex)` en `APPS` y
|
|
`/tmp/iconenv/bin/python dev/gen_app_icons.py` (o el venv del registry, ya
|
|
trae `cairosvg` + `Pillow`).
|
|
|
|
Convenciones:
|
|
- **Glyph weight**: `fill` (mas legible a 16px que `regular` o `bold`).
|
|
- **Color**: 1 accent_hex distinto por app — Tailwind palette 500-700
|
|
funciona bien (`#0ea5e9` sky-500, `#16a34a` green-600, etc.).
|
|
- **Padding**: glyph ocupa ~70% del canvas, fondo redondeado al 16% del lado.
|
|
- **Glyph color**: siempre blanco sobre el fondo accent.
|
|
|
|
Si Phosphor no tiene el icono adecuado: buscar en `sources/phosphor-core/assets/fill/`
|
|
con `ls | grep <keyword>` antes de inventar — 1512 disponibles.
|
|
|
|
#### Re-deploy tras cambiar icono
|
|
|
|
```bash
|
|
# 1. Regenerar .ico
|
|
./fn run generate_app_icon "chart-bar" "#0ea5e9" "apps/chart_demo/appicon.ico"
|
|
# (o editar dev/gen_app_icons.py + relanzar)
|
|
|
|
# 2. Rebuild + redeploy (build dispara windres → nuevo .rsrc)
|
|
./fn run redeploy_cpp_app_windows chart_demo apps/chart_demo --build
|
|
```
|
|
|
|
Windows cachea iconos en `iconcache.db`. Si el nuevo icono no aparece tras
|
|
desplegar, refresh con `ie4uinit.exe -show` o reiniciar Explorer.
|