diff --git a/AGENTS.md b/AGENTS.md index 6ac5b315..9ff8a3c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,49 +1,46 @@ # AGENTS.md -Flutter mobile app for browsing Twitch on iOS and Android. Uses MobX for state management with code generation. +Flutter Twitch client for iOS and Android. MobX with `build_runner`. The landing page is `web/` (own `AGENTS.md`). Flutter is pinned in `pubspec.yaml` `environment.flutter` — bump that one field; CI reads it via `flutter-version-file`. ## Commands ```bash -flutter pub get # Install dependencies -flutter run --dart-define=CLIENT_ID=ID --dart-define=SECRET=SECRET # Run with Twitch credentials -flutter analyze # Static analysis -flutter test # Run tests -dart run build_runner build # Regenerate .g.dart files (MobX/JSON models) +flutter pub get +flutter run --dart-define-from-file=.env # local; .env is gitignored +flutter run --dart-define=CLIENT_ID=ID --dart-define=SECRET=SECRET +flutter analyze +flutter test +flutter test test/path/to/file.dart +dart run build_runner build # after MobX / @JsonSerializable changes ``` -`--delete-conflicting-outputs` was removed in build_runner 2.15 and is now ignored — conflicting outputs are handled automatically. +`--delete-conflicting-outputs` is ignored as of build_runner 2.15. Don't add it. -## Source Structure +## Layout -- `lib/apis/` — API services (all extend `BaseApiClient`) -- `lib/models/` — Data models (`@JsonSerializable` with `.g.dart` codegen) -- `lib/screens/{feature}/` — Feature screens with co-located `stores/` subdirectories -- `lib/stores/` — Global stores only (e.g., `global_assets_store.dart`) -- `lib/services/` — App-level services -- `lib/widgets/` — Shared widgets -- `lib/utils/` — Utility functions +- `lib/apis/` — API clients extend `BaseApiClient` +- `lib/models/` — data models; `@JsonSerializable` ones commit `.g.dart` +- `lib/screens/{feature}/` — screens; feature stores in co-located `stores/` +- `lib/stores/` — global stores only (`global_assets_store.dart`) +- `test/` mirrors `lib/`; shared fixtures in `test/fixtures/` -## Architecture +Don't add top-level store files or standalone API classes. Canonical MobX store: `lib/screens/settings/stores/auth_store.dart`. Extract state, reactions, and actions into stores when it keeps widgets on rendering. -- Feature stores live in `lib/screens/{feature}/stores/` — don't create new top-level store files (only `lib/stores/` holds global stores) -- All API services extend `BaseApiClient` (`lib/apis/base_api_client.dart`) — don't create standalone API classes -- MobX stores use a generated mixin pattern — see `lib/screens/settings/stores/auth_store.dart` for the canonical example - -## Code Opinions - -- Extract related/grouped logic (state, reactions, actions) into dedicated stores when it improves readability — keep widgets focused on rendering -- Default to writing tests: new or changed logic (stores, API clients, parsing, utils) ships with tests; when touching existing tested code, update or extend its tests. Pure UI/rendering or config changes may skip. +New or changed logic (stores, API clients, parsing, utils) ships with tests; extend existing tests when touching tested code. Pure UI or config may skip. HTTP: `http_mock_adapter` (`DioAdapter`) with full URLs. Otherwise `mocktail`. ## Gotchas -- After changing MobX stores or `@JsonSerializable` models, regenerate with `dart run build_runner build`. Never edit `.g.dart` files directly. Commit `.g.dart` files to source control. -- The secure storage cleanup in `main.dart` looks unnecessary but handles an Android/iOS edge case where secure storage persists after uninstall. Don't remove it. -- Several deps in `pubspec.yaml` are git-pinned forks carrying compatibility patches (`extended_text_field`, `better_native_video_player`, `simple_pip_mode`). When upgrading dependencies, don't swap them back to pub.dev releases. +- Commit regenerated `.g.dart` files. Never edit them. CI fails the PR if they're stale. +- `main.dart` secure-storage `deleteAll()` looks redundant; it handles Android/iOS storage surviving uninstall. Don't remove it. +- `flutter_secure_storage` stays on v9 (v10 shipped in 5.0.0 and was reverted in 5.0.1 over an Android 14 startup freeze). That also holds `package_info_plus` v9 and `device_info_plus` v12 (win32). Don't jump them in a drive-by upgrade. +- Git-pinned forks in `pubspec.yaml` (`extended_text_field`, `better_native_video_player`, `simple_pip_mode`) carry patches. Don't swap them back to pub.dev. +- `android/gradle.properties`: keep `android.builtInKotlin=false` and `android.newDsl=false` so plugins that still apply KGP build on AGP 9. +- `ios/Runner/AppDelegate.swift` is customized (`CookieExtractorPlugin` + `FlutterImplicitEngineDelegate`). Flutter will not auto-migrate UIScene; don't replace the delegate with the template. +- `android/.../MainActivity.kt` extends `PipCallbackHelperActivityWrapper` and registers `CookieExtractorPlugin` by hand. Don't revert it to `FlutterActivity`. +- Helix OAuth (`Bearer`) and the Twitch web GQL cookie are different tokens. Native playback uses the cookie; don't send the Helix token to GQL/Usher. +- `syncedChatDelay` is runtime-only (`includeFromJson/toJson: false`). Don't persist it with the other settings. +- Don't remove the `ALLOW_DEBUG_SIGNED_RELEASE` branch in `android/app/build.gradle.kts`. Real releases must still fail if `key.properties` is missing. -## Testing +## Releases -- `flutter test` runs all tests; `flutter test test/path/to/file.dart` for a single file -- HTTP mocking: `http_mock_adapter` (`DioAdapter`) — use full URLs in `onGet`/`onPost` -- General mocking: `mocktail` (no codegen required) -- Test layout mirrors `lib/` (`test/apis/`, `test/models/`, `test/screens/`); shared fixtures live in `test/fixtures/` (e.g., `irc_messages.dart`, `api_responses.dart`) +When cutting a store or GitHub release, read `docs/release.md` and follow it. That playbook is repo-local, not a global skill. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 00000000..479f9a14 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,55 @@ +# Release — version, notes, tag, then stores + +Cut a GitHub + Play + App Store release from `main`. Not a changelog dump. Not a docs rewrite. + +This playbook lives in the repo and loads only when you open this workspace and ask to release. It is not a global skill. + +Flow: **preflight → bump + notes → commit on `main` → tag → wait for workflows → publish the GitHub draft.** + +The tag is the mechanical trigger. This file is the judgment pass around it. + +## Preflight + +Main CI green on current `HEAD`. Then `gh workflow run "Release dry-run"` on that `main` — the monthly heartbeat is not enough after a toolchain bump. + +Click any pending Apple agreement first (`developer.apple.com/account`, then App Store Connect Business) or the iOS job 403s. There is no API for that. On Play, glance Console Home and **Policy and programs > App content** (`Needs attention`); a DDA banner or new declaration can block the AAB the same way. + +## Version + +Release commit on `main` is only `pubspec.yaml` (`x.y.z+build`, bump both) and `assets/release-notes.md`. Message: `release vX.Y.Z`. + +Features since last tag → minor. Fixes only → patch. Build number +1. + +Do not put `AGENTS.md` or other docs in that commit. + +## Notes + +Write for someone opening the app, not the commit log. Match the latest shipped version's voice (`Added` / `Improved` / `Fixed`). Skip CI, Flutter upgrades, codegen, and anything a user wouldn't notice; the GitHub compare link is the rest. + +- Features that didn't exist last release. Improvements only for behavior that already shipped — gating a new feature (opt-in permissions, an Enable flow) is not an improvement. +- If you can't remember the user-facing symptom, it doesn't belong. +- `[Android]` / `[iOS]` on platform-only lines. Cluster them: one tagged line at the end of its section, two or more adjacent. +- Credit GitHub PR authors as `(by @user on GitHub)` on each bullet they landed. Not AI, not issue reporters. The GitHub release body strips ` on GitHub` as redundant. + +Example shape (from v5.1.0): `Added a new experimental native video player option with quality selection…` not `Bumped better_native_video_player`. + +## Tag + +```bash +git commit -m "release vX.Y.Z" +git push origin main +git tag vX.Y.Z # lightweight, on that commit +git push origin vX.Y.Z # v*.*.* triggers both workflows +``` + +Do not `gh release create`. **Draft GitHub release** attaches `frosty-vX.Y.Z.apk` and fills the body from this version's section in `assets/release-notes.md`. **Build and release Frosty** uploads the Play AAB and submits iOS for review (`automatic_release: true`). + +When the draft has the APK: + +```bash +gh release edit vX.Y.Z --draft=false +``` + +The README latest-release badge stays stale until that publish. Store listing copy already points at GitHub — don't add per-version Play changelog files. + +Do not push the tag until the release commit is on `origin/main`.