feat(ui): keep the overlap corner quiet during rechecks

A pending or running recheck no longer swaps the green check for status
text. A clear result stays as a gray check, and earlier findings stay as
dimmed text, neither of which claims anything about the edited layout.
Views without automatic rechecks still ask for a manual check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-11-01 23:42:25 -05:00
1 parent c0cd7c6aff
commit fd37cbffef
5 files changed
+111 -8

No files matched your search

@@ -31,6 +31,13 @@ public sealed class OverlapReportState
public bool IsAllClear => Status == OverlapCheckStatus.Current && Report.Pairs.Count == 0
&& Report.ClampFindings.Count == 0 && Report.CutOffClampFindings.Count == 0
&& Report.SpacingPairs.Count == 0;
/// <summary>
/// The last published result's message, kept through layout edits and rechecks so the canvas
/// stays quiet until the next result; never a claim about the current layout. Null when none.
/// </summary>
public string SettledMessage { get; private set; }
/// <summary>Whether that last published result was all clear.</summary>
public bool SettledClear { get; private set; }
public string Message => Status switch
{
@@ -91,6 +98,8 @@ public sealed class OverlapReportState
observedDisplayStamp = stamp;
uncheckedPartCount = CountUncheckedParts(report.Issues.Concat(report.SpacingIssues));
Status = report.IsComplete ? OverlapCheckStatus.Current : OverlapCheckStatus.Incomplete;
SettledMessage = Message;
SettledClear = IsAllClear;
return true;
}
@@ -175,7 +184,11 @@ public sealed class OverlapReportState
private void Clear(OverlapCheckStatus status, bool preserveDisplay = false)
{
if (!preserveDisplay)
{
ClearDisplayPairs();
SettledMessage = null;
SettledClear = false;
}
Generation++;
Report = null;
uncheckedPartCount = 0;
@@ -200,6 +200,29 @@ public class OverlapSpacingLabelTests
Assert.False(state.IsAllClear);
}
[Fact]
public void SettledResult_SurvivesEditsAndRechecksButNotFailureOrCancel()
{
var plate = PlateWith(Spacing, Square(0), Square(4.1));
var state = new OverlapReportState();
Assert.Null(state.SettledMessage);
Assert.True(state.TryPublish(state.Begin(plate), plate, Analyze(plate)));
const string message = "No material overlaps; part spacing: 1 pairs too close";
plate.Parts[1].Offset(1, 0);
state.EnsureFresh(plate);
Assert.Equal((message, false), (state.SettledMessage, state.SettledClear));
var request = state.Begin(plate);
Assert.Equal(message, state.SettledMessage);
Assert.True(state.TryPublish(request, plate, Analyze(plate)));
Assert.True(state.SettledClear);
Assert.True(state.TryFail(state.Begin(plate), plate));
Assert.Null(state.SettledMessage);
Assert.False(state.SettledClear);
}
[Fact]
public void Presentation_ShowsPositionsNamesAndGap()
{
@@ -358,6 +358,62 @@ public class PlateOverlapOverlayTests
Assert.False(CheckIsDrawn(run));
});
[Fact]
public void ClearPlateKeepsAGrayCheckWhileRechecking() => RunSta(() =>
{
using var run = new OverlayRun();
run.View.Plate.Parts.RemoveAt(1);
run.EnableAutoCheck();
var overlay = run.View.OverlapOverlay;
var task = run.AutoTick();
run.Next().Complete();
run.Pump(task);
Assert.True(overlay.ShowsAllClear);
run.View.Plate.Parts[0].Offset(1, 0);
run.Paint();
Assert.True(overlay.IsAutoCheckScheduled);
Assert.True(overlay.ShowsPendingCheck);
Assert.False(overlay.ShowsAllClear);
Assert.False(CheckIsDrawn(run)); // Gray, not green: this layout is unconfirmed.
task = run.AutoTick();
var work = run.Next();
Assert.True(overlay.ShowsPendingCheck);
work.Complete();
run.Pump(task);
Assert.False(overlay.ShowsPendingCheck);
Assert.True(overlay.ShowsAllClear);
});
[Fact]
public void FindingsStayInTheLabelWhileRechecking() => RunSta(() =>
{
using var run = new OverlayRun();
run.EnableAutoCheck();
var task = run.AutoTick();
run.Next().Complete();
run.Pump(task);
run.View.Plate.Parts[1].Offset(0.5, 0);
run.Paint();
Assert.False(run.View.OverlapOverlay.ShowsPendingCheck);
Assert.False(run.View.OverlapOverlay.ShowsAllClear);
});
[Fact]
public void StaleWithoutAutoCheckStillAsksForACheck() => RunSta(() =>
{
using var run = new OverlayRun();
run.View.Plate.Parts.RemoveAt(1);
run.Finish();
run.View.Plate.Parts[0].Offset(1, 0);
run.Paint();
Assert.Equal(OverlapCheckStatus.Stale, run.View.OverlapStatus);
Assert.False(run.View.OverlapOverlay.ShowsPendingCheck);
Assert.False(run.View.OverlapOverlay.ShowsAllClear);
});
private static bool CheckIsDrawn(OverlayRun run)
{
using var image = new Bitmap(64, 64);
+15 -7
View File
@@ -81,7 +81,12 @@ internal sealed class OverlapOverlayController : IDisposable
: state.Message;
/// <summary>The canvas shows a green check instead of the label: a pending recheck is not clear.</summary>
internal bool ShowsAllClear => state.IsAllClear && !autoCheck.IsWaiting;
internal bool ShowsAllClear => state.IsAllClear && !IsSettling;
/// <summary>A gray check while a recheck settles: unconfirmed, and steady in the corner.</summary>
internal bool ShowsPendingCheck => IsSettling && (state.SettledMessage == null || state.SettledClear);
private bool IsSettling => autoCheck.IsWaiting || state.IsRunning;
/// <summary>
/// Recheck automatically after layout edits settle. Units are read when each request
@@ -683,13 +688,13 @@ internal sealed class OverlapOverlayController : IDisposable
}
// Same corner as the label; Message keeps the words for the status bar.
private void DrawAllClear(Graphics graphics)
private void DrawCheck(Graphics graphics, Color color)
{
var scale = view.DeviceDpi / 96f;
var box = 24 * scale;
using var background = new SolidBrush(Color.FromArgb(235, Color.White));
graphics.FillRectangle(background, 6, 6, box, box);
using var pen = new Pen(Color.FromArgb(0, 140, 60), 3 * scale)
using var pen = new Pen(color, 3 * scale)
{
StartCap = LineCap.Round,
EndCap = LineCap.Round,
@@ -712,16 +717,19 @@ internal sealed class OverlapOverlayController : IDisposable
try
{
graphics.ResetTransform();
if (ShowsAllClear)
if (ShowsAllClear || ShowsPendingCheck)
{
DrawAllClear(graphics);
DrawCheck(graphics, ShowsAllClear ? Color.FromArgb(0, 140, 60) : Color.FromArgb(150, 150, 150));
return;
}
var text = Message + (state.DisplayMode == OverlapDisplayMode.Off ? " (display off)" : "");
// While settling, the last findings stay put, dimmed, instead of status text flickering.
var settling = IsSettling;
var text = (settling ? state.SettledMessage : Message)
+ (state.DisplayMode == OverlapDisplayMode.Off ? " (display off)" : "");
var size = graphics.MeasureString(text, view.Font);
using var background = new SolidBrush(Color.FromArgb(235, Color.White));
graphics.FillRectangle(background, 6, 6, size.Width + 8, size.Height + 6);
graphics.DrawString(text, view.Font, Brushes.DarkRed, 10, 9);
graphics.DrawString(text, view.Font, settling ? Brushes.Gray : Brushes.DarkRed, 10, 9);
}
finally
{
+4 -1
View File
@@ -30,7 +30,10 @@ zero. Selection and dragging remain ordinary plate operations, not overlay
interactions.
A current, fully checked report with no finding of any kind draws a green check in
the corner instead of words; the status bar still uses the wording below. Every
the corner instead of words; the status bar still uses the wording below. While a
recheck is pending or running the corner stays quiet: a gray check after a clear
result or when there is no result yet, or the last findings dimmed in gray, neither
of which says anything about the edited layout. Every
other state keeps a persistent label, which distinguishes unchecked, checking,
current with findings, incomplete, stale,
canceled, and failed checks. Only a completed, current, fully checked report can