Files
eryao/.trellis/spec/frontend/directory-structure.md
T

297 lines
9.2 KiB
Markdown

# Directory Structure
> How Flutter app code is organized in this project.
---
## Overview
This Flutter app follows a **feature-first architecture** with clear separation of concerns:
- **Feature modules** in `features/` for bounded product capabilities
- **Core infrastructure** in `core/` for cross-feature protocols
- **Shared UI components** in `shared/` for reusable widgets
- **Data layer** in `data/` for infrastructure abstractions
---
## Directory Layout
```
apps/lib/
├── main.dart # Only root entry file
├── app/ # App bootstrap & DI
│ ├── di/ # Dependency injection setup
│ ├── router.dart # Route definitions
│ └── app.dart # App configuration
├── core/ # Cross-feature infrastructure
│ ├── auth/ # Session store, auth state
│ ├── config/ # Env configuration
│ ├── logging/ # Structured logging
│ └── network/ # HTTP client, error mapping
├── data/ # Shared infrastructure ONLY
│ ├── cache/ # Cache implementations
│ ├── network/ # Network adapters
│ └── storage/ # Local storage
├── features/ # Feature modules
│ ├── auth/ # Authentication feature
│ ├── home/ # Home feature
│ ├── divination/ # Divination feature
│ ├── settings/ # Settings feature
│ └── ... # Other features
├── shared/ # Reusable UI components
│ ├── widgets/ # Shared widgets
│ └── theme/ # App theme, design tokens
└── l10n/ # Localization
```
---
## Module Organization
### Feature Module Structure
Each feature follows consistent structure:
```
features/<feature>/
├── data/ # Data layer
│ ├── apis/ # API clients
│ ├── repositories/ # Repository implementations
│ ├── services/ # Feature-specific services
│ └── models/ # Data models/DTOs
└── presentation/ # Presentation layer
├── bloc/ # State management (BLoC/Cubit)
├── screens/ # Screen widgets
└── widgets/ # Feature-specific widgets
```
**Example: `features/auth/`**
```
features/auth/
├── data/
│ ├── apis/
│ │ └── auth_api.dart # HTTP API calls
│ ├── repositories/
│ │ └── auth_repository.dart # Repository implementation
│ └── models/
│ ├── auth_user.dart # User model
│ └── session_response.dart # Session DTO
└── presentation/
├── bloc/
│ ├── auth_bloc.dart # AuthBloc (ChangeNotifier)
│ └── auth_state.dart # AuthState
└── screens/
└── login_screen.dart # Login screen widget
```
### Core Module Structure
**Core contains cross-feature infrastructure:**
```
core/
├── auth/
│ └── session_store.dart # Global session management
├── config/
│ └── env.dart # Environment configuration
├── logging/
│ ├── logger.dart # Logger interface
│ ├── log_service.dart # LogService implementation
│ ├── log_entry.dart # Log entry model
│ └── error_handler.dart # Global error handler
└── network/
├── api_problem.dart # RFC7807 error model
└── api_problem_mapper.dart # Error mapping
```
### Shared Widget Structure
**Shared contains reusable UI components:**
```
shared/
├── widgets/
│ ├── app_banner.dart # App-wide banner
│ ├── app_loading_indicator.dart # Loading indicator
│ ├── bottom_nav_bar.dart # Navigation bar
│ └── divination/ # Divination domain widgets
│ ├── gua_icon.dart # Gua icon widget
│ ├── yao_glyph.dart # Yao glyph widget
│ └── ...
└── theme/
├── app_theme.dart # Theme definition
└── design_tokens.dart # Spacing, radius, colors
```
---
## Placement Rules
### Where to Put Code
| Code Type | Location | Reason |
|-----------|----------|--------|
| Feature business logic | `features/<feature>/` | Bounded context |
| Cross-feature protocol | `core/` | Shared by multiple features |
| Reusable UI widget | `shared/widgets/` | Reusable by multiple screens |
| Infrastructure abstraction | `data/` | Cache/network/storage |
| Feature repository/model | `features/<feature>/data/` | Feature-scoped data |
### Decision Tree
```
Is it feature-specific business logic?
→ Yes: features/<feature>/
→ No: Is it reusable UI?
→ Yes: shared/widgets/
→ No: Is it infrastructure?
→ Yes: data/
→ No: Is it cross-feature protocol?
→ Yes: core/
→ No: Re-evaluate
```
### Forbidden Patterns
**❌ Do NOT:**
1. Place feature business repositories in `data/`
- Wrong: `data/repositories/auth_repository.dart`
- Right: `features/auth/data/repositories/auth_repository.dart`
2. Create directories under `lib/` other than allowed second-level
- Wrong: `lib/utils/`, `lib/helpers/`, `lib/constants/`
- Right: Use `core/`, `shared/`, or feature-specific locations
3. Put feature-specific UI in `shared/widgets/`
- Wrong: `shared/widgets/auth_login_form.dart`
- Right: `features/auth/presentation/widgets/login_form.dart`
4. Import feature data layer from other features
- Wrong: `import 'package:app/features/auth/data/repositories/auth_repository.dart'` in `features/home/`
- Right: Access via app-level facade or DI
---
## Naming Conventions
### Files
- **snake_case**: `auth_bloc.dart`, `login_screen.dart`
- **Feature prefix for shared**: `app_loading_indicator.dart`, `app_banner.dart`
### Classes
- **PascalCase**: `AuthBloc`, `AuthState`, `LoginScreen`
- **Suffixes**:
- `*Bloc` / `*Cubit` - State management
- `*Repository` - Data access
- `*Api` - HTTP clients
- `*Service` - Business services
- `*Screen` - Screen widgets
- `*Widget` - Reusable widgets
### Directories
- **Plural for collections**: `screens/`, `widgets/`, `models/`
- **Singular for features**: `auth/`, `home/`, `divination/`
---
## Examples
### Well-organized Feature: `features/divination/`
```
features/divination/
├── data/
│ ├── apis/
│ │ └── divination_api.dart # HTTP API
│ ├── repositories/
│ │ └── divination_repository.dart # Repository
│ ├── services/
│ │ └── voice_recorder.dart # Feature service
│ └── models/
│ ├── divination_result.dart # Domain model
│ ├── divination_params.dart # Request params
│ └── follow_up_message.dart # Message model
└── presentation/
└── screens/
├── divination_screen.dart # Main screen
├── auto_divination_screen.dart # Auto mode
├── manual_divination_screen.dart # Manual mode
└── follow_up_chat_screen.dart # Follow-up chat
```
### Shared Widget: `shared/widgets/app_loading_indicator.dart`
```dart
import 'package:flutter/material.dart';
class AppLoadingIndicator extends StatelessWidget {
const AppLoadingIndicator({super.key});
@override
Widget build(BuildContext context) {
return const CircularProgressIndicator();
}
}
```
### Core Infrastructure: `core/logging/logger.dart`
```dart
import 'log_service.dart';
class Logger {
final String module;
Logger(this.module, this._service);
static void setLogService(LogService service) {
_globalLogService = service;
}
void error({
required String message,
required Object error,
required StackTrace stackTrace,
}) {
_service!.error(
message: message,
error: error,
stackTrace: stackTrace,
module: module,
);
}
}
Logger getLogger(String module) => Logger.get(module);
```
---
## Key Principles
### Directory Contract (Must)
1. **Only allowed second-level directories**: `app/`, `core/`, `data/`, `features/`, `shared/`, `l10n/`
2. **Only one root entry**: `lib/main.dart`
3. **No ad-hoc directories**: No `utils/`, `helpers/`, `constants/` under `lib/`
4. **Feature isolation**: Features should not import each other's data layer
### Layer Boundaries
1. **Presentation****Data** (via Repository interface)
2. **Data****Core/Infrastructure** (via DI)
3. **Core****Nothing** (foundation layer)
4. **Shared****Core** (for utilities)
5. **Feature****Core** + **Shared** (for cross-cutting concerns)
### Data Layer Boundary (Must)
- `data/` = infrastructure abstractions (cache/network/storage)
- `features/<feature>/data/` = feature business repositories/models
- **NEVER** mix these boundaries