Sprint 37: architecture refactor pass

This commit is contained in:
Bill
2026-02-17 21:32:46 -07:00
parent b5f03bd7ae
commit 98eae97f24
3 changed files with 327 additions and 230 deletions

View File

@@ -6,6 +6,7 @@
#include "../AgentTaskStatusOverlay.h"
#include "../EditorState.h"
#include "../EditorUtils.h"
#include <ctime>
static int countChatContextNodes(const ASTNode* node) {
@@ -15,71 +16,69 @@ static int countChatContextNodes(const ASTNode* node) {
return total;
}
static void renderAgentChatPanel(EditorState& state) {
if (!state.ui.showAgentChatPanel) return;
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
ImGui::End();
return;
}
static void syncChatSessionForProject(EditorState& state,
AgentChatState& chat,
const std::string& projectFile) {
if (projectFile.empty()) return;
if (chat.sessionLoaded && chat.loadedProjectFile == projectFile) return;
AgentChatState& chat = state.agent.chat;
AgentTaskSlotsState& taskSlots = state.agent.taskSlots;
const std::string projectFile = state.activeBuffer ? state.activeBuffer->path : "";
if (!projectFile.empty() &&
(!chat.sessionLoaded || chat.loadedProjectFile != projectFile)) {
AgentChatSavedSession loaded;
std::string error;
if (AgentChatSessionPersistence::loadLatestSession(state.workspaceRoot,
projectFile,
&loaded,
&error)) {
chat = loaded.chat;
chat.loadedProjectFile = projectFile;
chat.sessionLoaded = true;
chat.persistedMessageCount = chat.messages.size();
} else {
chat.loadedProjectFile = projectFile;
chat.sessionLoaded = true;
}
AgentChatSavedSession loaded;
std::string error;
if (AgentChatSessionPersistence::loadLatestSession(state.workspaceRoot,
projectFile,
&loaded,
&error)) {
chat = loaded.chat;
chat.persistedMessageCount = chat.messages.size();
}
chat.loadedProjectFile = projectFile;
chat.sessionLoaded = true;
}
if (chat.systemContext.empty()) {
AgentChatRuntimeSnapshot snapshot;
if (state.activeBuffer) {
snapshot.activeFile = state.activeBuffer->path;
snapshot.language = state.activeBuffer->language;
}
snapshot.workspacePath = state.workspaceRoot.empty() ? "." : state.workspaceRoot;
Module* ast = state.activeAST();
snapshot.astNodeCount = countChatContextNodes(ast);
snapshot.openAnnotationCount = countAnnotationNodes(ast);
AgentChatContextInjector::inject(&chat, snapshot, "context");
static void ensureChatSystemContext(EditorState& state, AgentChatState& chat) {
if (!chat.systemContext.empty()) return;
AgentChatRuntimeSnapshot snapshot;
if (state.activeBuffer) {
snapshot.activeFile = state.activeBuffer->path;
snapshot.language = state.activeBuffer->language;
}
snapshot.workspacePath = state.workspaceRoot.empty() ? "." : state.workspaceRoot;
Module* ast = state.activeAST();
snapshot.astNodeCount = countChatContextNodes(ast);
snapshot.openAnnotationCount = countAnnotationNodes(ast);
AgentChatContextInjector::inject(&chat, snapshot, "context");
}
static std::vector<AgentTaskOverlayRow> buildTaskOverlayRows(AgentTaskSlotsState& taskSlots) {
(void)AgentTaskSlots::assignPendingToSlots(&taskSlots);
const int nowSeconds = static_cast<int>(std::time(nullptr));
const auto overlayRows = AgentTaskStatusOverlay::buildRows(taskSlots, nowSeconds);
return AgentTaskStatusOverlay::buildRows(taskSlots, nowSeconds);
}
if (!overlayRows.empty() && ImGui::BeginTabBar("##AgentTaskSlotsTabs")) {
for (const auto& row : overlayRows) {
std::string tabName = (row.slotIndex >= 0)
? ("Slot " + std::to_string(row.slotIndex + 1) + ": " + row.taskId)
: ("Pending: " + row.taskId);
if (ImGui::BeginTabItem(tabName.c_str())) {
ImGui::Text("Task: %s", row.description.c_str());
ImGui::Text("Status: %s", row.status.c_str());
ImGui::Text("Elapsed: %ds", row.elapsedSeconds);
ImGui::Text("Tool Calls: %d", row.toolCallsMade);
ImGui::Text("Current Step: %s", row.currentStep.c_str());
if (row.canCancel &&
ImGui::Button(("Cancel##tab_" + row.taskId).c_str())) {
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
}
ImGui::EndTabItem();
}
static void renderTaskSlotTabs(AgentTaskSlotsState& taskSlots,
const std::vector<AgentTaskOverlayRow>& rows) {
if (rows.empty() || !ImGui::BeginTabBar("##AgentTaskSlotsTabs")) return;
for (const auto& row : rows) {
std::string tabName = (row.slotIndex >= 0)
? ("Slot " + std::to_string(row.slotIndex + 1) + ": " + row.taskId)
: ("Pending: " + row.taskId);
if (!ImGui::BeginTabItem(tabName.c_str())) continue;
ImGui::Text("Task: %s", row.description.c_str());
ImGui::Text("Status: %s", row.status.c_str());
ImGui::Text("Elapsed: %ds", row.elapsedSeconds);
ImGui::Text("Tool Calls: %d", row.toolCallsMade);
ImGui::Text("Current Step: %s", row.currentStep.c_str());
if (row.canCancel && ImGui::Button(("Cancel##tab_" + row.taskId).c_str())) {
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
}
ImGui::EndTabBar();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
static void renderChatMessageHistory(AgentChatState& chat) {
ImGui::BeginChild("##AgentChatHistory", ImVec2(0, -120), true);
for (const auto& msg : chat.messages) {
ImGui::Text("[%s] %s", msg.timestamp.c_str(), AgentChatPanelModel::roleLabel(msg.role));
@@ -92,113 +91,152 @@ static void renderAgentChatPanel(EditorState& state) {
}
chat.autoScroll = false;
ImGui::EndChild();
}
if (!chat.toolCalls.empty()) {
ImGui::Separator();
ImGui::TextUnformatted("Tool Calls");
for (std::size_t i = 0; i < chat.toolCalls.size(); ++i) {
auto& call = chat.toolCalls[i];
std::string label = AgentToolCallVisualization::inlineLabel(call);
if (ImGui::CollapsingHeader((label + "##tool_" + std::to_string(i)).c_str())) {
call.expanded = true;
ImGui::TextUnformatted("Input");
ImGui::BeginChild(("##tool_in_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.inputJson.c_str());
ImGui::EndChild();
ImGui::TextUnformatted("Output");
ImGui::BeginChild(("##tool_out_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.outputJson.c_str());
ImGui::EndChild();
} else {
call.expanded = false;
}
}
}
if (!chat.mutationPreviews.empty()) {
ImGui::Separator();
ImGui::TextUnformatted("Mutation Previews");
for (std::size_t i = 0; i < chat.mutationPreviews.size(); ++i) {
const auto& preview = chat.mutationPreviews[i];
AgentMutationApproval::ensureRecord(&chat.mutationApprovals, preview.previewId);
const MutationApprovalRecord* approval =
AgentMutationApproval::find(chat.mutationApprovals, preview.previewId);
std::string header = preview.previewId + (preview.hasChanges ? " (changes)" : " (no changes)");
if (ImGui::CollapsingHeader((header + "##preview_" + std::to_string(i)).c_str())) {
ImGui::TextUnformatted("Mutation JSON");
ImGui::BeginChild(("##preview_json_" + std::to_string(i)).c_str(), ImVec2(-1, 60), true);
ImGui::TextUnformatted(preview.mutationJson.c_str());
ImGui::EndChild();
ImGui::Columns(2, ("##preview_cols_" + std::to_string(i)).c_str(), true);
ImGui::TextUnformatted("Before");
ImGui::BeginChild(("##preview_before_" + std::to_string(i)).c_str(), ImVec2(0, 100), true);
ImGui::TextUnformatted(preview.beforeCode.c_str());
ImGui::EndChild();
ImGui::NextColumn();
ImGui::TextUnformatted("After");
ImGui::BeginChild(("##preview_after_" + std::to_string(i)).c_str(), ImVec2(0, 100), true);
ImGui::TextUnformatted(preview.afterCode.c_str());
ImGui::EndChild();
ImGui::Columns(1);
ImGui::Separator();
if (approval) {
ImGui::Text("Decision: %s", AgentMutationApproval::decisionText(approval->decision).c_str());
}
if (ImGui::Button(("Accept##" + preview.previewId).c_str())) {
AgentMutationApproval::accept(&chat.mutationApprovals, preview.previewId);
}
ImGui::SameLine();
if (ImGui::Button(("Reject##" + preview.previewId).c_str())) {
AgentMutationApproval::reject(&chat.mutationApprovals, preview.previewId, "user_rejected");
}
ImGui::SameLine();
if (ImGui::Button(("Modify##" + preview.previewId).c_str())) {
AgentMutationApproval::modify(&chat.mutationApprovals,
preview.previewId,
preview.mutationJson);
}
}
}
}
if (!overlayRows.empty()) {
ImGui::Separator();
ImGui::TextUnformatted("Agent Tasks");
for (const auto& row : overlayRows) {
ImGui::Text("[%s] %s", row.status.c_str(), row.description.c_str());
ImGui::SameLine();
ImGui::TextDisabled("(elapsed: %ds, tools: %d, step: %s)",
row.elapsedSeconds,
row.toolCallsMade,
row.currentStep.c_str());
if (row.canCancel &&
ImGui::Button(("Cancel##row_" + row.taskId).c_str())) {
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
}
static void renderToolCallSection(AgentChatState& chat) {
if (chat.toolCalls.empty()) return;
ImGui::Separator();
ImGui::TextUnformatted("Tool Calls");
for (std::size_t i = 0; i < chat.toolCalls.size(); ++i) {
auto& call = chat.toolCalls[i];
std::string label = AgentToolCallVisualization::inlineLabel(call);
if (!ImGui::CollapsingHeader((label + "##tool_" + std::to_string(i)).c_str())) {
call.expanded = false;
continue;
}
call.expanded = true;
ImGui::TextUnformatted("Input");
ImGui::BeginChild(("##tool_in_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.inputJson.c_str());
ImGui::EndChild();
ImGui::TextUnformatted("Output");
ImGui::BeginChild(("##tool_out_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
ImGui::TextUnformatted(call.outputJson.c_str());
ImGui::EndChild();
}
}
static void renderSingleMutationPreview(AgentChatState& chat,
const AgentMutationPreview& preview,
std::size_t index) {
AgentMutationApproval::ensureRecord(&chat.mutationApprovals, preview.previewId);
const MutationApprovalRecord* approval =
AgentMutationApproval::find(chat.mutationApprovals, preview.previewId);
std::string header = preview.previewId + (preview.hasChanges ? " (changes)" : " (no changes)");
if (!ImGui::CollapsingHeader((header + "##preview_" + std::to_string(index)).c_str())) return;
ImGui::TextUnformatted("Mutation JSON");
ImGui::BeginChild(("##preview_json_" + std::to_string(index)).c_str(), ImVec2(-1, 60), true);
ImGui::TextUnformatted(preview.mutationJson.c_str());
ImGui::EndChild();
ImGui::Columns(2, ("##preview_cols_" + std::to_string(index)).c_str(), true);
ImGui::TextUnformatted("Before");
ImGui::BeginChild(("##preview_before_" + std::to_string(index)).c_str(), ImVec2(0, 100), true);
ImGui::TextUnformatted(preview.beforeCode.c_str());
ImGui::EndChild();
ImGui::NextColumn();
ImGui::TextUnformatted("After");
ImGui::BeginChild(("##preview_after_" + std::to_string(index)).c_str(), ImVec2(0, 100), true);
ImGui::TextUnformatted(preview.afterCode.c_str());
ImGui::EndChild();
ImGui::Columns(1);
ImGui::Separator();
if (approval) {
ImGui::Text("Decision: %s", AgentMutationApproval::decisionText(approval->decision).c_str());
}
if (ImGui::Button(("Accept##" + preview.previewId).c_str())) {
AgentMutationApproval::accept(&chat.mutationApprovals, preview.previewId);
}
ImGui::SameLine();
if (ImGui::Button(("Reject##" + preview.previewId).c_str())) {
AgentMutationApproval::reject(&chat.mutationApprovals, preview.previewId, "user_rejected");
}
ImGui::SameLine();
if (ImGui::Button(("Modify##" + preview.previewId).c_str())) {
AgentMutationApproval::modify(&chat.mutationApprovals, preview.previewId, preview.mutationJson);
}
}
static void renderMutationPreviewSection(AgentChatState& chat) {
if (chat.mutationPreviews.empty()) return;
ImGui::Separator();
ImGui::TextUnformatted("Mutation Previews");
for (std::size_t i = 0; i < chat.mutationPreviews.size(); ++i) {
renderSingleMutationPreview(chat, chat.mutationPreviews[i], i);
}
}
static void renderAgentTaskRows(AgentTaskSlotsState& taskSlots,
const std::vector<AgentTaskOverlayRow>& rows) {
if (rows.empty()) return;
ImGui::Separator();
ImGui::TextUnformatted("Agent Tasks");
for (const auto& row : rows) {
ImGui::Text("[%s] %s", row.status.c_str(), row.description.c_str());
ImGui::SameLine();
ImGui::TextDisabled("(elapsed: %ds, tools: %d, step: %s)",
row.elapsedSeconds,
row.toolCallsMade,
row.currentStep.c_str());
if (row.canCancel && ImGui::Button(("Cancel##row_" + row.taskId).c_str())) {
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
}
}
}
static void renderChatComposer(AgentChatState& chat) {
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;
InputTextMultilineStr("##AgentChatInput", &chat.draftInput, ImVec2(-90, 80), flags);
ImGui::SameLine();
if (ImGui::Button("Send", ImVec2(80, 80))) {
AgentChatPanelModel::sendUserMessage(&chat, "now");
}
}
if (!projectFile.empty() &&
chat.sessionLoaded &&
chat.messages.size() != chat.persistedMessageCount) {
std::string error;
if (AgentChatSessionPersistence::saveSession(state.workspaceRoot,
projectFile,
"autosave",
chat,
&error)) {
chat.persistedMessageCount = chat.messages.size();
}
static void autosaveChatSession(EditorState& state,
const std::string& projectFile,
AgentChatState& chat) {
if (projectFile.empty() || !chat.sessionLoaded) return;
if (chat.messages.size() == chat.persistedMessageCount) return;
std::string error;
if (AgentChatSessionPersistence::saveSession(state.workspaceRoot,
projectFile,
"autosave",
chat,
&error)) {
chat.persistedMessageCount = chat.messages.size();
}
}
static void renderAgentChatPanel(EditorState& state) {
if (!state.ui.showAgentChatPanel) return;
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
ImGui::End();
return;
}
AgentChatState& chat = state.agent.chat;
AgentTaskSlotsState& taskSlots = state.agent.taskSlots;
const std::string projectFile = state.activeBuffer ? state.activeBuffer->path : "";
syncChatSessionForProject(state, chat, projectFile);
ensureChatSystemContext(state, chat);
const auto overlayRows = buildTaskOverlayRows(taskSlots);
renderTaskSlotTabs(taskSlots, overlayRows);
renderChatMessageHistory(chat);
renderToolCallSection(chat);
renderMutationPreviewSection(chat);
renderAgentTaskRows(taskSlots, overlayRows);
renderChatComposer(chat);
autosaveChatSession(state, projectFile, chat);
ImGui::End();
}

View File

@@ -1,15 +1,19 @@
#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../AgentTaskStatusOverlay.h"
#include "../ThemeEngine.h"
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#ifdef _WIN32
#include <windows.h>
#include <psapi.h>
#include <windows.h>
#endif
static std::string detectGitBranch(const std::string& root) {
@@ -17,12 +21,11 @@ static std::string detectGitBranch(const std::string& root) {
std::filesystem::path headPath = std::filesystem::path(root) / ".git" / "HEAD";
std::ifstream in(headPath.string());
if (!in.is_open()) return "-";
std::string line;
std::getline(in, line);
const std::string refPrefix = "ref: refs/heads/";
if (line.rfind(refPrefix, 0) == 0) {
return line.substr(refPrefix.size());
}
if (line.rfind(refPrefix, 0) == 0) return line.substr(refPrefix.size());
if (!line.empty()) return line.substr(0, 8);
return "-";
}
@@ -56,49 +59,39 @@ static size_t processMemoryBytes() {
static std::string formatMemory(size_t bytes) {
if (bytes == 0) return "-";
const double mb = 1024.0 * 1024.0;
double value = bytes / mb;
char buf[64];
std::snprintf(buf, sizeof(buf), "%.0f MB", value);
std::snprintf(buf, sizeof(buf), "%.0f MB", bytes / mb);
return buf;
}
static void renderStatusBar(EditorState& state) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGuiWindowFlags sbFlags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings;
float sbHeight = ImGui::GetFrameHeight() + 2;
ImVec2 sbPos(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - sbHeight);
ImVec2 sbSize(viewport->WorkSize.x, sbHeight);
ImGui::SetNextWindowPos(sbPos);
ImGui::SetNextWindowSize(sbSize);
ImVec4 bg = ImVec4(0.10f, 0.12f, 0.16f, 1.0f);
static ImVec4 statusBarBackgroundColor(const EditorState& state) {
bool hasError = false;
for (const auto& d : state.whetstoneDiagnostics) {
if (d.severity == 1) { hasError = true; break; }
if (d.severity == 1) {
hasError = true;
break;
}
}
if (!hasError) {
for (const auto& d : state.emacsDiagnostics) {
if (d.severity == 1) { hasError = true; break; }
if (d.severity == 1) {
hasError = true;
break;
}
}
}
if (state.build.runInProgress) {
bg = ImVec4(0.10f, 0.35f, 0.25f, 1.0f);
} else if (hasError) {
bg = ImVec4(0.45f, 0.12f, 0.12f, 1.0f);
}
ImGui::PushStyleColor(ImGuiCol_WindowBg, bg);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2));
ImGui::Begin("##StatusBar", nullptr, sbFlags);
ImGui::PushFont(state.uiFont);
// Left cluster: mode, language, encoding, line ending
if (state.build.runInProgress) return ImVec4(0.10f, 0.35f, 0.25f, 1.0f);
if (hasError) return ImVec4(0.45f, 0.12f, 0.12f, 1.0f);
return ImVec4(0.10f, 0.12f, 0.16f, 1.0f);
}
static void renderStatusBarLeftCluster(EditorState& state) {
std::string modeLabel = state.active()
? (state.active()->bufferMode == BufferManager::BufferMode::Text ? "Text Mode" : "Structured Mode")
: "-";
if (ImGui::Button(modeLabel.c_str())) {
toggleBufferMode(state);
}
if (ImGui::Button(modeLabel.c_str())) toggleBufferMode(state);
if (state.active() &&
state.active()->bufferMode == BufferManager::BufferMode::Text &&
state.active()->fileSizeBytes > 0) {
@@ -107,19 +100,19 @@ static void renderStatusBar(EditorState& state) {
state.setActiveBufferMode(BufferManager::BufferMode::Structured);
}
}
ImGui::SameLine(0, 10);
const char* langLabel = state.active() ? state.active()->language.c_str() : "-";
if (ImGui::Button(langLabel)) {
ImGui::OpenPopup("##langPopup");
}
if (ImGui::Button(langLabel)) ImGui::OpenPopup("##langPopup");
if (ImGui::BeginPopup("##langPopup")) {
const char* langs[] = {"python","cpp","elisp","javascript","typescript","java","rust","go","org"};
const char* labels[] = {"Python","C++","Elisp","JavaScript","TypeScript","Java","Rust","Go","Org"};
const char* langs[] = {"python", "cpp", "elisp", "javascript", "typescript", "java", "rust", "go", "org"};
const char* labels[] = {"Python", "C++", "Elisp", "JavaScript", "TypeScript", "Java", "Rust", "Go", "Org"};
for (int i = 0; i < IM_ARRAYSIZE(langs); ++i) {
if (ImGui::MenuItem(labels[i])) state.setLanguage(langs[i]);
}
ImGui::EndPopup();
}
ImGui::SameLine(0, 10);
if (ImGui::Button("UTF-8")) {
state.notify(NotificationLevel::Info, "Encoding settings not yet configurable.");
@@ -129,48 +122,45 @@ static void renderStatusBar(EditorState& state) {
if (ImGui::Button(lineEnding.c_str())) {
state.notify(NotificationLevel::Info, "Line ending toggle not yet implemented.");
}
}
// Center: notifications
static void renderStatusBarCenter(EditorState& state, float windowWidth) {
std::string centerText = "(no notifications)";
if (const Notification* n = state.notifications.latest()) {
centerText = n->message;
}
if (const Notification* n = state.notifications.latest()) centerText = n->message;
float centerWidth = ImGui::CalcTextSize(centerText.c_str()).x;
float windowWidth = ImGui::GetWindowWidth();
float centerX = std::max(0.0f, (windowWidth * 0.5f) - (centerWidth * 0.5f));
ImGui::SetCursorPosX(centerX);
ImGui::TextUnformatted(centerText.c_str());
if (ImGui::IsItemClicked()) {
state.notifications.showHistory = !state.notifications.showHistory;
}
}
// Recording indicator
static void renderRecordingAndAgentIndicator(EditorState& state) {
ImGui::SameLine(0, 10);
bool recording = state.agent.workflowRecorder.isRecording();
const bool recording = state.agent.workflowRecorder.isRecording();
if (recording) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.75f, 0.15f, 0.15f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.85f, 0.20f, 0.20f, 1.0f));
}
if (ImGui::Button("REC")) {
state.toggleSessionRecording();
}
if (ImGui::Button("REC")) state.toggleSessionRecording();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(recording ? "Session recording active" : "Start session recording");
}
if (recording) {
ImGui::PopStyleColor(2);
}
if (recording) ImGui::PopStyleColor(2);
AgentTaskStatusSummary taskSummary =
AgentTaskStatusOverlay::buildSummary(state.agent.taskSlots);
if (taskSummary.hasWork) {
ImGui::SameLine(0, 10);
if (ImGui::Button(taskSummary.label.c_str())) {
state.ui.showAgentChatPanel = true;
}
}
if (!taskSummary.hasWork) return;
// Right cluster: Ln/Col, selection, zoom, git
ImGui::SameLine(0, 10);
if (ImGui::Button(taskSummary.label.c_str())) {
state.ui.showAgentChatPanel = true;
}
}
static std::string buildStatusBarRightText(EditorState& state) {
int selStart = -1;
int selEnd = -1;
int selChars = 0;
@@ -185,31 +175,54 @@ static void renderStatusBar(EditorState& state) {
}
}
}
std::string rightText;
if (state.active()) {
rightText = "Ln " + std::to_string(state.active()->cursorLine) +
", Col " + std::to_string(state.active()->cursorCol);
} else {
rightText = "Ln -, Col -";
}
if (selChars > 0) {
rightText += " | Sel " + std::to_string(selChars) + "c " +
std::to_string(selLines) + "l";
}
if (state.active() && state.active()->largeFileMode) {
rightText += " | Large File";
}
size_t memBytes = processMemoryBytes();
if (memBytes > 0) {
rightText += " | Mem " + formatMemory(memBytes);
}
rightText += " | Zoom " + std::to_string(zoomPercent(state.settings.getFontSize(),
state.baseFontSize)) + "%";
rightText += " | Git " + detectGitBranch(state.workspaceRoot);
std::string rightText = state.active()
? ("Ln " + std::to_string(state.active()->cursorLine) +
", Col " + std::to_string(state.active()->cursorCol))
: "Ln -, Col -";
if (selChars > 0) {
rightText += " | Sel " + std::to_string(selChars) + "c " + std::to_string(selLines) + "l";
}
if (state.active() && state.active()->largeFileMode) rightText += " | Large File";
size_t memBytes = processMemoryBytes();
if (memBytes > 0) rightText += " | Mem " + formatMemory(memBytes);
rightText += " | Zoom " + std::to_string(zoomPercent(state.settings.getFontSize(),
state.baseFontSize)) + "%";
rightText += " | Git " + detectGitBranch(state.workspaceRoot);
return rightText;
}
static void renderStatusBarRight(EditorState& state, float windowWidth) {
std::string rightText = buildStatusBarRightText(state);
float rightWidth = ImGui::CalcTextSize(rightText.c_str()).x;
ImGui::SetCursorPosX(std::max(0.0f, windowWidth - rightWidth - 8.0f));
ImGui::TextUnformatted(rightText.c_str());
}
static void renderStatusBar(EditorState& state) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGuiWindowFlags sbFlags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings;
const float sbHeight = ImGui::GetFrameHeight() + 2;
const ImVec2 sbPos(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - sbHeight);
const ImVec2 sbSize(viewport->WorkSize.x, sbHeight);
ImGui::SetNextWindowPos(sbPos);
ImGui::SetNextWindowSize(sbSize);
ImGui::PushStyleColor(ImGuiCol_WindowBg, statusBarBackgroundColor(state));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2));
ImGui::Begin("##StatusBar", nullptr, sbFlags);
ImGui::PushFont(state.uiFont);
const float windowWidth = ImGui::GetWindowWidth();
renderStatusBarLeftCluster(state);
renderStatusBarCenter(state, windowWidth);
renderRecordingAndAgentIndicator(state);
renderStatusBarRight(state, windowWidth);
ImGui::PopFont();
ImGui::End();

View File

@@ -12779,3 +12779,49 @@ mutation preview acceptance, code update, and chat-session save/restore.
- `editor/src/Sprint37IntegrationSummary.h` (`104` <= `600`)
- `editor/tests/step633_test.cpp` within test-file size guidance (`109` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
## Sprint 37 Refactor Pass (Architecture Compliance)
**Status:** PASS
Completed end-of-sprint modified-files audit for Steps 624-633 and refactored
the two Sprint 37-touched UI functions that exceeded the `ARCHITECTURE.md`
single-function line limit.
**Files modified:**
- `editor/src/panels/AgentChatPanel.h`
- `editor/src/panels/StatusBarPanel.h`
**Refactor result:**
- Split `renderAgentChatPanel(...)` into focused helpers:
- session sync, context injection, task-tab rendering, history rendering,
tool-call rendering, mutation-preview rendering, task-row rendering,
composer, autosave.
- Split `renderStatusBar(...)` into focused helpers:
- background-color selection, left cluster, center notification, recording +
agent indicator, right-text composition/render.
- Preserved panel behavior while bringing Sprint 37 touched code in line with
function-size constraints.
**Verification run (full sprint matrix):**
- `cmake --build editor/build-native --target whetstone_editor` - PASS
- `cmake --build editor/build-native --target step624_test step625_test step626_test step627_test step628_test step629_test step630_test step631_test step632_test step633_test` - PASS
- `./editor/build-native/step624_test` - PASS (12/12)
- `./editor/build-native/step625_test` - PASS (12/12)
- `./editor/build-native/step626_test` - PASS (12/12)
- `./editor/build-native/step627_test` - PASS (12/12)
- `./editor/build-native/step628_test` - PASS (8/8)
- `./editor/build-native/step629_test` - PASS (12/12)
- `./editor/build-native/step630_test` - PASS (12/12)
- `./editor/build-native/step631_test` - PASS (12/12)
- `./editor/build-native/step632_test` - PASS (12/12)
- `./editor/build-native/step633_test` - PASS (8/8)
- Sprint 37 matrix total: **112/112 passing**
**Architecture gate check:**
- `editor/src/panels/AgentChatPanel.h` (`242` <= `600`)
- `editor/src/panels/StatusBarPanel.h` (`231` <= `600`)
- `editor/src/Sprint37IntegrationSummary.h` (`104` <= `600`)
- `editor/src/AgentTaskSlots.h` (`154` <= `600`)
- `editor/src/AgentTaskStatusOverlay.h` (`79` <= `600`)
- `editor/tests/step633_test.cpp` within test-file size guidance (`109` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`