#pragma once // // agent_runs_timeline.h — ImGui panel: cross-app timeline of agent runs. // // API surface declared per issue 0118. Render is impure (uses ImGui); pure // helpers live in agent_runs_timeline_helpers.{h,cpp} so unit tests can // exercise filter / sort / formatting without a GL context. // // Typical wiring (consumer side): // // fn_viz::TimelineState g_state; // g_state.sse_url = "http://127.0.0.1:8497/api/runs/stream"; // g_state.on_select = [](const std::string& id){ open_detail_panel(id); }; // // ImGui::Begin("Agent runs"); // fn_viz::render_agent_runs_timeline(g_state); // ImGui::End(); // // // Whenever a worker thread (real SSE client, polling fallback, fixture) // // wants to update the list: // { // std::lock_guard lk(g_state.runs_mutex); // g_state.runs = new_runs; // g_state.connection_status = "connected"; // } // // SSE client itself is OUT OF SCOPE for this issue — see the documented stub // in poll_sse_runs() and the .md ## Gotchas section. #include #include #include #include #include namespace fn_viz { struct AgentRun { std::string id; std::string app; // kanban_cpp | skill_tree | ... std::string issue_id; std::string card_id; std::string branch; std::string status; // pending|running|done|validated|merged|aborted|failed int64_t started_at = 0; // unix epoch seconds int64_t finished_at = 0; // 0 if still running int dod_total = 0; int dod_done = 0; int dod_validated = 0; }; struct TimelineFilter { std::vector apps; std::vector statuses; int64_t since_ts = 0; // 0 means "no lower bound" }; struct TimelineState { std::string sse_url; std::vector runs; TimelineFilter filter; std::function on_select; std::string selected_run_id; std::string connection_status; // "connected"|"disconnected"|"connecting" std::mutex runs_mutex; // SSE thread writes, UI reads }; // Renders the timeline INLINE inside the current ImGui window. The caller // owns ImGui::Begin/End — this function only draws filters, badge, table // and footer counts. Safe to call every frame; cost is O(N) over runs. void render_agent_runs_timeline(TimelineState& state); // STUB. Documented in agent_runs_timeline.md ## Gotchas. // Today this is a no-op. The intent is that a future issue wires either: // (a) a real SSE client (libcurl multi + custom read callback), or // (b) a polling fallback that calls fn_core::http_request("GET", url, ...) // every N seconds and replaces state.runs under runs_mutex. // Until then, the consumer is responsible for populating state.runs (e.g. // test fixtures, or an in-process orchestrator that owns the data already). void poll_sse_runs(TimelineState& state); } // namespace fn_viz