fix: docking gaps + hover radius + node spread + markdown notes

Bug fixes
- ImGui ID conflict en menu Change type: dedup tipos del grafo +
  defaults; PushID/PopID por entrada.
- Dockspace ya no tapa la toolbar: se posiciona 44 px por debajo, asi
  las ventanas dockeadas al borde superior quedan bajo la barra de
  filtros, no detras.
- Hover radius proporcional al tamaño visual del nodo: query espacial
  amplio (24/zoom) + filtro fino por (radio_visual + 2 px) / zoom. El
  tooltip solo se dispara si el raton esta efectivamente sobre el nodo.

Layout
- Default layout = grid (en vez de force) para que los grafos cargados
  se distribuyan ordenadamente al abrir.
- Boton "Reset layout" en la toolbar: limpia NF_PINNED en todos los
  nodos, resetea velocidades y reaplica el layout activo.
- Nodos recien creados (add_node, duplicate) caen en un anillo poisson
  alrededor del centro de la vista, no en el origen. Posicion
  determinista por user_data para que el mismo nodo no salte entre
  reloads.

Notes (markdown)
- Panel "Note" (dockeable) abierto con doble click sobre un nodo.
- entity_get_notes / entity_set_notes en entity_ops sobre la columna
  `notes` de operations.db (ya existente en el schema).
- Ctrl+S guarda. Cabecera muestra entity, type, id.
This commit is contained in:
2026-04-30 23:11:48 +02:00
parent 02eef6e339
commit adde3026ea
5 changed files with 273 additions and 27 deletions
+43
View File
@@ -286,6 +286,49 @@ bool relation_insert(const char* db_path, const char* from_id, const char* to_id
return ok;
}
// ----------------------------------------------------------------------------
// Notes (markdown)
// ----------------------------------------------------------------------------
bool entity_get_notes(const char* db_path, const char* id, std::string* out) {
if (!db_path || !id || !out) return false;
out->clear();
sqlite3* db = nullptr;
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
if (db) sqlite3_close(db);
return false;
}
sqlite3_stmt* st = nullptr;
if (sqlite3_prepare_v2(db, "SELECT notes FROM entities WHERE id = ?", -1, &st, nullptr) != SQLITE_OK) {
sqlite3_close(db);
return false;
}
sqlite3_bind_text(st, 1, id, -1, SQLITE_TRANSIENT);
bool ok = false;
if (sqlite3_step(st) == SQLITE_ROW) {
const unsigned char* p = sqlite3_column_text(st, 0);
if (p) *out = (const char*)p;
ok = true;
}
sqlite3_finalize(st);
sqlite3_close(db);
return ok;
}
bool entity_set_notes(const char* db_path, const char* id, const char* notes) {
if (!db_path || !id) return false;
sqlite3* db = nullptr;
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK) {
if (db) sqlite3_close(db);
return false;
}
std::string ts = now_iso();
const char* p[3] = { notes ? notes : "", ts.c_str(), id };
bool ok = exec_one(db, "UPDATE entities SET notes = ?, updated_at = ? WHERE id = ?", p, 3);
sqlite3_close(db);
return ok;
}
// ----------------------------------------------------------------------------
// Index user_data -> sql id
// ----------------------------------------------------------------------------