--- name: agent_runs_timeline kind: function lang: cpp domain: viz version: 0.1.0 description: "Panel ImGui timeline de agent runs cross-app, filtros + sort + click row + connection badge" tags: [agents, timeline, sse, imgui, viz, panel] purity: impure signature: "fn_viz::render_agent_runs_timeline(state) — ImGui draw call" params: - name: state desc: "TimelineState con runs, filter, callbacks. Mutex protege runs ante el thread del SSE/poll" output: "void — pinta el panel inline en el current ImGui window" uses_functions: - http_request_cpp_core uses_types: [] returns: [] returns_optional: false error_type: error_go_core imports: [imgui] example: "ver ## Ejemplo abajo; demo visual TODO en cpp/apps/primitives_gallery/demos_viz.cpp" tested: true tests: [test_agent_runs_timeline] test_file_path: "cpp/tests/test_agent_runs_timeline.cpp" file_path: "cpp/functions/viz/agent_runs_timeline.cpp" framework: "imgui" --- # agent_runs_timeline Panel ImGui que pinta una tabla cross-app de "agent runs" (ejecuciones de `fn-orquestador` / `/autonomous-task` / hooks que disparan agentes) con: - Filtros: multi-select de **apps**, multi-select de **statuses**, slider `Since (days)` para acotar `started_at`. - **Connection badge** (● verde / ○ rojo / ◐ amarillo) segun `state.connection_status`. - Tabla ordenada por `started_at` DESC con columnas: status icon | app (chip coloreado) | issue/card | branch | dod (badge `done/validated/total`) | duration | started (humano). - Row click → setea `state.selected_run_id` e invoca `state.on_select(id)` si existe. - Footer: contadores por status sobre el set FULL (no el filtrado). La logica pura (filter / sort / formato duration / mapping status→color/icon / app→chip hex) vive en `agent_runs_timeline_helpers.{h,cpp}` y es testable sin contexto ImGui (ver `cpp/tests/test_agent_runs_timeline.cpp`). ## Ejemplo ```cpp #include "viz/agent_runs_timeline.h" static fn_viz::TimelineState g_runs_state; // En setup (una vez): g_runs_state.sse_url = "http://127.0.0.1:8497/api/runs/stream"; g_runs_state.on_select = [](const std::string& run_id) { open_run_detail_window(run_id); }; // Por algun mecanismo (test fixture, http poll, future SSE) — poblar runs: { std::lock_guard lk(g_runs_state.runs_mutex); g_runs_state.runs = { {"r1", "kanban_cpp", "0118", "c1", "issue/0118", "running", /*started*/ 1731920000, /*finished*/ 0, /*dod*/ 5, 2, 0}, }; g_runs_state.connection_status = "connected"; } // En render (cada frame): ImGui::Begin("Agent runs"); fn_viz::render_agent_runs_timeline(g_runs_state); ImGui::End(); ``` ## Cuando usarla Cuando construyas un dashboard cross-app que exponga el estado de los agentes (orquestador, autonomous-task, hooks reactivos): registry_dashboard, kanban, agent_runner_ui. La quieres siempre que el usuario necesite una vista unica "que esta corriendo / que merge / que fallo" sin abrir N apps. Tambien util como panel `Recent runs` dentro de una app que dispara agentes propios. ## Gotchas - **SSE client NO esta implementado.** `poll_sse_runs()` es un stub documentado. Hoy el consumer tiene que poblar `state.runs` por su cuenta (fixture, orquestador in-proc, o polling manual con `http_request_cpp_core` contra `GET /api/runs`). Cuando exista un endpoint estable `/api/runs/stream`, conectar via libcurl multi en thread aparte y empujar updates bajo `state.runs_mutex`. - **Timestamps en segundos epoch** (no ms). `format_duration` asume `int64_t` unix-epoch seconds; si tu fuente emite ms, divide entre 1000 antes de rellenar `started_at`/`finished_at`. - **`runs_mutex` es obligatorio** cuando algun thread distinto del UI escribe `state.runs` o `state.connection_status`. El render hace un snapshot bajo lock al principio del frame para no bloquear el resto del dibujo. - **No autoescroll**. Si la tabla crece y queres mantener "lo mas reciente visible", anade tu scroll-to-top tras detectar `runs.size()` cambia. Decision deliberada: muchas vistas prefieren no saltar mientras lees. - **Selectable + SpanAllColumns**: usa `ImGuiSelectableFlags_AllowOverlap` (Dear ImGui >= 1.91). En forks viejos pasaba a llamarse `AllowItemOverlap` — si tu vendor de imgui es viejo, sustituye en `agent_runs_timeline.cpp`. - **App chip colors** estan hardcodeados en `app_chip_hex`. Anadir entrada cuando salga una app nueva; sin entrada cae a gris neutral. Mantener el hex aligned con `icon.accent` del `app.md` de cada app C++. - **Filter "Since (days)"** convierte a/desde epoch usando `std::time(nullptr)`: no determinista en tests. Por eso los tests del helper pasan `since_ts` directamente en segundos epoch ficticios (ej. `f.since_ts = 1000`).