---
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<std::mutex> 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`).
