Files
kanban_cpp/main.cpp
T

110 lines
4.1 KiB
C++

// main.cpp — kanban_cpp entry point.
//
// Six panels declared via cfg.panels. fn::run_app paints the menubar /
// dockspace / about / layouts automatically.
#include "app_base.h"
#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;
static bool g_show_dashboard = true;
static bool g_show_runs = true;
static bool g_show_worktrees = true;
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);
if (g_show_dashboard) kanban_cpp::draw_dashboard (g_state, &g_show_dashboard);
if (g_show_runs) kanban_cpp::draw_agent_runs(g_state, &g_show_runs);
if (g_show_worktrees) kanban_cpp::draw_worktrees (g_state, &g_show_worktrees);
if (g_show_dod) kanban_cpp::draw_dod (g_state, &g_show_dod);
}
// Headless self-test: verifies the binary links, panels include compile,
// and the data layer accepts a config. Used by app.md e2e_checks.
static int run_self_test() {
std::printf("kanban_cpp --self-test\n");
kanban_cpp::AppState s;
s.cfg.base_url = "http://127.0.0.1:65535"; // unreachable on purpose
bool ok = kanban_cpp::health(s.cfg);
std::printf(" health(unreachable) = %s (expected: false)\n", ok ? "true" : "false");
if (ok) return 1;
std::printf("OK\n");
return 0;
}
int main(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "--self-test") == 0) return run_self_test();
}
static fn_ui::PanelToggle panels[] = {
{ "Board", nullptr, &g_show_board },
{ "Calendar", nullptr, &g_show_calendar },
{ "Dashboard", nullptr, &g_show_dashboard },
{ "Agent runs", nullptr, &g_show_runs },
{ "Worktrees", nullptr, &g_show_worktrees },
{ "DoD inspector", nullptr, &g_show_dod },
};
fn::AppConfig cfg;
cfg.title = "kanban_cpp — agentes LLM con DoD";
cfg.about = { "kanban_cpp", "0.1.0",
"Clon C++ ImGui de kanban_web — agentes LLM con DoD evidence" };
cfg.log = { "kanban_cpp.log", 1 };
cfg.panels = panels;
cfg.panel_count = sizeof(panels) / sizeof(panels[0]);
// 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();
// 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;
}