From c744646394bf3bb955a0a9b94e10ebf6a912c5a7 Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Thu, 15 Oct 2026 15:49:04 +0200 Subject: [PATCH 01/26] ci: fix consistency-gate verdict handling [skip ci] --- .github/scripts/greg-gates.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/scripts/greg-gates.sh b/.github/scripts/greg-gates.sh index 1c0d2ef..23775e2 100644 --- a/.github/scripts/greg-gates.sh +++ b/.github/scripts/greg-gates.sh @@ -162,7 +162,7 @@ esac # ---------- version consistency (repos with VERSION file) ---------- if [ -f VERSION ]; then - python3 - <<'PYEOF' >>gate-report.txt 2>&1 || echo "GATE consistency: FAIL - checker error" | tee -a gate-report.txt + CONS_OUT="$(python3 - <<'PYEOF' 2>&1 import re, json, subprocess, glob, sys def sh(*a): try: return subprocess.run(a, capture_output=True, text=True).stdout.strip() @@ -195,9 +195,11 @@ for f in sp.run(["git", "grep", "-l", "MelonInfo", "--", "src"], capture_output= if re.sub(r"-dev$", "", v) != base: ok = False; notes.append(f"{f}: MelonInfo {v}") print(("GATE consistency: PASS - all version sources agree on " + base) if ok else ("GATE consistency: FAIL - " + "; ".join(notes) + f" (VERSION base={base})")) -sys.exit(0 if ok else 1) +sys.exit(0) PYEOF - [ $? -eq 0 ] || GATES_OK=false +)" + echo "$CONS_OUT" >>gate-report.txt + echo "$CONS_OUT" | grep -q "^GATE consistency: PASS" || GATES_OK=false else gna consistency "no VERSION file"; fi # ---------- conventional commits ---------- From 64c1ee0de8d3d7a8aace70ba61ae77ef6da772b9 Mon Sep 17 00:00:00 2001 From: mleem97 Date: Tue, 27 Oct 2026 11:18:39 +0100 Subject: [PATCH 02/26] ci: add Codacy code coverage reporting (CI job + local script) - Add `coverage` job to .forgejo/.gitea/workflows/rust.yml: generates a workspace-wide LCOV report via cargo-tarpaulin and uploads it to Codacy using the Codacy Coverage Reporter (get.sh) on push/PR. - Mirror workflows to .gitea/workflows/ (byte-identical, parity job). - Add build/scripts/linux/coverage-codacy.sh for local coverage generation and upload (matches CI job; supports --partial/--final, --force-language). - Document in TOOLS.md, CHANGELOG.md, EXTERNAL_DEPENDENCIES.md. --- .forgejo/workflows/rust.yml | 55 ++++++ .gitea/workflows/rust.yml | 55 ++++++ CHANGELOG.md | 11 ++ EXTERNAL_DEPENDENCIES.md | 2 + TOOLS.md | 31 ++++ build/scripts/linux/coverage-codacy.sh | 241 +++++++++++++++++++++++++ 6 files changed, 395 insertions(+) create mode 100644 build/scripts/linux/coverage-codacy.sh diff --git a/.forgejo/workflows/rust.yml b/.forgejo/workflows/rust.yml index d2f2a91..9595c08 100644 --- a/.forgejo/workflows/rust.yml +++ b/.forgejo/workflows/rust.yml @@ -70,6 +70,61 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test --workspace + coverage: + name: Coverage → Codacy + runs-on: ubuntu-latest + timeout-minutes: 30 + # Pushes to main/dev und PRs aus diesem Repo (Forks haben keinen Zugriff auf Secrets). + # Codacy benötigt den Head-Commit + gemeinsamen Vorfahren für PR-Diff-Coverage, + # deshalb wird Coverage auch bei PRs hochgeladen. + if: > + github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository) + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Install Slint system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ + libxkbcommon-dev libfontconfig1-dev + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin + - name: Generate workspace coverage (LCOV) + run: | + cargo tarpaulin --workspace --out lcov --output-dir target/codacy --verbose + - name: Locate LCOV report + id: locate + run: | + REPORT="$(find target/codacy -maxdepth 1 -type f \( -name 'lcov.info' -o -name 'lcov.dat' -o -name '*.lcov' \) | head -n 1)" + if [ -z "$REPORT" ]; then + echo "::error::No LCOV coverage report was generated by cargo-tarpaulin" + exit 1 + fi + echo "report=$REPORT" >> "$GITHUB_OUTPUT" + - name: Upload coverage to Codacy + if: ${{ secrets.CODACY_PROJECT_TOKEN != '' }} + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + run: | + # Codacy Coverage Reporter (Docs: coverage hinzufügen, alternative Ausführung, erweiterte Szenarien) + # -l Rust : das LCOV-Report der Rust-Sprache zuordnen + # -r : workspace-weites LCOV-Report, das jedes getestete Crate abdeckt + # Erkennt Commit-SHA-1 und Branch automatisch aus git. + bash <(curl -Ls https://coverage.codacy.com/get.sh) report \ + -l Rust \ + -r "${{ steps.locate.outputs.report }}" + - name: Upload coverage übersprungen (kein Token) + if: ${{ secrets.CODACY_PROJECT_TOKEN == '' }} + run: | + echo "::notice::CODACY_PROJECT_TOKEN ist nicht gesetzt — Coverage wurde lokal erzeugt, aber nicht an Codacy hochgeladen." + echo "Das Geheimnis CODACY_PROJECT_TOKEN setzen (Codacy → Project settings → API token), um Uploads zu aktivieren." + audit: name: Security audit runs-on: ubuntu-latest diff --git a/.gitea/workflows/rust.yml b/.gitea/workflows/rust.yml index d2f2a91..9595c08 100644 --- a/.gitea/workflows/rust.yml +++ b/.gitea/workflows/rust.yml @@ -70,6 +70,61 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test --workspace + coverage: + name: Coverage → Codacy + runs-on: ubuntu-latest + timeout-minutes: 30 + # Pushes to main/dev und PRs aus diesem Repo (Forks haben keinen Zugriff auf Secrets). + # Codacy benötigt den Head-Commit + gemeinsamen Vorfahren für PR-Diff-Coverage, + # deshalb wird Coverage auch bei PRs hochgeladen. + if: > + github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository) + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Install Slint system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ + libxkbcommon-dev libfontconfig1-dev + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin + - name: Generate workspace coverage (LCOV) + run: | + cargo tarpaulin --workspace --out lcov --output-dir target/codacy --verbose + - name: Locate LCOV report + id: locate + run: | + REPORT="$(find target/codacy -maxdepth 1 -type f \( -name 'lcov.info' -o -name 'lcov.dat' -o -name '*.lcov' \) | head -n 1)" + if [ -z "$REPORT" ]; then + echo "::error::No LCOV coverage report was generated by cargo-tarpaulin" + exit 1 + fi + echo "report=$REPORT" >> "$GITHUB_OUTPUT" + - name: Upload coverage to Codacy + if: ${{ secrets.CODACY_PROJECT_TOKEN != '' }} + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + run: | + # Codacy Coverage Reporter (Docs: coverage hinzufügen, alternative Ausführung, erweiterte Szenarien) + # -l Rust : das LCOV-Report der Rust-Sprache zuordnen + # -r : workspace-weites LCOV-Report, das jedes getestete Crate abdeckt + # Erkennt Commit-SHA-1 und Branch automatisch aus git. + bash <(curl -Ls https://coverage.codacy.com/get.sh) report \ + -l Rust \ + -r "${{ steps.locate.outputs.report }}" + - name: Upload coverage übersprungen (kein Token) + if: ${{ secrets.CODACY_PROJECT_TOKEN == '' }} + run: | + echo "::notice::CODACY_PROJECT_TOKEN ist nicht gesetzt — Coverage wurde lokal erzeugt, aber nicht an Codacy hochgeladen." + echo "Das Geheimnis CODACY_PROJECT_TOKEN setzen (Codacy → Project settings → API token), um Uploads zu aktivieren." + audit: name: Security audit runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 1228bd9..d62a6db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Cross-platform build infrastructure: `build-profiles/` (9 targets), `schemas/build-manifest.schema.json`, `build-images/` container definitions, `smoke/` programs, and `cargo xtask profiles|validate`. +- Documentation: `docs/PLATFORMS.md`, `docs/APPLE-SDK-LICENSING.md`, `docs/CONTAINERS.md`, `docs/CI-CD.md`, `docs/TROUBLESHOOTING.md`, `docs/ROADMAP.md`. + +- Optional error reporting via Bugsink (Sentry-SDK compatible) for the CLI, + enabled by setting `BUGSINK_DSN` / `SENTRY_DSN`. +- New crates `eu-greg-error-reporting` and `eu-greg-bug-report-form` with structured error reports and bug-report form model/validation. +- Fixed `cli-main` crate import alias for steam integration and removed stray doc comments causing clippy errors. +- CI: Codacy code coverage — workspace-weites LCOV-Report via `cargo tarpaulin`, hochgeladen mit dem Codacy + Coverage Reporter bei jedem Push/PR (Job `coverage` in `rust.yml`); benötigt das Geheimnis `CODACY_PROJECT_TOKEN`. ## [1.7.0] - 2026-09-28 ### Added diff --git a/EXTERNAL_DEPENDENCIES.md b/EXTERNAL_DEPENDENCIES.md index 0011f5b..8840baa 100644 --- a/EXTERNAL_DEPENDENCIES.md +++ b/EXTERNAL_DEPENDENCIES.md @@ -40,6 +40,8 @@ signing, and notarization. | nfpm and native package tools | DEB, RPM, APK, and Arch packages | | WSL | optional Windows-host Linux package adapter | | Docker/Buildx | repeatable Linux build/test targets; not a Windows or macOS GUI emulator | +| cargo-tarpaulin | Rust code coverage (LCOV) generated per workspace, uploaded to Codacy | +| Codacy Coverage Reporter | uploads LCOV coverage reports to Codacy (CI job `coverage`) | | Forgejo Actions (`.forgejo/workflows/`) + Gitea Actions (`.gitea/workflows/`, byte-identical) | CI on Linux-only runners; Windows artifacts are cross-built, releases go through the instance API, one Discord notification per run. GitHub is a read-only archive mirror with no workflows. | ## Release formats diff --git a/TOOLS.md b/TOOLS.md index ef0f8e6..bfd14b7 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -30,9 +30,40 @@ - Telemetry must respect `AppSettings.TelemetryEnabled`. - Installation tracking uses an anonymized machine identifier. +## Code coverage (Codacy) + +- Coverage wird bei jedem Push auf `main`/`dev` und bei Pull Requests (aus diesem + Repo, nicht von Forks) mit [cargo-tarpaulin](https://github.com/xd009642/tarpaulin) + als workspace-weites LCOV-Report erzeugt und über den + [Codacy Coverage Reporter](https://github.com/codacy/codacy-coverage-reporter) + an Codacy hochgeladen (`.forgejo/workflows/rust.yml` / `.gitea/workflows/rust.yml`, + Job `coverage`). +- Es wird ein einzelnes workspace-weites LCOV-Report hochgeladen + (`cargo tarpaulin --workspace --out lcov`), sodass auch unveränderte Module + (Monorepo) abgedeckt sind. Siehe die Codacy-Docs zu mehreren Reports + (`-r` mehrfach / `--partial` + `final`) und zu nicht unterstützten Sprachen + (`-l --force-language`). +- Authentifizierung: das Repository-Geheimnis `CODACY_PROJECT_TOKEN` setzen + (Codacy → Project settings → API token). Wird es nicht gesetzt, wird Coverage + nur lokal erzeugt, nicht hochgeladen. Bei Codacy Self-hosted zusätzlich + `CODACY_API_BASE_URL` (und ggf. `CODACY_REPORTER_VERSION`) setzen. + +- Lokal: + `./build/scripts/linux/coverage-codacy.sh` erzeugt ein workspace-weites + LCOV-Report (cargo-tarpaulin) und lädt es über den Codacy Coverage Reporter + hoch — identisch mit dem CI-Job. Ohne `CODACY_PROJECT_TOKEN` bricht das + Skript mit einer klaren Fehlermeldung ab. + Optionen: `--check` (Voraussetzungen prüfen), `--install-only` (nur + Reporter + tarpaulin installieren), `--partial` / `--final` (mehrere Reports + nacheinander, erweitertes Szenario), `--force-language -l ` (für + nicht unterstützte Sprachen), `-r ` (explizites Report-File). + ## Troubleshooting - Duplicate Avalonia title bars: check custom-chrome settings. - Steam upload blocked: inspect rate-limit state and cooldown copy. - JSON trimming warnings: verify source-generation registration in `AppJsonContext`. - Linux package build failures: verify external packaging tools listed in `EXTERNAL_DEPENDENCIES.md`. +- Codacy zeigt keine Coverage: `CODACY_PROJECT_TOKEN` geheim gesetzt? Der letzte + Commit des Branches musste bereits statisch von Codacy analysiert sein, bevor + Coverage-Metriken erscheinen (siehe Codacy-Docs „Validating that the coverage setup is complete“). diff --git a/build/scripts/linux/coverage-codacy.sh b/build/scripts/linux/coverage-codacy.sh new file mode 100644 index 0000000..2016c36 --- /dev/null +++ b/build/scripts/linux/coverage-codacy.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +# +# coverage-codacy.sh — lokale Codacy Code-Coverage für das Rust-Workspace +# +# Generiert ein workspace-weites LCOV-Coverage-Report mit cargo-tarpaulin und +# lädt es über den Codacy Coverage Reporter an Codacy hoch. +# +# Dies spiegelt den CI-Job `coverage` (.forgejo/.gitea/workflows/rust.yml) wider, +# sodass lokale Uploads identisch mit CI-Uploads verlaufen. +# +# Voraussetzungen: +# - cargo (Rust toolchain) +# - CODACY_PROJECT_TOKEN (Codacy → Project settings → API token) +# - Optional: CODACY_API_BASE_URL (bei Codacy Self-hosted) +# - Optional: CODACY_REPORTER_VERSION (Codacy Coverage Reporter Version) +# +# Verwendung: +# ./build/scripts/linux/coverage-codacy.sh # erzeugt + hochlädt (ein Report) +# ./build/scripts/linux/coverage-codacy.sh --partial -r a.lcov # Teil-Report hochladen +# ./build/scripts/linux/coverage-codacy.sh --final # final signal senden +# ./build/scripts/linux/coverage-codacy.sh --check # nur Voraussetzungen prüfen +# ./build/scripts/linux/coverage-codacy.sh --install-only # nur Reporter + tarpaulin installieren +# +# Mehrere Reports für dieselbe Sprache (erweitertes Szenario): +# ./build/scripts/linux/coverage-codacy.sh --partial -l Rust -r report1.lcov +# ./build/scripts/linux/coverage-codacy.sh --partial -l Rust -r report2.lcov +# ./build/scripts/linux/coverage-codacy.sh --final +# +# Nicht unterstützte Sprache erzwingen: +# ./build/scripts/linux/coverage-codacy.sh --force-language -l Kotlin -r report.lcov + +set -euo pipefail + +# --- Pfade ------------------------------------------------------------------ + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "$REPO_ROOT" + +COVERAGE_DIR="$REPO_ROOT/target/codacy" +REPORTER_TMP="$HOME/.cache/codacy/codacy-coverage-reporter" +REPORTER_BIN="$REPORTER_TMP/codacy-coverage-reporter" + +LANGUAGE="Rust" +FORCE_LANGUAGE=false +PARTIAL=false +FINAL=false +CHECK_ONLY=false +INSTALL_ONLY=false +REPORT_FILES=() + +# --- Hilfsfunktionen -------------------------------------------------------- + +log() { printf '[coverage-codacy] %s\n' "$*"; } +err() { printf '[coverage-codacy] FEHLER: %s\n' "$*" >&2; } + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + err "Befehl '$1' nicht gefunden." + return 1 + fi +} + +# --- Argumente parsen ------------------------------------------------------- + +while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + sed -n '3,48p' "$0" + exit 0 + ;; + -l|--language) + [ "$#" -ge 2 ] || { err "--language benötigt einen Wert"; exit 2; } + LANGUAGE="$2"; shift 2 ;; + --force-language) + FORCE_LANGUAGE=true; shift ;; + -r|--report) + [ "$#" -ge 2 ] || { err "--report benötigt einen Pfad"; exit 2; } + REPORT_FILES+=("$2"); shift 2 ;; + --partial) + PARTIAL=true; shift ;; + --final) + FINAL=true; shift ;; + --check) + CHECK_ONLY=true; shift ;; + --install-only) + INSTALL_ONLY=true; shift ;; + --workspace-dir) + [ "$#" -ge 2 ] || { err "--workspace-dir benötigt einen Pfad"; exit 2; } + COVERAGE_DIR="$2"; shift 2 ;; + --*) + err "Unbekannte Option: $1"; exit 2 ;; + *) + err "Unbekanntes Argument: $1"; exit 2 ;; + esac +done + +# --- Voraussetzungen prüfen ------------------------------------------------- + +check_prereqs() { + local ok=true + need_cmd cargo || ok=false + need_cmd git || ok=false + if [ "$INSTALL_ONLY" = false ] && [ "$CHECK_ONLY" = false ]; then + # cargo-tarpaulin wird installiert, falls fehlt; dennoch prüfen wir cargo. + : + fi + if [ -z "${CODACY_PROJECT_TOKEN:-}" ]; then + err "CODACY_PROJECT_TOKEN ist nicht gesetzt." + err " → Codacy → Project settings → API token → CODACY_PROJECT_TOKEN exportieren." + ok=false + fi + if [ "$ok" = false ]; then + exit 1 + fi + log "Voraussetzungen OK (cargo, git, CODACY_PROJECT_TOKEN gesetzt)." +} + +# --- cargo-tarpaulin installieren ------------------------------------------- + +ensure_tarpaulin() { + if command -v cargo-tarpaulin >/dev/null 2>&1; then + log "cargo-tarpaulin bereits installiert: $(command -v cargo-tarpaulin)" + return 0 + fi + log "cargo-tarpaulin nicht gefunden — installiere über cargo..." + cargo install cargo-tarpaulin +} + +# --- Codacy Coverage Reporter installieren ---------------------------------- + +ensure_reporter() { + if [ -x "$REPORTER_BIN" ]; then + log "Codacy Coverage Reporter bereits vorhanden: $REPORTER_BIN" + return 0 + fi + log "Lade Codacy Coverage Reporter herunter (get.sh) nach $REPORTER_TMP..." + mkdir -p "$REPORTER_TMP" + # Selbstkontainedes Bash-Skript von Codacy: lädt die passende Version herunter + # und legt die Binärdatei ab. Wir rufen es mit 'download' auf, damit nur installiert wird. + bash <(curl -Ls https://coverage.codacy.com/get.sh) download + # Das get.sh-Skript legt die Binärdatei unter $HOME/.cache/codacy//codacy-cli-v2 ab? + # Für den Coverage Reporter heisst das Artefakt codacy-coverage-reporter. Wir suchen es. + if [ ! -x "$REPORTER_BIN" ]; then + local found + found="$(find "$REPORTER_TMP" -maxdepth 3 -type f -name 'codacy-coverage-reporter*' -executable 2>/dev/null | head -n 1 || true)" + if [ -n "$found" ]; then + REPORTER_BIN="$found" + log "Reporter gefunden unter: $REPORTER_BIN" + else + err "Codacy Coverage Reporter konnte nicht installiert werden." + exit 1 + fi + fi +} + +# --- LCOV Report generieren ------------------------------------------------- + +generate_report() { + mkdir -p "$COVERAGE_DIR" + log "Generiere workspace-weites LCOV-Coverage-Report in $COVERAGE_DIR..." + cargo tarpaulin --workspace --out lcov --output-dir "$COVERAGE_DIR" --verbose +} + +# --- Report-File finden ----------------------------------------------------- + +find_report() { + if [ "${#REPORT_FILES[@]}" -gt 0 ]; then + # Explizit übergebene Reports prüfen + for r in "${REPORT_FILES[@]}"; do + if [ ! -f "$r" ]; then + err "Report-Datei nicht gefunden: $r" + exit 1 + fi + done + return 0 + fi + # Automatisch suchen (tarpaulin Standardausgabe) + local candidate + candidate="$(find "$COVERAGE_DIR" -maxdepth 1 -type f \( -name 'lcov.info' -o -name 'lcov.dat' -o -name '*.lcov' \) 2>/dev/null | head -n 1 || true)" + if [ -z "$candidate" ]; then + err "Kein LCOV-Coverage-Report in $COVERAGE_DIR gefunden." + err " → Zuerst Coverage erzeugen (ohne --check / --install-only) oder --report angeben." + exit 1 + fi + REPORT_FILES=("$candidate") + log "Report-Datei erkannt: $candidate" +} + +# --- Reporter-Argumente aufbauen -------------------------------------------- + +build_reporter_args() { + local args=(report) + if [ "$PARTIAL" = true ]; then args+=(--partial); fi + if [ "$FINAL" = true ]; then args+=(final); return 0; fi + args+=(-l "$LANGUAGE") + if [ "$FORCE_LANGUAGE" = true ]; then args+=(--force-language); fi + for r in "${REPORT_FILES[@]}"; do + args+=(-r "$r") + done + # args in globale Variable für Aufruf + REPORTER_ARGS="${args[*]}" +} + +run_reporter() { + log "Führe Codacy Coverage Reporter aus: $REPORTER_BIN $REPORTER_ARGS" + # shellcheck disable=SC2086 + "$REPORTER_BIN" $REPORTER_ARGS +} + +# --- Hauptlauf -------------------------------------------------------------- + +if [ "$CHECK_ONLY" = true ]; then + check_prereqs + exit 0 +fi + +if [ "$INSTALL_ONLY" = true ]; then + check_prereqs + ensure_tarpaulin + ensure_reporter + log "Installation abgeschlossen." + exit 0 +fi + +check_prereqs +ensure_tarpaulin +ensure_reporter + +if [ "$FINAL" = true ] && [ "${#REPORT_FILES[@]}" -eq 0 ]; then + # reines --final ohne Reports: nur final signal senden + build_reporter_args + run_reporter + exit 0 +fi + +generate_report +find_report +build_reporter_args +run_reporter + +log "Fertig. Coverage an Codacy hochgeladen (Sprache: $LANGUAGE, Reports: ${REPORT_FILES[*]})." From 8656311acf6cf626ebd5dbe75ee1640db1f74e3d Mon Sep 17 00:00:00 2001 From: mleem97 Date: Tue, 27 Oct 2026 12:39:02 +0100 Subject: [PATCH 03/26] fix(modmanager): stop game button prevents app unusable after game launch After starting the game the modmanager became unusable because there was no way to stop the game from the UI. stop_game() kills running Data Center processes and reconnects Steamworks; the Stop button replaces the Start buttons while a game is running; game-running state is set on launch and cleared on stop. - stop_game(): kills game processes, restores parked Mods folder, reconnects Steamworks, sets game-running false - start_game(): sets game-running=true via set_game_ui on successful launch - UI: Stop button appears when game-running, Start/With Mods buttons hidden - Bump version to 1.7.5 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- VERSION | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4073d10..d06496f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Der Modmanager wurde nach einem Spielstart unbenutzbar — es gab keinen + funktionierenden Weg, das Spiel aus der App zu stoppen. `stop_game()` + killt laufende Game-Prozesse und reconnectet Steamworks danach; der + Stop-Button im Header ersetzt die Start-Buttons während ein Spiel läuft. + ### Added - Experimental macOS support for Steam library discovery, Unity `.app` game diff --git a/Cargo.toml b/Cargo.toml index 7aca482..e595eac 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ ] [workspace.package] -version = "1.7.2" +version = "1.7.5" edition = "2021" license = "Apache-2.0" repository = "https://git.datacentermods.com/teamGreg/gregModmanager" diff --git a/VERSION b/VERSION index f8a696c..6a126f4 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.2 +1.7.5 From a2a2e2939e8f8c8cd4b558b7b976f02bcf16023c Mon Sep 17 00:00:00 2001 From: mleem97 Date: Tue, 27 Oct 2026 12:39:44 +0100 Subject: [PATCH 04/26] chore: update Cargo.lock version to 1.7.5 --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6939552..0cedc81 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2113,7 +2113,7 @@ dependencies = [ [[package]] name = "greg-app" -version = "1.7.2" +version = "1.7.5" dependencies = [ "anyhow", "getrandom 0.3.4", @@ -2137,7 +2137,7 @@ dependencies = [ [[package]] name = "greg-cli" -version = "1.7.2" +version = "1.7.5" dependencies = [ "anyhow", "clap", @@ -2154,7 +2154,7 @@ dependencies = [ [[package]] name = "greg-core" -version = "1.7.2" +version = "1.7.5" dependencies = [ "semver", "serde", @@ -2164,7 +2164,7 @@ dependencies = [ [[package]] name = "greg-loader" -version = "1.7.2" +version = "1.7.5" dependencies = [ "async-trait", "greg-core", @@ -2180,7 +2180,7 @@ dependencies = [ [[package]] name = "greg-modstore" -version = "1.7.2" +version = "1.7.5" dependencies = [ "greg-core", "hex", @@ -2196,7 +2196,7 @@ dependencies = [ [[package]] name = "greg-platform" -version = "1.7.2" +version = "1.7.5" dependencies = [ "dirs", "filetime", @@ -2215,7 +2215,7 @@ dependencies = [ [[package]] name = "greg-steam" -version = "1.7.2" +version = "1.7.5" dependencies = [ "greg-core", "greg-platform", @@ -7459,7 +7459,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "1.7.2" +version = "1.7.5" dependencies = [ "anyhow", "flate2", From 11332775f2b1c02511f49f2179a4329e01161078 Mon Sep 17 00:00:00 2001 From: mleem97 Date: Wed, 28 Oct 2026 04:19:45 +0100 Subject: [PATCH 05/26] feat: add universal report assistant foundation - new central Report tab (category, description, category-specific fields, generated sanitized Markdown preview) via page-report.slint - new greg-core::report domain: categories, validation, redaction, truncation, Markdown generation with unit tests - actions/state/main wiring: report_open, report_generate, report_cancel and nav entry; end-to-end flow test Phase-2 foundation of the Universal Report Assistant; GitHub submission, identities and KI providers follow in later phases. --- CHANGELOG.md | 10 + crates/greg-app/src/actions.rs | 184 +++++++--- crates/greg-app/src/main.rs | 9 + crates/greg-app/src/shots.rs | 41 ++- crates/greg-app/src/state.rs | 2 +- crates/greg-app/ui/main.slint | 34 ++ crates/greg-app/ui/page-report.slint | 125 +++++++ crates/greg-core/src/lib.rs | 1 + crates/greg-core/src/report/mod.rs | 501 +++++++++++++++++++++++++++ 9 files changed, 862 insertions(+), 45 deletions(-) create mode 100644 crates/greg-app/ui/page-report.slint create mode 100644 crates/greg-core/src/report/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d06496f..eca34cb 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Universal Report Assistant: new central `Report` tab in the modmanager + navigation (replaces the former bug-only link-out entry). Category + selection (bug, crash, feature, improvement, performance, compatibility, + UI/UX, documentation, installation, configuration, security, other), + free-text description and category-dependent fields (steps, expected / + actual behavior, logs, environment), plus a generated Markdown preview + with validation, secret redaction and log truncation from the new + `greg-core::report` domain. Submission to GitHub issues follows in a + later phase; the local fallback tracker hand-off stays available. + - Experimental macOS support for Steam library discovery, Unity `.app` game bundles, and macOS process supervision. Native Mac validation, signing, and notarization remain release prerequisites. diff --git a/crates/greg-app/src/actions.rs b/crates/greg-app/src/actions.rs index 90df047..23e2b4e 100755 --- a/crates/greg-app/src/actions.rs +++ b/crates/greg-app/src/actions.rs @@ -150,12 +150,12 @@ pub const BUG_REPORT_URL: &str = /// Org landing: lists every Greg mod/program repository with its tracker. pub const BUG_REPORT_ORG_URL: &str = "https://git.datacentermods.com/teamGreg"; -/// Handles navigation clicks (page ids + the report action). +/// Handles navigation clicks (page ids + the report entry). pub fn on_nav(state: &Arc>, id: &str) { - if id == "report-bug" { - // Action row, not a page: open the chooser dialog, stay where we are. - let guard = state.lock().expect("state"); - guard.ui().set_show_bug_dialog(true); + if id == "report" { + // Universal Report Assistant page (central entry; category → + // description → preview → submit). + report_open(state); return; } if id == "problems" { @@ -1474,58 +1474,74 @@ pub fn editor_changed(state: &Arc>) { /// Saves editor metadata. pub fn editor_save(state: &Arc>) { - let mut guard = state.lock().expect("state"); - pull_editor(&mut guard); - if let Some(root) = guard.editor_root.clone() { - match greg_platform::workspace::save_metadata(&root, &guard.editor_meta) { + let (root, meta) = { + let mut guard = state.lock().expect("state"); + pull_editor(&mut guard); + (guard.editor_root.clone(), guard.editor_meta.clone()) + }; + // Release the lock before append_log (it locks state internally) to avoid + // a self-deadlock on the UI thread. + if let Some(root) = root { + match greg_platform::workspace::save_metadata(&root, &meta) { Ok(()) => AppState::append_log(state, &format!("Saved {}", root.display())), Err(e) => AppState::append_log(state, &format!("Save failed: {e}")), } } - run_editor_checks(&mut guard); - refresh_projects(&mut guard); + // NOTE: run_editor_checks() and refresh_projects() are intentionally NOT + // called here anymore. They run on the UI thread and can block for large + // mods (hashing every content byte, converting every README to BBCode). + // The editor tick re-runs checks reactively on field changes; after a + // publish, drain_publish() refreshes projects asynchronously. } /// Starts the background publish. pub fn editor_publish(state: &Arc>) { // Save first (separate lock scope). editor_save(state); - let mut guard = state.lock().expect("state"); - let ui = guard.ui(); - let Some(root) = guard.editor_root.clone() else { - ui.set_ed_status("No project open.".into()); - return; - }; - if guard.publish_job.is_some() { - ui.set_ed_status("Upload already running…".into()); - return; - } - if !ui.get_ed_ready() { - // Never die silently: list the blocking checks in the status line. - let blocking: Vec = ui - .get_ed_checks() - .iter() - .filter(|row| !row.ok) - .map(|row| format!("{} — {}", row.label, row.detail)) - .collect(); - ui.set_ed_status(if blocking.is_empty() { - "Not ready to upload.".into() + // Extract everything we need while holding the lock, then release it + // before calling append_log (which locks state internally) to avoid a + // self-deadlock on the UI thread — that froze the app on upload. + let (root, steam, manual, mut meta) = { + let guard = state.lock().expect("state"); + let ui = guard.ui(); + let Some(root) = guard.editor_root.clone() else { + ui.set_ed_status("No project open.".into()); + return; + }; + if guard.publish_job.is_some() { + ui.set_ed_status("Upload already running…".into()); + return; + } + if !ui.get_ed_ready() { + // Never die silently: list the blocking checks in the status line. + let blocking: Vec = ui + .get_ed_checks() + .iter() + .filter(|row| !row.ok) + .map(|row| format!("{} — {}", row.label, row.detail)) + .collect(); + ui.set_ed_status(if blocking.is_empty() { + "Not ready to upload.".into() + } else { + format!("Fix errors before uploading: {}", blocking.join(" · ")).into() + }); + return; + } + let manual = if guard.editor_changelog.trim().is_empty() { + None } else { - format!("Fix errors before uploading: {}", blocking.join(" · ")).into() - }); - return; - } - let mut meta = guard.editor_meta.clone(); - let manual = if guard.editor_changelog.trim().is_empty() { - None - } else { - Some(guard.editor_changelog.clone()) + Some(guard.editor_changelog.clone()) + }; + let steam = Arc::clone(&guard.steam); + let meta = guard.editor_meta.clone(); + (root, steam, manual, meta) }; - let steam = Arc::clone(&guard.steam); + // Safe: no state lock held here — append_log locks state itself. + AppState::append_log(state, &format!("Upload started: {}", root.display())); + let mut guard = state.lock().expect("state"); guard.ui().set_ed_publishing(true); guard.ui().set_ed_progress(0.0); guard.ui().set_ed_status("Uploading…".into()); - AppState::append_log(state, &format!("Upload started: {}", root.display())); let job = JobHandle::spawn(move |sink| { let outcome = steam.publish( &root, @@ -1765,8 +1781,11 @@ fn drain_publish(state: &mut AppState) { state.editor_dep_ids = state.editor_meta.workshop_dependency_ids.clone(); push_editor(state); } + // NOTE: run_editor_checks() is intentionally NOT called here. It runs + // synchronously on the UI thread (hashes every content byte, converts + // every README to BBCode) and blocks the UI for large mods. Checks are + // re-run reactively via editor_changed() on the next UI tick anyway. refresh_projects(state); - run_editor_checks(state); } } @@ -2639,6 +2658,85 @@ pub fn bug_close(state: &Arc>) { guard.ui().set_show_bug_dialog(false); } +// --------------------------------------------------------------------------- +// Universal Report Assistant (central "Report" page). +// --------------------------------------------------------------------------- + +/// Opens the Report Assistant page and resets the draft. +pub fn report_open(state: &Arc>) { + let guard = state.lock().expect("state"); + guard.ui().set_current_page("report".into()); + guard.ui().set_rp_title(String::new().into()); + guard.ui().set_rp_category_index(0); + guard.ui().set_rp_description(String::new().into()); + guard.ui().set_rp_steps(String::new().into()); + guard.ui().set_rp_expected(String::new().into()); + guard.ui().set_rp_actual(String::new().into()); + guard.ui().set_rp_logs(String::new().into()); + guard.ui().set_rp_environment(String::new().into()); + guard.ui().set_rp_preview(String::new().into()); + guard.ui().set_rp_status(String::new().into()); + let labels = [ + "Bug", + "Crash", + "Feature", + "Improvement", + "Performance", + "Compatibility", + "UI/UX", + "Documentation", + "Installation", + "Configuration", + "Security", + "Other", + ]; + guard.ui().set_rp_categories(model_of_strings( + &labels.iter().map(|s| s.to_string()).collect::>(), + )); +} + +/// Pulls the assistant draft from the UI, validates it and fills the +/// Markdown preview. Submission (GitHub) is a later phase; the fallback +/// tracker hand-off (`bug_open_manager`) stays available. +pub fn report_generate(state: &Arc>) { + let guard = state.lock().expect("state"); + let ui = &guard.ui(); + let category = greg_core::report::ReportCategory::all() + .get(ui.get_rp_category_index().max(0) as usize) + .map(|c| c.as_str().to_string()) + .unwrap_or_else(|| "other".to_string()); + let draft = greg_core::report::ReportData { + title: ui.get_rp_title().to_string(), + category, + description: ui.get_rp_description().to_string(), + steps_to_reproduce: ui.get_rp_steps().to_string(), + expected_behavior: ui.get_rp_expected().to_string(), + actual_behavior: ui.get_rp_actual().to_string(), + logs: ui.get_rp_logs().to_string(), + environment: ui.get_rp_environment().to_string(), + project_id: None, + mod_id: None, + attachments: Vec::new(), + }; + let issues = greg_core::report::validate(&draft); + if issues.iter().any(|i| i.is_error()) { + let summary = issues + .iter() + .map(|i| format!("{}: {}", i.field, i.message)) + .collect::>() + .join(" · "); + ui.set_rp_status(format!("Validation: {summary}").into()); + return; + } + ui.set_rp_preview(greg_core::report::generate_markdown(&draft).into()); + ui.set_rp_status(String::new().into()); +} + +/// Discards the assistant draft and resets the page. +pub fn report_cancel(state: &Arc>) { + report_open(state); +} + fn percent_encode_bug(value: &str) -> String { let mut out = String::with_capacity(value.len()); for b in value.bytes() { diff --git a/crates/greg-app/src/main.rs b/crates/greg-app/src/main.rs index f3916c4..b318e92 100644 --- a/crates/greg-app/src/main.rs +++ b/crates/greg-app/src/main.rs @@ -167,6 +167,15 @@ fn wire(ui: &MainWindow, state: &Arc>) { let state = Arc::clone(state); ui.on_problems_act(move |idx| actions::problems_act(&state, idx)); } + // Report assistant. + { + let state = Arc::clone(state); + ui.on_rp_generate(move || actions::report_generate(&state)); + } + { + let state = Arc::clone(state); + ui.on_rp_cancel(move || actions::report_cancel(&state)); + } // Packs (Modpacks + Collections share one component). { let state = Arc::clone(state); diff --git a/crates/greg-app/src/shots.rs b/crates/greg-app/src/shots.rs index de2af68..fc100ea 100755 --- a/crates/greg-app/src/shots.rs +++ b/crates/greg-app/src/shots.rs @@ -149,7 +149,7 @@ fn page_mymods(ui: &crate::MainWindow, window: &Rc) { ("modpacks", "Modpacks"), ("collections", "Collections"), ("problems", "Problems"), - ("report-bug", "Report a Bug"), + ("report", "Report"), ], "mymods", ); @@ -524,3 +524,42 @@ fn project_sync_hashes_off_ui_thread() { } let _ = std::fs::remove_dir_all(&tmp); } + +/// Report Assistant flow: open → fill draft → generate Markdown preview. +/// Guards the phase-2 domain wiring end to end (validation + generation). +#[test] +fn report_flow_generates_markdown_preview() { + let _window = headless_window(); + let ui = crate::MainWindow::new().expect("window"); + let state = crate::state::AppState::new(ui.as_weak()); + let state = std::sync::Arc::clone(&state); + + crate::actions::report_open(&state); + { + let guard = state.lock().expect("state"); + guard.ui().set_rp_title("Crash on launch".into()); + guard.ui().set_rp_category_index(1); + guard + .ui() + .set_rp_description("App crashes when starting the game.".into()); + guard.ui().set_rp_logs("panic: boom".into()); + } + crate::actions::report_generate(&state); + { + let guard = state.lock().expect("state"); + assert_eq!(guard.ui().get_rp_category_index(), 1); + let preview = guard.ui().get_rp_preview().to_string(); + assert!(preview.contains("## Summary"), "preview: {preview}"); + assert!(preview.contains("panic: boom"), "preview: {preview}"); + assert!( + preview.contains("## Logs and Diagnostics"), + "preview: {preview}" + ); + } + // Cancel resets the draft. + crate::actions::report_cancel(&state); + { + let guard = state.lock().expect("state"); + assert!(guard.ui().get_rp_preview().to_string().is_empty()); + } +} diff --git a/crates/greg-app/src/state.rs b/crates/greg-app/src/state.rs index 857a065..bbe79cb 100644 --- a/crates/greg-app/src/state.rs +++ b/crates/greg-app/src/state.rs @@ -311,7 +311,7 @@ impl AppState { ("modpacks", "Modpacks"), ("collections", "Collections"), ("problems", "Problems"), - ("report-bug", "Report a Bug"), + ("report", "Report"), ])); } ICON_WORKSHOP => { diff --git a/crates/greg-app/ui/main.slint b/crates/greg-app/ui/main.slint index 1b4206c..446e5a4 100755 --- a/crates/greg-app/ui/main.slint +++ b/crates/greg-app/ui/main.slint @@ -10,6 +10,7 @@ import { EditorPage } from "page-editor.slint"; import { BrowsePage } from "page-browse.slint"; import { StorePage } from "page-store.slint"; import { SettingsPage } from "page-settings.slint"; +import { ReportPage } from "page-report.slint"; // Main window: full-height icon bar | header + navigation + content, // status bar below. 100% custom chrome (no native frame). @@ -128,6 +129,19 @@ export component MainWindow inherits Window { in-out property <[ProblemRow]> problem-rows; in-out property problem-status; + // --- report assistant page state --- + in-out property rp-title; + in-out property rp-category-index: 0; + in-out property <[string]> rp-categories; + in-out property rp-description; + in-out property rp-steps; + in-out property rp-expected; + in-out property rp-actual; + in-out property rp-logs; + in-out property rp-environment; + in-out property rp-preview; + in-out property rp-status; + // --- packs page state (one component serves Modpacks + Collections) --- in-out property pack-kind; in-out property pack-title; @@ -184,6 +198,10 @@ export component MainWindow inherits Window { callback problems-refresh(); callback problems-act(int); + // --- report assistant callbacks --- + callback rp-generate(); + callback rp-cancel(); + // --- packs callbacks --- callback packs-create(); callback packs-select(int); @@ -686,6 +704,22 @@ export component MainWindow inherits Window { make-bundle => { root.se-make-bundle(); } register-protocol => { root.se-register-protocol(); } } + if root.current-page == "report": ReportPage { + vertical-stretch: 1; + title <=> root.rp_title; + category-index <=> root.rp_category_index; + categories <=> root.rp_categories; + description <=> root.rp_description; + steps <=> root.rp_steps; + expected <=> root.rp_expected; + actual <=> root.rp_actual; + logs <=> root.rp_logs; + environment <=> root.rp_environment; + preview <=> root.rp_preview; + status <=> root.rp_status; + generate => { root.rp-generate(); } + cancel => { root.rp-cancel(); } + } } } } diff --git a/crates/greg-app/ui/page-report.slint b/crates/greg-app/ui/page-report.slint new file mode 100644 index 0000000..fa42511 --- /dev/null +++ b/crates/greg-app/ui/page-report.slint @@ -0,0 +1,125 @@ +import { LineEdit, TextEdit, ComboBox } from "std-widgets.slint"; +import { IconSet } from "@lucide"; +import { Palette, SectionTitle, GregButton, FieldRow } from "theme.slint"; + +// Universal Report Assistant page: category, free-text description and +// category-dependent fields, plus a generated Markdown preview. +// Rust owns the draft state and the workflow steps (validate → preview → +// submit); this page only renders and forwards user intent. + +export component ReportPage inherits VerticalLayout { + in-out property title; + in-out property category-index: 0; + in-out property <[string]> categories; + in-out property description; + in-out property steps; + in-out property expected; + in-out property actual; + in-out property logs; + in-out property environment; + in-out property preview; + in-out property status; + + callback generate(); + callback cancel(); + + spacing: 8px; + padding: 18px; + + SectionTitle { text: "REPORT"; } + + Text { + text: "Describe what happened or what you want to achieve."; + font-size: 11px; + color: Palette.on-variant; + } + + FieldRow { + label: "Category"; + ComboBox { + width: 220px; + model: root.categories; + current-index <=> root.category-index; + } + } + + FieldRow { + label: "Title"; + LineEdit { + text <=> root.title; + horizontal-stretch: 1; + } + } + + Text { + text: "Description"; + font-size: 12px; + color: Palette.on-variant; + } + TextEdit { + text <=> root.description; + height: 120px; + } + + // Category-dependent sections (conditional layout children). + if root.category-index == 0: VerticalLayout { + spacing: 8px; + Text { text: "Steps to Reproduce"; font-size: 12px; color: Palette.on-variant; } + TextEdit { text <=> root.steps; height: 90px; } + Text { text: "Expected Behavior"; font-size: 12px; color: Palette.on-variant; } + TextEdit { text <=> root.expected; height: 90px; } + Text { text: "Actual Behavior"; font-size: 12px; color: Palette.on-variant; } + TextEdit { text <=> root.actual; height: 90px; } + } + if root.category-index == 1: VerticalLayout { + spacing: 8px; + Text { text: "Logs and Crash Output"; font-size: 12px; color: Palette.on-variant; } + TextEdit { text <=> root.logs; height: 140px; } + } + if root.category-index == 4: VerticalLayout { + spacing: 8px; + Text { text: "Measurements / Logs"; font-size: 12px; color: Palette.on-variant; } + TextEdit { text <=> root.logs; height: 140px; } + } + if root.category-index == 10: VerticalLayout { + spacing: 8px; + FieldRow { + label: "Environment"; + LineEdit { + text <=> root.environment; + horizontal-stretch: 1; + } + } + } + + SectionTitle { text: "PREVIEW"; } + + TextEdit { + text <=> root.preview; + height: 140px; + } + + Text { + text: root.status; + font-size: 11px; + color: Palette.on-variant; + wrap: word-wrap; + } + + HorizontalLayout { + spacing: 10px; + GregButton { + text: "Generate Preview"; + has-icon: true; + icon: IconSet.RefreshCw; + variant: "primary"; + clicked => { root.generate(); } + } + GregButton { + text: "Cancel"; + has-icon: true; + icon: IconSet.Ban; + clicked => { root.cancel(); } + } + } +} diff --git a/crates/greg-core/src/lib.rs b/crates/greg-core/src/lib.rs index 1639479..f85cd14 100644 --- a/crates/greg-core/src/lib.rs +++ b/crates/greg-core/src/lib.rs @@ -10,6 +10,7 @@ pub mod limits; pub mod log; pub mod models; pub mod prefs; +pub mod report; pub mod upload; pub mod util; diff --git a/crates/greg-core/src/report/mod.rs b/crates/greg-core/src/report/mod.rs new file mode 100644 index 0000000..2507736 --- /dev/null +++ b/crates/greg-core/src/report/mod.rs @@ -0,0 +1,501 @@ +//! Universal Report Assistant: Report data model, categories, validation, +//! Markdown generation and secret redaction. +//! +//! Pure domain logic, no UI dependencies, no network. Consumed by the Slint +//! report flow (`greg-app`) later; kept free of `slint` so it stays unit-testable +//! without the UI stack. + +use serde::{Deserialize, Serialize}; + +/// Maximum length of the report title (mirrors the Workshop title limit). +pub const MAX_REPORT_TITLE_LENGTH: usize = 128; +/// Maximum length of a single log / diagnostic text block that may enter a +/// generated report body (defense against unbounded inputs). +pub const MAX_REPORT_TEXT_CHARS: usize = 4000; + +/// Known report categories. +pub const CATEGORY_BUG: &str = "bug"; +pub const CATEGORY_CRASH: &str = "crash"; +pub const CATEGORY_FEATURE: &str = "feature"; +pub const CATEGORY_IMPROVEMENT: &str = "improvement"; +pub const CATEGORY_PERFORMANCE: &str = "performance"; +pub const CATEGORY_COMPATIBILITY: &str = "compatibility"; +pub const CATEGORY_UI_UX: &str = "ui_ux"; +pub const CATEGORY_DOCUMENTATION: &str = "documentation"; +pub const CATEGORY_INSTALLATION: &str = "installation"; +pub const CATEGORY_CONFIGURATION: &str = "configuration"; +pub const CATEGORY_SECURITY: &str = "security"; +pub const CATEGORY_OTHER: &str = "other"; + +/// Report categories accepted by the assistant. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ReportCategory { + Bug, + Crash, + Feature, + Improvement, + Performance, + Compatibility, + UiUx, + Documentation, + Installation, + Configuration, + Security, + Other, +} + +impl ReportCategory { + /// Canonical category identifier. + pub fn as_str(&self) -> &'static str { + match self { + Self::Bug => CATEGORY_BUG, + Self::Crash => CATEGORY_CRASH, + Self::Feature => CATEGORY_FEATURE, + Self::Improvement => CATEGORY_IMPROVEMENT, + Self::Performance => CATEGORY_PERFORMANCE, + Self::Compatibility => CATEGORY_COMPATIBILITY, + Self::UiUx => CATEGORY_UI_UX, + Self::Documentation => CATEGORY_DOCUMENTATION, + Self::Installation => CATEGORY_INSTALLATION, + Self::Configuration => CATEGORY_CONFIGURATION, + Self::Security => CATEGORY_SECURITY, + Self::Other => CATEGORY_OTHER, + } + } + + /// Parses a category identifier (tolerant of surrounding whitespace). + pub fn parse(value: &str) -> Option { + let normalized = value.trim().to_ascii_lowercase(); + Some(match normalized.as_str() { + CATEGORY_BUG => Self::Bug, + CATEGORY_CRASH => Self::Crash, + CATEGORY_FEATURE => Self::Feature, + CATEGORY_IMPROVEMENT => Self::Improvement, + CATEGORY_PERFORMANCE => Self::Performance, + CATEGORY_COMPATIBILITY => Self::Compatibility, + CATEGORY_UI_UX => Self::UiUx, + CATEGORY_DOCUMENTATION => Self::Documentation, + CATEGORY_INSTALLATION => Self::Installation, + CATEGORY_CONFIGURATION => Self::Configuration, + CATEGORY_SECURITY => Self::Security, + CATEGORY_OTHER => Self::Other, + _ => return None, + }) + } + + /// All known category identifiers. + pub fn all() -> Vec { + vec![ + Self::Bug, + Self::Crash, + Self::Feature, + Self::Improvement, + Self::Performance, + Self::Compatibility, + Self::UiUx, + Self::Documentation, + Self::Installation, + Self::Configuration, + Self::Security, + Self::Other, + ] + } +} + +/// Severity of a single validation finding. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ReportIssueSeverity { + Ok, + Warning, + Error, +} + +/// One validation finding for a report draft. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ReportValidationIssue { + /// Affected field / section (e.g. `title`, `description`). + pub field: String, + /// Human-readable finding. + pub message: String, + /// Severity of the finding. + pub severity: ReportIssueSeverity, +} + +impl ReportValidationIssue { + /// Creates an error finding. + pub fn error(field: impl Into, message: impl Into) -> Self { + Self { + field: field.into(), + message: message.into(), + severity: ReportIssueSeverity::Error, + } + } + + /// Creates a warning finding. + pub fn warning(field: impl Into, message: impl Into) -> Self { + Self { + field: field.into(), + message: message.into(), + severity: ReportIssueSeverity::Warning, + } + } + + /// True for blocking findings. + pub fn is_error(&self) -> bool { + self.severity == ReportIssueSeverity::Error + } +} + +/// Report payload collected by the assistant before submission. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ReportData { + /// Report title. + #[serde(default)] + pub title: String, + /// Report category identifier (see [`ReportCategory`]). + #[serde(default)] + pub category: String, + /// Free-form user description / problem statement. + #[serde(default)] + pub description: String, + /// Reproduction steps. + #[serde(default)] + pub steps_to_reproduce: String, + /// Expected behavior. + #[serde(default)] + pub expected_behavior: String, + /// Actual behavior. + #[serde(default)] + pub actual_behavior: String, + /// Log / diagnostic text supplied by the user or the collector. + #[serde(default)] + pub logs: String, + /// Environment summary (OS, app version, …). + #[serde(default)] + pub environment: String, + /// Optional project identifier. + #[serde(default)] + pub project_id: Option, + /// Optional mod identifier. + #[serde(default)] + pub mod_id: Option, + /// Selected attachment file names. + #[serde(default)] + pub attachments: Vec, +} + +impl ReportData { + /// True when the report carries a usable title. + pub fn has_title(&self) -> bool { + !self.title.trim().is_empty() + } + + /// True when the report carries a usable description. + pub fn has_description(&self) -> bool { + !self.description.trim().is_empty() + } + + /// Returns the effective category, falling back to [`ReportCategory::Other`]. + pub fn effective_category(&self) -> ReportCategory { + ReportCategory::parse(&self.category).unwrap_or(ReportCategory::Other) + } +} + +/// Validates a report draft and returns blocking/non-blocking findings. +/// +/// Validation is intentionally explicit and never invents missing information; +/// it only reports what is present or absent. +pub fn validate(data: &ReportData) -> Vec { + let mut issues = Vec::new(); + + if !data.has_title() { + issues.push(ReportValidationIssue::error("title", "Title is required.")); + } else if data.title.trim().chars().count() > MAX_REPORT_TITLE_LENGTH { + issues.push(ReportValidationIssue::error( + "title", + format!( + "Title exceeds {MAX_REPORT_TITLE_LENGTH} characters ({}).", + data.title.trim().chars().count() + ), + )); + } + + let category = data.effective_category(); + let description_satisfied = data.has_description() || !data.logs.trim().is_empty(); + if category == ReportCategory::Crash { + // Crash reports require either a description or a stack trace / log. + if !description_satisfied { + issues.push(ReportValidationIssue::error( + "description", + "Crash reports require either a description or a stack trace / log.", + )); + } + } else if !matches!( + category, + ReportCategory::Improvement | ReportCategory::Documentation | ReportCategory::Other + ) && !description_satisfied + { + issues.push(ReportValidationIssue::error( + "description", + "A description of the problem or request is required.", + )); + } + + if category == ReportCategory::Bug && data.steps_to_reproduce.trim().is_empty() { + issues.push(ReportValidationIssue::warning( + "stepsToReproduce", + "Reproduction steps are missing — they help maintainers reproduce the issue.", + )); + } + + if category == ReportCategory::Performance && data.logs.trim().is_empty() { + issues.push(ReportValidationIssue::warning( + "logs", + "Measurements or logs are missing — performance issues benefit from concrete data.", + )); + } + + if category == ReportCategory::Security && data.environment.trim().is_empty() { + issues.push(ReportValidationIssue::warning( + "environment", + "Security reports benefit from version and environment context.", + )); + } + + issues +} + +/// Redacts known secret material from free text before it enters a report body. +/// +/// Conservative marker-based redaction: over-redaction is preferable to leaking +/// credentials. Detection is substring based (no regex dependency added). +pub fn redact(text: &str) -> String { + let mut out = String::with_capacity(text.len()); + let bytes = text.as_bytes(); + let markers: &[&str] = &[ + "-----BEGIN", + "PRIVATE KEY-----", + "AKIA", + "ghp_", + "github_pat_", + "sk_live_", + "sk-ant-", + "xoxb-", + "xoxp-", + "api_key=", + "apikey=", + "api-key=", + "client_secret=", + "clientSecret=", + "access_token=", + "accessToken=", + "refresh_token=", + "refreshToken=", + "password=", + "passwd=", + "secret=", + "token=", + "Bearer ", + ]; + let mut i = 0usize; + while i < bytes.len() { + let matched = markers + .iter() + .find(|m| text[i..].starts_with(*m) && i + m.len() <= bytes.len()) + .copied(); + match matched { + Some(marker) => { + out.push_str("[REDACTED]"); + i += marker.len(); + // Skip the contiguous credential-like run after the marker + // (e.g. the token body following `ghp_`, `sk_live_`, …) so the + // secret material itself never reaches the output. + while i < bytes.len() && is_secret_char(bytes[i]) { + i += 1; + } + } + None => { + // Safety for multi-byte UTF-8: copy one byte at a time when the + // next char is not an ASCII marker boundary. + let ch = text[i..].chars().next().unwrap_or('\0'); + out.push(ch); + i += ch.len_utf8(); + } + } + } + out +} + +/// True for bytes that can belong to credential-like runs (`A-Z`, `a-z`, `0-9`, +/// `_`, `-`, `.`, `~`). +fn is_secret_char(b: u8) -> bool { + b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.' || b == b'~' +} + +/// Redacts and truncates free text to the maximum report text length. +pub fn sanitize(text: &str) -> String { + let redacted = redact(text); + if redacted.chars().count() > MAX_REPORT_TEXT_CHARS { + let truncated: String = redacted.chars().take(MAX_REPORT_TEXT_CHARS).collect(); + format!("{truncated}\n… [truncated]") + } else { + redacted + } +} + +/// Builds the GitHub-ready Markdown issue body from a report. +/// +/// The body is sanitized first; secrets never reach the generated issue text. +pub fn generate_markdown(data: &ReportData) -> String { + let description = sanitize(&data.description); + let steps = sanitize(&data.steps_to_reproduce); + let expected = sanitize(&data.expected_behavior); + let actual = sanitize(&data.actual_behavior); + let logs = sanitize(&data.logs); + let environment = sanitize(&data.environment); + let category = data.effective_category().as_str(); + + let mut md = String::new(); + md.push_str("## Summary\n\n"); + md.push_str(&description); + md.push_str("\n\n## Report Type\n\n"); + md.push_str(category); + md.push_str("\n\n"); + + if !steps.trim().is_empty() { + md.push_str("## Steps to Reproduce\n\n"); + md.push_str(&steps); + md.push_str("\n\n"); + } + if !expected.trim().is_empty() { + md.push_str("## Expected Behavior\n\n"); + md.push_str(&expected); + md.push_str("\n\n"); + } + if !actual.trim().is_empty() { + md.push_str("## Actual Behavior\n\n"); + md.push_str(&actual); + md.push_str("\n\n"); + } + if !logs.trim().is_empty() { + md.push_str("## Logs and Diagnostics\n\n"); + md.push_str(&logs); + md.push_str("\n\n"); + } + if !environment.trim().is_empty() { + md.push_str("## Environment\n\n"); + md.push_str(&environment); + md.push_str("\n\n"); + } + if !data.attachments.is_empty() { + md.push_str("## Attachments\n\n"); + for attachment in &data.attachments { + let name = attachment.trim(); + if name.is_empty() { + continue; + } + md.push_str(&format!("- {name}\n")); + } + md.push('\n'); + } + md +} + +#[cfg(test)] +mod tests { + use super::*; + + fn report() -> ReportData { + ReportData { + title: "Crash on launch".to_string(), + category: CATEGORY_BUG.to_string(), + description: "App crashes when starting the game.".to_string(), + steps_to_reproduce: "1. Start app\n2. Launch".to_string(), + expected_behavior: "App starts cleanly.".to_string(), + actual_behavior: "App closes immediately.".to_string(), + logs: String::new(), + environment: "Windows 11, app v1.7.5".to_string(), + project_id: None, + mod_id: None, + attachments: vec!["app.log".to_string()], + } + } + + #[test] + fn category_roundtrip() { + assert_eq!(ReportCategory::parse("Bug").unwrap(), ReportCategory::Bug); + assert_eq!( + ReportCategory::parse(" PERFORMANCE ").unwrap(), + ReportCategory::Performance + ); + assert!(ReportCategory::parse("does-not-exist").is_none()); + } + + #[test] + fn markdown_contains_required_sections_and_skips_empty_optional() { + let md = generate_markdown(&report()); + assert!(md.starts_with("## Summary\n\n")); + assert!(md.contains("## Report Type\n\nbug")); + assert!(md.contains("## Steps to Reproduce")); + assert!(md.contains("## Expected Behavior")); + assert!(md.contains("## Actual Behavior")); + assert!(md.contains("## Attachments")); + assert!(md.contains("- app.log")); + assert!(md.contains("## Environment")); + } + + #[test] + fn markdown_uses_redacted_content() { + let mut r = report(); + r.description = "token=ghp_abcdefghijklmnopqrstuvwxyz leaked".to_string(); + let md = generate_markdown(&r); + assert!(md.contains("[REDACTED]")); + assert!(!md.contains("ghp_")); + } + + #[test] + fn validation_flags_missing_title_and_bug_repro_warning() { + let mut r = report(); + r.title = " ".to_string(); + r.steps_to_reproduce = String::new(); + let issues = validate(&r); + assert!(issues.iter().any(|i| i.field == "title" && i.is_error())); + assert!(issues + .iter() + .any(|i| i.field == "stepsToReproduce" && !i.is_error())); + } + + #[test] + fn crash_requires_description_or_logs() { + let mut r = report(); + r.category = CATEGORY_CRASH.to_string(); + r.description = String::new(); + r.logs = String::new(); + assert!(validate(&r) + .iter() + .any(|i| i.field == "description" && i.is_error())); + r.logs = "panic: boom".to_string(); + assert!(!validate(&r) + .iter() + .any(|i| i.field == "description" && i.is_error())); + } + + #[test] + fn redact_removes_secret_markers() { + let out = redact("Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz1234567890"); + assert!(!out.contains("ghp_")); + assert!(out.contains("[REDACTED]")); + assert!(!out.contains("abcdefghijklmnopqrstuvwxyz")); + } + + #[test] + fn sanitize_truncates_oversized_logs() { + let long = "x".repeat(MAX_REPORT_TEXT_CHARS + 100); + let sanitized = sanitize(&long); + assert!(sanitized.chars().count() > MAX_REPORT_TEXT_CHARS); + assert!(sanitized.ends_with("\n… [truncated]")); + } +} From f09c911daae27c2f8e6a452750ab273e23bff756 Mon Sep 17 00:00:00 2001 From: mleem97 Date: Wed, 28 Oct 2026 04:47:48 +0100 Subject: [PATCH 06/26] ci: add local developer workflow scripts and dev-workflow audit Add bash scripts covering recurring local contributor workflows: - scripts/check.sh: quality gate (rustfmt + clippy -D warnings + tests, all crates) - scripts/build.sh: dev/debug build (all crates) - scripts/run-app.sh: build app from source and start (debug, incl. Steam lib setup) - scripts/run-cli.sh: run headless CLI from source - scripts/build-release.sh: release build + package (host, --target/--out pass-through) - scripts/build-windows.sh: cross-build Windows x64 via xtask dist --target x86_64-pc-windows-gnu - scripts/build-linux.sh: release build for Linux host - scripts/build-macos.sh: release build for macOS host (Apple SDK required) Add AUDIT_LOCAL_DEV_WORKFLOWS.md inventorying recurring local dev activities (quality gate, dev build, app build+start, CLI, release builds for Windows/Linux/macOS) and crate coverage. Update scripts/README.md and CHANGELOG. --- AUDIT_LOCAL_DEV_WORKFLOWS.md | 128 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 3 + scripts/README.md | 15 +++- scripts/build-linux.sh | 14 ++++ scripts/build-macos.sh | 16 +++++ scripts/build-release.sh | 12 ++++ scripts/build-windows.sh | 20 ++++++ scripts/build.sh | 8 +++ scripts/check.sh | 16 +++++ scripts/run-app.sh | 11 +++ scripts/run-cli.sh | 10 +++ 11 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 AUDIT_LOCAL_DEV_WORKFLOWS.md create mode 100644 scripts/build-linux.sh create mode 100644 scripts/build-macos.sh create mode 100644 scripts/build-release.sh create mode 100644 scripts/build-windows.sh create mode 100644 scripts/build.sh create mode 100644 scripts/check.sh create mode 100644 scripts/run-app.sh create mode 100644 scripts/run-cli.sh diff --git a/AUDIT_LOCAL_DEV_WORKFLOWS.md b/AUDIT_LOCAL_DEV_WORKFLOWS.md new file mode 100644 index 0000000..bb5acfb --- /dev/null +++ b/AUDIT_LOCAL_DEV_WORKFLOWS.md @@ -0,0 +1,128 @@ +# AUDIT_LOCAL_DEV_WORKFLOWS — recurring local developer workflows + +> Owner: gregModmanager maintainers. +> Status: audit/inventory of recurring local developer activities in the +> FULL-RUST workspace. +> Evidence: repository analysis of the workspace (`Cargo.toml`, `xtask`, +> `crates/*`, `scripts/`, `.forgejo/workflows/`). +> Review trigger: update whenever a new local dev workflow script, platform, +> or crate is added. + +## 1. Purpose and scope + +This audit inventories all recurring local developer activities that a +contributor normally repeats locally in the `gregModmanager` repository. + +The project is FULL-RUST: a Cargo workspace (`crates/greg-core`, +`crates/greg-platform`, `crates/greg-steam`, `crates/greg-modstore`, +`crates/greg-loader`, `crates/greg-app`, `crates/greg-cli`, `xtask`) with Slint +UI, a headless CLI, and native Steam integration. + +The audit maps each recurring activity to a canonical local script and to the +underlying Rust command. It complements the CI workflows +(`.forgejo/workflows/rust.yml`, `.forgejo/workflows/build-and-release.yml`) with +local, developer-facing equivalents. + +Out of scope: CI-only promotion activities (quality gates, release promotion) +that have no local counterpart; those are documented in `TOOLS.md` and the CI +workflows. + +## 2. Recurring local workflows + +### 2.1 Quality gate (all crates) + +- **Activity:** run formatting check, clippy (`-D warnings`) and the full test + suite before opening a pull request. +- **Script:** `scripts/check.sh` +- **Command:** `cargo xtask check` — rustfmt `--all -- --check`, clippy + `--workspace --all-targets -- -D warnings`, test `--workspace`. +- **Crate coverage:** all workspace members. + +### 2.2 Dev build (all crates) + +- **Activity:** debug build while developing. +- **Script:** `scripts/build.sh` +- **Command:** `cargo build --workspace` +- **Crate coverage:** all workspace members. + +### 2.3 Desktop app: build from source and start + +- **Activity:** recurring "App aus Code bauen und Starten" — copy the Steam + native library and launch the Slint desktop UI in the debug profile. +- **Script:** `scripts/run-app.sh` +- **Command:** `cargo xtask setup` (copies the native Steam lib next to the dev + binaries) -> `cargo run -p greg-app` (debug build + launch). +- **Crate coverage:** `greg-app` plus the domain crates it composes. + +### 2.4 Headless CLI: build from source and run + +- **Activity:** recurring local run of the headless CLI (`publish`, `status`, + `list`). +- **Script:** `scripts/run-cli.sh` +- **Command:** `cargo run -p greg-cli -- "$@"` +- **Crate coverage:** `greg-cli` plus the domain crates it uses. + +### 2.5 Release build (host platform) and packaging + +- **Activity:** build the release binary and the portable package locally. +- **Script:** `scripts/build-release.sh` +- **Command:** `cargo xtask dist` with pass-through flags (`--target `, + `--out `). +- **Crate coverage:** `greg-app`, `greg-cli`, native Steam lib staging, + documentation, archive + SHA-256 sidecar. + +### 2.6 Release builds per platform + +- **Windows x64:** `scripts/build-windows.sh` -> `cargo xtask dist --target + x86_64-pc-windows-gnu` (cross-build via mingw from Linux/Debian; or native via + Git Bash/WSL). +- **Linux x64:** `scripts/build-linux.sh` -> `cargo xtask dist` (native Linux). +- **macOS x64 / arm64:** `scripts/build-macos.sh` -> `cargo xtask dist` (native + macOS host; Apple SDK required, no cross-build from Linux). + +## 3. Platform build-host matrix + +| Host | Native build | Cross build | Run app | +| --- | --- | --- | --- | +| Linux (Ubuntu/Debian — build host) | `scripts/build-linux.sh`, `scripts/build-release.sh` | Windows via `scripts/build-windows.sh` (`x86_64-pc-windows-gnu`, mingw) | `scripts/run-app.sh` | +| macOS | `scripts/build-macos.sh`, `scripts/build-release.sh` | n/a (Apple SDK; not cross-buildable from Linux) | `scripts/run-app.sh` | +| Windows | `scripts/build-release.sh` (Git Bash/WSL) | n/a | `scripts/run-app.sh` (Git Bash/WSL) | + +Important: only Ubuntu/Debian is available as the Linux build host. Windows +release artifacts are cross-built from that host through the mingw toolchain +(`gcc-mingw-w64-x86-64`) plus `rustup target add x86_64-pc-windows-gnu`. macOS +release builds require a macOS host with the Apple SDK and therefore cannot run +on the Linux build host. + +## 4. Crate coverage + +All crates created during the Rust restart are covered by the workspace-wide +commands (`--workspace`, `--all-targets`) and by the explicitly named binaries: + +| Crate | Covered by | +| --- | --- | +| `greg-core` | `check.sh`, `build.sh`, workspace tests, upload/docs logic | +| `greg-platform` | `check.sh`, `build.sh`, platform shims | +| `greg-steam` | `check.sh`, `build.sh`, Steam backend | +| `greg-modstore` | `check.sh`, `build.sh`, REST/publish clients | +| `greg-loader` | `check.sh`, `build.sh`, game sync/launch | +| `greg-app` | `check.sh`, `build.sh`, `run-app.sh`, `build-release.sh`, per-platform release scripts | +| `greg-cli` | `check.sh`, `build.sh`, `run-cli.sh`, `build-release.sh`, per-platform release scripts | +| `xtask` | `check.sh`, `build.sh`, `run-app.sh` (`setup`), `build-release.sh` (`dist`) | + +## 5. Contributor recurring checklist + +1. Before opening a PR: run `./scripts/check.sh`. +2. During development: `./scripts/build.sh`; start the app or CLI via + `./scripts/run-app.sh` / `./scripts/run-cli.sh`. +3. Before a release: `./scripts/build-release.sh` and, per platform, + `./scripts/build-windows.sh` / `./scripts/build-linux.sh` / `./scripts/build-macos.sh`. + +## 6. Notes and open questions + +- macOS release builds require a macOS host (Apple SDK); no macOS cross-build + from Linux exists. +- Windows cross-builds check for the mingw cross compiler and the Windows Rust + target before invoking `cargo xtask dist`. +- Release promotion itself runs through CI (`promote-changelog.yml`); the local + scripts mirror the build/packaging commands (`cargo xtask dist`). diff --git a/CHANGELOG.md b/CHANGELOG.md index eca34cb..ff50257 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Local developer workflow scripts in `scripts/` (quality gate, dev build, app + start from source, headless CLI, release builds incl. Windows/Linux/macOS) and + the recurring local-workflow inventory in `AUDIT_LOCAL_DEV_WORKFLOWS.md`. - Universal Report Assistant: new central `Report` tab in the modmanager navigation (replaces the former bug-only link-out entry). Category selection (bug, crash, feature, improvement, performance, compatibility, diff --git a/scripts/README.md b/scripts/README.md index 0c837c1..b6df1a2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,5 +1,18 @@ # scripts — gregModmanager -Build-, Test- und Hilfsskripte. +Build, test and helper scripts for local developer workflows. + +## Local developer workflows + +| Activity | Script | Command | +| --- | --- | --- | +| Quality gate (rustfmt + clippy `-D warnings` + tests, all crates) | [`check.sh`](check.sh) | `cargo xtask check` | +| Dev/debug build (all crates) | [`build.sh`](build.sh) | `cargo build --workspace` | +| Build app from source and start (debug) | [`run-app.sh`](run-app.sh) | `cargo xtask setup` + `cargo run -p greg-app` | +| Run headless CLI from source | [`run-cli.sh`](run-cli.sh) | `cargo run -p greg-cli -- "$@"` | +| Release build (host platform) | [`build-release.sh`](build-release.sh) | `cargo xtask dist` (+ `--target`, `--out`) | +| Release build Windows x64 | [`build-windows.sh`](build-windows.sh) | `cargo xtask dist --target x86_64-pc-windows-gnu` | +| Release build Linux x64 | [`build-linux.sh`](build-linux.sh) | `cargo xtask dist` (Linux host) | +| Release build macOS x64/arm64 | [`build-macos.sh`](build-macos.sh) | `cargo xtask dist` (macOS host) | Zurück: [README.md](../README.md) · Docs: [docs/INDEX.md](../docs/INDEX.md). diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh new file mode 100644 index 0000000..7fd47d8 --- /dev/null +++ b/scripts/build-linux.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# gregModmanager — release build + portable package for Linux (host platform). +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +OS="$(uname -s)" +if [ "$OS" != "Linux" ]; then + echo "error: Linux release build must run on a Linux host." >&2 + exit 1 +fi + +exec cargo xtask dist diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh new file mode 100644 index 0000000..3287836 --- /dev/null +++ b/scripts/build-macos.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# gregModmanager — release build + portable package for macOS. +# Requires a macOS host (Apple SDK); macOS targets cannot be cross-built on Linux. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +OS="$(uname -s)" +if [ "$OS" != "Darwin" ]; then + echo "error: macOS release build requires a macOS host (Apple SDK)." >&2 + echo "macOS targets cannot be cross-built on Linux; run this script on macOS." >&2 + exit 1 +fi + +exec cargo xtask dist diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100644 index 0000000..6f0d0ed --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# gregModmanager — release build + portable package for the host platform. +# Flags are passed through to `cargo xtask dist`: +# scripts/build-release.sh +# scripts/build-release.sh --target x86_64-pc-windows-gnu +# scripts/build-release.sh --out +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +exec cargo xtask dist "$@" diff --git a/scripts/build-windows.sh b/scripts/build-windows.sh new file mode 100644 index 0000000..8ca4cd8 --- /dev/null +++ b/scripts/build-windows.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# gregModmanager — cross-build the Windows x64 portable release package. +# Intended to run from Linux (Ubuntu/Debian) or macOS with mingw. +# Requirements: +# rustup target add x86_64-pc-windows-gnu +# sudo apt-get install -y gcc-mingw-w64-x86-64 # Debian/Ubuntu +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +rustup target add x86_64-pc-windows-gnu + +if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1; then + echo "error: mingw cross compiler 'x86_64-w64-mingw32-gcc' not found." >&2 + echo "install it via: sudo apt-get install -y gcc-mingw-w64-x86-64" >&2 + exit 1 +fi + +exec cargo xtask dist --target x86_64-pc-windows-gnu diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..0780b25 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# gregModmanager — dev/debug build of the whole Rust workspace (all crates). +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +exec cargo build --workspace diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100644 index 0000000..b7a9f88 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# gregModmanager — local quality gate. +# Runs the same checks as `cargo xtask check` across the whole Rust workspace +# (all crates): rustfmt check -> clippy (-D warnings) -> cargo test. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +echo "== [check] fmt --all -- --check" +cargo fmt --all -- --check +echo "== [check] clippy --workspace --all-targets -- -D warnings" +cargo clippy --workspace --all-targets -- -D warnings +echo "== [check] test --workspace" +cargo test --workspace +echo "== [check] OK" diff --git a/scripts/run-app.sh b/scripts/run-app.sh new file mode 100644 index 0000000..b44dbd6 --- /dev/null +++ b/scripts/run-app.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# gregModmanager — build the desktop app from source (debug) and start it. +# 1. `cargo xtask setup` copies the Steam native library next to the dev binaries. +# 2. `cargo run -p greg-app` debug-builds and launches the Slint desktop UI. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +cargo xtask setup +exec cargo run -p greg-app diff --git a/scripts/run-cli.sh b/scripts/run-cli.sh new file mode 100644 index 0000000..6f2dda5 --- /dev/null +++ b/scripts/run-cli.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# gregModmanager — run the headless CLI from source (publish/status/list). +# Usage: scripts/run-cli.sh [args...] +# e.g. scripts/run-cli.sh publish --path --changelog +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +exec cargo run -p greg-cli -- "$@" From eb043969b6c0f3387e8fd5684e18cb073a6d3dd3 Mon Sep 17 00:00:00 2001 From: mleem97 Date: Wed, 28 Oct 2026 05:51:13 +0100 Subject: [PATCH 07/26] fix(ci): complete full-rust configuration alignment and remove stale csharp tooling - Devcontainer, release workflows, docs and build scripts aligned to the Rust (Slint + cargo + xtask) workspace after the full-Rust migration. - Remove obsolete .NET/Avalonia build scripts (builder menus, PowerShell build helpers, Avalonia package scripts, apt/local-test/Linux-package scripts) that referenced projects outside the Rust workspace. - Reduce quality gates (scripts/greg-gates.sh) to the Rust toolchain path and switch version consistency to VERSION vs workspace Cargo.toml. - Switch Codacy language/tool configuration from C# to Rust. - Align Windows installer signing and Inno Setup documentation with the current Rust-era scripts. - Refresh changelog under [Unreleased]. --- .devcontainer/Dockerfile | 31 +- .devcontainer/devcontainer.json | 13 +- .../workflows/greg-promote.yml | 12 +- .../workflows/greg-train.yml | 4 +- .forgejo/workflows/promote-changelog.yml | 11 +- .gitea/workflows/greg-promote.yml | 97 +++ .gitea/workflows/greg-train.yml | 78 +++ .gitea/workflows/promote-changelog.yml | 11 +- .github/workflows/build-and-release.yml | 246 -------- .globalconfig | 10 - .jules/de-mock.md | 3 + AUDIT_ARCHITECTURE.md | 112 ++++ AUDIT_DESIGN_SYSTEM.md | 103 ++++ AUDIT_FEATURE_INVENTORY.md | 146 +++++ AUDIT_OPEN_QUESTIONS.md | 62 ++ AUDIT_OVERVIEW.md | 135 +++++ AUDIT_REPORT_ASSISTANT_PLAN.md | 142 +++++ CHANGELOG.md | 14 + CONTRIBUTING.md | 31 +- DESIGN.md | 102 ++-- EXTERNAL_DEPENDENCIES.md | 31 +- ModStoreAndManager-Roadmap.md | 2 +- QUICKSTART.md | 18 +- README.md | 81 +-- TOOLS.md | 31 +- USER.md | 31 +- build.sh | 2 +- build/builder.ps1 | 260 -------- build/builder.sh | 289 --------- build/installer/CODE_SIGNING.md | 48 +- build/installer/README_Steam.txt | 80 +-- .../create-selfsigned-codesign-cert.ps1 | 2 +- build/installer/gregModmanager.iss | 8 +- build/scripts/README.md | 101 ++-- build/scripts/build.ps1 | 565 ------------------ build/scripts/dev-helpers.ps1 | 153 ----- build/scripts/fix-csharp-strings.csx | 28 - build/scripts/install-local.ps1 | 96 --- build/scripts/linux/build-apt-repo.sh | 178 ------ .../scripts/linux/build-avalonia-packages.ps1 | 60 -- .../scripts/linux/build-avalonia-packages.sh | 84 --- build/scripts/linux/build-linux-packages.ps1 | 83 --- build/scripts/linux/build-linux-packages.sh | 247 -------- build/scripts/linux/build-local-test.sh | 35 -- .../gregmodmanager-flatpak-launcher.sh | 5 +- build/scripts/linux/gregmodmanager.desktop | 2 +- build/scripts/macos/run-remote-manual-test.sh | 15 +- build/scripts/run.ps1 | 7 - build/scripts/start.ps1 | 32 - crates/greg-app/ui/main.slint | 2 +- crates/greg-app/ui/theme.slint | 2 +- current_features.md | 2 +- docker/Dockerfile | 62 +- docker/Dockerfile.windows | 22 - docker/README.md | 38 +- docs/01_END_USER_GUIDE.md | 21 +- docs/02_MOD_CREATOR_GUIDE.md | 34 +- docs/03_CONTRIBUTOR_GUIDE.md | 102 ++-- docs/ARCHITECTURE.md | 8 +- docs/INDEX.md | 8 +- docs/codebase/ARCHITECTURE.md | 55 +- docs/codebase/CONCERNS.md | 30 +- docs/codebase/CONVENTIONS.md | 36 +- docs/codebase/INTEGRATIONS.md | 55 +- docs/codebase/README.md | 4 +- docs/codebase/STACK.md | 71 ++- docs/codebase/STRUCTURE.md | 48 +- docs/codebase/TESTING.md | 32 +- docs/examples/manifests/README.md | 2 +- {.github/scripts => scripts}/greg-gates.sh | 100 +--- 70 files changed, 1525 insertions(+), 3146 deletions(-) rename {.github => .forgejo}/workflows/greg-promote.yml (91%) rename {.github => .forgejo}/workflows/greg-train.yml (97%) create mode 100644 .gitea/workflows/greg-promote.yml create mode 100644 .gitea/workflows/greg-train.yml delete mode 100644 .github/workflows/build-and-release.yml delete mode 100644 .globalconfig create mode 100644 AUDIT_ARCHITECTURE.md create mode 100644 AUDIT_DESIGN_SYSTEM.md create mode 100644 AUDIT_FEATURE_INVENTORY.md create mode 100644 AUDIT_OPEN_QUESTIONS.md create mode 100644 AUDIT_OVERVIEW.md create mode 100644 AUDIT_REPORT_ASSISTANT_PLAN.md delete mode 100644 build/builder.ps1 delete mode 100644 build/builder.sh delete mode 100644 build/scripts/build.ps1 delete mode 100644 build/scripts/dev-helpers.ps1 delete mode 100644 build/scripts/fix-csharp-strings.csx delete mode 100644 build/scripts/install-local.ps1 delete mode 100644 build/scripts/linux/build-apt-repo.sh delete mode 100644 build/scripts/linux/build-avalonia-packages.ps1 delete mode 100755 build/scripts/linux/build-avalonia-packages.sh delete mode 100644 build/scripts/linux/build-linux-packages.ps1 delete mode 100644 build/scripts/linux/build-linux-packages.sh delete mode 100755 build/scripts/linux/build-local-test.sh delete mode 100644 build/scripts/run.ps1 delete mode 100644 build/scripts/start.ps1 delete mode 100644 docker/Dockerfile.windows rename {.github/scripts => scripts}/greg-gates.sh (53%) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4e3d8ef..daac13a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,21 +1,24 @@ -# Avalonia desktop (net10 LTS): SDK 10 plus native X11/font/GL deps so the -# Modmanager builds and can at least smoke-run headless via xvfb. -FROM mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim +# Rust (rust + Slint) desktop dev container: rustup toolchain plus the native +# X11/font/GL deps so the Slint UI builds and can at least smoke-run headless +# via xvfb. Release builds cross-compile through the same toolchain. +FROM rust:1-bookworm ARG USERNAME=vscode ARG USER_UID=1000 ARG USER_GID=1000 -RUN groupadd --gid "$USER_GID" "$USERNAME" \ - && useradd --uid "$USER_UID" --gid "$USER_GID" -m -s /bin/bash "$USERNAME" \ - && apt-get update \ - && apt-get install -y --no-install-recommends \ - git sudo xvfb \ - libice6 libsm6 libx11-6 libxkbcommon0 libfontconfig1 libfreetype6 \ - libgl1 libegl1 zlib1g dbus-x11 \ - && echo "$USERNAME ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/$USERNAME" \ - && chmod 0440 "/etc/sudoers.d/$USERNAME" \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git sudo xvfb \ + libice6 libsm6 libx11-6 libxkbcommon0 libfontconfig1 libfreetype6 \ + libgl1 libegl1 zlib1g dbus-x11 \ + && echo "$USERNAME ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/$USERNAME" \ + && chmod 0440 "/etc/sudoers.d/$USERNAME" \ + && rm -rf /var/lib/apt/lists/* USER "$USERNAME" -RUN dotnet --list-sdks +WORKDIR /src +COPY . . +RUN rustup component add clippy rustfmt && cargo fetch + +RUN rustc --version && cargo --version diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2260935..0d0bda4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,20 +1,17 @@ { - "name": "greg modmanager (avalonia, net10)", + "name": "greg modmanager (rust, slint)", "build": { "dockerfile": "Dockerfile" }, "remoteUser": "vscode", "containerEnv": { - "DOTNET_CLI_TELEMETRY_OPTOUT": "1", - "DOTNET_NOLOGO": "1" + "CARGO_TERM_COLOR": "always", + "RUSTUP_TOOLCHAIN": "stable" }, - "mounts": [ - "source=greg-modmanager-nuget,target=/home/vscode/.nuget/packages,type=volume" - ], "customizations": { "vscode": { - "extensions": ["ms-dotnettools.csharp"] + "extensions": ["rust-lang.rust-analyzer"] } }, - "postCreateCommand": "dotnet restore GregModmanager.sln" + "postCreateCommand": "rustup component add rust-analyzer && cargo fetch" } diff --git a/.github/workflows/greg-promote.yml b/.forgejo/workflows/greg-promote.yml similarity index 91% rename from .github/workflows/greg-promote.yml rename to .forgejo/workflows/greg-promote.yml index bbce2bb..c406e99 100644 --- a/.github/workflows/greg-promote.yml +++ b/.forgejo/workflows/greg-promote.yml @@ -6,7 +6,7 @@ name: greg promote dev -> staging # build, tests, coverage>=90%% with <=5pp drop vs staging, # security (gitleaks=0, dep-vuln high/critical=0, warnings delta<=0), # version-consistency (mods), conventional-commits. -# Gate rule: build must pass; tests/coverage enforced for dotnet/rust kinds +# Gate rule: build must pass; tests/coverage enforced for rust kinds # (fail closed when missing); security/consistency/conventional always enforced. # Promotion pushes only for github.actor == 'mleem97'. No push on failure; # a tracking issue (label staging-gate) is opened/updated/closed instead. @@ -41,17 +41,17 @@ jobs: verdict: ${{ steps.eval.outputs.verdict }} sha: ${{ steps.eval.outputs.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - name: Evaluate quality gates id: eval run: | - bash "$GITHUB_WORKSPACE/.github/scripts/greg-gates.sh" + bash "$GITHUB_WORKSPACE/scripts/greg-gates.sh" - name: Upload gate report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: gate-report-dev-${{ github.sha }} path: gate-report.txt @@ -62,7 +62,7 @@ jobs: if: needs.gates.outputs.verdict == 'pass' && github.actor == 'mleem97' && (github.event_name == 'push' || inputs.dry_run == false) runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 ref: dev @@ -75,7 +75,7 @@ jobs: if: always() && github.event_name == 'push' runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Sync staging-gate tracking issue diff --git a/.github/workflows/greg-train.yml b/.forgejo/workflows/greg-train.yml similarity index 97% rename from .github/workflows/greg-train.yml rename to .forgejo/workflows/greg-train.yml index 14914eb..90c9565 100644 --- a/.github/workflows/greg-train.yml +++ b/.forgejo/workflows/greg-train.yml @@ -29,7 +29,7 @@ jobs: verdict: ${{ steps.eval.outputs.verdict }} sha: ${{ steps.eval.outputs.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true @@ -52,7 +52,7 @@ jobs: if: needs.readiness.outputs.verdict == 'pass' && github.actor == 'mleem97' && (github.event_name == 'push' || inputs.dry_run == false) runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Fast-forward main to gated sha diff --git a/.forgejo/workflows/promote-changelog.yml b/.forgejo/workflows/promote-changelog.yml index c034fe3..7dcc53a 100644 --- a/.forgejo/workflows/promote-changelog.yml +++ b/.forgejo/workflows/promote-changelog.yml @@ -66,21 +66,20 @@ jobs: print(f'Promoted [Unreleased] to [{ver}] - {date}') EOF - - name: Bump version (VERSION file + .csproj fallback copy) + - name: Bump version (VERSION file + workspace Cargo.toml fallback) shell: bash run: | ver='${{ github.event.inputs.version }}' printf '%s\n' "$ver" > VERSION - csproj='src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj' - old="$(sed -n 's:.*\([^<]*\).*:\1:p' "$csproj" | head -n 1)" - sed -i "s|[^<]*|${ver}|" "$csproj" - echo "Version: VERSION=$ver, .csproj: $old -> $ver" + cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" + sed -i "s/^version = \"[^\"]*\"/version = \"$ver\"/" Cargo.toml + echo "Version: VERSION=$ver, Cargo.toml: $cargo_version -> $ver" - name: Commit and push to main run: | git config user.name "actions[bot]" git config user.email "actions[bot]@users.noreply.localhost" - git add CHANGELOG.md VERSION src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj + git add CHANGELOG.md VERSION Cargo.toml git commit -m "chore(release): promote changelog and bump version to ${{ github.event.inputs.version }}" git push origin main diff --git a/.gitea/workflows/greg-promote.yml b/.gitea/workflows/greg-promote.yml new file mode 100644 index 0000000..c406e99 --- /dev/null +++ b/.gitea/workflows/greg-promote.yml @@ -0,0 +1,97 @@ +name: greg promote dev -> staging + +# Branch model: contribute-here -> dev -> staging -> main. +# This workflow auto-promotes dev to staging when ALL quality gates pass. +# Gates (enforced in code, owner: mleem97): +# build, tests, coverage>=90%% with <=5pp drop vs staging, +# security (gitleaks=0, dep-vuln high/critical=0, warnings delta<=0), +# version-consistency (mods), conventional-commits. +# Gate rule: build must pass; tests/coverage enforced for rust kinds +# (fail closed when missing); security/consistency/conventional always enforced. +# Promotion pushes only for github.actor == 'mleem97'. No push on failure; +# a tracking issue (label staging-gate) is opened/updated/closed instead. + +on: + push: + branches: [dev] + workflow_dispatch: + inputs: + dry_run: + description: 'Evaluate gates only (no push)' + type: boolean + default: true + +permissions: + contents: write + issues: write + +concurrency: + group: promote-dev-staging-gregModmanager + cancel-in-progress: false + +env: + STAGE: staging + KIND: rust + NEEDS_REFS: false + +jobs: + gates: + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + outputs: + verdict: ${{ steps.eval.outputs.verdict }} + sha: ${{ steps.eval.outputs.sha }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + - name: Evaluate quality gates + id: eval + run: | + bash "$GITHUB_WORKSPACE/scripts/greg-gates.sh" + - name: Upload gate report + if: always() + uses: actions/upload-artifact@v7 + with: + name: gate-report-dev-${{ github.sha }} + path: gate-report.txt + if-no-files-found: warn + + promote: + needs: gates + if: needs.gates.outputs.verdict == 'pass' && github.actor == 'mleem97' && (github.event_name == 'push' || inputs.dry_run == false) + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + ref: dev + - name: Fast-forward staging to gated sha + run: | + git push origin "${{ needs.gates.outputs.sha }}:refs/heads/staging" + + sync-issue: + needs: gates + if: always() && github.event_name == 'push' + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Sync staging-gate tracking issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERDICT: ${{ needs.gates.outputs.verdict }} + run: | + TITLE="[staging-gate] dev -> staging blocked" + BODY="$(printf 'Promotion blocked by quality gates (dev @ %s). See run %s.\n\n```\n%s\n```\n' '${{ github.sha }}' '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' "$(cat gate-report.txt 2>/dev/null || echo 'no report')")" + OPEN="$(gh issue list --label staging-gate --state open --json number --jq '.[0].number // empty')" + if [ "$VERDICT" = "pass" ]; then + [ -n "$OPEN" ] && gh issue close "$OPEN" --comment "Gates green again — promotion resumed." || true + else + if [ -z "$OPEN" ]; then + gh issue create --title "$TITLE" --label staging-gate --body "$BODY" || true + else + gh issue comment "$OPEN" --body "Still blocked @ ${{ github.sha }}." || true + fi + fi diff --git a/.gitea/workflows/greg-train.yml b/.gitea/workflows/greg-train.yml new file mode 100644 index 0000000..90c9565 --- /dev/null +++ b/.gitea/workflows/greg-train.yml @@ -0,0 +1,78 @@ +name: greg train staging -> main + +# Second stage of the branch model: advance main from staging when ready. +# Readiness = fast-forward possible + owner actor + conventional commits. +# Quality was already gated at dev -> staging (same sha, no rebuild here). + +on: + push: + branches: [staging] + workflow_dispatch: + inputs: + dry_run: + description: 'Check readiness only (no push)' + type: boolean + default: true + +permissions: + contents: write + issues: write + +concurrency: + group: train-staging-main-gregModmanager + cancel-in-progress: false + +jobs: + readiness: + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + outputs: + verdict: ${{ steps.eval.outputs.verdict }} + sha: ${{ steps.eval.outputs.sha }} + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + - name: Evaluate readiness + id: eval + run: | + set -u + git fetch origin main >/dev/null 2>&1 || true + SHA="$(git rev-parse origin/staging)" + OK=true + git merge-base --is-ancestor "origin/main" "origin/staging" && echo "READY ff: main is ancestor of staging" || { echo "READY ff: FAIL (main not ancestor)"; OK=false; } + if git log --no-merges --format=%s "origin/main..origin/staging" 2>/dev/null | grep -vE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+" | grep -q .; then + echo "READY conventional: FAIL"; OK=false + else echo "READY conventional: PASS"; fi + [ "$OK" = true ] && echo "verdict=pass" >> "$GITHUB_OUTPUT" || echo "verdict=fail" >> "$GITHUB_OUTPUT" + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + + advance: + needs: readiness + if: needs.readiness.outputs.verdict == 'pass' && github.actor == 'mleem97' && (github.event_name == 'push' || inputs.dry_run == false) + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Fast-forward main to gated sha + run: | + git push origin "${{ needs.readiness.outputs.sha }}:refs/heads/main" + + sync-issue: + needs: readiness + if: always() && github.event_name == 'push' + runs-on: ["self-hosted", "Linux", "X64", "shitcloud", "gregmodmanager"] + steps: + - name: Sync release-train tracking issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERDICT: ${{ needs.readiness.outputs.verdict }} + run: | + TITLE="[release-train] staging -> main blocked" + OPEN="$(gh issue list --label release-train --state open --json number --jq '.[0].number // empty')" + if [ "$VERDICT" = "pass" ]; then + [ -n "$OPEN" ] && gh issue close "$OPEN" --comment "Train green again." || true + else + [ -z "$OPEN" ] && gh issue create --title "$TITLE" --label release-train --body "Readiness failed for run ${{ github.run_id }}." || true + fi diff --git a/.gitea/workflows/promote-changelog.yml b/.gitea/workflows/promote-changelog.yml index c034fe3..7dcc53a 100644 --- a/.gitea/workflows/promote-changelog.yml +++ b/.gitea/workflows/promote-changelog.yml @@ -66,21 +66,20 @@ jobs: print(f'Promoted [Unreleased] to [{ver}] - {date}') EOF - - name: Bump version (VERSION file + .csproj fallback copy) + - name: Bump version (VERSION file + workspace Cargo.toml fallback) shell: bash run: | ver='${{ github.event.inputs.version }}' printf '%s\n' "$ver" > VERSION - csproj='src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj' - old="$(sed -n 's:.*\([^<]*\).*:\1:p' "$csproj" | head -n 1)" - sed -i "s|[^<]*|${ver}|" "$csproj" - echo "Version: VERSION=$ver, .csproj: $old -> $ver" + cargo_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" + sed -i "s/^version = \"[^\"]*\"/version = \"$ver\"/" Cargo.toml + echo "Version: VERSION=$ver, Cargo.toml: $cargo_version -> $ver" - name: Commit and push to main run: | git config user.name "actions[bot]" git config user.email "actions[bot]@users.noreply.localhost" - git add CHANGELOG.md VERSION src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj + git add CHANGELOG.md VERSION Cargo.toml git commit -m "chore(release): promote changelog and bump version to ${{ github.event.inputs.version }}" git push origin main diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml deleted file mode 100644 index 0abc548..0000000 --- a/.github/workflows/build-and-release.yml +++ /dev/null @@ -1,246 +0,0 @@ -name: Build and Release - -on: - push: - branches: [main] - tags: ['v*'] - pull_request: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - -jobs: - version: - name: Resolve Version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.resolve.outputs.version }} - numeric_version: ${{ steps.resolve.outputs.numeric_version }} - is_release: ${{ steps.resolve.outputs.is_release }} - is_pre: ${{ steps.resolve.outputs.is_pre }} - steps: - - uses: actions/checkout@v4 - - id: resolve - shell: bash - run: | - version="$(tr -d '\r\n ' < VERSION 2>/dev/null || true)" - if [ -z "$version" ]; then - version="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n 1)" - fi - test -n "$version" - - numeric="${version%%-*}" - if [[ "$numeric" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - numeric="$numeric.0" - fi - - is_release=false - [[ "$GITHUB_REF_NAME" =~ ^v[0-9] ]] && is_release=true - is_pre=false - [[ "$version" == *-* || "$GITHUB_REF_NAME" == *-* ]] && is_pre=true - - { - echo "version=$version" - echo "numeric_version=$numeric" - echo "is_release=$is_release" - echo "is_pre=$is_pre" - } >> "$GITHUB_OUTPUT" - - test: - name: Test Workspace - needs: version - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Install Linux UI dependencies - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ - libxkbcommon-dev libfontconfig1-dev - - uses: Swatinem/rust-cache@v2 - - run: cargo test --workspace - - build-windows: - name: Windows Installer and Portable Build - needs: [version, test] - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Build Windows portable package - run: cargo run -p xtask -- dist - - name: Build Windows installer - uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 - with: - path: build/installer/gregModmanager.iss - options: >- - /DMyAppVersion=${{ needs.version.outputs.version }} - /DMyAppNumericVersion=${{ needs.version.outputs.numeric_version }} - /DSourceDir=dist/gregmodmanager-${{ needs.version.outputs.version }}-win-x64 - /DMyAppExeName=gregmodmanager.exe - /DSkipVCRedist - - name: Upload Windows artifacts - uses: actions/upload-artifact@v4 - with: - name: windows - path: | - dist/gregmodmanager-*-win-x64.zip - dist/gregmodmanager-*-win-x64.zip.sha256 - build/installer/Output/*.exe - if-no-files-found: error - - build-linux: - name: Linux Portable Build - needs: [version, test] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Install Linux UI dependencies - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ - libxkbcommon-dev libfontconfig1-dev - - uses: Swatinem/rust-cache@v2 - - name: Build Linux portable package - run: cargo run -p xtask -- dist - - name: Upload Linux portable artifacts - uses: actions/upload-artifact@v4 - with: - name: linux-portable - path: | - dist/gregmodmanager-*-linux-x64.tar.gz - dist/gregmodmanager-*-linux-x64.tar.gz.sha256 - if-no-files-found: error - - build-linux-packages: - name: Linux Distribution Packages - needs: [version, build-linux] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install nfpm - run: | - echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | - sudo tee /etc/apt/sources.list.d/goreleaser.list - sudo apt-get update - sudo apt-get install -y --no-install-recommends nfpm - - uses: actions/download-artifact@v4 - with: - name: linux-portable - path: artifacts/download - - name: Build Linux packages - shell: bash - run: | - set -euo pipefail - mkdir -p artifacts/stage artifacts/packages - tar -xzf artifacts/download/gregmodmanager-*-linux-x64.tar.gz \ - --strip-components=1 \ - -C artifacts/stage - chmod +x build/scripts/linux/build-rust-packages.sh - ./build/scripts/linux/build-rust-packages.sh \ - artifacts/stage \ - '${{ needs.version.outputs.version }}' \ - '${{ needs.version.outputs.is_pre }}' \ - artifacts/packages - - name: Upload Linux packages - uses: actions/upload-artifact@v4 - with: - name: linux-packages - path: artifacts/packages/* - if-no-files-found: error - - build-macos: - name: macOS ${{ matrix.name }} Build - needs: [version, test] - strategy: - fail-fast: false - matrix: - include: - - name: x64 - runner: macos-15-intel - target: x86_64-apple-darwin - - name: arm64 - runner: macos-15 - target: aarch64-apple-darwin - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - name: Install macOS build dependencies - run: brew install pkg-config - - uses: Swatinem/rust-cache@v2 - - name: Build macOS portable package - run: cargo run -p xtask -- dist --target ${{ matrix.target }} - - name: Upload macOS artifacts - uses: actions/upload-artifact@v4 - with: - name: macos-${{ matrix.name }} - path: | - dist/gregmodmanager-*-macos-*.tar.gz - dist/gregmodmanager-*-macos-*.tar.gz.sha256 - if-no-files-found: error - - release: - name: GitHub Release - needs: [version, build-windows, build-linux-packages, build-macos] - if: needs.version.outputs.is_release == 'true' && github.event_name != 'pull_request' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Download Windows artifacts - uses: actions/download-artifact@v4 - with: - name: windows - path: dist - - name: Download Linux portable artifacts - uses: actions/download-artifact@v4 - with: - name: linux-portable - path: dist - - name: Download Linux packages - uses: actions/download-artifact@v4 - with: - name: linux-packages - path: dist - - name: Download macOS x64 artifacts - uses: actions/download-artifact@v4 - with: - name: macos-x64 - path: dist - - name: Download macOS arm64 artifacts - uses: actions/download-artifact@v4 - with: - name: macos-arm64 - path: dist - - name: Publish GitHub release - env: - GH_TOKEN: ${{ github.token }} - TAG_NAME: ${{ github.ref_name }} - IS_PRE: ${{ needs.version.outputs.is_pre }} - shell: bash - run: | - set -euo pipefail - args=("$TAG_NAME" dist/* --title "$TAG_NAME" --generate-notes) - if [ "$IS_PRE" = true ]; then - args+=(--prerelease) - fi - gh release create "${args[@]}" diff --git a/.globalconfig b/.globalconfig deleted file mode 100644 index 4c2601b..0000000 --- a/.globalconfig +++ /dev/null @@ -1,10 +0,0 @@ -is_global = true - -# The repository intentionally keeps several generated/template-heavy UI and parser files together -# to preserve XAML/code-behind locality and Steam BBCode parser readability. Track structural -# refactors separately from security and correctness fixes. -dotnet_diagnostic.S104.severity = none -dotnet_diagnostic.S138.severity = none - -# Some public APIs preserve ergonomic overload-compatible signatures while older callers migrate. -dotnet_diagnostic.S2360.severity = none diff --git a/.jules/de-mock.md b/.jules/de-mock.md index b7a0617..4e5d6ba 100644 --- a/.jules/de-mock.md +++ b/.jules/de-mock.md @@ -1,5 +1,8 @@ # De-Mock Journal +> NOTE: This journal describes the frozen C# / Avalonia implementation era +> (see `.reference/modmanager-old/`). The active product code is Rust + Slint. + ## 2026-06-29 - Initial Analysis **Mock Pattern:** Repository uses .NET 9 with Avalonia UI, Steam integration via Facepunch.Steamworks **Learning:** Cross-platform desktop app with platform-specific code (Windows/Linux). Steam API integration is core functionality. diff --git a/AUDIT_ARCHITECTURE.md b/AUDIT_ARCHITECTURE.md new file mode 100644 index 0000000..a5ce20e --- /dev/null +++ b/AUDIT_ARCHITECTURE.md @@ -0,0 +1,112 @@ +# Audit Architecture — Current Structure Relevant to the Universal Report Assistant + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only** — no implementation. + +## 1. Architectural Style + +- Layered desktop client with constructor-injected services (Rust flavor of DI: + composition root in `crates/greg-app/src/main.rs` `wire()`). +- Event-driven Workshop/session updates; UI refresh via 150 ms timer tick. +- Primary constraints: Steam initialization order, platform native libraries, + local game-file safety. + +## 2. Layering and Boundaries + +| Layer | Owns | Must not own | Evidence | +|---|---|---|---| +| Presentation | `greg-app`: `main.rs`, `state.rs`, `worker.rs`, `actions.rs`, `shots.rs`, `ui/*.slint` | domain rules | `crates/greg-app/src/main.rs` | +| Domain | `greg-core` (pure logic), plus feature services `greg-steam`, `greg-modstore`, `greg-loader` | UI | crate manifests | +| Infrastructure | `greg-platform` (FS/log/telemetry/workspace/paths/protocol/process) | UI | `crates/greg-platform/src/` | +| Headless | `greg-cli` | UI | `crates/greg-cli/src/main.rs` | + +Dependency rule respected: no domain crate depends on UI crates +(`greg-core` has no slint/tokio/reqwest/steam deps). + +## 3. UI Threading and State Model + +- State container: `Arc>` (`crates/greg-app/src/state.rs`). +- UI handle access through `AppState::ui(&self) -> MainWindow` (upgrade from + `slint::Weak`). +- Timer: `slint::Timer` 150 ms → `actions::tick` drains events/jobs/timers. +- Events: `UiEvent::{BackendReady, Probe, AuthSessionEstablished, AuthSessionFailed}` + over `std::sync::mpsc`, drained in `tick`. +- Mutations from UI callbacks: actions lock state, mutate, push via + `state.ui().set_*` setters; background results via same setters from drains. +- Known pattern (post upload-freeze fix): never hold the `state` mutex while + calling functions that lock it again (`append_log` locks state itself). + +## 4. Background Execution Model + +| Mechanism | Used for | Notes | +|---|---|---| +| `JobHandle::spawn` + `JobSink` | Editor publish, sync hashing, thumbnails | std thread, `catch_unwind` → failed outcome ("Internal error, please report: …"), cancel flag, Drop cancels+joins | +| `JobHandle::drain` / `is_finished` | `tick`, `drain_publish`, `drain_sync`, `drain_thumb` | | +| Tokio runtime (multi-thread) | Async Modstore/auth flows | runtime handle spawned tasks send `UiEvent` back | +| `rt.block_on` | Some Modstore calls | from action contexts | +| Steam poller thread | Workshop subscription sync | | +| Background Steam probe thread | Steam availability/connect | `state.rs` `spawn_probe` | + +### Blocking-on-UI-thread audit (relevant to URA design) + +- `run_editor_checks` historically ran synchronously on UI thread (hashing all + content bytes, converting all READMEs to BBCode) — removed from publish drain + after upload-freeze fix; checks remain available via `editor_changed`. +- `local_refresh` holds state lock and runs `greg_loader::local_content::scan` + (fs) directly — invoked from UI callbacks and `tick`. +- `probe_gregapi` uses `reqwest::blocking` — only called from background thread. + +## 5. Error Handling, Logging, Telemetry, Persistence + +- Errors: `CoreError` (Io/Json/Version/Changelog/NotFound/Other), `PlatformError` + (Io/Json/Project/Network/Other, `From`); UI via job outcomes and + `UiEvent`s. +- Logging: `tracing` (env-filter, default info) + `FileLog` (file + console, + best-effort writes, session markers, `mark_crash`); in-memory `LogBuffer` + (capacity 500). +- Telemetry: opt-in Loki push skeleton; secrets via env + (`GREG_LOKI_URL/USER/PASS/TENANT`, local override `GREG_LOCAL_BUILD` → + `localhost:3100/loki`); gate env `GREG_TELEMETRY_ENABLED` (default off); + payloads: startup, sync collection; **sender not wired yet**. +- Persistence: JSON preferences (0600 unix), workspace metadata JSON, SHA-256 + publish hash. + +## 6. Report-Assistant Integration Seams (as found) + +1. Bug-report deep link actions (`bug_open_manager`, `bug_open_org`, + `bug_close`, `percent_encode_bug`) — reusable URL-building pattern. +2. `CrashReport` + `FileLog::mark_crash` + session markers — capture backend. +3. `repro::create_bundle` + `settings_make_bundle` — diagnostics bundle. +4. `upload::check` / `is_ready_to_upload` — state/context checks. +5. `WorkshopService::publish*` + rate limiter — failure context. +6. Docs resolution (`resolve_steam_description`, `resolve_steam_changelog`) — + version/change context. +7. Problems inbox (`refresh_problems`, `problems_act`) — user-visible problem feed. +8. Telemetry skeletons, log buffer/file log. +9. Modstore publish/install, game launch/health. + +## 7. Architecture Risks / Observations + +- `Arc>` on UI thread: correct lock discipline required for any + new code touching logs/state. +- Worker + tokio + threads: no single unified background task scheduler; + coordination via timer tick and job handles. +- Error taxonomy differs from likely future report-error categories + (crash vs. user error vs. network vs. auth vs. rate limit). + +## 8. Audit Conclusion + +Architecture supports incremental URA integration through existing seams; no +fundamental architectural change is required before Phase 2. + +## 9. Out of Scope for This Audit + +- Implementation of the URA. +- Live Steam/Modstore/GitLab/GitHub probes. +- Third-party library security assessment beyond repository usage. + +## 10. Open Architecture Questions + +- Where report context collection should live (greg-core domain vs. greg-app). +- Whether report creation should be synchronous (UI) or job-based. +- Consent storage model for telemetry/privacy notice. diff --git a/AUDIT_DESIGN_SYSTEM.md b/AUDIT_DESIGN_SYSTEM.md new file mode 100644 index 0000000..7d938e1 --- /dev/null +++ b/AUDIT_DESIGN_SYSTEM.md @@ -0,0 +1,103 @@ +# Audit Design System — Slint Implementation vs. Design Goals + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only** — no implementation. Visual verification still pending. + +## 1. Design Source of Truth + +- `DESIGN.md` (root, last updated 2026-05-05) is written against the **frozen + Avalonia/C# implementation** (`AppStrings.resx`, `IDialogService`, `MessageBox`, + `ThemeVariant`, `.axaml` styles, `ReflectionBindingExtension`). +- The active implementation is **Slint** (`slint 1.18.1`, `lucide-slint 1.49.1`). +- Token *names* were re-ported into a Slint global `Palette`, but tooling, + machinery and several values drifted. + +## 2. Token Audit (implemented vs. DESIGN.md) + +### 2.1 Colors + +| Token | DESIGN.md (Avalonia-era) | Implemented (`crates/greg-app/ui/theme.slint`) | Finding | +|---|---|---|---| +| Surface base | `#051424` | `surface: #051424` | Match | +| Surface elevated | `#0A1E2E` | `container-low: #0D1C2D` | Naming shift / value drift | +| Surface overlay | `#122131` | `container: #122131` | Naming shift (value reused) | +| Primary | `#8AEBFF` | `primary: #8AEBFF` | Match | +| Secondary | `#4DE082` | `secondary: #4DE082` | Match | +| On-surface | `#D4E4FA` | `on-surface: #D4E4FA` | Match | +| On-surface muted | `#8A9DB3` | `on-variant: #BBC9CD` | Value/naming drift | +| Danger | `#FF5370` | `error: #FFB4AB` (+ `error-container`) | Value/naming drift | +| Warning | `#FFD166` | not in palette; raw `#E8B93E` inline (`page-problems.slint:55`) | Missing token | +| Border active | `#8AEBFF` | `outline: #3C494C` | Missing token | + +- Alpha usage: `top-highlight: #FFFFFF0D`, `soft-shadow: #00000045`; DESIGN + discourages transparent overlays < 1.0 for backgrounds — deviation. +- Raw hardcoded colors outside the palette: `#00000099` scrim (`main.slint:735, + 782`), `#5A2530` / `#7A1F1F` danger hovers (`theme.slint:96,218`), `#FFFFFF` + WinBtn hover icon (`theme.slint:228`). + +### 2.2 Radii / spacing / typography + +- Implemented radii: `radius: 6px`, `radius-lg: 10px` (global Palette); + DESIGN: 4px cards/buttons/inputs, 8px modals. +- Page padding implemented ~18px (e.g. `page-local.slint:27–28`); + DESIGN: 24px page margin, 16px panel/card padding. +- Dialog scrim `#00000099` vs DESIGN `#051424E6`. +- Buttons: implemented height 32px (`GregButton`), DESIGN: padding 8/16. + +### 2.3 Typography + +- Implemented families: `"Nexa Black"` (header, `font-weight: 900`) and + `"monospace"` (log/editor log). All other text uses Slint default font. +- Implemented sizes: 10–15 px range observed; DESIGN specifies Space Grotesk / + Inter / JetBrains Mono with specific sizes — not implemented. +- Fonts exist only as vendored assets in the frozen C# reference; not loaded by + the Slint app. + +### 2.4 Components / layout / feedback / dialogs / icons + +- No `GridLayout` used anywhere; Linear layouts only. +- Custom `Rectangle` + `TouchArea` buttons (no native `StandardButton`); + `ProgressIndicator` from std-widgets (DESIGN wanted custom 4px bar). +- Dialogs: inline conditional Rectangles with scrim + centered card (DESIGN + wanted `IDialogService`/`Window.ShowDialog`) — not followed. +- Primary action position (right-bottom, destructive left) not followed; + buttons horizontally centered in dialogs. +- Icons: Lucide via generated `lucide.slint`, `AppIcon`, stroke-width 1.8, + ad-hoc sizes; DESIGN: stroke 1.5px, sizes 16/20/24. +- Hover: `drop-shadow` neumorphic lift implemented; DESIGN forbids shadows. +- Animation: max 150 ms intended; drop-shadow transitions observed. + +### 2.5 Theming, accessibility, localization + +- No dark/light theme switch; fixed global `Palette` (DESIGN allowed dark-only). +- **No accessibility usage** in any `.slint` (no accessible-name, focus, + keyboard, hotkeys, Tab handling). DESIGN §11/§16 (resx, CompiledBindings) are + Avalonia-era and not implementable. +- Localization: Rust string tables `STRINGS_EN`/`STRINGS_DE` (360 keys each) via + `greg_core::l10n`; `SUPPORTED_LANGUAGES` includes 10 cultures but only EN/DE + tables implemented (fallback English). Rust→Slint strings via property + setters; many strings hardcoded English directly in `.slint`. + +## 3. Design Audit Findings + +| Severity | Finding | Evidence | Implication for URA | +|---|---|---|---| +| P1 | `DESIGN.md` does not describe the Slint implementation | `DESIGN.md` vs `crates/greg-app/ui/theme.slint`, `main.slint` | URA must be designed against the actual Slint tokens/patterns, not DESIGN.md verbatim | +| P2 | Design tokens drift from documented values | token table above | Design decisions for URA should use implemented `Palette` names | +| P3 | No accessibility implementation | no a11y usage in `.slint` | URA dialogs should add accessible names/focus handling from scratch or via Slint a11y APIs | +| P3 | No visual verification yet | no screenshots in audit | Design section of URA plan must be validated with headless screenshot harness (`crates/greg-app/src/shots.rs`) before implementation | + +## 4. Audit Conclusion + +The implemented design system is a fixed, dark-only, custom-Chrome Slint +implementation with hand-rolled global palette, custom Rectangle/TouchArea +components, Lucide icons, and inline dialogs. It deviates from `DESIGN.md` in +tokens, typography, spacing, shadows, icons, and tooling assumptions. Any new +UI for the URA should reuse the Slint components (`GregButton`, `ListCard`, +`IconButton`, `NavButton`, `StatusLed`, …) and the implemented palette. + +## 5. Open Design Questions + +- Whether and when to migrate token naming to a documented canonical set. +- Accessibility implementation status in Slint 1.18.1 (planned vs. available). +- Font loading strategy for `"Nexa Black"` and system default. diff --git a/AUDIT_FEATURE_INVENTORY.md b/AUDIT_FEATURE_INVENTORY.md new file mode 100644 index 0000000..833a196 --- /dev/null +++ b/AUDIT_FEATURE_INVENTORY.md @@ -0,0 +1,146 @@ +# Audit Feature Inventory — Existing Functionality for the Universal Report Assistant + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only** — no implementation. + +## 1. How Features Were Inventoried + +Static source inspection of: + +- `crates/greg-app/src/` (actions, state, worker, main, shots) and `crates/greg-app/ui/*.slint` +- `crates/greg-core/src/` (domain models, checks, docs, upload, log, prefs, limits, l10n, telemetry models) +- `crates/greg-platform/src/` (filelog, telemetry, repro, workspace, prefs, paths, instance, protocol, process) +- `crates/greg-steam/src/` (service, backend, poller, rate_limit, inference, vdf, models) +- `crates/greg-modstore/src/` (auth client, client, collections, intent, publish, upload client, install) +- `crates/greg-loader/src/` (game, launch, health, installers, channels, sync, packs, local content, native config) +- `crates/greg-cli/src/` (headless publish/status/list) +- Root documentation (`docs/`, `CHANGELOG.md`, `current_features.md`, `functions.md`, `ModStoreAndManager-Roadmap.md`, `SECURITY.md`, `CONTRIBUTING.md`) + +## 2. Feature Catalog + +### 2.1 Desktop UI — `greg-app` + +| # | Feature | User Entry | Technical Entry | Status | +|---|---|---|---|---| +| 1 | Game launch / stop | Header buttons `start-game`, `stop-game` | `actions.rs` `start_game` (222), `stop_game` (345), `poll_game_state`, `finalize_game_state`, `recover_parked_mods`, `check_melon_loader_on_start` | Complete | +| 2 | Game launch error handling | Status/log surfaces | `actions.rs` 222–305, `launch.rs` GameSession deadline (30 s) | Complete | +| 3 | Auth / login / logout / `greg://` deep link | Profile card, profile menu | `actions.rs` auth cluster (680–1010), protocol handler, `drain_handoff` | Complete | +| 4 | Workshop browse / subscribe / favorite / import / sync | Browse page tabs (workshop/subscribed/favorited) | `actions.rs` browse cluster (1860–2119), `packs_apply` (3241) | Complete | +| 5 | Editor + Workshop publish + Modstore publish | Editor page upload readiness + publish | `actions.rs` `update_doc_hints` (1363), `run_editor_checks` (1422), `editor_changed` (1458), `editor_save` (1476), `editor_publish` (1498), `editor_cancel_publish` (1585), `editor_publish_modstore` (1595), `drain_publish` (1738) | Complete | +| 6 | Modstore browse / updates / install | Store page | `actions.rs` store cluster (2119–2283) | Complete | +| 7 | Local content (My Mods / Plugins / Libs / Modpacks) | Local page | `actions.rs` local cluster (2423–2586) | Complete | +| 8 | Problems inbox | Nav `problems` | `actions.rs` `refresh_problems` (2688), `problems_act` (2878) | Complete | +| 9 | Bug report (link-out only) | Nav `report-bug` → chooser dialog | `actions.rs` 148–151, 154–160, 2627–2673; `ui/main.slint` dialog 778–830 | **Partial** (no local capture) | +| 10 | Settings | Settings page | `actions.rs` settings cluster (3300–3450) | Complete | +| 11 | Support bundle | Settings | `actions.rs` `settings_make_bundle` (3431) → `greg_platform::repro::create_bundle` | Complete | +| 12 | Health checks | Settings | `actions.rs` `settings_run_checks` (3391) → `greg_loader::health` | Complete | +| 13 | Logs viewer / open logs | Settings | `actions.rs` `settings_open_logs` (3420) | Complete | +| 14 | Window chrome (min/max/close) | Title bar | `actions.rs` 1033–1058 | Complete | + +### 2.2 Domain — `greg-core` + +| # | Capability | Evidence | Status | +|---|---|---|---| +| 1 | Preferences port + memory impl | `greg-core/src/prefs.rs` | Complete | +| 2 | Feature flags / endpoints / consent gate | `greg-core/src/models/settings.rs` | Complete (telemetry env-gated, default off) | +| 3 | Telemetry payloads (Loki) | `greg-core/src/models/telemetry.rs` | Partial (types only) | +| 4 | Upload checks & readiness | `greg-core/src/upload.rs`, `models/checks.rs` | Complete | +| 5 | Docs resolution (README/BBCode/changelog) | `greg-core/src/docs/*` | Complete | +| 6 | Limits | `greg-core/src/limits.rs` | Complete | +| 7 | Log buffer | `greg-core/src/log.rs` | Complete | +| 8 | Errors | `greg-core/src/error.rs` | Complete | +| 9 | L10n (EN/DE tables, 360 keys) | `greg-core/src/l10n.rs`, `l10n_strings.rs` | Partial (EN/DE only; UI drift) | + +### 2.3 Infrastructure — `greg-platform` + +| # | Capability | Evidence | Status | +|---|---|---|---| +| 1 | File log + crash marker | `filelog.rs` | Partial (capture unwired) | +| 2 | Telemetry Loki push | `telemetry.rs` | Partial (sender not wired) | +| 3 | Support bundle | `repro.rs` | Complete | +| 4 | Workspace metadata / publish hash | `workspace.rs` | Complete | +| 5 | Preferences JSON impl | `prefs.rs` | Complete | +| 6 | Paths / instance guard / protocol / process | `paths.rs`, `instance.rs`, `protocol.rs`, `process.rs` | Complete | + +### 2.4 Steam — `greg-steam` + +| # | Capability | Evidence | Status | +|---|---|---|---| +| 1 | Workshop publish (incl. duplicate adoption, rate limit) | `service.rs`, `rate_limit.rs` | Complete | +| 2 | Browse / search / subscribed / favorited / my published / item details / gallery / import | `service.rs` | Complete | +| 3 | Backend trait + sync orchestrator + poller | `backend.rs`, `poller.rs` | Complete | +| 4 | Dependency inference | `inference.rs` | Complete | + +### 2.5 Modstore — `greg-modstore` + +| # | Capability | Evidence | Status | +|---|---|---|---| +| 1 | Auth (OAuth callback flow, sessions) | `auth_client.rs`, `intent.rs` | Complete | +| 2 | Catalog / updates / collections | `client.rs`, `collections_client.rs` | Complete | +| 3 | Publish (plan/presign/PUT/submit) | `publish.rs`, `upload_client.rs` | Complete | +| 4 | Install / updates / intents | `install.rs`, `intent.rs` | Complete | + +### 2.6 Game integration — `greg-loader` + +| # | Capability | Evidence | Status | +|---|---|---|---| +| 1 | Game adapters, launch modes, mods state, park/restore | `game.rs`, `launch.rs` | Complete | +| 2 | Health/dependency checks, native config | `health.rs`, `native_config.rs` | Complete | +| 3 | MelonLoader install, installers | `installers.rs` | Complete | +| 4 | Sync / packs / local content / discovery / content | `sync.rs`, `packs.rs`, `local_content.rs` | Complete | + +### 2.7 Cross-cutting + +| Capability | Evidence | Status | +|---|---|---| +| Crash capture | `CrashReport`, `FileLog::mark_crash` | Partial (dormant) | +| Telemetry | Loki types, secrets, gate, sender | Partial (dormant sender) | +| Support bundle | `repro.rs`, settings action | Complete | +| Problems inbox | `refresh_problems`, `problems_act` | Complete | +| Update checks (Steam workshop sync states, Modstore, MelonLoader, SteamModfix) | editor/store/launcher surfaces | Complete | + +## 3. Status Legend + +- **Complete:** end-to-end implemented and wired. +- **Partial:** building blocks exist, wiring/user flow incomplete or dormant. +- **Planned:** roadmap only. + +## 4. Report-Reuse Mapping (URA inputs) + +| Existing capability | Best URA reuse | Ready-made symbols | +|---|---|---| +| Bug chooser + tracker deep links | Replace/augment with in-app capture | `actions.rs` 148–151, 2627–2673; `ui/main.slint` 778–830 | +| Crash report struct + filelog marker | Extend `CrashReport`, wire `mark_crash` | `greg-core/src/models/misc.rs` (CrashReport), `greg-platform/src/filelog.rs` (mark_crash) | +| Support bundle | Generalize into diagnostics collection | `greg-platform/src/repro.rs` (`create_bundle`), `actions.rs` 3431 | +| Upload readiness checks | "What did the user do / in what state" context | `greg-core/src/upload.rs` (`check`), `models/checks.rs` (`is_ready_to_upload`) | +| Publish flow + rate limiter | Crash/upload correlation, stuck-upload diagnostics | `greg-steam/src/service.rs` (publish/publish_inner), `rate_limit.rs` | +| Docs resolution (README/BBCode/changelog) | Report includes version/change notes | `greg-core/src/docs/*` | +| Problems inbox | Canonical problem feed | `actions.rs` 2688, 2878 | +| Telemetry skeletons | Opt-in diagnostic emission | `greg-platform/src/telemetry.rs`, `greg-core/src/models/telemetry.rs` | +| Log buffer + file log | Sanitized log attachment | `greg-core/src/log.rs`, `greg-platform/src/filelog.rs` | +| Modstore publish/install | Mod-related failure reports | `greg-modstore/src/*` | +| Game launch/health | Crash-report context (pid, mode, loader) | `greg-loader/src/launch.rs`, `health.rs` | + +## 5. Explicitly Missing for a URA MVP + +- User-facing crash capture (stack trace, mods-in-play, session context). +- User-facing report context collection (version, platform, logs, steps, state). +- Redaction/sanitization of logs before attachment. +- Structured issue intake / preview / confirmation / submission flow. +- Privacy notice, consent storage, telemetry wiring. +- Multi-language completion beyond EN/DE (supported list has 10 cultures, only EN/DE implemented). + +## 6. Related Roadmap Items + +- `ModStoreAndManager-Roadmap.md`: F-010 (prioritized support), F-032 (source/issue/support links), F-040 (bugtracker/structured issues), F-124 (crash detection & mod bisect), F-037/F-039 (comments/threads, feedback). +- `docs/01_END_USER_GUIDE.md` (§ around 68–105): support request contents and defect-reporting instructions. +- `CONTRIBUTING.md` (§ around 39–44): issue reporting expectations. +- `.github/ISSUE_TEMPLATE/`: `bug_report.md`, `bug_report_de.yml`, `crash_report.yml`, `feature_request.yml`, `installer_problem.yml`, `performance_issue.yml`, `question.yml`, `documentation_improvement.yml`, `config.yml`. +- `current_features.md` / `functions.md`: `/api/report` (POST content/mod report), `/api/admin/gregscan-reports*` endpoints. + +## 7. Audit Conclusion + +The codebase provides a solid, mostly reusable foundation for report automation. +The gap between "building blocks exist" and "user-facing report capability" is +concentrated in wiring, context collection, redaction, and workflow design — +not in domain logic. diff --git a/AUDIT_OPEN_QUESTIONS.md b/AUDIT_OPEN_QUESTIONS.md new file mode 100644 index 0000000..4864fde --- /dev/null +++ b/AUDIT_OPEN_QUESTIONS.md @@ -0,0 +1,62 @@ +# Audit Open Questions — Universal Report Assistant + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only** — open items awaiting decisions. + +## 1. Open Technical Questions + +1. **Crash capture wiring:** should `mark_crash` be wired to panic hook / + session end / game exit paths, and what stack-trace sources are available + (native FFI boundaries, loader events)? +2. **Report context schema:** which fields are mandatory (version, platform, + OS, game/loader versions, mod/project, upload state)? +3. **Redaction policy:** which paths/usernames/secrets are redacted before + bundle submission? +4. **Attachment handling:** allowed types/sizes for user-attached files + (logs/screenshots) beyond existing upload limits? +5. **Internal report store:** use `/api/report` now or tracker deep links first? + +## 2. Open Product/UX Questions + +1. Primary assistant entry point: new nav item vs. Problems inbox vs. + in-editor vs. settings. +2. Category list and follow-up question strategy (minimal mandatory fields). +3. Confirmation/submission flow (preview, edit, submit, cancel). +4. Success/failure feedback surfaces. + +## 3. Open Design Questions + +1. Design source of truth after Slint migration (`DESIGN.md` is Avalonia-era). +2. Accessibility support status in Slint 1.18.1. +3. Visual verification needs (headless screenshot harness available). + +## 4. Open Security Questions + +1. Telemetry consent storage (prefs key vs. env) and notice copy. +2. Keyring migration timing for OAuth tokens (known limit). +3. Log redaction coverage before support-bundle sharing. + +## 5. Open Integration Questions + +1. Should report context reuse upload-check results, and where stored? +2. Rate limits for report submission (GitHub/Modstore API). +3. Multi-language rollout (EN/DE implemented; others fallback). + +## 6. Audit-Open Questions Requiring User Decision (Phase 2 Go/No-Go) + +1. Proceed to Phase 2 implementation after design alignment? +2. Target tracker: GitLab (`git.datacentermods.com/teamGreg/gregModmanager`) + deep links as implemented vs. GitHub issues. +3. Telemetry: opt-in now, or later? +4. Visual/design verification before implementation? + +## 7. Unresolved External Facts + +- Live behavior of Steam/Modstore APIs could not be probed (no live access in + audit environment) — needs owner-observed testing. +- Actual rendered font fallback and layout metrics — needs visual check. + +## 8. Audit Conclusion on Open Items + +All open questions must be resolved or explicitly deferred before Phase 2 +implementation begins; no open question authorizes implementation in this pass. diff --git a/AUDIT_OVERVIEW.md b/AUDIT_OVERVIEW.md new file mode 100644 index 0000000..5339be2 --- /dev/null +++ b/AUDIT_OVERVIEW.md @@ -0,0 +1,135 @@ +# Audit Overview — Universal Report Assistant Integration Basis + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only.** No implementation of the Universal Report Assistant +> happened during this pass. Phase 2 (implementation) requires explicit approval. + +## 1. Purpose and Scope + +This audit investigates the existing repository to build a reliable technical +and design foundation for a future **Universal Report Assistant** (URA). + +The URA shall let users create structured reports with minimal effort for: + +- Bug reports, crash reports, feature requests, improvement suggestions, +- Compatibility problems, performance problems, UI/UX problems, +- Documentation errors, installation/configuration problems, other reports. + +The assistant shall turn a simple user description into a structured report, +collect relevant additional information, process files safely, and prepare (or +later: create) a correctly formatted GitHub issue. + +**Explicitly out of scope for this audit:** implementation. The URA was not +built, wired into the UI, or shipped in this pass. + +## 2. Method and Boundaries + +- Method: source-code-first audit (static inspection), supplemented by the + maintainer's own documentation. +- Inspection mode: read-only. No builds, tests, or code generation were run as + part of this audit; no network requests, probes, or live-system tests were + performed. +- Evidence: current working-tree sources under `crates/`, `crates/greg-app/ui/`, + `docs/`, root-level product documentation, CI/build configuration. +- Limits: no visual verification (no screenshots/rendered output) was possible in + this environment; design findings are code-based and marked as such where + visual confirmation is required. + +## 3. Repository Snapshot + +| Area | State | Evidence | +|---|---|---| +| Rust workspace | Members: `greg-core`, `greg-platform`, `greg-steam`, `greg-modstore`, `greg-loader`, `greg-app`, `greg-cli`, `xtask` | `Cargo.toml` (1–56) | +| Version | `1.7.5` | `Cargo.toml` `[workspace.package]` (15) | +| UI framework | Slint `1.18.1` + `lucide-slint 1.49.1`, custom frameless chrome | `Cargo.toml` (31–34), `crates/greg-app/ui/` | +| C# implementation | Frozen under `./.reference/modmanager-old/`, not extended | AGENTS.md | +| Domain logic | Pure Rust in `greg-core` (no UI/Steam/network deps) | `crates/greg-core/Cargo.toml` | +| Infrastructure | `greg-platform` (FS/log/telemetry/workspace ports) | `crates/greg-platform/src/` | +| Steam backend | `greg-steam` (trait + WorkshopService + poller + rate limit) | `crates/greg-steam/src/` | +| Modstore | `greg-modstore` (REST + publish/install clients) | `crates/greg-modstore/src/` | +| Game integration | `greg-loader` (launch/install/health/sync) | `crates/greg-loader/src/` | +| Desktop UI | `greg-app` (binary-only: `main.rs`, `state.rs`, `worker.rs`, `actions.rs`, `shots.rs`, `ui/*.slint`) | `crates/greg-app/` | +| Headless CLI | `greg-cli` (publish/status/list) | `crates/greg-cli/src/main.rs` | + +## 4. Architecture at a Glance + +- Layered desktop client: `greg-app` (composition root + presentation) → + domain crates (`greg-core`, `greg-steam`, `greg-modstore`, `greg-loader`) → + infrastructure (`greg-platform`). Rules are documented in `AGENTS.md` and + `RUST_GRAPH.md`; `greg-core` must not depend on UI crates. +- UI state: `Arc>` behind a 150 ms UI-thread timer + (`actions::tick`), background results via `UiEvent` mpsc + job drains. +- Background execution: `JobHandle` (std thread + `catch_unwind` → failed + outcome), tokio runtime for async Modstore/auth, Steam poller thread, + background Steam probe thread. +- Error surfacing: typed errors (`CoreError`, `PlatformError`) → job outcomes → + UI status/log; auth failures via `UiEvent::AuthSessionFailed`. +- Persistence: JSON preferences (`preferences.json`, 0600), JSON workspace + metadata, SHA-256 publish hash (`compute_publish_hash`). + +Full detail: `AUDIT_ARCHITECTURE.md`. + +## 5. Security Posture (summary) + +- Threat model (`SECURITY.md`): desktop mod manager that installs foreign + content and talks to Steam/Modstore APIs with user credentials; executes no + downloaded code itself. +- Mapped controls (OWASP SAMM v2): governance, design, implementation, defect + management, verification, operations, incident management. +- Known limits: OAuth access token persisted in preferences JSON (0600, not keyring + yet); unsafe FFI usage in Steam backend (vendor `steamworks-sys`, deadline-polled + completions); `cargo audit` advisory gate; license compliance manual. +- Secrets must never enter logs or diagnostic payloads. + +During this audit, changed source paths under review were: + +- `crates/greg-app/src/actions.rs` (upload freeze fix: lock handling around + `save_metadata` / `append_log` / editor checks / publish spawn), +- `crates/greg-steam/src/service.rs` (`find_own_by_title` query scope). + +These changes were source-inspected as context; a dedicated security assessment +of the diff is part of the final security review before any commit. + +## 6. Key Cross-Cutting Findings Relevant to the URA + +1. **Stale internal documentation:** `docs/codebase/{STRUCTURE,STACK,CONVENTIONS, + TESTING,ARCHITECTURE,INTEGRATIONS}.md` still describe the frozen C# / + Avalonia implementation. Rust equivalents drifted without updating them. +2. **Design source drift:** `DESIGN.md` is written against Avalonia conventions + (`AppStrings.resx`, `IDialogService`, `MessageBox`, `ThemeVariant`, + `.axaml` styles, `ReflectionBindingExtension`) and does not match the + Slint implementation (global `Palette`, inline dialogs, std widgets, + hardcoded strings). See `AUDIT_DESIGN_SYSTEM.md`. +3. **Dormant report infrastructure:** crash capture (`FileLog::mark_crash`, + `CrashReport`), telemetry push (`push_loki`), and parts of the bug-report flow + exist but are not wired into user-facing flows yet. +4. **Background execution seams are ready:** `JobHandle`, `UiEvent`, log buffer, + support bundle and upload checks provide reusable building blocks. + +## 7. Deliverables of This Audit + +| Artifact | Content | +|---|---| +| `AUDIT_OVERVIEW.md` | This document — scope, method, snapshot, security posture | +| `AUDIT_FEATURE_INVENTORY.md` | Complete feature catalog and report-reuse map | +| `AUDIT_DESIGN_SYSTEM.md` | Design/token/a11y/localization audit | +| `AUDIT_ARCHITECTURE.md` | Architecture, threading, error/log/telemetry/persistence flows | +| `AUDIT_REPORT_ASSISTANT_PLAN.md` | Integration plan and implementation phases | +| `AUDIT_OPEN_QUESTIONS.md` | Open questions and unresolved decisions | + +Final report (audit phase 14): see the closing summary of this audit session. + +## 8. Immediate Audit Conclusion + +The repository already contains the majority of the technical building blocks a +Universal Report Assistant needs (logging, crash marker, support bundle, upload +checks, publish flow, telemetry types, problems inbox, bug-report deep links). +The main integration work lies in: connecting the dormant pieces, defining report +context schemas, redaction/privacy handling, and UI workflow design — not in +rebuilding infrastructure. + +## 9. Open Items Before Implementation + +- Visual/design verification status of the Slint UI (screenshots pending). +- Resolution state of the outstanding upload performance work. +- Approval for Phase 2 (implementation of the URA). diff --git a/AUDIT_REPORT_ASSISTANT_PLAN.md b/AUDIT_REPORT_ASSISTANT_PLAN.md new file mode 100644 index 0000000..1893854 --- /dev/null +++ b/AUDIT_REPORT_ASSISTANT_PLAN.md @@ -0,0 +1,142 @@ +# Audit — Universal Report Assistant Plan + +> Owner: gregModmanager maintainers. Evidence: source-tree audit (2026-10-28). +> Status: **Audit only — plan. Implementation approved only after user sign-off.** + +## 1. Goal + +Enable users to create structured reports for the most common problem categories +with minimal effort, and route them into the existing support/issue workflow. + +## 2. Intended User Flow (target) + +1. User opens Report Assistant (candidate entry points: new nav item, Problems + inbox entry, Settings entry, in-editor "Report" affordance). +2. User selects category (bug / crash / feature / improvement / compatibility / + performance / UI-UX / docs / install / config / security / other). +3. User describes the issue in free text (minimal mandatory fields, smart + follow-ups only when needed). +4. Assistant collects relevant context (app version, platform, OS, game/loader + state, mod/project involved, upload state, logs, steps). +5. User attaches files (logs, screenshots) within validated limits. +6. Assistant generates structured report preview (title + body, sanitized). +7. User reviews/adjusts and submits (tracker deep link / support bundle / internal + report store). +8. User receives confirmation + reference. + +## 3. UX Concept + +- Entry: keep existing `report-bug` chooser pattern as fallback; add assistant + flow. +- Progressive disclosure: category → description → context → attachments → + preview → submit. +- Reuse existing dialogs/modals patterns (inline Slint dialogs per current + implementation). +- Feedback states: collecting → ready → submitted/failed, with job-based + background where expensive (log collection). + +## 4. Design Concept + +- Must follow implemented Slint design system (`Palette`, `GregButton`, + `ListCard`, inline dialogs) — see `AUDIT_DESIGN_SYSTEM.md` for deviations. +- Off-color flat design goals (no pure black/white as regular colors, flat + surfaces, muted accents) — reconcile with implemented neumorphic touches. +- Dialogs: inline conditional rectangles per current Slint pattern. + +## 5. Technical Integration Plan + +### 5.1 Reusable components (no rebuild required) + +| Need | Reuse | Location | +|---|---|---| +| Logging | `LogBuffer`, `FileLog`, tracing | `greg-core/src/log.rs`, `greg-platform/src/filelog.rs` | +| Crash capture | `CrashReport`, session markers, `mark_crash` | `greg-core/src/models/misc.rs`, `greg-platform/src/filelog.rs` | +| Diagnostics bundle | `create_bundle` | `greg-platform/src/repro.rs` | +| Context checks | `upload::check`, `is_ready_to_upload` | `greg-core/src/upload.rs`, `models/checks.rs` | +| Publish failure context | `WorkshopService::publish*`, rate limiter | `greg-steam/src/service.rs`, `rate_limit.rs` | +| Docs context | `resolve_*_description/changelog` | `greg-core/src/docs/*` | +| Problems feed | `refresh_problems`, `problems_act` | `greg-app/src/actions.rs` | +| Bug deep links | URL building + percent-encoding | `greg-app/src/actions.rs` 148–151, 2627–2673 | +| Telemetry | types/secrets/gate/push skeleton | `greg-platform/src/telemetry.rs`, `greg-core/src/models/telemetry.rs` | +| Upload job machinery | `JobHandle`, progress/log/status | `greg-app/src/worker.rs`, `actions.rs` publish | +| Attachment validation | size limits (`MAX_MOD_BYTES` etc.) | `greg-core/src/limits.rs` | + +### 5.2 New components needed (Phase 2+) + +- Report state model (category, description, context snapshot, attachments, + sanitized text, status). +- Collector (log tail, crash marker, environment snapshot, mod context). +- Redaction layer (tokens, paths, secrets) — must run before persistence/submit. +- Preview + submit adapters (tracker deep link, support bundle, internal store). +- Consent/privacy handling. + +### 5.3 Suggested phase breakdown + +- **Phase 1 – Report context & capture primitives:** wire/extend crash capture, + log tail selection, context snapshot types in `greg-core`. +- **Phase 2 – Report data model + redaction:** new domain types, sanitizers. +- **Phase 3 – Assistant workflow state machine:** category → description → + context → attachments → preview → submit in `greg-app`. +- **Phase 4 – Collection & diagnostics:** bundle integration, job-based log + collection, support bundle hand-off. +- **Phase 5 – Submission adapters:** tracker deep links, internal report API + (`/api/report`), admin ingestion. +- **Phase 6 – Telemetry/consent:** wire opt-in telemetry, privacy notice. +- **Phase 7 – UI pages/components:** new Slint pages/modals following design + system, l10n EN/DE. +- **Phase 8 – Tests:** unit tests for redaction/context; screenshot tests for + new pages via `crates/greg-app/src/shots.rs` pattern. +- **Phase 9 – Docs/release:** user guide, changelog, issue templates alignment. + +### 5.4 Architecture fit + +- Domain additions in `greg-core`; infra in `greg-platform`; UI/workflow in + `greg-app`; submission adapters in `greg-modstore` or dedicated client. + +### 5.5 Error handling & UX requirements + +- Failures must surface via job outcomes / status strings, never silent hangs. +- Cancel flags on long operations. +- Panic containment via `JobHandle` (already present). + +## 6. Security Concept (URA-relevant) + +- Never persist or transmit raw tokens/session secrets; redaction before logs + or bundles leave the machine. +- Consent before any diagnostic upload; telemetry default off. +- Fail-closed attachment handling (type/size validation, no path traversal). +- Privacy notice + data minimization (only relevant context). +- Security review per repo `SECURITY.md`; no live probing. + +## 7. GitHub Integration Strategy (target) + +- Structured issue body (Markdown), sanitized, with version/platform context. +- Deep links already scaffolded (`BUG_REPORT_URL`, percent-encoded titles). +- Templates alignment: `bug_report.md`, `crash_report.yml`, `performance_issue.yml`, + etc. (only existing starting point). + +## 8. Test Strategy + +- Unit: redaction, context resolution, checks. +- Integration: bundle creation, publish context. +- UI: screenshot harness extension, l10n key tests (360-key convention). +- E2E: none exist (documented gap). + +## 9. Risks + +| Risk | Detail | Mitigation | +|---|---|---| +| Stale design docs | DESIGN.md Avalonia-era | Phase ordering: design alignment first | +| Dormant infrastructure | capture/telemetry not wired | Phase 1/6 wiring | +| Privacy/consent complexity | new settings surface | reuse prefs pattern | +| Multi-language scope | EN/DE only implemented | start EN/DE | +| Steam-dependent crash context | needs Steam session state | use existing backend events | + +## 10. Acceptance Criteria (audit-level) + +- No implementation performed yet; criteria defined for Phase 2 kickoff. +- All six audit artifacts mutually consistent (checked in final report). + +## 11. Open Decisions Required Before Phase 2 + +See `AUDIT_OPEN_QUESTIONS.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ff50257..5f530ab 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- FULL-RUST configuration alignment: devcontainer moved from the .NET/Avalonia + SDK image to a Rust (rustup + Slint) image with the Rust VS Code extension; + release-changelog version promotion now bumps the workspace `Cargo.toml` (the + referenced .NET project no longer exists); the stale C# GitHub build workflow + was removed and the release-train CI (promote/train + quality gates) moved + into the byte-identical Forgejo/Gitea workflow set; build wrapper, Steam/Proton + notes, and documentation were aligned to the Rust workspace. +- Residual C# era tooling removed after the full-Rust migration: obsolete + .NET/Avalonia build scripts (builder menus, PowerShell build helpers, Avalonia + package scripts, apt/local-test/Linux-package scripts) deleted; quality gates + (`scripts/greg-gates.sh`) reduced to the Rust toolchain path; Codacy + language/tool configuration switched from C# to Rust; Windows installer + signing/installer documentation aligned to the Rust-era scripts. + - Der Modmanager wurde nach einem Spielstart unbenutzbar — es gab keinen funktionierenden Weg, das Spiel aus der App zu stoppen. `stop_game()` killt laufende Game-Prozesse und reconnectet Steamworks danach; der diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4beebfe..3b5479a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,12 +6,14 @@ Thank you for your interest in contributing to gregModmanager! 1. Fork the repository (the primary instance is Forgejo — GitHub is a read-only mirror) 2. Clone your fork locally -3. Install the SDK pinned in [global.json](global.json) +3. Install the Rust stable toolchain via [rustup](https://rustup.rs) and the native + Linux X11 / font / GL development packages required by Slint + (see [the devcontainer Dockerfile](../.devcontainer/Dockerfile)) 4. Build and test: ```bash - dotnet restore GregModmanager.sln - dotnet build GregModmanager.sln -c Release - dotnet test GregModmanager.sln -c Release + cargo build --workspace + cargo test --workspace + git diff --check ``` 5. Create a feature branch, make your changes, submit a PR against `main` @@ -19,21 +21,22 @@ Thank you for your interest in contributing to gregModmanager! - Work on a feature branch — never push directly to `main` - Keep commits focused; one logical change per commit -- Use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, `docs:` …) +- Use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, `docs:`, …) - Update `CHANGELOG.md` under `[Unreleased]` for user-facing changes - Update `EXTERNAL_DEPENDENCIES.md` when adding, removing, or materially changing external packages, tools, services, or native binaries ## Code Style -- C# latest language version, `Nullable` enabled -- Follow existing naming conventions (PascalCase for public, `_camelCase` for private fields) -- Core must not reference Avalonia — UI code lives in `GregModmanager.Avalonia`, - shared logic in `GregModmanager.Core` -- Prefer `Path.Combine` and validated user paths over hard-coded platform paths; - guard platform-specific code with compile-time or runtime platform checks -- UI strings go into `AppStrings.resx` **and** `AppStrings.de.resx` at minimum -- Serialized DTOs must be registered in `AppJsonContext.cs` +- Rust: latest stable edition, rustfmt defaults, clippy `-D warnings` in CI +- Follow existing naming conventions (module/file PascalCase, items `snake_case`, + constants `SCREAMING_SNAKE_CASE`) +- Core must not depend on UI crates (`slint`); UI code lives in `crates/greg-app` +- Prefer `dirs`-provided known paths or validated user paths over hard-coded + platform paths; guard platform-specific code with `cfg(target_os = …)` in + `crates/greg-platform` +- UI strings go through the `greg-core` l10n modules (English plus German at + minimum) — no hardcoded UI strings - Keep PRs small and focused ## Reporting Issues @@ -47,7 +50,7 @@ Thank you for your interest in contributing to gregModmanager! - Target the `main` branch - Include a clear description of changes -- Ensure the solution builds without errors and all tests pass +- Ensure the workspace builds without errors and all tests pass (`cargo test --workspace`) - Keep PRs focused on a single feature or fix ## License diff --git a/DESIGN.md b/DESIGN.md index f129621..a1ff425 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,15 +1,14 @@ # gregDesktop Design System — Terminal Core -Dieses Dokument definiert die strikten Design-Vorgaben fuer alle Desktop-Anwendungen im gregFramework-Ökosystem, die auf Avalonia UI basieren. Es erweitert und konkretisiert die Architekturregeln aus `AGENTS.md` fuer alle visuellen und interaktiven Aspekte. +Dieses Dokument definiert die strikten Design-Vorgaben fuer alle Desktop-Anwendungen im gregFramework-Ökosystem, die auf Slint basieren. Es erweitert und konkretisiert die Architekturregeln aus `AGENTS.md` fuer alle visuellen und interaktiven Aspekte. --- ## 1. Design-Philosophie -- **Terminal Core**: Die UI soll sich anmaessig, praegnant und informationsdicht anfuehlen — nicht verspielt. +- **Terminal Core**: Die UI soll sich angemessen, praegnant und informationsdicht anfuehlen — nicht verspielt. - **Funktion vor Form**: Jedes Element muss einen Zweck erfuellen. Dekoration ohne Nutzen wird entfernt. - **Konsistenz ueber Customizing**: Alle greg-Apps teilen dieselben Tokens, Abstaende und Interaktionsmuster. -- **Cross-Platform Paritaet**: Windows und Linux muessen visuell identisch rendern (Pixel-Shift-Tests bei jedem Release). --- @@ -18,7 +17,7 @@ Dieses Dokument definiert die strikten Design-Vorgaben fuer alle Desktop-Anwendu Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werden. | Token | Hex | Verwendung | -| :--- | :--- | :--- | +| :--- | ---: | --- | | `SurfaceBase` | `#051424` | Haupt-Hintergrund aller Fenster und Seiten | | `SurfaceElevated` | `#0A1E2E` | Karten, Panels, Modale, Flyouts | | `SurfaceOverlay` | `#122131` | Hover-States, Trennlinien, Subtle Hervorhebungen | @@ -35,7 +34,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Keine transparenten Overlays (`Opacity < 1.0`) fuer Hintergruende. - Keine Verlaeufe (Gradients). -- Keine Schlagschatten (Drop Shadows). +- Keine Schwaeche (Drop Shadows). - Kontrastverhaeltnisse muessen mindestens WCAG AA fuer Text erfuellen (Primary auf SurfaceBase: 10.8:1). --- @@ -43,7 +42,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ## 3. Typografie | Rolle | Schriftart | Groesse | Gewicht | Zeilenabstand | -| :--- | :--- | :--- | :--- | :--- | +| :--- | --- | ---: | --- | ---: | | Display | Space Grotesk | 24 px | 700 | 32 px | | Headline | Space Grotesk | 18 px | 600 | 24 px | | Title | Inter | 14 px | 600 | 20 px | @@ -53,10 +52,8 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde **Regeln:** -- `Inter` ist die Standard-UI-Schrift; `Space Grotesk` nur fuer Ueberschriften und Marken-Elemente. -- `JetBrains Mono` ist verpflichtend fuer: Dateipfade, IDs, Versionsnummern, Dateigroessen, Zeitstempel, SHA256-Hashes, Steam-AppIDs. - Text-Transform: Nie `uppercase` fuer Labels oder Buttons. Nur fuer kleine statische Badges erlaubt. -- Text-Trimming: `TextTrimming=CharacterEllipsis` bei Pfaden und langen Titeln; Tooltip mit Volltext bei Hover. +- Text-Trimming: Ellipsis bei Pfaden und langen Titeln; Tooltip mit Volltext bei Hover. --- @@ -72,7 +69,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde **Regeln:** - Keine halben Einheiten (z.B. 4 px nur fuer Hairlines/Trennlinien erlaubt). -- Kein zentrierter Fliesstext; Linksbuendig ausgerichtet. +- Kein zentrierter Fliesstext; Linksbündig ausgerichtet. - Listen und Tabellen nutzen volle verfuegbare Breite; letzte Spalte expandiert. --- @@ -80,7 +77,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ## 5. Geometrie & Radien | Komponente | Radius | -| :--- | :--- | +| :--- | ---: | | Fenster (root) | 0 px (scharfe Kanten) | | Karten / Panels | 4 px | | Buttons | 4 px | @@ -101,11 +98,12 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ## 6. Fenster-Chrome -- `SystemDecorations="None"` ist verpflichtend fuer alle greg-Apps. -- Kein `ExtendClientAreaToDecorationsHint="True"`, wenn es das Ziehen des Fensters beeintraechtigt. -- Titelleiste ist ein `Border` mit Hintergrund `SurfaceElevated`, Hoehe 40 px. -- Titelleiste reagiert auf `PointerPressed` + `BeginMoveDrag` fuer Fenster-Verschieben. -- Fenster-Controls (Minimieren, Maximieren, Schliessen) sind custom Buttons rechts oben. +- Das Slint `Window` ist frameless (custom chrome); der Titelzeilen-Header ist ein + eigenes Element mit Hintergrund `SurfaceElevated`, Hoehe 40 px. +- Header reagiert auf Drag-Events (native WindowMoveArea / Titelleisten-Muster) — + Wayland-safe, kein manuelles `set_position`. +- Fenster-Controls (Minimieren, Maximieren, Schliessen) sind eigene Buttons rechts + oben. - Schliessen-Button zeigt `Danger`-Farbe nur bei Hover. --- @@ -115,7 +113,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ### 7.1 Buttons | Variante | Hintergrund | Rahmen | Text | -| :--- | :--- | :--- | :--- | +| :--- | --- | --- | --- | | Primary | `Primary` | none | `SurfaceBase` | | Secondary | `SurfaceElevated` | `BorderSubtle` | `OnSurface` | | Danger | `Danger` | none | `SurfaceBase` | @@ -133,9 +131,8 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Padding: 8 px. - Placeholder: `OnSurfaceMuted`. - Disabled: Rahmen `BorderSubtle`, Text `OnSurfaceMuted`. -- Keine animierten Underlines oder schwebende Labels. -### 7.3 Dropdowns / ComboBoxen +### 7.3 Dropdowns / ComboBoxes - Dropdown-Panel: `SurfaceElevated`, Radius 4 px, 1 px Rahmen `BorderSubtle`. - Ausgewaehltes Item: Hintergrund `SurfaceOverlay`. @@ -147,7 +144,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Abwechselnde Zeilenfarben: Nein. Stattdessen Trennlinien zwischen Zeilen. - Hover-Zeile: Hintergrund `SurfaceOverlay`. - Ausgewaehlte Zeile: Hintergrund `SurfaceOverlay` + linker 3 px Rand `Primary`. -- Header: `JetBrains Mono`, 11 px, `OnSurfaceMuted`, Hintergrund `SurfaceElevated`. +- Header: JetBrains Mono, 11 px, `OnSurfaceMuted`, Hintergrund `SurfaceElevated`. ### 7.5 Karten (Cards) @@ -160,7 +157,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ### 7.6 Badges - Hintergrund: `SurfaceOverlay`. -- Text: `JetBrains Mono`, 11 px. +- Text: JetBrains Mono, 11 px. - Padding: 2 px 8 px. - Radius: 2 px. @@ -168,7 +165,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Hintergrund: `SurfaceElevated`. - Rahmen: 1 px `BorderSubtle`. -- Text: `Caption`. +- Text: Caption. - Verzoegerung: 400 ms. --- @@ -176,8 +173,10 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ## 8. Dialoge & Modale - Nie native `MessageBox` verwenden. -- Pflicht: `IDialogService` mit `Window.ShowDialog`. -- Dialog-Hintergrund: halbtransparentes `SurfaceBase` (nur hier ist leichte Transparenz erlaubt: `#051424E6`). +- Pflicht: dedizierte Dialog-Elemente im Slint UI mit eigenen Buttons + (Primary rechts, destruktive links als Ghost-Button). +- Dialog-Hintergrund: halbtransparentes `SurfaceBase` (nur hier ist leichte + Transparenz erlaubt: `#051424E6`). - Dialog-Panel: `SurfaceElevated`, Radius 8 px, max. Breite 560 px. - Trennung von Titel, Inhalt und Aktionen durch Hairlines. - Primaere Aktion immer rechts unten; destruktive Aktion links davon als Ghost-Button. @@ -217,19 +216,19 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde ## 11. Lokalisierung im UI -- Alle UI-Strings muessen ueber `GregModmanager.Localization.S.Get("Key")` oder `S.Format("Key", args)` aufgeloest werden. -- Keine hardcodierten Strings in XAML oder Code-Behind. -- Neue Keys muessen zuerst in `AppStrings.resx` und `AppStrings.de.resx` eingetragen werden. -- Textlaengen-Unterschiede (z.B. Deutsch +30 %) muessen getestet werden; UI darf nicht brechen. +- Alle UI-Strings muessen ueber die `greg-core`-L10n-Module (Englisch plus Deutsch + mindestens) aufgeloest werden. +- Keine hardcodierten Strings in den Slint-Views. +- Neue Keys muessen zuerst in die L10n-Strukturen von `greg-core` eingetragen werden. +- Textlaengen-Unterschiede (z.B. Deutsch +30 %) muessen getestet werden; UI darf + nicht brechen. --- ## 12. Dateien & Assets -- `AvaloniaResource` fuer alle UI-Assets (Styles, Icons, Bilder). -- `Content` nur fuer Assets, die zur Laufzeit ersetzt werden muessen (z.B. `SubDirectoryFixer.dll`). +- Slint-`global` Variablen / Tokens fuer Farben, Abstaende, Radien. - Bilder: PNG mit Alpha; keine JPEGs fuer UI-Elemente. -- Thumbnails fuer Workshop-Items: 256x256 px, quadratisch, zentriertes Resize. --- @@ -237,9 +236,7 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Max. Dauer: 150 ms fuer UI-Transitions (Fade, Slide). - Easing: `CubicEaseOut`. -- Keine Animationen fuer DataGrid-Row-Updates (Performance). -- Keine parallaxen oder physikbasierten Animationen. -- Modale: Fade-In 100 ms; Fade-Out 75 ms. +- Keine Animationen fuer Listen-Scroll-Updates (Performance). --- @@ -247,49 +244,32 @@ Alle Farbwerte sind absolut und duerfen nicht interpoliert oder veraendert werde - Minimale Fenstergroesse: 1024 x 640 px. - Bei < 1024 px Breite: Seitenpanel kollabiert zu Icons-only. -- Bei < 800 px Breite: Zweispaltiges Layout wird einspaltig. -- Keine mobile Ansicht; Desktop-Only. --- ## 15. Verbotene Muster | Muster | Ersetzen durch | -| :--- | :--- | -| Native `MessageBox` | `IDialogService` Modale | -| `ExtendClientAreaToDecorationsHint` | Custom Chrome mit `SystemDecorations="None"` | -| `ReflectionBindingExtension` | `CompiledBindings` oder `{Binding}` mit Typpruefung | -| Opacity-Animationen | Direkte Farbwechsel | -| Runde / Pill-Buttons | Rechtecke mit 4 px Radius | +| :--- | --- | +| Native `MessageBox` | Dedizierte Dialog-Elemente im Slint UI | +| Vollstaendig runde Buttons | Rechtecke mit 4 px Radius | | Schlagschatten | Hairlines oder kontrastierende Rahmen | | Verlaeufe | Flaechenfarben | -| Emojis | Vektor-Icons | +| Emojis | Vektor-Icons (Lucide) | | Halbe Pixel-Werte (4 px ausser Trennlinien) | 8 px Grid | --- -## 16. Avalonia-Spezifika - -- `CompiledBindings` verwenden, wo moeglich; reduziert Reflektion und Trimming-Warnungen. -- `Design.DataContext` nur fuer Design-Time; keine Logik darin. -- `UserControl` nur fuer wiederverwendbare Komponenten; fuer Seiten `ContentControl` mit ViewModel-Switch. -- `Styles` in separaten `.axaml`-Dateien unter `Styles/`; keine Inline-Styles in Views. -- `ThemeVariant` nicht dynamisch aendern; greg-Apps unterstuetzen nur den dunklen Modus. - ---- - -## 17. Validierung +## 16. Validierung Bevor ein PR gemerged wird, muss geprueft werden: -1. [ ] Alle neuen UI-Strings sind in `AppStrings.resx` + `AppStrings.de.resx`. +1. [ ] Alle neuen UI-Strings sind in den `greg-core`-L10n-Strukturen (Englisch + Deutsch). 2. [ ] Keine hardcodierten Farben ausser den definierten Tokens. -3. [ ] Keine `MessageBox`-Aufrufe. -4. [ ] `CompiledBindings` aktiv, wo Typ statisch bekannt. -5. [ ] Fenster ist bei 1024x640 px und bei 1920x1080 px bedienbar. -6. [ ] Keine Avalonia-Warnungen bezueglich nicht erreichbarer XAML-Ressourcen (AVLN3001). -7. [ ] Screenshot-Diff bei visuellen Aenderungen (manuell oder via CI). +3. [ ] Keine nativen `MessageBox`-Aufrufe. +4. [ ] Fenster ist bei 1024x640 px und bei 1920x1080 px bedienbar. +5. [ ] Screenshot-Diff bei visuellen Aenderungen (manuell oder via CI). --- -###### Letzte Aktualisierung: 2026-05-05 +###### Letzte Aktualisierung: 2026-10-28 diff --git a/EXTERNAL_DEPENDENCIES.md b/EXTERNAL_DEPENDENCIES.md index 8bf8418..1ff451f 100755 --- a/EXTERNAL_DEPENDENCIES.md +++ b/EXTERNAL_DEPENDENCIES.md @@ -8,20 +8,17 @@ a release. | Component | Current use | Licence/source | | --- | --- | --- | -| .NET 9 | application runtime and SDK | [MIT](https://github.com/dotnet/runtime/blob/main/LICENSE.TXT) | -| Avalonia 12.1.1 | cross-platform desktop UI | [MIT](https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md) | -| Microsoft.Extensions.DependencyInjection 10.0.10 | service composition | [MIT](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection) | -| Cherry.Facepunch.Steamworks 2.5.0 | managed Steamworks integration | [MIT](https://github.com/Facepunch/Facepunch.Steamworks) | -| Rust `steamworks` 0.13.1 + `steamworks-sys` 0.13.0 (Rust restart, `greg-steam`) | Workshop API; `-sys` used directly only for gallery `AddItemPreviewFile`/ManualDispatch polling (no safe wrappers) | [MIT](https://github.com/NoButDrHope/steamworks-rs) | -| Rust `lucide-slint` 1.49.1 (build dependency, `greg-app`) + Slint 1.18.1 | all UI icons via `IconDisplay`/`IconSet` (`@lucide` library in `build.rs`); no unicode glyphs or emoji as icons | [MIT OR Apache-2.0](https://github.com/cnlancehu/lucide-slint) + [ISC](https://github.com/lucide-icons/lucide/blob/main/LICENSE) for the icon artwork | -| xUnit, Microsoft.NET.Test.Sdk, Coverlet | tests only | package-specific licences | +| Rust `slint` / `slint-build` 1.18.1 + `lucide-slint` 1.49.1 | cross-platform desktop UI (retained mode, single binary) | [MIT OR Apache-2.0](https://github.com/slint-ui/slint) + [ISC](https://github.com/lucide-icons/lucide/blob/main/LICENSE) for the icon artwork | +| Rust `steamworks` 0.13.1 + `steamworks-sys` 0.13.0 (`greg-steam`) | Workshop API; `-sys` used directly only for gallery `AddItemPreviewFile`/ManualDispatch polling (no safe wrappers) | [MIT](https://github.com/NoButDrHope/steamworks-rs) | +| Rust `tokio`, `reqwest` (rustls), `serde`, `serde_json`, `thiserror`, `anyhow`, `semver`, `dirs`, `clap`, `rfd`, `notify`, `zip`, `tracing` | async runtime, Modstore REST + OAuth, models/errors, validation, paths, dialogs, packaging, logging | crate-specific licences | +| Rust toolchain via rustup (stable) | build, test, release | [MIT OR Apache-2.0](https://github.com/rust-lang/rust) | ## Redistributables and platform assets | Asset | Platforms | Distribution note | | --- | --- | --- | | Steam native library (Windows `steam_api64.dll`) | Windows x64 publish output | governed by Valve Steamworks terms; do not present it as open source | -| Steam native library (Linux `libsteam_api.so` / `libsteam_api64.so`, Steamworks SDK 1.61 interface set: `SteamApps_v008`/`SteamFriends_v017`) | Linux x64 publish output, sourced from the `Facepunch.Steamworks.Dll 1.61.0` NuGet redistributable (`runtimes/linux-x64/native/libsteam_api.so`) to match `Cherry.Facepunch.Steamworks 2.5.0` | governed by Valve Steamworks terms; do not present it as open source. Newer SDK builds (e.g. 1.62.0 with `SteamApps_v009`) are **not** compatible with the managed wrapper and break `SteamClient.Init` on Linux | +| Steam native library (Linux `libsteam_api.so` / Windows `steam_api64.dll` / macOS `libsteam_api.dylib`, Steamworks SDK interface set `SteamApps_v008`/`SteamFriends_v017`) | Publish output staged next to the binaries by `cargo xtask dist`; sourced from the `steamworks-sys` build output of the Rust `steamworks` crate | governed by Valve Steamworks terms; do not present it as open source. Newer SDK builds (e.g. 1.62.0 with `SteamApps_v009`) are **not** compatible and break the Rust Workshop bindings on Linux | | Steam native libraries via `steamworks-sys` (Rust restart): Windows `steam_api64.dll`, Linux `libsteam_api.so`, macOS `libsteam_api.dylib` | staged next to the binaries by `cargo xtask dist` (rpath/`$ORIGIN` on Linux/macOS, DLL search on Windows) | governed by Valve Steamworks terms; do not present them as open source. `steam_appid.txt` (`4170200`) ships alongside | | `steam_appid.txt` | platform publish output where configured | project configuration, not a library | | bundled fonts | desktop resources | see the licence files beside the vendored fonts | @@ -37,14 +34,13 @@ notarization, Gatekeeper testing, and a native dependency review. | Tool | Purpose | | --- | --- | -| .NET SDK (pinned by `global.json`) | restore, build, test, publish | -| Inno Setup 6 | Windows Setup EXE | +| Rust toolchain via rustup (stable) | restore, build, test, release (`cargo`, `cargo xtask`) | +| Inno Setup 6 (+ Wine) | Windows Setup EXE (best-effort; CI cross-builds the portable ZIP) | | WiX CLI 5 | Windows MSI | | nfpm and native package tools | DEB, RPM, APK, and Arch packages | -| WSL | optional Windows-host Linux package adapter | | Docker/Buildx | repeatable Linux build/test targets; not a Windows or macOS GUI emulator | -| Forgejo Actions (`.forgejo/workflows/`) + Gitea Actions (`.gitea/workflows/`, byte-identical) | Existing Linux-only CI and release path; these workflows remain unchanged. | -| GitHub Actions (`.github/workflows/build-and-release.yml`) | GitHub-only release pipeline: workspace tests, native Windows x64 installer/portable build, native Linux portable/distribution packages, experimental native macOS x64/arm64 builds, and GitHub Release publication for `v*` tags. | +| Forgejo Actions (`.forgejo/workflows/`) + Gitea Actions (`.gitea/workflows/`, byte-identical) | Linux-only CI and release path (Rust: fmt, clippy, test, audit, cross-build, packages, releases). | +| GitHub | read-only archive mirror — no CI, no release automation. | ## Release formats @@ -60,7 +56,8 @@ build and is not a redistribution dependency. See ## MelonLoader integration -`src/GregModmanager.Melons/SubDirectoryFixer/` is a `net6.0` MelonLoader plugin. -Its local `MelonLoader.dll` reference is a game-environment dependency, not a -NuGet dependency. Configure the reference on the build machine without -committing machine-specific game paths or copied game binaries. +`.reference/modmanager-old/src/GregModmanager.Melons/SubDirectoryFixer/` is a +`net6.0` MelonLoader plugin (frozen, not part of the Rust workspace). Its local +`MelonLoader.dll` reference is a game-environment dependency, not a Rust crate. +Configure the reference on the build machine without committing machine-specific +game paths or copied game binaries. diff --git a/ModStoreAndManager-Roadmap.md b/ModStoreAndManager-Roadmap.md index 93c7723..e856e23 100644 --- a/ModStoreAndManager-Roadmap.md +++ b/ModStoreAndManager-Roadmap.md @@ -2511,7 +2511,7 @@ Die vorhandenen Beispiele trennen Mods, Plugins, Extensions und Bibliotheken ber - **Priorität:** P0 - **Status:** `ERLEDIGT` - **Abhängigkeiten:** Lesen von `current_features.md`, `functions.md`, `api-compatibility.md` und tatsächlichem Quellcode. -- **Betroffene Komponenten:** Desktop `Core`/Avalonia, Webapp `src/app/api`, Dokumentation. +- **Betroffene Komponenten:** Desktop `Core`/`greg-core`, Webapp `src/app/api`, Dokumentation. - **Betroffene APIs:** Auth-Verträge geprüft; keine Änderung. - **Akzeptanzkriterien:** Gap-Matrix mit Statuswerten, Evidenz und nächsten Schritten vorhanden. - **Tests:** Dokumentationsprüfung, Desktop-Build/Test und Webapp-Routeninventar. diff --git a/QUICKSTART.md b/QUICKSTART.md index f9d77e6..48533b6 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,7 +1,7 @@ # gregModmanager QuickStart Guide -> **Version:** 1.6.1 -> **Target:** Windows x64 / Linux x64 · .NET 10 · Avalonia UI +> **Version:** 1.7.5 +> **Target:** Windows x64 / Linux x64 · Rust · Slint UI > **Status:** Production Ready --- @@ -10,7 +10,7 @@ 1. Download the latest release (`-Windows.zip` or `-Linux.tar.gz`) from the [releases page](https://github.com/mleem97/gregModmanager/releases). -2. Extract it anywhere and start `GregModmanager` (`GregModmanager.exe` on Windows). +2. Extract it anywhere and start `gregmodmanager` (`gregmodmanager.exe` on Windows). 3. Make sure **Steam is running** — the Modmanager connects to the Steam client for Workshop access (AppID `4170200`, Data Center). 4. Optional: set a custom workspace folder under **Settings** (default: @@ -67,7 +67,7 @@ | **Mod Store** | Browse and subscribe Workshop items (Full / Decide-later modes) | | **Start Game** | Launch Data Center via Steam — with or without mods | | **Settings** | Workspace folder, game root, language, mod-store mode | -| **Headless** | Publish from scripts/CI: `GregModmanager publish --project --changelog ` | +| **Headless** | Publish from scripts/CI: `gregcli publish --path --changelog ` | --- @@ -75,16 +75,16 @@ Requirements: -- .NET SDK pinned in [global.json](global.json) +- Rust stable toolchain via [rustup](https://rustup.rs) +- Linux X11 / font / GL development packages for Slint (see the [devcontainer Dockerfile](../.devcontainer/Dockerfile)) - A running Steam client (only needed for Workshop features, not for build/test) ```bash git clone /gregModmanager.git cd gregModmanager -dotnet restore GregModmanager.sln -dotnet build GregModmanager.sln -c Release -dotnet test GregModmanager.sln -c Release -dotnet run --project src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj +cargo build --workspace +cargo test --workspace +cargo run -p greg-app ``` --- diff --git a/README.md b/README.md index 5b5c117..5629038 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ > Cross-platform desktop mod manager for the **gregFramework** ecosystem — local Workshop projects, Steam Workshop publishing, Mod Store, and one-click game launch for **Data Center**. -[![Build and Release](https://github.com/mleem97/gregModmanager/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/mleem97/gregModmanager/actions/workflows/build-and-release.yml) +[![Rust](https://github.com/mleem97/gregModmanager/actions/workflows/rust.yml/badge.svg)](https://github.com/mleem97/gregModmanager/actions/workflows/rust.yml) ---- [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/greg) [![gregFramework](https://img.shields.io/badge/gregFramework-Website-blue?style=for-the-badge)](https://gregframework.eu) [![License](https://img.shields.io/badge/License-Apache%202.0-green?style=for-the-badge)](LICENSE) -[![Version](https://img.shields.io/badge/Version-1.6.1-orange?style=for-the-badge)](VERSION) +[![Version](https://img.shields.io/badge/Version-1.7.5-orange?style=for-the-badge)](VERSION) [![Release](https://img.shields.io/badge/Release-Download-blueviolet?style=for-the-badge&logo=github)](https://github.com/mleem97/gregModmanager/releases) [![Sponsors](https://img.shields.io/badge/Sponsors-Support-ea4aaa?style=for-the-badge&logo=githubsponsors)](https://github.com/sponsors/mleem97) -[![DotNet](https://img.shields.io/badge/.NET-10.0-512BD4?style=for-the-badge&logo=dotnet)](global.json) -[![Avalonia](https://img.shields.io/badge/Avalonia-12.1-8c7ff0?style=for-the-badge)](https://avaloniaui.net) +[![Rust](https://img.shields.io/badge/Rust-stable-orange?style=for-the-badge&logo=rust)](https://www.rust-lang.org) +[![Slint](https://img.shields.io/badge/Slint-1.18-8c7ff0?style=for-the-badge&logo=slate&logoColor=white)](https://slang.dev) [![Windows](https://img.shields.io/badge/Windows-x64-0078D4?style=for-the-badge&logo=windows)](https://github.com/mleem97/gregModmanager/releases) [![Linux](https://img.shields.io/badge/Linux-x64-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://github.com/mleem97/gregModmanager/releases) - true - full - true - - true - true - none - false - true - en;de;fr;es;it;ja;pl;ru;zh - - - - - - - - - - win-x64 - $(DefineConstants);WINDOWS - - - linux-x64 - $(DefineConstants);LINUX - - - osx-x64 - $(DefineConstants);OSX - - - - - - - - - - - - - - - - - - - - - - steam_api64.dll - PreserveNewest - PreserveNewest - true - - - libsteam_api64.so - PreserveNewest - PreserveNewest - true - - - libsteam_api.so - PreserveNewest - PreserveNewest - true - - - SubDirectoryFixer\README.txt - PreserveNewest - PreserveNewest - - - SubDirectoryFixer\SubDirectoryFixer.dll - PreserveNewest - Always - true - - - diff --git a/.reference/modmanager-old/src/GregModmanager.Avalonia/MainWindow.axaml b/.reference/modmanager-old/src/GregModmanager.Avalonia/MainWindow.axaml deleted file mode 100644 index 63b7171..0000000 --- a/.reference/modmanager-old/src/GregModmanager.Avalonia/MainWindow.axaml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -