12 KiB
12 KiB
Apps Domain Rules
This file governs apps/** (Flutter). Keep rules strict, short, and reusable.
Scope & Precedence
- Inherits root
AGENTS.mdand 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/libonly allows these second-level directories:app/,core/,data/,features/,shared/,l10n/.apps/lib/main.dartis the only allowed root entry file.- Do not add new second-level directories under
apps/libwithout 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 underfeatures/.
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 orColors.*. - Brand palette colors (event presets, avatar colors, Eisenhower matrix quadrants): use
Theme.of(context).extension<AppColorPalette>()!.*. - Spacing / Radius: use
AppSpacing/AppRadiusfromdesign_tokens.dart. No hardcoded values. AppTheme.light/AppTheme.darkprovide completeColorScheme(light + dark).MaterialAppwires them viatheme:/darkTheme:.- If a semantic slot is missing from
ColorScheme, add it toAppTheme— do not bypasscolorSchemewith 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.backgroundColorandsurfaceTintColorshould match the page background to avoid visual seams.- Example:
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 buildingAppBarindependently 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
- Edit
.arbfiles only — never edit generated.dartfiles directly. Edits to.dartfiles will be overwritten on nextflutter gen-l10n. - 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
@keymetadata blocks as shown in existing entries. - Run
flutter gen-l10nto regenerate all.dartfiles.
- Add it to all locale ARB files (
- When adding a new locale:
- Create a new
app_<locale>.arbfile with"@@locale": "<locale>". - Add the locale to
l10n.yaml'soutput-localization-file/supported-localesif configured. - Run
flutter gen-l10n.
- Create a new
Supported Locales
zh— Chinese Simplified (default)zh_Hant— Chinese Traditionalen— English- Script-based locales in Flutter code must use
Locale.fromSubtags(...); do not useLocale('zh', 'Hant')(that treatsHantas 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.placeholdersmetadata 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/AppBanneronly. - Loading indicators:
AppLoadingIndicatoronly. - Form pages should default to keyboard-overlay behavior to avoid full-page layout jumps.
ToastType.infoshould 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_STARTEDand exactly one ofRUN_FINISHEDorRUN_ERROR. - Current default text delivery is finalized
TEXT_MESSAGE_ENDpayloads; do not require token-levelTEXT_MESSAGE_CONTENTunless backend protocol explicitly enables it.
HTTP Error Parse Contract (Must)
- Frontend must parse backend errors as RFC7807:
type/title/status/detail/instance+ extensioncode/params. - Error code registry single source of truth:
docs/protocols/common/http-error-codes.md. - Frontend mapping must be based on documented
codeonly (code -> l10n key), not inferred fromdetailtext. - 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
AuthBlocis 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 importfeatures/**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 inshared/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
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:
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
_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
_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