feat(infra): auto-commit con 11 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 13:30:27 +02:00
parent 0c1727fef7
commit 231d5d87a6
10 changed files with 496 additions and 12 deletions
+59 -2
View File
@@ -35,6 +35,10 @@ bool ensure_schema(sqlite3* db) {
" name TEXT PRIMARY KEY,"
" ini TEXT NOT NULL,"
" updated_at INTEGER NOT NULL"
");"
"CREATE TABLE IF NOT EXISTS layout_meta ("
" key TEXT PRIMARY KEY,"
" value TEXT NOT NULL"
");";
char* errmsg = nullptr;
int rc = sqlite3_exec(db, sql, nullptr, nullptr, &errmsg);
@@ -42,6 +46,17 @@ bool ensure_schema(sqlite3* db) {
return rc == SQLITE_OK;
}
bool layout_exists(sqlite3* db, const std::string& name) {
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db,
"SELECT 1 FROM imgui_layouts WHERE name = ? LIMIT 1;",
-1, &stmt, nullptr) != SQLITE_OK) return false;
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
bool found = (sqlite3_step(stmt) == SQLITE_ROW);
sqlite3_finalize(stmt);
return found;
}
} // namespace
// ── API ───────────────────────────────────────────────────────────────────
@@ -154,6 +169,42 @@ bool layout_storage_delete(LayoutStorage* s, const std::string& name) {
return rc == SQLITE_DONE && changes > 0;
}
bool layout_storage_set_last_active(LayoutStorage* s, const std::string& name) {
if (!s || !s->db) return false;
sqlite3_stmt* stmt = nullptr;
if (name.empty()) {
if (sqlite3_prepare_v2(s->db,
"DELETE FROM layout_meta WHERE key = 'last_active';",
-1, &stmt, nullptr) != SQLITE_OK) return false;
} else {
if (sqlite3_prepare_v2(s->db,
"INSERT INTO layout_meta (key, value) VALUES ('last_active', ?) "
"ON CONFLICT(key) DO UPDATE SET value = excluded.value;",
-1, &stmt, nullptr) != SQLITE_OK) return false;
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
}
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
std::string layout_storage_get_last_active(LayoutStorage* s) {
if (!s || !s->db) return "";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(s->db,
"SELECT value FROM layout_meta WHERE key = 'last_active';",
-1, &stmt, nullptr) != SQLITE_OK) return "";
std::string name;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char* t = sqlite3_column_text(stmt, 0);
if (t) name.assign(reinterpret_cast<const char*>(t));
}
sqlite3_finalize(stmt);
if (name.empty()) return "";
if (!layout_exists(s->db, name)) return ""; // referencia colgante: ignorar
return name;
}
void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
out.list = [s]() {
return layout_storage_list(s);
@@ -164,23 +215,29 @@ void layout_storage_make_callbacks(LayoutStorage* s, LayoutCallbacks& out) {
// confirme que se aplico (la app debe tomar el valor de retorno
// y propagarlo). Aqui ya lo dejamos optimistamente.
out.active_name = name;
layout_storage_set_last_active(s, name);
}
};
out.on_save = [s, &out](const std::string& name) {
if (layout_storage_save(s, name)) {
out.active_name = name;
layout_storage_set_last_active(s, name);
}
};
out.on_delete = [s, &out](const std::string& name) {
layout_storage_delete(s, name);
if (out.active_name == name) out.active_name.clear();
if (out.active_name == name) {
out.active_name.clear();
layout_storage_set_last_active(s, "");
}
};
out.on_reset = [&out]() {
out.on_reset = [s, &out]() {
// Limpia el INI en memoria. La app puede ademas re-mostrar paneles.
ImGui::LoadIniSettingsFromMemory("", 0);
ImGuiIO& io = ImGui::GetIO();
io.WantSaveIniSettings = true;
out.active_name.clear();
layout_storage_set_last_active(s, "");
};
}
+9
View File
@@ -67,6 +67,15 @@ std::string layout_storage_apply_pending(LayoutStorage* s);
// Borra un layout por nombre. Retorna true si se borro al menos una fila.
bool layout_storage_delete(LayoutStorage* s, const std::string& name);
// Persiste el nombre del ultimo layout activo en la BD (tabla layout_meta,
// key='last_active'). Pasar string vacio limpia la entrada. Retorna true si
// la operacion completo. Idempotente.
bool layout_storage_set_last_active(LayoutStorage* s, const std::string& name);
// Lee el nombre del ultimo layout activo (o "" si no hay). Si el nombre
// almacenado ya no existe en imgui_layouts (porque se borro), retorna "".
std::string layout_storage_get_last_active(LayoutStorage* s);
// Rellena `out` con callbacks que envuelven este storage. Patron de uso:
//
// auto* st = fn_ui::layout_storage_open("app.db");
+19 -6
View File
@@ -3,10 +3,10 @@ name: layout_storage
kind: function
lang: cpp
domain: core
version: "1.0.0"
version: "1.1.0"
purity: impure
signature: "fn_ui::LayoutStorage* layout_storage_open(const char*); void layout_storage_close(LayoutStorage*); std::vector<std::string> layout_storage_list(LayoutStorage*); bool layout_storage_save(LayoutStorage*, const std::string&); bool layout_storage_apply(LayoutStorage*, const std::string&); std::string layout_storage_apply_pending(LayoutStorage*); bool layout_storage_delete(LayoutStorage*, const std::string&); void layout_storage_make_callbacks(LayoutStorage*, LayoutCallbacks&)"
description: "Persistencia de layouts ImGui en SQLite con handle opaco. Una app abre el storage con un path, obtiene un LayoutCallbacks listo para pasar al menu de layouts (app_menubar/layouts_menu_items) y solo necesita llamar a layout_storage_apply_pending() al inicio de cada frame para activar layouts cargados."
signature: "fn_ui::LayoutStorage* layout_storage_open(const char*); void layout_storage_close(LayoutStorage*); std::vector<std::string> layout_storage_list(LayoutStorage*); bool layout_storage_save(LayoutStorage*, const std::string&); bool layout_storage_apply(LayoutStorage*, const std::string&); std::string layout_storage_apply_pending(LayoutStorage*); bool layout_storage_delete(LayoutStorage*, const std::string&); bool layout_storage_set_last_active(LayoutStorage*, const std::string&); std::string layout_storage_get_last_active(LayoutStorage*); void layout_storage_make_callbacks(LayoutStorage*, LayoutCallbacks&)"
description: "Persistencia de layouts ImGui en SQLite con handle opaco. Una app abre el storage con un path, obtiene un LayoutCallbacks listo para pasar al menu de layouts (app_menubar/layouts_menu_items) y solo necesita llamar a layout_storage_apply_pending() al inicio de cada frame para activar layouts cargados. Persiste tambien el ultimo layout activo (tabla layout_meta) para restore-on-open."
tags: [imgui, sqlite, layouts, persistence, dockspace, public-api]
uses_functions: []
uses_types: []
@@ -14,9 +14,9 @@ returns: []
returns_optional: false
error_type: "error_go_core"
imports: [sqlite3, imgui]
tested: false
tests: []
test_file_path: ""
tested: true
tests: ["layout_storage: last_active survives reopen", "layout_storage: get_last_active ignora referencia colgante", "layout_storage: callbacks on_apply persiste last_active", "layout_storage: callbacks on_delete del activo limpia last_active", "layout_storage: callbacks on_reset limpia last_active", "layout_storage: open con BD legacy (sin layout_meta) no rompe"]
test_file_path: "cpp/tests/test_layout_storage.cpp"
file_path: "cpp/functions/core/layout_storage.cpp"
params:
- name: db_path
@@ -42,10 +42,23 @@ CREATE TABLE IF NOT EXISTS imgui_layouts (
ini TEXT NOT NULL,
updated_at INTEGER NOT NULL -- unix epoch en segundos
);
-- Meta key/value para "last_active" (1.1.0+). Aditiva: BBDD viejas se
-- migran al primer open() sin romper datos existentes.
CREATE TABLE IF NOT EXISTS layout_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
```
`CREATE TABLE IF NOT EXISTS` significa que la nueva API convive con tablas pre-existentes en la misma BD (por ejemplo `ui_layouts` heredada o cualquier otra).
## Restore-on-open / save-on-close
`fn::run_app` (cuando `auto_layouts = true`) lee `layout_meta.last_active` justo despues de abrir el storage y, si apunta a un layout existente, lo deja pendiente con `layout_storage_apply` para que el primer frame lo cargue. Al salir, antes de cerrar el storage, reescribe el slot activo con `layout_storage_save(active_name)` para que los retoques de docking que el usuario hizo durante la sesion sobrevivan al cierre.
`make_callbacks` mantiene `layout_meta` sincronizado: `on_apply`/`on_save` apuntan al nuevo nombre, `on_delete` del layout activo y `on_reset` limpian la entrada. Si el `last_active` apunta a un layout que ya no existe (referencia colgante), `get_last_active` retorna `""`.
## Patron de uso (recomendado)
```cpp