Merge pull request #580 from Oloodi/575-establish-root-level-agent-skills-directory-claudeskills-for-ai-agent-extraction---for-mobile
Add skills to aid the mobile application development
This commit is contained in:
233
.agents/skills/README.md
Normal file
233
.agents/skills/README.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# KROW Mobile Development Skills
|
||||||
|
|
||||||
|
This directory contains project-specific skills for AI agents working on the KROW mobile applications. These skills encode the development standards, architecture patterns, UI system usage, and release practices defined in the mobile documentation.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
These skills help AI agents contribute effectively to mobile application development by providing:
|
||||||
|
- **Clear guidelines** on development standards and constraints
|
||||||
|
- **Architecture patterns** for Clean Architecture implementation
|
||||||
|
- **Design system rules** for consistent UI implementation
|
||||||
|
- **Release procedures** for version management and deployment
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
### 1. krow-mobile-development-rules
|
||||||
|
|
||||||
|
**Purpose:** Enforce development standards and prevent architectural degradation
|
||||||
|
|
||||||
|
**Covers:**
|
||||||
|
- File creation and package structure (feature-first packaging)
|
||||||
|
- Naming conventions (Dart standards)
|
||||||
|
- Logic placement boundaries (strict separation of concerns)
|
||||||
|
- Localization integration (core_localization package)
|
||||||
|
- Data Connect integration strategy
|
||||||
|
- Prototype migration rules
|
||||||
|
- Navigation with safe extensions
|
||||||
|
- Session management patterns
|
||||||
|
- Error handling requirements
|
||||||
|
|
||||||
|
**Use When:**
|
||||||
|
- Creating new mobile features or packages
|
||||||
|
- Implementing BLoCs, Use Cases, or Repositories
|
||||||
|
- Integrating with Firebase Data Connect backend
|
||||||
|
- Migrating code from prototypes
|
||||||
|
- Reviewing mobile code for compliance
|
||||||
|
|
||||||
|
**Key Documentation:**
|
||||||
|
- Source: `docs/MOBILE/00-agent-development-rules.md`
|
||||||
|
|
||||||
|
### 2. krow-mobile-architecture
|
||||||
|
|
||||||
|
**Purpose:** Maintain Clean Architecture across the mobile codebase
|
||||||
|
|
||||||
|
**Covers:**
|
||||||
|
- High-level architecture overview
|
||||||
|
- Package structure and responsibilities
|
||||||
|
- Dependency direction rules
|
||||||
|
- Feature isolation and communication
|
||||||
|
- Data Connect service and session management
|
||||||
|
- BLoC lifecycle and state emission safety
|
||||||
|
- Avoiding prop drilling patterns
|
||||||
|
- Data Connect connectors pattern overview
|
||||||
|
|
||||||
|
**Use When:**
|
||||||
|
- Architecting new mobile features
|
||||||
|
- Debugging state management or BLoC lifecycle issues
|
||||||
|
- Preventing prop drilling in UI code
|
||||||
|
- Managing session state and authentication
|
||||||
|
- Understanding package boundaries and dependencies
|
||||||
|
- Refactoring legacy code to Clean Architecture
|
||||||
|
|
||||||
|
**Key Documentation:**
|
||||||
|
- Source: `docs/MOBILE/01-architecture-principles.md`
|
||||||
|
- Related: `docs/MOBILE/03-data-connect-connectors-pattern.md`
|
||||||
|
|
||||||
|
### 3. krow-mobile-design-system
|
||||||
|
|
||||||
|
**Purpose:** Ensure visual consistency using immutable design tokens
|
||||||
|
|
||||||
|
**Covers:**
|
||||||
|
- Design system ownership and authority
|
||||||
|
- Colors usage rules (UiColors)
|
||||||
|
- Typography usage rules (UiTypography)
|
||||||
|
- Icons usage rules (UiIcons)
|
||||||
|
- Spacing and layout constants (UiConstants)
|
||||||
|
- Smart widgets usage
|
||||||
|
- Theme configuration
|
||||||
|
- POC → Production workflow
|
||||||
|
- Anti-patterns to avoid
|
||||||
|
|
||||||
|
**Use When:**
|
||||||
|
- Implementing any UI in mobile features
|
||||||
|
- Migrating POC/prototype designs to production
|
||||||
|
- Creating themed widgets or components
|
||||||
|
- Reviewing UI code for design system compliance
|
||||||
|
- Matching colors and typography from designs
|
||||||
|
- Adding icons, spacing, or layout elements
|
||||||
|
|
||||||
|
**Key Documentation:**
|
||||||
|
- Source: `docs/MOBILE/02-design-system-usage.md`
|
||||||
|
|
||||||
|
### 4. krow-mobile-release
|
||||||
|
|
||||||
|
**Purpose:** Manage mobile app releases, versioning, and hotfixes
|
||||||
|
|
||||||
|
**Covers:**
|
||||||
|
- Versioning strategy (semantic versioning with milestones)
|
||||||
|
- CHANGELOG management and format
|
||||||
|
- Git tagging strategy
|
||||||
|
- GitHub Actions workflows (product-release, hotfix)
|
||||||
|
- APK signing setup (24 GitHub Secrets)
|
||||||
|
- Release process (dev → stage → prod)
|
||||||
|
- Hotfix procedures
|
||||||
|
- Troubleshooting release issues
|
||||||
|
|
||||||
|
**Use When:**
|
||||||
|
- Preparing for mobile app releases
|
||||||
|
- Updating CHANGELOG files with new features
|
||||||
|
- Triggering GitHub Actions release workflows
|
||||||
|
- Creating hotfix branches for production issues
|
||||||
|
- Understanding version numbering
|
||||||
|
- Documenting release notes
|
||||||
|
|
||||||
|
**Key Documentation:**
|
||||||
|
- Source: `docs/MOBILE/05-release-process.md`
|
||||||
|
- Comprehensive: `docs/RELEASE/mobile-releases.md` (900+ lines)
|
||||||
|
|
||||||
|
## Skill Organization
|
||||||
|
|
||||||
|
Each skill follows this structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
.agents/skills/
|
||||||
|
├── krow-mobile-development-rules/
|
||||||
|
│ └── SKILL.md
|
||||||
|
├── krow-mobile-architecture/
|
||||||
|
│ └── SKILL.md
|
||||||
|
├── krow-mobile-design-system/
|
||||||
|
│ └── SKILL.md
|
||||||
|
└── krow-mobile-release/
|
||||||
|
└── SKILL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skill Descriptions
|
||||||
|
|
||||||
|
Each skill includes a description in its frontmatter that helps AI agents determine when to use it. These descriptions are designed to be "pushy" to ensure skills are triggered appropriately.
|
||||||
|
|
||||||
|
## Using These Skills
|
||||||
|
|
||||||
|
### For AI Agents
|
||||||
|
|
||||||
|
1. **Skill triggering is automatic** based on:
|
||||||
|
- User task description matching skill description
|
||||||
|
- Context keywords (mobile, flutter, feature, release, etc.)
|
||||||
|
- Task type (implementation, architecture, UI, release)
|
||||||
|
|
||||||
|
2. **Skills can be combined** - multiple skills may be relevant:
|
||||||
|
- Development rules + Architecture (implementing features)
|
||||||
|
- Architecture + Design System (creating UI with proper structure)
|
||||||
|
- Release + Development rules (preparing releases)
|
||||||
|
|
||||||
|
3. **Reference documentation** when needed:
|
||||||
|
- Skills provide comprehensive guidance
|
||||||
|
- Link to source documentation for deep dives
|
||||||
|
- Include examples and anti-patterns
|
||||||
|
|
||||||
|
### For Developers
|
||||||
|
|
||||||
|
These skills serve as:
|
||||||
|
- **Quick reference** for mobile development standards
|
||||||
|
- **Onboarding material** for new team members
|
||||||
|
- **Code review checklist** for ensuring compliance
|
||||||
|
- **Architecture guide** for feature implementation
|
||||||
|
|
||||||
|
## Skill Maintenance
|
||||||
|
|
||||||
|
### Updating Skills
|
||||||
|
|
||||||
|
When mobile documentation changes:
|
||||||
|
1. Review corresponding skill(s)
|
||||||
|
2. Update skill content to match new standards
|
||||||
|
3. Update examples and patterns
|
||||||
|
4. Keep descriptions current for proper triggering
|
||||||
|
|
||||||
|
### Adding New Skills
|
||||||
|
|
||||||
|
Consider creating new skills for:
|
||||||
|
- New architectural patterns (e.g., state management approaches)
|
||||||
|
- New subsystems (e.g., analytics, crash reporting)
|
||||||
|
- Complex workflows spanning multiple skills
|
||||||
|
- Domain-specific patterns (e.g., payment processing)
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
### Mobile Documentation Structure
|
||||||
|
```
|
||||||
|
docs/MOBILE/
|
||||||
|
├── 00-agent-development-rules.md → krow-mobile-development-rules
|
||||||
|
├── 01-architecture-principles.md → krow-mobile-architecture
|
||||||
|
├── 02-design-system-usage.md → krow-mobile-design-system
|
||||||
|
├── 03-data-connect-connectors-pattern.md (not in skills)
|
||||||
|
├── 04-use-case-completion-audit.md (not in skills yet)
|
||||||
|
└── 05-release-process.md → krow-mobile-release
|
||||||
|
|
||||||
|
docs/RELEASE/
|
||||||
|
└── mobile-releases.md → krow-mobile-release (comprehensive)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
These skills encode **NON-NEGOTIABLE** standards. When AI agents:
|
||||||
|
- Create features → Must follow development rules
|
||||||
|
- Implement UI → Must use design system
|
||||||
|
- Prepare releases → Must follow release process
|
||||||
|
- Structure code → Must maintain Clean Architecture
|
||||||
|
|
||||||
|
**Zero tolerance for violations** ensures:
|
||||||
|
- Architectural integrity
|
||||||
|
- Visual consistency
|
||||||
|
- Code quality
|
||||||
|
- Maintainability
|
||||||
|
- Scalability
|
||||||
|
|
||||||
|
## Questions or Issues?
|
||||||
|
|
||||||
|
If you encounter:
|
||||||
|
- **Unclear guidelines** - Refer to source documentation
|
||||||
|
- **Conflicting patterns** - Architecture document takes precedence
|
||||||
|
- **Missing patterns** - Document assumption and ask for clarification
|
||||||
|
- **Technical debt** - Follow skills for new code, refactor legacy gradually
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
These skills transform documentation into actionable, contextual guidance for AI agents working on KROW mobile applications. They ensure consistency, prevent architectural degradation, and accelerate development while maintaining quality standards.
|
||||||
|
|
||||||
|
**Key Principles:**
|
||||||
|
- Clean Architecture with strict boundaries
|
||||||
|
- Feature isolation via zero cross-feature imports
|
||||||
|
- Immutable design system
|
||||||
|
- Semantic versioning and structured releases
|
||||||
|
- Localization-first user interfaces
|
||||||
|
|
||||||
|
When in doubt, consult the skills or source documentation. Architecture is not negotiable.
|
||||||
900
.agents/skills/krow-mobile-architecture/SKILL.md
Normal file
900
.agents/skills/krow-mobile-architecture/SKILL.md
Normal file
@@ -0,0 +1,900 @@
|
|||||||
|
---
|
||||||
|
name: krow-mobile-architecture
|
||||||
|
description: KROW mobile app Clean Architecture implementation including package structure, dependency rules, feature isolation, BLoC lifecycle management, session handling, and Data Connect connectors pattern. Use this when architecting new mobile features, debugging state management issues, preventing prop drilling, managing BLoC disposal, implementing session stores, or setting up connector repositories. Essential for maintaining architectural integrity across staff and client apps.
|
||||||
|
---
|
||||||
|
|
||||||
|
# KROW Mobile Architecture
|
||||||
|
|
||||||
|
This skill defines the authoritative mobile architecture for the KROW platform. All code must strictly adhere to these principles to prevent architectural degradation.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
- Architecting new mobile features
|
||||||
|
- Debugging state management or BLoC lifecycle issues
|
||||||
|
- Preventing prop drilling in UI code
|
||||||
|
- Managing session state and authentication
|
||||||
|
- Implementing Data Connect connector repositories
|
||||||
|
- Setting up feature modules and dependency injection
|
||||||
|
- Understanding package boundaries and dependencies
|
||||||
|
- Refactoring legacy code to Clean Architecture
|
||||||
|
|
||||||
|
## 1. High-Level Architecture
|
||||||
|
|
||||||
|
KROW follows **Clean Architecture** in a **Melos Monorepo**. Dependencies flow **inward** toward the Domain.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Apps (Entry Points) │
|
||||||
|
│ • apps/mobile/apps/client │
|
||||||
|
│ • apps/mobile/apps/staff │
|
||||||
|
│ Role: DI roots, navigation assembly, env config │
|
||||||
|
└─────────────────┬───────────────────────────────────────┘
|
||||||
|
│ depends on
|
||||||
|
┌─────────────────▼───────────────────────────────────────┐
|
||||||
|
│ Features (Vertical Slices) │
|
||||||
|
│ • apps/mobile/packages/features/client/* │
|
||||||
|
│ • apps/mobile/packages/features/staff/* │
|
||||||
|
│ Role: Pages, BLoCs, Use Cases, Feature Repositories │
|
||||||
|
└─────┬───────────────────────────────────────┬───────────┘
|
||||||
|
│ depends on │ depends on
|
||||||
|
┌─────▼────────────────┐ ┌───────▼───────────┐
|
||||||
|
│ Design System │ │ Core Localization│
|
||||||
|
│ • UI components │ │ • LocaleBloc │
|
||||||
|
│ • Theme/colors │ │ • Translations │
|
||||||
|
│ • Typography │ │ • ErrorTranslator│
|
||||||
|
└──────────────────────┘ └───────────────────┘
|
||||||
|
│ both depend on
|
||||||
|
┌─────────────────▼───────────────────────────────────────┐
|
||||||
|
│ Services (Interface Adapters) │
|
||||||
|
│ • data_connect: Backend integration, session mgmt │
|
||||||
|
│ • core: Extensions, base classes, utilities │
|
||||||
|
└─────────────────┬───────────────────────────────────────┘
|
||||||
|
│ both depend on
|
||||||
|
┌─────────────────▼───────────────────────────────────────┐
|
||||||
|
│ Domain (Stable Core) │
|
||||||
|
│ • Entities (immutable data models) │
|
||||||
|
│ • Failures (domain-specific errors) │
|
||||||
|
│ • Pure Dart only, zero Flutter dependencies │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical Rule:** Dependencies point INWARD only. Domain knows nothing about the outer layers.
|
||||||
|
|
||||||
|
## 2. Package Structure & Responsibilities
|
||||||
|
|
||||||
|
### 2.1 Apps (`apps/mobile/apps/`)
|
||||||
|
|
||||||
|
**Role:** Application entry points and DI roots
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Initialize Flutter Modular
|
||||||
|
- Assemble features into navigation tree
|
||||||
|
- Inject concrete implementations (from `data_connect`) into features
|
||||||
|
- Configure environment-specific settings (dev/stage/prod)
|
||||||
|
- Initialize session management
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```
|
||||||
|
apps/mobile/apps/staff/
|
||||||
|
├── lib/
|
||||||
|
│ ├── main.dart # Entry point, session initialization
|
||||||
|
│ ├── app_module.dart # Root module, imports features
|
||||||
|
│ ├── app_widget.dart # MaterialApp setup
|
||||||
|
│ └── src/
|
||||||
|
│ ├── navigation/ # Typed navigators
|
||||||
|
│ └── widgets/ # SessionListener wrapper
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**RESTRICTION:** NO business logic. NO UI widgets (except App and Main).
|
||||||
|
|
||||||
|
### 2.2 Features (`apps/mobile/packages/features/<APP>/<FEATURE>`)
|
||||||
|
|
||||||
|
**Role:** Vertical slices of user-facing functionality
|
||||||
|
|
||||||
|
**Internal Structure:**
|
||||||
|
```
|
||||||
|
features/staff/profile/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── domain/
|
||||||
|
│ │ │ ├── repositories/ # Repository interfaces
|
||||||
|
│ │ │ │ └── profile_repository_interface.dart
|
||||||
|
│ │ │ └── usecases/ # Application logic
|
||||||
|
│ │ │ └── get_profile_usecase.dart
|
||||||
|
│ │ ├── data/
|
||||||
|
│ │ │ └── repositories_impl/ # Repository concrete classes
|
||||||
|
│ │ │ └── profile_repository_impl.dart
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ │ ├── blocs/ # State management
|
||||||
|
│ │ │ └── profile_cubit.dart
|
||||||
|
│ │ ├── pages/ # Screens (StatelessWidget preferred)
|
||||||
|
│ │ │ └── profile_page.dart
|
||||||
|
│ │ └── widgets/ # Reusable UI components
|
||||||
|
│ │ └── profile_header.dart
|
||||||
|
│ └── profile_feature.dart # Barrel file (public API only)
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Principles:**
|
||||||
|
- **Presentation:** UI Pages and Widgets, BLoCs/Cubits for state
|
||||||
|
- **Application:** Use Cases (business logic orchestration)
|
||||||
|
- **Data:** Repository implementations (backend integration)
|
||||||
|
- **Pages as StatelessWidget:** Move state to BLoCs for better performance and testability
|
||||||
|
|
||||||
|
**RESTRICTION:** Features MUST NOT import other features. Communication happens via:
|
||||||
|
- Shared domain entities
|
||||||
|
- Session stores (`StaffSessionStore`, `ClientSessionStore`)
|
||||||
|
- Navigation via Modular
|
||||||
|
- Data Connect connector repositories
|
||||||
|
|
||||||
|
### 2.3 Domain (`apps/mobile/packages/domain`)
|
||||||
|
|
||||||
|
**Role:** The stable, pure heart of the system
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Define **Entities** (immutable data models using Data Classes or Freezed)
|
||||||
|
- Define **Failures** (domain-specific error types)
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```
|
||||||
|
domain/
|
||||||
|
├── lib/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── entities/
|
||||||
|
│ │ ├── user.dart
|
||||||
|
│ │ ├── staff.dart
|
||||||
|
│ │ └── shift.dart
|
||||||
|
│ └── failures/
|
||||||
|
│ ├── failure.dart # Base class
|
||||||
|
│ ├── auth_failure.dart
|
||||||
|
│ └── network_failure.dart
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Entity:**
|
||||||
|
```dart
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class Staff extends Equatable {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String email;
|
||||||
|
final StaffStatus status;
|
||||||
|
|
||||||
|
const Staff({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.email,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [id, name, email, status];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RESTRICTION:**
|
||||||
|
- NO Flutter dependencies (no `import 'package:flutter/material.dart'`)
|
||||||
|
- NO `json_annotation` or serialization code
|
||||||
|
- Only `equatable` for value equality
|
||||||
|
- Pure Dart only
|
||||||
|
|
||||||
|
### 2.4 Data Connect (`apps/mobile/packages/data_connect`)
|
||||||
|
|
||||||
|
**Role:** Interface Adapter for Backend Access
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Centralized connector repositories (see Data Connect Connectors Pattern section)
|
||||||
|
- Implement Firebase Data Connect service layer
|
||||||
|
- Map Domain Entities ↔ Data Connect generated code
|
||||||
|
- Handle Firebase exceptions → domain failures
|
||||||
|
- Provide `DataConnectService` with session management
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```
|
||||||
|
data_connect/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ │ ├── data_connect_service.dart # Core service
|
||||||
|
│ │ │ └── mixins/
|
||||||
|
│ │ │ └── session_handler_mixin.dart
|
||||||
|
│ │ ├── connectors/ # Connector pattern (see below)
|
||||||
|
│ │ │ ├── staff/
|
||||||
|
│ │ │ │ ├── domain/
|
||||||
|
│ │ │ │ │ ├── repositories/
|
||||||
|
│ │ │ │ │ │ └── staff_connector_repository.dart
|
||||||
|
│ │ │ │ │ └── usecases/
|
||||||
|
│ │ │ │ │ └── get_profile_completion_usecase.dart
|
||||||
|
│ │ │ │ └── data/
|
||||||
|
│ │ │ │ └── repositories/
|
||||||
|
│ │ │ │ └── staff_connector_repository_impl.dart
|
||||||
|
│ │ │ ├── order/
|
||||||
|
│ │ │ └── shifts/
|
||||||
|
│ │ └── session/
|
||||||
|
│ │ ├── staff_session_store.dart
|
||||||
|
│ │ └── client_session_store.dart
|
||||||
|
│ └── krow_data_connect.dart # Exports
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**RESTRICTION:**
|
||||||
|
- NO feature-specific logic
|
||||||
|
- Connectors are domain-neutral and reusable
|
||||||
|
- All queries follow Clean Architecture (domain interfaces → data implementations)
|
||||||
|
|
||||||
|
### 2.5 Design System (`apps/mobile/packages/design_system`)
|
||||||
|
|
||||||
|
**Role:** Visual language and component library
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Theme definitions (`UiColors`, `UiTypography`)
|
||||||
|
- UI constants (`spacingL`, `radiusM`, etc.)
|
||||||
|
- Shared widgets (if reused across multiple features)
|
||||||
|
- Assets (icons, images, fonts)
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```
|
||||||
|
design_system/
|
||||||
|
├── lib/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── ui_colors.dart
|
||||||
|
│ ├── ui_typography.dart
|
||||||
|
│ ├── ui_icons.dart
|
||||||
|
│ ├── ui_constants.dart
|
||||||
|
│ ├── ui_theme.dart # ThemeData factory
|
||||||
|
│ └── widgets/ # Shared UI components
|
||||||
|
│ └── custom_button.dart
|
||||||
|
└── assets/
|
||||||
|
├── icons/
|
||||||
|
└── images/
|
||||||
|
```
|
||||||
|
|
||||||
|
**RESTRICTION:**
|
||||||
|
- Dumb widgets ONLY (no state management)
|
||||||
|
- NO business logic
|
||||||
|
- Colors and typography are IMMUTABLE (no feature can override)
|
||||||
|
|
||||||
|
### 2.6 Core Localization (`apps/mobile/packages/core_localization`)
|
||||||
|
|
||||||
|
**Role:** Centralized i18n management
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Define all user-facing strings in `l10n/`
|
||||||
|
- Provide `LocaleBloc` for locale state management
|
||||||
|
- Export `TranslationProvider` for `context.strings` access
|
||||||
|
- Map domain failures to localized error messages via `ErrorTranslator`
|
||||||
|
|
||||||
|
**Feature Integration:**
|
||||||
|
```dart
|
||||||
|
// Features access strings
|
||||||
|
Text(context.strings.loginButton)
|
||||||
|
|
||||||
|
// BLoCs emit domain failures (not strings)
|
||||||
|
emit(AuthError(InvalidCredentialsFailure()));
|
||||||
|
|
||||||
|
// UI translates failures to localized messages
|
||||||
|
final message = ErrorTranslator.translate(failure, context.strings);
|
||||||
|
```
|
||||||
|
|
||||||
|
**App Setup:**
|
||||||
|
```dart
|
||||||
|
// App imports LocalizationModule
|
||||||
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
|
List<Module> get imports => [LocalizationModule()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap app with providers
|
||||||
|
BlocProvider<LocaleBloc>(
|
||||||
|
create: (_) => Modular.get<LocaleBloc>(),
|
||||||
|
child: TranslationProvider(
|
||||||
|
child: MaterialApp.router(...),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.7 Core (`apps/mobile/packages/core`)
|
||||||
|
|
||||||
|
**Role:** Cross-cutting concerns
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Extension methods (NavigationExtensions, ListExtensions, etc.)
|
||||||
|
- Base classes (UseCase, Failure, BlocErrorHandler)
|
||||||
|
- Logger configuration
|
||||||
|
- Result types for functional error handling
|
||||||
|
|
||||||
|
## 3. Dependency Direction Rules
|
||||||
|
|
||||||
|
1. **Domain Independence:** `domain` knows NOTHING about outer layers
|
||||||
|
- Defines *what* needs to be done, not *how*
|
||||||
|
- Pure Dart, zero Flutter dependencies
|
||||||
|
- Stable contracts that rarely change
|
||||||
|
|
||||||
|
2. **UI Agnosticism:** Features depend on `design_system` for UI and `domain` for logic
|
||||||
|
- Features do NOT know about Firebase or backend details
|
||||||
|
- Backend changes don't affect feature implementation
|
||||||
|
|
||||||
|
3. **Data Isolation:** `data_connect` depends on `domain` to know interfaces
|
||||||
|
- Implements domain repository interfaces
|
||||||
|
- Maps backend models to domain entities
|
||||||
|
- Does NOT know about UI
|
||||||
|
|
||||||
|
**Dependency Flow:**
|
||||||
|
```
|
||||||
|
Apps → Features → Design System
|
||||||
|
→ Core Localization
|
||||||
|
→ Data Connect → Domain
|
||||||
|
→ Core
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Data Connect Service & Session Management
|
||||||
|
|
||||||
|
### 4.1 Session Handler Mixin
|
||||||
|
|
||||||
|
**Location:** `apps/mobile/packages/data_connect/lib/src/services/mixins/session_handler_mixin.dart`
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Automatic token refresh (triggered when <5 minutes to expiry)
|
||||||
|
- Firebase auth state listening
|
||||||
|
- Role-based access validation
|
||||||
|
- Session state stream emissions
|
||||||
|
- 3-attempt retry with exponential backoff (1s → 2s → 4s)
|
||||||
|
|
||||||
|
**Key Method:**
|
||||||
|
```dart
|
||||||
|
// Call once on app startup
|
||||||
|
DataConnectService.instance.initializeAuthListener(
|
||||||
|
allowedRoles: ['STAFF', 'BOTH'], // or ['CLIENT', 'BUSINESS', 'BOTH']
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Session Listener Widget
|
||||||
|
|
||||||
|
**Location:** `apps/mobile/apps/<app>/lib/src/widgets/session_listener.dart`
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Wraps entire app to listen to session state changes
|
||||||
|
- Shows user-friendly dialogs for session expiration/errors
|
||||||
|
- Handles navigation on auth state changes
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```dart
|
||||||
|
// main.dart
|
||||||
|
runApp(
|
||||||
|
SessionListener( // ← Critical wrapper
|
||||||
|
child: ModularApp(module: AppModule(), child: AppWidget()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Repository Pattern with Data Connect
|
||||||
|
|
||||||
|
**Step 1:** Define interface in feature domain:
|
||||||
|
```dart
|
||||||
|
// features/staff/profile/lib/src/domain/repositories/
|
||||||
|
abstract interface class ProfileRepositoryInterface {
|
||||||
|
Future<Staff> getProfile(String id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2:** Implement using `DataConnectService.run()`:
|
||||||
|
```dart
|
||||||
|
// features/staff/profile/lib/src/data/repositories_impl/
|
||||||
|
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||||
|
final DataConnectService _service = DataConnectService.instance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Staff> getProfile(String id) async {
|
||||||
|
return await _service.run(() async {
|
||||||
|
final response = await _service.connector
|
||||||
|
.getStaffById(id: id)
|
||||||
|
.execute();
|
||||||
|
return _mapToStaff(response.data.staff);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of `_service.run()`:**
|
||||||
|
- ✅ Auto validates user is authenticated
|
||||||
|
- ✅ Refreshes token if <5 min to expiry
|
||||||
|
- ✅ Executes the query
|
||||||
|
- ✅ 3-attempt retry with exponential backoff
|
||||||
|
- ✅ Maps exceptions to domain failures
|
||||||
|
|
||||||
|
### 4.4 Session Store Pattern
|
||||||
|
|
||||||
|
After successful auth, populate session stores:
|
||||||
|
|
||||||
|
**Staff App:**
|
||||||
|
```dart
|
||||||
|
StaffSessionStore.instance.setSession(
|
||||||
|
StaffSession(
|
||||||
|
user: user,
|
||||||
|
staff: staff,
|
||||||
|
ownerId: ownerId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client App:**
|
||||||
|
```dart
|
||||||
|
ClientSessionStore.instance.setSession(
|
||||||
|
ClientSession(
|
||||||
|
user: user,
|
||||||
|
business: business,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lazy Loading:** If session is null, fetch from backend and update:
|
||||||
|
```dart
|
||||||
|
final session = StaffSessionStore.instance.session;
|
||||||
|
if (session?.staff == null) {
|
||||||
|
final staff = await getStaffById(session!.user.uid);
|
||||||
|
StaffSessionStore.instance.setSession(
|
||||||
|
session.copyWith(staff: staff),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Feature Isolation & Communication
|
||||||
|
|
||||||
|
### Zero Direct Imports
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ❌ FORBIDDEN
|
||||||
|
import 'package:staff_profile/staff_profile.dart'; // in another feature
|
||||||
|
|
||||||
|
// ✅ ALLOWED
|
||||||
|
import 'package:krow_domain/krow_domain.dart'; // shared domain
|
||||||
|
import 'package:krow_core/krow_core.dart'; // shared utilities
|
||||||
|
import 'package:design_system/design_system.dart'; // shared UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation: Typed Navigators with Safe Extensions
|
||||||
|
|
||||||
|
**Safe Navigation Extensions** (from `core` package):
|
||||||
|
```dart
|
||||||
|
extension NavigationExtensions on IModularNavigator {
|
||||||
|
/// Safely navigate with fallback to home
|
||||||
|
Future<void> safeNavigate(String route) async {
|
||||||
|
try {
|
||||||
|
await navigate(route);
|
||||||
|
} catch (e) {
|
||||||
|
await navigate('/home'); // Fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safely push with fallback to home
|
||||||
|
Future<T?> safePush<T>(String route) async {
|
||||||
|
try {
|
||||||
|
return await pushNamed<T>(route);
|
||||||
|
} catch (e) {
|
||||||
|
await navigate('/home');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safely pop with guard against empty stack
|
||||||
|
void popSafe() {
|
||||||
|
if (canPop()) {
|
||||||
|
pop();
|
||||||
|
} else {
|
||||||
|
navigate('/home');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Typed Navigators:**
|
||||||
|
```dart
|
||||||
|
// apps/mobile/apps/staff/lib/src/navigation/staff_navigator.dart
|
||||||
|
extension StaffNavigator on IModularNavigator {
|
||||||
|
Future<void> toStaffHome() => safeNavigate(StaffPaths.home);
|
||||||
|
|
||||||
|
Future<void> toShiftDetails(String shiftId) =>
|
||||||
|
safePush('${StaffPaths.shifts}/$shiftId');
|
||||||
|
|
||||||
|
Future<void> toProfileEdit() => safePush(StaffPaths.profileEdit);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage in Features:**
|
||||||
|
```dart
|
||||||
|
// ✅ CORRECT
|
||||||
|
Modular.to.toStaffHome();
|
||||||
|
Modular.to.toShiftDetails(shiftId: '123');
|
||||||
|
Modular.to.popSafe();
|
||||||
|
|
||||||
|
// ❌ AVOID
|
||||||
|
Modular.to.navigate('/home'); // No safety
|
||||||
|
Navigator.push(...); // No Modular integration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Sharing Patterns
|
||||||
|
|
||||||
|
Features don't share state directly. Use:
|
||||||
|
|
||||||
|
1. **Domain Repositories:** Centralized data sources
|
||||||
|
2. **Session Stores:** `StaffSessionStore`, `ClientSessionStore` for app-wide context
|
||||||
|
3. **Event Streams:** If needed, via `DataConnectService` streams
|
||||||
|
4. **Navigation Arguments:** Pass IDs, not full objects
|
||||||
|
|
||||||
|
## 6. App-Specific Session Management
|
||||||
|
|
||||||
|
### Staff App
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// main.dart
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
DataConnectService.instance.initializeAuthListener(
|
||||||
|
allowedRoles: ['STAFF', 'BOTH'],
|
||||||
|
);
|
||||||
|
|
||||||
|
runApp(
|
||||||
|
SessionListener(
|
||||||
|
child: ModularApp(module: StaffAppModule(), child: StaffApp()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Session Store:** `StaffSessionStore`
|
||||||
|
- Fields: `user`, `staff`, `ownerId`
|
||||||
|
- Lazy load: `getStaffById()` if staff is null
|
||||||
|
|
||||||
|
**Navigation:**
|
||||||
|
- Authenticated → `Modular.to.toStaffHome()`
|
||||||
|
- Unauthenticated → `Modular.to.toInitialPage()`
|
||||||
|
|
||||||
|
### Client App
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// main.dart
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
DataConnectService.instance.initializeAuthListener(
|
||||||
|
allowedRoles: ['CLIENT', 'BUSINESS', 'BOTH'],
|
||||||
|
);
|
||||||
|
|
||||||
|
runApp(
|
||||||
|
SessionListener(
|
||||||
|
child: ModularApp(module: ClientAppModule(), child: ClientApp()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Session Store:** `ClientSessionStore`
|
||||||
|
- Fields: `user`, `business`
|
||||||
|
- Lazy load: `getBusinessById()` if business is null
|
||||||
|
|
||||||
|
**Navigation:**
|
||||||
|
- Authenticated → `Modular.to.toClientHome()`
|
||||||
|
- Unauthenticated → `Modular.to.toInitialPage()`
|
||||||
|
|
||||||
|
## 7. Data Connect Connectors Pattern
|
||||||
|
|
||||||
|
**Problem:** Without connectors, each feature duplicates backend queries.
|
||||||
|
|
||||||
|
**Solution:** Centralize all backend queries in `data_connect/connectors/`.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
Mirror backend connector structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
data_connect/lib/src/connectors/
|
||||||
|
├── staff/
|
||||||
|
│ ├── domain/
|
||||||
|
│ │ ├── repositories/
|
||||||
|
│ │ │ └── staff_connector_repository.dart # Interface
|
||||||
|
│ │ └── usecases/
|
||||||
|
│ │ └── get_profile_completion_usecase.dart
|
||||||
|
│ └── data/
|
||||||
|
│ └── repositories/
|
||||||
|
│ └── staff_connector_repository_impl.dart # Implementation
|
||||||
|
├── order/
|
||||||
|
├── shifts/
|
||||||
|
└── user/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Maps to backend:**
|
||||||
|
```
|
||||||
|
backend/dataconnect/connector/
|
||||||
|
├── staff/
|
||||||
|
├── order/
|
||||||
|
├── shifts/
|
||||||
|
└── user/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clean Architecture in Connectors
|
||||||
|
|
||||||
|
**Domain Interface:**
|
||||||
|
```dart
|
||||||
|
// staff_connector_repository.dart
|
||||||
|
abstract interface class StaffConnectorRepository {
|
||||||
|
Future<bool> getProfileCompletion();
|
||||||
|
Future<Staff> getStaffById(String id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Case:**
|
||||||
|
```dart
|
||||||
|
// get_profile_completion_usecase.dart
|
||||||
|
class GetProfileCompletionUseCase {
|
||||||
|
final StaffConnectorRepository _repository;
|
||||||
|
|
||||||
|
GetProfileCompletionUseCase({required StaffConnectorRepository repository})
|
||||||
|
: _repository = repository;
|
||||||
|
|
||||||
|
Future<bool> call() => _repository.getProfileCompletion();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Data Implementation:**
|
||||||
|
```dart
|
||||||
|
// staff_connector_repository_impl.dart
|
||||||
|
class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||||
|
final DataConnectService _service;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> getProfileCompletion() async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final staffId = await _service.getStaffId();
|
||||||
|
final response = await _service.connector
|
||||||
|
.getStaffProfileCompletion(id: staffId)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return _isProfileComplete(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature Integration
|
||||||
|
|
||||||
|
**Step 1:** Feature registers connector repository:
|
||||||
|
```dart
|
||||||
|
// staff_main_module.dart
|
||||||
|
class StaffMainModule extends Module {
|
||||||
|
@override
|
||||||
|
void binds(Injector i) {
|
||||||
|
i.addSingleton<StaffConnectorRepository>(
|
||||||
|
StaffConnectorRepositoryImpl.new,
|
||||||
|
);
|
||||||
|
|
||||||
|
i.addSingleton(
|
||||||
|
() => GetProfileCompletionUseCase(
|
||||||
|
repository: i.get<StaffConnectorRepository>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
i.addSingleton(
|
||||||
|
() => StaffMainCubit(
|
||||||
|
getProfileCompletionUsecase: i.get(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2:** BLoC uses it:
|
||||||
|
```dart
|
||||||
|
class StaffMainCubit extends Cubit<StaffMainState> {
|
||||||
|
final GetProfileCompletionUseCase _getProfileCompletionUsecase;
|
||||||
|
|
||||||
|
Future<void> loadProfileCompletion() async {
|
||||||
|
final isComplete = await _getProfileCompletionUsecase();
|
||||||
|
emit(state.copyWith(isProfileComplete: isComplete));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
✅ **No Duplication** - Query implemented once, used by many features
|
||||||
|
✅ **Single Source of Truth** - Backend change → update one place
|
||||||
|
✅ **Reusability** - Any feature can use any connector
|
||||||
|
✅ **Testability** - Mock connector repo to test features
|
||||||
|
✅ **Scalability** - Easy to add connectors as backend grows
|
||||||
|
|
||||||
|
## 8. Avoiding Prop Drilling: Direct BLoC Access
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
|
||||||
|
Passing data through intermediate widgets creates maintenance burden:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ❌ BAD: Prop drilling
|
||||||
|
ProfilePage(status: status)
|
||||||
|
→ ProfileHeader(status: status)
|
||||||
|
→ ProfileLevelBadge(status: status) // Only widget that needs it
|
||||||
|
```
|
||||||
|
|
||||||
|
### The Solution: BlocBuilder in Leaf Widgets
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ✅ GOOD: Direct BLoC access
|
||||||
|
class ProfileLevelBadge extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<ProfileCubit, ProfileState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.profile == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final level = _mapStatusToLevel(state.profile!.status);
|
||||||
|
return LevelBadgeUI(level: level);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
|
||||||
|
1. **Leaf Widgets Access BLoC:** Widgets needing specific data should use `BlocBuilder`
|
||||||
|
2. **Container Widgets Stay Simple:** Parent widgets only manage layout
|
||||||
|
3. **No Unnecessary Props:** Don't pass data to intermediate widgets
|
||||||
|
4. **Single Responsibility:** Each widget has one reason to exist
|
||||||
|
|
||||||
|
**Decision Tree:**
|
||||||
|
```
|
||||||
|
Does this widget need data?
|
||||||
|
├─ YES, leaf widget → Use BlocBuilder
|
||||||
|
├─ YES, container → Use BlocBuilder in child
|
||||||
|
└─ NO → Don't add prop
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. BLoC Lifecycle & State Emission Safety
|
||||||
|
|
||||||
|
### The Problem: StateError After Dispose
|
||||||
|
|
||||||
|
When async operations complete after BLoC is closed:
|
||||||
|
```
|
||||||
|
StateError: Cannot emit new states after calling close
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root Causes:**
|
||||||
|
1. Transient BLoCs created with `BlocProvider(create:)` → disposed prematurely
|
||||||
|
2. Multiple BlocProviders disposing same singleton
|
||||||
|
3. User navigates away during async operation
|
||||||
|
|
||||||
|
### The Solution: Singleton BLoCs + Safe Emit
|
||||||
|
|
||||||
|
#### Step 1: Register as Singleton
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ✅ GOOD: Singleton registration
|
||||||
|
i.addSingleton<ProfileCubit>(
|
||||||
|
() => ProfileCubit(useCase1, useCase2),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ❌ BAD: Creates new instance each time
|
||||||
|
i.add(ProfileCubit.new);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Use BlocProvider.value()
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ✅ GOOD: Reuse singleton
|
||||||
|
final cubit = Modular.get<ProfileCubit>();
|
||||||
|
BlocProvider<ProfileCubit>.value(
|
||||||
|
value: cubit,
|
||||||
|
child: MyWidget(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ❌ BAD: Creates duplicate
|
||||||
|
BlocProvider<ProfileCubit>(
|
||||||
|
create: (_) => Modular.get<ProfileCubit>(),
|
||||||
|
child: MyWidget(),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Safe Emit with BlocErrorHandler
|
||||||
|
|
||||||
|
**Location:** `apps/mobile/packages/core/lib/src/presentation/mixins/bloc_error_handler.dart`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
mixin BlocErrorHandler<S> on Cubit<S> {
|
||||||
|
void _safeEmit(void Function(S) emit, S state) {
|
||||||
|
try {
|
||||||
|
emit(state);
|
||||||
|
} on StateError catch (e) {
|
||||||
|
developer.log(
|
||||||
|
'Could not emit state: ${e.message}. Bloc may have been disposed.',
|
||||||
|
name: runtimeType.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```dart
|
||||||
|
class ProfileCubit extends Cubit<ProfileState> with BlocErrorHandler<ProfileState> {
|
||||||
|
Future<void> loadProfile() async {
|
||||||
|
emit(state.copyWith(status: ProfileStatus.loading));
|
||||||
|
|
||||||
|
await handleError(
|
||||||
|
emit: emit,
|
||||||
|
action: () async {
|
||||||
|
final profile = await getProfile();
|
||||||
|
emit(state.copyWith(status: ProfileStatus.loaded, profile: profile));
|
||||||
|
// ✅ Safe even if BLoC disposed
|
||||||
|
},
|
||||||
|
onError: (errorKey) => state.copyWith(status: ProfileStatus.error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern Summary
|
||||||
|
|
||||||
|
| Pattern | When to Use | Risk |
|
||||||
|
|---------|------------|------|
|
||||||
|
| Singleton + BlocProvider.value() | Long-lived features | Low |
|
||||||
|
| Transient + BlocProvider(create:) | Temporary widgets | Medium |
|
||||||
|
| Direct BlocBuilder | Leaf widgets | Low |
|
||||||
|
|
||||||
|
## 10. Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
❌ **Feature imports feature**
|
||||||
|
```dart
|
||||||
|
import 'package:staff_profile/staff_profile.dart'; // in another feature
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Business logic in BLoC**
|
||||||
|
```dart
|
||||||
|
on<LoginRequested>((event, emit) {
|
||||||
|
if (event.email.isEmpty) { // ← Use case responsibility
|
||||||
|
emit(AuthError('Email required'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Direct Data Connect in features**
|
||||||
|
```dart
|
||||||
|
final response = await FirebaseDataConnect.instance.query(); // ← Use repository
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Global state variables**
|
||||||
|
```dart
|
||||||
|
User? currentUser; // ← Use SessionStore
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Direct Navigator.push**
|
||||||
|
```dart
|
||||||
|
Navigator.push(context, MaterialPageRoute(...)); // ← Use Modular
|
||||||
|
```
|
||||||
|
|
||||||
|
❌ **Hardcoded navigation**
|
||||||
|
```dart
|
||||||
|
Modular.to.navigate('/profile'); // ← Use safe extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The architecture enforces:
|
||||||
|
- **Clean Architecture** with strict layer boundaries
|
||||||
|
- **Feature Isolation** via zero cross-feature imports
|
||||||
|
- **Session Management** via DataConnectService and SessionListener
|
||||||
|
- **Connector Pattern** for reusable backend queries
|
||||||
|
- **BLoC Lifecycle** safety with singletons and safe emit
|
||||||
|
- **Navigation Safety** with typed navigators and fallbacks
|
||||||
|
|
||||||
|
When implementing features:
|
||||||
|
1. Follow package structure strictly
|
||||||
|
2. Use connector repositories for backend access
|
||||||
|
3. Register BLoCs as singletons with `.value()`
|
||||||
|
4. Use safe navigation extensions
|
||||||
|
5. Avoid prop drilling with direct BLoC access
|
||||||
|
6. Keep domain pure and stable
|
||||||
|
|
||||||
|
Architecture is not negotiable. When in doubt, refer to existing well-structured features or ask for clarification.
|
||||||
717
.agents/skills/krow-mobile-design-system/SKILL.md
Normal file
717
.agents/skills/krow-mobile-design-system/SKILL.md
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
---
|
||||||
|
name: krow-mobile-design-system
|
||||||
|
description: KROW mobile design system usage rules covering colors, typography, icons, spacing, and UI component patterns. Use this when implementing UI in KROW mobile features, matching POC designs to production, creating themed widgets, enforcing visual consistency, or reviewing UI code compliance. Prevents hardcoded values and ensures brand consistency across staff and client apps. Critical for maintaining immutable design tokens.
|
||||||
|
---
|
||||||
|
|
||||||
|
# KROW Mobile Design System Usage
|
||||||
|
|
||||||
|
This skill defines mandatory standards for UI implementation using the shared `apps/mobile/packages/design_system`. All UI must consume design system tokens exclusively.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
- Implementing any UI in mobile features
|
||||||
|
- Migrating POC/prototype designs to production
|
||||||
|
- Creating new themed widgets or components
|
||||||
|
- Reviewing UI code for design system compliance
|
||||||
|
- Matching colors and typography from designs
|
||||||
|
- Adding icons, spacing, or layout elements
|
||||||
|
- Setting up theme configuration in apps
|
||||||
|
- Refactoring UI code with hardcoded values
|
||||||
|
|
||||||
|
## Core Principle
|
||||||
|
|
||||||
|
**Design tokens (colors, typography, spacing) are IMMUTABLE and defined centrally.**
|
||||||
|
|
||||||
|
Features consume tokens but NEVER modify them. The design system maintains visual coherence across all apps.
|
||||||
|
|
||||||
|
## 1. Design System Ownership
|
||||||
|
|
||||||
|
### Centralized Authority
|
||||||
|
|
||||||
|
- `apps/mobile/packages/design_system` owns:
|
||||||
|
- All brand assets
|
||||||
|
- Colors and semantic color mappings
|
||||||
|
- Typography and font configurations
|
||||||
|
- Core UI components
|
||||||
|
- Icons and images
|
||||||
|
- Spacing, radius, elevation constants
|
||||||
|
|
||||||
|
### No Local Overrides
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// Feature uses design system
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
|
||||||
|
Container(
|
||||||
|
color: UiColors.background,
|
||||||
|
padding: EdgeInsets.all(UiConstants.spacingL),
|
||||||
|
child: Text(
|
||||||
|
'Hello',
|
||||||
|
style: UiTypography.display1m,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Custom colors in feature
|
||||||
|
const myBlue = Color(0xFF1A2234);
|
||||||
|
|
||||||
|
// ❌ Custom text styles in feature
|
||||||
|
const myStyle = TextStyle(fontSize: 24, fontWeight: FontWeight.bold);
|
||||||
|
|
||||||
|
// ❌ Theme overrides in feature
|
||||||
|
Theme(
|
||||||
|
data: ThemeData(primaryColor: Colors.blue),
|
||||||
|
child: MyWidget(),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extension Policy
|
||||||
|
|
||||||
|
If a required style is missing:
|
||||||
|
1. **FIRST:** Add it to `design_system` following existing patterns
|
||||||
|
2. **THEN:** Use it in your feature
|
||||||
|
|
||||||
|
**DO NOT** create temporary workarounds with hardcoded values.
|
||||||
|
|
||||||
|
## 2. Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/mobile/packages/design_system/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── ui_colors.dart # Color tokens
|
||||||
|
│ │ ├── ui_typography.dart # Text styles
|
||||||
|
│ │ ├── ui_icons.dart # Icon exports
|
||||||
|
│ │ ├── ui_constants.dart # Spacing, radius, elevation
|
||||||
|
│ │ ├── ui_theme.dart # ThemeData factory
|
||||||
|
│ │ └── widgets/ # Shared UI components
|
||||||
|
│ │ ├── custom_button.dart
|
||||||
|
│ │ └── custom_app_bar.dart
|
||||||
|
│ └── design_system.dart # Public exports
|
||||||
|
├── assets/
|
||||||
|
│ ├── icons/
|
||||||
|
│ ├── images/
|
||||||
|
│ └── fonts/
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Colors Usage Rules
|
||||||
|
|
||||||
|
### Strict Protocol
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
```dart
|
||||||
|
// Use UiColors for all color needs
|
||||||
|
Container(color: UiColors.background)
|
||||||
|
Text('Hello', style: TextStyle(color: UiColors.foreground))
|
||||||
|
Icon(Icons.home, color: UiColors.primary)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
```dart
|
||||||
|
// ❌ Hardcoded hex colors
|
||||||
|
Container(color: Color(0xFF1A2234))
|
||||||
|
|
||||||
|
// ❌ Material color constants
|
||||||
|
Container(color: Colors.blue)
|
||||||
|
|
||||||
|
// ❌ Opacity on hardcoded colors
|
||||||
|
Container(color: Color(0xFF1A2234).withOpacity(0.5))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Color Categories
|
||||||
|
|
||||||
|
**Brand Colors:**
|
||||||
|
- `UiColors.primary` - Main brand color
|
||||||
|
- `UiColors.secondary` - Secondary brand color
|
||||||
|
- `UiColors.accent` - Accent highlights
|
||||||
|
|
||||||
|
**Semantic Colors:**
|
||||||
|
- `UiColors.background` - Page background
|
||||||
|
- `UiColors.foreground` - Primary text color
|
||||||
|
- `UiColors.card` - Card/container background
|
||||||
|
- `UiColors.border` - Border colors
|
||||||
|
- `UiColors.mutedForeground` - Secondary text
|
||||||
|
|
||||||
|
**Status Colors:**
|
||||||
|
- `UiColors.success` - Success states
|
||||||
|
- `UiColors.warning` - Warning states
|
||||||
|
- `UiColors.error` - Error states
|
||||||
|
- `UiColors.info` - Information states
|
||||||
|
|
||||||
|
### Color Matching from POCs
|
||||||
|
|
||||||
|
When migrating POC designs:
|
||||||
|
|
||||||
|
1. **Find closest match** in `UiColors`
|
||||||
|
2. **Use existing color** even if slightly different
|
||||||
|
3. **DO NOT add new colors** without design team approval
|
||||||
|
|
||||||
|
**Example Process:**
|
||||||
|
```dart
|
||||||
|
// POC has: Color(0xFF2C3E50)
|
||||||
|
// Find closest: UiColors.background or UiColors.card
|
||||||
|
// Use: UiColors.card
|
||||||
|
|
||||||
|
// POC has: Color(0xFF27AE60)
|
||||||
|
// Find closest: UiColors.success
|
||||||
|
// Use: UiColors.success
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Access
|
||||||
|
|
||||||
|
Colors can also be accessed via theme:
|
||||||
|
```dart
|
||||||
|
// Both are valid:
|
||||||
|
Container(color: UiColors.primary)
|
||||||
|
Container(color: Theme.of(context).colorScheme.primary)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Typography Usage Rules
|
||||||
|
|
||||||
|
### Strict Protocol
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
```dart
|
||||||
|
// Use UiTypography for all text
|
||||||
|
Text('Title', style: UiTypography.display1m)
|
||||||
|
Text('Body', style: UiTypography.body1r)
|
||||||
|
Text('Label', style: UiTypography.caption1m)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
```dart
|
||||||
|
// ❌ Custom TextStyle
|
||||||
|
Text('Title', style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
))
|
||||||
|
|
||||||
|
// ❌ Manual font configuration
|
||||||
|
Text('Body', style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 16,
|
||||||
|
))
|
||||||
|
|
||||||
|
// ❌ Modifying existing styles inline
|
||||||
|
Text('Title', style: UiTypography.display1m.copyWith(
|
||||||
|
fontSize: 28, // ← Don't override size
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Typography Styles
|
||||||
|
|
||||||
|
**Display Styles (Large Headers):**
|
||||||
|
- `UiTypography.display1m` - Display Medium
|
||||||
|
- `UiTypography.display1sb` - Display Semi-Bold
|
||||||
|
- `UiTypography.display1b` - Display Bold
|
||||||
|
|
||||||
|
**Heading Styles:**
|
||||||
|
- `UiTypography.heading1m` - H1 Medium
|
||||||
|
- `UiTypography.heading1sb` - H1 Semi-Bold
|
||||||
|
- `UiTypography.heading1b` - H1 Bold
|
||||||
|
- `UiTypography.heading2m` - H2 Medium
|
||||||
|
- `UiTypography.heading2sb` - H2 Semi-Bold
|
||||||
|
|
||||||
|
**Body Styles:**
|
||||||
|
- `UiTypography.body1r` - Body Regular
|
||||||
|
- `UiTypography.body1m` - Body Medium
|
||||||
|
- `UiTypography.body1sb` - Body Semi-Bold
|
||||||
|
- `UiTypography.body2r` - Body 2 Regular
|
||||||
|
|
||||||
|
**Caption/Label Styles:**
|
||||||
|
- `UiTypography.caption1m` - Caption Medium
|
||||||
|
- `UiTypography.caption1sb` - Caption Semi-Bold
|
||||||
|
- `UiTypography.label1m` - Label Medium
|
||||||
|
|
||||||
|
### Allowed Customizations
|
||||||
|
|
||||||
|
**✅ ALLOWED (Color Only):**
|
||||||
|
```dart
|
||||||
|
// You MAY change color
|
||||||
|
Text(
|
||||||
|
'Title',
|
||||||
|
style: UiTypography.display1m.copyWith(
|
||||||
|
color: UiColors.error, // ← OK
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN (Size, Weight, Family):**
|
||||||
|
```dart
|
||||||
|
// ❌ Don't change size
|
||||||
|
Text(
|
||||||
|
'Title',
|
||||||
|
style: UiTypography.display1m.copyWith(fontSize: 28),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ❌ Don't change weight
|
||||||
|
Text(
|
||||||
|
'Title',
|
||||||
|
style: UiTypography.display1m.copyWith(fontWeight: FontWeight.w900),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ❌ Don't change family
|
||||||
|
Text(
|
||||||
|
'Title',
|
||||||
|
style: UiTypography.display1m.copyWith(fontFamily: 'Roboto'),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography Matching from POCs
|
||||||
|
|
||||||
|
When migrating:
|
||||||
|
1. Identify text role (heading, body, caption)
|
||||||
|
2. Find closest matching style in `UiTypography`
|
||||||
|
3. Use existing style even if size/weight differs slightly
|
||||||
|
|
||||||
|
## 5. Icons Usage Rules
|
||||||
|
|
||||||
|
### Strict Protocol
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
```dart
|
||||||
|
// Use UiIcons
|
||||||
|
Icon(UiIcons.home)
|
||||||
|
Icon(UiIcons.profile)
|
||||||
|
Icon(UiIcons.chevronLeft)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
```dart
|
||||||
|
// ❌ Direct icon library imports
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
Icon(LucideIcons.home)
|
||||||
|
|
||||||
|
// ❌ Font Awesome direct
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
FaIcon(FontAwesomeIcons.house)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Centralize Icons?
|
||||||
|
|
||||||
|
1. **Consistency:** Same icon for same action everywhere
|
||||||
|
2. **Branding:** Unified icon set with consistent stroke weight
|
||||||
|
3. **Swappability:** Change icon library in one place
|
||||||
|
|
||||||
|
### Icon Libraries
|
||||||
|
|
||||||
|
Design system uses:
|
||||||
|
- `typedef _IconLib = LucideIcons;` (primary)
|
||||||
|
- `typedef _IconLib2 = FontAwesomeIcons;` (secondary)
|
||||||
|
|
||||||
|
**Features MUST NOT import these directly.**
|
||||||
|
|
||||||
|
### Adding New Icons
|
||||||
|
|
||||||
|
If icon missing:
|
||||||
|
1. Add to `ui_icons.dart`:
|
||||||
|
```dart
|
||||||
|
class UiIcons {
|
||||||
|
static const home = _IconLib.home;
|
||||||
|
static const newIcon = _IconLib.newIcon; // Add here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. Use in feature:
|
||||||
|
```dart
|
||||||
|
Icon(UiIcons.newIcon)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Spacing & Layout Constants
|
||||||
|
|
||||||
|
### Strict Protocol
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
```dart
|
||||||
|
// Use UiConstants for spacing
|
||||||
|
Padding(padding: EdgeInsets.all(UiConstants.spacingL))
|
||||||
|
SizedBox(height: UiConstants.spacingM)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.spacingL,
|
||||||
|
vertical: UiConstants.spacingM,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use UiConstants for radius
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusM),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use UiConstants for elevation
|
||||||
|
elevation: UiConstants.elevationLow
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
```dart
|
||||||
|
// ❌ Magic numbers
|
||||||
|
Padding(padding: EdgeInsets.all(16.0))
|
||||||
|
SizedBox(height: 24.0)
|
||||||
|
BorderRadius.circular(8.0)
|
||||||
|
elevation: 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Constants
|
||||||
|
|
||||||
|
**Spacing:**
|
||||||
|
```dart
|
||||||
|
UiConstants.spacingXs // Extra small
|
||||||
|
UiConstants.spacingS // Small
|
||||||
|
UiConstants.spacingM // Medium
|
||||||
|
UiConstants.spacingL // Large
|
||||||
|
UiConstants.spacingXl // Extra large
|
||||||
|
UiConstants.spacing2xl // 2x Extra large
|
||||||
|
```
|
||||||
|
|
||||||
|
**Border Radius:**
|
||||||
|
```dart
|
||||||
|
UiConstants.radiusS // Small
|
||||||
|
UiConstants.radiusM // Medium
|
||||||
|
UiConstants.radiusL // Large
|
||||||
|
UiConstants.radiusXl // Extra large
|
||||||
|
UiConstants.radiusFull // Fully rounded
|
||||||
|
```
|
||||||
|
|
||||||
|
**Elevation:**
|
||||||
|
```dart
|
||||||
|
UiConstants.elevationNone
|
||||||
|
UiConstants.elevationLow
|
||||||
|
UiConstants.elevationMedium
|
||||||
|
UiConstants.elevationHigh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Smart Widgets Usage
|
||||||
|
|
||||||
|
### When to Use
|
||||||
|
|
||||||
|
- **Prefer standard Flutter Material widgets** styled via theme
|
||||||
|
- **Use design system widgets** for non-standard patterns
|
||||||
|
- **Create new widgets** in design system if reused >3 features
|
||||||
|
|
||||||
|
### Navigation in Widgets
|
||||||
|
|
||||||
|
Widgets with navigation MUST use safe methods:
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// In UiAppBar back button:
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_core/krow_core.dart';
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(UiIcons.chevronLeft),
|
||||||
|
onPressed: () => Modular.to.popSafe(), // ← Safe pop
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Direct Navigator
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(UiIcons.chevronLeft),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ❌ Unsafe Modular
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(UiIcons.chevronLeft),
|
||||||
|
onPressed: () => Modular.to.pop(), // Can crash
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Composition Over Inheritance
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// Compose standard widgets
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(UiConstants.spacingL),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.card,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusM),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text('Title', style: UiTypography.heading1sb),
|
||||||
|
SizedBox(height: UiConstants.spacingM),
|
||||||
|
Text('Body', style: UiTypography.body1r),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ AVOID:**
|
||||||
|
```dart
|
||||||
|
// ❌ Deep custom widget hierarchies
|
||||||
|
class CustomCard extends StatelessWidget {
|
||||||
|
// Complex custom implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Theme Configuration
|
||||||
|
|
||||||
|
### App Setup
|
||||||
|
|
||||||
|
Apps initialize theme ONCE in root MaterialApp:
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// apps/mobile/apps/staff/lib/app_widget.dart
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
|
||||||
|
class StaffApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp.router(
|
||||||
|
theme: StaffTheme.light, // ← Design system theme
|
||||||
|
darkTheme: StaffTheme.dark, // ← Optional dark mode
|
||||||
|
themeMode: ThemeMode.system,
|
||||||
|
// ...
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Custom theme in app
|
||||||
|
MaterialApp.router(
|
||||||
|
theme: ThemeData(
|
||||||
|
primaryColor: Colors.blue, // ← NO!
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// ❌ Theme override in feature
|
||||||
|
Theme(
|
||||||
|
data: ThemeData(...),
|
||||||
|
child: MyFeatureWidget(),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Theme
|
||||||
|
|
||||||
|
**Both methods valid:**
|
||||||
|
```dart
|
||||||
|
// Method 1: Direct design system import
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
Text('Hello', style: UiTypography.body1r)
|
||||||
|
|
||||||
|
// Method 2: Via theme context
|
||||||
|
Text('Hello', style: Theme.of(context).textTheme.bodyMedium)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prefer Method 1** for explicit type safety.
|
||||||
|
|
||||||
|
## 9. POC → Production Workflow
|
||||||
|
|
||||||
|
### Step 1: Implement Structure (POC Matching)
|
||||||
|
|
||||||
|
Implement UI layout exactly matching POC:
|
||||||
|
```dart
|
||||||
|
// Temporary: Match POC visually
|
||||||
|
Container(
|
||||||
|
color: Color(0xFF1A2234), // ← POC color
|
||||||
|
padding: EdgeInsets.all(16.0), // ← POC spacing
|
||||||
|
child: Text(
|
||||||
|
'Title',
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), // ← POC style
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Purpose:** Ensure visual parity with POC before refactoring.
|
||||||
|
|
||||||
|
### Step 2: Architecture Refactor
|
||||||
|
|
||||||
|
Move to Clean Architecture:
|
||||||
|
- Extract business logic to use cases
|
||||||
|
- Move state management to BLoCs
|
||||||
|
- Implement repository pattern
|
||||||
|
- Use dependency injection
|
||||||
|
|
||||||
|
### Step 3: Design System Integration
|
||||||
|
|
||||||
|
Replace hardcoded values:
|
||||||
|
```dart
|
||||||
|
// Production: Design system tokens
|
||||||
|
Container(
|
||||||
|
color: UiColors.background, // ← Found closest match
|
||||||
|
padding: EdgeInsets.all(UiConstants.spacingL), // ← Used constant
|
||||||
|
child: Text(
|
||||||
|
'Title',
|
||||||
|
style: UiTypography.heading1sb, // ← Matched typography
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Color Matching:**
|
||||||
|
- POC `#1A2234` → `UiColors.background`
|
||||||
|
- POC `#3498DB` → `UiColors.primary`
|
||||||
|
- POC `#27AE60` → `UiColors.success`
|
||||||
|
|
||||||
|
**Typography Matching:**
|
||||||
|
- POC `24px bold` → `UiTypography.heading1sb`
|
||||||
|
- POC `16px regular` → `UiTypography.body1r`
|
||||||
|
- POC `14px medium` → `UiTypography.caption1m`
|
||||||
|
|
||||||
|
**Spacing Matching:**
|
||||||
|
- POC `16px` → `UiConstants.spacingL`
|
||||||
|
- POC `8px` → `UiConstants.spacingM`
|
||||||
|
- POC `4px` → `UiConstants.spacingS`
|
||||||
|
|
||||||
|
## 10. Anti-Patterns & Common Mistakes
|
||||||
|
|
||||||
|
### ❌ Magic Numbers
|
||||||
|
```dart
|
||||||
|
// BAD
|
||||||
|
EdgeInsets.all(12.0)
|
||||||
|
SizedBox(height: 24.0)
|
||||||
|
BorderRadius.circular(8.0)
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
EdgeInsets.all(UiConstants.spacingM)
|
||||||
|
SizedBox(height: UiConstants.spacingL)
|
||||||
|
BorderRadius.circular(UiConstants.radiusM)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Local Themes
|
||||||
|
```dart
|
||||||
|
// BAD
|
||||||
|
Theme(
|
||||||
|
data: ThemeData(primaryColor: Colors.blue),
|
||||||
|
child: MyWidget(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
// Use global theme defined in app
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Hex Hunting
|
||||||
|
```dart
|
||||||
|
// BAD: Copy-paste from Figma
|
||||||
|
Container(color: Color(0xFF3498DB))
|
||||||
|
|
||||||
|
// GOOD: Find matching design system color
|
||||||
|
Container(color: UiColors.primary)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Direct Icon Library
|
||||||
|
```dart
|
||||||
|
// BAD
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
Icon(LucideIcons.home)
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
Icon(UiIcons.home)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Custom Text Styles
|
||||||
|
```dart
|
||||||
|
// BAD
|
||||||
|
Text('Title', style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
))
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
Text('Title', style: UiTypography.heading1sb)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Design System Review Checklist
|
||||||
|
|
||||||
|
Before merging UI code:
|
||||||
|
|
||||||
|
### ✅ Design System Compliance
|
||||||
|
- [ ] No hardcoded `Color(...)` or `0xFF...` hex values
|
||||||
|
- [ ] No custom `TextStyle(...)` definitions
|
||||||
|
- [ ] All spacing uses `UiConstants.spacing*`
|
||||||
|
- [ ] All radius uses `UiConstants.radius*`
|
||||||
|
- [ ] All elevation uses `UiConstants.elevation*`
|
||||||
|
- [ ] All icons from `UiIcons`, not direct library imports
|
||||||
|
- [ ] Theme consumed from design system, no local overrides
|
||||||
|
- [ ] Layout matches POC intent using design system primitives
|
||||||
|
|
||||||
|
### ✅ Architecture Compliance
|
||||||
|
- [ ] No business logic in widgets
|
||||||
|
- [ ] State managed by BLoCs
|
||||||
|
- [ ] Navigation uses Modular safe extensions
|
||||||
|
- [ ] Localization used for all text (no hardcoded strings)
|
||||||
|
- [ ] No direct Data Connect queries in widgets
|
||||||
|
|
||||||
|
### ✅ Code Quality
|
||||||
|
- [ ] Widget build methods concise (<50 lines)
|
||||||
|
- [ ] Complex widgets extracted to separate files
|
||||||
|
- [ ] Meaningful widget names
|
||||||
|
- [ ] Doc comments on reusable widgets
|
||||||
|
|
||||||
|
## 12. When to Extend Design System
|
||||||
|
|
||||||
|
### Add New Color
|
||||||
|
**When:** New brand color approved by design team
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Add to `ui_colors.dart`:
|
||||||
|
```dart
|
||||||
|
class UiColors {
|
||||||
|
static const myNewColor = Color(0xFF123456);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. Update theme if needed
|
||||||
|
3. Use in features
|
||||||
|
|
||||||
|
### Add New Typography Style
|
||||||
|
**When:** New text style pattern emerges across multiple features
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Add to `ui_typography.dart`:
|
||||||
|
```dart
|
||||||
|
class UiTypography {
|
||||||
|
static const myNewStyle = TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: _fontFamily,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. Use in features
|
||||||
|
|
||||||
|
### Add Shared Widget
|
||||||
|
**When:** Widget reused in 3+ features
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Create in `lib/src/widgets/`:
|
||||||
|
```dart
|
||||||
|
// my_widget.dart
|
||||||
|
class MyWidget extends StatelessWidget {
|
||||||
|
// Implementation using design system tokens
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. Export from `design_system.dart`
|
||||||
|
3. Use across features
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Core Rules:**
|
||||||
|
1. **All colors from `UiColors`** - Zero hex codes in features
|
||||||
|
2. **All typography from `UiTypography`** - Zero custom TextStyle
|
||||||
|
3. **All spacing/radius/elevation from `UiConstants`** - Zero magic numbers
|
||||||
|
4. **All icons from `UiIcons`** - Zero direct library imports
|
||||||
|
5. **Theme defined once** in app entry point
|
||||||
|
6. **POC → Production** requires design system integration step
|
||||||
|
|
||||||
|
**The Golden Rule:** Design system is immutable. Features adapt to the system, not the other way around.
|
||||||
|
|
||||||
|
When implementing UI:
|
||||||
|
1. Import `package:design_system/design_system.dart`
|
||||||
|
2. Use design system tokens exclusively
|
||||||
|
3. Match POC intent with available tokens
|
||||||
|
4. Request new tokens only when truly necessary
|
||||||
|
5. Never create temporary hardcoded workarounds
|
||||||
|
|
||||||
|
Visual consistency is non-negotiable. Every pixel must come from the design system.
|
||||||
646
.agents/skills/krow-mobile-development-rules/SKILL.md
Normal file
646
.agents/skills/krow-mobile-development-rules/SKILL.md
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
---
|
||||||
|
name: krow-mobile-development-rules
|
||||||
|
description: Enforce KROW mobile app development standards including file structure, naming conventions, logic placement boundaries, localization, Data Connect integration, and prototype migration rules. Use this skill whenever working on KROW Flutter mobile features, creating new packages, implementing BLoCs, integrating with backend, or migrating from prototypes. Critical for maintaining clean architecture and preventing architectural degradation.
|
||||||
|
---
|
||||||
|
|
||||||
|
# KROW Mobile Development Rules
|
||||||
|
|
||||||
|
These rules are **NON-NEGOTIABLE** enforcement guidelines for the KROW mobile application. They prevent architectural degradation and ensure consistency across the codebase.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
- Creating new mobile features or packages
|
||||||
|
- Implementing BLoCs, Use Cases, or Repositories
|
||||||
|
- Integrating with Firebase Data Connect backend
|
||||||
|
- Migrating code from prototypes
|
||||||
|
- Reviewing mobile code for compliance
|
||||||
|
- Setting up new feature modules
|
||||||
|
- Handling user sessions and authentication
|
||||||
|
- Implementing navigation flows
|
||||||
|
|
||||||
|
## 1. File Creation & Package Structure
|
||||||
|
|
||||||
|
### Feature-First Packaging
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
- Create new features as independent packages:
|
||||||
|
```
|
||||||
|
apps/mobile/packages/features/<app_name>/<feature_name>/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── domain/
|
||||||
|
│ │ │ ├── repositories/
|
||||||
|
│ │ │ └── usecases/
|
||||||
|
│ │ ├── data/
|
||||||
|
│ │ │ └── repositories_impl/
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ │ ├── blocs/
|
||||||
|
│ │ ├── pages/
|
||||||
|
│ │ └── widgets/
|
||||||
|
│ └── <feature_name>.dart # Barrel file
|
||||||
|
└── pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
- Add features to `apps/mobile/packages/core` directly
|
||||||
|
- Create files in app directories (`apps/mobile/apps/client/` or `apps/mobile/apps/staff/`)
|
||||||
|
- Create cross-feature or cross-app dependencies (features must not import other features)
|
||||||
|
|
||||||
|
### Path Conventions (Strict)
|
||||||
|
|
||||||
|
Follow these exact paths:
|
||||||
|
|
||||||
|
| Layer | Path Pattern | Example |
|
||||||
|
|-------|-------------|---------|
|
||||||
|
| **Entities** | `apps/mobile/packages/domain/lib/src/entities/<entity>.dart` | `user.dart`, `shift.dart` |
|
||||||
|
| **Repository Interface** | `.../features/<app>/<feature>/lib/src/domain/repositories/<name>_repository_interface.dart` | `auth_repository_interface.dart` |
|
||||||
|
| **Repository Impl** | `.../features/<app>/<feature>/lib/src/data/repositories_impl/<name>_repository_impl.dart` | `auth_repository_impl.dart` |
|
||||||
|
| **Use Cases** | `.../features/<app>/<feature>/lib/src/application/<name>_usecase.dart` | `login_usecase.dart` |
|
||||||
|
| **BLoCs** | `.../features/<app>/<feature>/lib/src/presentation/blocs/<name>_bloc.dart` | `auth_bloc.dart` |
|
||||||
|
| **Pages** | `.../features/<app>/<feature>/lib/src/presentation/pages/<name>_page.dart` | `login_page.dart` |
|
||||||
|
| **Widgets** | `.../features/<app>/<feature>/lib/src/presentation/widgets/<name>_widget.dart` | `password_field.dart` |
|
||||||
|
|
||||||
|
### Barrel Files
|
||||||
|
|
||||||
|
**✅ DO:**
|
||||||
|
```dart
|
||||||
|
// lib/auth_feature.dart
|
||||||
|
export 'src/presentation/pages/login_page.dart';
|
||||||
|
export 'src/domain/repositories/auth_repository_interface.dart';
|
||||||
|
// Only export PUBLIC API
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ DON'T:**
|
||||||
|
```dart
|
||||||
|
// Don't export internal implementation details
|
||||||
|
export 'src/data/repositories_impl/auth_repository_impl.dart';
|
||||||
|
export 'src/presentation/blocs/auth_bloc.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Naming Conventions (Dart Standard)
|
||||||
|
|
||||||
|
| Type | Convention | Example | File Name |
|
||||||
|
|------|-----------|---------|-----------|
|
||||||
|
| **Files** | `snake_case` | `user_profile_page.dart` | - |
|
||||||
|
| **Classes** | `PascalCase` | `UserProfilePage` | - |
|
||||||
|
| **Variables** | `camelCase` | `userProfile` | - |
|
||||||
|
| **Interfaces** | End with `Interface` | `AuthRepositoryInterface` | `auth_repository_interface.dart` |
|
||||||
|
| **Implementations** | End with `Impl` | `AuthRepositoryImpl` | `auth_repository_impl.dart` |
|
||||||
|
| **BLoCs** | End with `Bloc` or `Cubit` | `AuthBloc`, `ProfileCubit` | `auth_bloc.dart` |
|
||||||
|
| **Use Cases** | End with `UseCase` | `LoginUseCase` | `login_usecase.dart` |
|
||||||
|
|
||||||
|
## 3. Logic Placement (Zero Tolerance Boundaries)
|
||||||
|
|
||||||
|
### Business Rules → Use Cases ONLY
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// login_usecase.dart
|
||||||
|
class LoginUseCase extends UseCase<User, LoginParams> {
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, User>> call(LoginParams params) async {
|
||||||
|
// Business logic here: validation, transformation, orchestration
|
||||||
|
if (params.email.isEmpty) {
|
||||||
|
return Left(ValidationFailure('Email required'));
|
||||||
|
}
|
||||||
|
return await repository.login(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Business logic in BLoC
|
||||||
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
|
on<LoginRequested>((event, emit) {
|
||||||
|
if (event.email.isEmpty) { // ← NO! This is business logic
|
||||||
|
emit(AuthError('Email required'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Business logic in Widget
|
||||||
|
class LoginPage extends StatelessWidget {
|
||||||
|
void _login() {
|
||||||
|
if (_emailController.text.isEmpty) { // ← NO! This is business logic
|
||||||
|
showSnackbar('Email required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Logic → BLoCs ONLY
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// auth_bloc.dart
|
||||||
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
|
on<LoginRequested>((event, emit) async {
|
||||||
|
emit(AuthLoading());
|
||||||
|
final result = await loginUseCase(LoginParams(email: event.email));
|
||||||
|
result.fold(
|
||||||
|
(failure) => emit(AuthError(failure)),
|
||||||
|
(user) => emit(AuthAuthenticated(user)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// login_page.dart (StatelessWidget)
|
||||||
|
class LoginPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AuthBloc, AuthState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AuthLoading) return LoadingIndicator();
|
||||||
|
if (state is AuthError) return ErrorWidget(state.message);
|
||||||
|
return LoginForm();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ setState in Pages for complex state
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
bool _isLoading = false; // ← NO! Use BLoC
|
||||||
|
String? _error; // ← NO! Use BLoC
|
||||||
|
|
||||||
|
void _login() {
|
||||||
|
setState(() => _isLoading = true); // ← NO! Use BLoC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RECOMMENDATION:** Pages should be `StatelessWidget` with state delegated to BLoCs.
|
||||||
|
|
||||||
|
### Data Transformation → Repositories
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// profile_repository_impl.dart
|
||||||
|
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||||
|
@override
|
||||||
|
Future<Staff> getProfile(String id) async {
|
||||||
|
final response = await dataConnect.getStaffById(id: id).execute();
|
||||||
|
// Data transformation happens here
|
||||||
|
return Staff(
|
||||||
|
id: response.data.staff.id,
|
||||||
|
name: response.data.staff.name,
|
||||||
|
// Map Data Connect model to Domain entity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ JSON parsing in UI
|
||||||
|
class ProfilePage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final json = jsonDecode(response.body); // ← NO!
|
||||||
|
final name = json['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ JSON parsing in Domain Use Case
|
||||||
|
class GetProfileUseCase extends UseCase<Staff, String> {
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, Staff>> call(String id) async {
|
||||||
|
final response = await http.get('/staff/$id');
|
||||||
|
final json = jsonDecode(response.body); // ← NO!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation → Flutter Modular + Safe Extensions
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// Use Safe Navigation Extensions
|
||||||
|
import 'package:krow_core/krow_core.dart';
|
||||||
|
|
||||||
|
// In widget/BLoC:
|
||||||
|
Modular.to.safePush('/profile');
|
||||||
|
Modular.to.safeNavigate('/home');
|
||||||
|
Modular.to.popSafe();
|
||||||
|
|
||||||
|
// Even better: Use Typed Navigators
|
||||||
|
Modular.to.toStaffHome(); // Defined in StaffNavigator
|
||||||
|
Modular.to.toShiftDetails(shiftId: '123');
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Direct Navigator.push
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => ProfilePage()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ❌ Direct Modular navigation without safety
|
||||||
|
Modular.to.navigate('/profile'); // ← Can cause blank screens
|
||||||
|
Modular.to.pop(); // ← Can crash if stack is empty
|
||||||
|
```
|
||||||
|
|
||||||
|
**PATTERN:** All navigation MUST have fallback to Home page. Safe extensions automatically handle this.
|
||||||
|
|
||||||
|
### Session Management → DataConnectService + SessionHandlerMixin
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// In main.dart:
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Initialize session listener (pick allowed roles for app)
|
||||||
|
DataConnectService.instance.initializeAuthListener(
|
||||||
|
allowedRoles: ['STAFF', 'BOTH'], // for staff app
|
||||||
|
);
|
||||||
|
|
||||||
|
runApp(
|
||||||
|
SessionListener( // Wraps entire app
|
||||||
|
child: ModularApp(module: AppModule(), child: AppWidget()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In repository:
|
||||||
|
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||||
|
final DataConnectService _service = DataConnectService.instance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Staff> getProfile(String id) async {
|
||||||
|
// _service.run() handles:
|
||||||
|
// - Auth validation
|
||||||
|
// - Token refresh (if <5 min to expiry)
|
||||||
|
// - Error handling with 3 retries
|
||||||
|
return await _service.run(() async {
|
||||||
|
final response = await _service.connector
|
||||||
|
.getStaffById(id: id)
|
||||||
|
.execute();
|
||||||
|
return _mapToStaff(response.data.staff);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PATTERN:**
|
||||||
|
- **SessionListener** widget wraps app and shows dialogs for session errors
|
||||||
|
- **SessionHandlerMixin** in `DataConnectService` provides automatic token refresh
|
||||||
|
- **3-attempt retry logic** with exponential backoff (1s → 2s → 4s)
|
||||||
|
- **Role validation** configurable per app
|
||||||
|
|
||||||
|
## 4. Localization Integration (core_localization)
|
||||||
|
|
||||||
|
All user-facing text MUST be localized.
|
||||||
|
|
||||||
|
### String Management
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// In presentation layer:
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
|
||||||
|
class LoginPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Text(context.strings.loginButton); // ← From localization
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: _login,
|
||||||
|
child: Text(context.strings.submit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ FORBIDDEN:**
|
||||||
|
```dart
|
||||||
|
// ❌ Hardcoded English strings
|
||||||
|
Text('Login')
|
||||||
|
Text('Submit')
|
||||||
|
ElevatedButton(child: Text('Click here'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### BLoC Integration
|
||||||
|
|
||||||
|
**✅ CORRECT:**
|
||||||
|
```dart
|
||||||
|
// BLoCs emit domain failures (not localized strings)
|
||||||
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
|
on<LoginRequested>((event, emit) async {
|
||||||
|
final result = await loginUseCase(params);
|
||||||
|
result.fold(
|
||||||
|
(failure) => emit(AuthError(failure)), // ← Domain failure
|
||||||
|
(user) => emit(AuthAuthenticated(user)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI translates failures to user-friendly messages
|
||||||
|
class LoginPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AuthBloc, AuthState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AuthError) {
|
||||||
|
final message = ErrorTranslator.translate(
|
||||||
|
state.failure,
|
||||||
|
context.strings,
|
||||||
|
);
|
||||||
|
return ErrorWidget(message); // ← Localized
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### App Setup
|
||||||
|
|
||||||
|
Apps must import `LocalizationModule()`:
|
||||||
|
```dart
|
||||||
|
// app_module.dart
|
||||||
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
|
List<Module> get imports => [
|
||||||
|
LocalizationModule(), // ← Required
|
||||||
|
DataConnectModule(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// main.dart
|
||||||
|
runApp(
|
||||||
|
BlocProvider<LocaleBloc>( // ← Expose locale state
|
||||||
|
create: (_) => Modular.get<LocaleBloc>(),
|
||||||
|
child: TranslationProvider( // ← Enable context.strings
|
||||||
|
child: MaterialApp.router(...),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Data Connect Integration
|
||||||
|
|
||||||
|
All backend access goes through `DataConnectService`.
|
||||||
|
|
||||||
|
### Repository Pattern
|
||||||
|
|
||||||
|
**Step 1:** Define interface in feature domain:
|
||||||
|
```dart
|
||||||
|
// domain/repositories/profile_repository_interface.dart
|
||||||
|
abstract interface class ProfileRepositoryInterface {
|
||||||
|
Future<Staff> getProfile(String id);
|
||||||
|
Future<bool> updateProfile(Staff profile);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2:** Implement using `DataConnectService.run()`:
|
||||||
|
```dart
|
||||||
|
// data/repositories_impl/profile_repository_impl.dart
|
||||||
|
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||||
|
final DataConnectService _service = DataConnectService.instance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Staff> getProfile(String id) async {
|
||||||
|
return await _service.run(() async {
|
||||||
|
final response = await _service.connector
|
||||||
|
.getStaffById(id: id)
|
||||||
|
.execute();
|
||||||
|
return _mapToStaff(response.data.staff);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of `_service.run()`:**
|
||||||
|
- ✅ Automatic auth validation
|
||||||
|
- ✅ Token refresh if needed
|
||||||
|
- ✅ 3-attempt retry with exponential backoff
|
||||||
|
- ✅ Consistent error handling
|
||||||
|
|
||||||
|
### Session Store Pattern
|
||||||
|
|
||||||
|
After successful auth, populate session stores:
|
||||||
|
```dart
|
||||||
|
// For Staff App:
|
||||||
|
StaffSessionStore.instance.setSession(
|
||||||
|
StaffSession(
|
||||||
|
user: user,
|
||||||
|
staff: staff,
|
||||||
|
ownerId: ownerId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// For Client App:
|
||||||
|
ClientSessionStore.instance.setSession(
|
||||||
|
ClientSession(
|
||||||
|
user: user,
|
||||||
|
business: business,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lazy Loading:** If session is null, fetch via `getStaffById()` or `getBusinessById()` and update store.
|
||||||
|
|
||||||
|
## 6. Prototype Migration Rules
|
||||||
|
|
||||||
|
When migrating from `prototypes/`:
|
||||||
|
|
||||||
|
### ✅ MAY Copy
|
||||||
|
- Icons, images, assets (but match to design system)
|
||||||
|
- `build` methods for UI layout structure
|
||||||
|
- Screen flow and navigation patterns
|
||||||
|
|
||||||
|
### ❌ MUST REJECT & REFACTOR
|
||||||
|
- `GetX`, `Provider`, or `MVC` patterns
|
||||||
|
- Any state management not using BLoC
|
||||||
|
- Direct HTTP calls (must use Data Connect)
|
||||||
|
- Hardcoded colors/typography (must use design system)
|
||||||
|
- Global state variables
|
||||||
|
- Navigation without Modular
|
||||||
|
|
||||||
|
### Colors & Typography Migration
|
||||||
|
**When matching POC to production:**
|
||||||
|
1. Find closest color in `UiColors` (don't add new colors without approval)
|
||||||
|
2. Find closest text style in `UiTypography`
|
||||||
|
3. Use design system constants, NOT POC hardcoded values
|
||||||
|
|
||||||
|
**DO NOT change the design system itself.** Colors and typography are FINAL. Match your feature to the system, not the other way around.
|
||||||
|
|
||||||
|
## 7. Handling Ambiguity
|
||||||
|
|
||||||
|
If requirements are unclear:
|
||||||
|
|
||||||
|
1. **STOP** - Don't guess domain fields or workflows
|
||||||
|
2. **ANALYZE** - Refer to:
|
||||||
|
- Architecture: `apps/mobile/docs/01-architecture-principles.md`
|
||||||
|
- Design System: `apps/mobile/docs/02-design-system-usage.md`
|
||||||
|
- Existing features for patterns
|
||||||
|
3. **DOCUMENT** - Add `// ASSUMPTION: <explanation>` if you must proceed
|
||||||
|
4. **ASK** - Prefer asking user for clarification on business rules
|
||||||
|
|
||||||
|
## 8. Dependencies
|
||||||
|
|
||||||
|
### DO NOT
|
||||||
|
- Add 3rd party packages without checking `apps/mobile/packages/core` first
|
||||||
|
- Add `firebase_auth` or `firebase_data_connect` to Feature packages (they belong in `data_connect` only)
|
||||||
|
- Use `addSingleton` for BLoCs (always use `add` method in Modular)
|
||||||
|
|
||||||
|
### DO
|
||||||
|
- Use `DataConnectService.instance` for backend operations
|
||||||
|
- Use Flutter Modular for dependency injection
|
||||||
|
- Register BLoCs with `i.addSingleton<CubitType>(() => CubitType(...))`
|
||||||
|
- Register Use Cases as factories or singletons as needed
|
||||||
|
|
||||||
|
## 9. Error Handling Pattern
|
||||||
|
|
||||||
|
### Domain Failures
|
||||||
|
```dart
|
||||||
|
// domain/failures/auth_failure.dart
|
||||||
|
abstract class AuthFailure extends Failure {
|
||||||
|
const AuthFailure(String message) : super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidCredentialsFailure extends AuthFailure {
|
||||||
|
const InvalidCredentialsFailure() : super('Invalid credentials');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repository Error Mapping
|
||||||
|
```dart
|
||||||
|
// Map Data Connect exceptions to Domain failures
|
||||||
|
try {
|
||||||
|
final response = await dataConnect.query();
|
||||||
|
return Right(response);
|
||||||
|
} on DataConnectException catch (e) {
|
||||||
|
if (e.message.contains('unauthorized')) {
|
||||||
|
return Left(InvalidCredentialsFailure());
|
||||||
|
}
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI Feedback
|
||||||
|
```dart
|
||||||
|
// BLoC emits error state
|
||||||
|
emit(AuthError(failure));
|
||||||
|
|
||||||
|
// UI shows user-friendly message
|
||||||
|
if (state is AuthError) {
|
||||||
|
final message = ErrorTranslator.translate(state.failure, context.strings);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(message)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Errors
|
||||||
|
`SessionListener` automatically shows dialogs for:
|
||||||
|
- Session expiration
|
||||||
|
- Token refresh failures
|
||||||
|
- Network errors during auth
|
||||||
|
|
||||||
|
## 10. Testing Requirements
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
```dart
|
||||||
|
// Test use cases with real repository implementations
|
||||||
|
test('login with valid credentials returns user', () async {
|
||||||
|
final useCase = LoginUseCase(repository: mockRepository);
|
||||||
|
final result = await useCase(LoginParams(email: 'test@test.com'));
|
||||||
|
expect(result.isRight(), true);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Widget Tests
|
||||||
|
```dart
|
||||||
|
// Test UI widgets and BLoC interactions
|
||||||
|
testWidgets('shows loading indicator when logging in', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
BlocProvider<AuthBloc>(
|
||||||
|
create: (_) => authBloc,
|
||||||
|
child: LoginPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
authBloc.add(LoginRequested(email: 'test@test.com'));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byType(LoadingIndicator), findsOneWidget);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Test full feature flows end-to-end with Data Connect
|
||||||
|
- Use dependency injection to swap implementations if needed
|
||||||
|
|
||||||
|
## 11. Clean Code Principles
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- ✅ Add doc comments to all public classes and methods
|
||||||
|
```dart
|
||||||
|
/// Authenticates user with email and password.
|
||||||
|
///
|
||||||
|
/// Returns [User] on success or [AuthFailure] on failure.
|
||||||
|
/// Throws [NetworkException] if connection fails.
|
||||||
|
class LoginUseCase extends UseCase<User, LoginParams> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Single Responsibility
|
||||||
|
- Keep methods focused on one task
|
||||||
|
- Extract complex logic to separate methods
|
||||||
|
- Keep widget build methods concise
|
||||||
|
- Extract complex widgets to separate files
|
||||||
|
|
||||||
|
### Meaningful Names
|
||||||
|
```dart
|
||||||
|
// ✅ GOOD
|
||||||
|
final isProfileComplete = await checkProfileCompletion();
|
||||||
|
final userShifts = await fetchUserShifts();
|
||||||
|
|
||||||
|
// ❌ BAD
|
||||||
|
final flag = await check();
|
||||||
|
final data = await fetch();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enforcement Checklist
|
||||||
|
|
||||||
|
Before merging any mobile feature code:
|
||||||
|
|
||||||
|
### Architecture Compliance
|
||||||
|
- [ ] Feature follows package structure (domain/data/presentation)
|
||||||
|
- [ ] No business logic in BLoCs or Widgets
|
||||||
|
- [ ] All state management via BLoCs
|
||||||
|
- [ ] All backend access via repositories
|
||||||
|
- [ ] Session accessed via SessionStore, not global state
|
||||||
|
- [ ] Navigation uses Flutter Modular safe extensions
|
||||||
|
- [ ] No feature-to-feature imports
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- [ ] No hardcoded strings (use localization)
|
||||||
|
- [ ] No hardcoded colors/typography (use design system)
|
||||||
|
- [ ] All spacing uses UiConstants
|
||||||
|
- [ ] Doc comments on public APIs
|
||||||
|
- [ ] Meaningful variable names
|
||||||
|
- [ ] Zero analyzer warnings
|
||||||
|
|
||||||
|
### Integration
|
||||||
|
- [ ] Data Connect queries via `_service.run()`
|
||||||
|
- [ ] Error handling with domain failures
|
||||||
|
- [ ] Proper dependency injection in modules
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The key principle: **Clean Architecture with zero tolerance for violations.** Business logic in Use Cases, state in BLoCs, data access in Repositories, UI in Widgets. Features are isolated, backend is centralized, localization is mandatory, and design system is immutable.
|
||||||
|
|
||||||
|
When in doubt, refer to existing features following these patterns or ask for clarification. It's better to ask than to introduce architectural debt.
|
||||||
778
.agents/skills/krow-mobile-release/SKILL.md
Normal file
778
.agents/skills/krow-mobile-release/SKILL.md
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
---
|
||||||
|
name: krow-mobile-release
|
||||||
|
description: KROW mobile app release process including versioning strategy, CHANGELOG management, GitHub Actions workflows, APK signing, Git tagging, and hotfix procedures. Use this when preparing mobile releases, updating CHANGELOGs, triggering release workflows, creating hotfix branches, troubleshooting release issues, or documenting release features. Covers both staff (worker) and client mobile products across dev/stage/prod environments.
|
||||||
|
---
|
||||||
|
|
||||||
|
# KROW Mobile Release Process
|
||||||
|
|
||||||
|
This skill defines the comprehensive release process for KROW mobile applications (staff and client). It covers versioning, changelog management, GitHub Actions automation, and hotfix procedures.
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
- Preparing for a mobile app release
|
||||||
|
- Updating CHANGELOG files with new features
|
||||||
|
- Triggering GitHub Actions release workflows
|
||||||
|
- Creating hotfix branches for production issues
|
||||||
|
- Understanding version numbering strategy
|
||||||
|
- Setting up APK signing secrets
|
||||||
|
- Troubleshooting release workflow failures
|
||||||
|
- Documenting release notes
|
||||||
|
- Managing release cadence (dev → stage → prod)
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Release Workflows
|
||||||
|
- **Product Release:** [GitHub Actions - Product Release](https://github.com/Oloodi/krow-workforce/actions/workflows/product-release.yml)
|
||||||
|
- **Hotfix Creation:** [GitHub Actions - Product Hotfix](https://github.com/Oloodi/krow-workforce/actions/workflows/hotfix-branch-creation.yml)
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
- **Staff CHANGELOG:** `apps/mobile/apps/staff/CHANGELOG.md`
|
||||||
|
- **Client CHANGELOG:** `apps/mobile/apps/client/CHANGELOG.md`
|
||||||
|
- **Staff Version:** `apps/mobile/apps/staff/pubspec.yaml`
|
||||||
|
- **Client Version:** `apps/mobile/apps/client/pubspec.yaml`
|
||||||
|
|
||||||
|
### Comprehensive Documentation
|
||||||
|
For complete details, see: [`docs/RELEASE/mobile-releases.md`](docs/RELEASE/mobile-releases.md) (900+ lines)
|
||||||
|
|
||||||
|
## 1. Versioning Strategy
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
```
|
||||||
|
v{major}.{minor}.{patch}-{milestone}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `v0.0.1-m4` - Milestone 4 release
|
||||||
|
- `v0.1.0-m5` - Minor version bump for Milestone 5
|
||||||
|
- `v1.0.0` - First production release (no milestone suffix)
|
||||||
|
|
||||||
|
### Semantic Versioning Rules
|
||||||
|
|
||||||
|
**Major (X.0.0):**
|
||||||
|
- Breaking changes
|
||||||
|
- Complete architecture overhaul
|
||||||
|
- Incompatible API changes
|
||||||
|
|
||||||
|
**Minor (0.X.0):**
|
||||||
|
- New features
|
||||||
|
- Backwards-compatible additions
|
||||||
|
- Milestone completions
|
||||||
|
|
||||||
|
**Patch (0.0.X):**
|
||||||
|
- Bug fixes
|
||||||
|
- Security patches
|
||||||
|
- Performance improvements
|
||||||
|
|
||||||
|
**Milestone Suffix:**
|
||||||
|
- `-m1`, `-m2`, `-m3`, `-m4`, etc.
|
||||||
|
- Indicates pre-production milestone phase
|
||||||
|
- Removed for production releases
|
||||||
|
|
||||||
|
### Version Location
|
||||||
|
|
||||||
|
Versions are defined in `pubspec.yaml`:
|
||||||
|
|
||||||
|
**Staff App:**
|
||||||
|
```yaml
|
||||||
|
# apps/mobile/apps/staff/pubspec.yaml
|
||||||
|
name: krow_staff_app
|
||||||
|
version: 0.0.1-m4+1 # version+build_number
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client App:**
|
||||||
|
```yaml
|
||||||
|
# apps/mobile/apps/client/pubspec.yaml
|
||||||
|
name: krow_client_app
|
||||||
|
version: 0.0.1-m4+1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format:** `version+build`
|
||||||
|
- `version`: Semantic version with milestone (e.g., `0.0.1-m4`)
|
||||||
|
- `build`: Build number (increments with each build, e.g., `+1`, `+2`)
|
||||||
|
|
||||||
|
## 2. CHANGELOG Management
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
Each app maintains a separate CHANGELOG following [Keep a Changelog](https://keepachangelog.com/) format.
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```markdown
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New feature descriptions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Modified feature descriptions
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bug fix descriptions
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed feature descriptions
|
||||||
|
|
||||||
|
## [0.0.1-m4] - Milestone 4 - 2026-03-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Profile management with 13 subsections
|
||||||
|
- Documents & certificates management
|
||||||
|
- Benefits overview section
|
||||||
|
- Camera/gallery support for attire verification
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Enhanced session management with auto token refresh
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Navigation fallback to home on invalid routes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Section Guidelines
|
||||||
|
|
||||||
|
**[Unreleased]**
|
||||||
|
- Work in progress
|
||||||
|
- Features merged to dev but not released
|
||||||
|
- Updated continuously during development
|
||||||
|
|
||||||
|
**[Version] - Milestone X - Date**
|
||||||
|
- Released version
|
||||||
|
- Format: `[X.Y.Z-mN] - Milestone N - YYYY-MM-DD`
|
||||||
|
- Organized by change type (Added/Changed/Fixed/Removed)
|
||||||
|
|
||||||
|
### Change Type Definitions
|
||||||
|
|
||||||
|
**Added:**
|
||||||
|
- New features
|
||||||
|
- New UI screens
|
||||||
|
- New API integrations
|
||||||
|
- New user-facing capabilities
|
||||||
|
|
||||||
|
**Changed:**
|
||||||
|
- Modifications to existing features
|
||||||
|
- UI/UX improvements
|
||||||
|
- Performance enhancements
|
||||||
|
- Refactored code (if user-facing impact)
|
||||||
|
|
||||||
|
**Fixed:**
|
||||||
|
- Bug fixes
|
||||||
|
- Error handling improvements
|
||||||
|
- Crash fixes
|
||||||
|
- UI/UX issues resolved
|
||||||
|
|
||||||
|
**Removed:**
|
||||||
|
- Deprecated features
|
||||||
|
- Removed screens or capabilities
|
||||||
|
- Discontinued integrations
|
||||||
|
|
||||||
|
### Writing Guidelines
|
||||||
|
|
||||||
|
**✅ GOOD:**
|
||||||
|
```markdown
|
||||||
|
### Added
|
||||||
|
- Profile management with 13 subsections organized into onboarding, compliance, finances, and support categories
|
||||||
|
- Documents & certificates management with upload, status tracking, and expiry dates
|
||||||
|
- Camera and gallery support for attire verification with photo capture
|
||||||
|
- Benefits overview section displaying perks and company information
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ BAD:**
|
||||||
|
```markdown
|
||||||
|
### Added
|
||||||
|
- New stuff
|
||||||
|
- Fixed things
|
||||||
|
- Updated code
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Principles:**
|
||||||
|
- Be specific and descriptive
|
||||||
|
- Focus on user-facing changes
|
||||||
|
- Mention UI screens, features, or capabilities
|
||||||
|
- Avoid technical jargon users won't understand
|
||||||
|
- Group related changes together
|
||||||
|
|
||||||
|
### Updating CHANGELOG Workflow
|
||||||
|
|
||||||
|
**Step 1:** During development, add to `[Unreleased]`:
|
||||||
|
```markdown
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New shift calendar view with month/week toggle
|
||||||
|
- Shift acceptance confirmation dialog
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Navigation crash when popping empty stack
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2:** Before release, move to version section:
|
||||||
|
```markdown
|
||||||
|
## [0.1.0-m5] - Milestone 5 - 2026-03-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New shift calendar view with month/week toggle
|
||||||
|
- Shift acceptance confirmation dialog
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Navigation crash when popping empty stack
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
<!-- Empty for next development cycle -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3:** Update version in `pubspec.yaml`:
|
||||||
|
```yaml
|
||||||
|
version: 0.1.0-m5+1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Git Tagging Strategy
|
||||||
|
|
||||||
|
### Tag Format
|
||||||
|
|
||||||
|
```
|
||||||
|
krow-withus-<app>-mobile/<env>-vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `<app>`: `worker` (staff) or `client`
|
||||||
|
- `<env>`: `dev`, `stage`, or `prod`
|
||||||
|
- `vX.Y.Z`: Semantic version (from pubspec.yaml)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```
|
||||||
|
krow-withus-worker-mobile/dev-v0.0.1-m4
|
||||||
|
krow-withus-worker-mobile/stage-v0.0.1-m4
|
||||||
|
krow-withus-worker-mobile/prod-v0.0.1-m4
|
||||||
|
krow-withus-client-mobile/dev-v0.0.1-m4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag Creation
|
||||||
|
|
||||||
|
Tags are created automatically by GitHub Actions workflows. Manual tagging:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Staff app - dev environment
|
||||||
|
git tag krow-withus-worker-mobile/dev-v0.0.1-m4
|
||||||
|
git push origin krow-withus-worker-mobile/dev-v0.0.1-m4
|
||||||
|
|
||||||
|
# Client app - prod environment
|
||||||
|
git tag krow-withus-client-mobile/prod-v1.0.0
|
||||||
|
git push origin krow-withus-client-mobile/prod-v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag Listing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all mobile tags
|
||||||
|
git tag -l "krow-withus-*-mobile/*"
|
||||||
|
|
||||||
|
# List staff app tags
|
||||||
|
git tag -l "krow-withus-worker-mobile/*"
|
||||||
|
|
||||||
|
# List production tags
|
||||||
|
git tag -l "krow-withus-*-mobile/prod-*"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. GitHub Actions Workflows
|
||||||
|
|
||||||
|
### 4.1 Product Release Workflow
|
||||||
|
|
||||||
|
**File:** `.github/workflows/product-release.yml`
|
||||||
|
|
||||||
|
**Purpose:** Automated production releases with APK signing
|
||||||
|
|
||||||
|
**Trigger:** Manual dispatch via GitHub UI
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- `app`: Select `worker` (staff) or `client`
|
||||||
|
- `environment`: Select `dev`, `stage`, or `prod`
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. ✅ Extracts version from `pubspec.yaml` automatically
|
||||||
|
2. ✅ Builds signed APKs for selected app
|
||||||
|
3. ✅ Creates GitHub release with CHANGELOG notes
|
||||||
|
4. ✅ Tags release (e.g., `krow-withus-worker-mobile/dev-v0.0.1-m4`)
|
||||||
|
5. ✅ Uploads APKs as release assets
|
||||||
|
6. ✅ Generates step summary with emojis
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- **No manual version input** - reads from pubspec.yaml
|
||||||
|
- **APK signing** - uses GitHub Secrets for keystore
|
||||||
|
- **CHANGELOG extraction** - pulls release notes automatically
|
||||||
|
- **Visual feedback** - emojis in all steps
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```
|
||||||
|
1. Go to: GitHub Actions → "📦 Product Release"
|
||||||
|
2. Click "Run workflow"
|
||||||
|
3. Select app (worker/client)
|
||||||
|
4. Select environment (dev/stage/prod)
|
||||||
|
5. Click "Run workflow"
|
||||||
|
6. Wait for completion (~5-10 minutes)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Release Naming:**
|
||||||
|
```
|
||||||
|
Krow With Us - Worker Product - DEV - v0.0.1-m4
|
||||||
|
Krow With Us - Client Product - PROD - v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Product Hotfix Workflow
|
||||||
|
|
||||||
|
**File:** `.github/workflows/hotfix-branch-creation.yml`
|
||||||
|
|
||||||
|
**Purpose:** Emergency production fix automation
|
||||||
|
|
||||||
|
**Trigger:** Manual dispatch with version input
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- `current_version`: Current production version (e.g., `0.0.1-m4`)
|
||||||
|
- `issue_description`: Brief description of the hotfix
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. ✅ Creates `hotfix/<version>` branch from latest production tag
|
||||||
|
2. ✅ Auto-increments PATCH version (e.g., `0.0.1-m4` → `0.0.2-m4`)
|
||||||
|
3. ✅ Updates `pubspec.yaml` with new version
|
||||||
|
4. ✅ Updates `CHANGELOG.md` with hotfix section
|
||||||
|
5. ✅ Creates PR back to main branch
|
||||||
|
6. ✅ Includes hotfix instructions in PR description
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```
|
||||||
|
1. Go to: GitHub Actions → "🚨 Product Hotfix - Create Branch"
|
||||||
|
2. Click "Run workflow"
|
||||||
|
3. Enter current production version (e.g., 0.0.1-m4)
|
||||||
|
4. Enter issue description (e.g., "critical crash on login")
|
||||||
|
5. Click "Run workflow"
|
||||||
|
6. Workflow creates branch and PR
|
||||||
|
7. Fix bug on hotfix branch
|
||||||
|
8. Merge PR to main
|
||||||
|
9. Use Product Release workflow to deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hotfix Branch Naming:**
|
||||||
|
```
|
||||||
|
hotfix/0.0.2-m4-critical-crash-on-login
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Helper Scripts
|
||||||
|
|
||||||
|
**Location:** `.github/scripts/`
|
||||||
|
|
||||||
|
**Available Scripts:**
|
||||||
|
1. **extract-version.sh** - Extract version from pubspec.yaml
|
||||||
|
2. **generate-tag-name.sh** - Generate standardized tag names
|
||||||
|
3. **extract-release-notes.sh** - Extract CHANGELOG sections
|
||||||
|
4. **create-release-summary.sh** - Generate GitHub Step Summary with emojis
|
||||||
|
|
||||||
|
**Script Permissions:**
|
||||||
|
```bash
|
||||||
|
chmod +x .github/scripts/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage Example:**
|
||||||
|
```bash
|
||||||
|
# Extract version from staff app
|
||||||
|
.github/scripts/extract-version.sh apps/mobile/apps/staff/pubspec.yaml
|
||||||
|
|
||||||
|
# Generate tag name
|
||||||
|
.github/scripts/generate-tag-name.sh worker dev 0.0.1-m4
|
||||||
|
|
||||||
|
# Extract release notes for version
|
||||||
|
.github/scripts/extract-release-notes.sh apps/mobile/apps/staff/CHANGELOG.md 0.0.1-m4
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. APK Signing Setup
|
||||||
|
|
||||||
|
### Required GitHub Secrets (24 Total)
|
||||||
|
|
||||||
|
**Per App (12 secrets each):**
|
||||||
|
|
||||||
|
**Staff (Worker) App:**
|
||||||
|
```
|
||||||
|
STAFF_UPLOAD_KEYSTORE_BASE64 # Base64-encoded keystore file
|
||||||
|
STAFF_UPLOAD_STORE_PASSWORD # Keystore password
|
||||||
|
STAFF_UPLOAD_KEY_ALIAS # Key alias
|
||||||
|
STAFF_UPLOAD_KEY_PASSWORD # Key password
|
||||||
|
STAFF_KEYSTORE_PROPERTIES_BASE64 # Base64-encoded key.properties file
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client App:**
|
||||||
|
```
|
||||||
|
CLIENT_UPLOAD_KEYSTORE_BASE64
|
||||||
|
CLIENT_UPLOAD_STORE_PASSWORD
|
||||||
|
CLIENT_UPLOAD_KEY_ALIAS
|
||||||
|
CLIENT_UPLOAD_KEY_PASSWORD
|
||||||
|
CLIENT_KEYSTORE_PROPERTIES_BASE64
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating Secrets
|
||||||
|
|
||||||
|
**Step 1: Create Keystore**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For staff app
|
||||||
|
keytool -genkey -v \
|
||||||
|
-keystore staff-upload-keystore.jks \
|
||||||
|
-keyalg RSA \
|
||||||
|
-keysize 2048 \
|
||||||
|
-validity 10000 \
|
||||||
|
-alias staff-upload
|
||||||
|
|
||||||
|
# For client app
|
||||||
|
keytool -genkey -v \
|
||||||
|
-keystore client-upload-keystore.jks \
|
||||||
|
-keyalg RSA \
|
||||||
|
-keysize 2048 \
|
||||||
|
-validity 10000 \
|
||||||
|
-alias client-upload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Base64 Encode**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encode keystore
|
||||||
|
base64 -i staff-upload-keystore.jks | tr -d '\n' > staff-keystore.txt
|
||||||
|
|
||||||
|
# Encode key.properties
|
||||||
|
base64 -i key.properties | tr -d '\n' > key-props.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Add to GitHub Secrets**
|
||||||
|
|
||||||
|
```
|
||||||
|
Repository → Settings → Secrets and variables → Actions → New repository secret
|
||||||
|
```
|
||||||
|
|
||||||
|
Add each secret:
|
||||||
|
- Name: `STAFF_UPLOAD_KEYSTORE_BASE64`
|
||||||
|
- Value: Contents of `staff-keystore.txt`
|
||||||
|
|
||||||
|
Repeat for all 24 secrets.
|
||||||
|
|
||||||
|
### key.properties Format
|
||||||
|
|
||||||
|
```properties
|
||||||
|
storePassword=your_store_password
|
||||||
|
keyPassword=your_key_password
|
||||||
|
keyAlias=staff-upload
|
||||||
|
storeFile=../staff-upload-keystore.jks
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Release Process (Step-by-Step)
|
||||||
|
|
||||||
|
### Standard Release (Dev/Stage/Prod)
|
||||||
|
|
||||||
|
**Step 1: Prepare CHANGELOG**
|
||||||
|
|
||||||
|
Update `CHANGELOG.md` with all changes since last release:
|
||||||
|
```markdown
|
||||||
|
## [0.1.0-m5] - Milestone 5 - 2026-03-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Shift calendar with month/week views
|
||||||
|
- Enhanced navigation with typed routes
|
||||||
|
- Profile completion wizard
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Session token refresh timing
|
||||||
|
- Navigation fallback logic
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Update Version**
|
||||||
|
|
||||||
|
Edit `pubspec.yaml`:
|
||||||
|
```yaml
|
||||||
|
version: 0.1.0-m5+1 # Changed from 0.0.1-m4+1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Commit and Push**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add apps/mobile/apps/staff/CHANGELOG.md
|
||||||
|
git add apps/mobile/apps/staff/pubspec.yaml
|
||||||
|
git commit -m "chore(staff): prepare v0.1.0-m5 release"
|
||||||
|
git push origin dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Trigger Workflow**
|
||||||
|
|
||||||
|
1. Go to GitHub Actions → "📦 Product Release"
|
||||||
|
2. Click "Run workflow"
|
||||||
|
3. Select branch: `dev`
|
||||||
|
4. Select app: `worker` (or `client`)
|
||||||
|
5. Select environment: `dev` (or `stage`, `prod`)
|
||||||
|
6. Click "Run workflow"
|
||||||
|
|
||||||
|
**Step 5: Monitor Progress**
|
||||||
|
|
||||||
|
Watch workflow execution:
|
||||||
|
- ⏳ Version extraction
|
||||||
|
- ⏳ APK building
|
||||||
|
- ⏳ APK signing
|
||||||
|
- ⏳ GitHub Release creation
|
||||||
|
- ⏳ Tag creation
|
||||||
|
- ⏳ Asset upload
|
||||||
|
|
||||||
|
**Step 6: Verify Release**
|
||||||
|
|
||||||
|
1. Check GitHub Releases page
|
||||||
|
2. Download APK to verify
|
||||||
|
3. Install on test device
|
||||||
|
4. Verify version in app
|
||||||
|
|
||||||
|
### Hotfix Release
|
||||||
|
|
||||||
|
**Step 1: Identify Production Issue**
|
||||||
|
|
||||||
|
- Critical bug in production
|
||||||
|
- User-reported crash
|
||||||
|
- Security vulnerability
|
||||||
|
|
||||||
|
**Step 2: Trigger Hotfix Workflow**
|
||||||
|
|
||||||
|
1. Go to GitHub Actions → "🚨 Product Hotfix - Create Branch"
|
||||||
|
2. Click "Run workflow"
|
||||||
|
3. Enter current version: `0.0.1-m4`
|
||||||
|
4. Enter description: `Critical crash on login screen`
|
||||||
|
5. Click "Run workflow"
|
||||||
|
|
||||||
|
**Step 3: Review Created Branch**
|
||||||
|
|
||||||
|
Workflow creates:
|
||||||
|
- Branch: `hotfix/0.0.2-m4-critical-crash-on-login`
|
||||||
|
- PR to `main` branch
|
||||||
|
- Updated `pubspec.yaml`: `0.0.2-m4+1`
|
||||||
|
- Updated `CHANGELOG.md` with hotfix section
|
||||||
|
|
||||||
|
**Step 4: Fix Bug**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout hotfix/0.0.2-m4-critical-crash-on-login
|
||||||
|
|
||||||
|
# Make fixes
|
||||||
|
# ... code changes ...
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "fix(auth): resolve crash on login screen"
|
||||||
|
git push origin hotfix/0.0.2-m4-critical-crash-on-login
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5: Merge PR**
|
||||||
|
|
||||||
|
1. Review PR on GitHub
|
||||||
|
2. Approve and merge to `main`
|
||||||
|
3. Delete hotfix branch
|
||||||
|
|
||||||
|
**Step 6: Release to Production**
|
||||||
|
|
||||||
|
1. Use Product Release workflow
|
||||||
|
2. Select `main` branch
|
||||||
|
3. Select `prod` environment
|
||||||
|
4. Deploy hotfix
|
||||||
|
|
||||||
|
## 7. Release Cadence
|
||||||
|
|
||||||
|
### Development (dev)
|
||||||
|
|
||||||
|
- **Frequency:** Multiple times per day
|
||||||
|
- **Purpose:** Testing features in dev environment
|
||||||
|
- **Branch:** `dev`
|
||||||
|
- **Audience:** Internal development team
|
||||||
|
- **Approval:** Not required
|
||||||
|
|
||||||
|
### Staging (stage)
|
||||||
|
|
||||||
|
- **Frequency:** 1-2 times per week
|
||||||
|
- **Purpose:** QA testing, stakeholder demos
|
||||||
|
- **Branch:** `main`
|
||||||
|
- **Audience:** QA team, stakeholders
|
||||||
|
- **Approval:** Tech lead approval
|
||||||
|
|
||||||
|
### Production (prod)
|
||||||
|
|
||||||
|
- **Frequency:** Every 2-3 weeks (milestone completion)
|
||||||
|
- **Purpose:** End-user releases
|
||||||
|
- **Branch:** `main`
|
||||||
|
- **Audience:** All users
|
||||||
|
- **Approval:** Product owner + tech lead approval
|
||||||
|
|
||||||
|
### Milestone Releases
|
||||||
|
|
||||||
|
- **Frequency:** Every 2-4 weeks
|
||||||
|
- **Version Bump:** Minor version (e.g., `0.1.0-m5` → `0.2.0-m6`)
|
||||||
|
- **Process:**
|
||||||
|
1. Complete all milestone features
|
||||||
|
2. Update CHANGELOG with comprehensive release notes
|
||||||
|
3. Deploy to stage for final QA
|
||||||
|
4. After approval, deploy to prod
|
||||||
|
5. Create GitHub release with milestone summary
|
||||||
|
|
||||||
|
## 8. Troubleshooting
|
||||||
|
|
||||||
|
### Workflow Fails: Version Extraction
|
||||||
|
|
||||||
|
**Error:** "Could not extract version from pubspec.yaml"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Verify `pubspec.yaml` exists at expected path
|
||||||
|
2. Check version format: `version: X.Y.Z-mN+B`
|
||||||
|
3. Ensure no extra spaces or tabs
|
||||||
|
4. Verify file is committed and pushed
|
||||||
|
|
||||||
|
### Workflow Fails: APK Signing
|
||||||
|
|
||||||
|
**Error:** "Keystore password incorrect"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Verify GitHub Secrets are set correctly
|
||||||
|
2. Re-generate and re-encode keystore
|
||||||
|
3. Check key.properties format
|
||||||
|
4. Ensure passwords don't contain special characters that need escaping
|
||||||
|
|
||||||
|
### Workflow Fails: CHANGELOG Extraction
|
||||||
|
|
||||||
|
**Error:** "Could not find version in CHANGELOG"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Verify CHANGELOG format matches: `## [X.Y.Z-mN] - Milestone N - YYYY-MM-DD`
|
||||||
|
2. Check square brackets are present
|
||||||
|
3. Ensure version matches pubspec.yaml
|
||||||
|
4. Add version section if missing
|
||||||
|
|
||||||
|
### Tag Already Exists
|
||||||
|
|
||||||
|
**Error:** "tag already exists"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Delete existing tag locally and remotely:
|
||||||
|
```bash
|
||||||
|
git tag -d krow-withus-worker-mobile/dev-v0.0.1-m4
|
||||||
|
git push origin :refs/tags/krow-withus-worker-mobile/dev-v0.0.1-m4
|
||||||
|
```
|
||||||
|
2. Re-run workflow
|
||||||
|
|
||||||
|
### Build Fails: Flutter Errors
|
||||||
|
|
||||||
|
**Error:** "flutter build failed"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Test build locally first:
|
||||||
|
```bash
|
||||||
|
cd apps/mobile/apps/staff
|
||||||
|
flutter build apk --release
|
||||||
|
```
|
||||||
|
2. Fix any analyzer errors
|
||||||
|
3. Ensure all dependencies are compatible
|
||||||
|
4. Clear build cache:
|
||||||
|
```bash
|
||||||
|
flutter clean
|
||||||
|
flutter pub get
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Local Testing
|
||||||
|
|
||||||
|
Before triggering workflows, test builds locally:
|
||||||
|
|
||||||
|
### Building APKs Locally
|
||||||
|
|
||||||
|
**Staff App:**
|
||||||
|
```bash
|
||||||
|
cd apps/mobile/apps/staff
|
||||||
|
flutter clean
|
||||||
|
flutter pub get
|
||||||
|
flutter build apk --release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client App:**
|
||||||
|
```bash
|
||||||
|
cd apps/mobile/apps/client
|
||||||
|
flutter clean
|
||||||
|
flutter pub get
|
||||||
|
flutter build apk --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Release Notes
|
||||||
|
|
||||||
|
Extract CHANGELOG section:
|
||||||
|
```bash
|
||||||
|
.github/scripts/extract-release-notes.sh \
|
||||||
|
apps/mobile/apps/staff/CHANGELOG.md \
|
||||||
|
0.0.1-m4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verifying Version
|
||||||
|
|
||||||
|
Extract version from pubspec:
|
||||||
|
```bash
|
||||||
|
.github/scripts/extract-version.sh \
|
||||||
|
apps/mobile/apps/staff/pubspec.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Best Practices
|
||||||
|
|
||||||
|
### CHANGELOG
|
||||||
|
- ✅ Update continuously during development
|
||||||
|
- ✅ Be specific and user-focused
|
||||||
|
- ✅ Group related changes
|
||||||
|
- ✅ Include UI/UX changes
|
||||||
|
- ❌ Don't include technical debt or refactoring (unless user-facing)
|
||||||
|
- ❌ Don't use vague descriptions
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
- ✅ Use semantic versioning strictly
|
||||||
|
- ✅ Increment patch for bug fixes
|
||||||
|
- ✅ Increment minor for new features
|
||||||
|
- ✅ Keep milestone suffix until production
|
||||||
|
- ❌ Don't skip versions
|
||||||
|
- ❌ Don't use arbitrary version numbers
|
||||||
|
|
||||||
|
### Git Tags
|
||||||
|
- ✅ Follow standard format
|
||||||
|
- ✅ Let workflow create tags automatically
|
||||||
|
- ✅ Keep tags synced with releases
|
||||||
|
- ❌ Don't create tags manually unless necessary
|
||||||
|
- ❌ Don't reuse deleted tags
|
||||||
|
|
||||||
|
### Workflows
|
||||||
|
- ✅ Test builds locally first
|
||||||
|
- ✅ Monitor workflow execution
|
||||||
|
- ✅ Verify release assets
|
||||||
|
- ✅ Test APK on device before announcing
|
||||||
|
- ❌ Don't trigger multiple workflows simultaneously
|
||||||
|
- ❌ Don't bypass approval process
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Release Process Overview:**
|
||||||
|
1. Update CHANGELOG with changes
|
||||||
|
2. Update version in pubspec.yaml
|
||||||
|
3. Commit and push to appropriate branch
|
||||||
|
4. Trigger Product Release workflow
|
||||||
|
5. Monitor execution and verify release
|
||||||
|
6. Test APK on device
|
||||||
|
7. Announce to team/users
|
||||||
|
|
||||||
|
**Key Files:**
|
||||||
|
- `apps/mobile/apps/staff/CHANGELOG.md`
|
||||||
|
- `apps/mobile/apps/client/CHANGELOG.md`
|
||||||
|
- `apps/mobile/apps/staff/pubspec.yaml`
|
||||||
|
- `apps/mobile/apps/client/pubspec.yaml`
|
||||||
|
|
||||||
|
**Key Workflows:**
|
||||||
|
- Product Release (standard releases)
|
||||||
|
- Product Hotfix (emergency fixes)
|
||||||
|
|
||||||
|
**For Complete Details:**
|
||||||
|
See [`docs/RELEASE/mobile-releases.md`](docs/RELEASE/mobile-releases.md) - 900+ line comprehensive guide with:
|
||||||
|
- Detailed APK signing setup
|
||||||
|
- Complete troubleshooting guide
|
||||||
|
- All helper scripts documentation
|
||||||
|
- Release checklist
|
||||||
|
- Security best practices
|
||||||
|
|
||||||
|
When in doubt, refer to the comprehensive documentation or ask for clarification before releasing to production.
|
||||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
<!-- Provide a clear and concise description of your changes -->
|
<!-- Provide a clear and concise description of your changes -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Related Issues
|
||||||
|
|
||||||
|
<!-- Link any related issues using #issue_number -->
|
||||||
|
|
||||||
|
Closes #
|
||||||
|
Related to #
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,15 +41,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔗 Related Issues
|
|
||||||
|
|
||||||
<!-- Link any related issues using #issue_number -->
|
|
||||||
|
|
||||||
Closes #
|
|
||||||
Related to #
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Testing
|
## ✅ Testing
|
||||||
|
|
||||||
<!-- Describe how you tested these changes -->
|
<!-- Describe how you tested these changes -->
|
||||||
@@ -93,7 +92,6 @@ Related to #
|
|||||||
|
|
||||||
- [ ] Code quality and readability
|
- [ ] Code quality and readability
|
||||||
- [ ] Design patterns follow project conventions
|
- [ ] Design patterns follow project conventions
|
||||||
- [ ] Test coverage is adequate
|
|
||||||
- [ ] Performance implications reviewed
|
- [ ] Performance implications reviewed
|
||||||
- [ ] Security concerns addressed
|
- [ ] Security concerns addressed
|
||||||
- [ ] Documentation is complete
|
- [ ] Documentation is complete
|
||||||
|
|||||||
57
BLOCKERS.md
57
BLOCKERS.md
@@ -1,57 +0,0 @@
|
|||||||
# Blockers
|
|
||||||
|
|
||||||
## App
|
|
||||||
- Client application
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/210
|
|
||||||
### Why this task is blocked:
|
|
||||||
- This task is currently blocked, mainly because client registration via social logins is blocked. To create a business, we require a business name, and with social sign-up we don’t have a screen to capture that information. Because of this, the flow cannot be completed.
|
|
||||||
- The best option, in my opinion, is to allow Google and Apple sign-in only for existing users, and not use them for new user registration.
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/257
|
|
||||||
### Why this task is blocked:
|
|
||||||
- Although this page existed in the prototype, it was not connected to any other pages. In other words, there was no way to navigate to it from anywhere in the application. Therefore, this issue can be closed, as the page is not required in the main application.
|
|
||||||
|
|
||||||
## App
|
|
||||||
- Staff application
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/249
|
|
||||||
### Why this task is blocked:
|
|
||||||
- Although this page existed in the prototype, it was not connected to any other pages. In other words, there was no way to navigate to it from anywhere in the application. Therefore, this issue can be closed, as the page is not required in the main application.
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/262
|
|
||||||
### Why this task is blocked:
|
|
||||||
- Although this page existed in the prototype, it was not connected to any other pages. In other words, there was no way to navigate to it from anywhere in the application. Therefore, this issue can be closed, as the page is not required in the main application.
|
|
||||||
|
|
||||||
# Deviations
|
|
||||||
|
|
||||||
## App
|
|
||||||
- Client Application
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/240
|
|
||||||
### Deveations:
|
|
||||||
- In the web prototype, when creating an order, position role rates are displayed based on the selected vendor. This behavior was missing in the mobile prototype. Therefore, we added a dropdown to select the vendor and display the corresponding role rates based on that selection.
|
|
||||||
|
|
||||||
# Points to considerate in the future
|
|
||||||
- client APP:
|
|
||||||
- Billing need to download a pdf of their invoice.
|
|
||||||
- On app launch, check whether there is an active session. If a valid session exists, skip the auth flow and navigate directly to Home, loading business account.
|
|
||||||
- Add an expiration time (TTL) to the session (store expiresAt / expiryTimestamp) and invalidate/clear the session when it has expired.
|
|
||||||
- Rapid order need IA to work, I think we need also to add a form as the webpage.
|
|
||||||
- Staff APP:
|
|
||||||
- On app launch, check whether there is an active session. If a valid session exists, skip the auth flow and navigate directly to Home, loading Staff account.
|
|
||||||
- Add an expiration time (TTL) to the session (store expiresAt / expiryTimestamp) and invalidate/clear the session when it has expired.
|
|
||||||
- For staffs Skills = Roles? thinking in the future for the smart assigned that need to know the roles of staff to assign.
|
|
||||||
|
|
||||||
## App
|
|
||||||
- Staff Application
|
|
||||||
|
|
||||||
### Github issue
|
|
||||||
- https://github.com/Oloodi/krow-workforce/issues/248
|
|
||||||
### Deveations:
|
|
||||||
- Assumed that a worker can only have one shift per day.
|
|
||||||
52
README.md
52
README.md
@@ -1,7 +1,17 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="apps/mobile/packages/design_system/assets/logo-yellow.png" alt="KROW Logo" width="200"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
# KROW Workforce Monorepo
|
# KROW Workforce Monorepo
|
||||||
|
|
||||||
KROW is a comprehensive workforce management platform designed to streamline operations for events, hospitality, and enterprise staffing. This monorepo contains all components of the ecosystem, from the data layer to the user-facing applications.
|
KROW is a comprehensive workforce management platform designed to streamline operations for events, hospitality, and enterprise staffing. This monorepo contains all components of the ecosystem, from the data layer to the user-facing applications.
|
||||||
|
|
||||||
|
## 📍 Current Status
|
||||||
|
|
||||||
|
**Latest Milestone:** M4 (Released: March 5, 2026)
|
||||||
|
- ✅ Staff Mobile App: [v0.0.1-m4](https://github.com/Oloodi/krow-workforce/releases/tag/krow-withus-worker-mobile%2Fdev-v0.0.1-m4)
|
||||||
|
- ✅ Client Mobile App: [v0.0.1-m4](https://github.com/Oloodi/krow-workforce/releases/tag/krow-withus-client-mobile%2Fdev-v0.0.1-m4)
|
||||||
|
|
||||||
## 🚀 Repository Structure
|
## 🚀 Repository Structure
|
||||||
|
|
||||||
### 📦 Apps (`/apps`)
|
### 📦 Apps (`/apps`)
|
||||||
@@ -26,6 +36,7 @@ Tools and resources for the development and operations team:
|
|||||||
- **`/makefiles`**: Modularized `Makefile` logic for project automation.
|
- **`/makefiles`**: Modularized `Makefile` logic for project automation.
|
||||||
- **`/scripts`**: Automation scripts (security, hachage, environment setup).
|
- **`/scripts`**: Automation scripts (security, hachage, environment setup).
|
||||||
- **`/firebase`**: Global Firebase configuration (Firestore/Storage rules).
|
- **`/firebase`**: Global Firebase configuration (Firestore/Storage rules).
|
||||||
|
- **`/.github`**: GitHub Actions workflows for CI/CD and release automation.
|
||||||
|
|
||||||
## 🛠️ Tech Stack
|
## 🛠️ Tech Stack
|
||||||
- **Frontend:** React (Vite)
|
- **Frontend:** React (Vite)
|
||||||
@@ -58,17 +69,56 @@ This project uses a modular `Makefile` for all common tasks.
|
|||||||
make launchpad-dev
|
make launchpad-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
5. **Mobile app development:**
|
||||||
|
```bash
|
||||||
|
make mobile-install
|
||||||
|
make mobile-client-dev-android [DEVICE=android]
|
||||||
|
make mobile-staff-dev-android [DEVICE=android]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Release Process
|
||||||
|
|
||||||
|
### Mobile App Releases
|
||||||
|
|
||||||
|
We use GitHub Actions for automated mobile releases:
|
||||||
|
|
||||||
|
- **Standard Release**: Trigger [Product Release workflow](https://github.com/Oloodi/krow-workforce/actions/workflows/product-release.yml)
|
||||||
|
- Auto-extracts version from `pubspec.yaml`
|
||||||
|
- Creates Git tags: `krow-withus-<app>-mobile/<env>-vX.Y.Z`
|
||||||
|
- Generates GitHub Release with CHANGELOG
|
||||||
|
- Builds and signs APK (dev/stage/prod keystores)
|
||||||
|
|
||||||
|
- **Hotfix Release**: Trigger [Hotfix Branch Creation workflow](https://github.com/Oloodi/krow-workforce/actions/workflows/hotfix-branch-creation.yml)
|
||||||
|
- Auto-increments PATCH version
|
||||||
|
- Updates `pubspec.yaml` and `CHANGELOG.md`
|
||||||
|
- Creates PR with fix instructions
|
||||||
|
|
||||||
|
**See:** [Mobile Release Documentation](./docs/RELEASE/mobile-releases.md) for complete guide.
|
||||||
|
|
||||||
## 📚 Documentation
|
## 📚 Documentation
|
||||||
|
|
||||||
|
### Core Documentation
|
||||||
- **[00-vision.md](./docs/00-vision.md)**: Project objectives and guiding principles.
|
- **[00-vision.md](./docs/00-vision.md)**: Project objectives and guiding principles.
|
||||||
- **[01-backend-api-specification.md](./docs/01-backend-api-specification.md)**: (Legacy) Reference for data schemas.
|
- **[01-backend-api-specification.md](./docs/01-backend-api-specification.md)**: (Legacy) Reference for data schemas.
|
||||||
- **[02-codemagic-env-vars.md](./docs/02-codemagic-env-vars.md)**: Guide for CI/CD environment variables.
|
- **[02-codemagic-env-vars.md](./docs/02-codemagic-env-vars.md)**: Guide for CI/CD environment variables.
|
||||||
- **[03-contributing.md](./docs/03-contributing.md)**: Guidelines for new developers and setup checklist.
|
- **[03-contributing.md](./docs/03-contributing.md)**: Guidelines for new developers and setup checklist.
|
||||||
- **[04-sync-prototypes.md](./docs/04-sync-prototypes.md)**: How to sync prototypes for local dev and AI context.
|
- **[04-sync-prototypes.md](./docs/04-sync-prototypes.md)**: How to sync prototypes for local dev and AI context.
|
||||||
|
- **[05-project-onboarding-master.md](./docs/05-project-onboarding-master.md)**: Comprehensive onboarding guide and project overview.
|
||||||
|
|
||||||
### Mobile Development Documentation
|
### Mobile Development Documentation
|
||||||
|
- **[MOBILE/00-agent-development-rules.md](./docs/MOBILE/00-agent-development-rules.md)**: Rules and best practices for mobile development.
|
||||||
- **[MOBILE/01-architecture-principles.md](./docs/MOBILE/01-architecture-principles.md)**: Flutter clean architecture, package roles, and dependency flow.
|
- **[MOBILE/01-architecture-principles.md](./docs/MOBILE/01-architecture-principles.md)**: Flutter clean architecture, package roles, and dependency flow.
|
||||||
- **[MOBILE/02-design-system-usage.md](./docs/MOBILE/02-design-system-usage.md)**: Design system components and theming guidelines.
|
- **[MOBILE/02-design-system-usage.md](./docs/MOBILE/02-design-system-usage.md)**: Design system components and theming guidelines.
|
||||||
- **[MOBILE/00-agent-development-rules.md](./docs/MOBILE/00-agent-development-rules.md)**: Rules and best practices for mobile development.
|
- **[MOBILE/03-data-connect-connectors-pattern.md](./docs/MOBILE/03-data-connect-connectors-pattern.md)**: Data Connect integration patterns.
|
||||||
|
- **[MOBILE/04-use-case-completion-audit.md](./docs/MOBILE/04-use-case-completion-audit.md)**: Feature implementation status and audit.
|
||||||
|
- **[MOBILE/05-release-process.md](./docs/MOBILE/05-release-process.md)**: Mobile app release process (quick reference).
|
||||||
|
|
||||||
|
### Release Documentation
|
||||||
|
- **[RELEASE/mobile-releases.md](./docs/RELEASE/mobile-releases.md)**: Comprehensive mobile release guide with versioning, CHANGELOGs, GitHub Actions workflows, and APK signing.
|
||||||
|
|
||||||
|
### CHANGELOGs
|
||||||
|
- **[Staff Mobile CHANGELOG](./apps/mobile/apps/staff/CHANGELOG.md)**: Staff app release history (M3, M4).
|
||||||
|
- **[Client Mobile CHANGELOG](./apps/mobile/apps/client/CHANGELOG.md)**: Client app release history (M3, M4).
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
New to the team? Please read our **[Contributing Guide](./docs/03-contributing.md)** to get your environment set up and understand our workflow.
|
New to the team? Please read our **[Contributing Guide](./docs/03-contributing.md)** to get your environment set up and understand our workflow.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# KROW Workforce Platform - Project Onboarding Master Document
|
# KROW Workforce Platform - Project Onboarding Master Document
|
||||||
|
|
||||||
> **Version:** 1.1
|
> **Version:** 2.0
|
||||||
> **Last Updated:** 2026-01-22
|
> **Last Updated:** 2026-03-06
|
||||||
> **Purpose:** Source of Truth for Team Onboarding & Sprint Planning
|
> **Purpose:** Source of Truth for Team Onboarding & Sprint Planning
|
||||||
|
> **Latest Milestone:** M4 (Released: March 5, 2026)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
3. [Core Domain Logic](#3-core-domain-logic)
|
3. [Core Domain Logic](#3-core-domain-logic)
|
||||||
4. [Feature Gap Analysis](#4-feature-gap-analysis)
|
4. [Feature Gap Analysis](#4-feature-gap-analysis)
|
||||||
5. [Data Connect & Development Strategy](#5-data-connect--development-strategy)
|
5. [Data Connect & Development Strategy](#5-data-connect--development-strategy)
|
||||||
6. [Definition of Done (DoD)](#6-definition-of-done-dod)
|
6. [Release Process & Automation](#6-release-process--automation)
|
||||||
|
7. [Definition of Done (DoD)](#7-definition-of-done-dod)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -160,27 +162,54 @@ graph TB
|
|||||||
- Event-driven architecture for async operations
|
- Event-driven architecture for async operations
|
||||||
- Clean separation from data layer
|
- Clean separation from data layer
|
||||||
|
|
||||||
3. **PostgreSQL Retention:**
|
3. **Core API Services:**
|
||||||
|
- Document verification service
|
||||||
|
- File upload service with signed URLs
|
||||||
|
- LLM service for AI features (RAPID orders)
|
||||||
|
- Integrated via ApiService (Dio-based)
|
||||||
|
|
||||||
|
4. **PostgreSQL Retention:**
|
||||||
- Full data ownership and portability
|
- Full data ownership and portability
|
||||||
- Complex queries and reporting capabilities
|
- Complex queries and reporting capabilities
|
||||||
- Industry-standard for enterprise requirements
|
- Industry-standard for enterprise requirements
|
||||||
|
|
||||||
4. **Monorepo Structure:**
|
5. **Monorepo Structure:**
|
||||||
```
|
```
|
||||||
krow-workforce-web/
|
krow-workforce-web/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── web-dashboard/ # Vite + React
|
│ ├── web-dashboard/ # Vite + React
|
||||||
│ ├── mobile/
|
│ ├── mobile/
|
||||||
| | ├── apps/
|
| | ├── apps/
|
||||||
| | │ ├── client/ # Flutter (business app)
|
| | │ ├── client/ # Flutter (business app) - v0.0.1-m4
|
||||||
| | │ └── staff/ # Flutter (staff app)
|
| | │ └── staff/ # Flutter (staff app) - v0.0.1-m4
|
||||||
|
| | └── packages/
|
||||||
|
| | ├── features/
|
||||||
|
| | │ ├── client/ # Client app features
|
||||||
|
| | │ └── staff/ # Staff app features
|
||||||
|
| | │ └── profile_sections/ # Modular profile (M4)
|
||||||
|
| | │ ├── onboarding/ # Profile info, experience, emergency
|
||||||
|
| | │ ├── compliance/ # Documents, certificates, attire
|
||||||
|
| | │ ├── finances/ # Bank, tax forms, timecard
|
||||||
|
| | │ └── support/ # FAQs, privacy & security
|
||||||
|
| | ├── core/ # Cross-cutting concerns
|
||||||
|
| | ├── data_connect/ # Backend integration
|
||||||
|
| | ├── domain/ # Entities & failures
|
||||||
|
| | ├── design_system/ # UI components & theme
|
||||||
|
| | └── core_localization/ # i18n
|
||||||
├── backend/
|
├── backend/
|
||||||
│ ├── dataconnect/ # Firebase Data Connect schemas
|
│ ├── dataconnect/ # Firebase Data Connect schemas
|
||||||
│ └── functions/ # Cloud Functions
|
│ ├── cloud-functions/ # Firebase Cloud Functions
|
||||||
|
│ ├── command-api/ # Command API service
|
||||||
|
│ └── core-api/ # Core API (verification, upload, LLM)
|
||||||
├── firebase/ # Firebase config
|
├── firebase/ # Firebase config
|
||||||
├── internal/
|
├── internal/
|
||||||
│ └── launchpad/ # Internal tools & prototypes
|
│ └── launchpad/ # Internal tools & prototypes
|
||||||
|
├── .github/
|
||||||
|
│ ├── workflows/ # GitHub Actions (release automation)
|
||||||
|
│ └── scripts/ # Release helper scripts
|
||||||
└── docs/ # Documentation
|
└── docs/ # Documentation
|
||||||
|
├── MOBILE/ # Mobile dev docs
|
||||||
|
└── RELEASE/ # Release docs
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -634,7 +663,92 @@ These **must be ported** from legacy:
|
|||||||
11. **Push Notifications** - FCM integration
|
11. **Push Notifications** - FCM integration
|
||||||
12. **Geofencing** - Location-based clock validation
|
12. **Geofencing** - Location-based clock validation
|
||||||
|
|
||||||
### 4.6 Migration Strategy
|
### 4.6 M4 Milestone Completion Status (Released: March 5, 2026)
|
||||||
|
|
||||||
|
**Version:** v0.0.1-m4 (both staff and client apps)
|
||||||
|
|
||||||
|
#### Staff App M4 Achievements
|
||||||
|
|
||||||
|
**Profile Management - 13 Subsections (✅ Complete):**
|
||||||
|
|
||||||
|
Via modular `profile_sections/` architecture:
|
||||||
|
|
||||||
|
**Onboarding:**
|
||||||
|
- ✅ Personal Info (name, email, phone, address, SSN)
|
||||||
|
- ✅ Experience (work history, skills, preferences)
|
||||||
|
- ✅ Emergency Contacts (safety contacts with relationship)
|
||||||
|
|
||||||
|
**Compliance:**
|
||||||
|
- ✅ Documents (ID verification, uploads)
|
||||||
|
- ✅ Certificates (certifications with expiry tracking)
|
||||||
|
- ✅ Attire (uniform verification with camera/gallery)
|
||||||
|
|
||||||
|
**Finances:**
|
||||||
|
- ✅ Bank Account (payout setup with validation)
|
||||||
|
- ✅ Tax Forms (I-9, W-4 forms)
|
||||||
|
- ✅ Time Card (hours log with history)
|
||||||
|
|
||||||
|
**Support:**
|
||||||
|
- ✅ FAQs (help articles)
|
||||||
|
- ✅ Privacy & Security (settings, account management)
|
||||||
|
|
||||||
|
**Profile Features:**
|
||||||
|
- ✅ Benefits Overview (perks information)
|
||||||
|
- ✅ Profile completion tracking
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
- ✅ Modular feature structure (`profile_sections/onboarding`, `/compliance`, `/finances`, `/support`)
|
||||||
|
- ✅ Flutter Modular dependency injection
|
||||||
|
- ✅ Data Connect integration with type-safe SDKs
|
||||||
|
- ✅ Shared design system components
|
||||||
|
|
||||||
|
#### M4 Core Features
|
||||||
|
|
||||||
|
**Authentication:**
|
||||||
|
- ✅ Firebase phone auth (OTP flow)
|
||||||
|
- ✅ Email/password authentication
|
||||||
|
- ✅ Profile setup wizard
|
||||||
|
|
||||||
|
**Session Management:**
|
||||||
|
- ✅ Clock in/out flow (prototype UI)
|
||||||
|
- ✅ Shift viewing and management
|
||||||
|
- ✅ Session status tracking
|
||||||
|
|
||||||
|
**Core API Integration:**
|
||||||
|
- ✅ Document verification service
|
||||||
|
- ✅ File upload service with signed URLs
|
||||||
|
- ✅ ApiService (Dio-based) with error handling
|
||||||
|
|
||||||
|
**Release Automation:**
|
||||||
|
- ✅ GitHub Actions workflows (product-release, hotfix-branch-creation)
|
||||||
|
- ✅ APK signing with 24 GitHub Secrets
|
||||||
|
- ✅ Automated changelog generation
|
||||||
|
- ✅ Git tag automation
|
||||||
|
- ✅ 8 helper scripts for release management
|
||||||
|
|
||||||
|
#### Client App M4 Status
|
||||||
|
|
||||||
|
**Completion:** 89% of 9 major feature categories
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- ✅ Authentication (email/password)
|
||||||
|
- ✅ Order management (4 types: one-time, rapid, recurring, permanent)
|
||||||
|
- ✅ Coverage dashboard
|
||||||
|
- ✅ Reports (daily ops, spend, forecast, performance, no-show)
|
||||||
|
- ✅ Hub management
|
||||||
|
- ✅ Settings and preferences
|
||||||
|
|
||||||
|
#### Documentation M4
|
||||||
|
|
||||||
|
- ✅ Comprehensive release guide (`docs/RELEASE/mobile-releases.md` - 900+ lines)
|
||||||
|
- ✅ Mobile development documentation (6 files in `docs/MOBILE/`)
|
||||||
|
- ✅ Release process quick reference
|
||||||
|
- ✅ GitHub Actions workflow documentation
|
||||||
|
- ✅ APK signing setup guide
|
||||||
|
- ✅ Architecture principles and patterns
|
||||||
|
- ✅ Use case completion audit
|
||||||
|
|
||||||
|
### 4.7 Migration Strategy
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
@@ -665,9 +779,9 @@ These **must be ported** from legacy:
|
|||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.7 Mobile Prototype Analysis (Source Code)
|
### 4.8 Mobile Prototype Analysis (Source Code)
|
||||||
|
|
||||||
#### 4.7.1 Mobile Client App (Flutter)
|
#### 4.8.1 Mobile Client App (Flutter)
|
||||||
|
|
||||||
**Location:** `internal/launchpad/prototypes-src/mobile/apps/client/`
|
**Location:** `internal/launchpad/prototypes-src/mobile/apps/client/`
|
||||||
|
|
||||||
@@ -706,7 +820,7 @@ These **must be ported** from legacy:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 4.7.2 Mobile Staff App (Flutter)
|
#### 4.8.2 Mobile Staff App (Flutter)
|
||||||
|
|
||||||
**Location:** `internal/launchpad/prototypes-src/mobile/apps/staff/`
|
**Location:** `internal/launchpad/prototypes-src/mobile/apps/staff/`
|
||||||
|
|
||||||
@@ -776,7 +890,7 @@ These **must be ported** from legacy:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.8 Complete Feature Comparison (All 3 Apps)
|
### 4.9 Complete Feature Comparison (All 3 Apps)
|
||||||
|
|
||||||
| Feature | Legacy Mobile | Web Prototype | Mobile Prototype |
|
| Feature | Legacy Mobile | Web Prototype | Mobile Prototype |
|
||||||
|---------|--------------|---------------|------------------|
|
|---------|--------------|---------------|------------------|
|
||||||
@@ -802,7 +916,7 @@ These **must be ported** from legacy:
|
|||||||
| **Penalty System** | ✅ | ❌ | ❌ |
|
| **Penalty System** | ✅ | ❌ | ❌ |
|
||||||
| **Staff Rating** | ✅ | ❌ | ❌ |
|
| **Staff Rating** | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
### 4.9 Source Code Locations
|
### 4.10 Source Code Locations
|
||||||
|
|
||||||
| Component | Location | Tech |
|
| Component | Location | Tech |
|
||||||
|-----------|----------|------|
|
|-----------|----------|------|
|
||||||
@@ -926,9 +1040,133 @@ Based on minimal dependencies:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Definition of Done (DoD)
|
## 6. Release Process & Automation
|
||||||
|
|
||||||
### 6.1 Feature-Level DoD (MVP Phase)
|
### 6.1 Versioning Strategy
|
||||||
|
|
||||||
|
**Mobile Apps:**
|
||||||
|
- Format: `v{major}.{minor}.{patch}-{milestone}`
|
||||||
|
- Example: `v0.0.1-m4` (current release)
|
||||||
|
- Milestones: m1, m2, m3, m4, etc.
|
||||||
|
- Auto-extracted from `apps/mobile/apps/{staff|client}/pubspec.yaml`
|
||||||
|
|
||||||
|
**Git Tags:**
|
||||||
|
- Format: `{app-name}/{version}`
|
||||||
|
- Examples: `staff/v0.0.1-m4`, `client/v0.0.1-m4`
|
||||||
|
|
||||||
|
### 6.2 GitHub Actions Workflows
|
||||||
|
|
||||||
|
**1. Product Release (`.github/workflows/product-release.yml`)**
|
||||||
|
- **Trigger:** Manual dispatch via GitHub UI
|
||||||
|
- **Purpose:** Automated production releases with APK signing
|
||||||
|
- **Process:**
|
||||||
|
1. Extracts version from `pubspec.yaml`
|
||||||
|
2. Builds signed APKs for both apps
|
||||||
|
3. Creates GitHub release with changelog
|
||||||
|
4. Tags release (e.g., `staff/v0.0.1-m4`)
|
||||||
|
5. Uploads APKs as release assets
|
||||||
|
- **Secrets Required:** 24 GitHub Secrets for APK signing (see `docs/RELEASE/mobile-releases.md`)
|
||||||
|
|
||||||
|
**2. Hotfix Branch Creation (`.github/workflows/hotfix-branch-creation.yml`)**
|
||||||
|
- **Trigger:** Manual dispatch with version input
|
||||||
|
- **Purpose:** Emergency fixes for production issues
|
||||||
|
- **Process:**
|
||||||
|
1. Creates `hotfix/{version}` branch from latest tag
|
||||||
|
2. Opens PR back to `dev` branch
|
||||||
|
3. Auto-updates PR description with hotfix checklist
|
||||||
|
|
||||||
|
**3. Helper Scripts (`.github/scripts/`)**
|
||||||
|
- `extract-version.sh` - Extracts version from pubspec.yaml
|
||||||
|
- `generate-changelog.sh` - Generates release notes from CHANGELOG
|
||||||
|
- `create-release.sh` - Creates GitHub release
|
||||||
|
- `upload-assets.sh` - Uploads APKs to release
|
||||||
|
- `build-apk.sh` - Builds signed APK
|
||||||
|
- `tag-release.sh` - Creates Git tags
|
||||||
|
- `hotfix-branch.sh` - Creates hotfix branches
|
||||||
|
- `update-pr.sh` - Updates PR descriptions
|
||||||
|
|
||||||
|
### 6.3 APK Signing Setup
|
||||||
|
|
||||||
|
**Required GitHub Secrets (per app):**
|
||||||
|
```
|
||||||
|
STAFF_UPLOAD_KEYSTORE_BASE64 # Base64-encoded keystore
|
||||||
|
STAFF_UPLOAD_STORE_PASSWORD # Keystore password
|
||||||
|
STAFF_UPLOAD_KEY_ALIAS # Key alias
|
||||||
|
STAFF_UPLOAD_KEY_PASSWORD # Key password
|
||||||
|
STAFF_KEYSTORE_PROPERTIES_BASE64 # Base64-encoded key.properties
|
||||||
|
|
||||||
|
CLIENT_UPLOAD_KEYSTORE_BASE64
|
||||||
|
CLIENT_UPLOAD_STORE_PASSWORD
|
||||||
|
CLIENT_UPLOAD_KEY_ALIAS
|
||||||
|
CLIENT_UPLOAD_KEY_PASSWORD
|
||||||
|
CLIENT_KEYSTORE_PROPERTIES_BASE64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 CHANGELOG Format
|
||||||
|
|
||||||
|
Each app maintains a separate CHANGELOG:
|
||||||
|
- `apps/mobile/apps/staff/CHANGELOG.md`
|
||||||
|
- `apps/mobile/apps/client/CHANGELOG.md`
|
||||||
|
|
||||||
|
**Format:**
|
||||||
|
```markdown
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New feature descriptions
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Modified feature descriptions
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bug fix descriptions
|
||||||
|
|
||||||
|
## [0.0.1-m4] - 2026-03-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Profile management with 13 subsections
|
||||||
|
- Documents & certificates management
|
||||||
|
- Benefits overview
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 Release Documentation
|
||||||
|
|
||||||
|
**Comprehensive Guide:**
|
||||||
|
- Location: `docs/RELEASE/mobile-releases.md`
|
||||||
|
- Length: 900+ lines
|
||||||
|
- Contents:
|
||||||
|
- Complete versioning strategy
|
||||||
|
- CHANGELOG format and examples
|
||||||
|
- GitHub Actions workflow details
|
||||||
|
- APK signing setup (with secret generation)
|
||||||
|
- Helper scripts reference
|
||||||
|
- Troubleshooting guide
|
||||||
|
|
||||||
|
**Quick Reference:**
|
||||||
|
- Location: `docs/MOBILE/05-release-process.md`
|
||||||
|
- Links to comprehensive guide and workflows
|
||||||
|
|
||||||
|
### 6.6 Local Release Commands (via Makefile)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build unsigned APKs locally
|
||||||
|
make build-apk-staff
|
||||||
|
make build-apk-client
|
||||||
|
|
||||||
|
# Run GitHub Actions locally (requires act)
|
||||||
|
make test-release-workflow
|
||||||
|
|
||||||
|
# Generate changelog entries
|
||||||
|
make changelog-staff
|
||||||
|
make changelog-client
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Definition of Done (DoD)
|
||||||
|
|
||||||
|
### 7.1 Feature-Level DoD (MVP Phase)
|
||||||
|
|
||||||
A feature is **DONE** when:
|
A feature is **DONE** when:
|
||||||
|
|
||||||
@@ -944,7 +1182,7 @@ A feature is **DONE** when:
|
|||||||
|
|
||||||
> **Note (MVP):** For rapid MVP delivery, we focus on manual/integration testing directly in applications rather than unit tests. Automated test coverage will be added post-MVP.
|
> **Note (MVP):** For rapid MVP delivery, we focus on manual/integration testing directly in applications rather than unit tests. Automated test coverage will be added post-MVP.
|
||||||
|
|
||||||
### 6.2 Sprint-Level DoD
|
### 7.2 Sprint-Level DoD
|
||||||
|
|
||||||
A sprint is **DONE** when:
|
A sprint is **DONE** when:
|
||||||
|
|
||||||
@@ -956,7 +1194,7 @@ A sprint is **DONE** when:
|
|||||||
- [ ] Demo-ready for stakeholders
|
- [ ] Demo-ready for stakeholders
|
||||||
- [ ] Documentation updated
|
- [ ] Documentation updated
|
||||||
|
|
||||||
### 6.3 Code Quality Standards
|
### 7.3 Code Quality Standards
|
||||||
|
|
||||||
| Aspect | Standard |
|
| Aspect | Standard |
|
||||||
|--------|----------|
|
|--------|----------|
|
||||||
@@ -967,7 +1205,7 @@ A sprint is **DONE** when:
|
|||||||
| **PRs** | Template completed, 1+ approval |
|
| **PRs** | Template completed, 1+ approval |
|
||||||
| **Testing (MVP)** | Manual testing in application |
|
| **Testing (MVP)** | Manual testing in application |
|
||||||
|
|
||||||
### 6.4 Commit Message Format
|
### 7.4 Commit Message Format
|
||||||
|
|
||||||
```
|
```
|
||||||
<type>(<scope>): <subject>
|
<type>(<scope>): <subject>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user