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
#include"viz/agent_runs_timeline.h"staticfn_viz::TimelineStateg_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=[](conststd::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).