# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

CoinVision — a production cryptocurrency exchange (CEX) Flutter app targeting Android and iOS.

## Commands

```bash
# Run
flutter run

# Build
flutter build apk --debug --target-platform android-arm64   # Android debug
flutter build apk --release --target-platform android-arm64 # Android release
flutter build ipa --release --export-method=ad-hoc          # iOS ad-hoc
flutter build ipa --release                                   # iOS release

# Code generation (required after model/provider changes)
dart run build_runner build --delete-conflicting-outputs
dart run build_runner watch   # watch mode

# Format & analyze
dart format .
flutter analyze

# Test
flutter test

# Reset
flutter clean && flutter pub get
```

### Runtime overrides via `--dart-define`
```bash
flutter run --dart-define=API_BASE_URL=https://custom.api.com/api/
flutter run --dart-define=WS_URL=wss://custom.ws.com/market
flutter run --dart-define=ENABLE_MOCK=true
```

## Architecture

Strict 3-layer clean architecture. **Never bypass layers.**

```
用户交互 → Notifier → Repository → Service/API
                                        ↓
UI (Widget) ←── State (immutable) ←── Notifier ←──
```

| Layer | Location | Responsibility |
|-------|----------|----------------|
| **Presentation** | `lib/presentation/` | Rendering & user interaction only. No business logic. |
| **Provider (ViewModel)** | `lib/providers/` | Riverpod Notifiers. Expose immutable State + methods. |
| **Data** | `lib/data/` | Services (stateless HTTP), Repositories (SSOT, caching), Models (Freezed) |
| **Core** | `lib/core/` | Dio client, GoRouter, theme, l10n, utils. No business logic. |

### State Management — Riverpod 2
- Use `@riverpod` annotation for code generation
- State objects must be **immutable Freezed classes**, updated with `copyWith`
- In Widgets: `ref.watch()` for state, `ref.read(…notifier)` for actions
- Notifier re-initialization: `ref.invalidate(someProvider)`

### Navigation — GoRouter 14
- All routes defined in `lib/core/router/app_router.dart`
- StatefulShellRoute with 5 bottom-nav branches (preserves tab state)
- Auth guard: routes under `/asset`, `/user/*`, `/broker/*` require login
- Session expiry: API error code `4000` triggers logout dialog
- **Always use `context.push()` / `context.go()`** — never `Navigator.push`

### Networking — Dio
- Central client in `lib/core/network/dio_client.dart` with interceptors for token injection, error wrapping, and node failover
- Multi-node resilience: `lib/providers/node_provider.dart` — speed tests nodes, circuit-breaks after 3 failures for 60s
- Timeouts: connect 15s, receive 15s
- Session error (code 4000) → auto logout; interceptor suppresses duplicate navigation

### Data Models — Freezed
- All domain models use `@freezed` with `fromJson`/`toJson`
- Run `build_runner` after any model or provider annotation change
- Generated files (`*.freezed.dart`, `*.g.dart`) are committed

### Storage
| Data | Storage |
|------|---------|
| Auth tokens | `flutter_secure_storage` (platform vault) |
| User preferences | `SharedPreferences` |
| Local caching | Hive |

### Real-time Data — WebSocket
- `web_socket_channel` for market data streams
- Lifecycle managed in `lib/providers/ws_provider.dart`
- Manual reconnect logic on disconnect

## Key Conventions

### New feature development order (required)
1. Define Freezed domain model (`data/models/`)
2. Implement Service — stateless HTTP only (`data/services/`)
3. Implement Repository — SSOT, caching, data transform (`data/repositories/`)
4. Implement Notifier — Freezed State + methods (`providers/`)
5. Implement Screen/Widget — ConsumerWidget, 3-state UI (`presentation/`)

### UI rules
- Widgets use `ConsumerWidget` or `ConsumerStatefulWidget`
- Always handle **loading / error / empty** three states
- Use `AppColors` — rise: `#0ecb81`, fall: `#f6465d`
- Use `formatPrice()` / `formatChange()` from `core/utils/` — never hardcode decimal places
- Layout scaled with `flutter_screenutil` (design base: 375×812)
- Use `ListView.builder` for lists; `const` constructors for static sub-widgets
- BottomSheets via `showModalBottomSheet` with `isScrollControlled: true`

### Localization
5 locales: `zh`, `zh_TW`, `en`, `ja`, `ko`. ARB files in `lib/core/l10n/`. Config in `l10n.yaml`.

### Environment / Build
- Debug config: `lib/core/config/env_debug.dart`, Android package `com.ibit.app.test`
- Release config: `lib/core/config/env_release.dart`, Android package `com.ibit.app`
- Android signing: keystore at `android/app/keystore`, password in `android/key.properties` (gitignored)
- Version format in `pubspec.yaml`: `major.minor.patch+major*10000+minor*100+patch`

## Git Commit Convention

`<type>(<scope>): <subject>` (Conventional Commits)

Scopes: `auth`, `market`, `trading`, `futures`, `asset`, `user`, `core`, `widget`, `l10n`

Use `/commit` skill to generate commit messages. Only commit locally — do not push unless explicitly asked.

## Pre-commit Checklist

- `dart format .` — no errors
- `flutter analyze` — no errors or warnings
- No `print()` / `debugPrint()` left in code
- `build_runner` output is up to date
- State is immutable Freezed; updated via `copyWith`
- Tokens stored in `flutter_secure_storage`, not `SharedPreferences`
