Step 11 of the connection wizard build (ADR-0005): the automation identifiers the interactive UI tests find controls by are recorded, and held by tests, before the window is rewritten as a tree and a wizard.
What changes in the product
14 controls that had no identifier get one (for example the share check box of a printer row, the Log settings dialog's fields, "Exit companion"). The two controls that had only an x:Name (Navigation, SupportLink) get an explicit identifier of the same value, so an automation client sees no rename.
None of the 107 identifiers that existed is renamed, removed or moved. The first reviewer extracted both sets independently of the tests and compared control by control.
No live region was added or removed; nothing an operator sees or hears changes.
What the tests hold (tests/Qqq.PrintAgent.Presentation.Tests/Views/, read as text; no reference to WinUI or the companion project)
Every interactive control has an identifier; the recorded list names each identifier with its class and control, so a moved identifier fails as well as a removed or renamed one.
Every identifier the UI tests and the layout harness look up is set by the companion.
Status texts that change are either declared as a live region or listed with the reason they are not.
Position-based identifiers (Server.<n>, Printer.<n>, Job.<n>) have a table that says where each may be produced; none is produced yet.
Verification
Head 9b542ce on release/2.0 at c3bf23d.
build/Verify.ps1 passed on that head: 1,174 Core, 2,337 Windows runtime and command line, 1,110 presentation tests, none failed; Core 100% of 2,311 lines, presentation 100% of 2,236, command line 100%, Windows 96.06% (4,543 of 4,729; minimum 95).
A clean export with only the presentation test project restored passes with the companion never built.
Review (independent reviewers)
1f480bc: 0 blockers, 8 should-fix, 7 nits. The product part was confirmed clean; every should-fix was in the tests (a moved identifier passed; controls built in code and some lookups escaped).
7fd04bd: 0 blockers, 3 should-fix, 6 nits, all in the test readers.
0e9816f: 0 blockers, 2 should-fix, 6 nits, both in the test readers (a lookup whose identifier is built from a sum or a call was not read; one failure message gave advice that does not apply).
The last fix round (0e9816f to b0206f3) answered those two and four nits, changed nothing under src, and was not sent through a fourth review: under the owner's merge rule of 2026-11-11, findings that are only about tests are recorded and merged. Its evidence: 136 single changes rerun with the reviewers' own runners; four results changed, all four intended. The coordinator added one documentation commit (9b542ce) correcting a sentence that pull request 56 made untrue.
Recorded, for the step that rebuilds the views (plan step 12)
The tree's fixed nodes get their identifiers from the view models, which these tests cannot see; the check will then read them from the presentation assembly itself.
Texts created in code are outside the live-region check.
Audit U28: three status texts a screen reader is not told about today (the job list's failure text, the start-at-sign-in result, the connection dialog's message bar), to be decided when the views are rebuilt.
Two reviewer nits left on purpose: the type of a named element is read by name across a file; two creation messages whose advice does not fit.
A stale comment at src/Qqq.PrintAgent/MainWindow.xaml.cs:71.
Not proven here
The interactive UI tests and the layout harness were not run (they need an elevated interactive desktop). That FlaUI finds the 14 new identifiers, and what Narrator says, only an interactive run can show.
Step 11 of the connection wizard build ([ADR-0005](docs/adr/0005-connection-wizard-and-server-tree.md)): the automation identifiers the interactive UI tests find controls by are recorded, and held by tests, before the window is rewritten as a tree and a wizard.
**What changes in the product**
- 14 controls that had no identifier get one (for example the share check box of a printer row, the Log settings dialog's fields, "Exit companion"). The two controls that had only an `x:Name` (`Navigation`, `SupportLink`) get an explicit identifier of the same value, so an automation client sees no rename.
- None of the 107 identifiers that existed is renamed, removed or moved. The first reviewer extracted both sets independently of the tests and compared control by control.
- No live region was added or removed; nothing an operator sees or hears changes.
**What the tests hold** (`tests/Qqq.PrintAgent.Presentation.Tests/Views/`, read as text; no reference to WinUI or the companion project)
- Every interactive control has an identifier; the recorded list names each identifier with its class and control, so a moved identifier fails as well as a removed or renamed one.
- Every identifier the UI tests and the layout harness look up is set by the companion.
- Status texts that change are either declared as a live region or listed with the reason they are not.
- Position-based identifiers (`Server.<n>`, `Printer.<n>`, `Job.<n>`) have a table that says where each may be produced; none is produced yet.
**Verification**
- Head `9b542ce` on `release/2.0` at `c3bf23d`.
- `build/Verify.ps1` passed on that head: 1,174 Core, 2,337 Windows runtime and command line, 1,110 presentation tests, none failed; Core 100% of 2,311 lines, presentation 100% of 2,236, command line 100%, Windows 96.06% (4,543 of 4,729; minimum 95).
- A clean export with only the presentation test project restored passes with the companion never built.
**Review (independent reviewers)**
- `1f480bc`: `0 blockers, 8 should-fix, 7 nits`. The product part was confirmed clean; every should-fix was in the tests (a moved identifier passed; controls built in code and some lookups escaped).
- `7fd04bd`: `0 blockers, 3 should-fix, 6 nits`, all in the test readers.
- `0e9816f`: `0 blockers, 2 should-fix, 6 nits`, both in the test readers (a lookup whose identifier is built from a sum or a call was not read; one failure message gave advice that does not apply).
- The last fix round (`0e9816f` to `b0206f3`) answered those two and four nits, changed nothing under `src`, and was not sent through a fourth review: under the owner's merge rule of 2026-11-11, findings that are only about tests are recorded and merged. Its evidence: 136 single changes rerun with the reviewers' own runners; four results changed, all four intended. The coordinator added one documentation commit (`9b542ce`) correcting a sentence that pull request 56 made untrue.
**Recorded, for the step that rebuilds the views (plan step 12)**
- The tree's fixed nodes get their identifiers from the view models, which these tests cannot see; the check will then read them from the presentation assembly itself.
- Texts created in code are outside the live-region check.
- Audit U28: three status texts a screen reader is not told about today (the job list's failure text, the start-at-sign-in result, the connection dialog's message bar), to be decided when the views are rebuilt.
- Two reviewer nits left on purpose: the type of a named element is read by name across a file; two creation messages whose advice does not fit.
- A stale comment at `src/Qqq.PrintAgent/MainWindow.xaml.cs:71`.
**Not proven here**
The interactive UI tests and the layout harness were not run (they need an elevated interactive desktop). That FlaUI finds the 14 new identifiers, and what Narrator says, only an interactive run can show.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Fourteen controls had no identifier, so a UI test could find them only by
their text or their position:
- MainWindow.xaml: the navigation pane (Navigation) and the Settings page's
support link (SupportLink), which get the element names Windows already
reported for them; the empty Printers page's "Connection settings" button
(PrintersConnectionSettings); "Windows Services" (WindowsServices), "Get
the installer" (GetInstaller) and the Settings page's "Exit companion"
(ExitCompanion).
- Code-behind: a printer row's share checkbox (SharePrinter) and its
"Windows diagnostics" expander (PrinterDiagnostics); a job row's event
history expander (JobEventHistory); the About dialog's "Contact support"
link (AboutSupportLink); the Log settings dialog's three number boxes and
its switch (LogRetentionDays, LogMaxFileMegabytes, LogSegmentsPerDay,
LogDebugLogging, named after the fields of LogSettings).
No identifier that existed is renamed or removed, and nothing an operator
sees changes. The tray icon's menu items stay without one: H.NotifyIcon
draws that menu as a native Windows menu, so they are never elements on
screen.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The UI tests and the layout harness find controls by automation identifier
and are not in the gate, so a view could drop or rename one unnoticed. The
tree and the wizard of ADR-0005 are about to rewrite these views.
XamlContractTests reads the companion's markup and code-behind as text.
The project links them, and the two interactive suites, as content; it
references neither WinUI nor the companion.
- EveryInteractiveControl_HasAnIdentifier: every element of an interactive
kind in a view, and every control of one that code creates, has an
identifier. The kinds are listed in the test; a kind no view has had
before fails until it is put in a list. The tray menu's four items are
the one counted exception.
- Identifiers_AreTheRecordedSet: the identifiers are the 121 lines of
Views/automation-ids.txt, so a rename or a removal is a changed line.
Identifiers_AreEachSetInOnePlace: none is written twice.
- IdentifiersTheInteractiveSuitesLookUp_AreSetByTheCompanion: the 68
identifiers the two suites look up (Views/interactive-test-ids.txt) are
each set by the companion, or are one of AppKit's two.
ElementNamesTheLayoutHarnessLooksUp_AreInTheViews does the same for the
x:Names the harness finds.
- StatusTexts_DeclareALiveRegion: every text a view changes while it is
shown is a status text that declares its live region (the footer status,
the message bar, the two update texts) or is listed as not announced,
with the reason. No declaration is added or removed by this change.
ViewContractReaderTests tries the readers on small pieces of source: each
form they claim to see, and that what they cannot read is reported.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Answers the review of the identifier baseline (0 blockers, 8 should-fix, 7 nits). Nothing under src changes: the
121 identifiers are on the same controls. The tests, their readers and the recorded list change.
An identifier that moves now fails. Each line of Views/automation-ids.txt is "Identifier | class | control": the
view's class or the class the C# file declares, and the control as the readers describe it without a line number
(for markup the tag and the x:Name, or the tag, the label and the nearest named element around it; for code the
target of SetAutomationId, with the side of a choice or the call that forwarded the identifier). Two identifiers
that swap controls are two changed lines. The class stands where the file's name would, so a file that is only
renamed changes nothing. Two identified controls that the list could not tell apart fail, and so does an element
without an identifier whose x:Name is another control's identifier, since WinUI reports the name.
Controls created in code are read from every new in the companion. A creation is matched to a SetAutomationId of
the same method, and of the same file only for a field or a property. Nullable declarations, global:: and
namespaces are read; a new( whose type is written where the reader does not look fails. Code is held to the same
lists of kinds as markup, with a fourth list for what code creates that is no element (values, commands,
services), so a type in none of them fails. The message of the commit that added these tests ("test(presentation):
hold the companion's identifiers and live regions to a recorded contract", reviewed as 00de256) said "a kind no
view has had before fails until it is put in a list"; at that commit this held for markup only, and it holds for
code from this one. A class of the companion needs no entry: it is what it derives from, and one with its own
markup is a view. An
identifier is read only from SetAutomationId; AutomationIdProperty, the method used as a delegate, and
XamlReader fail with what to write instead. A helper that forwards an identifier is read from its callers in
every file. The tray menu's items are excepted by the menu they are added to, which must be the one given to the
TaskbarIcon, not by a count. An element that takes input (a handler for a press or a key, in markup or in code,
or a tab stop) needs an identifier whatever its kind.
Identifiers that are not written out are allowed only for the forms ADR-0005 and the plan's G17 give
(Server.<n>, Printer.<n>, Job.<n>), each at the one place a table in the test names. Nothing produces them yet,
and a test holds that: no such identifier is set, written or looked up. The view step fills in places; it does
not change a rule.
Both interactive suites are linked with every sub-folder. A mention of AutomationId in a suite outside a lookup
the reader knows fails (an XPath, a property comparison, ==), and the harness's Field<T>(window, nameof(...)) is
read.
Every named TextBlock, RichTextBlock and InfoBar is now in one of three lists: status text, changes without
being announced, never changes. x:DefaultBindMode is honoured, and a text written from another file or through
a method that writes its parameter is seen. JobsSummary, StartupGuidanceText and ConnectionFeedback are listed
as not announced today, to be decided in the view step, each with the fact that makes it a result (audit U28).
The one announcement the window makes, for the footer, is held as text; declaring a live region is not
announcing.
Not seen, and said so in the readers: a control made without new in the companion, code in the holes of an
interpolated string, a text that code creates, and what a screen reader says.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The changelog entry said every control an operator acts on has an identifier. It now says what is covered (the
companion's markup and the controls its code creates) and what is not: the choices of a combo box, a dialog's
platform buttons, AppKit's own views and the tray icon's native menu. Its list of what the test fails on is
brought up to the test, with what the test does not see.
The entry gave a reason for leaving the connection dialog's message bar without a live region: that one "would
say the same result twice". That was not established and the code contradicts it; the bar is left as it is and
goes with the dialog.
Audit U28 (should-fix, open): the Jobs page's summary line, the text under the sign-in startup switch and a new
result in the already open connection dialog are announced by nothing. To be decided in the view step, with U9.
No behavior changes here.
ADR-0005, "What the re-audit scores accessibility against", said none of the gated tests it names exists yet.
XamlContractTests exists, with the three tests the plan names for it; the tests of the other four rows, and the
types they would test, do not.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Answers the confirmation review of the identifier baseline (0 blockers, 3 should-fix, 6 nits). Nothing under src
changes. The readers, the contract tests and their own tests change.
FlaUI's FindFirstDescendant and FindFirstChild find by automation identifier when their one argument is a string
(FlaUI.Core 5.0.0 documents those two overloads so, and no other Find method of it takes an identifier as a
string). They are now lookups: a string literal is read; anything else that starts with a quote, a name the
method declares as a string, and what cannot be told from a string (an undeclared or var name, a member, an
element of an array, a sum, a cast, nameof, ToString) is read as any other lookup's argument or reported. A
lambda, a call that makes a condition and a name declared with another type stay as they were. FindAllDescendants
and FindAllChildren have no such overload in 5.0.0 and are read in the same way, so that a later FlaUI or an
extension method is not skipped.
A control created in code is one control within the block that creates it (the innermost braces around the
creation) when it is a local, and within its file when it is a field or a property. A SetAutomationId outside that
block names another control, so the second branch of an if, an accessor and a lambda in a field's initializer
each need their own, whatever the method before them names. Two controls of an interactive kind assigned to one
name in one block, or to one field, fail with both places: chosen over counting the calls, because the message
then names both controls and says what to do, and because the recorded list, which describes a control by its
class and that name, could not tell the two apart even with a call for each. A declaration now reaches to the end
of its body, read from its brackets, so a local function lies inside its method and an accessor or an initializer
is in none; nothing there is read as a parameter of the method before it. The comment in CSharpText that called
the earlier rule "stricter, never looser" said what was not true and is replaced by what the readers do.
A call of a forwarding helper that passes no literal is reported at the call, with the place the helper sets the
identifier and both ways out: write the literal, or, if it is a call of another method of the same name, rename
one of the two, because a forwarding helper needs a name no other method in the companion has. The calls that do
pass a literal are still read, so the one cause is one message and no longer also "no longer set" for the
helper's other identifiers.
The owner of a line is the type whose body encloses it, not the first type the file declares, and a class "sets
an identifier on itself" only when the call is in its own body.
Three ways an identifier reaches an automation client without being written on its control now fail: a Style's
Setter for AutomationProperties.AutomationId; a Name that code gives an element from a value that is not written
out (WinUI reports an element's name as its identifier when it has no other), unless what is named is known to be
no element; and a name set through NameProperty. A binding's mode is read with or without spaces around its
equals sign, and a Name attribute in markup is read as an x:Name is. What the readers still do not see is listed
in CSharpText and XamlContractTests: a name given as a plain literal in code, what a template or a style of
another assembly gives, a control made without new, a string that some call returns into a FlaUI lookup, and code
in the holes of an interpolated string.
The three reasons that named a line by its number name the member instead.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
The changelog said AppKit's views, the log viewer among them, "set their own" identifiers. AppKit sets two,
AboutVersionText on the About dialog's version line and AppKitSplash on the splash, and none in the log viewer,
whose toolbar the UI tests find by name. The entry's list of what the contract test fails on gains the two new
failures: an identifier a style sets, and a name that code gives an element from a value.
ADR-0005, "What the re-audit scores accessibility against": the tests hold the row "Live regions declared" for
today's window with one exception, the connection dialog's message bar, which they hold to declaring none (audit
U28). And the search for the six names of the tests that do not exist yet finds nothing as whole words;
"Announcement" is part of longer names in seven test files, none of them a test of those rows.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
A name that code gives an element from a value fails the contract, because WinUI reports an element's name as
its automation identifier when it has no other. The reader saw "Name = value" and missed the compound forms,
"Name += value" and "Name ??= value", which make a name at run time just the same. Both are read now.
Two comments are brought up to the code: the readers of the companion's C# are four files, CodeBehind.Names.cs
among them, and a Name attribute in markup is read as an x:Name is.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Answers the third confirmation review of the identifier baseline (0 blockers, 2 should-fix, 6 nits). Nothing under
src changes, and no rule and no reader is added.
A FlaUI lookup's argument was taken for a condition whenever it had a round bracket in it, so
FindFirstChild(s_stem + Position(2)) and FindFirstDescendant(position.ToString(CultureInfo.InvariantCulture))
passed with an identifier nothing sets, against the reader's own comment. What makes an argument a lambda, a call,
a sum, a choice or an indexing is now looked for outside the argument's brackets: a +, a ? or a [ there makes it
no condition, only a => there makes it a lambda, and ToString is known to give a string with or without
arguments. The comment now says what is still unseen: a string written with a call or a new and none of those
marks, and a name the method declares with a type that is a string under another name.
A call of a forwarding helper that passes no literal now also says what to do when the identifier is one of the
generated forms: set it with SetAutomationId directly at the place the table of those forms names, because a
forwarding helper carries written identifiers only. The comment on the table says the same.
A name given at run time was skipped when its literal ended the line and the sum went on in the next. A literal is
the whole value only when the statement, the item or the initializer ends after it. A member of an enum that is
called Name and given a value is no longer reported as a name.
Three comments called the braces around a creation "the block that declares it"; they are the block that creates
it.
Left for the view step: the type of what is named is read by name across the whole file, and two creation
messages give advice that does not fit where the control is.
Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The entry said the log viewer has no identifier of the companion's. AppKit sets none there, but the companion
inserts a "Clear logs" button into the viewer's toolbar and identifies it (ClearLogsFromViewer, recorded in
automation-ids.txt). The clause now states that exception, as the clause on combo boxes states its own.
Signed-off-by: James Maes <james@kof22.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The paragraph under 'What the re-audit scores accessibility against' said that
a whole-word search finds none of six type names. That was true when this
branch wrote it and stopped being true when the wizard's frame and Server step
merged (pull request 56): Announcement, PrimaryAction, CancelAction and
FocusTarget exist now. ServerTree and HealthBadge still do not.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Step 11 of the connection wizard build (ADR-0005): the automation identifiers the interactive UI tests find controls by are recorded, and held by tests, before the window is rewritten as a tree and a wizard.
What changes in the product
x:Name(Navigation,SupportLink) get an explicit identifier of the same value, so an automation client sees no rename.What the tests hold (
tests/Qqq.PrintAgent.Presentation.Tests/Views/, read as text; no reference to WinUI or the companion project)Server.<n>,Printer.<n>,Job.<n>) have a table that says where each may be produced; none is produced yet.Verification
9b542ceonrelease/2.0atc3bf23d.build/Verify.ps1passed on that head: 1,174 Core, 2,337 Windows runtime and command line, 1,110 presentation tests, none failed; Core 100% of 2,311 lines, presentation 100% of 2,236, command line 100%, Windows 96.06% (4,543 of 4,729; minimum 95).Review (independent reviewers)
1f480bc:0 blockers, 8 should-fix, 7 nits. The product part was confirmed clean; every should-fix was in the tests (a moved identifier passed; controls built in code and some lookups escaped).7fd04bd:0 blockers, 3 should-fix, 6 nits, all in the test readers.0e9816f:0 blockers, 2 should-fix, 6 nits, both in the test readers (a lookup whose identifier is built from a sum or a call was not read; one failure message gave advice that does not apply).0e9816ftob0206f3) answered those two and four nits, changed nothing undersrc, and was not sent through a fourth review: under the owner's merge rule of 2026-11-11, findings that are only about tests are recorded and merged. Its evidence: 136 single changes rerun with the reviewers' own runners; four results changed, all four intended. The coordinator added one documentation commit (9b542ce) correcting a sentence that pull request 56 made untrue.Recorded, for the step that rebuilds the views (plan step 12)
src/Qqq.PrintAgent/MainWindow.xaml.cs:71.Not proven here
The interactive UI tests and the layout harness were not run (they need an elevated interactive desktop). That FlaUI finds the 14 new identifiers, and what Narrator says, only an interactive run can show.
🤖 Generated with Claude Code
Answers the review of the identifier baseline (0 blockers, 8 should-fix, 7 nits). Nothing under src changes: the 121 identifiers are on the same controls. The tests, their readers and the recorded list change. An identifier that moves now fails. Each line of Views/automation-ids.txt is "Identifier | class | control": the view's class or the class the C# file declares, and the control as the readers describe it without a line number (for markup the tag and the x:Name, or the tag, the label and the nearest named element around it; for code the target of SetAutomationId, with the side of a choice or the call that forwarded the identifier). Two identifiers that swap controls are two changed lines. The class stands where the file's name would, so a file that is only renamed changes nothing. Two identified controls that the list could not tell apart fail, and so does an element without an identifier whose x:Name is another control's identifier, since WinUI reports the name. Controls created in code are read from every new in the companion. A creation is matched to a SetAutomationId of the same method, and of the same file only for a field or a property. Nullable declarations, global:: and namespaces are read; a new( whose type is written where the reader does not look fails. Code is held to the same lists of kinds as markup, with a fourth list for what code creates that is no element (values, commands, services), so a type in none of them fails. The message of the commit that added these tests ("test(presentation): hold the companion's identifiers and live regions to a recorded contract", reviewed as 00de256) said "a kind no view has had before fails until it is put in a list"; at that commit this held for markup only, and it holds for code from this one. A class of the companion needs no entry: it is what it derives from, and one with its own markup is a view. An identifier is read only from SetAutomationId; AutomationIdProperty, the method used as a delegate, and XamlReader fail with what to write instead. A helper that forwards an identifier is read from its callers in every file. The tray menu's items are excepted by the menu they are added to, which must be the one given to the TaskbarIcon, not by a count. An element that takes input (a handler for a press or a key, in markup or in code, or a tab stop) needs an identifier whatever its kind. Identifiers that are not written out are allowed only for the forms ADR-0005 and the plan's G17 give (Server.<n>, Printer.<n>, Job.<n>), each at the one place a table in the test names. Nothing produces them yet, and a test holds that: no such identifier is set, written or looked up. The view step fills in places; it does not change a rule. Both interactive suites are linked with every sub-folder. A mention of AutomationId in a suite outside a lookup the reader knows fails (an XPath, a property comparison, ==), and the harness's Field<T>(window, nameof(...)) is read. Every named TextBlock, RichTextBlock and InfoBar is now in one of three lists: status text, changes without being announced, never changes. x:DefaultBindMode is honoured, and a text written from another file or through a method that writes its parameter is seen. JobsSummary, StartupGuidanceText and ConnectionFeedback are listed as not announced today, to be decided in the view step, each with the fact that makes it a result (audit U28). The one announcement the window makes, for the footer, is held as text; declaring a live region is not announcing. Not seen, and said so in the readers: a control made without new in the companion, code in the holes of an interpolated string, a text that code creates, and what a screen reader says. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Signed-off-by: James Maes <james@kof22.com>