# 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//...`; 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()!.*`. - **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 - Protocol/storage values and backend field names remain canonical Chinese (e.g. 官鬼, 妻财, 世, 应). - Frontend hexagram detail MUST localize for English users: translate section headers, five-element labels, status values, and provide a legend mapping Chinese symbols to English equivalents. - Yao row cells keep Chinese characters (compact display); a legend card above the rows provides English translations. - AI English output uses Chinese parenthetical notes for key Liu Yao terms (e.g. "Officer (官鬼) line") so users can cross-reference with the Chinese characters shown in the hexagram detail. ## 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_.arb` file with `"@@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 { final Logger _logger = getLogger('features..'); } ``` ### 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