feat: initial scaffold kanban_cpp v0.1.0

C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar,
Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry
functions http_request, kpi_card, sparkline, agent_runs_timeline,
dod_evidence_panel. Backend Go on :8403 (independent operations.db from
kanban_web).
This commit is contained in:
Egutierrez
2026-05-18 18:46:09 +02:00
commit a76ec74338
42 changed files with 5922 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
// panel_worktrees.cpp — lists git worktrees via `git worktree list --porcelain`.
//
// Read-only MVP: shows path, head, branch. Future work: create/remove from
// inside the panel (TODO in app.md ## Gotchas).
#include "panels.h"
#include "core/icons_tabler.h"
#include <imgui.h>
#include <cstdio>
#include <string>
#include <vector>
#include <array>
namespace kanban_cpp {
namespace {
struct WT {
std::string path;
std::string head;
std::string branch;
};
std::vector<WT> scan_worktrees() {
std::vector<WT> out;
#ifdef _WIN32
FILE* fp = _popen("git worktree list --porcelain 2>nul", "r");
#else
FILE* fp = popen("git worktree list --porcelain 2>/dev/null", "r");
#endif
if (!fp) return out;
std::array<char, 1024> buf;
WT cur;
while (std::fgets(buf.data(), static_cast<int>(buf.size()), fp)) {
std::string line(buf.data());
while (!line.empty() && (line.back() == '\n' || line.back() == '\r')) line.pop_back();
if (line.empty()) {
if (!cur.path.empty()) out.push_back(cur);
cur = WT();
continue;
}
if (line.rfind("worktree ", 0) == 0) cur.path = line.substr(9);
else if (line.rfind("HEAD ", 0) == 0) cur.head = line.substr(5);
else if (line.rfind("branch ", 0) == 0) cur.branch = line.substr(7);
}
if (!cur.path.empty()) out.push_back(cur);
#ifdef _WIN32
_pclose(fp);
#else
pclose(fp);
#endif
return out;
}
} // namespace
void draw_worktrees(AppState& /*s*/, bool* p_open) {
if (!ImGui::Begin(TI_GIT_BRANCH " Worktrees", p_open)) {
ImGui::End();
return;
}
static std::vector<WT> wts;
static bool first = true;
if (first) { wts = scan_worktrees(); first = false; }
if (ImGui::Button(TI_REFRESH " Rescan")) wts = scan_worktrees();
ImGui::SameLine();
ImGui::TextDisabled("%zu worktrees", wts.size());
ImGui::Separator();
if (ImGui::BeginTable("##wts", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Branch");
ImGui::TableSetupColumn("HEAD");
ImGui::TableSetupColumn("Path");
ImGui::TableHeadersRow();
for (const auto& w : wts) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(w.branch.empty() ? "(detached)" : w.branch.c_str());
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted(w.head.substr(0, 10).c_str());
ImGui::TableSetColumnIndex(2);
ImGui::TextUnformatted(w.path.c_str());
}
ImGui::EndTable();
}
ImGui::End();
}
} // namespace kanban_cpp