Merge pull request #582 from Oloodi/575-establish-root-level-agent-skills-directory-claudeskills-for-ai-agent-extraction---for-mobile

Mobile App Front end changes
This commit is contained in:
Achintha Isuru
2026-03-07 03:03:27 -05:00
committed by GitHub
12 changed files with 252 additions and 83 deletions

View File

@@ -54,9 +54,7 @@ If any of these files are missing or unreadable, notify the user before proceedi
- Use `BlocProvider.value()` for singleton BLoCs
- Use `UiColors`, `UiTypography`, `UiIcons`, `UiConstants` for all design values
- Use `core_localization` for user-facing strings
- Write unit tests for use cases and repositories
- Mock dependencies with `mocktail`
- Test BLoCs with `bloc_test`
- Add human readable doc comments for `dartdoc` for all classes and methods.
## Standard Workflow
@@ -109,7 +107,6 @@ Follow these steps in order for every feature implementation:
### 8. Self-Review
- Run `melos analyze` and fix all issues
- Run `melos test` and ensure all pass
- Manually verify no architectural violations exist
- Check all barrel files are complete
- Verify no hardcoded design values
@@ -150,9 +147,7 @@ Before declaring work complete, verify:
- [ ] BLoCs only depend on use cases
- [ ] Use cases only depend on repository interfaces
- [ ] All barrel files are complete and up to date
- [ ] Tests exist for use cases, repositories, and BLoCs
- [ ] `melos analyze` passes
- [ ] `melos test` passes
## Escalation Criteria
@@ -168,7 +163,6 @@ Stop and escalate to the human when you encounter:
After completing implementation, prepare a handoff summary including:
- Feature name and target app
- List of all changed/created files
- Test coverage percentage
- Any concerns, trade-offs, or technical debt introduced
- Recommendation for Architecture Review Agent review
@@ -179,9 +173,8 @@ As you work on features, update your agent memory with discoveries about:
- Session store usage patterns and available stores
- DataConnect query/mutation names and their locations
- Design token values and component patterns actually in use
- Common test setup patterns and shared test utilities
- Module registration patterns and route conventions
- Recurring issues found during `melos analyze` or `melos test`
- Recurring issues found during `melos analyze`
- Codebase-specific naming conventions that differ from general Flutter conventions
This builds institutional knowledge that improves your effectiveness across conversations.

View File

@@ -585,7 +585,7 @@ testWidgets('shows loading indicator when logging in', (tester) async {
## 11. Clean Code Principles
### Documentation
- ✅ Add doc comments to all public classes and methods
- ✅ Add human readable doc comments for `dartdoc` for all classes and methods.
```dart
/// Authenticates user with email and password.
///

View File

@@ -133,6 +133,30 @@ lib/src/
- `docs/MOBILE/05-release-process.md` — Release quick reference
- `docs/RELEASE/mobile-releases.md` — Complete release guide
## Skills & Sub-Agents
The project has 4 specialized skills in `.claude/skills/` that provide deep domain knowledge. **Invoke them when working in their domains** — they contain detailed rules, patterns, and code examples beyond what's in this file.
### krow-mobile-architecture
**When to use:** Architecting new mobile features, debugging state management or BLoC lifecycle issues, preventing prop drilling, managing session state, implementing Data Connect connector repositories, setting up feature modules and DI, refactoring to Clean Architecture.
**What it covers:** Full Clean Architecture implementation, package dependency graph, Data Connect service & session management (SessionHandlerMixin, SessionListener), connector pattern for reusable backend queries, BLoC lifecycle safety (singleton registration, BlocProvider.value(), BlocErrorHandler mixin with _safeEmit()), feature isolation rules, typed navigation with safe extensions, session store pattern.
### krow-mobile-development-rules
**When to use:** Creating new mobile features/packages, implementing BLoCs/Use Cases/Repositories, integrating with Firebase Data Connect, migrating from prototypes, reviewing code compliance, setting up navigation flows.
**What it covers:** Non-negotiable enforcement rules — file creation & package structure with exact path conventions, naming conventions, zero-tolerance logic placement boundaries (business rules → Use Cases only, state → BLoCs only, data transformation → Repositories), localization integration (all strings via core_localization, BLoCs emit failures not strings), Data Connect repository pattern with `_service.run()`, prototype migration rules, error handling pattern (domain failures → ErrorTranslator), enforcement checklist.
### krow-mobile-design-system
**When to use:** Implementing any UI in mobile features, migrating POC/prototype designs to production, creating themed widgets, reviewing UI code for design system compliance, matching colors/typography from designs, adding icons/spacing/layout.
**What it covers:** Immutable design token rules — all colors from `UiColors` (zero hex codes), all typography from `UiTypography` (zero custom TextStyle), all spacing/radius/elevation from `UiConstants` (zero magic numbers), all icons from `UiIcons` (zero direct library imports). POC → Production workflow (structure → architecture → design system integration), color/typography matching tables, extension policy for adding new tokens, review checklist.
### krow-mobile-release
**When to use:** Preparing mobile releases, updating CHANGELOGs, triggering GitHub Actions release workflows, creating hotfix branches, understanding versioning strategy, setting up APK signing, troubleshooting release failures.
**What it covers:** Versioning strategy (`v{major}.{minor}.{patch}-{milestone}`), CHANGELOG management (Keep a Changelog format, writing guidelines), Git tagging (`krow-withus-<app>-mobile/<env>-vX.Y.Z`), GitHub Actions workflows (Product Release, Product Hotfix), APK signing setup (24 GitHub Secrets), step-by-step release process for dev/stage/prod, hotfix procedures, release cadence, troubleshooting guide, helper scripts.
## CI/CD
- `.github/workflows/mobile-ci.yml` — Mobile build & test on PR

View File

@@ -1249,7 +1249,7 @@
"clock_in": "CLOCK IN",
"decline": "DECLINE",
"accept_shift": "ACCEPT SHIFT",
"apply_now": "APPLY NOW",
"apply_now": "BOOK SHIFT",
"book_dialog": {
"title": "Book Shift",
"message": "Do you want to instantly book this shift?"

View File

@@ -1244,7 +1244,7 @@
"clock_in": "ENTRADA",
"decline": "RECHAZAR",
"accept_shift": "ACEPTAR TURNO",
"apply_now": "SOLICITAR AHORA",
"apply_now": "RESERVAR TURNO",
"book_dialog": {
"title": "Reservar turno",
"message": "\u00bfDesea reservar este turno al instante?"

View File

@@ -8,13 +8,23 @@ import 'package:krow_domain/krow_domain.dart';
import 'package:staff_attire/src/presentation/blocs/attire/attire_cubit.dart';
import 'package:staff_attire/src/presentation/blocs/attire/attire_state.dart';
import '../widgets/attire_filter_chips.dart';
import '../widgets/attire_empty_section.dart';
import '../widgets/attire_info_card.dart';
import '../widgets/attire_item_card.dart';
import '../widgets/attire_section_header.dart';
import '../widgets/attire_section_tab.dart';
class AttirePage extends StatelessWidget {
class AttirePage extends StatefulWidget {
const AttirePage({super.key});
@override
State<AttirePage> createState() => _AttirePageState();
}
class _AttirePageState extends State<AttirePage> {
bool _showRequired = true;
bool _showNonEssential = true;
@override
Widget build(BuildContext context) {
final AttireCubit cubit = Modular.get<AttireCubit>();
@@ -42,7 +52,12 @@ class AttirePage extends StatelessWidget {
return const Center(child: CircularProgressIndicator());
}
final List<AttireItem> filteredOptions = state.filteredOptions;
final List<AttireItem> requiredItems = state.options
.where((AttireItem item) => item.isMandatory)
.toList();
final List<AttireItem> nonEssentialItems = state.options
.where((AttireItem item) => !item.isMandatory)
.toList();
return Column(
children: <Widget>[
@@ -55,55 +70,110 @@ class AttirePage extends StatelessWidget {
const AttireInfoCard(),
const SizedBox(height: UiConstants.space6),
// Filter Chips
AttireFilterChips(
selectedFilter: state.filter,
onFilterChanged: cubit.updateFilter,
// Section toggle chips
Row(
children: <Widget>[
AttireSectionTab(
label: 'Required',
isSelected: _showRequired,
onTap: () => setState(
() => _showRequired = !_showRequired,
),
),
const SizedBox(width: UiConstants.space3),
AttireSectionTab(
label: 'Non-Essential',
isSelected: _showNonEssential,
onTap: () => setState(
() => _showNonEssential = !_showNonEssential,
),
),
],
),
const SizedBox(height: UiConstants.space6),
// Item List
if (filteredOptions.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space10,
),
child: Center(
child: Column(
children: <Widget>[
const Icon(
UiIcons.shirt,
size: 48,
color: UiColors.iconInactive,
),
const SizedBox(height: UiConstants.space4),
Text(
context.t.staff_profile_attire.capture.no_items_filter,
style: UiTypography.body1m.textSecondary,
),
],
),
// Required section
if (_showRequired) ...<Widget>[
AttireSectionHeader(
title: 'Required',
count: requiredItems.length,
),
const SizedBox(height: UiConstants.space3),
if (requiredItems.isEmpty)
AttireEmptySection(
message: context
.t
.staff_profile_attire
.capture
.no_items_filter,
)
else
...requiredItems.map((AttireItem item) {
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space3,
),
child: AttireItemCard(
item: item,
isUploading: false,
uploadedPhotoUrl: state.photoUrls[item.id],
onTap: () {
Modular.to.toAttireCapture(
item: item,
initialPhotoUrl: state.photoUrls[item.id],
);
},
),
);
}),
],
// Divider between sections
if (_showRequired && _showNonEssential)
const Padding(
padding: EdgeInsets.symmetric(
vertical: UiConstants.space8,
),
child: Divider(),
)
else
...filteredOptions.map((AttireItem item) {
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space3,
),
child: AttireItemCard(
item: item,
isUploading: false,
uploadedPhotoUrl: state.photoUrls[item.id],
onTap: () {
Modular.to.toAttireCapture(
item: item,
initialPhotoUrl: state.photoUrls[item.id],
);
},
),
);
}),
const SizedBox(height: UiConstants.space6),
// Non-Essential section
if (_showNonEssential) ...<Widget>[
AttireSectionHeader(
title: 'Non-Essential',
count: nonEssentialItems.length,
),
const SizedBox(height: UiConstants.space3),
if (nonEssentialItems.isEmpty)
AttireEmptySection(
message: context
.t
.staff_profile_attire
.capture
.no_items_filter,
)
else
...nonEssentialItems.map((AttireItem item) {
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space3,
),
child: AttireItemCard(
item: item,
isUploading: false,
uploadedPhotoUrl: state.photoUrls[item.id],
onTap: () {
Modular.to.toAttireCapture(
item: item,
initialPhotoUrl: state.photoUrls[item.id],
);
},
),
);
}),
],
const SizedBox(height: UiConstants.space20),
],
),
@@ -117,3 +187,4 @@ class AttirePage extends StatelessWidget {
);
}
}

View File

@@ -0,0 +1,24 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class AttireEmptySection extends StatelessWidget {
const AttireEmptySection({super.key, required this.message});
final String message;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: UiConstants.space6),
child: Center(
child: Column(
children: <Widget>[
const Icon(UiIcons.shirt, size: 48, color: UiColors.iconInactive),
const SizedBox(height: UiConstants.space4),
Text(message, style: UiTypography.body1m.textSecondary),
],
),
),
);
}
}

View File

@@ -0,0 +1,24 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class AttireSectionHeader extends StatelessWidget {
const AttireSectionHeader({
super.key,
required this.title,
required this.count,
});
final String title;
final int count;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Text(title, style: UiTypography.headline4b),
const SizedBox(width: UiConstants.space2),
Text('($count)', style: UiTypography.body1m.textSecondary),
],
);
}
}

View File

@@ -0,0 +1,41 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class AttireSectionTab extends StatelessWidget {
const AttireSectionTab({
super.key,
required this.label,
required this.isSelected,
required this.onTap,
});
final String label;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space2,
),
decoration: BoxDecoration(
color: isSelected ? UiColors.primary : UiColors.white,
borderRadius: UiConstants.radiusFull,
border: Border.all(
color: isSelected ? UiColors.primary : UiColors.border,
),
),
child: Text(
label,
style: isSelected
? UiTypography.footnote2m.white
: UiTypography.footnote2m.textSecondary,
),
),
);
}
}

View File

@@ -328,9 +328,9 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
backgroundColor: UiColors.bgPopup,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
title: Row(
spacing: UiConstants.space2,
children: [
const Icon(UiIcons.warning, color: UiColors.error),
const SizedBox(width: UiConstants.space2),
Expanded(
child: Text(
context.t.staff_shifts.shift_details.eligibility_requirements,
@@ -350,7 +350,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
UiButton.primary(
text: "Go to Certificates",
onPressed: () {
Navigator.of(ctx).pop();
Modular.to.popSafe();
Modular.to.toCertificates();
},
),

View File

@@ -141,10 +141,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
charcode:
dependency: transitive
description:
@@ -853,14 +853,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation:
dependency: transitive
description:
@@ -929,18 +921,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
melos:
dependency: "direct dev"
description:
@@ -1516,26 +1508,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
url: "https://pub.dev"
source: hosted
version: "1.26.3"
version: "1.29.0"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.9"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
url: "https://pub.dev"
source: hosted
version: "0.6.12"
version: "0.6.15"
typed_data:
dependency: transitive
description:

View File

@@ -3,11 +3,11 @@
# Usage examples:
# make dataconnect-sync DC_ENV=dev
# make dataconnect-sync-full DC_ENV=dev
# make dataconnect-seed DC_ENV=validation
# make dataconnect-clean DC_ENV=validation
# make dataconnect-seed DC_ENV=dev
# make dataconnect-clean DC_ENV=dev
# make dataconnect-generate-sdk DC_ENV=dev
#
DC_ENV ?= validation
DC_ENV ?= dev
DC_LOCATION ?= us-central1
DC_CONNECTOR_ID ?= example