110 lines
4.1 KiB
C++
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;
|
|
}
|