fix(reader): expose the reading scroller and preserve native focus

Name and focus the original document scroll region for real browser UIA
scrolling. Defer duplicate-reader activation until menus finish closing.
Use visible menu peers and preserve Settings focus during UIA invocation.

Retain exact font-growth, PDF width/edge, source-color and palette assertions.
Give the font fixture a clear rounding margin and center its narrow print
body; sample the real empty reading gutter. Record actual failure values.

Validation: zero-warning Release build;586 core tests and100% measured line
coverage; native84/97 on the prior head;75 real pipe stress tests pass.
Current hosted/published/installed and production signatures remain pending.

Co-Authored-By: GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <jmaes@dmdbrands.com>
This commit is contained in:
James MaesandGPT-6 committed 2026-10-11 14:39:15 -05:00
1 parent 05bf612779
commit 8abbd4d43c
11 files changed
+73 -35

No files matched your search

+2
View File
@@ -56,6 +56,8 @@ jobs:
artifacts/reader-regressions/*.trx
artifacts/installed-smoke-ui/*.png
artifacts/installed-smoke-ui/*.log
artifacts/installed-smoke-print/*.pdf
artifacts/installed-smoke-print/*.png
retention-days: 7
installed-updates:
+33 -23
View File
@@ -4,31 +4,41 @@ Updated: 2026-10-11
## Current release verification - 2026-10-11
Signed/DCO0ed608e is pushed to PR#3. Exact CI38166230860 is terminal:
source-hover regression passes (actual native pointer/focus/selection colors,
TRX in artifacts/source-hover-38166230860); full native74/97,23failures,
zero skips. Build/core/format pass. Published acceptance again exceeded its
600second process deadline before installed channel outcomes; no signed release
or current installed acceptance is established. Retained exact job logs:
artifacts/current-native-0ed608e.log and artifacts/current-installed-0ed608e.log.
Signed/DCO05bf612 is pushed to PR#3. Exact CI38167591076 full native run
completed84/97,13failures,zero skips; previous0ed608e was74/97. Current
focused12-case TRX is retained in artifacts/reader-regressions-38167591076:
Settings panel defaults, toolbar alignment, picker cancellation, custom theme
paint, source theme/mode and exact copying pass. Source-hover passes the full
run but has a focused initial-color timeout before pointer input; retain the
variant instead of calling all highlighting accepted. Build/core/format pass.
Published/installed job114553267448 is terminal: published dotnet tests again
exceeded600seconds before any installed outcomes; retain
artifacts/current-installed-05bf612.log. No deadline was increased.
Exact full-native log: artifacts/current-native-05bf612.log.
Next reviewed patch corrects the observed Open picker filename selector1148
(control1001 is its address toolbar), checks the actual outline-depth peer,
and aligns Contents/Print48px hit targets with the other toolbar commands.
Full-page printing now resets custom outer body width as well as inner width.
Document paint checks sample the owned browser instead of a split divider;
custom CSS fixture targets the visible body layer of the original stylesheet.
Unchanged native pane-width/scroll/glyph assertions now retain precise failed
geometry/provider evidence; no cases are skipped or relaxed. The focused hosted
job covers the actual failing paths, with full acceptance still required.
Next patch gives the original #content div a named, focusable main landmark so
Chromium exposes the actual inner scroll surface. The prior root reports Scroll
support but no vertical extent, while the unnamed inner container is omitted.
Native menu lookup now selects visible owned peers and dismisses leaf View
menus; UIA Invoke no longer forces reader foreground over Settings/menu owners.
Recent activation defers until the invoking menu finishes restoring focus.
Local Release build is zero-warning;586 core tests and100% measured line
coverage pass in artifacts/reader-fixes-core-verified. Formatting remains a
required final gate. Owner preview PID55472 remains responsive and untouched.
Azure access is unavailable; exact repository-specific immutable OIDC trust,
resource verification and profile role commands are committed in
[azure-signing-admin.md](azure-signing-admin.md). No Azure mutations, human
review acknowledgement, merges or release publication have been fabricated.
Existing exact font/pixel/print assertions remain: typography fixture requests
28px body because24px produces20→28px glyph heights, exactly40%, rejected by its
strict greater-than40% comparison. The narrow print fixture explicitly centers
its custom body before testing the Full-page margin/width reset. Palette checks
sample the owned empty8px reading gutter rather than a glyph/find mark; no
color expectation is changed. Source color/picker failures retain actual values.
No test is skipped or weakened; actual hosted behavior must prove these changes.
Local zero-warning Release build,586 core tests/100% measured lines pass in
artifacts/reader-accessibility-core. Real pipe stress25runs/75tests passes,
zero skips; the older intermittent disconnect failure did not reproduce, and
no speculative pipe change was made. Formatting passes.
Owner preview55472 stays responsive and preserved. Azure access unavailable;
[azure-signing-admin.md](azure-signing-admin.md) provides exact immutable OIDC,
resource/profile verification and profile-role commands. No signing/release,
review acknowledgement, Azure mutation or merge is claimed.
## Current owner review launch - 2026-10-11
@@ -20,6 +20,6 @@ public static class DocumentPage
ArgumentNullException.ThrowIfNull(title);
ArgumentNullException.ThrowIfNull(theme);
ArgumentNullException.ThrowIfNull(appearance);
return $"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{WebUtility.HtmlEncode(title)}</title><style media="screen">{theme.GetReadingStyles(appearance, zoomScale)}</style></head><body><div id="content"><div id="content-inner">{sanitizedHtml}</div></div></body></html>""";
return $"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{WebUtility.HtmlEncode(title)}</title><style media="screen">{theme.GetReadingStyles(appearance, zoomScale)}</style></head><body><div id="content" role="main" aria-label="Document reading area" tabindex="0"><div id="content-inner">{sanitizedHtml}</div></div></body></html>""";
}
}
+5 -1
View File
@@ -66,7 +66,11 @@ public partial class App
MainWindow? existing = _windows.FirstOrDefault(window => string.Equals(window.DocumentPath, canonical, StringComparison.OrdinalIgnoreCase));
if(existing is not null)
{
existing.Activate();
// The invoking menu must finish restoring focus before the existing reader activates.
if(!existing.DispatcherQueue.TryEnqueue(() => existing.Activate()))
{
existing.Activate();
}
}
else if(requester.DocumentPath is null)
{
@@ -213,9 +213,16 @@ public sealed class AppSession : IDisposable
Invoke(menu);
}
Wait.UntilInputIsProcessed();
item = Retry.WhileNull(() => FindInApp(cf => cf.ByAutomationId(itemAutomationId)), timeout: TimeSpan.FromSeconds(3), interval: s_pollInterval, ignoreException: true).Result;
item = Retry.WhileNull(
() => TopLevelWindows().Prepend(MainWindow).SelectMany(root => root.FindAllDescendants(cf => cf.ByAutomationId(itemAutomationId))).FirstOrDefault(candidate => !candidate.IsOffscreen),
timeout: TimeSpan.FromSeconds(3), interval: s_pollInterval, ignoreException: true).Result;
}
Invoke(item ?? throw new TimeoutException($"Menu item '{itemAutomationId}' never appeared under '{menuName}'. (app exited: {App.HasExited})"));
if(string.Equals(menuName, "View", StringComparison.Ordinal) && !item.Patterns.ExpandCollapse.IsSupported)
{
menu.Patterns.ExpandCollapse.Pattern.Collapse();
Wait.UntilInputIsProcessed();
}
}
@@ -323,7 +330,6 @@ public sealed class AppSession : IDisposable
/// <summary>Invokes an element through UI Automation, falling back to a mouse click.</summary>
private void Invoke(AutomationElement element)
{
MainWindow.SetForeground();
if(element.Patterns.Invoke.TryGetPattern(out IInvokePattern? invoke))
{
LastInvocation = $"UIA Invoke: {element.Properties.AutomationId.ValueOrDefault}.";
@@ -120,7 +120,7 @@ public partial class AppTests
/// <summary>Samples only the explicitly foreground owned WebView surface, excluding native source panes and other readers.</summary>
/// <summary>Samples the foreground owned reading gutter, excluding native panes, glyphs and active find highlights.</summary>
private static void WaitForAppearanceWindowColor(AppSession session, Window window, Color expected)
{
window.SetForeground();
@@ -137,7 +137,7 @@ public partial class AppTests
return null;
}
using Bitmap image = view.Capture();
return image.GetPixel(image.Width / 2, image.Height / 2).ToArgb() == expected.ToArgb() ? view : null;
return image.GetPixel(Math.Min(8, image.Width - 1), Math.Min(8, image.Height - 1)).ToArgb() == expected.ToArgb() ? view : null;
}, "actual owned reader background " + expected);
}
}
@@ -198,7 +198,14 @@ public partial class AppTests
SendReadingKey(VirtualKeyShort.CONTROL, VirtualKeyShort.KEY_A);
Keyboard.Type(path);
Wait.UntilInputIsProcessed();
session.WaitFor(() => string.Equals(filename.AsTextBox().Text, path, StringComparison.Ordinal) ? filename : null, "the fully committed exact owned picker filename");
try
{
session.WaitFor(() => string.Equals(filename.AsTextBox().Text, path, StringComparison.Ordinal) ? filename : null, "the fully committed exact owned picker filename");
}
catch(TimeoutException exception)
{
throw new TimeoutException(exception.Message + "; Expected=" + path + "; Actual=" + filename.AsTextBox().Text + "; Last invocation=" + session.LastInvocation, exception);
}
}
@@ -68,7 +68,7 @@ public partial class AppTests
session.WaitFor(() => session.FindInApp(cf => cf.ByAutomationId("mv-heading-0")), "the print width heading", TimeSpan.FromSeconds(30));
OpenSettingsAction(session, "EditDocumentThemeMenu");
session.EnterText("ThemeEditorName", "Print geometry");
session.EnterText("ThemeEditorCss", "body { max-width: 240px; } pre { border: none; border-radius: 0; }");
session.EnterText("ThemeEditorCss", "body { max-width: 240px; margin: 0 auto; } pre { border: none; border-radius: 0; }");
session.ClickButton("Save theme");
session.WaitForElementGone("ThemeEditorName");
CloseSettings(session);
@@ -110,7 +110,15 @@ public partial class AppTests
int green = int.Parse(expected.AsSpan(3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
int blue = int.Parse(expected.AsSpan(5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
int color = red | green << 8 | blue << 16;
session.WaitFor(() => NativeSourceAttribute(SourceRange(source, literal), session.Automation.TextAttributeLibrary.ForegroundColor) == color ? source : null, "the actual native source color of " + literal);
try
{
session.WaitFor(() => NativeSourceAttribute(SourceRange(source, literal), session.Automation.TextAttributeLibrary.ForegroundColor) == color ? source : null, "the actual native source color of " + literal);
}
catch(TimeoutException exception)
{
string actual = NativeSourceColorHex(SourceRange(source, literal), session);
throw new TimeoutException(exception.Message + "; Expected=" + expected + "; Actual=" + actual + "; Last native invocation=" + session.LastInvocation + "; Native tree: " + session.DescribeDocumentStatus(), exception);
}
}
@@ -29,9 +29,10 @@ public partial class AppTests
{
session.EnterText(id, "Consolas");
}
// Give native rounded glyph metrics a clear growth margin while retaining the40% assertion.
var sizes = new Dictionary<string, (string Id, double Value)>(StringComparer.Ordinal)
{
["baseFontSize"] = ("ThemeEditorBodySize", 24),
["baseFontSize"] = ("ThemeEditorBodySize", 28),
["h1Size"] = ("ThemeEditorHeading1Size", 56),
["h2Size"] = ("ThemeEditorHeading2Size", 48),
["h3Size"] = ("ThemeEditorHeading3Size", 44),
@@ -1344,7 +1344,7 @@ public partial class AppTests
/// <summary>Waits for the actual painted document palette rather than just a saved preference or old heading.</summary>
/// <summary>Samples the actual owned browser reading gutter, excluding glyphs, find marks and native dividers.</summary>
private static void WaitForDocumentColor(AppSession session, System.Drawing.Color expected)
{
session.WaitFor(
@@ -1352,7 +1352,7 @@ public partial class AppTests
{
AutomationElement view = session.WaitForElement("DocumentView");
using System.Drawing.Bitmap image = view.Capture();
return image.GetPixel(image.Width / 2, image.Height / 2).ToArgb() == expected.ToArgb() ? view : null;
return image.GetPixel(Math.Min(8, image.Width - 1), Math.Min(8, image.Height - 1)).ToArgb() == expected.ToArgb() ? view : null;
},
"the rendered document background " + expected);
}