From 81d8a7c95da1c2b1155aef1f2c50c78bea7a20a5 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sun, 3 May 2026 00:50:33 +0200 Subject: [PATCH] feat(framework): assets/ subfolder para distribuibles read-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refina la convencion de layout: el top de cada app distribuible solo lleva el .exe + DLLs nativas; todo lo demas (TTFs, enrichers, runtime Python, MCP servers) vive en /assets/. Cambios: - cpp/CMakeLists.txt::add_imgui_app — copia las 5 TTFs (Karla, Roboto, DroidSans, Cousine, tabler-icons) a $/assets/ en lugar de junto al exe. - framework/app_base: nuevas funciones fn::asset_dir() y fn::asset_path(name) que resuelven a /assets/. - functions/core/icon_font.cpp::find_asset — anade fn::asset_path(filename) como PRIMERA ruta de busqueda, antes de las legacy ./ y ./assets/. Mantiene los fallbacks para dev (FN_ASSETS_DIR, FN_CPP_ROOT). - .claude/commands/compile.md — el deploy a Desktop pone TTFs + enrichers/ + runtime/ + gx-cli en /assets/. Solo .exe y DLLs nativas (duckdb.dll) quedan en el top. local_files/ se preserva si existe. Layout final: Desktop/apps// ├── .exe + *.dll (binario + DLLs Windows) ├── assets/ (read-only distribuible) │ ├── *.ttf, enrichers/, runtime/, gx-cli, ... └── local_files/ (per-PC, creado al primer arranque) Esto cierra la separacion conceptual de la convencion: la carpeta es trivial de zippear (solo .exe + assets/), el reset/sync es trivial (local_files/), y todas las apps del registry adoptan el mismo layout via fn_framework. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/commands/compile.md | 53 ++++++++++++++++++++------------ cpp/CMakeLists.txt | 25 ++++++++++----- cpp/framework/app_base.cpp | 24 +++++++++++++++ cpp/framework/app_base.h | 9 ++++++ cpp/functions/core/icon_font.cpp | 12 ++++++-- 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/.claude/commands/compile.md b/.claude/commands/compile.md index 7d43eca6..53a905f0 100644 --- a/.claude/commands/compile.md +++ b/.claude/commands/compile.md @@ -101,22 +101,26 @@ Si el target no existe en CMake (porque la app no esta registrada en `cpp/CMakeL ### 4. Copiar a `/mnt/c/Users/lucas/Desktop/apps//` -Layout estandar (convencion `local_files/`, ver `cpp_apps.md` §7): +Layout estandar (convencion `assets/` + `local_files/`, ver `cpp_apps.md` §7): ``` Desktop/apps// -├── .exe ← binario -├── *.ttf, *.dll ← fuentes + DLLs (junto al exe) -├── assets/ ← opcional, copiada del build -├── enrichers/ ← opcional, si /enrichers existe -├── runtime/ ← opcional, si app.md tiene python_runtime: true -└── local_files/ ← creado por la app al primer arranque - NUNCA borrar al recompilar +├── .exe ← binario (top level por convencion Windows DLL) +├── *.dll ← DLLs nativas (Windows las busca junto al exe) +├── assets/ ← read-only, ships con el zip +│ ├── *.ttf ← fuentes (vienen de add_imgui_app) +│ ├── enrichers/ ← si /enrichers existe +│ ├── runtime/ ← Python embed si app.md tiene python_runtime: true +│ ├── gx-cli, gx-cli.exe ← si la app necesita un MCP server +│ └── ... ← cualquier otro asset distribuible +└── local_files/ ← writable, per-PC, creado por la app al + primer arranque. NUNCA borrar al recompilar. ``` ```bash DEST="/mnt/c/Users/lucas/Desktop/apps/$APP_ARG" -mkdir -p "$DEST" +ASSETS="$DEST/assets" +mkdir -p "$DEST" "$ASSETS" EXE_SRC="$BUILD_WIN/apps/$APP_ARG/$APP_ARG.exe" if [ ! -f "$EXE_SRC" ]; then @@ -124,24 +128,24 @@ if [ ! -f "$EXE_SRC" ]; then exit 1 fi -# 1. Binario + TTFs + DLLs (junto al exe del build, copiados por add_imgui_app). +# 1. Binario + DLLs en el top level (Windows DLL search convention). cp -v "$EXE_SRC" "$DEST/" -find "$BUILD_WIN/apps/$APP_ARG" -maxdepth 1 -type f \ - \( -name '*.ttf' -o -name '*.dll' \) -exec cp -v {} "$DEST/" \; +find "$BUILD_WIN/apps/$APP_ARG" -maxdepth 1 -type f -name '*.dll' \ + -exec cp -v {} "$DEST/" \; -# 2. assets/ del build (opcional). +# 2. assets/ — TTFs (las copia add_imgui_app a build//assets/) y +# cualquier asset extra del build (build//assets/*). if [ -d "$BUILD_WIN/apps/$APP_ARG/assets" ]; then - rsync -a --delete "$BUILD_WIN/apps/$APP_ARG/assets/" "$DEST/assets/" + rsync -a --delete "$BUILD_WIN/apps/$APP_ARG/assets/" "$ASSETS/" fi -# 3. enrichers/ del app_dir (opcional). Excluye __pycache__ + .pyc. +# 3. enrichers/ del app_dir -> assets/enrichers/. if [ -d "$APP_DIR/enrichers" ]; then rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' \ - "$APP_DIR/enrichers/" "$DEST/enrichers/" + "$APP_DIR/enrichers/" "$ASSETS/enrichers/" fi -# 4. runtime/ Python embebido (si la app lo declara). -# Lee `python_runtime: true` del frontmatter de app.md. +# 4. runtime/ Python embebido -> assets/runtime/ (si la app lo declara). if grep -q '^python_runtime:[[:space:]]*true' "$APP_DIR/app.md" 2>/dev/null; then if [ ! -d "$APP_DIR/runtime/python" ] || \ [ "$APP_DIR/app.md" -nt "$APP_DIR/runtime/.lock" ]; then @@ -149,10 +153,19 @@ if grep -q '^python_runtime:[[:space:]]*true' "$APP_DIR/app.md" 2>/dev/null; the "$APP_DIR/tools/freeze_python_runtime.sh" "$APP_DIR" windows fi rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' \ - "$APP_DIR/runtime/" "$DEST/runtime/" + "$APP_DIR/runtime/" "$ASSETS/runtime/" fi -# 5. NO TOCAR local_files/. Si existe en $DEST, preservar — contiene +# 5. Otros assets sueltos del app_dir (gx-cli, scripts varios). El +# convention es: si vive en / y no es codigo fuente, va a +# assets/. Ahora mismo la unica excepcion es gx-cli (graph_explorer). +for extra in gx-cli gx-cli.exe; do + if [ -f "$APP_DIR/$extra" ]; then + cp -v "$APP_DIR/$extra" "$ASSETS/" + fi +done + +# 6. NO TOCAR local_files/. Si existe en $DEST, preservar — contiene # estado del usuario (DBs, settings, layouts ImGui, proyectos). echo "OK: $APP_ARG -> $DEST" [ -d "$DEST/local_files" ] && echo " local_files/ preservado: $(du -sh "$DEST/local_files" | cut -f1)" diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 9fe76639..b160398c 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -154,8 +154,11 @@ add_library(fn_framework STATIC functions/core/panel_menu.cpp functions/core/layouts_menu.cpp functions/core/app_menubar.cpp + functions/core/logger.cpp + functions/core/log_window.cpp functions/gfx/gl_loader.cpp functions/core/layout_storage.cpp + functions/core/selectable_text.cpp ) target_include_directories(fn_framework PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/framework @@ -194,25 +197,31 @@ function(add_imgui_app target) target_include_directories(${target} PRIVATE ${FN_CPP_ROOT_DIR}/functions ) - # Copia las fuentes junto al ejecutable para deploys autonomos (sin - # FN_CPP_ROOT en runtime). 4 TTFs vectoriales para el menu Settings + Tabler - # para los iconos TI_*. + # Convencion de layout (cpp_apps.md §7): + # /.exe + .dll (binario + DLLs Windows convention) + # /assets/ (read-only: ttfs, enrichers, runtime, etc.) + # /local_files/ (creado en runtime: ini, db, projects) + # + # add_imgui_app copia las TTFs a /assets/. La app las + # encuentra en runtime via fn::asset_path() (icon_font.cpp). + set(_ASSETS_DIR $/assets) add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${_ASSETS_DIR} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Karla-Regular.ttf - $/Karla-Regular.ttf + ${_ASSETS_DIR}/Karla-Regular.ttf COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Roboto-Medium.ttf - $/Roboto-Medium.ttf + ${_ASSETS_DIR}/Roboto-Medium.ttf COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/DroidSans.ttf - $/DroidSans.ttf + ${_ASSETS_DIR}/DroidSans.ttf COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Cousine-Regular.ttf - $/Cousine-Regular.ttf + ${_ASSETS_DIR}/Cousine-Regular.ttf COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FN_CPP_ROOT_DIR}/vendor/tabler-icons/tabler-icons.ttf - $/tabler-icons.ttf + ${_ASSETS_DIR}/tabler-icons.ttf VERBATIM ) endfunction() diff --git a/cpp/framework/app_base.cpp b/cpp/framework/app_base.cpp index 179d6399..77c09e22 100644 --- a/cpp/framework/app_base.cpp +++ b/cpp/framework/app_base.cpp @@ -106,6 +106,30 @@ const char* local_path(const char* name) { return buf.c_str(); } +namespace { +const std::string& asset_dir_ref() { + static std::string cached; + static bool inited = false; + if (inited) return cached; + const std::string& edir = exe_dir_ref(); + cached = edir.empty() ? std::string("assets") : edir + "/assets"; + inited = true; + return cached; +} +} // namespace + +const char* asset_dir() { return asset_dir_ref().c_str(); } + +const char* asset_path(const char* name) { + static thread_local std::string buf; + buf = asset_dir_ref(); + if (name && *name) { + if (!buf.empty() && buf.back() != '/' && buf.back() != '\\') buf += '/'; + buf += name; + } + return buf.c_str(); +} + void migrate_to_local_files(const char* const* names, std::size_t n) { if (!names || n == 0) return; const std::string& ldir = local_dir_ref(); diff --git a/cpp/framework/app_base.h b/cpp/framework/app_base.h index 91a261bf..c6a1d802 100644 --- a/cpp/framework/app_base.h +++ b/cpp/framework/app_base.h @@ -68,6 +68,15 @@ const char* local_dir(); // proxima llamada — copia el valor si vas a guardarlo. const char* local_path(const char* name); +// Devuelve `/assets/` — read-only sidecar shipped con la +// app (ttfs, enrichers, runtime Python, etc.). NO crea la carpeta; +// si no existe, la app debe fallback a buscar los assets en +// FN_CPP_ROOT u otras rutas. Sin trailing slash. +const char* asset_dir(); + +// Construye `/`. Mismo lifetime que local_path. +const char* asset_path(const char* name); + // Mueve los archivos listados de cwd o exe_dir a local_files/ si // existen ahi pero NO existen ya en local_files/. Idempotente. Las // apps lo llaman al iniciar para migrar instalaciones viejas. diff --git a/cpp/functions/core/icon_font.cpp b/cpp/functions/core/icon_font.cpp index 5d059c41..d5c4686f 100644 --- a/cpp/functions/core/icon_font.cpp +++ b/cpp/functions/core/icon_font.cpp @@ -1,6 +1,7 @@ #include "icon_font.h" #include "app_settings.h" +#include "../../framework/app_base.h" #include "imgui.h" #include @@ -25,13 +26,18 @@ bool file_exists(const char* path) { return false; } -// Busca un asset (TTF) en las rutas estandar del registry. Devuelve la primera +// Busca un asset (TTF) en las rutas estandar. Devuelve la primera // ruta valida o vacio. // -// Orden: ./ → ./assets/ → $FN_ASSETS_DIR/ -// → ${FN_CPP_ROOT}/ +// Orden: +// 1. fn::asset_path(filename) /assets/ +// 2. ./ cwd directo (legacy) +// 3. ./assets/ cwd subdir (legacy / dev) +// 4. $FN_ASSETS_DIR/ override env +// 5. ${FN_CPP_ROOT}/ fuente del repo (dev sin build) std::string find_asset(const char* filename, const char* repo_subpath) { std::string p; + p = fn::asset_path(filename); if (file_exists(p.c_str())) return p; p = std::string("./") + filename; if (file_exists(p.c_str())) return p; p = std::string("./assets/") + filename; if (file_exists(p.c_str())) return p; if (const char* env = std::getenv("FN_ASSETS_DIR")) {