diff --git a/issues/0036e-row-click-focus-viewport.md b/issues/0036e-row-click-focus-viewport.md new file mode 100644 index 0000000..4048ef7 --- /dev/null +++ b/issues/0036e-row-click-focus-viewport.md @@ -0,0 +1,70 @@ +--- +id: 0036e +title: Row click en NodeGroups enfoca la entidad en el viewport (kind-aware) +status: pending +priority: medium +created: 2026-05-04 +parent: 0036 +depends_on: [0036b] +--- + +## Objetivo + +Click sobre una fila en la NodeGroups window centra/selecciona la +entidad correspondiente en el viewport. Comportamiento ramificado por +kind: + +- **kind = Group**: la fila ES una entidad. Click -> pan/zoom de la + camara hacia su posicion + select. +- **kind = Table** (DuckDB): la fila NO es entidad necesariamente. + Resolucion **opcion (c)**: + - Si ya existe una entidad promovida para ese row_id: click = + focus. + - Si no: click = no-op visual + texto disabled abajo del row tipo + "promote first to focus" como hint sutil. Sin auto-promote. + +## Cambios + +### Estado para focus + +`AppState::want_focus_entity = true; AppState::focus_entity_id = id;` +Render loop consume el flag, recentra la camara al nodo, marca +`viewport.selected_node_idx = ...` y limpia el flag. + +(Probablemente ya existe esta infra desde issue 0011 promote+open +inspector — reusar `app.want_focus_entity` / `app.focus_entity_id`.) + +### Lookup de entidad para fila DuckDB + +Para kind=Table, dado un row_id, comprobar si existe entity con +`SELECT id FROM entities WHERE source = '' AND +metadata LIKE '%"row_id": ""%' LIMIT 1` (o el query exacto +usado en 0011 para detectar promovidas). + +Cache simple: por window, mantener un `unordered_set +promoted_row_ids` que se rellena en cada paginacion. Asi el render +sabe sin query extra que filas tienen entidad lista para focus. + +### Render + +Hacer la fila completa clickable (Selectable o boton invisible +sobre la fila). Hover indica accion clara. + +## Acceptance criteria + +- Click en row de Group: el viewport recentra al nodo y lo + selecciona. +- Click en row Table promovida: focus como kind=Group. +- Click en row Table no promovida: no-op + hint visible. +- Tests: + - `test_node_groups_row_click_sets_focus_entity_for_group` + - `test_node_groups_row_click_noop_for_unpromoted_table_row` + +## TBD + +Branch `issue/0036e-row-click-focus`, merge `--no-ff` a master. + +## Out of scope + +- Multi-select (fase 2). +- Hover preview (fase 2). diff --git a/views.cpp b/views.cpp index c05ecb7..bf42f4b 100644 --- a/views.cpp +++ b/views.cpp @@ -2089,14 +2089,18 @@ void views_node_groups_window(AppState& app) { ImGui::Selectable(row.id.c_str(), false, sf); if (is_group) { - // En kind=Group la fila YA es una entidad real del grafo. - // Doble click → focus inspector. Right click → focus. - // (El boton "Promote" no aplica — 0036d hace eso para - // contextos donde tenga sentido.) - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + // 0036e: en kind=Group la fila YA es una entidad real del + // grafo. Single click → focus + select en viewport. + // Doble click tambien dispara focus (mismo efecto). + // Right click → menu contextual con focus. + if (ImGui::IsItemHovered() + && (ImGui::IsMouseClicked(0) || ImGui::IsMouseDoubleClicked(0))) { app.want_focus_entity = true; app.focus_entity_id = row.id; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Click to focus entity in viewport"); + } if (ImGui::BeginPopupContextItem()) { if (ImGui::MenuItem(TI_FOCUS " Focus in Inspector")) { app.want_focus_entity = true; @@ -2122,8 +2126,20 @@ void views_node_groups_window(AppState& app) { ImGui::SetTooltip("Promote out of group (move to canvas)"); } } else { - // kind=Table — comportamiento original (DuckDB-backed). + // kind=Table (DuckDB-backed). + // 0036e: click ramificado por estado de promocion: + // - promovida → single click = focus en viewport. + // - no promovida → single click = no-op + hint tooltip. + // El doble click sobre fila no promovida sigue lanzando + // el flujo de promote (legado de 0036c) por convenience. bool is_promoted = !row.promoted_entity_id.empty(); + if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) { + if (is_promoted) { + app.want_focus_entity = true; + app.focus_entity_id = row.promoted_entity_id; + } + // else: no-op (hint mostrado via tooltip abajo). + } if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { if (is_promoted) { app.want_focus_entity = true; @@ -2134,6 +2150,13 @@ void views_node_groups_window(AppState& app) { app.promote_row_id = row.id; } } + if (ImGui::IsItemHovered()) { + if (is_promoted) { + ImGui::SetTooltip("Click to focus entity in viewport"); + } else { + ImGui::SetTooltip("promote first to focus\n(double click or right click to promote)"); + } + } if (ImGui::BeginPopupContextItem()) { if (is_promoted) { if (ImGui::MenuItem(TI_FOCUS " Focus in Inspector")) {