Look the host list up by name, rather than off a field that is never assigned
ci / build and test (push) Successful in 1m51s
ci / api image (push) Canceled after 0s
ci / android head (push) Canceled after 3m51s

Nightly 0.0.0-alpha.0.133 died before its first frame. The long press I added
attached itself in HostsScreen's constructor through the field the Avalonia name
generator declares for `x:Name` — and that field is assigned by the generated
InitializeComponent, which no view in this repository calls. Every one of them
loads its XAML directly. So the field compiles, resolves in the editor, and is
null at run time; PhoneShell builds this control on the way up, so the
NullReferenceException took the launch rather than the hosts screen.

PhoneShell and TerminalScreen both use FindControl, and PhoneShell carries a
<remarks> saying exactly this and naming exactly this consequence. I read neither
and wrote the field.

So the rule is in docs/platform-flags.md now as well. A comment on the control
that already got it right is not where somebody writing a new one is looking,
which is the whole of why two correct examples and one warning were not enough.

And phase 8 opens with "it launches at all". Nothing on this head is covered by a
test — no test project, no headless surface — so a view that throws while being
built takes the launch with it and no gate anywhere says so. Thirty seconds, and
it would have caught this one before it was published.
This commit is contained in:
2026-08-05 12:00:20 +02:00
parent 9a7e3bbd5c
commit e0655dbb31
3 changed files with 45 additions and 2 deletions
@@ -34,17 +34,31 @@ internal sealed partial class HostsScreen : UserControl
private bool held;
/// <remarks>
/// <para>
/// The long press is attached here rather than in the markup so that it sits beside the property that
/// makes it fire at all. <see cref="InputElement.IsHoldingEnabledProperty"/> is set rather than assumed:
/// it is the whole of the gesture, and a default that changed would take it away silently — every tap
/// would go on working and nothing would ever open the bar again.
/// </para>
/// <para>
/// ◆ <b><c>FindControl</c> rather than the field the name generator declares for <c>x:Name</c></b>,
/// which is the same rule <c>PhoneShell</c> and <c>TerminalScreen</c> already carry, and which this
/// file learned the hard way. That field is
/// assigned by the generated <c>InitializeComponent</c>, and no view on this head calls it — they load
/// their XAML directly, as above. So the field compiles, is null at run time, and the first thing
/// touching it throws. Here that was a <see cref="NullReferenceException"/> in this constructor, which
/// is a crash before the first frame: the phone shell builds this control on the way up, so the
/// application died on launch rather than on the hosts screen.
/// </para>
/// </remarks>
public HostsScreen()
{
AvaloniaXamlLoader.Load(this);
InputElement.SetIsHoldingEnabled(Rows, true);
Rows.Holding += OnRowHeld;
var rows = this.FindControl<ListBox>("Rows")!;
InputElement.SetIsHoldingEnabled(rows, true);
rows.Holding += OnRowHeld;
}
/// <summary>