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

View File

@@ -133,6 +133,30 @@ lib/src/
- `docs/MOBILE/05-release-process.md` — Release quick reference - `docs/MOBILE/05-release-process.md` — Release quick reference
- `docs/RELEASE/mobile-releases.md` — Complete release guide - `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 ## CI/CD
- `.github/workflows/mobile-ci.yml` — Mobile build & test on PR - `.github/workflows/mobile-ci.yml` — Mobile build & test on PR

View File

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

View File

@@ -1244,7 +1244,7 @@
"clock_in": "ENTRADA", "clock_in": "ENTRADA",
"decline": "RECHAZAR", "decline": "RECHAZAR",
"accept_shift": "ACEPTAR TURNO", "accept_shift": "ACEPTAR TURNO",
"apply_now": "SOLICITAR AHORA", "apply_now": "RESERVAR TURNO",
"book_dialog": { "book_dialog": {
"title": "Reservar turno", "title": "Reservar turno",
"message": "\u00bfDesea reservar este turno al instante?" "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_cubit.dart';
import 'package:staff_attire/src/presentation/blocs/attire/attire_state.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_info_card.dart';
import '../widgets/attire_item_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}); const AttirePage({super.key});
@override
State<AttirePage> createState() => _AttirePageState();
}
class _AttirePageState extends State<AttirePage> {
bool _showRequired = true;
bool _showNonEssential = true;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AttireCubit cubit = Modular.get<AttireCubit>(); final AttireCubit cubit = Modular.get<AttireCubit>();
@@ -42,7 +52,12 @@ class AttirePage extends StatelessWidget {
return const Center(child: CircularProgressIndicator()); 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( return Column(
children: <Widget>[ children: <Widget>[
@@ -55,38 +70,45 @@ class AttirePage extends StatelessWidget {
const AttireInfoCard(), const AttireInfoCard(),
const SizedBox(height: UiConstants.space6), const SizedBox(height: UiConstants.space6),
// Filter Chips // Section toggle chips
AttireFilterChips( Row(
selectedFilter: state.filter,
onFilterChanged: cubit.updateFilter,
),
const SizedBox(height: UiConstants.space6),
// Item List
if (filteredOptions.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space10,
),
child: Center(
child: Column(
children: <Widget>[ children: <Widget>[
const Icon( AttireSectionTab(
UiIcons.shirt, label: 'Required',
size: 48, isSelected: _showRequired,
color: UiColors.iconInactive, onTap: () => setState(
() => _showRequired = !_showRequired,
),
),
const SizedBox(width: UiConstants.space3),
AttireSectionTab(
label: 'Non-Essential',
isSelected: _showNonEssential,
onTap: () => setState(
() => _showNonEssential = !_showNonEssential,
), ),
const SizedBox(height: UiConstants.space4),
Text(
context.t.staff_profile_attire.capture.no_items_filter,
style: UiTypography.body1m.textSecondary,
), ),
], ],
), ),
const SizedBox(height: UiConstants.space6),
// 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 else
...filteredOptions.map((AttireItem item) { ...requiredItems.map((AttireItem item) {
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: UiConstants.space3, bottom: UiConstants.space3,
@@ -104,6 +126,54 @@ class AttirePage extends StatelessWidget {
), ),
); );
}), }),
],
// Divider between sections
if (_showRequired && _showNonEssential)
const Padding(
padding: EdgeInsets.symmetric(
vertical: UiConstants.space8,
),
child: Divider(),
)
else
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), 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, backgroundColor: UiColors.bgPopup,
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg), shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
title: Row( title: Row(
spacing: UiConstants.space2,
children: [ children: [
const Icon(UiIcons.warning, color: UiColors.error), const Icon(UiIcons.warning, color: UiColors.error),
const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: Text( child: Text(
context.t.staff_shifts.shift_details.eligibility_requirements, context.t.staff_shifts.shift_details.eligibility_requirements,
@@ -350,7 +350,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
UiButton.primary( UiButton.primary(
text: "Go to Certificates", text: "Go to Certificates",
onPressed: () { onPressed: () {
Navigator.of(ctx).pop(); Modular.to.popSafe();
Modular.to.toCertificates(); Modular.to.toCertificates();
}, },
), ),

View File

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

View File

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