diff --git a/issues/0005-type-schema-fields.md b/issues/0005-type-schema-fields.md new file mode 100644 index 0000000..5fe8e7b --- /dev/null +++ b/issues/0005-type-schema-fields.md @@ -0,0 +1,56 @@ +--- +id: 0005 +title: Type schema en types.yaml — fields por tipo + principal_field +status: pending +priority: high +created: 2026-04-30 +--- + +## Objetivo + +Extender `types.yaml` para declarar el **esquema de campos** de cada entity +type, ademas de la cosmetica que ya existe (color/shape/icon). Es la base +sobre la que tiran 0007 (Type Editor) y 0008 (Inspector editable). + +## Formato propuesto + +```yaml +entities: + - name: Person + color: "#5B8DEF" + shape: circle + icon: ti-user + principal_field: name # opcional, default = "name" + fields: + - { name: name, type: string, required: true } + - { name: first_name, type: string } + - { name: last_name, type: string } + - { name: age, type: int } + - { name: nationality, type: string } + - { name: birth_date, type: date } + - { name: gender, type: enum, values: [male, female, other] } +``` + +Tipos soportados (v1): `string`, `int`, `float`, `bool`, `date`, `url`, +`enum`. `enum` requiere `values: [...]`. + +`principal_field` es el campo que el viewport usa como label visible del +nodo cuando no hay otro override (sustituye al actual `name` hardcodeado). + +## Cambios en codigo + +- `types_registry.h`: + - Nuevo `FieldSpec { string name; FieldKind kind; bool required; vector enum_values; }`. + - `EntitySpec` gana `string principal_field` y `vector fields`. +- `types_registry.cpp`: + - Parser tolerante: ignora `fields:` si no esta presente; tipos + desconocidos se mapean a `string` con warning. + - **Writer** nuevo: `bool types_save_yaml(const char* path, const ParsedTypes& types, string* error_msg)`. Round-trip que preserva el orden de claves y el formato compacto del ejemplo. +- `examples/types.yaml`: anadir `fields` a Person, Email, Domain, Phone, Org como semilla. + +## Definicion de hecho + +- Parser lee el yaml extendido sin romper el yaml existente (sin `fields`). +- Writer regenera un yaml legible y re-parseable (test round-trip). +- `examples/types.yaml` incluye `fields` para los 10 tipos existentes. +- `apply_types_yaml` no se rompe (no toca `fields` aun, eso es 0007/0008). diff --git a/issues/0006-projects-subfolders.md b/issues/0006-projects-subfolders.md new file mode 100644 index 0000000..1a50513 --- /dev/null +++ b/issues/0006-projects-subfolders.md @@ -0,0 +1,68 @@ +--- +id: 0006 +title: Sistema de proyectos dentro de la app — subcarpetas + switcher +status: pending +priority: high +created: 2026-04-30 +--- + +## Objetivo + +Cada "proyecto" es una subcarpeta junto al exe que contiene su propio +`operations.db`, `types.yaml` y `graph_explorer.db` (layouts). El usuario +crea proyectos, los nombra, y conmuta entre ellos sin reiniciar la app. +Esto es lo que va a permitir guardar muchos datos separados por caso/tema +sin acumularlos en un solo grafo. + +Independiente del concepto de "project" del registry (`projects/` raiz +del repo) — aqui es organizativo, dentro de la app desplegada. + +## Layout en disco (junto al exe) + +``` +graph_explorer.exe +graph_explorer.ini # estado global: ultimo proyecto abierto, recientes +projects/ + default/ + operations.db + types.yaml # copia editable; semilla = examples/types.yaml + graph_explorer.db # layouts de este proyecto + notes/ # opcional, .md sueltos + caso_aurgi/ + operations.db + types.yaml + graph_explorer.db + osint_demo/ + ... +``` + +## UI + +- Menu "Project" en la menubar: + - `New...` (input de nombre, valida slug, crea carpeta + semilla types.yaml + operations.db vacio con schema migrado). + - `Open...` (combo con los proyectos detectados en `./projects/`). + - `Recent` (lista los ultimos 5 desde `graph_explorer.ini`). + - `Reveal in explorer` (abre la carpeta del proyecto activo). +- Title bar muestra `graph_explorer — `. +- Switch de proyecto: cierra BDs, limpia grafo + viewport, abre nuevas BDs, hace reload. + +## Cambios en codigo + +- `main.cpp`: + - Resolver paths a traves de `g_active_project` en vez de `./operations.db` y `./graph_explorer.db` directos. + - Modo legacy: si arranca con `--input ` o positional, no usa proyecto (compatibilidad con flujo actual). +- `app_state` nuevo campo `string active_project_dir`. +- Nueva fn `project_create(name)` que escribe schema base de operations.db (entities/relations/...) — extraerlo de lo que ya hace el registry o copiar el DDL. +- `graph_explorer.ini` lee/escribe via `app_settings_*` ya existente. + +## Migracion del flujo actual + +- Al primer arranque sin `--input`, si no existe `./projects/`, crear `./projects/default/` y mover `./operations.db` + `./graph_explorer.db` ahi (si existen). +- Issues 0005/0007/0008 ya asumen este layout. + +## Definicion de hecho + +- Crear, abrir y conmutar proyectos sin reiniciar. +- Cada proyecto tiene su `operations.db`, `types.yaml`, `graph_explorer.db` aislados. +- "Recent" persiste entre sesiones via `graph_explorer.ini`. +- El layout (posiciones de nodos) es por-proyecto: cambiar de proyecto recupera las posiciones del nuevo, no las del anterior. diff --git a/issues/0007-type-editor-panel.md b/issues/0007-type-editor-panel.md new file mode 100644 index 0000000..8e70ec9 --- /dev/null +++ b/issues/0007-type-editor-panel.md @@ -0,0 +1,44 @@ +--- +id: 0007 +title: Type Editor panel — CRUD de tipos desde la app +status: pending +priority: medium +created: 2026-04-30 +depends_on: [0005, 0006] +--- + +## Objetivo + +Panel "Types" dockeable que permita editar el `types.yaml` del proyecto +activo desde dentro de la app. Cosmetica (color/shape/icon) y schema de +campos (lista de fields con tipo). Save reescribe el yaml y dispara +`apply_types_yaml` + reload del grafo. + +## UI + +- Tabs: "Entities" / "Relations". +- Lista a la izquierda con los tipos. Botones `+` (anadir), `-` (borrar), drag para reordenar. +- Panel derecho con la edicion del tipo seleccionado: + - Entities: name, color (color picker), shape (combo), icon (input + preview), principal_field (combo entre los fields). + - Relations: name, color, style. + - Sub-seccion **Fields** (solo entities): tabla con name / type / required / values (para enum). Botones para anadir/quitar/reordenar fila. +- Footer: `Save to types.yaml` (deshabilitado si no hay cambios) + `Reload from disk` (descarta cambios). + +## Cambios en codigo + +- `views.{h,cpp}`: nueva fn `views_type_editor(AppState&)` + entrada en panels menu. +- `app_state`: anade `ParsedTypes types_draft;` y `bool types_dirty;`. +- Usa `types_save_yaml` de 0005 para persistir. +- Tras Save: `apply_types_yaml(graph, types_draft)` + rebuild del IconAtlas. + +## Notas + +- Borrar un tipo NO toca las entidades existentes con ese `type_ref` — se quedan con un tipo "huerfano" hasta que el usuario las cambie. Mostrar warning antes del delete con conteo de entidades afectadas. +- Cambiar `principal_field` de un tipo regenera los labels visibles. + +## Definicion de hecho + +- Crear, editar y borrar entity/relation types desde la UI. +- Editar fields (anadir, cambiar type, marcar required, definir enum values). +- Save persiste a `projects//types.yaml` y aplica al grafo en vivo. +- Reload from disk descarta cambios. diff --git a/issues/0008-inspector-editable.md b/issues/0008-inspector-editable.md new file mode 100644 index 0000000..e55d7a5 --- /dev/null +++ b/issues/0008-inspector-editable.md @@ -0,0 +1,55 @@ +--- +id: 0008 +title: Inspector editable — campos del schema + tags + extras +status: pending +priority: high +created: 2026-04-30 +depends_on: [0005] +--- + +## Objetivo + +Refactor del Inspector (`views_inspector`) de read-only a editable. El usuario +edita los datos de un nodo y al pulsar Save se persisten en `operations.db` +con un solo UPDATE. + +## Bloques del nuevo Inspector + +1. **Identidad** + - `name` (InputText), `type_ref` (combo con tipos del proyecto), `description` (InputTextMultiline corto), `status` (combo: active/stale/corrupted/archived). + +2. **Fields del tipo** (driven by 0005) + - Render automatico segun `EntitySpec.fields`: + - `string` → InputText. + - `int` → InputInt. + - `float` → InputDouble. + - `bool` → Checkbox. + - `date` → InputText con placeholder `YYYY-MM-DD` + validacion. + - `url` → InputText + boton "Open" que lanza el navegador. + - `enum` → Combo con `values`. + - Lee/escribe a `entities.metadata` (JSON). Campos requeridos marcados con `*`. + +3. **Extras** (campos fuera del schema del tipo) + - Lista key-value, "+" para anadir. Tipo siempre string en v1. + - Permite que el usuario meta info ad-hoc sin tocar el schema. + +4. **Tags** + - Chips con boton X para quitar. Input al final con autocomplete (lista de tags ya existentes en la BD via `SELECT DISTINCT json_each.value FROM entities, json_each(entities.tags)`). + - Enter o coma valida el chip. + +5. **Notes** — sigue en su panel actual, pero anadir un boton "Open notes" en el Inspector que enfoca el panel Note. + +## Cambios en codigo + +- `entity_ops.{h,cpp}`: + - `bool entity_update(db_path, id, name, type_ref, description, status, tags_json, metadata_json)` — un solo UPDATE. + - `bool entity_list_distinct_tags(db_path, vector* out)` para autocomplete. +- `views.cpp::views_inspector`: reemplazo completo. Estado de edicion en `AppState` para que cerrar y reabrir conserve el draft hasta Save. +- Save → reload del grafo (mismo flujo que toolbar add-node hoy). + +## Definicion de hecho + +- Editar name, type_ref, description, status, tags, fields tipados y extras. +- Save persiste y el viewport refleja los cambios (label, color del tipo, etc.). +- Cancelar / cambiar de seleccion sin guardar muestra un confirm dialog. +- Tags se autocompletan con las existentes en la BD. diff --git a/issues/0009-tag-filter-fts-search.md b/issues/0009-tag-filter-fts-search.md new file mode 100644 index 0000000..179f534 --- /dev/null +++ b/issues/0009-tag-filter-fts-search.md @@ -0,0 +1,37 @@ +--- +id: 0009 +title: Filtro por tag + busqueda FTS5 en toolbar +status: pending +priority: medium +created: 2026-04-30 +depends_on: [0008] +--- + +## Objetivo + +Buscar y filtrar entidades por nombre/tag/description directamente desde la +toolbar, aprovechando el `entities_fts` que ya existe en `operations.db`. + +## UI + +- Toolbar: input de busqueda + chip-list de tags activas como filtro. +- Mientras se escribe, dropdown con resultados (max 20). Click en resultado selecciona/centra el nodo. +- Click en un tag (en Inspector o en el dropdown) lo anade como chip de filtro. +- Modos de filtro: + - **Highlight**: nodos no-coincidentes se atenuan (alpha bajado), pero siguen presentes. + - **Hide**: nodos no-coincidentes se ocultan del viewport. + - Toggle en la toolbar. + +## Cambios en codigo + +- Nueva fn `entity_search_fts(db_path, query, limit, vector* out)` con resultados ordenados por rank. +- Filtro vive en `AppState`: vector de tags + query string + modo (highlight/hide). +- En `graph_viewport.cpp` o donde aplique: aplicar mascara de visibilidad/alpha segun el filtro activo (sin tocar la BD). +- Reset filter desde la toolbar. + +## Definicion de hecho + +- FTS sobre name/description/tags/domain funciona y los resultados centran el viewport. +- Multiples chips de tag combinan con AND. +- Modo highlight y modo hide funcionan sin recargar el grafo. +- Limpiar filtro restaura visibilidad completa. diff --git a/issues/0010-table-node.md b/issues/0010-table-node.md new file mode 100644 index 0000000..1883a25 --- /dev/null +++ b/issues/0010-table-node.md @@ -0,0 +1,71 @@ +--- +id: 0010 +title: Nodo tabla — contenedor cuadrado con filas que son nodos del grafo +status: pending +priority: medium +created: 2026-04-30 +depends_on: [0004, 0005, 0008] +--- + +## Objetivo + +Un tipo especial de nodo, **Table**, que se renderiza en el viewport como +un rectangulo (no circulo) y agrupa visualmente N entidades del grafo. Cada +fila de la tabla = un nodo real del grafo (con su `type_ref`, sus fields, +sus tags). Las filas se pueden **extraer** (salen al canvas como nodos +sueltos) y **meter** (un nodo suelto entra como fila). + +Distinto del issue 0004 (vista tabla global por tipo): ese es una **ventana +auxiliar** que tabula entidades existentes; este es un **nodo en el grafo** +que existe en `entities` y posee filas via relaciones. + +## Modelo de datos + +- El nodo tabla es una entidad normal con `type_ref = 'Table'` y metadata: + ```json + { + "row_type": "Person", // tipo esperado de las filas (puede ser vacio = mixto) + "columns": ["name","age","email"], // subset de fields del row_type a mostrar como columnas + "expanded": true // estado de UI persistido + } + ``` +- La pertenencia se modela con relaciones: + - `name = "CONTAINS_ROW"`, `from_entity = `, `to_entity = `, `order = N`. +- Una fila puede pertenecer a varias tablas (varias relaciones `CONTAINS_ROW` apuntando al mismo nodo). Confirmado en la conversacion. +- Las columnas pueden ser fijas (`row_type` definido → columnas = subset de los `fields` de ese tipo) o libres (definidas por el creador de la tabla en `columns`). + +## Render en viewport + +- Forma: `square` o `rounded_square` con tamano dependiente del numero de filas (clamp a min/max). +- Cuando esta **colapsada**: caja con titulo + contador (`Table · 23 filas`). +- Cuando esta **expandida**: caja crece y dibuja un grid interno (filas x columnas) con los valores principales. Las filas son arrastrables individualmente. +- Las relaciones `CONTAINS_ROW` no se dibujan como aristas normales (serian ruido visual). En su lugar, una fila extraida muestra una arista fina punteada hacia su tabla de origen. +- Aristas entrantes/salientes del nodo tabla se dibujan al borde del rectangulo, no al centro. + +## Operaciones + +- **Crear tabla**: en context menu del viewport, "New table here". Pide `row_type` opcional. Crea entidad `Table` y la posiciona donde el click. +- **Anadir fila** (tabla expandida o seleccionada): boton "+ row" que crea una entidad nueva con `type_ref = row_type` (si esta definido) y la engancha via `CONTAINS_ROW`. +- **Extraer fila**: borra la relacion `CONTAINS_ROW`. La fila queda como nodo libre, posicionada al lado de la tabla. +- **Extraer multiples**: shift+click en filas dentro de la tabla expandida, "Extract selected". +- **Meter fila**: drag de un nodo sobre el rectangulo de una tabla. Confirm dialog si su `type_ref` no coincide con `row_type` de la tabla. +- **Editar fila**: doble-click en fila → abre Inspector con esa entidad seleccionada. + +## Cambios en codigo + +- `entity_ops`: + - `bool table_create(db_path, name, row_type, columns_csv, char* out_id)`. + - `bool table_add_row(db_path, table_id, char* out_row_id)` (crea entidad + relacion CONTAINS_ROW). + - `bool table_extract_row(db_path, table_id, row_id)` (borra solo la relacion). + - `bool table_attach_row(db_path, table_id, row_id, int order)`. + - `bool table_list_rows(db_path, table_id, vector* out_row_ids)`. +- Renderer del viewport (`graph_viewport.cpp` y/o `graph_renderer`): branch para `is_table_node` (detectado por `type_ref == "Table"`) que dibuja rectangulo + grid expandido y devuelve hit-testing por filas individuales. +- `graph_load_from_operations`: filtrar las aristas `CONTAINS_ROW` para que no entren en el layout fisico (no aplican fuerzas). + +## Definicion de hecho + +- Crear tabla, anadir filas, extraer y meter filas funciona round-trip via SQLite. +- Tabla colapsada y expandida se renderizan correctamente en el viewport. +- Doble-click en fila enfoca el Inspector con esa entidad. +- Una fila puede pertenecer a varias tablas sin duplicarse. +- Borrar la tabla pregunta: "borrar tabla y todas sus filas" o "extraer filas y borrar solo la tabla".