154 Commits

Author SHA1 Message Date
egutierrez d6d7b03d09 chore: auto-commit (5 archivos)
- app.md
- appicon.ico
- extract_panel.cpp
- main.cpp
- views.cpp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:34 +02:00
egutierrez 688b399fd3 docs(flows): DoD obligatorio con user-facing surface + abrir issues 0100-0103 (taxonomia, frontmatter migration, dev_console, work dashboard)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 00:07:04 +02:00
egutierrez e742a203a0 chore: auto-commit (1 archivos)
- appicon.ico

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:33:25 +02:00
egutierrez a43303b30f merge graph_explorer Fase 2 cleanup (issue 0081-J) 2026-05-15 17:10:52 +02:00
egutierrez 35ac7d9a24 absorb jobs_actions into main Jobs table via Button renderer (Fase 2, issue 0081-J)
- Remove ##jobs_actions_tbl BeginTable (the separate actions mini-table).
- Add 2 virtual columns to main data_table: cancel (col 5) + delete (col 6).
  cancel = "Cancel" only for queued/running jobs; delete = "Delete" only for done/error/cancelled.
- ColumnSpec: CellRenderer::Button, button_action cancel_job/delete_job, amber/red colors.
- Maintain s_frow_ids parallel vector (job id per filtered row) for O(1) ButtonClick dispatch.
- Dispatch loop after render: ButtonClick -> jobs_cancel/jobs_delete + cache invalidation.
- te_rows (##te_rows) NOT migrated: table has Selectable AllowOverlap + right-click context
  menus + Promote/Demote SmallButton with AllowOverlap — requires RowContextMenu renderer hook
  not yet in data_table v1.2.0. Deferred to Phase 3 (TextInput + context-menu hook).

Build: Linux + Windows clean. pytest 125/125 passed.
2026-05-15 17:10:50 +02:00
egutierrez 265e14a71b merge Jobs renderers migration (issue 0081-J) 2026-05-15 16:45:43 +02:00
egutierrez 6f05e4bf60 migrate Jobs table to data_table::render with Progress/Duration/Badge renderers (issue 0081-J)
- Replace inline ImGui::BeginTable (6 cols) with data_table::render for 5 data
  columns: status (Badge), enricher (Text), target (Text), progress (Progress
  0..1), time_ms (Duration warn=1000ms error=10000ms).
- Add AppState::jobs_dt_state (data_table::State) for persistent filter/sort state.
- Keep Cancel/Delete action buttons via separate small ImGui::BeginTable (option a
  from 0081-J spec); data_table::State does not expose selected_row_idx yet.
  TODO(Phase 2): migrate actions to selected-row toolbar when State exposes it.
- Pre-filter cells by g_jobs_cache.filter_idx before passing to data_table so the
  combo filter and the declarative table filter are both respected.
- Linux build: OK. Tests: 125/125 pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:45:39 +02:00
egutierrez 6109911a3d merge data_table migration (issue 0081-J) 2026-05-15 14:42:10 +02:00
egutierrez f9d2229512 migrate entity panel Table to data_table_cpp_viz (issue 0081-J)
- views.cpp: replace ImGui::BeginTable("##tablev", 6, ...) + 200 LOC of
  manual sort/filter/clipper helpers with data_table::render("##tablev_dt", ...)
  AppState::table_dt_state persists sort+filter+stages between frames.
  Removed helpers: render_one_table, render_table_headers_with_filters,
  table_row_lt, table_row_field, table_col_name_by_id, k_table_cols,
  TableColMeta, TableSortCtx, ci_contains, build_visible lambda.
  NOTE: click-to-select-in-viewport removed from table panel; use Inspector.

- views.h: add #include "core/data_table_types.h" + data_table::State
  table_dt_state member to AppState.

- CMakeLists.txt: target_link_libraries(graph_explorer PRIVATE fn_table_viz)

- app.md: uses_functions += [data_table_cpp_viz, viz_render_cpp_viz,
  compute_stage_cpp_core, compute_pipeline_cpp_core, tql_emit_cpp_core,
  tql_apply_cpp_core, lua_engine_cpp_core, join_tables_cpp_core,
  auto_detect_type_cpp_core, compute_column_stats_cpp_core,
  llm_anthropic_cpp_core, tql_to_sql_cpp_core]

Panels NOT migrated (have inline widget interactions incompatible with
data_table::render):
  TODO: jobs_table (views_jobs.cpp) — ProgressBar + Cancel/Delete buttons
  TODO: ##te_rows (views.cpp NodeGroups) — promote/demote buttons, context menus
  TODO: ##ents/##rels (extract_panel.cpp) — editable InputText cells
  TODO: ##insp_id/##insp_fields/##enr_params/##te_fields — layout helpers (2 col)

Build: OK (1 warning: tmpnam in llm_anthropic.cpp, unrelated to migration)
Tests: 125 pytest passed, 0 failures.
2026-05-15 14:42:07 +02:00
egutierrez 0b8aadd774 feat(kotlin-compose): design system + 33 components + gallery_kt + e2e android emulator + scaffolder fixes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:29:05 +02:00
egutierrez 0bab5c97c7 chore: auto-commit (5 archivos)
- CMakeLists.txt
- agent.cpp
- agent.h
- gx-cli
- main.cpp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:30:27 +02:00
egutierrez fb84a566f2 chore: auto-commit (2 archivos)
- app.md
- cdp-cli/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:11:25 +02:00
egutierrez 5e6023f639 docs: issues 0041 (split thresholds) + 0042 (GLiNER2), supersedes mREBEL
Cierra el ciclo del analysis gliner_glirel_tuning: documenta en app.md el
pipeline NER+RE disponible en el registry y abre los dos issues que faltan
para cablearlo en extract_graph_hybrid + panel paste_extract. Archiva el
0042 original (mREBEL) tras la decision a favor de GLiNER2 (Apache 2.0,
joint NER+RE, 20-30x mas rapido en CPU).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 10:07:43 +02:00
egutierrez 3c98fee443 Merge quick/browser-cdp-issues — issues 0038, 0039, 0040 (browser externo + CDP + profiles) 2026-05-04 22:16:46 +02:00
egutierrez 8733b7d175 docs(issues): browser externo + CDP + multi-profile (0038, 0039, 0040)
- 0038: lanzar Chrome/Edge/Brave externo con --remote-debugging-port +
  --user-data-dir por profile, control via CDP desde cdp-cli Go.
  Decision Go vs C++ in-process documentada; deja la puerta abierta a
  un cliente C++ minimo solo para streaming en el futuro. Supersedes 0032.
- 0039: gestor de cookies/sesiones por profile via CDP — list, export
  EditThisCookie, import, clear selectivo, health checks con selectores,
  locks cuando un enricher esta usando el profile.
- 0040: profiles como concepto de primera clase — metadata (color, icon,
  browser_preference, UA, project, template), templates anon/auth/work/
  investigation, ProfilePicker reusable, project default, tag en
  executions.metrics. Actualiza 0038 para apuntar a 0040 como duenio
  del UX de profiles.
2026-05-04 22:16:42 +02:00
egutierrez 2a49c2b3fa Merge issue 0013 — Paste & Extract panel
- Panel ImGui dockable: textarea, Extract button, preview tables (entities + relations)
- Subprocess directo a enrichers/paste_extract/run.py (no usa jobs system; preview puro)
- Pipeline Python emite preview JSON; commit a operations.db lo hace C++ con dedupe (type_ref, name)
- 12 tests pytest nuevos (paste_extract enricher + extract_panel logic)
- GLiNER/GLiREL path cableado pero no ejercitado en tests (modelos pesados); validacion interactiva pendiente

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:31:54 +02:00
egutierrez f614a51c58 Merge issue 0035e — polish del Group + tests cross-platform
- Iconografia heredada del tipo mayoritario (homogeneo) o slate generico
- Threshold via manifest auto_group_threshold propagado a Python
- 11 tests pytest nuevos (visual inheritance, threshold override, migration)
- gx-cli group visual <id> mirror del SQL
- conftest.py endurecido: marker estricto + FN_REGISTRY_ROOT override

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:31:45 +02:00
egutierrez 65a4e7f4a8 docs: cerrar issue 0035e 2026-05-04 14:25:40 +02:00
egutierrez deb86b24ec test(0035e): cobertura del visual heredado, threshold override y migracion idempotente
- test_group_visual_inheritance.py (4 tests): homogeneo->Url heredado,
  heterogeneo->generico Group, vacio->generico, subgrupos anidados
  ignorados.
- test_manifest_threshold_override.py (4 tests): override 100 con 80
  unicas no agrupa; override bajo (20) si agrupa cuando se supera;
  threshold=0 cae al default 50; mirror Python del parser de manifest
  C++ confirma el campo se extrae como int.
- test_schema_migration_group_id.py (3 tests): mirror Python de
  project_migrate_schema, verifica idempotencia (1a y 2a apertura
  no duplican columna), no-op sobre BD ya migrada, datos previos
  sobreviven la migracion.
2026-05-04 14:25:03 +02:00
egutierrez 5417834950 feat(0035e): gx-cli group visual <id> espejea visual heredado del Group
Subcomando que ejecuta SELECT DISTINCT type_ref FROM entities WHERE
group_id = ? AND type_ref != 'Group' (mismo SQL que el lado C++ de
apply_group_inherited_visuals). Devuelve homogeneous bool, child_types
ordenado y inherited (tipo unico o 'Group' generico). Permite a los
tests pytest validar el contrato sin ejecutar el binario.
2026-05-04 14:24:54 +02:00
egutierrez 5056b5e0d8 docs(0013): cerrar issue 0013 — paste & extract panel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:24:47 +02:00
egutierrez 2233280302 test(0013): pytest suite for paste_extract
12 tests cubriendo:
- modo preview (no escribe a operations.db)
- dedupe dentro de un run (mismo (type_ref, name) una sola vez)
- texto vacio retorna error con exit 2
- max_entities trunca al limite
- types filtra por tipo IoC
- use_hybrid=false ⇒ stats.layers solo regex
- runs idempotentes producen mismo proposal

Apply-side (replica en Python del extract_panel_apply C++):
- inserta solo selected
- dedupe por (type_ref, name)
- inserta relaciones cuando endpoints resuelven
- skip relacion si endpoint unselected
- dedupe relacion (from, to, name) en repeticion

GLiNER/GLiREL no se ejercitan en pytest — los modelos pesan
cientos de MB. La logica de hybrid se valida con regex+regex
(mismo path de merge/dedup) y con tests unitarios separados si
se quisiera. Se documenta la decision en el docstring del modulo.

Helper real_registry_root resuelve fn_registry desde un worktree
(el conftest del repo asume ancestor que en worktrees no existe).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:24:44 +02:00
egutierrez 992e8fa06c feat(0013): wire extract_panel into main + CMakeLists + app.md
- main.cpp: registra panel_extract en g_panels[], llama
  extract_panel_init tras jobs_init y extract_panel_shutdown en el
  cleanup, anade extract_panel_render(g_app) al render.
- CMakeLists.txt: anade extract_panel.cpp al target.
- app.md: declara extract_iocs_py_cybersecurity y
  extract_graph_hybrid_py_pipelines en uses_functions (regla
  uses_functions.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:24:32 +02:00
egutierrez fdc6b91f4d feat(0013): add extract_panel — UI + subprocess + apply (dedupe)
extract_panel.{h,cpp}: panel ImGui dockeable con textarea grande,
boton Extract que lanza enrichers/paste_extract/run.py en un
std::thread aparte (no bloquea UI), tablas editables de entidades y
relaciones propuestas con checkboxes, y boton Apply Selected que
persiste a operations.db con dedupe por (type_ref, name) y por
(from, to, name) en relaciones.

Parser JSON ad-hoc (suficiente para el contrato del enricher) para
no añadir dependencias. Apply usa SQLite directamente (mismo
patron que entity_ops/jobs.cpp).

Anade panel_extract a AppState. La logica apply esta separada de
ImGui para poder testarla en aislamiento desde pytest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:24:26 +02:00
egutierrez 009d387d9a feat(0013): add paste_extract enricher (preview-only)
Modo preview puro — no escribe a operations.db. Recibe texto via
params.text y devuelve JSON con entidades y relaciones propuestas.
Cascada: extract_iocs (regex) siempre + extract_graph_hybrid
(GLiNER+GLiREL) opcional con use_hybrid=true. La aplicacion procesa
el JSON y persiste con dedupe via codigo C++ (extract_panel_apply).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:24:16 +02:00
egutierrez c27d8e7ffc feat(0035e): Group hereda iconografia de hijos homogeneos
apply_group_inherited_visuals(GraphData*, db_path) recorre los nodos
Group del grafo y, para cada uno, consulta los type_ref distintos de
sus hijos (entities con group_id apuntando al Group). Si todos
comparten un solo tipo, reasigna el type_id del Group al type_id de
ese tipo y fija shape_override = SHAPE_SQUARE para preservar el
cuadrado distintivo. Heterogeneo o sin hijos: el Group conserva su
visual generico (slate + ti-stack-2). Se invoca desde main.cpp y
reload_graph antes de apply_group_filter para que la reasignacion
sobreviva al compactado del array.
2026-05-04 14:21:01 +02:00
egutierrez 52495af779 feat(0035e): manifest auto_group_threshold override + propagacion a Python
Manifest YAML puede declarar 'auto_group_threshold: <int>' a nivel
top-level. enrichers.cpp lo parsea y lo guarda en EnricherSpec.
jobs.cpp lo inyecta como campo opcional 'auto_group_threshold' en el
JSON stdin del subprocess. Los enrichers Python que crean Groups
(web_search, split_words, split_sentences, extract_iocs_text) leen el
campo y, si viene > 0, lo usan en lugar de su DEFAULT_GROUP_THRESHOLD.
Helper _coerce_threshold tolera int / str / None / 0 cayendo al default.
2026-05-04 14:20:52 +02:00
egutierrez 65a14749f3 test(0035e): conftest resolver tolerante a worktrees fuera de fn_registry/
El resolver buscaba un marker 'registry.db' que falla en /home/lucas
con un .db parasito (4KB, sin tabla functions). Endurecemos el marker
a cmd/fn/main.go (mas estricto), anadimos override via FN_REGISTRY_ROOT
y un fallback a ~/fn_registry. Sin esto los tests de vendor_script
fallan al ejecutarse desde un git worktree.
2026-05-04 14:20:44 +02:00
egutierrez c6d17998e7 docs(app.md): document additional uses_functions deps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:46:48 +02:00
egutierrez 27849200ce Merge issue/0037b-fix-promote-button-and-tighter-placement 2026-05-04 01:36:54 +02:00
egutierrez 616c46297b fix: promote button funciona + placement direccional mas cercano
Dos bugs reportados tras 0036d/0037:

1. Promote button en NodeGroups window kind=Group no respondia al
   click. Causa: el Selectable con SpanAllColumns + AllowDoubleClick
   se tragaba el click destinado al SmallButton(TI_ARROW_UP) sobre
   la misma fila. ImGui tiene un flag dedicado para esto:
   AllowOverlap. Anyadido al Selectable y los botones recuperan los
   clicks. Mismo fix beneficia al kind=Table porque los botones
   "promote" y "expand" de DuckDB rows estaban en la misma situacion
   silenciosa.

2. Placement direccional de 0037 enviaba hijos hasta r=400 cuando
   habia colisiones, "muy lejos" segun el usuario. Ajustes:
   - Anillos mas cercanos: {60,90,120,150,180,210,240} en vez de
     {80,140,200,280,400}. Maximo 240px del source.
   - Mas anillos disponibles (7 vs 5) — fan-out gradual sin saltar
     bruscamente de 80 a 140.
   - Espaciado entre hermanos en arco usa cluster_min_dist=35 en
     vez de min_dist=60. Permite mas hijos por anillo dentro del
     arco de 45 grados (cap @ r=240 = 5 vs 3 antes).
   - Para 10-15 hijos tipicos los inner rings cubren todo dentro
     de 200px del source.

Build limpio. Tests WSL 102 / Windows 91 + 11 skipped.

Bonus: borrado /home/lucas/fn_registry/cpp/registry.db (vacio, 0
bytes, creado por algun binario con flag O_CREAT) — violacion de
db_locations.md (registry.db solo en raiz del repo). Era el motivo
de un test flaky de python_runtime_resolver.
2026-05-04 01:36:54 +02:00
egutierrez 28548e053d Merge issue/0037-directional-orphan-placement 2026-05-04 01:27:13 +02:00
egutierrez fdd169bc35 feat(0037): placement direccional 45 grados de orphans (away from centroide)
Antes los hijos del mismo anchor se distribuian en un anillo de 360
grados alrededor del padre. Cuando un enricher producia 10+ hijos,
se llenaban todas las direcciones y se pisaban nodos preexistentes.

Ahora los hijos se reparten en un abanico de 45 grados (pi/4) saliendo
del anchor en la direccion outward (vector anchor - centroide del resto
del grafo). Si solo hay 1 nodo placed o coincide con el anchor, default
a la derecha (0 rad). Capacidad por anillo restringida al arco
(arc_span * r / min_dist), con fallback de subida de radio en mismo
angulo si el slot ideal colisiona con un nodo no-orphan.

Solo afecta la pasada 2 (orphans con anchor). Pasadas 1 y 3 intactas.
build limpio, 102 pytest passed (WSL) + 91 passed/11 skipped (Windows).

Refs: issues/0037-directional-orphan-placement.md
2026-05-04 01:27:11 +02:00
egutierrez 502ce80b9f Merge issue/0036f-view-menu-open-nodegroups 2026-05-04 01:13:03 +02:00
egutierrez f6f53b60c3 feat(0036f): view menu accion 'Open NodeGroups for selected'
Anyade un item al menu View del framework via el nuevo callback
AppConfig.view_extras. El item:
- Esta enabled solo si la seleccion del viewport (o, en su defecto,
  el inspector) apunta a un nodo con type_ref Table o Group.
- Click → resuelve sql_id via entity_index_lookup, deriva
  NodeGroupsKind del type_ref y llama
  views_node_groups_open(g_app, sql_id, kind, ops_db). La API
  marca focus_request=true (cubierto por 0036c), de modo que la
  window emerge al frente si ya existia.
- Disabled → tooltip 'Select a Table or Group node first' (mostrado
  con AllowWhenDisabled).

Sin atajo de teclado (descartado por el usuario).
Sin submenu de windows abiertas (fase 2).

Refs: issues/0036f-view-menu-open-nodegroups.md
2026-05-04 01:12:58 +02:00
egutierrez 3e71fcc4ca docs(0036e): mark issue as done 2026-05-04 01:06:41 +02:00
egutierrez 436c23d155 Merge issue/0036e-row-click-focus 2026-05-04 01:06:34 +02:00
egutierrez f4e4dd5a0b feat(0036e): row click en NodeGroups enfoca la entidad (kind-aware)
- Reusa la infra de focus existente (AppState::want_focus_entity /
  focus_entity_id) ya cableada en main.cpp desde 0011.
- kind=Group: single click sobre la fila pone want_focus_entity con
  row.id; tooltip "Click to focus entity in viewport" en hover.
  El doble click sigue funcionando (mismo efecto). El menu contextual
  y el boton Promote-out-of-group quedan intactos.
- kind=Table promovida (row.promoted_entity_id no vacio): single click
  pone want_focus_entity con promoted_entity_id; tooltip de focus.
- kind=Table no promovida: single click es no-op visual; tooltip
  "promote first to focus\n(double click or right click to promote)"
  como hint sutil. El doble click sigue lanzando el flujo de promote
  (legado de 0036c) y el menu contextual ofrece Promote.
- Sin cambios en el handler de main.cpp — la logica de pan/zoom + select
  + load inspector ya existe y se reutiliza tal cual.
- Sin tests Python nuevos: el comportamiento es UI ImGui (no testeable
  desde pytest). 102 passed WSL / 91+11 skipped Windows sin regresion.

Refs: issues/0036e-row-click-focus-viewport.md
2026-05-04 01:06:30 +02:00
egutierrez 8bfe0b841c Merge issue/0036d-promote-kind-aware 2026-05-04 01:03:14 +02:00
egutierrez f0d8a5ad04 feat(0036d): promote kind-aware (Group → clear group_id)
NodeGroups window kind=Group ahora expone un boton SmallButton(TI_ARROW_UP)
por fila que saca la entidad del grupo (group_id = NULL) y dispara
reload del grafo. kind=Table mantiene el comportamiento de issue 0011.

- entity_ops: nueva op `entity_clear_group_id(db, id)` idempotente. Si
  la columna group_id no existe (BD pre-0035a) retorna true como no-op.
  Falla solo si la entidad no existe o SQLite revienta.
- views.cpp: extra columna "promote" en kind=Group, tooltip header
  diferenciado por kind, boton conectado a app.want_clear_group_id_entity.
- main.cpp: handler que ejecuta entity_clear_group_id, marca windows
  como dirty, llama reload_after_mutation y loguea
  `[node_groups] promoted X out of group`.
- gx-cli: flag `node update --clear-group-id` (booleano) y exposicion
  MCP en inputSchema + MCP_DISPATCH defaults para que el agente Echo
  pueda promover via tool calls.
- tests: 3 nuevos CLI (clear, idempotente, combinable con --name) y
  4 MCP (defaults, schema, dispatch end-to-end, idempotente).

WSL: 102 passed (95 base + 7).
Windows: 91 passed, 11 skipped (84 base + 7).

Refs: issues/0036d-promote-kind-aware.md
2026-05-04 01:03:11 +02:00
egutierrez 98e744ea4e docs(0036c): mark issue as done 2026-05-04 00:56:50 +02:00
egutierrez 176d9b232d Merge issue/0036c-doubleclick-group-opens-nodegroups 2026-05-04 00:56:47 +02:00
egutierrez 8f91b4ed23 feat(0036c): doble click en Group abre NodeGroups; cleanup Table panel
Cambia el dispatch del doble click sobre nodos del viewport: si el tipo
es Group o Table, ahora abre/enfoca la NodeGroups window correspondiente
via views_node_groups_open(...). El branch de Group ya no carga el
panel Table generico con un filtro group_id (logica heredada de 0035d
que provocaba el bug de "tabla vacia").

Limpieza correlativa en views_table:
  - Eliminado el breadcrumb "Group: <name> (N)" + boton Clear filter.
  - Eliminado el filtro r.group_id != table_filter_group_id en
    build_visible y la restriccion de types_present.
  - Eliminado el reset on-close de los campos de filtro.

Eliminados los campos AppState::table_filter_group_id y
table_filter_group_name (audit: git grep table_filter_group_id devuelve
vacio fuera de issues/).

Render de NodeGroups ahora consume focus_request: llama
SetNextWindowFocus() antes de Begin y SetWindowFocus() dentro, asi la
window queda al frente tanto al crearse como al re-enfocarse.

El right-click "Open NodeGroups" del context menu sigue intacto
(want_toggle_nodegroups + node_groups_set_expanded). El doble click es
flujo paralelo nuevo.

Refs: issues/0036c-double-click-group-opens-nodegroups.md
2026-05-04 00:56:44 +02:00
egutierrez 7277f63985 docs(0036b): mark issue as done 2026-05-04 00:52:36 +02:00
egutierrez b27578f093 Merge issue/0036b-kind-and-group-loader 2026-05-04 00:52:36 +02:00
egutierrez d6e13fddc3 feat(0036b): NodeGroups admite kind=Group + loader entities
NodeGroupsWindowState gana un discriminador `kind` (Table | Group) y
un flag `focus_request` (lo consumira 0036c). Por defecto Table, asi
que el flujo historico (DuckDB rows tras expand de un nodo Table) no
cambia.

kind=Group lee directamente operations.db consultando
`entities WHERE group_id = container_id` con columnas fijas
(id, name, type_ref, status, updated_at) ordenadas por updated_at DESC.
Los nuevos loaders viven en node_groups.cpp:

  - node_groups_count_for_group  -> SELECT count(*) ...
  - node_groups_page_for_group   -> SELECT id,name,type_ref,status,
                                     updated_at ... LIMIT ? OFFSET ?

Para columnas, opcion (A) del issue: pre-popular meta.columns con la
lista fija al abrir kind=Group, asi el render se mantiene generico.
NodeGroupsRow.values guarda los 5 campos en ese orden y row.id es la
key natural (= entity_id de la fila — al ser ya entidad, no hace falta
promocionarla).

Render en views.cpp ramifica por kind:

  - Table: layout original [id_col + columns + promoted] con doble
    click -> promote/focus.
  - Group: layout [columns fijas] sin promoted. Doble click sobre la
    fila ya pone want_focus_entity = id (los flujos posteriores 0036c-e
    afinan UX). Right click ofrece "Focus in Inspector".

main.cpp dispatcha por kind al refrescar paginas y, al cerrar via X,
solo llama a node_groups_set_expanded para kind=Table (Group no usa
ese flag).

views_node_groups_windows_sync se hace kind-aware: solo reconcilia
entries kind=Table contra el set de Tables expandidas; no toca las
entries kind=Group (las gestiona views_node_groups_open).

Nueva API publica:

  views_node_groups_open(app, container_id, kind, ops_db)

Crea o reusa la entry, setea focus_request=true y para kind=Group
pre-popula meta.columns + intenta leer `name` del Group para el
titulo. Sin caller todavia — la consume 0036c.

Tests:
  - tests/test_node_groups_loader.py (6 tests) verifica el contrato
    SQL via gx-cli. Nuevo subcomando `gx-cli group page <id>` espejea
    el loader C++ exactamente (mismo SQL); tambien expuesto como tool
    MCP `group_page` para que Echo pueda inspeccionar Groups.

Resultado:
  - WSL: 89 -> 95 passed
  - Windows: 78+11 -> 84+11 passed
  - Build C++ Windows limpio, sin warnings nuevos.
  - Regresion kind=Table: comportamiento identico (mismo render,
    mismo loader DuckDB).

Refs: issues/0036b-kind-discriminator-and-group-loader.md
2026-05-04 00:52:25 +02:00
egutierrez 2a783187a3 Merge issue/0036a-rename-nodegroups 2026-05-04 00:43:25 +02:00
egutierrez f8b16b2a5a docs(0036a): mark issue as done 2026-05-04 00:43:22 +02:00
egutierrez 810b564127 refactor(0036a): rename Table-expanded -> NodeGroups (paperwork)
Rename masivo sin cambio de comportamiento. Habilita 0036b-f que ya
asumen la nueva convencion.

Archivos:
- tableview.{cpp,h} -> node_groups.{cpp,h} (git mv para preservar history)
- CMakeLists.txt: tableview.cpp -> node_groups.cpp

Tipos:
- TableWindowState  -> NodeGroupsWindowState  (views.h)
- TableMetadata     -> NodeGroupsMeta         (node_groups.h)
- TablePageRow      -> NodeGroupsRow          (node_groups.h)

Campos AppState:
- table_windows         -> node_groups_windows
- table_node_counts     -> node_groups_counts
- toggle_expanded_id    -> toggle_nodegroups_id
- want_toggle_expanded  -> want_toggle_nodegroups

Funciones (window por contenedor — NO el panel generico Table):
- tableview_create / count / page / smoke_test / resolve_path /
  refresh_counts / list_columns / get_metadata / set_expanded /
  set_columns / promote_row / demote_row / ingest_file
  -> prefijo node_groups_*
- views_table_window         -> views_node_groups_window
- views_table_windows_sync   -> views_node_groups_windows_sync
- views_table_overlay        -> views_node_groups_overlay

Strings de UI:
- "Expand table" / "Collapse table" -> "Open NodeGroups" / "Close NodeGroups"
- Window title "<icon> <name>" -> "<icon> NodeGroups: <name>"
- Tooltip "(no expanded tables)" -> "(no open NodeGroups)"
- Logs [tableview_*] -> [node_groups_*]

Preservados intencionalmente (no son cambio de identificadores C++):
- CLI flag --test-tableview (cambiarlo seria cambio de behavior publico)
- Valor 'tableview' en columna entities.source (cambiarlo afectaria
  datos persistidos en BD)

NO tocado:
- Panel generico Table (views_table, panel_table, table_rows,
  table_show_all, table_search_buf, table_filter_*, table_col_filters,
  table_active_tab, TableRow, table_filter_group_*, etc.)
- issues/completed/* (historia)

Verificacion:
- Build C++ Linux + Windows: green sin warnings nuevos.
- pytest WSL: 89 passed.
- pytest Windows: 78 passed + 11 skipped.
- git grep audit: solo residuos en issues/ (historia) + CLI flag y
  source DB value preservados.

Refs: issues/0036a-rename-nodegroups.md
2026-05-04 00:43:16 +02:00
egutierrez 441a697abf Merge issue/split-words-enricher 2026-05-04 00:14:57 +02:00