Architecture Style: Feature-Sliced Design (adapted for Flutter Web + Firebase)
- Vertical slices around business capabilities (deck publication, discovery, user identity) rather than pure technical layers.
- Each slice owns UI + state + domain adapters but respects cross-slice layering rules.
- Centralized shared abstractions (config, firebase, design system) avoid duplication.
- Immutability constraints (deck core fields) enforced at repository and Firestore rules layers.
From outer (more concrete) to inner (more abstract):
- app (composition root, routing, global providers)
- processes (multi-step flows spanning multiple features, e.g., deck_publish_flow)
- features (user-facing atomic capabilities: create_deck, edit_deck_tags, list_decks, view_deck, auth_login, auth_signup; each owns its page widget)
- entities (core domain models + small domain logic: deck, tag, version, user)
- shared (reusable primitives: ui kit, theming, utils, firebase adapters, config)
(If a layer is not needed initially it can be omitted until first use.)
app/
lib/
app/ # Root app setup, MaterialApp, routes, dependency injection
app.dart
router.dart
di/
providers.dart
processes/ # Orchestrated flows
deck_publish_flow/
deck_publish_flow.dart
// Pages removed; each feature exposes its own page widget under features/<feature>/ui/
features/
create_deck/
ui/
create_deck_form.dart
model/
create_deck_state.dart
logic/
create_deck_controller.dart
edit_deck_tags/
ui/
edit_tags_sheet.dart
model/
edit_tags_state.dart
list_decks/
ui/
deck_list_view.dart
model/
deck_list_state.dart
logic/
deck_list_controller.dart
view_deck/
ui/
deck_detail_view.dart
model/
deck_detail_state.dart
auth_signup/
ui/signup_form.dart
model/signup_state.dart
logic/signup_controller.dart
auth_login/
ui/login_form.dart
model/login_state.dart
logic/login_controller.dart
entities/
deck/
deck.dart
deck_repository.dart
tag/
tag.dart
tag_repository.dart
user/
user.dart
user_repository.dart
version/
version.dart
version_provider.dart
shared/
firebase/
firebase_init.dart
firestore_paths.dart
security_notes.md
config/
active_version_provider.dart
ui/
widgets/
primary_button.dart
tag_chip.dart
theme/
app_theme.dart
utils/
result.dart
validators.dart
constants/
limits.dart
services/
auth_service.dart
remote_config_service.dart
Higher layer can depend on same or inner layers only.
- app -> (processes, features, entities, shared)
- processes -> features, entities, shared
- features -> entities, shared
- entities -> shared (avoid upward references)
- shared -> (no dependencies on other slices; only platform & packages)
Forbidden: features referencing each other directly (coordinate via entities or processes). Avoid singletons; use DI/providers.
- Use bloc/Cubit for reactive state; each feature owns a small state notifier, use the cubit state pattern instead of copywith.
- No global monolithic state store; composition at page/process level.
- Repositories live under entities/* and expose domain-centric interfaces (DeckRepository, TagRepository, UserRepository, VersionProvider).
- Services (firebase auth, remote config) in shared/services; repositories depend on them.
- Snake_case for files, lowerCamelCase for members, PascalCase for types.
- Feature folder names reflect user intent (create_deck, list_decks) not technical terms.
app/
test/
features/
create_deck/
create_deck_controller_test.dart
entities/
deck/
deck_repository_test.dart
shared/
utils/
validators_test.dart
- Unit tests for domain logic & validators.
- Golden tests using alchymist lib for form validation & deck list filtering.
- users/{uid}
- usernames/{username}
- tags/{tagId}
- decks/{deckId} No subcollections required for MVP.
- Remote Config: activeVersion { id, label, discordUrl }
- Build-time flavors (optional later): dev, prod.
- .env style (Flutter --dart-define) for non-secret flags (e.g., ENABLE_ANALYTICS=false for MVP).
- Add analytics module under shared/services/analytics_service.dart
- Add moderation feature slice later (report_deck)
- Introduce Serverpod adapter by implementing new repositories while keeping interfaces stable.
- User logs in (auth_login feature)
- Active version fetched (version_provider)
- Tags loaded (tag_repository > Firestore)
- Deck creation form (create_deck feature) validates & submits to repository
- Deck list page queries decks (list_decks feature)
- Detail view (view_deck) renders code, tags, version link
- Edit tags sheet (edit_deck_tags) updates only strategy tags
Lightweight debug logging utility added at lib/shared/utils/app_logger.dart
.
Highlights:
- Initialized in
main.dart
viaAppLogger.init()
andAppLogger.runGuarded()
. - Captures framework (
FlutterError.onError
), platform dispatcher, and zone uncaught errors. - Colorized log levels (INFO/WARN/ERR) only in debug/profile; release minimizes noise.
- Repository example: user signup repository logs auth failures and rollback issues without exposing sensitive input.
Usage example:
AppLogger.info('Starting fetch', 'endpoint=/decks');
try {
// ... work ...
} catch (e, s) {
AppLogger.error('DeckRepository', 'Fetch failed', error: e, stack: s);
}
Avoid logging secrets (passwords, tokens, PII). Sanitize values first.
// Initialization (already invoked in main.dart)
await AppLogger.init();
// Info with context key-value pairs (serialize lightweight primitives only)
AppLogger.info('SignupController', 'submit_start', fields: {
'username': usernameMasked(username), // never raw if sensitive
});
// Warning (non-fatal unexpected state)
AppLogger.warn('UserRepository', 'username_collision_retry');
// Error with exception + stack
try {
await _repo.createUserWithUsername(...);
} catch (e, s) {
AppLogger.error('UserRepository', 'create_failed', error: e, stack: s, fields: {
'phase': 'firestore_batch',
});
rethrow; // still propagate if caller needs to handle
}
// Helper masking example (pseudo)
String usernameMasked(String raw) => raw.length <= 2
? '**'
: raw.substring(0, 2) + ('*' * (raw.length - 2));
Guidelines:
- Use stable event identifiers (e.g., submit_start, create_failed) for future log aggregation.
- Prefer one log per significant user intent phase (start, success, failure).
- Do not log full email or password; mask or omit.
TBD.
Status: Initial implementation merged on branch 001-auth-signup-feature
.
Capability: Allows unauthenticated visitor to create an account with unique immutable username (regex ^[a-z0-9_]{3,20}$), email, and password (6-128 chars). Performs client-side validation and repository-mediated submission.
Key Components:
features/auth_signup/ui/signup_page.dart
: Page scaffold & success redirect to/decks
.features/auth_signup/ui/signup_form.dart
: Form fields + validation + error mapping.features/auth_signup/logic/signup_controller.dart
: Cubit controlling state machine (idle → submitting → success|error) and guards against double submission.features/auth_signup/model/signup_state.dart
: Immutable state + error codes.entities/user/user.dart
: User entity (id, username, email, createdAt).entities/user/user_repository.dart
: Abstract repository ensuring atomic username + user profile creation (implementation stubbed / to be completed with Firebase integration).shared/utils/validators.dart
: Username/email/password validation helpers (regex + length bounds).shared/utils/result.dart
: Lightweight Result type (success/failure) enabling error mapping without exceptions.
User Flow (Happy Path):
- User enters valid values → submit.
- Controller performs sync validation; on success invokes repository.
- Repository (future impl) creates auth user + Firestore docs atomically.
- State transitions to success → page redirects to
/decks
.
Failure Modes & Handling:
- Invalid fields → immediate
SignupStatus.error
with specificSignupErrorCode
. - Network/unknown failure →
networkFailure
surfaced. - Username taken (repository-level) →
usernameTaken
error.
Open Items / TODOs:
- Concrete Firebase-backed
UserRepository
+AuthService
implementation. - Tests (unit, widget, integration) outlined in
specs/001-auth-signup-feature/tasks.md
pending migration into test suite (some placeholders currently absent). - Redirect guard for already-authenticated users (FR-010) to be formalized in router middleware.
Specification Artifacts:
specs/001-auth-signup-feature/spec.md
(functional requirements & scenarios)specs/001-auth-signup-feature/tasks.md
(TDD task breakdown)specs/001-auth-signup-feature/quickstart.md
(manual validation steps)
Traceability Matrix (Excerpt):
- FR-001/002/007/015: Handled via validators + controller early checks.
- FR-009:
_inFlight
guard in controller prevents double-submit. - FR-011: Logging pending integration with
AppLogger
(add in repository/controller on completion). - FR-016: Rollback semantics require concrete repository + auth service (NOT YET IMPLEMENTED).
Next planned features: auth login, deck creation slice. Each will follow the same spec → tasks → implementation → README update workflow.
Standard steps for adding a new feature slice:
- Create spec under
specs/NNN-feature-slug/
using templates. - Add
plan.md
+tasks.md
describing TDD order. - Branch:
NNN-feature-slug
(e.g.,002-auth-login-feature
). - Write failing tests first.
- Implement minimal code to pass tests incrementally.
- Update READMEs with new slice summary.
- Open PR referencing spec & tasks; ensure constitution rules upheld.
See CONTRIBUTING.md
for detailed guidelines (lint, commit messages, review gates).
The simplified coverage script runs tests with coverage and produces a pull-request focused diff report.
Usage:
./scripts/check-coverage.sh
Steps performed:
flutter test --coverage
(generatescoverage/lcov.info
).git diff origin/main | pull_request_coverage --output-mode markdown --markdown-mode dart --fully-tested-message "All covered" > pull_request_coverage.md
Output:
coverage/lcov.info
: Raw lcov data from Flutter.pull_request_coverage.md
: Markdown summary for only the lines changed relative toorigin/main
. Shows uncovered changed lines or the messageAll covered
if every changed Dart line has test coverage.
Notes:
- Requires the external CLI
pull_request_coverage
in your PATH. If absent, the diff report is skipped (tests still run). - Intended for local validation and CI hooks; can be extended later with thresholds if needed.
Example (just run):
./scripts/check-coverage.sh
Additions or improvements (like custom base ref) can be added once needed.