feat: wire sse_client_cpp_core for live updates from /api/boards/issues/stream

This commit is contained in:
agent
2026-05-18 20:05:14 +02:00
parent 264c5939f3
commit 4f5e9f6fbe
16 changed files with 726 additions and 44 deletions
+39 -3
View File
@@ -6,12 +6,16 @@
#include "core/panel_menu.h"
#include "core/icons_tabler.h"
#include "core/logger.h"
#include "core/sse_client.h"
#include "panels.h"
#include <imgui.h>
#include <chrono>
#include <cstring>
#include <cstdio>
#include <mutex>
#include <string>
#include <thread>
static bool g_show_board = true;
static bool g_show_calendar = true;
@@ -22,6 +26,11 @@ static bool g_show_dod = true;
static kanban_cpp::AppState g_state;
// SSE client: receives push notifications from the backend stream so the
// board updates without polling. Lifetime tied to main() — stop() before
// returning so the worker thread joins cleanly.
static fn_sse::Client g_sse_client;
static void render() {
if (g_show_board) kanban_cpp::draw_board (g_state, &g_show_board);
if (g_show_calendar) kanban_cpp::draw_calendar (g_state, &g_show_calendar);
@@ -66,8 +75,35 @@ int main(int argc, char** argv) {
cfg.panels = panels;
cfg.panel_count = sizeof(panels) / sizeof(panels[0]);
// First refresh on startup (best-effort; failure surfaces in the Board).
kanban_cpp::refresh_data(g_state);
// First refresh on startup en thread separado: no bloquea primer frame
// si el backend :8403 esta caido (timeout HTTP ~9s).
std::thread([](){ kanban_cpp::refresh_data(g_state); }).detach();
return fn::run_app(cfg, render);
// SSE live updates: arranca tras 500ms para no competir con el primer
// refresh inicial. Auto-reconecta con backoff si el endpoint no existe
// aun o si el backend cae — NUNCA crashea el frame.
std::thread([](){
std::this_thread::sleep_for(std::chrono::milliseconds(500));
fn_sse::Config sse_cfg;
sse_cfg.url = g_state.cfg.base_url + "/api/boards/issues/stream";
sse_cfg.auto_reconnect = true;
g_sse_client.start(sse_cfg,
// on_event: cualquier evento dispara un refresh asincrono.
[](const fn_sse::Event& /*ev*/) {
std::thread([](){ kanban_cpp::refresh_data(g_state); }).detach();
},
// on_status: actualiza el badge UI bajo mutex.
[](const std::string& status) {
std::lock_guard<std::mutex> lock(g_state.mu);
g_state.sse_status = status;
});
}).detach();
int rc = fn::run_app(cfg, render);
// Kill SSE worker antes de salir — orden importa para evitar dangling
// thread storage cuando se destruyen los globales.
g_sse_client.stop();
return rc;
}