Files
fn_registry/dev/issues/0049k-graph-explorer-app.md
T
egutierrez f8f72e4bf7 chore(issues): plan 0049 OSINT graph viewer multi-issue
Aggregates the planning artifacts for the 0049 series (umbrella + 0049a..0049k):

- New rule cpp_apps.md (registered in INDEX) — standardize structure, CMake
  patterns, app.md frontmatter and sub-repo for C++ apps; points to the
  authoritative cpp/PATTERNS.md and cpp/DESIGN_SYSTEM.md.
- Feature flag osint_graph_v1 (disabled until 0049k closes).
- Issue 0049 (umbrella) and sub-issues 0049b..0049k describing the GPU
  rendering system, force-layout, types, sources, labels and the final
  graph_explorer app integration.
- README updated with the new rows (all pending; 0049a will flip to
  completed in the next commit).
2026-04-29 21:08:36 +02:00

11 KiB
Raw Blame History

0049k — App graph_explorer (proyecto osint_graph)

Metadata

Campo Valor
ID 0049k
Estado pendiente
Prioridad alta
Tipo feature — parte de #0049integracion final + activacion del feature flag

Dependencias

Bloqueada por:

  • 0049a — proyecto + sub-repo
  • 0049f — renderer completo
  • 0049g — source operations.db
  • 0049h — layout GPU
  • 0049i — layouts estaticos + viewport
  • 0049j — labels

Desbloquea: activacion del feature flag osint_graph_v1.


Objetivo

Construir la app C++ graph_explorer en projects/osint_graph/apps/graph_explorer/, agnostica del backend, capaz de abrir cualquier operations.db del registry y visualizarlo con shapes/iconos/layouts/filtros/labels. Cumplir la regla cpp_apps.md y apps_tbd.md.

Contexto

Toda la libreria de visualizacion ya existe en cpp/functions/viz/ tras 0049bj. Esta app es el consumer final que orquesta:

  • graph_load_from_operations para cargar datos.
  • types_registry (modulo local) para mergear types.yaml opcional con tipos descubiertos.
  • graph_force_layout_gpu (o CPU) corriendo segun toggle.
  • graph_renderer + graph_viewport + graph_labels para visualizacion.
  • fn_ui::* (toolbar, modal, select, etc.) para chrome.

Arquitectura

projects/osint_graph/apps/graph_explorer/
├── .git/                          # sub-repo dataforge/graph_explorer (creado en 0049a)
├── app.md                         # NEW
├── CMakeLists.txt                 # NEW
├── main.cpp                       # NEW
├── data.{h,cpp}                   # NEW: dispatcher GraphLoadFn
├── views.{h,cpp}                  # NEW: Toolbar, Legend, Inspector, Stats
├── types_registry.{h,cpp}         # NEW: lee types.yaml y mergea
└── examples/
    └── types.yaml                 # NEW: ejemplo OSINT con Person/Email/Domain/...

app.md frontmatter

---
name: graph_explorer
lang: cpp
domain: viz
description: "Visor de grafos GPU-accelerated agnostico del backend. Lee operations.db de cualquier app del registry y permite explorar entidades/relaciones con shapes/iconos/layouts/filtros."
tags: [imgui, graph, osint, visualization, gpu]
uses_functions:
  - graph_renderer_cpp_viz
  - graph_force_layout_cpp_viz
  - graph_force_layout_gpu_cpp_viz
  - graph_layouts_cpp_viz
  - graph_viewport_cpp_viz
  - graph_labels_cpp_viz
  - graph_icons_cpp_viz
  - graph_sources_cpp_viz
  - toolbar_cpp_core
  - modal_dialog_cpp_core
  - select_cpp_core
  - text_input_cpp_core
  - tree_view_cpp_core
  - page_header_cpp_core
  - fullscreen_window_cpp_core
uses_types: []
framework: "imgui"
entry_point: "main.cpp"
dir_path: "projects/osint_graph/apps/graph_explorer"
repo_url: "https://gitea-.../dataforge/graph_explorer"
---

CLI

graph_explorer [--input operations <path>] [--types <yaml>] [--layout force|grid|...]
graph_explorer apps/registry_dashboard/operations.db
graph_explorer --types projects/osint_graph/apps/graph_explorer/examples/types.yaml \
               apps/element_agents/operations.db

Layout de ventanas

┌──────────────────────────────────────────────────────────┐
│ MainMenuBar (View | Settings | About)                    │
├─[Toolbar: Open | Layout: [force▼] | Filters | Fit | …]──┤
├─Legend──┬───────────────────────────────────────┬─Inspector─┤
│ Type    │                                       │  id: ...   │
│ ☑ Person│                                       │  type: ... │
│ ☑ Email │           Viewport (FBO)              │  metadata… │
│ ☐ Domain│                                       │            │
│ Rels:   │                                       │  Neighbors:│
│ ☑ owns  │                                       │  • node A  │
│ ...     │                                       │  • node B  │
├─────────┴───────────────────────────────────────┴────────────┤
│ Stats: nodes=12345 edges=54321 fps=60 energy=0.04 sel=2     │
└──────────────────────────────────────────────────────────┘

Tareas

Fase 1 — Esqueleto

  • 1.1 Crear app.md con frontmatter completo.
  • 1.2 Crear CMakeLists.txt siguiendo cpp_apps.md. Listar todas las funciones del registry usadas explicitamente.
  • 1.3 main.cpp minimo con fn::run_app(cfg, render) + parseo de --input/--types/--layout.
  • 1.4 Registrar la app en cpp/CMakeLists.txt con el patron de registry_dashboard:
    set(_GE_DIR ${CMAKE_SOURCE_DIR}/../projects/osint_graph/apps/graph_explorer)
    if(EXISTS ${_GE_DIR}/CMakeLists.txt)
        add_subdirectory(${_GE_DIR} ${CMAKE_BINARY_DIR}/apps/graph_explorer)
    endif()
    

Fase 2 — data.{h,cpp} (dispatcher de sources)

  • 2.1 Implementar dispatcher segun --input:
    bool load_graph(const InputArgs& args, GraphData* out, GraphLoadStats* stats) {
        if (args.kind == INPUT_OPERATIONS) return graph_load_from_operations(args.uri, out, stats);
        // futuro: json/jsonl/graphml
        stats->errors++; return false;
    }
    
  • 2.2 Helper para reload (re-llamar la misma GraphLoadFn con la misma URI).

Fase 3 — types_registry.{h,cpp}

  • 3.1 Parser de types.yaml:
    entities:
      - name: Person
        color: "#5B8DEF"
        shape: circle
        icon: ti-user
      - name: Email
        color: "#58CA8C"
        shape: square
        icon: ti-mail
    relations:
      - name: owns
        color: "#888888"
        style: solid
    
  • 3.2 Helper apply_types_yaml(GraphData&, const ParsedTypes&): para cada EntityType/RelationType del grafo cuyo name matchee en el yaml, sobrescribir color/shape/icon_id/style. Tipos no encontrados se quedan con default (color por hash).
  • 3.3 Iconos: mapear nombre ti-user → codepoint Tabler usando una tabla en el header icons_tabler.h (helpers ya existentes o anadir uno nuevo tabler_codepoint_by_name(const char*)).
  • 3.4 Construccion del IconAtlas: collect codepoints distintos del yaml + fallback (icono "?" para no encontrados), llamar graph_icons_build(...).

Fase 4 — views.{h,cpp} (paneles)

  • 4.1 Toolbar: Open file…, Layout selector (select), Filters… (modal con checkboxes por tipo), Fit view, Save layout, Export PNG.
  • 4.2 Legend: lista de tipos con color swatch, icono y toggle de visibilidad. Toggle aplica a todos los nodos del tipo: for each node where type_id == t: flags ^= NF_VISIBLE. Igual para relaciones.
  • 4.3 Inspector: cuando hay seleccion de un solo nodo, mostrar id, type, metadata (raw JSON formateado), lista de vecinos directos clickables (click cambia seleccion).
  • 4.4 Stats: linea fija con counts + fps + energia.
  • 4.5 Paneles toggleables via AppConfig::panels.

Fase 5 — Persistencia

  • 5.1 graph_explorer.db SQLite junto al exe con tabla layouts(graph_hash TEXT, node_id TEXT, x REAL, y REAL, pinned INT, updated_at).
  • 5.2 graph_hash = hash del path del input (operations.db) para diferenciar entre grafos.
  • 5.3 Save layout boton en la toolbar: snapshot de posiciones + flags.
  • 5.4 Al cargar un grafo conocido (mismo hash), pre-aplicar las posiciones guardadas.
  • 5.5 Layout de paneles ImGui via layout_storage (ya existe como funcion publica desde 0042).

Fase 6 — types.yaml ejemplo

  • 6.1 Crear examples/types.yaml con ~10 tipos OSINT comunes (Person, Email, Domain, Phone, Org, IBAN, Account, Document, Address, Url) y 5 relaciones (owns, knows, located_in, transfers_to, member_of).

Fase 7 — TBD trabajo

  • 7.1 Trabajar en rama quick/graph-explorer o issue/0049k-graph-explorer-app segun preferencia.
  • 7.2 Commits atomicos por panel/feature.
  • 7.3 Merge --no-ff a master del sub-repo dataforge/graph_explorer.
  • 7.4 Push del sub-repo + push de fn_registry con la nueva ubicacion + app.md.

Fase 8 — Indexado + flag

  • 8.1 ./fn index desde la raiz.
  • 8.2 Verificar:
    SELECT id, name, project_id FROM apps WHERE id='graph_explorer_cpp_viz';
    
  • 8.3 Activar feature flag en dev/feature_flags.json:
    "osint_graph_v1": { "enabled": true, "issue": "0049", ... }
    
  • 8.4 Mover el issue principal 0049 + todos los sub-issues 0049ak a dev/issues/completed/ y actualizar README.

Fase 9 — Verificacion end-to-end

  • 9.1 Abrir apps/registry_dashboard/operations.db y verificar que se ven entidades.
  • 9.2 Abrir un dataset OSINT real (cuando exista) o un fixture en ~/vaults/osint_graph/raw/.
  • 9.3 Verificar todas las features: filtrado por tipo, drag, multi-select, lasso, layouts, labels, save/load layout.

Criterio de done

  • graph_explorer apps/registry_dashboard/operations.db arranca y muestra el grafo de entidades.
  • Con --types examples/types.yaml, los tipos se ven con shapes/iconos/colores correctos.
  • 50k nodos cargan y se navegan a 60fps con layout GPU.
  • Layouts intercambiables en runtime via toolbar.
  • Filtros, drag, multi-select, lasso, labels funcionando.
  • Persistencia de layout entre sesiones.
  • App registrada en registry.db con uses_functions correcto.
  • Sub-repo dataforge/graph_explorer con master limpio y pushed.
  • Feature flag osint_graph_v1 = true.
  • Issue 0049 + sub-issues movidos a completed.

Riesgos

Riesgo Mitigacion
Parser YAML pesa: anadir libreria Usar yaml-cpp si ya esta vendoreada; si no, parser minimal hand-rolled (10 tipos no necesita full YAML)
Mapeo ti-user → codepoint requiere tabla nueva Generar a partir del icons_tabler.h existente con un script una sola vez
Inspector con metadata grande satura el panel Truncar a ~512 chars + scroll del panel
Save layout en grafo grande es lento UPSERT por nodo es O(N) con prepared statement; para 50k = ~100 ms aceptable en boton, no en frame
Operations.db de un app esta locked si la app corre SQLite con mode=ro o immutable=1 en el URI