Commit graph

3701 commits

Author SHA1 Message Date
Erik
fb64af8b25 merge: Campaign LA gate round — headless MainWindow view tests close #399
Falsification-proven: 12/12 fail against AvaloniaXamlLoader.Load (the #398
crash shape), 12/12 pass against InitializeComponent. Launcher tests 66/66
on Windows and native Ubuntu, no display required. xunit -> xunit.v3 in the
launcher test project (required by Avalonia.Headless.XUnit 12.1.1 net10.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:55:55 +02:00
Erik
2b439cc107 test(launcher): Campaign LA — headless MainWindow view tests close #399
#398 was a crash on every modal open/close caused by MainWindow's
constructor calling AvaloniaXamlLoader.Load(this) instead of the
generated InitializeComponent() — only InitializeComponent assigns the
x:Name backing fields, so every named control was null and the first
Dispatcher.UIThread.Post callback in OnViewModelPropertyChanged threw
NullReferenceException, killing the process. It reached the user gate
because no test in tests/AcDream.Launcher.Tests (ViewModel-only) ever
constructed a MainWindow. #399 is the process gap that let that class of
defect through 14,012 green tests.

Adds Avalonia.Headless.XUnit 12.1.1 to the launcher test project. Its
net10.0 dependency group targets xunit v3, so the project migrates
xunit 2.9.3 -> xunit.v3 3.2.2 (drop-in: all 54 pre-existing tests compile
and pass unchanged under dotnet test via xunit.runner.visualstudio 3.1.4,
which already supported v1/v2/v3; two call sites needed
TestContext.Current.CancellationToken per the new xUnit1051 analyzer).
TestAppBuilder.cs wires [assembly: AvaloniaTestApplication] to a headless
AppBuilder.Configure<App>() so the real App.axaml FluentTheme is live in
tests.

MainWindowViewTests.cs adds 12 [AvaloniaFact]/[AvaloniaTheory] tests:
- an explicit non-null + type check of every x:Name field the
  code-behind dereferences (ProfilesTree, ServerNameTextBox,
  AccountNameTextBox, CharacterNameTextBox, EditorSubmitButton,
  FirstRunDatDirectoryTextBox, FirstRunCloseButton, UpdateCloseButton)
- a reflection sweep over every x:Name found in MainWindow.axaml, so a
  future named control without a matching non-null field fails loudly
- one open+close round trip per ProfileEditorKind (all seven, including
  Remove), plus the first-run wizard and the update prompt, each pumping
  Dispatcher.UIThread.RunJobs() so the queued focus callback actually
  executes instead of just being asserted vacuously
- a dedicated test for the _focusBeforeModal-restore branch (not just
  the ProfilesTree.Focus() fallback), anchored on a real focusable
  button since ProfilesTree (TreeView) has Focusable="False" under
  FluentTheme — its own tab stops are TreeViewItem rows, so the
  close-path assertions check "no exception escaped the dispatcher"
  rather than "focus landed on ProfilesTree"

Falsification (required evidence): reverting MainWindow's constructor to
AvaloniaXamlLoader.Load(this) and rerunning gives 12 failed / 0 passed —
10 tests throw NullReferenceException at MainWindow.FocusActiveModal,
propagating cleanly out of Dispatcher.UIThread.RunJobs() (confirming
dispatcher exceptions are not silently swallowed), and the 2 reflection
tests fail on an explicit "x:Name 'ProfilesTree' was null after
construction" message. Restoring InitializeComponent() gives 12 passed /
0 failed. Full launcher suite: 66 passed / 0 failed, reproduced on both
Windows and native Ubuntu (WSL, no display/Xvfb — Avalonia.Headless needs
none). AcDream.Launcher.Core.Tests: 317/317 unaffected.

No CI workflow change needed: .github/workflows/headless-portability.yml's
portable-launcher job already runs dotnet test on the launcher test
project on both windows-latest and ubuntu-latest with no display setup,
which is sufficient for Avalonia.Headless.

Closes #399.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:54:49 +02:00
Erik
e1e94697b2 fix(launcher): co-deploy acdream-bake on Build and write a crash report (#398)
Two gaps found launching the launcher for the LA11 gate.

1. acdream-bake was co-deployed only AfterTargets=Publish with a RID, so a
   plain `dotnet build` left the launcher with no bake tool beside it while
   App.OnFrameworkInitializationCompleted resolves it at
   AppContext.BaseDirectory/acdream-bake[.exe]. A developer-built launcher
   therefore reached the first-run wizard with an installer it could never
   run. CoDeployBakeToolToBuildOutput does for Build what the publish target
   does for Publish: still NO Launcher -> Bake project reference, still a
   self-contained single file so exactly one file lands beside the launcher
   rather than scattering Content/Chorizite assemblies into its output.
   Staged through obj/ because publishing straight into the launcher output
   makes the inner publish delete what the outer build just wrote.
   Inputs/Outputs keep it incremental - verified: 79.6 MB bake exe present,
   --help exits 0, and a second build skips the republish in ~1 s.

2. #398: the top-level guard printed only ex.Message, so the crash that
   preceded this commit surfaced with no file, line, or frame. The full
   exception now goes to a crash-reports file under the resolved data root
   and stderr names the path. The first implementation wrote to the machine
   real data root when option parsing itself failed, which broke LA11 process
   local roots during an isolated run; the reporter now reads --data-dir
   positionally for that fallback. Verified: report lands inside the isolated
   root and the real root stays empty.

The redaction comment states exactly what is guaranteed - args/environment
are never serialized, while exception text may quote an option name or path,
which is safe only because credentials never enter launcher state.

Launcher.Core 317/317 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:42:29 +02:00
Erik
ef9d610459 docs(issues): file #398 and #399 from the launcher gate launch
#399 (HIGH, process class): no test constructs MainWindow — the launcher
test project is ViewModel-only with no Avalonia headless package, which is
how a crash on every modal open/close passed 14,012 green tests and reached
the user gate. Fix direction is Avalonia.Headless.XUnit plus a view test
that drives every modal open/close, catching the class rather than one
spelling.

#398 (MODERATE): the top-level guard prints only ex.Message, so the fatal
NullReferenceException fixed at d54b8a78 surfaced with no file, line, or
frame; diagnosis needed a temporary code edit and rebuild. Fix direction is
a redaction-scanned crash file under the data root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:32:06 +02:00
Erik
d54b8a789e fix(launcher): Campaign LA — MainWindow must call InitializeComponent, not AvaloniaXamlLoader.Load
Every click in the launcher exited the process. MainWindow ctor called
AvaloniaXamlLoader.Load(this), which loads the XAML tree but never assigns
the generated x:Name backing fields, so ProfilesTree, ServerNameTextBox,
AccountNameTextBox, CharacterNameTextBox, EditorSubmitButton,
FirstRunDatDirectoryTextBox and UpdateCloseButton were all null. Opening or
closing any modal calls Focus() on one of them via
OnViewModelPropertyChanged, so the NullReferenceException escaped the
dispatcher and Program's top-level guard exited 74. A fresh isolated-root
start auto-opens the first-run wizard and hit the same line with no click
at all.

App.axaml.cs keeps AvaloniaXamlLoader.Load - that is the correct idiom for
Application.Initialize(), which has no named controls.

Verified live: the isolated-root launch that died instantly now stays up
with the first-run wizard open and the window responding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 07:31:34 +02:00
Erik
c25545e8d6 docs(launcher): prepare Campaign LA user gate 2026-08-15 02:16:41 +02:00
Erik
a22f54117b docs(launcher): record Campaign LA11 automated closeout 2026-08-15 02:08:18 +02:00
Erik
d39f3098d5 merge: Campaign LA LA11 - automated closeout review-closed
# Conflicts:
#	docs/plans/2026-08-14-launcher-campaign.md
2026-08-15 02:07:39 +02:00
Erik
9f9c116792 fix(launcher): harden LA11 gate evidence 2026-08-15 01:45:15 +02:00
Erik
accd01a008 fix(launcher): harden Campaign LA11 gate evidence 2026-08-15 01:08:41 +02:00
Erik
134edabed2 feat(launcher): complete Campaign LA11 pre-gate support 2026-08-15 00:02:04 +02:00
Erik
0ac8bf6171 docs(launcher): close Campaign LA10 2026-08-14 23:50:23 +02:00
Erik
da4fb3de19 merge: Campaign LA LA10 - updater review-closed 2026-08-14 23:47:25 +02:00
Erik
f881e5b467 feat(launcher): prepare Campaign LA11 user gate 2026-08-14 23:42:30 +02:00
Erik
09d84387a8 fix(launcher): verify self-update rollback sources 2026-08-14 23:41:55 +02:00
Erik
1955ca8ab5 fix(launcher): harden updater crash recovery 2026-08-14 23:12:15 +02:00
Erik
2d2a5b5046 feat(launcher): implement verified atomic updates 2026-08-14 22:09:34 +02:00
Erik
df824f366c docs(launcher): close Campaign LA6 LA8 and LA9 2026-08-14 21:18:05 +02:00
Erik
fe63ce186a merge: Campaign LA LA8 - retail character screen review-closed 2026-08-14 21:16:25 +02:00
Erik
1dd5706e15 Make character UI retries transactional 2026-08-14 21:14:08 +02:00
Erik
2198a0cc8e merge: Campaign LA LA9 - verified installer review-closed 2026-08-14 21:06:26 +02:00
Erik
208a70ac83 fix(launcher): guard orphan bake publication 2026-08-14 21:02:27 +02:00
Erik
aeac874dab Harden retail character selection recovery 2026-08-14 20:59:10 +02:00
Erik
2bb8ccb6b6 merge: Campaign LA LA6 - login commands review-closed 2026-08-14 20:48:42 +02:00
Erik
259f0e5ac3 fix(headless): route wire-only chat commands 2026-08-14 20:47:06 +02:00
Erik
3f68895120 fix(launcher): harden installer transactions 2026-08-14 20:36:11 +02:00
Erik
6cfab727f1 Implement retail character management screen 2026-08-14 20:29:19 +02:00
Erik
41b15efd4d feat(runtime): share chat commands and run login sequence 2026-08-14 20:27:45 +02:00
Erik
ff6ebb6a6a feat(launcher): add verified first-run installer 2026-08-14 20:06:37 +02:00
Erik
267804465e docs(launcher): close Campaign LA4 LA5 and LA7 2026-08-14 19:36:00 +02:00
Erik
5535d0adac merge: Campaign LA LA5 - plugin hosting review-closed 2026-08-14 19:33:27 +02:00
Erik
f820eb258d fix(plugins): close LA5 ownership races 2026-08-14 19:28:14 +02:00
Erik
60f627998c merge: Campaign LA LA4 - Avalonia launcher review-closed 2026-08-14 19:14:25 +02:00
Erik
ae2cbbee8c fix(launcher): require executable Linux hosts 2026-08-14 19:12:32 +02:00
Erik
fbe9c8a288 fix(plugins): close LA5 host lifecycle review 2026-08-14 19:05:13 +02:00
Erik
7691cf75e2 merge: Campaign LA LA7b - selection state and flow review-closed 2026-08-14 19:05:03 +02:00
Erik
ff40656293 fix(runtime): isolate Campaign LA7b delete state 2026-08-14 19:03:43 +02:00
Erik
10a712d66b fix(launcher): close LA4 review findings 2026-08-14 19:02:20 +02:00
Erik
1b9e7e41f9 fix(runtime): close Campaign LA7b review findings 2026-08-14 18:55:48 +02:00
Erik
0e82cbf700 feat(runtime): own character selection flow 2026-08-14 18:22:17 +02:00
Erik
d0a9c65d85 feat(launcher): Campaign LA add Avalonia desktop shell 2026-08-14 18:15:14 +02:00
Erik
95f4be94db feat(plugins): complete Campaign LA5 cross-host hosting 2026-08-14 18:12:59 +02:00
Erik
6c4cd2bbc6 docs(launcher): close Campaign LA2 integration 2026-08-14 17:31:33 +02:00
Erik
e01b2cd12f merge: Campaign LA LA2 - probe and idle review-closed
# Conflicts:
#	docs/plans/2026-08-14-launcher-campaign.md
#	src/AcDream.Headless/Hosting/HeadlessSessionHost.cs
2026-08-14 17:30:07 +02:00
Erik
1c5e66c05b fix(launcher): Campaign LA close LA2 review findings 2026-08-14 17:20:02 +02:00
Erik
3313577dcc docs(launcher): Campaign LA close LA1 and LA3 2026-08-14 17:17:56 +02:00
Erik
8a03a25fc3 test(launcher): Campaign LA enforce composer-host contract 2026-08-14 17:16:13 +02:00
Erik
7749545dc4 merge: Campaign LA LA3 - Launcher.Core review-closed 2026-08-14 17:11:25 +02:00
Erik
347a1a5d16 fix(launcher): Campaign LA LA3 narrow review fixes 2026-08-14 17:06:47 +02:00
Erik
890cf267fc docs(launcher): record Campaign LA LA1 fix-round gates 2026-08-14 17:00:46 +02:00