Files
plezy/CONTRIBUTING.md
edde746 bb3762ed63 ci: remove the Android Maestro e2e workflow
The Maestro suites remain runnable locally through scripts/run_maestro.py
and scripts/run_maestro_ci.py; drop the workflow, the test that parsed it,
and the CONTRIBUTING reference to automatic PR coverage.
2026-08-08 12:36:49 +02:00

6.5 KiB

Contributing

Getting Started

  1. Fork and clone the repository
  2. Run flutter pub get to install dependencies
  3. Run scripts/codegen.sh to generate translations and Dart model code
  4. Start developing!

Development

  • Follow Dart/Flutter conventions
  • Run dart format . to format Dart code (note: generated files like *.g.dart are excluded from CI checks)
  • Run scripts/format_native.sh --fix to format Kotlin, Swift, C++, C, Objective-C, and native headers
  • Run flutter analyze before submitting to check for issues
  • Run scripts/run_tests.sh to run the test suite (same as flutter test, but scaled to your core count)
  • Test your changes thoroughly

Code Quality Checks

The project includes automated CI checks that run on all pull requests:

  1. Code Formatting: Ensures code follows Dart and native formatting standards

    • Run locally: dart format . to format Dart files
    • Run locally: scripts/format_native.sh --fix to format native files
    • Note: CI only checks non-generated files (excludes .g.dart, .freezed.dart)
    • Generated files are reformatted automatically by build tools
  2. Static Analysis: Checks for code issues and potential bugs

    • Run locally: flutter analyze
    • Note: CI excludes generated files from analysis (configured in analysis_options.yaml)
  3. Generated Code: Ensures generated translations and model files are current

    • Run locally: scripts/codegen.sh --check
  4. Tests: Runs unit and widget tests (when available)

    • Run locally: scripts/run_tests.sh
    • This is flutter test with -j set to the core count. The default is half your cores, which leaves most of the machine idle because the suite is dominated by per-file compilation. Arguments are forwarded, so scripts/run_tests.sh test/widgets/some_test.dart works.

All these checks must pass before your changes can be merged.

Maestro end-to-end tests

Android E2E tests use Maestro against a disposable, pre-seeded Jellyfin container.

Prerequisites: Java 17, Flutter and Android SDK/platform tools, a running Android emulator, Docker, and the Maestro CLI.

Run the suites from the repository root (py -3 can replace python3 on Windows):

python3 scripts/run_maestro.py basic    # Basic user flows
python3 scripts/run_maestro.py catalog  # Catalog and music flows
python3 scripts/run_maestro.py media    # Codec playback and track selection

Run one flow with --flow:

python3 scripts/run_maestro.py basic --flow .maestro/flows/04_search.yaml

Use --skip-build to reuse the debug APK and --skip-jellyfin-build to reuse the Jellyfin image. Set --device <adb-serial> when multiple devices are connected; physical devices also require --adb-reverse.

Top-level flows live in .maestro/flows/, shared setup in .maestro/subflows/, and focused regressions in .maestro/regression_flows/. Automatic groups are declared in scripts/run_maestro_ci.py::GROUPS. Every top-level regression flow must be registered either there or in DESTRUCTIVE_MANUAL_TARGETS; reusable subflows are not independent tests. A manual-only classification must state why the flow cannot run automatically.

The profile-isolation and profile-teardown regressions create and remove profile connections, so they are a destructive manual target rather than an automatic group. Run them only against the pre-seeded Jellyfin fixture and a disposable emulator, using the required opt-in:

python3 scripts/run_maestro_ci.py profile-regressions --disposable-emulator

The target refuses to start without --disposable-emulator. Each profile flow writes to its own Jellyfin log and diagnostics directory under build/maestro-profile-regressions/.

Production container image updates

Production images in server/Dockerfile and server/docker-compose.yml use a readable version or source-revision tag plus an authoritative multi-platform index digest. The adjacent Platforms declaration records the supported linux/amd64 and linux/arm64 variants. Never replace these references with a mutable tag or a single-platform child manifest.

Update a production image only through a reviewed change:

  1. For the Bugs service, first record the running container's image ID, repository digest, platform, and OCI source revision without printing its environment. Prefer that reviewed running identity; selecting anything else is a service upgrade, not a routine pin refresh.
  2. Review the upstream source revision and changelog, provenance, vulnerability results, and manifest contents. Resolve the readable tag and digest-qualified reference independently and confirm they identify the same OCI index in two clean caches. The index must contain both declared platforms; provenance/attestation descriptors do not count as runnable platforms.
  3. Change the readable tag, full sha256 index digest, and adjacent platform declaration together. Include the old and new identities, manifest/platform evidence, review findings, smoke results, and rollback notes in the change.
  4. Before changing the Bugs digest, exercise it with non-production configuration and a disposable volume. Review migrations, take a restorable bugs_data backup, then validate a cloned volume. A forward-only migration rolls back with the prior digest and pre-change backup, not by changing the image reference alone.
  5. Run python3 scripts/check_container_image_pins.py, python3 scripts/test_check_container_image_pins.py, and (cd server && go test ./...). Inspect the rendered Compose configuration and rebuilt images locally without exposing configuration values. Do not publish or deploy from a review checkout, and never fall back to latest when a digest is unavailable.

Internationalization (i18n)

This project uses slang for internationalization with JSON files.

Adding New Strings

  1. Add your string to lib/i18n/strings.i18n.json:

    {
      "section": {
        "myNewString": "My new text"
      }
    }
    
  2. Run dart run slang to regenerate translation files

  3. Use in your code:

    Text(t.section.myNewString)
    

Adding New Languages

  1. Create new JSON file: lib/i18n/[locale].i18n.json
  2. Copy structure from en.i18n.json and translate values
  3. Run dart run slang to regenerate files

Guidelines

  • Organize strings logically in nested objects
  • Use camelCase for keys
  • Keep strings concise and clear
  • Always run dart run slang after changes