Add KROW mobile release and paper design conventions documentation

- Introduced SKILL.md for KROW mobile release process detailing versioning, CHANGELOG management, GitHub Actions workflows, APK signing, and hotfix procedures.
- Added SKILL.md for KROW paper design conventions covering design tokens, component patterns, screen structure, and naming rules to ensure visual consistency across design files.
This commit is contained in:
Achintha Isuru
2026-03-08 17:26:46 -04:00
parent 76cdeb4952
commit 2d1e3915c5
13 changed files with 6954 additions and 175 deletions

View File

@@ -0,0 +1,23 @@
{
"permissions": {
"allow": [
"Bash(grep:*)",
"mcp__paper__get_basic_info",
"mcp__paper__get_screenshot",
"mcp__paper__get_tree_summary",
"mcp__paper__update_styles",
"mcp__paper__set_text_content",
"mcp__paper__get_computed_styles",
"mcp__paper__finish_working_on_nodes",
"mcp__paper__get_font_family_info",
"mcp__paper__rename_nodes",
"mcp__paper__write_html",
"mcp__paper__get_children",
"mcp__paper__create_artboard",
"mcp__paper__delete_nodes",
"mcp__paper__get_jsx",
"mcp__paper__get_node_info",
"mcp__paper__duplicate_nodes"
]
}
}

View 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.addLazySingleton<StaffConnectorRepository>(
StaffConnectorRepositoryImpl.new,
);
i.addLazySingleton(
() => GetProfileCompletionUseCase(
repository: i.get<StaffConnectorRepository>(),
),
);
i.addLazySingleton(
() => 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.addLazySingleton<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.

View 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.

View 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 human readable doc comments for `dartdoc` for all 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.

View 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.

View File

@@ -0,0 +1,413 @@
---
name: krow-paper-design
description: KROW Paper design file conventions covering design tokens, component patterns, screen structure, and naming rules. Use this when creating or updating screens in the Paper design tool, auditing designs for token compliance, building new flows, or restructuring existing frames. Ensures visual consistency across all Paper design files for the KROW staff and client apps.
---
# KROW Paper Design Conventions
This skill defines the design token system, component patterns, screen structure conventions, and workflow rules established for the KROW Design Revamp Paper file. All design work in Paper must follow these conventions.
## When to Use This Skill
- Creating new screens or flows in Paper
- Updating existing frames to match the design system
- Auditing designs for token compliance
- Adding components (buttons, chips, inputs, badges, cards)
- Structuring shift detail pages, onboarding flows, or list screens
- Setting up navigation patterns (back buttons, bottom nav, CTAs)
- Reviewing Paper designs before handoff to development
## 1. Design Tokens
### Color Palette
| Token | Hex | Usage |
|-------|-----|-------|
| Primary | `#0A39DF` | CTAs, active states, links, selected chips, nav active icons, pay rates |
| Foreground | `#121826` | Headings, primary text, dark UI elements |
| Text Secondary | `#6A7382` | Labels, captions, inactive nav, section headers, placeholder text, back chevrons |
| Secondary BG | `#F1F3F5` | Subtle backgrounds, dividers, map placeholders |
| Border | `#D1D5DB` | Card borders, unselected chip borders, outline button borders |
| Input Border | `#E2E8F0` | Text input borders (lighter than general border) |
| Destructive | `#F04444` | Error states, destructive actions (e.g., Request Swap) |
| Background | `#FAFBFC` | Page/artboard background |
| Card BG | `#FFFFFF` | Card surfaces, input backgrounds |
| Success | `#059669` | Active status dot, checkmark icons, requirement met |
| Warning Amber | `#D97706` | Urgent/Pending badge text |
### Semantic Badge Colors
| Badge | Background | Text Color |
|-------|-----------|------------|
| Active | `#ECFDF5` | `#059669` |
| Confirmed | `#EBF0FF` | `#0A39DF` |
| Pending | `#FEF9EE` | `#D97706` |
| Urgent | `#FEF9EE` | `#D97706` |
| One-Time | `#ECFDF5` | `#059669` |
| Recurring | `#EBF0FF` | `#0A39DF` (use `#EFF6FF` bg on detail pages) |
### Typography
| Style | Font | Size | Weight | Line Height | Usage |
|-------|------|------|--------|-------------|-------|
| Display | Inter Tight | 28px | 700 | 34px | Page titles (Find Shifts, My Shifts) |
| H1 | Inter Tight | 24px | 700 | 30px | Detail page titles (venue names) |
| H2 | Inter Tight | 20px | 700 | 26px | Section headings |
| H3 | Inter Tight | 18px | 700 | 22px | Card titles, schedule values |
| Body Large | Manrope | 16px | 600 | 20px | Button text, CTA labels |
| Body Default | Manrope | 14px | 400-500 | 18px | Body text, descriptions |
| Body Small | Manrope | 13px | 400-500 | 16px | Card metadata, time/pay info |
| Caption | Manrope | 12px | 500-600 | 16px | Small chip text, tab labels |
| Section Label | Manrope | 11px | 700 | 14px | Uppercase section headers (letter-spacing: 0.06em) |
| Badge Text | Manrope | 11px | 600-700 | 14px | Status badge labels (letter-spacing: 0.04em) |
| Nav Label | Manrope | 10px | 600 | 12px | Bottom nav labels |
### Spacing
| Token | Value | Usage |
|-------|-------|-------|
| Page padding | 24px | Horizontal padding from screen edge |
| Section gap | 16-24px | Between major content sections |
| Group gap | 8-12px | Within a section (e.g., label to input) |
| Element gap | 4px | Tight spacing (e.g., subtitle under title) |
| Bottom safe area | 40px | Padding below last element / CTA |
### Border Radii
| Token | Value | Usage |
|-------|-------|-------|
| sm | 8px | Small chips, badges, status pills, map placeholder |
| md | 12px | Cards, inputs, location cards, contact cards, search fields |
| lg | 14px | Buttons, CTA containers, shift cards (Find Shifts) |
| xl | 24px | Not commonly used |
| pill | 999px | Progress bar segments only |
## 2. Component Patterns
### Buttons
**Primary CTA:**
- Background: `#0A39DF`, radius: 14px, height: 52px
- Text: Manrope 16px/600, color: `#FFFFFF`
- Padding: 16px vertical, 16px horizontal
**Secondary/Outline Button:**
- Background: `#FFFFFF`, border: 1.5px `#D1D5DB`, radius: 14px, height: 52px
- Text: Manrope 16px/600, color: `#121826`
**Destructive Outline Button:**
- Background: `#FFFFFF`, border: 1.5px `#F04444`, radius: 14px
- Text: Manrope 14px/600, color: `#F04444`
**Back Icon Button (Bottom CTA):**
- 52x52px square, border: 1.5px `#D1D5DB`, radius: 14px, background: `#FFFFFF`
- Contains chevron-left SVG (20x20, viewBox 0 0 24 24, stroke `#121826`, strokeWidth 2)
- Path: `M15 18L9 12L15 6`
### Chips
**Default (Large) - for role/skill selection:**
- Selected: bg `#EFF6FF`, border 1.5px `#0A39DF`, radius 10px, padding 12px/16px
- Checkmark icon (14x14, stroke `#0A39DF`), text Manrope 14px/600 `#0A39DF`
- Unselected: bg `#FFFFFF`, border 1.5px `#6A7382`, radius 10px, padding 12px/16px
- Text Manrope 14px/500 `#6A7382`
**Small - for tabs, filters:**
- Selected: bg `#EFF6FF`, border 1.5px `#0A39DF`, radius 8px, padding 6px/12px
- Checkmark icon (12x12), text Manrope 12px/600 `#0A39DF`
- Unselected: bg `#FFFFFF`, border 1.5px `#D1D5DB`, radius 8px, padding 6px/12px
- Text Manrope 12px/500 `#6A7382`
- Active (filled): bg `#0A39DF`, radius 8px, padding 6px/12px
- Text Manrope 12px/600 `#FFFFFF`
- Dark (filters button): bg `#121826`, radius 8px, padding 6px/12px
- Text Manrope 12px/600 `#FFFFFF`, with leading icon
**Status Badges:**
- Radius: 8px, padding: 4px/8px
- Text: Manrope 11px/600-700, uppercase, letter-spacing 0.04em
- Colors follow semantic badge table above
### Text Inputs
- Border: 1.5px `#E2E8F0`, radius: 12px, padding: 12px/14px
- Background: `#FFFFFF`
- Placeholder: Manrope 14px/400, color `#6A7382`
- Filled: Manrope 14px/500, color `#121826`
- Label above: Manrope 14px/500, color `#121826`
- Focused: border color `#0A39DF`, border-width 2px
- Error: border color `#F04444`, helper text `#F04444`
### Cards (Shift List Items)
- Background: `#FFFFFF`, border: 1px `#D1D5DB`, radius: 12-14px
- Padding: 16px
- Content: venue name (Manrope 15px/600 `#121826`), subtitle (Manrope 13px/400 `#6A7382`)
- Metadata row: icon (14px, `#6A7382`) + text (Manrope 13px/500 `#6A7382`)
- Pay rate: Inter Tight 18px/700 `#0A39DF`
### Schedule/Pay Info Cards
- Two-column layout with 12px gap
- Background: `#FFFFFF`, border: 1px `#D1D5DB`, radius: 12px, padding: 16px
- Label: Manrope 11px/500-700 uppercase `#6A7382` (letter-spacing 0.05em)
- Value: Inter Tight 18px/700 `#121826` (schedule) or `#121826` (pay)
- Sub-text: Manrope 13px/400 `#6A7382`
### Contact/Info Rows
- Container: radius 12px, border 1px `#D1D5DB`, background `#FFFFFF`, overflow clip
- Row: padding 13px/16px, gap 10px, border-bottom 1px `#F1F3F5` (except last)
- Icon: 16px, stroke `#6A7382`
- Label: Manrope 13px/500 `#6A7382`, width 72px fixed
- Value: Manrope 13px/500 `#121826` (or `#0A39DF` for phone/links)
### Section Headers
- Text: Manrope 11px/700, uppercase, letter-spacing 0.06em, color `#6A7382`
- Gap to content below: 10px
## 3. Screen Structure
### Artboard Setup
- Width: 390px (iPhone standard)
- Height: 844px (default), or `fit-content` for scrollable detail pages
- Background: `#FAFBFC`
- Flex column layout, overflow: clip
### Frame Naming Convention
```
<app>-<section>-<screen_number>-<screen_name>
```
Examples:
- `staff-1-1-splash`
- `staff-2-3-personal-information`
- `staff-4-1-my-shifts`
- `staff-5-2-shift-details`
- `shift-5-3-confirmation`
Section headers use: `<number> - <Section Name>` (e.g., `4 - My Shifts`)
### Status Bar
- Height: 44px, full width (390px)
- Left: "9:41" text (system font)
- Right: Signal, WiFi, Battery SVG icons (68px wide)
### Header Back Button
- Placed below status bar in a combined "Status Bar + Back" frame (390x72px)
- Chevron SVG: 20x20, viewBox 0 0 24 24, stroke `#6A7382`, strokeWidth 2
- Path: `M15 18L9 12L15 6`
- Back button frame: 390x28px, padding-left: 24px
### Progress Bar (Onboarding)
- Container: 342px wide (24px margins), 3px height segments
- Segments: pill radius (999px), gap between
- Filled: `#0A39DF`, Unfilled: `#F1F3F5`
### Bottom CTA Convention
- Pinned to bottom using `marginTop: auto` on the CTA container
- Layout: flex row, gap 12px, padding 0 24px
- Back button: 52x52px icon-only button with chevron-left (stroke `#121826`)
- Primary CTA: flex 1, height 52px, radius 14px, bg `#0A39DF`
- Bottom safe padding: 40px (on artboard paddingBottom)
### Bottom Navigation Bar
- Full width, padding: 10px top, 28px bottom
- Border-top: 1px `#F1F3F5`, background: `#FFFFFF`
- 5 items: Home, Shifts, Find, Payments, Profile
- Active: icon stroke `#0A39DF`, label Manrope 10px/600 `#0A39DF`
- Inactive: icon stroke `#6A7382`, label Manrope 10px/600 `#6A7382`
- Active icon may have light fill (e.g., `#EBF0FF` on calendar/search)
## 4. Screen Templates
### List Screen (My Shifts, Find Shifts)
```
Artboard (390x844, bg #FAFBFC)
Status Bar (390x44)
Header Section
Page Title (Display: Inter Tight 28px/700)
Tab/Filter Chips (Small chip variant)
Content
Date Header (Section label style, uppercase)
Shift Cards (12px radius, 1px border #D1D5DB)
Bottom Nav Bar
```
### Detail Screen (Shift Details)
```
Artboard (390x fit-content, bg #FAFBFC)
Status Bar (390x44)
Header Bar (Back chevron + "Shift Details" title + share icon)
Badges Row (status chips)
Role Title (H1) + Venue (with avatar)
Schedule/Pay Cards (two-column)
Job Description (section label + body text)
Location (card with map + address)
Requirements (section label + checkmark list)
Shift Contact (section label + contact card with rows)
[Optional] Note from Manager (warm bg card)
Bottom CTA (pinned)
```
### Onboarding Screen
```
Artboard (390x844, bg #FAFBFC, justify: flex-start, paddingBottom: 40px)
Status Bar + Back (390x72)
Progress Bar (342px, 3px segments)
Step Counter ("Step X of Y" - Body Small)
Page Title (H1: Inter Tight 24px/700)
[Optional] Subtitle (Body Default)
Form Content (inputs, chips, sliders)
Bottom CTA (marginTop: auto - back icon + Continue)
```
### Confirmation Screen
```
Artboard (390x844, bg #FAFBFC)
Status Bar
Centered Content
Success Icon (green circle + checkmark)
Title (Display: Inter Tight 26px/700, centered)
Subtitle (Body Default, centered, #6A7382)
Details Card (border #D1D5DB, rows with label/value pairs)
Bottom CTAs (primary + outline)
```
## 5. Workflow Rules
### Write Incrementally
Each `write_html` call should produce ONE visual group:
- A header, a card, a single list row, a button bar, a section
- Never batch an entire screen in one call
### Review Checkpoints
After every 2-3 modifications, take a screenshot and evaluate:
- **Spacing**: Uneven gaps, cramped groups
- **Typography**: Hierarchy, readability, correct font/weight
- **Contrast**: Text legibility, element distinction
- **Alignment**: Vertical lanes, horizontal alignment
- **Clipping**: Content cut off at edges
- **Token compliance**: All values match design system tokens
### Color Audit Process
When updating frames to match the design system:
1. Get computed styles for all text, background, border elements
2. Map old colors to design system tokens:
- Dark navy (`#0F4C81`, `#1A3A5C`) -> Primary `#0A39DF`
- Near-black (`#111827`, `#0F172A`) -> Foreground `#121826`
- Gray variants (`#94A3B8`, `#64748B`, `#475569`) -> Text Secondary `#6A7382`
- Green accents (`#20B486`) -> Primary `#0A39DF` (for pay) or `#059669` (for status)
3. Batch update using `update_styles` with multiple nodeIds per style change
4. Verify with screenshots
### Structural Consistency
When creating matching screens (e.g., two shift detail views):
- Use identical section ordering
- Match section header styles (11px/700 uppercase `#6A7382`)
- Use same card/row component patterns
- Maintain consistent padding and gap values
## 6. SVG Icon Patterns
### Chevron Left (Back)
```html
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Map Pin
```html
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="10" r="3" stroke="#6A7382" stroke-width="2"/>
</svg>
```
### User (Supervisor)
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="7" r="4" stroke="#6A7382" stroke-width="2"/>
</svg>
```
### Phone
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Checkmark (Requirement Met)
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" stroke="#059669" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 4L12 14.01l-3-3" stroke="#059669" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Chip Checkmark
```html
<!-- Large chip (14x14) -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2.5 7L5.5 10L11.5 4" stroke="#0A39DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<!-- Small chip (12x12) -->
<svg width="12" height="12" viewBox="0 0 14 14" fill="none">
<path d="M2.5 7L5.5 10L11.5 4" stroke="#0A39DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
## 7. Anti-Patterns
### Colors
- Never use `#0F4C81`, `#1A3A5C` (old navy) - use `#0A39DF` (Primary)
- Never use `#111827`, `#0F172A` - use `#121826` (Foreground)
- Never use `#94A3B8`, `#64748B`, `#475569` - use `#6A7382` (Text Secondary)
- Never use `#20B486` for pay rates - use `#0A39DF` (Primary)
- Never use `#E2E8F0` for card borders - use `#D1D5DB` (Border)
### Components
- Never use pill radius (999px) for chips or badges - use 8px or 10px
- Never use gradient backgrounds on buttons
- Never mix font families within a role (headings = Inter Tight, body = Manrope)
- Never place back buttons at the bottom of frames - always after status bar
- Never hardcode CTA position - use `marginTop: auto` for bottom pinning
### Structure
- Never batch an entire screen in one `write_html` call
- Never skip review checkpoints after 2-3 modifications
- Never create frames without following the naming convention
- Never use `justifyContent: space-between` on artboards with many direct children - use `marginTop: auto` on the CTA instead
## Summary
**The design file is the source of truth for visual direction.** Every element must use the established tokens:
1. **Colors**: 7 core tokens + semantic badge colors
2. **Typography**: Inter Tight (headings) + Manrope (body), defined scale
3. **Spacing**: 24px page padding, 16-24px section gaps, 40px bottom safe area
4. **Radii**: 8px (chips/badges), 12px (cards/inputs), 14px (buttons/CTAs)
5. **Components**: Buttons, chips (large/small), inputs, cards, badges, nav bars
6. **Structure**: Status bar > Back > Content > Bottom CTA (pinned)
7. **Naming**: `<app>-<section>-<number>-<name>`
When in doubt, screenshot an existing screen and match its patterns exactly.

View File

@@ -0,0 +1,23 @@
{
"permissions": {
"allow": [
"Bash(grep:*)",
"mcp__paper__get_basic_info",
"mcp__paper__get_screenshot",
"mcp__paper__get_tree_summary",
"mcp__paper__update_styles",
"mcp__paper__set_text_content",
"mcp__paper__get_computed_styles",
"mcp__paper__finish_working_on_nodes",
"mcp__paper__get_font_family_info",
"mcp__paper__rename_nodes",
"mcp__paper__write_html",
"mcp__paper__get_children",
"mcp__paper__create_artboard",
"mcp__paper__delete_nodes",
"mcp__paper__get_jsx",
"mcp__paper__get_node_info",
"mcp__paper__duplicate_nodes"
]
}
}

View 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.addLazySingleton<StaffConnectorRepository>(
StaffConnectorRepositoryImpl.new,
);
i.addLazySingleton(
() => GetProfileCompletionUseCase(
repository: i.get<StaffConnectorRepository>(),
),
);
i.addLazySingleton(
() => 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.addLazySingleton<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.

View 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.

View 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 human readable doc comments for `dartdoc` for all 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.

View 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.

View File

@@ -0,0 +1,413 @@
---
name: krow-paper-design
description: KROW Paper design file conventions covering design tokens, component patterns, screen structure, and naming rules. Use this when creating or updating screens in the Paper design tool, auditing designs for token compliance, building new flows, or restructuring existing frames. Ensures visual consistency across all Paper design files for the KROW staff and client apps.
---
# KROW Paper Design Conventions
This skill defines the design token system, component patterns, screen structure conventions, and workflow rules established for the KROW Design Revamp Paper file. All design work in Paper must follow these conventions.
## When to Use This Skill
- Creating new screens or flows in Paper
- Updating existing frames to match the design system
- Auditing designs for token compliance
- Adding components (buttons, chips, inputs, badges, cards)
- Structuring shift detail pages, onboarding flows, or list screens
- Setting up navigation patterns (back buttons, bottom nav, CTAs)
- Reviewing Paper designs before handoff to development
## 1. Design Tokens
### Color Palette
| Token | Hex | Usage |
|-------|-----|-------|
| Primary | `#0A39DF` | CTAs, active states, links, selected chips, nav active icons, pay rates |
| Foreground | `#121826` | Headings, primary text, dark UI elements |
| Text Secondary | `#6A7382` | Labels, captions, inactive nav, section headers, placeholder text, back chevrons |
| Secondary BG | `#F1F3F5` | Subtle backgrounds, dividers, map placeholders |
| Border | `#D1D5DB` | Card borders, unselected chip borders, outline button borders |
| Input Border | `#E2E8F0` | Text input borders (lighter than general border) |
| Destructive | `#F04444` | Error states, destructive actions (e.g., Request Swap) |
| Background | `#FAFBFC` | Page/artboard background |
| Card BG | `#FFFFFF` | Card surfaces, input backgrounds |
| Success | `#059669` | Active status dot, checkmark icons, requirement met |
| Warning Amber | `#D97706` | Urgent/Pending badge text |
### Semantic Badge Colors
| Badge | Background | Text Color |
|-------|-----------|------------|
| Active | `#ECFDF5` | `#059669` |
| Confirmed | `#EBF0FF` | `#0A39DF` |
| Pending | `#FEF9EE` | `#D97706` |
| Urgent | `#FEF9EE` | `#D97706` |
| One-Time | `#ECFDF5` | `#059669` |
| Recurring | `#EBF0FF` | `#0A39DF` (use `#EFF6FF` bg on detail pages) |
### Typography
| Style | Font | Size | Weight | Line Height | Usage |
|-------|------|------|--------|-------------|-------|
| Display | Inter Tight | 28px | 700 | 34px | Page titles (Find Shifts, My Shifts) |
| H1 | Inter Tight | 24px | 700 | 30px | Detail page titles (venue names) |
| H2 | Inter Tight | 20px | 700 | 26px | Section headings |
| H3 | Inter Tight | 18px | 700 | 22px | Card titles, schedule values |
| Body Large | Manrope | 16px | 600 | 20px | Button text, CTA labels |
| Body Default | Manrope | 14px | 400-500 | 18px | Body text, descriptions |
| Body Small | Manrope | 13px | 400-500 | 16px | Card metadata, time/pay info |
| Caption | Manrope | 12px | 500-600 | 16px | Small chip text, tab labels |
| Section Label | Manrope | 11px | 700 | 14px | Uppercase section headers (letter-spacing: 0.06em) |
| Badge Text | Manrope | 11px | 600-700 | 14px | Status badge labels (letter-spacing: 0.04em) |
| Nav Label | Manrope | 10px | 600 | 12px | Bottom nav labels |
### Spacing
| Token | Value | Usage |
|-------|-------|-------|
| Page padding | 24px | Horizontal padding from screen edge |
| Section gap | 16-24px | Between major content sections |
| Group gap | 8-12px | Within a section (e.g., label to input) |
| Element gap | 4px | Tight spacing (e.g., subtitle under title) |
| Bottom safe area | 40px | Padding below last element / CTA |
### Border Radii
| Token | Value | Usage |
|-------|-------|-------|
| sm | 8px | Small chips, badges, status pills, map placeholder |
| md | 12px | Cards, inputs, location cards, contact cards, search fields |
| lg | 14px | Buttons, CTA containers, shift cards (Find Shifts) |
| xl | 24px | Not commonly used |
| pill | 999px | Progress bar segments only |
## 2. Component Patterns
### Buttons
**Primary CTA:**
- Background: `#0A39DF`, radius: 14px, height: 52px
- Text: Manrope 16px/600, color: `#FFFFFF`
- Padding: 16px vertical, 16px horizontal
**Secondary/Outline Button:**
- Background: `#FFFFFF`, border: 1.5px `#D1D5DB`, radius: 14px, height: 52px
- Text: Manrope 16px/600, color: `#121826`
**Destructive Outline Button:**
- Background: `#FFFFFF`, border: 1.5px `#F04444`, radius: 14px
- Text: Manrope 14px/600, color: `#F04444`
**Back Icon Button (Bottom CTA):**
- 52x52px square, border: 1.5px `#D1D5DB`, radius: 14px, background: `#FFFFFF`
- Contains chevron-left SVG (20x20, viewBox 0 0 24 24, stroke `#121826`, strokeWidth 2)
- Path: `M15 18L9 12L15 6`
### Chips
**Default (Large) - for role/skill selection:**
- Selected: bg `#EFF6FF`, border 1.5px `#0A39DF`, radius 10px, padding 12px/16px
- Checkmark icon (14x14, stroke `#0A39DF`), text Manrope 14px/600 `#0A39DF`
- Unselected: bg `#FFFFFF`, border 1.5px `#6A7382`, radius 10px, padding 12px/16px
- Text Manrope 14px/500 `#6A7382`
**Small - for tabs, filters:**
- Selected: bg `#EFF6FF`, border 1.5px `#0A39DF`, radius 8px, padding 6px/12px
- Checkmark icon (12x12), text Manrope 12px/600 `#0A39DF`
- Unselected: bg `#FFFFFF`, border 1.5px `#D1D5DB`, radius 8px, padding 6px/12px
- Text Manrope 12px/500 `#6A7382`
- Active (filled): bg `#0A39DF`, radius 8px, padding 6px/12px
- Text Manrope 12px/600 `#FFFFFF`
- Dark (filters button): bg `#121826`, radius 8px, padding 6px/12px
- Text Manrope 12px/600 `#FFFFFF`, with leading icon
**Status Badges:**
- Radius: 8px, padding: 4px/8px
- Text: Manrope 11px/600-700, uppercase, letter-spacing 0.04em
- Colors follow semantic badge table above
### Text Inputs
- Border: 1.5px `#E2E8F0`, radius: 12px, padding: 12px/14px
- Background: `#FFFFFF`
- Placeholder: Manrope 14px/400, color `#6A7382`
- Filled: Manrope 14px/500, color `#121826`
- Label above: Manrope 14px/500, color `#121826`
- Focused: border color `#0A39DF`, border-width 2px
- Error: border color `#F04444`, helper text `#F04444`
### Cards (Shift List Items)
- Background: `#FFFFFF`, border: 1px `#D1D5DB`, radius: 12-14px
- Padding: 16px
- Content: venue name (Manrope 15px/600 `#121826`), subtitle (Manrope 13px/400 `#6A7382`)
- Metadata row: icon (14px, `#6A7382`) + text (Manrope 13px/500 `#6A7382`)
- Pay rate: Inter Tight 18px/700 `#0A39DF`
### Schedule/Pay Info Cards
- Two-column layout with 12px gap
- Background: `#FFFFFF`, border: 1px `#D1D5DB`, radius: 12px, padding: 16px
- Label: Manrope 11px/500-700 uppercase `#6A7382` (letter-spacing 0.05em)
- Value: Inter Tight 18px/700 `#121826` (schedule) or `#121826` (pay)
- Sub-text: Manrope 13px/400 `#6A7382`
### Contact/Info Rows
- Container: radius 12px, border 1px `#D1D5DB`, background `#FFFFFF`, overflow clip
- Row: padding 13px/16px, gap 10px, border-bottom 1px `#F1F3F5` (except last)
- Icon: 16px, stroke `#6A7382`
- Label: Manrope 13px/500 `#6A7382`, width 72px fixed
- Value: Manrope 13px/500 `#121826` (or `#0A39DF` for phone/links)
### Section Headers
- Text: Manrope 11px/700, uppercase, letter-spacing 0.06em, color `#6A7382`
- Gap to content below: 10px
## 3. Screen Structure
### Artboard Setup
- Width: 390px (iPhone standard)
- Height: 844px (default), or `fit-content` for scrollable detail pages
- Background: `#FAFBFC`
- Flex column layout, overflow: clip
### Frame Naming Convention
```
<app>-<section>-<screen_number>-<screen_name>
```
Examples:
- `staff-1-1-splash`
- `staff-2-3-personal-information`
- `staff-4-1-my-shifts`
- `staff-5-2-shift-details`
- `shift-5-3-confirmation`
Section headers use: `<number> - <Section Name>` (e.g., `4 - My Shifts`)
### Status Bar
- Height: 44px, full width (390px)
- Left: "9:41" text (system font)
- Right: Signal, WiFi, Battery SVG icons (68px wide)
### Header Back Button
- Placed below status bar in a combined "Status Bar + Back" frame (390x72px)
- Chevron SVG: 20x20, viewBox 0 0 24 24, stroke `#6A7382`, strokeWidth 2
- Path: `M15 18L9 12L15 6`
- Back button frame: 390x28px, padding-left: 24px
### Progress Bar (Onboarding)
- Container: 342px wide (24px margins), 3px height segments
- Segments: pill radius (999px), gap between
- Filled: `#0A39DF`, Unfilled: `#F1F3F5`
### Bottom CTA Convention
- Pinned to bottom using `marginTop: auto` on the CTA container
- Layout: flex row, gap 12px, padding 0 24px
- Back button: 52x52px icon-only button with chevron-left (stroke `#121826`)
- Primary CTA: flex 1, height 52px, radius 14px, bg `#0A39DF`
- Bottom safe padding: 40px (on artboard paddingBottom)
### Bottom Navigation Bar
- Full width, padding: 10px top, 28px bottom
- Border-top: 1px `#F1F3F5`, background: `#FFFFFF`
- 5 items: Home, Shifts, Find, Payments, Profile
- Active: icon stroke `#0A39DF`, label Manrope 10px/600 `#0A39DF`
- Inactive: icon stroke `#6A7382`, label Manrope 10px/600 `#6A7382`
- Active icon may have light fill (e.g., `#EBF0FF` on calendar/search)
## 4. Screen Templates
### List Screen (My Shifts, Find Shifts)
```
Artboard (390x844, bg #FAFBFC)
Status Bar (390x44)
Header Section
Page Title (Display: Inter Tight 28px/700)
Tab/Filter Chips (Small chip variant)
Content
Date Header (Section label style, uppercase)
Shift Cards (12px radius, 1px border #D1D5DB)
Bottom Nav Bar
```
### Detail Screen (Shift Details)
```
Artboard (390x fit-content, bg #FAFBFC)
Status Bar (390x44)
Header Bar (Back chevron + "Shift Details" title + share icon)
Badges Row (status chips)
Role Title (H1) + Venue (with avatar)
Schedule/Pay Cards (two-column)
Job Description (section label + body text)
Location (card with map + address)
Requirements (section label + checkmark list)
Shift Contact (section label + contact card with rows)
[Optional] Note from Manager (warm bg card)
Bottom CTA (pinned)
```
### Onboarding Screen
```
Artboard (390x844, bg #FAFBFC, justify: flex-start, paddingBottom: 40px)
Status Bar + Back (390x72)
Progress Bar (342px, 3px segments)
Step Counter ("Step X of Y" - Body Small)
Page Title (H1: Inter Tight 24px/700)
[Optional] Subtitle (Body Default)
Form Content (inputs, chips, sliders)
Bottom CTA (marginTop: auto - back icon + Continue)
```
### Confirmation Screen
```
Artboard (390x844, bg #FAFBFC)
Status Bar
Centered Content
Success Icon (green circle + checkmark)
Title (Display: Inter Tight 26px/700, centered)
Subtitle (Body Default, centered, #6A7382)
Details Card (border #D1D5DB, rows with label/value pairs)
Bottom CTAs (primary + outline)
```
## 5. Workflow Rules
### Write Incrementally
Each `write_html` call should produce ONE visual group:
- A header, a card, a single list row, a button bar, a section
- Never batch an entire screen in one call
### Review Checkpoints
After every 2-3 modifications, take a screenshot and evaluate:
- **Spacing**: Uneven gaps, cramped groups
- **Typography**: Hierarchy, readability, correct font/weight
- **Contrast**: Text legibility, element distinction
- **Alignment**: Vertical lanes, horizontal alignment
- **Clipping**: Content cut off at edges
- **Token compliance**: All values match design system tokens
### Color Audit Process
When updating frames to match the design system:
1. Get computed styles for all text, background, border elements
2. Map old colors to design system tokens:
- Dark navy (`#0F4C81`, `#1A3A5C`) -> Primary `#0A39DF`
- Near-black (`#111827`, `#0F172A`) -> Foreground `#121826`
- Gray variants (`#94A3B8`, `#64748B`, `#475569`) -> Text Secondary `#6A7382`
- Green accents (`#20B486`) -> Primary `#0A39DF` (for pay) or `#059669` (for status)
3. Batch update using `update_styles` with multiple nodeIds per style change
4. Verify with screenshots
### Structural Consistency
When creating matching screens (e.g., two shift detail views):
- Use identical section ordering
- Match section header styles (11px/700 uppercase `#6A7382`)
- Use same card/row component patterns
- Maintain consistent padding and gap values
## 6. SVG Icon Patterns
### Chevron Left (Back)
```html
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Map Pin
```html
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="10" r="3" stroke="#6A7382" stroke-width="2"/>
</svg>
```
### User (Supervisor)
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="7" r="4" stroke="#6A7382" stroke-width="2"/>
</svg>
```
### Phone
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z" stroke="#6A7382" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Checkmark (Requirement Met)
```html
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" stroke="#059669" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22 4L12 14.01l-3-3" stroke="#059669" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
### Chip Checkmark
```html
<!-- Large chip (14x14) -->
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M2.5 7L5.5 10L11.5 4" stroke="#0A39DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<!-- Small chip (12x12) -->
<svg width="12" height="12" viewBox="0 0 14 14" fill="none">
<path d="M2.5 7L5.5 10L11.5 4" stroke="#0A39DF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
```
## 7. Anti-Patterns
### Colors
- Never use `#0F4C81`, `#1A3A5C` (old navy) - use `#0A39DF` (Primary)
- Never use `#111827`, `#0F172A` - use `#121826` (Foreground)
- Never use `#94A3B8`, `#64748B`, `#475569` - use `#6A7382` (Text Secondary)
- Never use `#20B486` for pay rates - use `#0A39DF` (Primary)
- Never use `#E2E8F0` for card borders - use `#D1D5DB` (Border)
### Components
- Never use pill radius (999px) for chips or badges - use 8px or 10px
- Never use gradient backgrounds on buttons
- Never mix font families within a role (headings = Inter Tight, body = Manrope)
- Never place back buttons at the bottom of frames - always after status bar
- Never hardcode CTA position - use `marginTop: auto` for bottom pinning
### Structure
- Never batch an entire screen in one `write_html` call
- Never skip review checkpoints after 2-3 modifications
- Never create frames without following the naming convention
- Never use `justifyContent: space-between` on artboards with many direct children - use `marginTop: auto` on the CTA instead
## Summary
**The design file is the source of truth for visual direction.** Every element must use the established tokens:
1. **Colors**: 7 core tokens + semantic badge colors
2. **Typography**: Inter Tight (headings) + Manrope (body), defined scale
3. **Spacing**: 24px page padding, 16-24px section gaps, 40px bottom safe area
4. **Radii**: 8px (chips/badges), 12px (cards/inputs), 14px (buttons/CTAs)
5. **Components**: Buttons, chips (large/small), inputs, cards, badges, nav bars
6. **Structure**: Status bar > Back > Content > Bottom CTA (pinned)
7. **Naming**: `<app>-<section>-<number>-<name>`
When in doubt, screenshot an existing screen and match its patterns exactly.

View File

@@ -1,175 +0,0 @@
## Recommended tasks for the next sprint
* In the mobile applications, since the structure is now finalized (at least for the existing features), we need to **strictly follow best practices while coding**:
* Break down large widgets into **smaller, reusable widgets**
* Add **doc comments** where necessary to improve readability and maintainability
* **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible
* Improvement points
- apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart
- Fix the location field in CoverageShiftRole to use the correct fallback logic.
- line 125 remove redundant location values.
- Change the name of the dataconnect connector replacing the "ExampleConnecter" with "KrowConnecter"
- ` final String status;` in `OrderItem` make it an enum.
- /// Date of the shift (ISO format).
final String date; make this in the DateTime format instead of string.
- in `view_orders_cubit.dart` combine the logic of `_calculateUpNextCount ` and `_calculateTodayCount` into a single function that calculates both counts together to avoid redundant filtering of orders.
- In places api call in the when the api's not working we need to show a proper error message instead of just an empty list.
- pending should come first in the view order list.
- How to check if the shift can be accepted by a worker?
- if a shift is already accepted in that time
- track minimum shift hours in the staff profile and show a warning if they try to apply for shifts that are below their minimum hours.
- this need to be added in the BE and also a FE validation (5 hrs).
- Cannot cancel before 24 hours of the shift start time. If do we should charge for 4 hours of work for each shifts.
- verify the order creation process in the client app.
- Vendor don't need to verify the order, when the order is created it should be automatically published.
- rethink the order status, we need to simplify it.
- Validation layer
- Profile info
- emergency contact
- experiences
- attires
- there should be manual verification by the client even if the ai verification is passed.
- to track false positives and false negatives.
- certifications
- there should be manual verification by the client even if the ai verification is passed.
- to track false positives and false negatives.
- documents
- tax forms
- How do we handle the current bank account verifcaiton in the current legacy application.
- We need have a show a list of clothing items in the staff app -> shift page.
- Template models for the pdf reports in the client and web apps.
- remove `any` type and replace it with the correct types in the codebase.
- What is the worker signup process
# Developement Tasks
## BE
- Shift acceptance validation by a worker
- How do we check if a shift can be accepted by a worker?
- if a shift is already accepted in that time
- we need to prevent accepting overlapping shifts.
- Make the alogrithm sclable which enables to add future rules for shift acceptance.
- This validation should be done in BE.
- Shift creation validation by a client
- Implement validation logic to ensure that shifts created by clients meet certain criteria
- This validation should be done in BE.
- CURRENTLY only add a Soft check for minimum shift hours when creating an order by a client
- When a client is creating an order, we need to check if the shift hours are below the minimum hours set by the vendor.
- This validation should be done in BE and also a FE validation
- Current minimum hours is 5 hrs.
- Make the alogrithm sclable which enables to add future rules for shift acceptance.
- Cancellation policy enforcement
- Implement logic to prevent cancellations within 24 hours of shift start time.
- If a cancellation is attempted within this window
- We need to finalise the penalty for this cancellation.
- Documentation upload process
- Implement a secure and efficient process for workers to upload required documentation (e.g., certifications, tax forms).
- Ensure that the uploaded documents are properly stored and linked to the worker's profile.
- Documentation parsing
- Implement a system to parse and extract relevant information from uploaded documents (e.g., certifications, tax forms) for verification purposes.
- there should be manual verification by the client even if the ai verification is passed.
- Attire upload
- Implement a system for workers to upload images of their attire for verification purposes.
- Attire verification
- Implement a system to verify the uploaded attire images against the required dress code for shifts.
- there should be manual verification by the client even if the ai verification is passed.
- Shift that require "awaiting confirmation" status
- Implement logic to handle shifts that require "awaiting confirmation" status, where the worker needs to manually confirm the shift before it becomes active.
- Enable NFC-based clock-in and clock-out functionality for staff members, allowing them to easily record their attendance using NFC technology (BE tasks).
- Enable worker profile visibility, where the worker's can hide their profile from clients if they choose to, and implement the necessary logic to handle profile visibility settings (BE tasks).
- Rapid order parsing (voice and text) using AI, allowing clients to quickly create orders by simply describing their needs, and implementing the necessary logic to parse and interpret the client's input to create accurate orders (BE tasks)
- This is always mapped similar to one time order creation.
## FE
### Staff mobile application
- Show google maps location in the shift details page in the woker app.
- Add a requirment section in the shift details page in the worker app which shows the requirements for that shift.
- Attire screen
- Show the list of MUST HAVE attire items.
- Show the list of NICE TO HAVE attire items.
- Allow workers to upload images of their attire for verification purposes.
- Show the list of uploaded attire images in the worker profile.
- FAQ screen in the worker app.
- Privacy and Security screen in the worker app.
- Profile visbility setting
- Terms of services (For now use a generated one but we need to have a proper one for the launch)
- Privacy policy (For now use a generated one but we need to have a proper one for the launch)
### Client mobile application
- Implement the remaining order types
- Rapid order creation using voicd and text input, allowing clients to quickly create orders by simply describing their needs, and implementing the necessary UI and logic to parse and interpret the client's input to create accurate orders (FE tasks).
- After parsing this should be populated in the screen similar to one time order creation screen where the client can make any necessary adjustments before finalising the order.
- This is always mapped similar to one time order creation as this only handles same day orders.
# Research Tasks
- How do we validate the SSN number of a worker in the US?
- Research third-party services or APIs that provide SSN validation.
- Evaluate the cost, reliability, and ease of integration of these services.
- Plan the integration process and identify any potential challenges.
- How do we validate the bank account details of a worker in the US?
- Research third-party services or APIs that provide bank account validation.
- Evaluate the cost, reliability, and ease of integration of these services.
- Plan the integration process and identify any potential challenges.
- In the legacy application we are only using soft FE checks but we need to have a proper validation process.
- What are the payment platforms we want to integrate for processing payments to workers?
- Research popular payment platforms (e.g., Stripe, PayPal, Square) that support payouts to workers.
- Evaluate the cost, reliability, and ease of integration of these platforms.
- Plan the integration process and identify any potential challenges.
- Implement test cases for 2 features in the web dashboard to be run in the agent browser (https://agent-browser.dev/)
- Research how to implement test cases for web applications using the agent browser.
# Business Tasks
- Create a template models for the pdf reports in the client and web apps.
- How do we handle situations like
- If a worker is a no-show for a shift.
- If the certain shifts are not getting enough applicants.
- These situtations hevaly disadvantages the clients and we need to have a clear policy on how to handle these situations.
- Terms of service and privacy policy for the mobile applications.
- How to handle the data request from worker side.
- Having a discussion about rephrasing certain termeniologies in the application
- Meaning of the worker registration process, is it signup or onboarding, because we are not doing a proper signup process where the worker can create an account by themselves, instead we are doing an onboarding process where the worker needs to provide their phone number and then we create an account for them and send them the OTP to login, as they should be already attached to a vendor when they are providing their phone number, we can consider this as an onboarding process rather than a signup process.
- Meaning of the worker profile visibility, is it "profile visibility" or "availability status", because the worker is not making their profile completely invisible to the clients, instead they are just marking themselves as unavailable for work, so we can consider this as "availability status" rather than "profile visibility".
- Meaning of the the auto match ?
- Shouldn't we do this any way ?
- Is this only a marketing item ?
- Validation layer
- Profile info
- emergency contact
- experiences
- attires
- there should be manual verification by the client even if the ai verification is passed.
- to track false positives and false negatives.
- certifications
- there should be manual verification by the client even if the ai verification is passed.
- to track false positives and false negatives.
- documents
- tax forms
- We need have a show a list of clothing items in the staff app -> shift page.