diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c6b5460..6938e238 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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). @@ -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 @@ -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 @@ -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 diff --git a/.github/workflows/package-dry-run.yml b/.github/workflows/package-dry-run.yml new file mode 100644 index 00000000..bdc0df8d --- /dev/null +++ b/.github/workflows/package-dry-run.yml @@ -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." diff --git a/.gitignore b/.gitignore index 782db0e2..31a18d26 100644 --- a/.gitignore +++ b/.gitignore @@ -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) diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..2287a83b --- /dev/null +++ b/AGENTS.md @@ -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 .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. diff --git a/ManualTests.md b/ManualTests.md index d44268e9..2d2a2447 100644 --- a/ManualTests.md +++ b/ManualTests.md @@ -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 diff --git a/docs/15-development.md b/docs/15-development.md index 490f25d1..86c8f1d3 100644 --- a/docs/15-development.md +++ b/docs/15-development.md @@ -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`. |