From d37ecacbd68c7e0c477e0ca77f4b16865ac653f6 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:05:55 -0400 Subject: [PATCH 1/2] Make release workflow reruns idempotent when release already exists (#246) If signing (or any step after "Create release") fails, the release + tag are created but empty. Previously every downstream step was guarded by EXISTS == 'false', so reruns short-circuited to a no-op and the only fix was to delete the empty release and tag before rerunning. Now only "Create release" itself is guarded; Setup .NET, Build and test, Publish, Sign, Velopack, and Package/upload always run. The final gh release upload uses --clobber, so rerunning against an existing release safely overwrites its assets. Happened during v1.7.0: first attempt timed out on SignPath approval, the rerun skipped all work because v1.7.0 already existed, and manual release+tag deletion was needed to unstick it. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99dcb14..2e269f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,20 +46,17 @@ jobs: gh release create "v${{ steps.version.outputs.VERSION }}" --title "v${{ steps.version.outputs.VERSION }}" --generate-notes --target main - name: Setup .NET 8.0 - if: steps.check.outputs.EXISTS == 'false' uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - name: Build and test - if: steps.check.outputs.EXISTS == 'false' run: | dotnet restore dotnet build -c Release dotnet test tests/PlanViewer.Core.Tests/PlanViewer.Core.Tests.csproj -c Release --no-build --verbosity normal - name: Publish App (all platforms) - if: steps.check.outputs.EXISTS == 'false' run: | dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r win-x64 --self-contained -o publish/win-x64 dotnet publish src/PlanViewer.App/PlanViewer.App.csproj -c Release -r linux-x64 --self-contained -o publish/linux-x64 @@ -68,7 +65,6 @@ jobs: # ── SignPath code signing (Windows only, skipped if secret not configured) ── - name: Check if signing is configured - if: steps.check.outputs.EXISTS == 'false' id: signing shell: bash run: | @@ -80,7 +76,7 @@ jobs: fi - name: Upload Windows build for signing - if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true' + if: steps.signing.outputs.ENABLED == 'true' id: upload-unsigned uses: actions/upload-artifact@v4 with: @@ -88,7 +84,7 @@ jobs: path: publish/win-x64/ - name: Sign Windows build - if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true' + if: steps.signing.outputs.ENABLED == 'true' uses: signpath/github-action-submit-signing-request@v1 with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' @@ -101,7 +97,7 @@ jobs: output-artifact-directory: 'signed/win-x64' - name: Replace unsigned Windows build with signed - if: steps.check.outputs.EXISTS == 'false' && steps.signing.outputs.ENABLED == 'true' + if: steps.signing.outputs.ENABLED == 'true' shell: pwsh run: | Remove-Item -Recurse -Force publish/win-x64 @@ -109,7 +105,6 @@ jobs: # ── Velopack (uses signed Windows binaries) ─────────────────────── - name: Create Velopack release (Windows) - if: steps.check.outputs.EXISTS == 'false' shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -126,7 +121,6 @@ jobs: # ── Package and upload ──────────────────────────────────────────── - name: Package and upload - if: steps.check.outputs.EXISTS == 'false' shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3d717e71d0a3d4fbdbe3922de965db608f62a39a Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:07:38 -0400 Subject: [PATCH 2/2] Render benefit % and actionable fix in all warning surfaces (#247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web viewer warnings strip was dropping both MaxBenefitPercent and ActionableFix — data made it through ResultMapper into the JSON but the strip only rendered Severity/Type/Message. Joe caught this on v1.7.0 (wait stat benefit missing from the rendered warning). While in the area, closed the same ActionableFix gap in the other surfaces that already render benefit: - Web viewer (Index.razor + app.css): add .warn-benefit badge + .warning-fix italic block. Sort strip by benefit desc, then severity, then type to match HTML export / Avalonia ordering. - HtmlExporter: append .warn-fix block after .warn-msg, style added. - PlanViewerControl (Avalonia): italic TextBlock under the message when ActionableFix is present. - TextFormatter: indented "Fix:" line after each warning. Version bump 1.7.0 -> 1.7.1. Co-authored-by: Claude Opus 4.7 (1M context) --- .../Controls/PlanViewerControl.axaml.cs | 12 +++++++++++ src/PlanViewer.App/PlanViewer.App.csproj | 2 +- src/PlanViewer.Core/Output/HtmlExporter.cs | 3 +++ src/PlanViewer.Core/Output/TextFormatter.cs | 2 ++ src/PlanViewer.Web/Pages/Index.razor | 13 +++++++++++- src/PlanViewer.Web/wwwroot/css/app.css | 20 +++++++++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs index 9f092f0..854b987 100644 --- a/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs +++ b/src/PlanViewer.App/Controls/PlanViewerControl.axaml.cs @@ -1754,6 +1754,18 @@ private void ShowPropertiesPanel(PlanNode node) TextWrapping = TextWrapping.Wrap, Margin = new Thickness(16, 0, 0, 0) }); + if (!string.IsNullOrEmpty(w.ActionableFix)) + { + warnPanel.Children.Add(new TextBlock + { + Text = w.ActionableFix, + FontSize = 11, + FontStyle = FontStyle.Italic, + Foreground = TooltipFgBrush, + TextWrapping = TextWrapping.Wrap, + Margin = new Thickness(16, 2, 0, 0) + }); + } planWarningsPanel.Children.Add(warnPanel); } diff --git a/src/PlanViewer.App/PlanViewer.App.csproj b/src/PlanViewer.App/PlanViewer.App.csproj index c04c93b..c00ff0c 100644 --- a/src/PlanViewer.App/PlanViewer.App.csproj +++ b/src/PlanViewer.App/PlanViewer.App.csproj @@ -6,7 +6,7 @@ app.manifest EDD.ico true - 1.7.0 + 1.7.1 Erik Darling Darling Data LLC Performance Studio diff --git a/src/PlanViewer.Core/Output/HtmlExporter.cs b/src/PlanViewer.Core/Output/HtmlExporter.cs index e945151..897598c 100644 --- a/src/PlanViewer.Core/Output/HtmlExporter.cs +++ b/src/PlanViewer.Core/Output/HtmlExporter.cs @@ -187,6 +187,7 @@ .card h3 { .warn-type { font-size: 0.75rem; font-weight: 600; } .warn-benefit { font-size: 0.7rem; font-weight: 600; color: var(--text-muted); padding: 0.05rem 0.3rem; border-radius: 3px; background: rgba(0,0,0,0.04); } .warn-msg { font-size: 0.8rem; color: var(--text); flex-basis: 100%; } +.warn-fix { font-size: 0.75rem; color: var(--text-secondary); font-style: italic; flex-basis: 100%; border-left: 2px solid var(--border); padding-left: 0.5rem; margin-top: 0.15rem; } /* Query text */ details { margin-bottom: 0.75rem; } @@ -459,6 +460,8 @@ private static void WriteWarnings(StringBuilder sb, StatementResult stmt) if (w.MaxBenefitPercent.HasValue) sb.AppendLine($"up to {w.MaxBenefitPercent:N0}% benefit"); sb.AppendLine($"{Encode(w.Message)}"); + if (!string.IsNullOrEmpty(w.ActionableFix)) + sb.AppendLine($"{Encode(w.ActionableFix)}"); sb.AppendLine(""); } sb.AppendLine(""); diff --git a/src/PlanViewer.Core/Output/TextFormatter.cs b/src/PlanViewer.Core/Output/TextFormatter.cs index da655ad..d5ec7d1 100644 --- a/src/PlanViewer.Core/Output/TextFormatter.cs +++ b/src/PlanViewer.Core/Output/TextFormatter.cs @@ -172,6 +172,8 @@ public static void WriteText(AnalysisResult result, TextWriter writer) ? $" (up to {w.MaxBenefitPercent:N0}% benefit)" : ""; writer.WriteLine($" [{w.Severity}] {w.Type}{benefitTag}: {EscapeNewlines(w.Message)}"); + if (!string.IsNullOrEmpty(w.ActionableFix)) + writer.WriteLine($" Fix: {EscapeNewlines(w.ActionableFix)}"); } } diff --git a/src/PlanViewer.Web/Pages/Index.razor b/src/PlanViewer.Web/Pages/Index.razor index 3a54c88..0fc82d5 100644 --- a/src/PlanViewer.Web/Pages/Index.razor +++ b/src/PlanViewer.Web/Pages/Index.razor @@ -332,7 +332,10 @@ else @if (infoCount > 0) { @infoCount }
- @foreach (var w in GetAllWarnings(ActiveStmt!)) + @foreach (var w in GetAllWarnings(ActiveStmt!) + .OrderByDescending(x => x.MaxBenefitPercent ?? -1) + .ThenByDescending(x => x.Severity == "Critical" ? 3 : x.Severity == "Warning" ? 2 : 1) + .ThenBy(x => x.Type)) {
@w.Severity @@ -341,7 +344,15 @@ else @w.Operator } @w.Type + @if (w.MaxBenefitPercent.HasValue) + { + up to @w.MaxBenefitPercent.Value.ToString("N0")% benefit + } @w.Message + @if (!string.IsNullOrEmpty(w.ActionableFix)) + { + @w.ActionableFix + }
}
diff --git a/src/PlanViewer.Web/wwwroot/css/app.css b/src/PlanViewer.Web/wwwroot/css/app.css index f512855..542cb5a 100644 --- a/src/PlanViewer.Web/wwwroot/css/app.css +++ b/src/PlanViewer.Web/wwwroot/css/app.css @@ -807,6 +807,26 @@ textarea::placeholder { font-size: 0.75rem; } +.warn-benefit { + font-size: 0.7rem; + font-weight: 600; + color: var(--text-muted); + padding: 0.05rem 0.35rem; + border-radius: 3px; + background: rgba(0, 0, 0, 0.05); + margin-right: 0.4rem; +} + +.warning-fix { + color: var(--text-secondary); + display: block; + margin-top: 0.25rem; + font-size: 0.75rem; + font-style: italic; + border-left: 2px solid var(--border); + padding-left: 0.5rem; +} + /* === Query Text === */ .stmt-text-section { margin-bottom: 0.75rem;