diff --git a/editor/src/panels/AgentChatPanel.h b/editor/src/panels/AgentChatPanel.h index db5941f..937280d 100644 --- a/editor/src/panels/AgentChatPanel.h +++ b/editor/src/panels/AgentChatPanel.h @@ -6,6 +6,7 @@ #include "../AgentTaskStatusOverlay.h" #include "../EditorState.h" #include "../EditorUtils.h" + #include 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 buildTaskOverlayRows(AgentTaskSlotsState& taskSlots) { (void)AgentTaskSlots::assignPendingToSlots(&taskSlots); const int nowSeconds = static_cast(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& 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& 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(); } diff --git a/editor/src/panels/StatusBarPanel.h b/editor/src/panels/StatusBarPanel.h index d984648..a669ee4 100644 --- a/editor/src/panels/StatusBarPanel.h +++ b/editor/src/panels/StatusBarPanel.h @@ -1,15 +1,19 @@ #pragma once + #include "../EditorState.h" #include "../EditorUtils.h" #include "../AgentTaskStatusOverlay.h" #include "../ThemeEngine.h" -#include -#include + +#include #include +#include +#include #include + #ifdef _WIN32 -#include #include +#include #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(); diff --git a/progress.md b/progress.md index 93cb6ac..c956241 100644 --- a/progress.md +++ b/progress.md @@ -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`