feat(framework): assets/ subfolder para distribuibles read-only

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 <exe_dir>/assets/.

Cambios:
- cpp/CMakeLists.txt::add_imgui_app — copia las 5 TTFs (Karla,
  Roboto, DroidSans, Cousine, tabler-icons) a
  $<TARGET_FILE_DIR>/assets/ en lugar de junto al exe.
- framework/app_base: nuevas funciones fn::asset_dir() y
  fn::asset_path(name) que resuelven a <exe_dir>/assets/<name>.
- functions/core/icon_font.cpp::find_asset — anade
  fn::asset_path(filename) como PRIMERA ruta de busqueda, antes
  de las legacy ./<file> y ./assets/<file>. 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 <DEST>/assets/. Solo .exe y
  DLLs nativas (duckdb.dll) quedan en el top. local_files/ se
  preserva si existe.

Layout final:
  Desktop/apps/<APP>/
  ├── <APP>.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) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 00:50:33 +02:00
parent 6249e01419
commit 81d8a7c95d
5 changed files with 92 additions and 31 deletions
+33 -20
View File
@@ -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/<APP>/`
Layout estandar (convencion `local_files/`, ver `cpp_apps.md` §7):
Layout estandar (convencion `assets/` + `local_files/`, ver `cpp_apps.md` §7):
```
Desktop/apps/<APP>/
├── <APP>.exe ← binario
├── *.ttf, *.dll ← fuentes + DLLs (junto al exe)
├── assets/ ← opcional, copiada del build
├── enrichers/opcional, si <app_dir>/enrichers existe
├── runtime/ ← opcional, si app.md tiene python_runtime: true
└── local_files/ ← creado por la app al primer arranque
NUNCA borrar al recompilar
├── <APP>.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 <app_dir>/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/<app>/assets/) y
# cualquier asset extra del build (build/<app>/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 <app_dir>/ 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)"