Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: CI

# Build-only continuous integration: verifies that the whole solution compiles on
# Windows and that the Avalonia net10.0 (Linux) target compiles on Ubuntu.
# Continuous integration: compiles the whole solution in Debug and Release on
# Windows and compiles the cross-platform projects for Linux. Warnings are
# treated as errors so the "0 warnings / 0 errors" policy is enforced.
# The test suites are intentionally NOT run in CI — they include live endpoints,
# real app launches and real mounts; run them locally (see AGENTS.md and
# docs/14-testing.md for the fast/slow split).
Expand All @@ -27,6 +28,7 @@ jobs:
build-windows:
name: Build solution (Windows)
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -36,12 +38,16 @@ jobs:
with:
dotnet-version: '10.0.x'

- name: Build (Debug)
run: dotnet build SimpleLauncher.sln -c Debug --nologo
- name: Build Debug (warnings as errors)
run: dotnet build SimpleLauncher.sln -c Debug --nologo -warnaserror

- name: Build Release (warnings as errors)
run: dotnet build SimpleLauncher.sln -c Release --nologo -warnaserror

build-linux:
name: Build Avalonia net10.0 (Linux)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -51,5 +57,14 @@ jobs:
with:
dotnet-version: '10.0.x'

- name: Build (Debug, net10.0)
run: dotnet build SimpleLauncher.Avalonia/SimpleLauncher.Avalonia.csproj -c Debug -f net10.0 --nologo
- name: Build Avalonia Debug (warnings as errors)
run: dotnet build SimpleLauncher.Avalonia/SimpleLauncher.Avalonia.csproj -c Debug -f net10.0 --nologo -warnaserror

- name: Build Avalonia Release (warnings as errors)
run: dotnet build SimpleLauncher.Avalonia/SimpleLauncher.Avalonia.csproj -c Release -f net10.0 --nologo -warnaserror

# The app references the updater with ReferenceOutputAssembly=false, so
# building the app does not compile it. The Linux release publishes it,
# therefore build it explicitly here for both configurations.
- name: Build updater (warnings as errors)
run: dotnet build SimpleLauncher.Avalonia.Updater/SimpleLauncher.Avalonia.Updater.csproj -c Release -f net10.0 --nologo -warnaserror
116 changes: 116 additions & 0 deletions .github/workflows/package-dry-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Package dry run

# Packages all four RIDs exactly like the release workflow, but never creates or
# updates a GitHub release. It exists to validate the packaging scripts and the
# Linux ZIP permissions in CI (and can be dispatched manually before a release).
#
# The version is read from the canonical SimpleLauncher.csproj, so this workflow
# never needs a version input and can never publish a version that is not
# consistent across the projects and manifests.

on:
push:
branches:
- master
paths:
- '.github/workflows/package-dry-run.yml'
- 'global.json'
- 'scripts/package-release.ps1'
- 'scripts/package-release-linux.ps1'
- 'SimpleLauncher.sln'
- 'SimpleLauncher/**'
- 'SimpleLauncher.Core/**'
- 'SimpleLauncher.Avalonia/**'
- 'SimpleLauncher.Avalonia.Updater/**'
pull_request:
paths:
- '.github/workflows/package-dry-run.yml'
- 'global.json'
- 'scripts/package-release.ps1'
- 'scripts/package-release-linux.ps1'
- 'SimpleLauncher.sln'
- 'SimpleLauncher/**'
- 'SimpleLauncher.Core/**'
- 'SimpleLauncher.Avalonia/**'
- 'SimpleLauncher.Avalonia.Updater/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: package-dry-run-${{ github.ref }}
cancel-in-progress: true

jobs:
package:
name: Package all RIDs (no release)
runs-on: windows-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up .NET SDK
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'

- name: Read the canonical version
shell: pwsh
run: |
[xml]$project = Get-Content -LiteralPath 'SimpleLauncher/SimpleLauncher.csproj' -Raw
$version = $project.SelectSingleNode('/Project/PropertyGroup/Version').InnerText.Trim()
if ($version -notmatch '^\d+\.\d+\.\d+$') {
throw "Unexpected version '$version' in SimpleLauncher.csproj."
}
"VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8

- name: Package Windows release (no GitHub release)
shell: pwsh
run: >
./scripts/package-release.ps1
-Version $env:VERSION
-OutputDir '${{ github.workspace }}/artifacts/release'
-WorkDir '${{ runner.temp }}/sl-publish'

- name: Package Linux release (no GitHub release)
shell: pwsh
run: >
./scripts/package-release-linux.ps1
-Version $env:VERSION
-OutputDir '${{ github.workspace }}/artifacts/release'
-WorkDir '${{ runner.temp }}/sl-publish-linux'

- name: Upload packages as workflow artifacts
uses: actions/upload-artifact@v7
with:
name: simplelauncher-dry-run
path: artifacts/release/*.zip
retention-days: 7

verify-linux:
name: Verify Linux packages
needs: package
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Download packages
uses: actions/download-artifact@v7
with:
name: simplelauncher-dry-run
path: artifacts

- name: Check the ZIP layout and executable bits
shell: bash
run: |
set -euo pipefail
unzip -q artifacts/release_*_linux-x64.zip -d app
test -x app/SimpleLauncher.Avalonia
test -f app/appsettings.json
test -f app/LICENSE.txt

unzip -q artifacts/updater_linux-x64.zip -d updater
test -x updater/Updater

echo "Linux payload layout and permissions are correct."
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ AVALONIABUGS.md
# Local-only files (untracked, never pushed to GitHub)
BUGSNEEDFIX.md
BUGSTOBEFIXED.md
AGENTS.md
AGENTS.local.md
LinuxBugs.md

# Local AI-generated source images (not shipped with the repo)
Expand Down
108 changes: 108 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Agent Instructions — SimpleLauncher

Standing rules for AI agents (and anyone) working in this repository.

## 🚫 Never delete files inside `bin\Release`

The `bin\Release` output folders (in any project: `SimpleLauncher`, `SimpleLauncher.Core`,
`SimpleLauncher.Avalonia`, `SimpleLauncher.Avalonia.Updater`, `Tools\*`, etc.) contain the
**built release artifacts used for packaging and publishing**. They are intentionally kept on
disk.

**Rules:**

- **NEVER delete, move, rename, or clean files inside any `bin\Release` folder** — not
individually, not with recursive cleanup, not with `git clean`, `dotnet clean`, `rm -rf`,
`rd /s`, or any other bulk operation.
- `bin\Release` folders are **never to be touched by cleanup scripts or agents**, even when
asked to "clean the build output" or "remove build artifacts" — if such a request comes in,
clarify it first (the requestor almost certainly means `bin\Debug` / `obj`, or nothing at all).
- If a task requires a fresh release build, rebuild **over** the existing `bin\Release` output
(`dotnet build -c Release` or `dotnet publish`) — do not delete it first.
- Deleting `bin\Debug`, `obj`, or `bin\Release\...` content that the task itself just created
during the same session is allowed, but only that.
- These folders are gitignored (`[Bb]in/`), so git operations will never touch them; the rule
protects against direct filesystem operations.

## ✅ Standing policies (do not revert or bypass)

- **CI scope**: GitHub Actions builds, packages releases and deploys docs — it never runs tests.
- `.github/workflows/ci.yml` — build-only on pushes to `master` (non-docs paths) and PRs: full
`SimpleLauncher.sln` Debug **and Release** builds on `windows-latest` + Avalonia `net10.0`
Debug/Release and the updater `net10.0` build on `ubuntu-latest`. Builds pass
`-warnaserror`, so the 0-warning policy is enforced. **Never add tests to it.**
- `.github/workflows/package-dry-run.yml` — packages all four RIDs exactly like the release
workflow (version read from `SimpleLauncher.csproj`), uploads the zips and verifies the Linux
executable bits on `ubuntu-latest`, but **never creates or updates a GitHub release**. Runs on
PRs/pushes touching the app projects or packaging scripts, and on manual dispatch.
- `.github/workflows/release.yml` — manual (`workflow_dispatch`); packages the unified WPF +
Avalonia bundle for `win-x64` / `win-arm64` via `scripts/package-release.ps1` and the
framework-dependent Linux bundle for `linux-x64` / `linux-arm64` via
`scripts/package-release-linux.ps1`. On Windows the payload places `SimpleLauncher.exe` and
`SimpleLauncher.Avalonia.exe` next to each other (both framework-dependent); on Linux it is
the framework-dependent `SimpleLauncher.Avalonia` (the .NET 10 runtime is required). Both ship
as `release_{version}_{rid}.zip` plus the standalone `updater_{rid}.zip` (`Updater.exe` /
`Updater`). Creates/updates the GitHub release.
- `.github/workflows/docs.yml` — on `docs/**` pushes to `master`; deploys GitHub Pages from
`docs/` and syncs the wiki via `scripts/sync-wiki.py` (needs the `WIKI_PAT` secret, skipped
with a warning when absent).
- Verification stays local: `dotnet build` / `dotnet test` per project on Windows (plus WSL2
for the Linux paths), because the suites include live endpoints and real app launches.
- **Fast/slow test split (no filtering)**: the suites are split **by project**, never by
`[Trait]`/filter: fast — `SimpleLauncher.Tests` and `SimpleLauncher.Avalonia.Tests`; slow —
`SimpleLauncher.SlowTests` (app-launch language checks, real ZIP/CHD mounts, live-endpoint
suites) and `SimpleLauncher.Avalonia.SlowTests` (cross-platform mount extraction/guards).
Every test in a project always runs unfiltered with `dotnet test` — never add a filter,
quarantine, runtime skip, or exclusion mechanism for flaky tests. Run only the **fast suites**
during iteration; run the slow suites **only before a deploy or when the admin explicitly asks**
for them — never as part of routine iteration or verification.
- **Bug reports**: the `BugReportApiSink` sends Warning+ log events to the bug-report API.
Expected user-error conditions (missing files, rate limits, timeouts, unsupported input)
must be logged at **Information** level, not Warning/Error, so they are never reported as bugs.
- **Audio never blocks the UI thread**: `PlaySoundEffects` owns a dedicated background thread;
callers only enqueue a request (newest wins). Never call NAudio `Init`/`Play`/`Stop`/`Dispose`
on the UI thread — `Stop`/`Dispose` join the playback thread, and one wedged ALSA/PipeWire stop
froze the whole app (BUG-19). `Dispose` must never join the audio thread. Regression guard:
`PlaySound_WhenAudioStopBlocks_DoesNotBlockCallerOrDispose`.
- **Accessibility instrumentation**: every interactive control must expose an accessible name so
screen readers (Narrator/NVDA on Windows, Orca on Linux) and the AT-SPI GUI-test harness can
identify it. Use `AutomationProperties.Name` (`Avalonia.Automation` in Avalonia, the WPF
equivalent in WPF); buttons whose `Content` is literal text are exempt because the text becomes
the name. Inputs, sliders, DataGrids and status/progress elements also get
`AutomationProperties.AutomationId` (stable PascalCase, mirror `x:Name` when present) and
`AutomationProperties.LiveSetting="Polite"` when they report changing status. Do not name
decorative TextBlocks/images. Guardrails: `AvaloniaAccessibilityTests` (Avalonia) and
`WpfAccessibilityTests` (WPF) fail on missing names — never weaken, filter or skip them.

## 🔧 Build & test

- Windows (per project): `dotnet build <proj>.csproj -c Debug` (or `-c Release`) — expect
0 warnings / 0 errors.
- Fast suites (run during iteration):
- `dotnet test SimpleLauncher.Tests/SimpleLauncher.Tests.csproj` (~40 s) — everything except
the slow projects below.
- `dotnet test SimpleLauncher.Avalonia.Tests/SimpleLauncher.Avalonia.Tests.csproj` (~20 s).
- Slow suites (**do not run during iteration** — only before a deploy or when the admin explicitly
asks for them):
- `dotnet test SimpleLauncher.SlowTests/SimpleLauncher.SlowTests.csproj` (~3.5 min) — launches
`SimpleLauncher.exe` for the language checks (no other SimpleLauncher instance may run),
mounts real ZIP/CHD images (needs Dokan + the bundled mount tools; skips cleanly when the
images/tools are absent) and hits the live GitHub/PureLogicCode endpoints.
- `dotnet test SimpleLauncher.Avalonia.SlowTests/SimpleLauncher.Avalonia.SlowTests.csproj`
(~1 s) — cross-platform mount extraction fallback and platform guards.
- Version metadata is centralized: `SimpleLauncher/SimpleLauncher.csproj` is canonical;
`SimpleLauncher.Core.csproj`, `SimpleLauncher/app.manifest`, the Avalonia csproj/manifest and
`SimpleLauncher.Avalonia.Updater.csproj` must stay in sync (`VersionConsistencyTests` enforces
and auto-corrects this). The two `appsettings.json` files must stay identical — the unified
bundle ships a single shared copy (`AppSettingsFilesAreIdentical` enforces this).

## 🖥️ Linux VM GUI test harness (AT-SPI + vision)

The Avalonia/Linux manual checklist (`docs/manual-tests.md`) is exercised automatically on a
Hyper-V Linux VM by the harness tracked in `scripts\gui-test-harness\`. The portable manual
(prerequisites, configuration, fixture contract, scenario authoring, troubleshooting) is
`docs/gui-test-harness.md`; the live scenario inventory and session state are in `ManualTests.md`.

Machine-specific details (VM IP/credentials, host share paths) and the current resume checklist
are kept **local only** in `AGENTS.local.md`, which is gitignored — when present, read it before
starting a GUI-test session.
3 changes: 2 additions & 1 deletion ManualTests.md
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,8 @@ parameter-suggestion dialogs — pre-existing behavior, not a regression.
`9f684574`.
- `dotnet test` at the time: Avalonia **696/696**, WPF **2122/2122** (2026-09-28); the 2026-10-13
state is Avalonia **701/701**, WPF **2182/2182** and the slow suites 83/83 + 11/11.
- `AGENTS.md` is gitignored (local-only, updated with the resume state); everything tracked is
- `AGENTS.md` is tracked (durable repo rules); the machine-specific VM harness and resume state
stay local in the gitignored `AGENTS.local.md` and in this file; everything tracked is
committed - nothing to commit before starting the next session.

## 7. Cost
Expand Down
3 changes: 2 additions & 1 deletion docs/15-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ the test suites (they include live endpoints and real app launches; run them loc

| Workflow | Trigger | What it does |
|---|---|---|
| `.github/workflows/ci.yml` | push to `master` (non-docs paths) and pull requests | Build-only: full `SimpleLauncher.sln` Debug build on `windows-latest` plus the Avalonia `net10.0` (Linux) build on `ubuntu-latest`. Never runs tests. |
| `.github/workflows/ci.yml` | push to `master` (non-docs paths) and pull requests | Build-only: full `SimpleLauncher.sln` Debug **and Release** builds on `windows-latest`, plus the Avalonia `net10.0` Debug/Release and updater `net10.0` builds on `ubuntu-latest`. All builds use `-warnaserror`. Never runs tests. |
| `.github/workflows/package-dry-run.yml` | push/PR touching the app projects or packaging scripts (or manual) | Packages all four RIDs with the same scripts as the release workflow, uploads the zips as workflow artifacts and verifies the Linux payload layout/executable bits — without creating a GitHub release. The version is read from `SimpleLauncher/SimpleLauncher.csproj`. |
| `.github/workflows/release.yml` | manual (`workflow_dispatch`) with a version | Packages the unified WPF + Avalonia bundle for `win-x64` + `win-arm64` via `scripts/package-release.ps1` and the framework-dependent single-file Linux bundle for `linux-x64` + `linux-arm64` via `scripts/package-release-linux.ps1`, uploads the zips as workflow artifacts, and creates/updates the `release{version}` GitHub release with `release_{version}_{rid}.zip` + `updater_{rid}.zip` for all four RIDs. Options: skip release creation, provide custom release notes. |
| `.github/workflows/docs.yml` | push to `master` touching `docs/**` (or manual) | Deploys GitHub Pages from `docs/` and syncs the wiki via `scripts/sync-wiki.py`. |

Expand Down
Loading