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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .claude/rules/generated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
paths:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "**/*.mocks.dart"
- "lib/core/localization/generated/**"
---

# Generated files — do not edit

This file is generated. Never edit by hand. Edit the source and regenerate.

## How to find the source
- `*.g.dart` → JSON-serializable / retrofit / envied / hive source in the sibling file. Look for `@JsonSerializable`, `@RestApi`, `@Envied`, `@HiveType`.
- `*.freezed.dart` → freezed model with `@freezed` in the sibling `.dart` file.
- `*.mocks.dart` → mockito `@GenerateMocks([...])` annotation in the sibling test file.
- `lib/core/localization/generated/**` → generated by `flutter gen-l10n` from `.arb` files (Flutter `generate: true`).

## Regenerate
- One-shot: `dart run build_runner build --delete-conflicting-outputs`
- Watch: `dart run build_runner watch --delete-conflicting-outputs`
- Localization: `flutter gen-l10n` (or `flutter pub get` triggers it when `generate: true`).
- Always format after generation: `dart format lib/ test/`.

## Hard rules
- Never paste fixes into a generated file — they are overwritten on next codegen.
- If a generated file is missing, run build_runner — do not create it by hand.
- Generated files are excluded from `flutter analyze` via `analysis_options.yaml`, so analyzer silence is not a green light to edit them.
29 changes: 29 additions & 0 deletions .claude/rules/state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
paths:
- "lib/**/*_cubit.dart"
- "lib/**/*_state.dart"
- "lib/**/presentation/cubits/**/*.dart"
---

# State management — flutter_bloc (Cubit)

This repo uses **Cubit** from `flutter_bloc` 9. Do not introduce `Bloc`, `ChangeNotifier`, `ValueNotifier`, Riverpod, or GetX.

## File / class layout
- File: `<name>_cubit.dart` in `lib/features/<feature>/presentation/cubits/`.
- Companion state: `<name>_state.dart` declared as `part of '<name>_cubit.dart'` (see `sign_in_cubit.dart` / `sign_in_state.dart`).
- Cubit class: `final class <Name>Cubit extends Cubit<<Name>State>`.
- State: `@freezed` sealed class with named factory constructors (`initial`, `inProgress`, `succeed`, `failed`, …). After editing, run build_runner.
- Each cubit constructor takes its dependencies (repository / service) as positional `final` fields. DI wires them in `lib/core/di/di.dart`.

## Inside a cubit
- Returns from repositories are `Result<T, F extends Failure>`. Switch on `Success(:final data)` / `Failure(:final error)` and emit the matching state — see `sign_in_cubit.dart`.
- Guard re-entrancy: check `state.maybeWhen(inProgress: () => true, orElse: () => false)` before starting an async op.
- After `await`, check `if (isClosed) return;` before `emit`.
- Do NOT perform navigation, snackbars, or dialogs inside the cubit — UI listens to state and reacts.
- Do NOT call `dio` / HTTP directly — go through a repository in `data/repositories/`.

## Tests
- Use `bloc_test` (already a dev dep). One file per cubit in `test/features/<feature>/presentation/cubits/`.
- Mock repositories with `mockito` (`@GenerateMocks([AuthRepository])` → `.mocks.dart`).
- Cover happy path + each `Failure` branch.
30 changes: 30 additions & 0 deletions .claude/rules/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
paths:
- "test/**/*.dart"
---

# Test rules

## Layout
- `test/` mirrors `lib/` 1:1 (e.g. `lib/features/auth/presentation/cubits/sign_in_cubit.dart` → `test/features/auth/presentation/cubits/sign_in_cubit_test.dart`).
- Shared fixtures per feature in `test/features/<feature>/support/<feature>_dto_fixtures.dart`.
- One test file per production file, suffix `_test.dart`.

## Frameworks
- `flutter_test` for widget tests.
- `mockito` 5 for mocks (this repo does **not** use `mocktail`). Declare via `@GenerateMocks([Foo, Bar])` at the top of the test file; mocks land in the sibling `<name>_test.mocks.dart`. Regenerate with `dart run build_runner build --delete-conflicting-outputs`.
- `bloc_test` 10 for cubits — use `blocTest<Cubit, State>(...)` with `build`, `act`, `expect`.
- `fake_async` for time-sensitive code instead of real delays.

## What to cover
- Every cubit: happy path + each `Failure` branch from the repository.
- Every repository: success + each `Failure` mapping from `DioException` / parsing errors.
- Every mapper: DTO ↔ domain round-trip.
- Validators (`presentation/validators/`): valid + each invalid branch.
- Network interceptors / mappers: see existing tests under `test/core/network/` for the pattern.

## Don'ts
- No `Future.delayed` for timing tests — use `fake_async` or pump helpers.
- No real network — mock the API client (`*_api.dart` retrofit interface).
- No real `FlutterSecureStorage` / `Hive` — mock the service wrapper from `lib/core/services/`.
- Do not edit `*.mocks.dart` — regenerate via build_runner.
40 changes: 40 additions & 0 deletions .claude/rules/widgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
paths:
- "lib/**/*_page.dart"
- "lib/**/*_widget.dart"
- "lib/**/presentation/**/*.dart"
- "lib/uikit/**/*.dart"
---

# Widget rules

## Naming
- Screens are `_page.dart` in `lib/features/<feature>/presentation/pages/` (this repo does **not** use `_screen.dart`).
- Reusable widgets local to a feature: `lib/features/<feature>/presentation/widgets/<name>_widget.dart`.
- Cross-feature reusable widgets / design-system: `lib/uikit/<category>/` (`buttons/`, `dialogs/`, `inputs/`, `cards/`, `images/`, `menus/`, `themes/`).
- A page that needs DI/BlocProvider wiring uses a separate `<name>_page_builder.dart` next to the page (see `sign_up_page_builder.dart`, `verify_reset_code_page_builder.dart`).
- Route argument holders live next to the page: `<name>_route_args.dart`.

## Widget construction
- `StatelessWidget` by default. Use `StatefulWidget` only for local-only state that can't live in a cubit (focus nodes, controllers, animations).
- `const` constructors everywhere possible — analyzer warns on `prefer_const_constructors` / `prefer_const_literals_to_create_immutables`.
- Use `super.key` (`use_super_parameters` lint enforced).
- Constructor params: named, `required` for non-nullable, no positional booleans (`avoid_positional_boolean_parameters`).
- Single quotes for strings (`prefer_single_quotes`).
- Imports inside `lib/` are relative (`prefer_relative_imports`).
- Every public member needs a dartdoc (`///`) — `public_member_api_docs` is enabled.
- `flutter_hooks` is **not** a dependency — do not use hooks.

## Composition
- If a sub-widget is used in only one place, make it a private `class _Foo extends StatelessWidget` in the same file.
- Split `build()` when it exceeds ~100 lines into private sub-widgets or factory methods.
- Wire cubits with `BlocProvider` / `BlocBuilder` / `BlocListener` / `BlocConsumer`. Read repositories from `di<T>()` (get_it) inside the page builder, not inside `build()`.
- Do not pass `BuildContext` through fields. Do not capture it across `await` without an `if (!context.mounted) return;` check.

## Navigation
- Use `context.go(...)` / `context.push(...)` / `context.pop()` only. Route paths come from `AppRoutePaths` in `lib/core/router/router_paths.dart`.
- New routes go through `lib/core/router/router.dart` — do not call `Navigator` directly.

## Theming / assets
- Colors, gradients, text styles live under `lib/uikit/themes/`. Don't hardcode `Color(0x...)` or `TextStyle(...)` in feature widgets.
- Image assets via `flutter_svg` (`SvgPicture.asset`) or `cached_network_image` for network. Asset paths come from `lib/core/constants/`.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,6 @@ android/app/google-services.json

# Generated localization
lib/core/localization/generated/

# Claude Code personal config
CLAUDE.local.md
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Profile saved cards section for the authenticated `/profile` tab, including dedicated cards API/repository flow, saved-cards list rendering, add-card dialog with manual card form, default-card command, delete-card confirmation, and local refresh after successful actions.
- Authenticated subscriptions catalog screen, including dedicated subscriptions route, catalog Cubit, card UI with normalized remote images, and a profile CTA for opening available subscription plans.
- Authenticated subscription details and payment flow, including a dedicated details route, catalog-backed item resolution, manual-card payment dialog, and redirect to `/profile` after successful purchase.
- Claude Code project config: `CLAUDE.md` with stack, commands, conventions, hard rules and PR workflow; path-scoped rules for generated files, state management, widgets, and tests; `docs/architecture.md` with full architecture reference.

### Changed

Expand Down
89 changes: 89 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# moveUP — mobile fitness app

Mobile client for the moveUP fitness platform. Targets iOS and Android.

## Stack
- Flutter 3.44.0 (3.41.0 in CI), Dart SDK `>=3.10.0 <4.0.0`, channel stable
- State: `flutter_bloc` 9 (Cubit pattern, not Bloc)
- DI: `get_it` 9 + `provider` 6 (Provider used for widget-tree DI only, not state)
- Routing: `go_router` 17 (paths in `lib/core/router/router_paths.dart`)
- HTTP: `dio` 5 + `retrofit` 4 + `dio_cookie_manager` + `cookie_jar`
- Models / codegen: `freezed` 3 + `json_serializable` 6 + `retrofit_generator` + `envied_generator`
- Local storage: `hive_ce_flutter` 2 + `flutter_secure_storage` 10
- Env: `envied` (obfuscated, reads `.env` at root)
- Tests: `flutter_test` + `mockito` 5 + `bloc_test` 10 + `fake_async`
- Logging: `logger`

## Commands
- `flutter pub get` — install deps
- `dart run build_runner build --delete-conflicting-outputs` — codegen one-shot
- `dart run build_runner watch --delete-conflicting-outputs` — codegen watch
- `dart format lib/ test/` — format (page width 100, trailing commas preserved)
- `flutter analyze --fatal-infos` — lints (CI fails on infos)
- `flutter test` / `flutter test --coverage` — unit + widget tests
- `flutter run` — run on attached device / simulator
- `dart doc --output doc/api` — generate dartdoc

## Repo layout
- `lib/core/` — `di/`, `router/`, `env/`, `network/`, `services/`, `failures/`, `result/`, `constants/`, `utils/`
- `lib/features/<feature>/` — feature-first, layers `data/` + `domain/` + `presentation/` (+ optional `support/`)
- `lib/features/<feature>/presentation/` — `pages/`, `cubits/`, `widgets/`, `validators/`
- `lib/uikit/` — shared `buttons/`, `dialogs/`, `images/`, `themes/`, `inputs/`, `cards/`, `menus/`
- `lib/main.dart` → `lib/runner.dart` — single entrypoint, no flavors
- `test/` — mirror of `lib/`
- `assets/` — `icons/`, `images/`, `splash/`, `legal/`, `fonts/montserrat/`

## Conventions
- Files: `snake_case`. Suffixes used in this repo: `_page.dart` (screens, **not** `_screen.dart`), `_widget.dart`, `_cubit.dart`, `_state.dart`, `_repository.dart`, `_dto.dart`, `_mapper.dart`, `_failure.dart`, `_api_client.dart`.
- State files: `part of` cubit, generated as `<name>_state.dart`; cubit class often `final class`.
- Imports: relative (`prefer_relative_imports` enabled) within `lib/`.
- Strings: single quotes (`prefer_single_quotes`).
- `const` everywhere possible (warning if missed).
- All public members require dartdoc (`public_member_api_docs`).
- Commits: Conventional Commits (`feat(auth): ...`, `fix(...): ...`, `chore(...): ...`).
- Branches: `feat/<slug>`, `fix/<slug>`, `chore/<slug>`. Default branch is `develop`; release PRs target `main`.

## Hard rules
- Never edit generated: `**/*.g.dart`, `**/*.freezed.dart`, `**/*.mocks.dart`, `lib/core/localization/generated/**`. After changing a `@freezed` / `@JsonSerializable` / `@RestApi` / `@Envied` source → run `dart run build_runner build --delete-conflicting-outputs`.
- Don't add or upgrade dependencies in `pubspec.yaml` without explicit request.
- Don't touch `ios/`, `android/`, `Dockerfile`, `.github/workflows/**` without explicit request.
- Don't use `Navigator.push` / `Navigator.pop` directly — use `context.go` / `context.push` / `context.pop` (go_router). Add new routes in `lib/core/router/router_paths.dart` + `router.dart`.
- Don't introduce a new state-management lib. Use Cubit from `flutter_bloc`.
- Don't bypass the `Result<T, F>` / `Failure` pattern in `lib/core/result/` + `lib/core/failures/` — repositories return `Result`, cubits switch on `Success` / `Failure`.

## Scope discipline
- Modify only files relevant to the current task.
- See an unrelated issue? Mention it in the final message — don't fix it. If user wants leave `// TODO(claude): <description>`.
- Large refactor (>100 lines or 3+ files): outline plan → wait for confirmation → code.
- Don't mass-rename or reformat without explicit request.

## When to ask
- Ambiguous requirements (more than one reasonable interpretation).
- Before adding any new package to `pubspec.yaml`.
- Before changing a public API of a widget / cubit / repository used in 3+ places.
- Before changing the navigation graph (new top-level route, redirect logic).
- Task touches >5 files and the plan is non-obvious.

## PR
- Before `gh pr create`: `dart format lib/ test/ && flutter analyze --fatal-infos && flutter test`.
- Target branch: `develop` (CI runs `develop-analysis.yml`). Release PRs target `main` (`main.yml`, full Android build).
- Title: Conventional Commits, under 70 chars, without body (1 line only).
- Body: **Why** / **What** / **How to test**, 1–3 lines each. Link issue `Closes #N`.
- Don't merge yourself.

## Keeping docs current
- New feature establishes a pattern → add/update the relevant `.claude/rules/*.md`.
- Dependency or layer changes → update `Stack` above and `docs/architecture.md`.
- One fact, one place: if a rule lives in `CLAUDE.md` and in a rules file, remove one and link.
- If you corrected Claude on the same thing twice — that's a signal to write a rule.

## Extended docs (NOT auto-loaded)
- `docs/architecture.md` — feature-first layers, data flow
- `README.md` — project overview, setup, Docker build
- `CHANGELOG.md` — release history

## Env notes
- `.env` at repo root (gitignored). Required: `API_URL` (e.g. `http://127.0.0.1:8000/`). CI falls back to `http://127.0.0.1:8000/` if secret missing.
- Envied is obfuscated — after changing `.env` regenerate via build_runner.
- No flavors. Single `main.dart` → `runner.dart` bootstrap.
- Local extras live in `CLAUDE.local.md` (gitignored).
Loading
Loading