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:
@@ -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
|
||||
Reference in New Issue
Block a user