From 3aeb847657ca89809df1ec838ccbd06b2c672873 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 12 Jul 2026 15:03:17 -0500 Subject: [PATCH] fix(market): make portfolio-editor dismiss gestures consistently auto-save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Close button auto-committed the current group, but Escape, outside-click, switching groups, adding a group, and deleting another group all *discarded* uncommitted edits — so whether your work was kept depended on how you happened to leave, and the prominent unsaved-dot / Save / Revert affordances implied a save model the exits didn't honor. Align every dismiss/switch gesture with the Close button's intent (auto-save): - Escape and outside-click now commit before closing (outside-click is caught right after BeginOverlayDialog, which clears open mid-frame). - Switching to another group, "Add entry", and deleting a *different* group commit the current group first. Deleting the selected group still discards its own working state. Save remains "persist now", Revert "undo to last saved", and the dot marks an in-progress group. Ordering is safe against the placeholder GC: that GC runs earlier in the frame (mktDrawPortfolio, before RenderPortfolioEditor) and only while the editor is closed, so it never shifts entry indices under a commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/market_tab.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index 73c3519..8d70981 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -734,9 +734,9 @@ static void RenderPortfolioEditor(App* app) // ---------------- Full-window blur overlay (shared framework) ---------------- // Backdrop + capture-once, floating card, plain heading, outside-click dismiss and effect - // suppression are owned by material::BeginOverlayDialog (BlurFloat). Esc still closes here; - // Esc/outside-click discard (only Close commits); LatchBlurOverlayActive (App::render) handles - // the acrylic re-capture on close. + // suppression are owned by material::BeginOverlayDialog (BlurFloat). Every dismiss gesture + // (Close button, Esc, outside-click, switching/adding/deleting groups) auto-saves the current + // group via pfCommitIfNeeded; LatchBlurOverlayActive (App::render) does the acrylic re-capture. material::OverlayDialogSpec ov; ov.title = TR("portfolio_manage_title"); ov.p_open = &s_pfEdit.open; @@ -745,11 +745,18 @@ static void RenderPortfolioEditor(App* app) ov.cardHeight = 760.0f; ov.idSuffix = "pf"; if (!material::BeginOverlayDialog(ov)) return; + // Outside-click dismiss is handled inside BeginOverlayDialog, which clears s_pfEdit.open mid-frame + // (the card still draws one final frame). Auto-save now, before that frame: the placeholder GC in + // mktDrawPortfolio already ran this frame while open was still true, so entry indices haven't + // shifted under us and pfCommitIfNeeded targets the right slot. + if (!s_pfEdit.open) pfCommitIfNeeded(app); // A first Escape should dismiss only an open popup (e.g. the custom-color picker); don't also tear - // down the whole editor — which discards uncommitted edits — while a popup is capturing the key. + // down the whole editor while a popup is capturing the key. Otherwise Escape auto-saves and closes. if (ImGui::IsKeyPressed(ImGuiKey_Escape) && - !ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel)) + !ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel)) { + pfCommitIfNeeded(app); s_pfEdit.open = false; + } { // Clamp/validate the selection. The in-editor delete already reloads the working state, but if the // list shrank via any other path, reload after clamping so a later commit can't overlay stale @@ -845,7 +852,9 @@ static void RenderPortfolioEditor(App* app) ImGui::PopID(); } if (delRow >= 0) { - // Operates on the stored entries; any uncommitted working edits are discarded. + // Auto-save edits to a *different* selected group before mutating the list; only the + // deleted row's own working state is dropped. (Re-read entries after the commit.) + if (delRow != s_pfEdit.sel) pfCommitIfNeeded(app); auto es = settings->getPortfolioEntries(); if (delRow < (int)es.size()) { es.erase(es.begin() + delRow); @@ -856,14 +865,16 @@ static void RenderPortfolioEditor(App* app) PortfolioBeginEdit(app, s_pfEdit.sel); } } else if (clickedSel != -999) { - PortfolioBeginEdit(app, clickedSel); // switching groups discards uncommitted edits + pfCommitIfNeeded(app); // auto-save the current group before switching + PortfolioBeginEdit(app, clickedSel); } } ImGui::EndChild(); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); // Add immediately creates a persisted "Untitled" group and selects it for editing. if (material::TactileButton(TR("portfolio_add_entry"), ImVec2(masterW, addH))) { - // Adding switches to a new group; uncommitted edits to the current one are discarded. + // Auto-save the current group, then start a fresh one selected for editing. + pfCommitIfNeeded(app); auto es = settings->getPortfolioEntries(); config::Settings::PortfolioEntry ne; ne.label = TR("portfolio_untitled");