273 lines
12 KiB
Markdown
273 lines
12 KiB
Markdown
# Apps Domain Rules
|
|
|
|
This file governs `apps/**` (Flutter). Keep rules strict, short, and reusable.
|
|
|
|
## Scope & Precedence
|
|
|
|
- Inherits root `AGENTS.md` and workspace runtime rules.
|
|
- If rules conflict, apply the stricter one.
|
|
- Visual language source of truth: `apps/rules/visual_design_language.md`.
|
|
|
|
## Flutter Directory Contract (Must)
|
|
|
|
- `apps/lib` only allows these second-level directories: `app/`, `core/`, `data/`, `features/`, `shared/`, `l10n/`.
|
|
- `apps/lib/main.dart` is the only allowed root entry file.
|
|
- Do not add new second-level directories under `apps/lib` without explicit approval.
|
|
|
|
## Module Responsibilities (Must)
|
|
|
|
- `app/`: app bootstrap, DI wiring, global lifecycle orchestration, router composition.
|
|
- `core/`: cross-feature business primitives/protocols/orchestrators (no feature-specific page logic).
|
|
- `data/`: shared infrastructure only (cache/network/storage/adapters), not feature business repositories/models.
|
|
- `features/`: user-facing bounded feature modules with clear product ownership.
|
|
- `shared/`: reusable UI widgets and presentation helpers without feature business orchestration.
|
|
- Cross-cutting capabilities (e.g. notification orchestration, UI schema protocol) must live in `core/` + `shared/`, not under `features/`.
|
|
|
|
## Placement Rules (Must)
|
|
|
|
- Put code in `features/` only when it belongs to one bounded product capability/screen flow.
|
|
- Put code in `core/` when it is cross-feature protocol, policy, or orchestration that does not belong to one feature.
|
|
- Put reusable UI renderers in `shared/widgets/`; they must not contain feature-only business orchestration.
|
|
- In feature data layers, use semantic subfolders: `data/apis/`, `data/repositories/`, `data/services/`, `data/models/`.
|
|
- Avoid deep redundant nesting like `models/<same_name>/...`; prefer flat by concern.
|
|
|
|
## Shared Data Layer Boundary (Must)
|
|
|
|
- Do not place feature business repositories/models under `apps/lib/data/`.
|
|
- Feature business repositories/models must live under each feature's `data/` tree.
|
|
- `apps/lib/data/` is only for infrastructure abstractions and implementations (cache/network/storage), reusable by features.
|
|
|
|
## UI Design System (Must)
|
|
|
|
- **Semantic colors**: always use `Theme.of(context).colorScheme.*` (primary, surface, error, etc.). Never hardcode hex or `Colors.*`.
|
|
- **Brand palette colors** (event presets, avatar colors, Eisenhower matrix quadrants): use `Theme.of(context).extension<AppColorPalette>()!.*`.
|
|
- **Spacing / Radius**: use `AppSpacing` / `AppRadius` from `design_tokens.dart`. No hardcoded values.
|
|
- `AppTheme.light` / `AppTheme.dark` provide complete `ColorScheme` (light + dark). `MaterialApp` wires them via `theme:` / `darkTheme:`.
|
|
- If a semantic slot is missing from `ColorScheme`, add it to `AppTheme` — do not bypass `colorScheme` with hardcoded values.
|
|
|
|
### Page Header (Must)
|
|
|
|
All sub-pages (sub-page = any page that is not a home Tab page) `AppBar` must follow:
|
|
|
|
- **`centerTitle: true`** — title must be horizontally centered; never left-aligned.
|
|
- **`backgroundColor`** and **`surfaceTintColor`** should match the page background to avoid visual seams.
|
|
- Example:
|
|
|
|
```dart
|
|
appBar: AppBar(
|
|
title: Text('Notifications'),
|
|
centerTitle: true,
|
|
backgroundColor: colors.surfaceContainerLow,
|
|
surfaceTintColor: colors.surfaceContainerLow,
|
|
actions: [...],
|
|
),
|
|
```
|
|
|
|
- When a repeated pattern emerges, extract a reusable component into `shared/widgets/` instead of building `AppBar` independently in each page.
|
|
|
|
## Divination Terminology (Must)
|
|
|
|
- Divination domain terminology must use fixed Chinese terms in code contracts, protocol fields, and UI semantic labels.
|
|
- Do not localize or translate canonical terms such as: 六爻、爻、动爻、静爻、六亲、六神、世爻、应爻、伏神、月建、日辰、月破、日冲、空亡、五行旺衰。
|
|
- Signature level labels (`上上签/中上签/中下签`) may be localized for UI display only, while protocol/storage values remain canonical Chinese.
|
|
- l10n can translate explanatory copy, but must not alter canonical divination terminology semantics.
|
|
|
|
## Localization Generation Rules (Must)
|
|
|
|
The l10n system uses ARB files as the **single source of truth**. Generated `.dart` files are auto-derived and **must not be edited manually**.
|
|
|
|
### File Structure
|
|
|
|
```
|
|
apps/lib/l10n/
|
|
├── l10n.yaml # flutter gen-l10n configuration
|
|
├── app_zh.arb # Chinese (Simplified) source
|
|
├── app_zh_hant.arb # Chinese (Traditional) source
|
|
├── app_en.arb # English source
|
|
├── app_localizations.dart # GENERATED — do not edit
|
|
├── app_localizations_zh.dart # GENERATED — do not edit
|
|
├── app_localizations_zh_hant.dart # GENERATED — do not edit
|
|
└── app_localizations_en.dart # GENERATED — do not edit
|
|
```
|
|
|
|
### Adding or Modifying Translations
|
|
|
|
1. **Edit `.arb` files only** — never edit generated `.dart` files directly. Edits to `.dart` files will be overwritten on next `flutter gen-l10n`.
|
|
2. When adding a new translation key:
|
|
- Add it to all locale ARB files (`app_zh.arb`, `app_zh_hant.arb`, `app_en.arb`) with the same key.
|
|
- For pluralizable/interpolated strings, also add `@key` metadata blocks as shown in existing entries.
|
|
- Run `flutter gen-l10n` to regenerate all `.dart` files.
|
|
3. When adding a new locale:
|
|
- Create a new `app_<locale>.arb` file with `"@@locale": "<locale>"`.
|
|
- Add the locale to `l10n.yaml`'s `output-localization-file` / `supported-locales` if configured.
|
|
- Run `flutter gen-l10n`.
|
|
|
|
### Supported Locales
|
|
|
|
- `zh` — Chinese Simplified (default)
|
|
- `zh_Hant` — Chinese Traditional
|
|
- `en` — English
|
|
- Script-based locales in Flutter code must use `Locale.fromSubtags(...)`; do not use `Locale('zh', 'Hant')` (that treats `Hant` as countryCode, not scriptCode).
|
|
|
|
### Traditional Chinese Translation Principles
|
|
|
|
- **Canonical divination terms** (六爻、爻、动爻、世爻、应爻、五行旺衰、空亡、干支、月建等) remain in Traditional Chinese form — they are not Simplified-to-Traditional converted. These are the correct scholarly expressions in both variants.
|
|
- General UI copy uses standard Simplified↔Traditional conversion (e.g., 設置→設定、資訊→信息).
|
|
- Placeholder and variable text in ARB files must match exactly — `{name}` (single braces) and corresponding `@key.placeholders` metadata must stay in sync.
|
|
|
|
## Reuse & Composition (Must)
|
|
|
|
- Prefer `apps/lib/shared/widgets/` before adding new components.
|
|
- Extract repeated page structures/components; do not duplicate sibling-page scaffolds.
|
|
- Detail page top-right actions must use shared action-menu components.
|
|
- Destructive confirmations must use project-consistent shared surfaces.
|
|
|
|
## Interaction & Feedback (Must)
|
|
|
|
- User feedback: `Toast` / `AppBanner` only.
|
|
- Loading indicators: `AppLoadingIndicator` only.
|
|
- Form pages should default to keyboard-overlay behavior to avoid full-page layout jumps.
|
|
- `ToastType.info` should be minimized: do not show informational toast for normal success paths (e.g., login success). Prefer silent success unless user must take action.
|
|
|
|
## Interaction & Feedback (Must)
|
|
|
|
## Agent Chat Protocol (Must)
|
|
|
|
- Agent chat must follow AG-UI over SSE.
|
|
- Lifecycle events are mandatory: `RUN_STARTED` and exactly one of `RUN_FINISHED` or `RUN_ERROR`.
|
|
- Current default text delivery is finalized `TEXT_MESSAGE_END` payloads; do not require token-level `TEXT_MESSAGE_CONTENT` unless backend protocol explicitly enables it.
|
|
|
|
## HTTP Error Parse Contract (Must)
|
|
|
|
- Frontend must parse backend errors as RFC7807: `type/title/status/detail/instance` + extension `code/params`.
|
|
- Error code registry single source of truth: `docs/protocols/common/http-error-codes.md`.
|
|
- Frontend mapping must be based on documented `code` only (`code -> l10n key`), not inferred from `detail` text.
|
|
- Any new/changed code requires protocol doc update first, then frontend mapping update.
|
|
- Unknown code fallback order: status-generic localized message -> safe generic localized message.
|
|
|
|
## High-Risk Modules (Must)
|
|
|
|
### Auth
|
|
|
|
- `AuthBloc` is the single source of truth.
|
|
- 401 invalidation must go through global callback chain; no feature-level token clearing or direct login navigation.
|
|
|
|
### Home Message Viewport
|
|
|
|
- Home message auto-scroll/anchor restore must be event-driven.
|
|
- Preserve viewport during history prepend and when user is reading above bottom.
|
|
|
|
### Cache / Repository
|
|
|
|
- Reads/writes that affect consistency must go through repository layer.
|
|
- Cache keys and invalidation policy belong to repository, not UI/Bloc.
|
|
- Shared cache infrastructure must live under `apps/lib/data/cache/`; feature modules must not duplicate low-level cache store logic.
|
|
- Shared cache infrastructure (`apps/lib/data/cache/`) must remain domain-agnostic: do not import `features/**` or business model DTOs there.
|
|
- Domain object serialization/deserialization belongs to repository/feature layer via local mappers/codecs; do not centralize feature-specific codecs in shared cache layer.
|
|
- Shared cache layer may only encode/decode primitives, collections, and cache metadata wrappers.
|
|
- Cache strategy default is `SWR + TTL + invalidation/reload`.
|
|
- Local partial cache patching is allowed only for simple single-entity updates with clear rollback paths; complex cross-list/cross-feature states must invalidate and refetch.
|
|
- Feature TTL policy must be defined in each feature repository; do not add centralized feature TTL registries in shared cache infra.
|
|
- Runtime cache is hybrid (`memory + local persistent`) managed by DI singletons; do not create per-screen/per-widget cache store instances.
|
|
- Cross-feature data access must go through app-level facade/usecase boundaries; do not import another feature's data implementation directly from UI/Bloc.
|
|
- Repository instances should be resolved from DI singletons to reuse cache and avoid per-feature re-creation.
|
|
|
|
### Reminder / Notification Rewrite Boundary
|
|
|
|
- Reminder/notification data-interaction logic is under rewrite. Do not reintroduce local-notification scheduling/callback execution paths in `apps/lib/data/services/`.
|
|
- During rewrite, keep protocol/orchestration in `core/notification/**` and reusable rendering in `shared/widgets/notification/**`.
|
|
|
|
## Testing Policy
|
|
|
|
- Prioritize tests for model parsing, service logic, and high-regression interaction flows.
|
|
- Simple static UI changes may skip tests.
|
|
- Auth/Home/Cache changes must include targeted regression tests.
|
|
|
|
## Logging Conventions (Must)
|
|
|
|
### Logger Setup
|
|
|
|
```dart
|
|
import 'core/logging/logger.dart';
|
|
|
|
class SomeBloc extends Cubit<SomeState> {
|
|
final Logger _logger = getLogger('features.<feature>.<component>');
|
|
}
|
|
```
|
|
|
|
### Log Level Policy
|
|
|
|
| Level | When to Use | Noise Level |
|
|
|-------|-------------|-------------|
|
|
| **error** | All exceptions and failures - MUST log every error site | Required, never skip |
|
|
| **warning** | Degraded behavior, retry, fallback, malformed data | Minimal, only when action taken |
|
|
| **info** | Key business events (login, logout, send message) | Minimal, only milestone events |
|
|
| **debug** | Detailed flow tracing (only in debug builds) | High, avoid in release |
|
|
|
|
### Error Logging Requirements
|
|
|
|
**Every try-catch that handles an exception MUST log it:**
|
|
```dart
|
|
try {
|
|
await _repository.someOperation();
|
|
} catch (e, stackTrace) {
|
|
_logger.error(
|
|
message: 'Operation failed: $operationName',
|
|
error: e,
|
|
stackTrace: stackTrace,
|
|
extra: {'context': 'relevant_data'},
|
|
);
|
|
// handle error
|
|
}
|
|
```
|
|
|
|
### Info Logging Requirements
|
|
|
|
**Only log these milestone events:**
|
|
- User login/logout
|
|
- Message sent/received
|
|
- Data sync completed
|
|
- Important state transitions
|
|
|
|
```dart
|
|
_logger.info(
|
|
message: 'User logged in',
|
|
extra: {'user_id': user.id},
|
|
);
|
|
```
|
|
|
|
### Warning Logging Requirements
|
|
|
|
**Only log when taking corrective action:**
|
|
- Retrying after failure
|
|
- Using fallback data
|
|
- Skipping malformed data
|
|
- Deprecation warnings
|
|
|
|
```dart
|
|
_logger.warning(
|
|
message: 'Cache miss, loading from remote',
|
|
extra: {'key': cacheKey},
|
|
);
|
|
```
|
|
|
|
### Module Naming Convention
|
|
|
|
| Feature | Module Path |
|
|
|---------|------------|
|
|
| auth | `features.auth` |
|
|
| calendar | `features.calendar` |
|
|
| chat | `features.chat` |
|
|
| contacts | `features.contacts` |
|
|
| home | `features.home` |
|
|
| messages | `features.messages` |
|
|
| settings | `features.settings` |
|
|
| todo | `features.todo` |
|
|
|
|
### Prohibited Practices
|
|
|
|
- **Never** log sensitive data: passwords, tokens, PII, message content
|
|
- **Never** log at debug level in production (release mode)
|
|
- **Never** skip error logging even if you "handle" the error
|
|
- **Never** log for every iteration in loops - only on failures
|