{"owner":"bagisto","repo":"opensource-ecommerce-mobile-app","hasSkills":true,"totalSkillsCount":16,"totalTokensCount":18046,"categories":["anthropic-skill","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/flutter-expert/SKILL.md",".agents/skills/flutter-expert/references/bloc-state.md",".agents/skills/flutter-expert/references/gorouter-navigation.md",".agents/skills/flutter-expert/references/performance.md",".agents/skills/flutter-expert/references/project-structure.md",".agents/skills/flutter-expert/references/riverpod-state.md",".agents/skills/flutter-expert/references/widget-patterns.md",".agents/skills/maestro-mobile-testing/SKILL.md",".kilocode/skills/flutter-expert/flutter-expert.md",".kilocode/skills/flutter-expert/references/bloc-state.md",".kilocode/skills/flutter-expert/references/gorouter-navigation.md",".kilocode/skills/flutter-expert/references/performance.md",".kilocode/skills/flutter-expert/references/project-structure.md",".kilocode/skills/flutter-expert/references/riverpod-state.md",".kilocode/skills/flutter-expert/references/widget-patterns.md","CLAUDE.md"],"skills":{".agents/skills/flutter-expert/SKILL.md":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n",".agents/skills/flutter-expert/references/bloc-state.md":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n",".agents/skills/flutter-expert/references/gorouter-navigation.md":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n",".agents/skills/flutter-expert/references/performance.md":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n",".agents/skills/flutter-expert/references/project-structure.md":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n",".agents/skills/flutter-expert/references/riverpod-state.md":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n",".agents/skills/flutter-expert/references/widget-patterns.md":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n",".agents/skills/maestro-mobile-testing/SKILL.md":"---\nname: maestro-mobile-testing\ndescription: \"Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration\"\nversion: 1.1.0\ncategory: toolchain\nauthor: tovimx\nlicense: MIT\nprogressive_disclosure:\n  entry_point:\n    summary: \"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification\"\n    when_to_use: \"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps\"\n    quick_start: \"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test\"\n  token_estimate:\n    entry: 150\n    full: 9000\ncontext_limit: 900\ntags:\n  - react-native\n  - expo\n  - testing\n  - maestro\n  - e2e\n  - mobile\n  - ios\n  - android\n  - yaml\n  - ci-cd\n  - mcp\nrequires_tools: []\n---\n\n# Maestro Mobile E2E Testing\n\n## Overview\n\nMaestro is a declarative YAML-based mobile E2E testing framework. It provides automatic waiting, built-in retry logic, and fast execution without boilerplate. It's more stable than Detox or Appium for React Native apps.\n\n### Key Features\n\n- **Declarative YAML** — no imperative test code, just steps\n- **Automatic waiting** — no manual `sleep()` or flaky waits\n- **Built-in retry** — reduces test flakiness\n- **Fast execution** — runs quickly without setup overhead\n- **Maestro Studio** — interactive test builder (`maestro studio`)\n- **Sub-flows** — reusable YAML sequences for DRY tests\n- **JavaScript scripting** — GraalJS runtime for HTTP calls and data manipulation\n- **Maestro Cloud** — real device testing in CI without local simulators\n\n## Quick Start\n\n### Install\n\n```bash\ncurl -Ls \"https://get.maestro.mobile.dev\" | bash\nbrew install openjdk@17\nexport JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home\n```\n\n### Minimal test\n\n```yaml\nappId: com.myapp\n---\n- launchApp\n- tapOn:\n    id: \"my-button\"\n- assertVisible: \"Expected Text\"\n```\n\n### Run\n\n```bash\nmaestro test .maestro/smoke-test.yaml\nmaestro test --debug .maestro/smoke-test.yaml  # step through\nmaestro studio                                  # interactive builder\n```\n\n---\n\n## Core Patterns\n\n### 1. Selector Strategy: testID vs Text\n\nChoose your selector approach based on project context. Both are valid — the right choice depends on whether your app is localized and your team's testing philosophy.\n\n| Context | Recommended Selector | Rationale |\n|---------|---------------------|-----------|\n| **Multi-language / i18n** | `id:` (testID) | Stable across translations |\n| **Single language** | Text labels | Human-readable, self-documenting tests |\n| **Agent-maintained tests** | Either — ask the developer | Readability matters less for AI-maintained flows |\n| **System dialogs** | Text (always) | No testID possible on native alerts |\n\n```yaml\n# testID selector — stable across translations\n- tapOn:\n    id: \"submit-button\"\n\n# Text selector — human-readable, self-documenting\n- tapOn: \"Submit\"\n```\n\n**When to prefer testIDs:**\n- App supports multiple languages or will be translated\n- UI text is dynamic or frequently changes\n- Multiple elements share the same visible text\n\n**When to prefer text selectors:**\n- Single-language app with stable copy\n- Readability and self-documentation are a priority\n- Testing user-visible behavior exactly as it appears\n\nIn React Native, add `testID` props when using ID-based selectors:\n\n```tsx\n<TouchableOpacity testID=\"submit-button\" onPress={handleSubmit}>\n  <Text>{t('submit')}</Text>\n</TouchableOpacity>\n```\n\n### testID Naming Convention\n\nWhen using ID-based selectors:\n\n```\n{component}-{action/type}[-{variant}]\n\nExamples:\n- auth-prompt-login-button\n- product-card-{id}\n- otp-input-0\n- tab-home\n- dashboard-loading\n```\n\n### 2. Auth Pre-Flight Pattern\n\nPrevent race conditions where Maestro interacts with the UI before auth state resolves. Add a zero-size `auth-loaded` marker that only renders when auth loading completes:\n\n```tsx\n// In your tab bar or root layout\n{!isLoading && <View testID=\"auth-loaded\" style={{ width: 0, height: 0 }} />}\n```\n\nThen in every test:\n\n```yaml\n- launchApp\n\n# Prevent XCTest crash on cold boot (iOS)\n- swipe:\n    direction: DOWN\n    duration: 100\n\n# Wait for auth state to resolve\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n# Now safe to interact\n- tapOn:\n    id: \"tab-home\"\n```\n\n### 3. Adaptive Tests (Handle Both Auth States)\n\nTests should work regardless of whether the user is authenticated:\n\n```yaml\n# Auth flow — only runs if login prompt is visible\n- runFlow:\n    when:\n      visible: \"Sign In\"\n    file: flows/auth-flow.yaml\n\n# Already authenticated — proceed directly\n- runFlow:\n    when:\n      visible:\n        id: \"tab-home\"\n    file: flows/authenticated-action.yaml\n```\n\n### 4. Testing Optimistic Updates\n\nUse short timeouts to verify UI changes happen before server response:\n\n```yaml\n# Trigger mutation\n- tapOn:\n    id: \"action-button\"\n\n# OPTIMISTIC: UI must change within 3s (not waiting for server)\n- extendedWaitUntil:\n    visible:\n      id: \"undo-button\"\n    timeout: 3000\n\n# Verify derived UI state\n- extendedWaitUntil:\n    visible:\n      id: \"user-indicator\"\n    timeout: 5000\n```\n\n| Action | Expected Change | Timeout |\n|--------|----------------|---------|\n| Mutation trigger | Button state flips | < 3s |\n| List update | Item appears/disappears | < 5s |\n| Re-do action | Proves persistence | < 3s |\n\n### 5. Dismissing Native Alerts\n\nReact Native `Alert.alert()` creates native dialogs that block the UI:\n\n```yaml\n- tapOn:\n    id: \"action-button\"\n\n# Wait for expected state change first\n- extendedWaitUntil:\n    visible:\n      id: \"new-state-element\"\n    timeout: 5000\n\n# Dismiss alert (optional in case it already closed)\n- tapOn:\n    text: \"OK\"\n    optional: true\n\n# Brief delay for alert animation\n- swipe:\n    direction: DOWN\n    duration: 300\n```\n\n### 6. Sub-Flows for Reusability\n\nBreak repeated sequences into sub-flow files:\n\n```\n.maestro/\n├── flows/\n│   ├── auth-and-return.yaml\n│   ├── complete-purchase.yaml\n│   └── verify-result.yaml\n├── smoke-test.yaml\n└── feature-test.yaml\n```\n\n```yaml\n# In main test\n- runFlow:\n    file: flows/auth-and-return.yaml\n```\n\n### 7. Deep Links (Expo)\n\nUse the Expo scheme from `app.json`, not the bundle ID:\n\n```yaml\n# WRONG\n- openLink: \"com.myapp://profile/settings\"\n\n# CORRECT\n- openLink: \"myapp://profile/settings\"\n```\n\nDeep links must be registered in your app's deep link handler. Unregistered routes silently fail.\n\n### 8. Platform-Specific Logic\n\n```yaml\n- runFlow:\n    when:\n      platform: ios\n    file: flows/ios-specific.yaml\n\n- runFlow:\n    when:\n      platform: android\n    file: flows/android-specific.yaml\n```\n\n### 9. Environment Variables\n\n```yaml\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-test@example.com\n  API_BASE_URL: http://localhost:3000\n---\n- inputText: ${TEST_EMAIL}\n```\n\n### 10. Selector State Properties\n\nUse `enabled`, `selected`, `checked`, and `focused` to target elements by their current state. This is useful for validating interactive element states before or after actions.\n\n```yaml\n# Only tap the submit button if it's enabled\n- tapOn:\n    id: \"submit-button\"\n    enabled: true\n\n# Assert a checkbox is checked\n- assertVisible:\n    id: \"terms-checkbox\"\n    checked: true\n\n# Wait for an input to be focused\n- extendedWaitUntil:\n    visible:\n      id: \"email-input\"\n      focused: true\n    timeout: 3000\n```\n\n| Property | Values | Use Case |\n|----------|--------|----------|\n| `enabled` | `true` / `false` | Buttons that disable during submission or until form is valid |\n| `checked` | `true` / `false` | Checkboxes, toggle switches |\n| `selected` | `true` / `false` | Tab items, segmented controls |\n| `focused` | `true` / `false` | Input fields with auto-focus |\n\n### 11. Relative Position Selectors\n\nDistinguish between similar elements by their spatial relationship to other elements. This is more idiomatic and resilient than index-based selection.\n\n```yaml\n# BAD — fragile, breaks if order changes\n- tapOn:\n    text: \"Add to Basket\"\n    index: 1\n\n# GOOD — contextual, self-documenting\n- tapOn:\n    text: \"Add to Basket\"\n    below:\n      text: \"Awesome Shoes\"\n```\n\nAvailable relative selectors:\n\n```yaml\n# Target element below another\n- tapOn:\n    text: \"Buy Now\"\n    below: \"Product Title\"\n\n# Target element that is a child of a parent\n- tapOn:\n    text: \"Delete\"\n    childOf:\n      id: \"item-card-42\"\n\n# Target a parent that contains a specific child\n- tapOn:\n    containsChild: \"Urgent\"\n\n# Target by multiple descendants\n- tapOn:\n    containsDescendants:\n      - id: title_id\n        text: \"Specific Title\"\n      - \"Another descendant text\"\n\n# Horizontal positioning\n- tapOn:\n    text: \"Edit\"\n    rightOf: \"Username\"\n```\n\n| Selector | Meaning |\n|----------|---------|\n| `below:` | Element is positioned below the referenced element |\n| `above:` | Element is positioned above the referenced element |\n| `leftOf:` | Element is to the left of the referenced element |\n| `rightOf:` | Element is to the right of the referenced element |\n| `childOf:` | Element is a direct child of the referenced parent |\n| `containsChild:` | Element contains a direct child matching the reference |\n| `containsDescendants:` | Element contains all specified descendant elements |\n\n---\n\n## Authentication Testing\n\n### Architecture\n\nTesting OTP or magic-link authentication in E2E requires capturing emails programmatically. The general pattern:\n\n```\n┌─────────────┐     ┌──────────────┐     ┌─────────────────┐\n│  Maestro    │────▶│  Auth        │────▶│  Email Capture  │\n│  Test       │     │  Provider    │     │  Service        │\n└─────────────┘     └──────────────┘     └─────────────────┘\n       │                                        │\n       │         ┌──────────────────────────────┘\n       │         ▼\n       │   ┌─────────────┐\n       └──▶│  REST API   │ ─── GET /api/v1/messages\n           │  (email)    │ ─── Extract OTP code\n           └─────────────┘\n```\n\nCommon email capture services: [Mailpit](https://github.com/axllent/mailpit), [MailHog](https://github.com/mailhog/MailHog), [Ethereal](https://ethereal.email/).\n\n### OTP Fetch Script\n\nFetch OTP codes from your email capture service using Maestro's GraalJS runtime:\n\n```javascript\n// CRITICAL: Maestro uses GraalJS — NO async/await, NO fetch()\nvar email = typeof EMAIL !== \"undefined\" ? EMAIL : \"test@example.com\";\nvar emailServiceUrl = typeof EMAIL_SERVICE_URL !== \"undefined\"\n  ? EMAIL_SERVICE_URL : \"http://localhost:8025\";\n\nvar response = http.get(emailServiceUrl + \"/api/v1/messages\");\nif (!response.ok) {\n  throw new Error(\"Failed to fetch emails: \" + response.status);\n}\n\nvar data = json(response.body);\n// Find the latest email and extract OTP code\nvar body = data.messages[0].Content.Body;\nvar match = body.match(/(\\d{6})/);\noutput.OTP_CODE = match[1];\n```\n\n### OTP Input Strategy\n\nOTP components with auto-focus need individual digit entry. Tap each input before typing:\n\n```yaml\n# Split OTP into digits via helper script\n- runScript:\n    file: scripts/split-otp.js\n    env:\n      OTP_CODE: ${output.OTP_CODE}\n\n# Enter each digit by tapping its input\n- tapOn:\n    id: \"otp-input-0\"\n- inputText: ${output.OTP_0}\n\n- tapOn:\n    id: \"otp-input-1\"\n- inputText: ${output.OTP_1}\n# ... repeat for all digits\n```\n\n> For provider-specific implementations (Supabase + Mailpit, Firebase Auth, Auth0), create a project-level skill that extends this one.\n\n---\n\n## GraalJS Script Rules\n\nMaestro uses the GraalJS runtime. These constraints are non-negotiable:\n\n| Feature | Status |\n|---------|--------|\n| `async/await` | **NOT supported** |\n| `fetch()` | **NOT supported** |\n| `http.get()`, `http.post()` | Use these instead |\n| `json()` | Use to parse response bodies |\n| `output.VAR` | Set variables for use in YAML flow |\n| `var` declarations | Required (use `var`, not `const`/`let` for safety) |\n\n```javascript\n// Script template\nvar response = http.get(\"http://localhost:8025/api/endpoint\");\nif (!response.ok) {\n  throw new Error(\"Request failed: \" + response.status);\n}\nvar data = json(response.body);\noutput.RESULT = data.value;\n```\n\n---\n\n## Critical Gotchas\n\n### clearState Does NOT Clear iOS Keychain\n\n`clearState: true` clears the app sandbox (UserDefaults, files, caches) but does **NOT** clear the iOS Keychain. Auth tokens stored via `expo-secure-store` (or any Keychain-based storage) persist across `clearState` resets and even app reinstalls.\n\n```yaml\n# WRONG — user may still be authenticated\n- launchApp:\n    clearState: true\n- assertVisible: \"Welcome\"  # Fails if Keychain has tokens\n\n# CORRECT — wait for auth resolution, then adapt\n- launchApp\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n```\n\n**Rules:**\n- Never rely on `clearState` to produce guest state on iOS\n- For auth tests: skip `clearState`, use `auth-loaded` pre-flight\n- For guest tests: use adaptive flows that handle both states\n- Never assert guest-only UI after `clearState`\n\n**Note:** On Android, `clearState: true` fully resets app data including credentials. This is an iOS-only gotcha.\n\n### XCTest kAXErrorInvalidUIElement Crash (iOS)\n\nThe XCTest driver may crash if Maestro interacts with the accessibility tree before the first render cycle completes on cold boot.\n\n**Fix:** Add a no-op swipe immediately after `launchApp`:\n\n```yaml\n- launchApp\n- swipe:\n    direction: DOWN\n    duration: 100\n```\n\n### API Server Dependency\n\nMobile apps calling backend APIs on localhost need either the full server or a mock server running. Without it, all API-dependent screens show loading spinners or empty states (queries fail silently).\n\n**Fix:** Start a mock API server before running Maestro tests:\n\n```bash\n# Start mock server (serves canned responses on your API port)\nnpx tsx scripts/mock-api-server.ts &\n\n# Then run tests\nmaestro test .maestro/my-test.yaml\n```\n\nCreate a lightweight mock that returns canned JSON for each endpoint your app calls. This is faster and more deterministic than running your full backend.\n\n### Auth-Aware Tab Bars\n\nTab bars that show different tabs for guest vs authenticated users will cause selector failures:\n\n| State | Typical Tabs |\n|-------|-------------|\n| Guest | home, search, cart, profile |\n| Auth | home, feed, create, messages, profile |\n\nOnly assert tabs that exist in both states, or use adaptive `when:` conditions.\n\n---\n\n## Test File Template\n\n```yaml\n# {Feature} {Action} Test\n#\n# Tests: {what this validates}\n# Prerequisites:\n# - Simulator/emulator running with app installed\n# - Backend or mock server running (if API-dependent)\n\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-{feature}@example.com\n  EMAIL_SERVICE_URL: http://localhost:8025\n---\n# ==========================================\n# STEP 1: LAUNCH + AUTH PRE-FLIGHT\n# ==========================================\n\n- launchApp\n\n- swipe:\n    direction: DOWN\n    duration: 100\n\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n- takeScreenshot: 01-initial-state\n\n# ==========================================\n# STEP 2: {ACTION}\n# ==========================================\n\n- tapOn:\n    id: \"target-element\"\n\n# ==========================================\n# STEP 3: VERIFY\n# ==========================================\n\n- extendedWaitUntil:\n    visible:\n      id: \"expected-result\"\n    timeout: 5000\n\n- takeScreenshot: 02-final-state\n```\n\n---\n\n## Folder Structure\n\n```\n.maestro/\n├── README.md                    # Quick reference + testID inventory\n├── config.yaml                  # Shared configuration\n├── flows/                       # Reusable sub-flows\n│   ├── auth-and-return.yaml\n│   ├── complete-action.yaml\n│   └── verify-result.yaml\n├── scripts/                     # GraalJS helpers\n│   ├── fetch-otp.js\n│   └── split-otp.js\n├── smoke-test.yaml              # Guest navigation\n├── auth-signin.yaml             # OTP sign-in flow\n├── feature-screenshots.yaml     # Screenshot capture flows\n└── feature-action.yaml          # Feature-specific tests\n\nscripts/\n├── mock-api-server.ts           # Lightweight mock for E2E\n└── run-e2e.sh                   # Orchestration script\n```\n\n### Naming Conventions\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Main test | `{feature}-{action}.yaml` | `checkout-purchase.yaml` |\n| Sub-flow | `{action}-{context}.yaml` | `auth-and-return-to-dashboard.yaml` |\n| Script | `{verb}-{noun}.js` | `fetch-otp.js` |\n\n---\n\n## Infrastructure\n\n### Mock Server for API-Dependent Tests\n\nCreate a lightweight mock server that serves canned responses for your API layer. This is faster and more deterministic than running your full backend during E2E tests.\n\n```bash\n# Start mock server before running Maestro tests\nnpx tsx scripts/mock-api-server.ts &\n```\n\n### Orchestration Script\n\nAutomate the full E2E setup with a shell script that:\n1. Starts backend services (database, auth)\n2. Seeds test data\n3. Starts mock API server\n4. Runs Maestro tests\n5. Cleans up all services\n\n```bash\nbash scripts/run-e2e.sh\n```\n\n### Seed Data\n\nTests that depend on specific data require seeded databases. Keep seed scripts alongside your test infrastructure and run them before each test suite.\n\n---\n\n## Android-Specific Patterns\n\n### Emulator Setup\n\nAndroid tests require an emulator or a USB-connected physical device. Maestro auto-detects connected devices.\n\n```bash\n# List available system images\nsdkmanager --list | grep system-images\n\n# Create emulator\navdmanager create avd -n maestro_test \\\n  -k \"system-images;android-34;google_apis;arm64-v8a\"\n\n# Start emulator\nemulator -avd maestro_test\n```\n\n### iOS vs Android Differences\n\n| Aspect | iOS | Android |\n|--------|-----|---------|\n| Device type | Simulator only (no physical) | Emulator + physical via ADB |\n| `clearState` | Does NOT clear Keychain | Fully resets app data |\n| Cold boot crash | XCTest kAXError (add swipe delay) | No equivalent issue |\n| Performance | Runs natively (fast) | ARM emulation (slower on x86) |\n| Permission dialogs | System alerts | System dialogs with different text |\n\n### ADB Debugging\n\n```bash\nadb devices                                    # List connected devices\nadb shell am start -n com.myapp/.MainActivity  # Launch app\nadb logcat | grep Maestro                      # Filter Maestro logs\nadb shell input keyevent 82                    # Unlock screen\n```\n\n### Android Permission Handling\n\nAndroid permissions appear as system dialogs. Dismiss with optional taps:\n\n```yaml\n- tapOn:\n    text: \"Allow\"\n    optional: true\n\n- tapOn:\n    text: \"While using the app\"\n    optional: true\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions with Maestro Cloud\n\nMaestro Cloud provides real devices in CI without local simulators. Use the official action:\n\n```yaml\nname: Mobile E2E Tests\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  maestro-e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # Build Android APK\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '17'\n          distribution: 'temurin'\n\n      - name: Build Android APK\n        run: |\n          cd apps/mobile\n          npx expo prebuild --platform android --no-install\n          cd android && ./gradlew assembleRelease\n\n      - name: Run Maestro Cloud Tests\n        id: maestro\n        uses: mobile-dev-inc/action-maestro-cloud@v2\n        with:\n          api-key: ${{ secrets.MAESTRO_API_KEY }}\n          app-file: apps/mobile/android/app/build/outputs/apk/release/app-release.apk\n          workspace: .maestro\n          include-tags: ci\n\n      # Access results\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_CONSOLE_URL }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}\n```\n\n### Tag-Based Flow Filtering\n\nUse tags to control which tests run in CI vs locally:\n\n```yaml\n# In your flow file header\nappId: com.myapp\ntags:\n  - ci\n  - smoke\n---\n- launchApp\n# ... test steps\n```\n\n```bash\n# Run only CI-tagged flows locally\nmaestro test --include-tags ci .maestro/\n\n# Exclude work-in-progress flows\nmaestro test --exclude-tags wip .maestro/\n```\n\n### Local CI with Docker (Android Only)\n\n```dockerfile\nFROM openjdk:17-slim\n\nRUN curl -Ls \"https://get.maestro.mobile.dev\" | bash\nENV PATH=\"/root/.maestro/bin:${PATH}\"\n\nCOPY .maestro/ /app/.maestro/\nWORKDIR /app\n\nCMD [\"maestro\", \"test\", \".maestro/\"]\n```\n\n**Note:** iOS tests cannot run in Docker (requires macOS). Use Maestro Cloud for iOS in CI.\n\n---\n\n## Maestro Cloud\n\n[Maestro Cloud](https://cloud.maestro.dev/) runs tests on real devices without local simulator setup.\n\n### Setup\n\n1. Create account at cloud.maestro.dev\n2. Generate API key from dashboard\n3. Store as `MAESTRO_API_KEY` secret in your CI provider\n\n### Running from CLI\n\n```bash\n# Upload and run on Maestro Cloud\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  .maestro/\n\n# With tag filtering\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  --include-tags smoke \\\n  .maestro/\n```\n\n### Key Points\n\n- **iOS testing**: Supported on Maestro Cloud (not on local physical devices)\n- **Android testing**: Both local physical devices and Maestro Cloud\n- **Results**: Dashboard with video recordings, logs, and screenshots\n- **CI outputs**: `MAESTRO_CLOUD_CONSOLE_URL`, `MAESTRO_CLOUD_FLOW_RESULTS`\n\n---\n\n## Maestro MCP Server\n\nThe [Maestro MCP server](https://docs.maestro.dev/getting-started/maestro-mcp) exposes Maestro's full command set as Model Context Protocol tools, letting AI agents **execute tests and interact with devices directly** — not just write YAML.\n\n### How It Complements This Skill\n\n| | **This Skill** | **Maestro MCP** |\n|---|---|---|\n| **Role** | Teaches correct patterns | Provides runtime execution |\n| **Layer** | Authoring (write good YAML) | Execution (run, tap, assert, screenshot) |\n| **Output** | Better test files | Live device interaction |\n\nUse both together: this skill ensures the AI writes correct tests; the MCP lets it run them immediately, see failures, and iterate.\n\n### Setup\n\nThe MCP ships with the Maestro CLI — no extra install needed:\n\n```bash\n# Verify it's available\nmaestro mcp\n```\n\n**Claude Code** — add to project `.mcp.json` or global settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\n**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nAlso supported on Cursor, Windsurf, VS Code, and JetBrains IDEs. See the [Maestro MCP docs](https://docs.maestro.dev/getting-started/maestro-mcp) for IDE-specific setup.\n\n### Key Capabilities\n\nThe MCP server exposes 47 tools organized by category:\n\n| Category | Tools | Examples |\n|----------|-------|---------|\n| UI Interaction | tap, swipe, scroll, long press | `tapOn`, `scrollUntilVisible` |\n| Text Input | type, erase, paste, copy | `inputText`, `eraseText` |\n| Assertions | visibility, AI-powered | `assertVisible`, `assertWithAI` |\n| App Lifecycle | launch, stop, clear state | `launchApp`, `clearState` |\n| Device Control | location, orientation, airplane | `setLocation`, `hideKeyboard` |\n| Flow Control | run flows, repeat, eval scripts | `runFlow`, `evalScript` |\n| Media | screenshots, recording | `takeScreenshot`, `startRecording` |\n| AI-Powered | visual assertions, defect detection | `assertWithAI`, `assertNoDefectsWithAI` |\n\n### Write-Run-Fix Loop\n\nWith both this skill and the MCP active, the AI can:\n\n1. **Write** a test YAML using patterns from this skill\n2. **Run** it via MCP's `runFlow` / `launchApp` + interaction tools\n3. **See** failures via screenshots and assertions\n4. **Fix** the YAML and re-run — all in one conversation\n\n---\n\n## Debugging\n\n```bash\nmaestro test --debug .maestro/test.yaml   # Step through interactively\nmaestro record .maestro/test.yaml          # Record as video\nmaestro studio                             # Interactive UI builder\nmaestro hierarchy                          # View element tree\n```\n\nScreenshots saved to `~/.maestro/tests/{timestamp}/`.\n\n---\n\n## Checklist for New Tests\n\n```\n[ ] Unique test email (maestro-{feature}@example.com)\n[ ] Selector strategy chosen (testID for i18n apps, text for single-language — see Pattern 1)\n[ ] Selectors use state properties where relevant (enabled, checked — see Pattern 10)\n[ ] Similar elements distinguished with relative selectors, not index (see Pattern 11)\n[ ] Auth pre-flight pattern used (auth-loaded)\n[ ] Post-launch swipe added (iOS crash prevention)\n[ ] Both auth states handled (adaptive flows)\n[ ] Native alerts dismissed after mutations\n[ ] Short timeouts for optimistic updates (3-5s)\n[ ] Sub-flows created for reusable sequences\n[ ] Descriptive screenshots at key points\n[ ] Header comment with prerequisites\n[ ] Added to README.md test table\n[ ] Mock API server started if backend-dependent\n[ ] Tags added for CI filtering (ci, smoke, wip)\n```\n\n---\n\n## Common Errors\n\n| Error | Cause | Fix |\n|-------|-------|-----|\n| \"Unable to locate Java Runtime\" | Java not in PATH | `export JAVA_HOME=/opt/homebrew/opt/openjdk@17/...` |\n| \"Element not found\" after tap | Native alert blocking | Add `tapOn: text: \"OK\" optional: true` |\n| OTP digits not entering | Auto-focus interference | Use individual `otp-input-N` testIDs |\n| Test passes but nothing happened | `optional: true` misused | Only use optional for truly optional actions |\n| \"Assertion is false\" on visibility | Element not rendered yet | Increase timeout or verify testID exists |\n| Script output empty | Wrong JS API | Use `http.get()` not `fetch()` |\n| Auth state inconsistent after clearState | iOS Keychain not cleared | Don't use `clearState`, use adaptive flows |\n| kAXErrorInvalidUIElement crash | Cold boot race (iOS) | Add post-launch swipe delay |\n| Loading spinners / empty screens | No API server running | Start mock API server before tests |\n| Permission dialog blocking (Android) | System dialog not dismissed | Add `tapOn: text: \"Allow\" optional: true` |\n\n---\n\n## Resources\n\n- [Maestro Documentation](https://docs.maestro.dev/)\n- [Maestro Selectors Reference](https://docs.maestro.dev/api-reference/selectors)\n- [Maestro Cloud](https://cloud.maestro.dev/)\n- [Maestro MCP Server](https://docs.maestro.dev/getting-started/maestro-mcp)\n- [Maestro GitHub](https://github.com/mobile-dev-inc/Maestro)\n- [GraalJS HTTP Requests](https://docs.maestro.dev/advanced/javascript/make-http-s-requests)\n- [Conditions & Adaptive Flows](https://docs.maestro.dev/advanced/conditions)\n- [GitHub Actions Integration](https://docs.maestro.dev/cloud/ci-integration/github-actions)\n- [BrowserStack: Maestro Testing Guide](https://www.browserstack.com/guide/maestro-testing)\n- [DEV.to: Tips & Tricks for Maestro + React Native](https://dev.to/retyui/best-tips-tricks-for-e2e-maestro-with-react-native-2kaa)\n",".kilocode/skills/flutter-expert/flutter-expert.md":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n",".kilocode/skills/flutter-expert/references/bloc-state.md":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n",".kilocode/skills/flutter-expert/references/gorouter-navigation.md":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n",".kilocode/skills/flutter-expert/references/performance.md":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n",".kilocode/skills/flutter-expert/references/project-structure.md":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n",".kilocode/skills/flutter-expert/references/riverpod-state.md":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n",".kilocode/skills/flutter-expert/references/widget-patterns.md":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n","CLAUDE.md":"# CLAUDE.md\n\nGuidance for Claude Code when working in this repository.\n\n## Mandatory skills: apply to EVERY prompt\n\nThese rules apply to every user prompt in this repo, including simple questions,\none-line fixes, and follow-ups. Do them before any other response or tool call.\n\n1. **`/superpowers:using-superpowers`**: invoke it first on every prompt (if not\n   already loaded this session). Then check each available superpowers skill\n   and invoke every one that could apply, for example:\n   - new feature or behavior change: `superpowers:brainstorming`, then `superpowers:writing-plans`\n   - bug, test failure, unexpected behavior: `superpowers:systematic-debugging`\n   - writing code: `superpowers:test-driven-development`\n   - before claiming \"done\" or committing: `superpowers:verification-before-completion`\n2. **`/caveman:caveman`**: keep caveman mode active for every reply (default\n   level `full`). Chat replies stay terse. Code, comments, commit messages, PR\n   text, docs, and memory files stay normal prose. Only turn caveman off when\n   the user says \"stop caveman\" or \"normal mode\".\n3. **`/graphify`**: invoke the `graphify` skill for every prompt about code,\n   architecture, file relationships, or project content.\n   - If `graphify-out/` exists, query the graph first, before grep or file reads.\n   - If `graphify-out/` does not exist, build it with `/graphify` first.\n   - After significant code changes, update the graph so it stays current.\n\nOrder per prompt: `using-superpowers`, then `caveman`, then `graphify`, then the\ntask-specific skills (for example `flutter-expert`), then the work itself.\n\nDo not skip these because a task \"seems simple\". Only skip a rule when the user\nexplicitly says so for that prompt.\n\n## Owner rules and backend-blocked list\n\nRead `plan.md` at the repo root before any task. Key rules: use only the\nBagisto **GraphQL** API (never the REST `/api/shop/...` endpoints), use only\nlive API data (no hardcoded values), validate every query against the\nconfigured server, and never commit or push without the owner's permission.\n`plan.md` also lists features that are blocked because the GraphQL schema\nlacks the field.\n\n## Project\n\nBagisto open-source e-commerce mobile app. Flutter (Dart SDK ^3.10.8), package\nname `bagisto_flutter`, talks to a Bagisto backend over GraphQL.\n\n## Commands\n\n```bash\nflutter pub get\nflutter run\nflutter analyze\nflutter test                       # all tests\nflutter test test/orders_bloc_test.dart\nflutter gen-l10n                   # after editing any lib/l10n/app_*.arb\n```\n\nMaestro E2E flows live in `maestro/` (see `maestro/LABEL_BASED_INSTRUCTIONS.md`).\n\n## Layout\n\n- `lib/core/`: shared code (`graphql/`, `theme/`, `error/`, `locale/`, `currency/`, `widgets/`, ...)\n- `lib/core/graphql/`: raw GraphQL query/mutation strings in static-only classes (`AccountQueries`, `ReturnsQueries`, ...)\n- `lib/features/<feature>/`: `account`, `auth`, `cart`, `category`, `checkout`, `home`, `product`, `search`, `splash`\n- `lib/l10n/`: 10 `app_*.arb` files plus generated `app_localizations*.dart` (committed)\n- `Docs/`: setup guides; design specs under `Docs/superpowers/specs/`\n- `Docs/ai-memory/`: notes for AI agents on past fix rounds (root causes,\n  gotchas, backend-blocked issues). Read the relevant note before touching\n  those areas, and add a new dated note after a large fix round.\n- `test/`: flat directory of unit and widget tests\n\n## House style\n\n- **State**: `flutter_bloc` + `equatable`. Events, states, and bloc live in ONE\n  file per bloc under `lib/features/<feature>/presentation/bloc/`.\n- **Models**: hand-written defensive `fromJson`. No codegen, no `json_serializable`.\n- **GraphQL**: `graphql_flutter`. Repositories call the client directly (no\n  datasource layer) and throw feature exceptions built with\n  `ErrorMapper.getUserMessage`. Single-entity Bagisto queries need IRI ids\n  (for example `/api/shop/customer-orders/{id}`).\n- **Pagination**: cursor (Relay). Repositories return records\n  `({list, totalCount, hasNextPage, endCursor})`.\n- **Navigation**: no route table or GoRouter. Use imperative\n  `Navigator.push(MaterialPageRoute(...))` with `BlocProvider` created inline.\n  Detail pages expose `static navigate(...)`.\n- **l10n**: every user-facing string goes into all 10 arb files. Key format is\n  camelCase `<feature><Thing>`. Placeholder metadata only in `app_en.arb`.\n  Run `flutter gen-l10n` and commit the generated files.\n- **UI**: widgets branch on `Theme.of(context).brightness` for dark mode.\n  Primary color `AppColors.primary500` (#FF6900). Cards: radius 10, padding 12.\n  Primary buttons: height 48, radius 54.\n- **Tests**: fake repositories subclass the real repository with a dummy\n  `GraphQLClient`. Seed bloc state through a `seedState` subclass\n  (see `test/orders_bloc_test.dart`).\n\n## Known baseline\n\n- `flutter analyze` already reports about 49 info/warnings in category, home,\n  and search files. Do not treat these as regressions.\n- `test/address_debug_payload_test.dart` fails on a clean tree.\n- Image upload for returns/messages is REST multipart only, not in GraphQL.\n"},"files":{".agents/skills/flutter-expert/SKILL.md":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n",".agents/skills/flutter-expert/references/bloc-state.md":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n",".agents/skills/flutter-expert/references/gorouter-navigation.md":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n",".agents/skills/flutter-expert/references/performance.md":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n",".agents/skills/flutter-expert/references/project-structure.md":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n",".agents/skills/flutter-expert/references/riverpod-state.md":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n",".agents/skills/flutter-expert/references/widget-patterns.md":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n",".agents/skills/maestro-mobile-testing/SKILL.md":"---\nname: maestro-mobile-testing\ndescription: \"Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration\"\nversion: 1.1.0\ncategory: toolchain\nauthor: tovimx\nlicense: MIT\nprogressive_disclosure:\n  entry_point:\n    summary: \"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification\"\n    when_to_use: \"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps\"\n    quick_start: \"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test\"\n  token_estimate:\n    entry: 150\n    full: 9000\ncontext_limit: 900\ntags:\n  - react-native\n  - expo\n  - testing\n  - maestro\n  - e2e\n  - mobile\n  - ios\n  - android\n  - yaml\n  - ci-cd\n  - mcp\nrequires_tools: []\n---\n\n# Maestro Mobile E2E Testing\n\n## Overview\n\nMaestro is a declarative YAML-based mobile E2E testing framework. It provides automatic waiting, built-in retry logic, and fast execution without boilerplate. It's more stable than Detox or Appium for React Native apps.\n\n### Key Features\n\n- **Declarative YAML** — no imperative test code, just steps\n- **Automatic waiting** — no manual `sleep()` or flaky waits\n- **Built-in retry** — reduces test flakiness\n- **Fast execution** — runs quickly without setup overhead\n- **Maestro Studio** — interactive test builder (`maestro studio`)\n- **Sub-flows** — reusable YAML sequences for DRY tests\n- **JavaScript scripting** — GraalJS runtime for HTTP calls and data manipulation\n- **Maestro Cloud** — real device testing in CI without local simulators\n\n## Quick Start\n\n### Install\n\n```bash\ncurl -Ls \"https://get.maestro.mobile.dev\" | bash\nbrew install openjdk@17\nexport JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home\n```\n\n### Minimal test\n\n```yaml\nappId: com.myapp\n---\n- launchApp\n- tapOn:\n    id: \"my-button\"\n- assertVisible: \"Expected Text\"\n```\n\n### Run\n\n```bash\nmaestro test .maestro/smoke-test.yaml\nmaestro test --debug .maestro/smoke-test.yaml  # step through\nmaestro studio                                  # interactive builder\n```\n\n---\n\n## Core Patterns\n\n### 1. Selector Strategy: testID vs Text\n\nChoose your selector approach based on project context. Both are valid — the right choice depends on whether your app is localized and your team's testing philosophy.\n\n| Context | Recommended Selector | Rationale |\n|---------|---------------------|-----------|\n| **Multi-language / i18n** | `id:` (testID) | Stable across translations |\n| **Single language** | Text labels | Human-readable, self-documenting tests |\n| **Agent-maintained tests** | Either — ask the developer | Readability matters less for AI-maintained flows |\n| **System dialogs** | Text (always) | No testID possible on native alerts |\n\n```yaml\n# testID selector — stable across translations\n- tapOn:\n    id: \"submit-button\"\n\n# Text selector — human-readable, self-documenting\n- tapOn: \"Submit\"\n```\n\n**When to prefer testIDs:**\n- App supports multiple languages or will be translated\n- UI text is dynamic or frequently changes\n- Multiple elements share the same visible text\n\n**When to prefer text selectors:**\n- Single-language app with stable copy\n- Readability and self-documentation are a priority\n- Testing user-visible behavior exactly as it appears\n\nIn React Native, add `testID` props when using ID-based selectors:\n\n```tsx\n<TouchableOpacity testID=\"submit-button\" onPress={handleSubmit}>\n  <Text>{t('submit')}</Text>\n</TouchableOpacity>\n```\n\n### testID Naming Convention\n\nWhen using ID-based selectors:\n\n```\n{component}-{action/type}[-{variant}]\n\nExamples:\n- auth-prompt-login-button\n- product-card-{id}\n- otp-input-0\n- tab-home\n- dashboard-loading\n```\n\n### 2. Auth Pre-Flight Pattern\n\nPrevent race conditions where Maestro interacts with the UI before auth state resolves. Add a zero-size `auth-loaded` marker that only renders when auth loading completes:\n\n```tsx\n// In your tab bar or root layout\n{!isLoading && <View testID=\"auth-loaded\" style={{ width: 0, height: 0 }} />}\n```\n\nThen in every test:\n\n```yaml\n- launchApp\n\n# Prevent XCTest crash on cold boot (iOS)\n- swipe:\n    direction: DOWN\n    duration: 100\n\n# Wait for auth state to resolve\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n# Now safe to interact\n- tapOn:\n    id: \"tab-home\"\n```\n\n### 3. Adaptive Tests (Handle Both Auth States)\n\nTests should work regardless of whether the user is authenticated:\n\n```yaml\n# Auth flow — only runs if login prompt is visible\n- runFlow:\n    when:\n      visible: \"Sign In\"\n    file: flows/auth-flow.yaml\n\n# Already authenticated — proceed directly\n- runFlow:\n    when:\n      visible:\n        id: \"tab-home\"\n    file: flows/authenticated-action.yaml\n```\n\n### 4. Testing Optimistic Updates\n\nUse short timeouts to verify UI changes happen before server response:\n\n```yaml\n# Trigger mutation\n- tapOn:\n    id: \"action-button\"\n\n# OPTIMISTIC: UI must change within 3s (not waiting for server)\n- extendedWaitUntil:\n    visible:\n      id: \"undo-button\"\n    timeout: 3000\n\n# Verify derived UI state\n- extendedWaitUntil:\n    visible:\n      id: \"user-indicator\"\n    timeout: 5000\n```\n\n| Action | Expected Change | Timeout |\n|--------|----------------|---------|\n| Mutation trigger | Button state flips | < 3s |\n| List update | Item appears/disappears | < 5s |\n| Re-do action | Proves persistence | < 3s |\n\n### 5. Dismissing Native Alerts\n\nReact Native `Alert.alert()` creates native dialogs that block the UI:\n\n```yaml\n- tapOn:\n    id: \"action-button\"\n\n# Wait for expected state change first\n- extendedWaitUntil:\n    visible:\n      id: \"new-state-element\"\n    timeout: 5000\n\n# Dismiss alert (optional in case it already closed)\n- tapOn:\n    text: \"OK\"\n    optional: true\n\n# Brief delay for alert animation\n- swipe:\n    direction: DOWN\n    duration: 300\n```\n\n### 6. Sub-Flows for Reusability\n\nBreak repeated sequences into sub-flow files:\n\n```\n.maestro/\n├── flows/\n│   ├── auth-and-return.yaml\n│   ├── complete-purchase.yaml\n│   └── verify-result.yaml\n├── smoke-test.yaml\n└── feature-test.yaml\n```\n\n```yaml\n# In main test\n- runFlow:\n    file: flows/auth-and-return.yaml\n```\n\n### 7. Deep Links (Expo)\n\nUse the Expo scheme from `app.json`, not the bundle ID:\n\n```yaml\n# WRONG\n- openLink: \"com.myapp://profile/settings\"\n\n# CORRECT\n- openLink: \"myapp://profile/settings\"\n```\n\nDeep links must be registered in your app's deep link handler. Unregistered routes silently fail.\n\n### 8. Platform-Specific Logic\n\n```yaml\n- runFlow:\n    when:\n      platform: ios\n    file: flows/ios-specific.yaml\n\n- runFlow:\n    when:\n      platform: android\n    file: flows/android-specific.yaml\n```\n\n### 9. Environment Variables\n\n```yaml\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-test@example.com\n  API_BASE_URL: http://localhost:3000\n---\n- inputText: ${TEST_EMAIL}\n```\n\n### 10. Selector State Properties\n\nUse `enabled`, `selected`, `checked`, and `focused` to target elements by their current state. This is useful for validating interactive element states before or after actions.\n\n```yaml\n# Only tap the submit button if it's enabled\n- tapOn:\n    id: \"submit-button\"\n    enabled: true\n\n# Assert a checkbox is checked\n- assertVisible:\n    id: \"terms-checkbox\"\n    checked: true\n\n# Wait for an input to be focused\n- extendedWaitUntil:\n    visible:\n      id: \"email-input\"\n      focused: true\n    timeout: 3000\n```\n\n| Property | Values | Use Case |\n|----------|--------|----------|\n| `enabled` | `true` / `false` | Buttons that disable during submission or until form is valid |\n| `checked` | `true` / `false` | Checkboxes, toggle switches |\n| `selected` | `true` / `false` | Tab items, segmented controls |\n| `focused` | `true` / `false` | Input fields with auto-focus |\n\n### 11. Relative Position Selectors\n\nDistinguish between similar elements by their spatial relationship to other elements. This is more idiomatic and resilient than index-based selection.\n\n```yaml\n# BAD — fragile, breaks if order changes\n- tapOn:\n    text: \"Add to Basket\"\n    index: 1\n\n# GOOD — contextual, self-documenting\n- tapOn:\n    text: \"Add to Basket\"\n    below:\n      text: \"Awesome Shoes\"\n```\n\nAvailable relative selectors:\n\n```yaml\n# Target element below another\n- tapOn:\n    text: \"Buy Now\"\n    below: \"Product Title\"\n\n# Target element that is a child of a parent\n- tapOn:\n    text: \"Delete\"\n    childOf:\n      id: \"item-card-42\"\n\n# Target a parent that contains a specific child\n- tapOn:\n    containsChild: \"Urgent\"\n\n# Target by multiple descendants\n- tapOn:\n    containsDescendants:\n      - id: title_id\n        text: \"Specific Title\"\n      - \"Another descendant text\"\n\n# Horizontal positioning\n- tapOn:\n    text: \"Edit\"\n    rightOf: \"Username\"\n```\n\n| Selector | Meaning |\n|----------|---------|\n| `below:` | Element is positioned below the referenced element |\n| `above:` | Element is positioned above the referenced element |\n| `leftOf:` | Element is to the left of the referenced element |\n| `rightOf:` | Element is to the right of the referenced element |\n| `childOf:` | Element is a direct child of the referenced parent |\n| `containsChild:` | Element contains a direct child matching the reference |\n| `containsDescendants:` | Element contains all specified descendant elements |\n\n---\n\n## Authentication Testing\n\n### Architecture\n\nTesting OTP or magic-link authentication in E2E requires capturing emails programmatically. The general pattern:\n\n```\n┌─────────────┐     ┌──────────────┐     ┌─────────────────┐\n│  Maestro    │────▶│  Auth        │────▶│  Email Capture  │\n│  Test       │     │  Provider    │     │  Service        │\n└─────────────┘     └──────────────┘     └─────────────────┘\n       │                                        │\n       │         ┌──────────────────────────────┘\n       │         ▼\n       │   ┌─────────────┐\n       └──▶│  REST API   │ ─── GET /api/v1/messages\n           │  (email)    │ ─── Extract OTP code\n           └─────────────┘\n```\n\nCommon email capture services: [Mailpit](https://github.com/axllent/mailpit), [MailHog](https://github.com/mailhog/MailHog), [Ethereal](https://ethereal.email/).\n\n### OTP Fetch Script\n\nFetch OTP codes from your email capture service using Maestro's GraalJS runtime:\n\n```javascript\n// CRITICAL: Maestro uses GraalJS — NO async/await, NO fetch()\nvar email = typeof EMAIL !== \"undefined\" ? EMAIL : \"test@example.com\";\nvar emailServiceUrl = typeof EMAIL_SERVICE_URL !== \"undefined\"\n  ? EMAIL_SERVICE_URL : \"http://localhost:8025\";\n\nvar response = http.get(emailServiceUrl + \"/api/v1/messages\");\nif (!response.ok) {\n  throw new Error(\"Failed to fetch emails: \" + response.status);\n}\n\nvar data = json(response.body);\n// Find the latest email and extract OTP code\nvar body = data.messages[0].Content.Body;\nvar match = body.match(/(\\d{6})/);\noutput.OTP_CODE = match[1];\n```\n\n### OTP Input Strategy\n\nOTP components with auto-focus need individual digit entry. Tap each input before typing:\n\n```yaml\n# Split OTP into digits via helper script\n- runScript:\n    file: scripts/split-otp.js\n    env:\n      OTP_CODE: ${output.OTP_CODE}\n\n# Enter each digit by tapping its input\n- tapOn:\n    id: \"otp-input-0\"\n- inputText: ${output.OTP_0}\n\n- tapOn:\n    id: \"otp-input-1\"\n- inputText: ${output.OTP_1}\n# ... repeat for all digits\n```\n\n> For provider-specific implementations (Supabase + Mailpit, Firebase Auth, Auth0), create a project-level skill that extends this one.\n\n---\n\n## GraalJS Script Rules\n\nMaestro uses the GraalJS runtime. These constraints are non-negotiable:\n\n| Feature | Status |\n|---------|--------|\n| `async/await` | **NOT supported** |\n| `fetch()` | **NOT supported** |\n| `http.get()`, `http.post()` | Use these instead |\n| `json()` | Use to parse response bodies |\n| `output.VAR` | Set variables for use in YAML flow |\n| `var` declarations | Required (use `var`, not `const`/`let` for safety) |\n\n```javascript\n// Script template\nvar response = http.get(\"http://localhost:8025/api/endpoint\");\nif (!response.ok) {\n  throw new Error(\"Request failed: \" + response.status);\n}\nvar data = json(response.body);\noutput.RESULT = data.value;\n```\n\n---\n\n## Critical Gotchas\n\n### clearState Does NOT Clear iOS Keychain\n\n`clearState: true` clears the app sandbox (UserDefaults, files, caches) but does **NOT** clear the iOS Keychain. Auth tokens stored via `expo-secure-store` (or any Keychain-based storage) persist across `clearState` resets and even app reinstalls.\n\n```yaml\n# WRONG — user may still be authenticated\n- launchApp:\n    clearState: true\n- assertVisible: \"Welcome\"  # Fails if Keychain has tokens\n\n# CORRECT — wait for auth resolution, then adapt\n- launchApp\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n```\n\n**Rules:**\n- Never rely on `clearState` to produce guest state on iOS\n- For auth tests: skip `clearState`, use `auth-loaded` pre-flight\n- For guest tests: use adaptive flows that handle both states\n- Never assert guest-only UI after `clearState`\n\n**Note:** On Android, `clearState: true` fully resets app data including credentials. This is an iOS-only gotcha.\n\n### XCTest kAXErrorInvalidUIElement Crash (iOS)\n\nThe XCTest driver may crash if Maestro interacts with the accessibility tree before the first render cycle completes on cold boot.\n\n**Fix:** Add a no-op swipe immediately after `launchApp`:\n\n```yaml\n- launchApp\n- swipe:\n    direction: DOWN\n    duration: 100\n```\n\n### API Server Dependency\n\nMobile apps calling backend APIs on localhost need either the full server or a mock server running. Without it, all API-dependent screens show loading spinners or empty states (queries fail silently).\n\n**Fix:** Start a mock API server before running Maestro tests:\n\n```bash\n# Start mock server (serves canned responses on your API port)\nnpx tsx scripts/mock-api-server.ts &\n\n# Then run tests\nmaestro test .maestro/my-test.yaml\n```\n\nCreate a lightweight mock that returns canned JSON for each endpoint your app calls. This is faster and more deterministic than running your full backend.\n\n### Auth-Aware Tab Bars\n\nTab bars that show different tabs for guest vs authenticated users will cause selector failures:\n\n| State | Typical Tabs |\n|-------|-------------|\n| Guest | home, search, cart, profile |\n| Auth | home, feed, create, messages, profile |\n\nOnly assert tabs that exist in both states, or use adaptive `when:` conditions.\n\n---\n\n## Test File Template\n\n```yaml\n# {Feature} {Action} Test\n#\n# Tests: {what this validates}\n# Prerequisites:\n# - Simulator/emulator running with app installed\n# - Backend or mock server running (if API-dependent)\n\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-{feature}@example.com\n  EMAIL_SERVICE_URL: http://localhost:8025\n---\n# ==========================================\n# STEP 1: LAUNCH + AUTH PRE-FLIGHT\n# ==========================================\n\n- launchApp\n\n- swipe:\n    direction: DOWN\n    duration: 100\n\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n- takeScreenshot: 01-initial-state\n\n# ==========================================\n# STEP 2: {ACTION}\n# ==========================================\n\n- tapOn:\n    id: \"target-element\"\n\n# ==========================================\n# STEP 3: VERIFY\n# ==========================================\n\n- extendedWaitUntil:\n    visible:\n      id: \"expected-result\"\n    timeout: 5000\n\n- takeScreenshot: 02-final-state\n```\n\n---\n\n## Folder Structure\n\n```\n.maestro/\n├── README.md                    # Quick reference + testID inventory\n├── config.yaml                  # Shared configuration\n├── flows/                       # Reusable sub-flows\n│   ├── auth-and-return.yaml\n│   ├── complete-action.yaml\n│   └── verify-result.yaml\n├── scripts/                     # GraalJS helpers\n│   ├── fetch-otp.js\n│   └── split-otp.js\n├── smoke-test.yaml              # Guest navigation\n├── auth-signin.yaml             # OTP sign-in flow\n├── feature-screenshots.yaml     # Screenshot capture flows\n└── feature-action.yaml          # Feature-specific tests\n\nscripts/\n├── mock-api-server.ts           # Lightweight mock for E2E\n└── run-e2e.sh                   # Orchestration script\n```\n\n### Naming Conventions\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Main test | `{feature}-{action}.yaml` | `checkout-purchase.yaml` |\n| Sub-flow | `{action}-{context}.yaml` | `auth-and-return-to-dashboard.yaml` |\n| Script | `{verb}-{noun}.js` | `fetch-otp.js` |\n\n---\n\n## Infrastructure\n\n### Mock Server for API-Dependent Tests\n\nCreate a lightweight mock server that serves canned responses for your API layer. This is faster and more deterministic than running your full backend during E2E tests.\n\n```bash\n# Start mock server before running Maestro tests\nnpx tsx scripts/mock-api-server.ts &\n```\n\n### Orchestration Script\n\nAutomate the full E2E setup with a shell script that:\n1. Starts backend services (database, auth)\n2. Seeds test data\n3. Starts mock API server\n4. Runs Maestro tests\n5. Cleans up all services\n\n```bash\nbash scripts/run-e2e.sh\n```\n\n### Seed Data\n\nTests that depend on specific data require seeded databases. Keep seed scripts alongside your test infrastructure and run them before each test suite.\n\n---\n\n## Android-Specific Patterns\n\n### Emulator Setup\n\nAndroid tests require an emulator or a USB-connected physical device. Maestro auto-detects connected devices.\n\n```bash\n# List available system images\nsdkmanager --list | grep system-images\n\n# Create emulator\navdmanager create avd -n maestro_test \\\n  -k \"system-images;android-34;google_apis;arm64-v8a\"\n\n# Start emulator\nemulator -avd maestro_test\n```\n\n### iOS vs Android Differences\n\n| Aspect | iOS | Android |\n|--------|-----|---------|\n| Device type | Simulator only (no physical) | Emulator + physical via ADB |\n| `clearState` | Does NOT clear Keychain | Fully resets app data |\n| Cold boot crash | XCTest kAXError (add swipe delay) | No equivalent issue |\n| Performance | Runs natively (fast) | ARM emulation (slower on x86) |\n| Permission dialogs | System alerts | System dialogs with different text |\n\n### ADB Debugging\n\n```bash\nadb devices                                    # List connected devices\nadb shell am start -n com.myapp/.MainActivity  # Launch app\nadb logcat | grep Maestro                      # Filter Maestro logs\nadb shell input keyevent 82                    # Unlock screen\n```\n\n### Android Permission Handling\n\nAndroid permissions appear as system dialogs. Dismiss with optional taps:\n\n```yaml\n- tapOn:\n    text: \"Allow\"\n    optional: true\n\n- tapOn:\n    text: \"While using the app\"\n    optional: true\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions with Maestro Cloud\n\nMaestro Cloud provides real devices in CI without local simulators. Use the official action:\n\n```yaml\nname: Mobile E2E Tests\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  maestro-e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # Build Android APK\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '17'\n          distribution: 'temurin'\n\n      - name: Build Android APK\n        run: |\n          cd apps/mobile\n          npx expo prebuild --platform android --no-install\n          cd android && ./gradlew assembleRelease\n\n      - name: Run Maestro Cloud Tests\n        id: maestro\n        uses: mobile-dev-inc/action-maestro-cloud@v2\n        with:\n          api-key: ${{ secrets.MAESTRO_API_KEY }}\n          app-file: apps/mobile/android/app/build/outputs/apk/release/app-release.apk\n          workspace: .maestro\n          include-tags: ci\n\n      # Access results\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_CONSOLE_URL }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}\n```\n\n### Tag-Based Flow Filtering\n\nUse tags to control which tests run in CI vs locally:\n\n```yaml\n# In your flow file header\nappId: com.myapp\ntags:\n  - ci\n  - smoke\n---\n- launchApp\n# ... test steps\n```\n\n```bash\n# Run only CI-tagged flows locally\nmaestro test --include-tags ci .maestro/\n\n# Exclude work-in-progress flows\nmaestro test --exclude-tags wip .maestro/\n```\n\n### Local CI with Docker (Android Only)\n\n```dockerfile\nFROM openjdk:17-slim\n\nRUN curl -Ls \"https://get.maestro.mobile.dev\" | bash\nENV PATH=\"/root/.maestro/bin:${PATH}\"\n\nCOPY .maestro/ /app/.maestro/\nWORKDIR /app\n\nCMD [\"maestro\", \"test\", \".maestro/\"]\n```\n\n**Note:** iOS tests cannot run in Docker (requires macOS). Use Maestro Cloud for iOS in CI.\n\n---\n\n## Maestro Cloud\n\n[Maestro Cloud](https://cloud.maestro.dev/) runs tests on real devices without local simulator setup.\n\n### Setup\n\n1. Create account at cloud.maestro.dev\n2. Generate API key from dashboard\n3. Store as `MAESTRO_API_KEY` secret in your CI provider\n\n### Running from CLI\n\n```bash\n# Upload and run on Maestro Cloud\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  .maestro/\n\n# With tag filtering\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  --include-tags smoke \\\n  .maestro/\n```\n\n### Key Points\n\n- **iOS testing**: Supported on Maestro Cloud (not on local physical devices)\n- **Android testing**: Both local physical devices and Maestro Cloud\n- **Results**: Dashboard with video recordings, logs, and screenshots\n- **CI outputs**: `MAESTRO_CLOUD_CONSOLE_URL`, `MAESTRO_CLOUD_FLOW_RESULTS`\n\n---\n\n## Maestro MCP Server\n\nThe [Maestro MCP server](https://docs.maestro.dev/getting-started/maestro-mcp) exposes Maestro's full command set as Model Context Protocol tools, letting AI agents **execute tests and interact with devices directly** — not just write YAML.\n\n### How It Complements This Skill\n\n| | **This Skill** | **Maestro MCP** |\n|---|---|---|\n| **Role** | Teaches correct patterns | Provides runtime execution |\n| **Layer** | Authoring (write good YAML) | Execution (run, tap, assert, screenshot) |\n| **Output** | Better test files | Live device interaction |\n\nUse both together: this skill ensures the AI writes correct tests; the MCP lets it run them immediately, see failures, and iterate.\n\n### Setup\n\nThe MCP ships with the Maestro CLI — no extra install needed:\n\n```bash\n# Verify it's available\nmaestro mcp\n```\n\n**Claude Code** — add to project `.mcp.json` or global settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\n**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nAlso supported on Cursor, Windsurf, VS Code, and JetBrains IDEs. See the [Maestro MCP docs](https://docs.maestro.dev/getting-started/maestro-mcp) for IDE-specific setup.\n\n### Key Capabilities\n\nThe MCP server exposes 47 tools organized by category:\n\n| Category | Tools | Examples |\n|----------|-------|---------|\n| UI Interaction | tap, swipe, scroll, long press | `tapOn`, `scrollUntilVisible` |\n| Text Input | type, erase, paste, copy | `inputText`, `eraseText` |\n| Assertions | visibility, AI-powered | `assertVisible`, `assertWithAI` |\n| App Lifecycle | launch, stop, clear state | `launchApp`, `clearState` |\n| Device Control | location, orientation, airplane | `setLocation`, `hideKeyboard` |\n| Flow Control | run flows, repeat, eval scripts | `runFlow`, `evalScript` |\n| Media | screenshots, recording | `takeScreenshot`, `startRecording` |\n| AI-Powered | visual assertions, defect detection | `assertWithAI`, `assertNoDefectsWithAI` |\n\n### Write-Run-Fix Loop\n\nWith both this skill and the MCP active, the AI can:\n\n1. **Write** a test YAML using patterns from this skill\n2. **Run** it via MCP's `runFlow` / `launchApp` + interaction tools\n3. **See** failures via screenshots and assertions\n4. **Fix** the YAML and re-run — all in one conversation\n\n---\n\n## Debugging\n\n```bash\nmaestro test --debug .maestro/test.yaml   # Step through interactively\nmaestro record .maestro/test.yaml          # Record as video\nmaestro studio                             # Interactive UI builder\nmaestro hierarchy                          # View element tree\n```\n\nScreenshots saved to `~/.maestro/tests/{timestamp}/`.\n\n---\n\n## Checklist for New Tests\n\n```\n[ ] Unique test email (maestro-{feature}@example.com)\n[ ] Selector strategy chosen (testID for i18n apps, text for single-language — see Pattern 1)\n[ ] Selectors use state properties where relevant (enabled, checked — see Pattern 10)\n[ ] Similar elements distinguished with relative selectors, not index (see Pattern 11)\n[ ] Auth pre-flight pattern used (auth-loaded)\n[ ] Post-launch swipe added (iOS crash prevention)\n[ ] Both auth states handled (adaptive flows)\n[ ] Native alerts dismissed after mutations\n[ ] Short timeouts for optimistic updates (3-5s)\n[ ] Sub-flows created for reusable sequences\n[ ] Descriptive screenshots at key points\n[ ] Header comment with prerequisites\n[ ] Added to README.md test table\n[ ] Mock API server started if backend-dependent\n[ ] Tags added for CI filtering (ci, smoke, wip)\n```\n\n---\n\n## Common Errors\n\n| Error | Cause | Fix |\n|-------|-------|-----|\n| \"Unable to locate Java Runtime\" | Java not in PATH | `export JAVA_HOME=/opt/homebrew/opt/openjdk@17/...` |\n| \"Element not found\" after tap | Native alert blocking | Add `tapOn: text: \"OK\" optional: true` |\n| OTP digits not entering | Auto-focus interference | Use individual `otp-input-N` testIDs |\n| Test passes but nothing happened | `optional: true` misused | Only use optional for truly optional actions |\n| \"Assertion is false\" on visibility | Element not rendered yet | Increase timeout or verify testID exists |\n| Script output empty | Wrong JS API | Use `http.get()` not `fetch()` |\n| Auth state inconsistent after clearState | iOS Keychain not cleared | Don't use `clearState`, use adaptive flows |\n| kAXErrorInvalidUIElement crash | Cold boot race (iOS) | Add post-launch swipe delay |\n| Loading spinners / empty screens | No API server running | Start mock API server before tests |\n| Permission dialog blocking (Android) | System dialog not dismissed | Add `tapOn: text: \"Allow\" optional: true` |\n\n---\n\n## Resources\n\n- [Maestro Documentation](https://docs.maestro.dev/)\n- [Maestro Selectors Reference](https://docs.maestro.dev/api-reference/selectors)\n- [Maestro Cloud](https://cloud.maestro.dev/)\n- [Maestro MCP Server](https://docs.maestro.dev/getting-started/maestro-mcp)\n- [Maestro GitHub](https://github.com/mobile-dev-inc/Maestro)\n- [GraalJS HTTP Requests](https://docs.maestro.dev/advanced/javascript/make-http-s-requests)\n- [Conditions & Adaptive Flows](https://docs.maestro.dev/advanced/conditions)\n- [GitHub Actions Integration](https://docs.maestro.dev/cloud/ci-integration/github-actions)\n- [BrowserStack: Maestro Testing Guide](https://www.browserstack.com/guide/maestro-testing)\n- [DEV.to: Tips & Tricks for Maestro + React Native](https://dev.to/retyui/best-tips-tricks-for-e2e-maestro-with-react-native-2kaa)\n",".kilocode/skills/flutter-expert/flutter-expert.md":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n",".kilocode/skills/flutter-expert/references/bloc-state.md":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n",".kilocode/skills/flutter-expert/references/gorouter-navigation.md":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n",".kilocode/skills/flutter-expert/references/performance.md":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n",".kilocode/skills/flutter-expert/references/project-structure.md":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n",".kilocode/skills/flutter-expert/references/riverpod-state.md":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n",".kilocode/skills/flutter-expert/references/widget-patterns.md":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n","CLAUDE.md":"# CLAUDE.md\n\nGuidance for Claude Code when working in this repository.\n\n## Mandatory skills: apply to EVERY prompt\n\nThese rules apply to every user prompt in this repo, including simple questions,\none-line fixes, and follow-ups. Do them before any other response or tool call.\n\n1. **`/superpowers:using-superpowers`**: invoke it first on every prompt (if not\n   already loaded this session). Then check each available superpowers skill\n   and invoke every one that could apply, for example:\n   - new feature or behavior change: `superpowers:brainstorming`, then `superpowers:writing-plans`\n   - bug, test failure, unexpected behavior: `superpowers:systematic-debugging`\n   - writing code: `superpowers:test-driven-development`\n   - before claiming \"done\" or committing: `superpowers:verification-before-completion`\n2. **`/caveman:caveman`**: keep caveman mode active for every reply (default\n   level `full`). Chat replies stay terse. Code, comments, commit messages, PR\n   text, docs, and memory files stay normal prose. Only turn caveman off when\n   the user says \"stop caveman\" or \"normal mode\".\n3. **`/graphify`**: invoke the `graphify` skill for every prompt about code,\n   architecture, file relationships, or project content.\n   - If `graphify-out/` exists, query the graph first, before grep or file reads.\n   - If `graphify-out/` does not exist, build it with `/graphify` first.\n   - After significant code changes, update the graph so it stays current.\n\nOrder per prompt: `using-superpowers`, then `caveman`, then `graphify`, then the\ntask-specific skills (for example `flutter-expert`), then the work itself.\n\nDo not skip these because a task \"seems simple\". Only skip a rule when the user\nexplicitly says so for that prompt.\n\n## Owner rules and backend-blocked list\n\nRead `plan.md` at the repo root before any task. Key rules: use only the\nBagisto **GraphQL** API (never the REST `/api/shop/...` endpoints), use only\nlive API data (no hardcoded values), validate every query against the\nconfigured server, and never commit or push without the owner's permission.\n`plan.md` also lists features that are blocked because the GraphQL schema\nlacks the field.\n\n## Project\n\nBagisto open-source e-commerce mobile app. Flutter (Dart SDK ^3.10.8), package\nname `bagisto_flutter`, talks to a Bagisto backend over GraphQL.\n\n## Commands\n\n```bash\nflutter pub get\nflutter run\nflutter analyze\nflutter test                       # all tests\nflutter test test/orders_bloc_test.dart\nflutter gen-l10n                   # after editing any lib/l10n/app_*.arb\n```\n\nMaestro E2E flows live in `maestro/` (see `maestro/LABEL_BASED_INSTRUCTIONS.md`).\n\n## Layout\n\n- `lib/core/`: shared code (`graphql/`, `theme/`, `error/`, `locale/`, `currency/`, `widgets/`, ...)\n- `lib/core/graphql/`: raw GraphQL query/mutation strings in static-only classes (`AccountQueries`, `ReturnsQueries`, ...)\n- `lib/features/<feature>/`: `account`, `auth`, `cart`, `category`, `checkout`, `home`, `product`, `search`, `splash`\n- `lib/l10n/`: 10 `app_*.arb` files plus generated `app_localizations*.dart` (committed)\n- `Docs/`: setup guides; design specs under `Docs/superpowers/specs/`\n- `Docs/ai-memory/`: notes for AI agents on past fix rounds (root causes,\n  gotchas, backend-blocked issues). Read the relevant note before touching\n  those areas, and add a new dated note after a large fix round.\n- `test/`: flat directory of unit and widget tests\n\n## House style\n\n- **State**: `flutter_bloc` + `equatable`. Events, states, and bloc live in ONE\n  file per bloc under `lib/features/<feature>/presentation/bloc/`.\n- **Models**: hand-written defensive `fromJson`. No codegen, no `json_serializable`.\n- **GraphQL**: `graphql_flutter`. Repositories call the client directly (no\n  datasource layer) and throw feature exceptions built with\n  `ErrorMapper.getUserMessage`. Single-entity Bagisto queries need IRI ids\n  (for example `/api/shop/customer-orders/{id}`).\n- **Pagination**: cursor (Relay). Repositories return records\n  `({list, totalCount, hasNextPage, endCursor})`.\n- **Navigation**: no route table or GoRouter. Use imperative\n  `Navigator.push(MaterialPageRoute(...))` with `BlocProvider` created inline.\n  Detail pages expose `static navigate(...)`.\n- **l10n**: every user-facing string goes into all 10 arb files. Key format is\n  camelCase `<feature><Thing>`. Placeholder metadata only in `app_en.arb`.\n  Run `flutter gen-l10n` and commit the generated files.\n- **UI**: widgets branch on `Theme.of(context).brightness` for dark mode.\n  Primary color `AppColors.primary500` (#FF6900). Cards: radius 10, padding 12.\n  Primary buttons: height 48, radius 54.\n- **Tests**: fake repositories subclass the real repository with a dummy\n  `GraphQLClient`. Seed bloc state through a `seedState` subclass\n  (see `test/orders_bloc_test.dart`).\n\n## Known baseline\n\n- `flutter analyze` already reports about 49 info/warnings in category, home,\n  and search files. Do not treat these as regressions.\n- `test/address_debug_payload_test.dart` fails on a clean tree.\n- Image upload for returns/messages is REST multipart only, not in GraphQL.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nGuidance for Claude Code when working in this repository.\n\n## Mandatory skills: apply to EVERY prompt\n\nThese rules apply to every user prompt in this repo, including simple questions,\none-line fixes, and follow-ups. Do them before any other response or tool call.\n\n1. **`/superpowers:using-superpowers`**: invoke it first on every prompt (if not\n   already loaded this session). Then check each available superpowers skill\n   and invoke every one that could apply, for example:\n   - new feature or behavior change: `superpowers:brainstorming`, then `superpowers:writing-plans`\n   - bug, test failure, unexpected behavior: `superpowers:systematic-debugging`\n   - writing code: `superpowers:test-driven-development`\n   - before claiming \"done\" or committing: `superpowers:verification-before-completion`\n2. **`/caveman:caveman`**: keep caveman mode active for every reply (default\n   level `full`). Chat replies stay terse. Code, comments, commit messages, PR\n   text, docs, and memory files stay normal prose. Only turn caveman off when\n   the user says \"stop caveman\" or \"normal mode\".\n3. **`/graphify`**: invoke the `graphify` skill for every prompt about code,\n   architecture, file relationships, or project content.\n   - If `graphify-out/` exists, query the graph first, before grep or file reads.\n   - If `graphify-out/` does not exist, build it with `/graphify` first.\n   - After significant code changes, update the graph so it stays current.\n\nOrder per prompt: `using-superpowers`, then `caveman`, then `graphify`, then the\ntask-specific skills (for example `flutter-expert`), then the work itself.\n\nDo not skip these because a task \"seems simple\". Only skip a rule when the user\nexplicitly says so for that prompt.\n\n## Owner rules and backend-blocked list\n\nRead `plan.md` at the repo root before any task. Key rules: use only the\nBagisto **GraphQL** API (never the REST `/api/shop/...` endpoints), use only\nlive API data (no hardcoded values), validate every query against the\nconfigured server, and never commit or push without the owner's permission.\n`plan.md` also lists features that are blocked because the GraphQL schema\nlacks the field.\n\n## Project\n\nBagisto open-source e-commerce mobile app. Flutter (Dart SDK ^3.10.8), package\nname `bagisto_flutter`, talks to a Bagisto backend over GraphQL.\n\n## Commands\n\n```bash\nflutter pub get\nflutter run\nflutter analyze\nflutter test                       # all tests\nflutter test test/orders_bloc_test.dart\nflutter gen-l10n                   # after editing any lib/l10n/app_*.arb\n```\n\nMaestro E2E flows live in `maestro/` (see `maestro/LABEL_BASED_INSTRUCTIONS.md`).\n\n## Layout\n\n- `lib/core/`: shared code (`graphql/`, `theme/`, `error/`, `locale/`, `currency/`, `widgets/`, ...)\n- `lib/core/graphql/`: raw GraphQL query/mutation strings in static-only classes (`AccountQueries`, `ReturnsQueries`, ...)\n- `lib/features/<feature>/`: `account`, `auth`, `cart`, `category`, `checkout`, `home`, `product`, `search`, `splash`\n- `lib/l10n/`: 10 `app_*.arb` files plus generated `app_localizations*.dart` (committed)\n- `Docs/`: setup guides; design specs under `Docs/superpowers/specs/`\n- `Docs/ai-memory/`: notes for AI agents on past fix rounds (root causes,\n  gotchas, backend-blocked issues). Read the relevant note before touching\n  those areas, and add a new dated note after a large fix round.\n- `test/`: flat directory of unit and widget tests\n\n## House style\n\n- **State**: `flutter_bloc` + `equatable`. Events, states, and bloc live in ONE\n  file per bloc under `lib/features/<feature>/presentation/bloc/`.\n- **Models**: hand-written defensive `fromJson`. No codegen, no `json_serializable`.\n- **GraphQL**: `graphql_flutter`. Repositories call the client directly (no\n  datasource layer) and throw feature exceptions built with\n  `ErrorMapper.getUserMessage`. Single-entity Bagisto queries need IRI ids\n  (for example `/api/shop/customer-orders/{id}`).\n- **Pagination**: cursor (Relay). Repositories return records\n  `({list, totalCount, hasNextPage, endCursor})`.\n- **Navigation**: no route table or GoRouter. Use imperative\n  `Navigator.push(MaterialPageRoute(...))` with `BlocProvider` created inline.\n  Detail pages expose `static navigate(...)`.\n- **l10n**: every user-facing string goes into all 10 arb files. Key format is\n  camelCase `<feature><Thing>`. Placeholder metadata only in `app_en.arb`.\n  Run `flutter gen-l10n` and commit the generated files.\n- **UI**: widgets branch on `Theme.of(context).brightness` for dark mode.\n  Primary color `AppColors.primary500` (#FF6900). Cards: radius 10, padding 12.\n  Primary buttons: height 48, radius 54.\n- **Tests**: fake repositories subclass the real repository with a dummy\n  `GraphQLClient`. Seed bloc state through a `seedState` subclass\n  (see `test/orders_bloc_test.dart`).\n\n## Known baseline\n\n- `flutter analyze` already reports about 49 info/warnings in category, home,\n  and search files. Do not treat these as regressions.\n- `test/address_debug_payload_test.dart` fails on a clean tree.\n- Image upload for returns/messages is REST multipart only, not in GraphQL.\n","isInternal":false,"tokens":1254,"sizeBytes":5099},{"name":"bloc-state.md","path":".agents/skills/flutter-expert/references/bloc-state.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/bloc-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n","isInternal":false,"tokens":1153,"sizeBytes":4836},{"name":"gorouter-navigation.md","path":".agents/skills/flutter-expert/references/gorouter-navigation.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/gorouter-navigation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n","isInternal":false,"tokens":630,"sizeBytes":2615},{"name":"performance.md","path":".agents/skills/flutter-expert/references/performance.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/performance.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n","isInternal":false,"tokens":494,"sizeBytes":2043},{"name":"project-structure.md","path":".agents/skills/flutter-expert/references/project-structure.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/project-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n","isInternal":false,"tokens":735,"sizeBytes":2775},{"name":"riverpod-state.md","path":".agents/skills/flutter-expert/references/riverpod-state.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/riverpod-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n","isInternal":false,"tokens":720,"sizeBytes":3102},{"name":"widget-patterns.md","path":".agents/skills/flutter-expert/references/widget-patterns.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/references/widget-patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n","isInternal":false,"tokens":574,"sizeBytes":2570},{"name":"SKILL.md","path":".agents/skills/flutter-expert/SKILL.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/flutter-expert/SKILL.md","title":"flutter-expert","category":"anthropic-skill","format":"markdown","content":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n","frontmatter":{"name":"flutter-expert","description":"Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.","license":"MIT","metadata":{"author":"https://github.com/Jeffallan","version":"1.0.0","domain":"frontend","triggers":"Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform","role":"specialist","scope":"implementation","output-format":"code","related-skills":"react-native-expert, test-master, fullstack-guardian"}},"isInternal":false,"tokens":679,"sizeBytes":3032},{"name":"SKILL.md","path":".agents/skills/maestro-mobile-testing/SKILL.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.agents/skills/maestro-mobile-testing/SKILL.md","title":"maestro-mobile-testing","category":"anthropic-skill","format":"markdown","content":"---\nname: maestro-mobile-testing\ndescription: \"Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration\"\nversion: 1.1.0\ncategory: toolchain\nauthor: tovimx\nlicense: MIT\nprogressive_disclosure:\n  entry_point:\n    summary: \"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification\"\n    when_to_use: \"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps\"\n    quick_start: \"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test\"\n  token_estimate:\n    entry: 150\n    full: 9000\ncontext_limit: 900\ntags:\n  - react-native\n  - expo\n  - testing\n  - maestro\n  - e2e\n  - mobile\n  - ios\n  - android\n  - yaml\n  - ci-cd\n  - mcp\nrequires_tools: []\n---\n\n# Maestro Mobile E2E Testing\n\n## Overview\n\nMaestro is a declarative YAML-based mobile E2E testing framework. It provides automatic waiting, built-in retry logic, and fast execution without boilerplate. It's more stable than Detox or Appium for React Native apps.\n\n### Key Features\n\n- **Declarative YAML** — no imperative test code, just steps\n- **Automatic waiting** — no manual `sleep()` or flaky waits\n- **Built-in retry** — reduces test flakiness\n- **Fast execution** — runs quickly without setup overhead\n- **Maestro Studio** — interactive test builder (`maestro studio`)\n- **Sub-flows** — reusable YAML sequences for DRY tests\n- **JavaScript scripting** — GraalJS runtime for HTTP calls and data manipulation\n- **Maestro Cloud** — real device testing in CI without local simulators\n\n## Quick Start\n\n### Install\n\n```bash\ncurl -Ls \"https://get.maestro.mobile.dev\" | bash\nbrew install openjdk@17\nexport JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home\n```\n\n### Minimal test\n\n```yaml\nappId: com.myapp\n---\n- launchApp\n- tapOn:\n    id: \"my-button\"\n- assertVisible: \"Expected Text\"\n```\n\n### Run\n\n```bash\nmaestro test .maestro/smoke-test.yaml\nmaestro test --debug .maestro/smoke-test.yaml  # step through\nmaestro studio                                  # interactive builder\n```\n\n---\n\n## Core Patterns\n\n### 1. Selector Strategy: testID vs Text\n\nChoose your selector approach based on project context. Both are valid — the right choice depends on whether your app is localized and your team's testing philosophy.\n\n| Context | Recommended Selector | Rationale |\n|---------|---------------------|-----------|\n| **Multi-language / i18n** | `id:` (testID) | Stable across translations |\n| **Single language** | Text labels | Human-readable, self-documenting tests |\n| **Agent-maintained tests** | Either — ask the developer | Readability matters less for AI-maintained flows |\n| **System dialogs** | Text (always) | No testID possible on native alerts |\n\n```yaml\n# testID selector — stable across translations\n- tapOn:\n    id: \"submit-button\"\n\n# Text selector — human-readable, self-documenting\n- tapOn: \"Submit\"\n```\n\n**When to prefer testIDs:**\n- App supports multiple languages or will be translated\n- UI text is dynamic or frequently changes\n- Multiple elements share the same visible text\n\n**When to prefer text selectors:**\n- Single-language app with stable copy\n- Readability and self-documentation are a priority\n- Testing user-visible behavior exactly as it appears\n\nIn React Native, add `testID` props when using ID-based selectors:\n\n```tsx\n<TouchableOpacity testID=\"submit-button\" onPress={handleSubmit}>\n  <Text>{t('submit')}</Text>\n</TouchableOpacity>\n```\n\n### testID Naming Convention\n\nWhen using ID-based selectors:\n\n```\n{component}-{action/type}[-{variant}]\n\nExamples:\n- auth-prompt-login-button\n- product-card-{id}\n- otp-input-0\n- tab-home\n- dashboard-loading\n```\n\n### 2. Auth Pre-Flight Pattern\n\nPrevent race conditions where Maestro interacts with the UI before auth state resolves. Add a zero-size `auth-loaded` marker that only renders when auth loading completes:\n\n```tsx\n// In your tab bar or root layout\n{!isLoading && <View testID=\"auth-loaded\" style={{ width: 0, height: 0 }} />}\n```\n\nThen in every test:\n\n```yaml\n- launchApp\n\n# Prevent XCTest crash on cold boot (iOS)\n- swipe:\n    direction: DOWN\n    duration: 100\n\n# Wait for auth state to resolve\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n# Now safe to interact\n- tapOn:\n    id: \"tab-home\"\n```\n\n### 3. Adaptive Tests (Handle Both Auth States)\n\nTests should work regardless of whether the user is authenticated:\n\n```yaml\n# Auth flow — only runs if login prompt is visible\n- runFlow:\n    when:\n      visible: \"Sign In\"\n    file: flows/auth-flow.yaml\n\n# Already authenticated — proceed directly\n- runFlow:\n    when:\n      visible:\n        id: \"tab-home\"\n    file: flows/authenticated-action.yaml\n```\n\n### 4. Testing Optimistic Updates\n\nUse short timeouts to verify UI changes happen before server response:\n\n```yaml\n# Trigger mutation\n- tapOn:\n    id: \"action-button\"\n\n# OPTIMISTIC: UI must change within 3s (not waiting for server)\n- extendedWaitUntil:\n    visible:\n      id: \"undo-button\"\n    timeout: 3000\n\n# Verify derived UI state\n- extendedWaitUntil:\n    visible:\n      id: \"user-indicator\"\n    timeout: 5000\n```\n\n| Action | Expected Change | Timeout |\n|--------|----------------|---------|\n| Mutation trigger | Button state flips | < 3s |\n| List update | Item appears/disappears | < 5s |\n| Re-do action | Proves persistence | < 3s |\n\n### 5. Dismissing Native Alerts\n\nReact Native `Alert.alert()` creates native dialogs that block the UI:\n\n```yaml\n- tapOn:\n    id: \"action-button\"\n\n# Wait for expected state change first\n- extendedWaitUntil:\n    visible:\n      id: \"new-state-element\"\n    timeout: 5000\n\n# Dismiss alert (optional in case it already closed)\n- tapOn:\n    text: \"OK\"\n    optional: true\n\n# Brief delay for alert animation\n- swipe:\n    direction: DOWN\n    duration: 300\n```\n\n### 6. Sub-Flows for Reusability\n\nBreak repeated sequences into sub-flow files:\n\n```\n.maestro/\n├── flows/\n│   ├── auth-and-return.yaml\n│   ├── complete-purchase.yaml\n│   └── verify-result.yaml\n├── smoke-test.yaml\n└── feature-test.yaml\n```\n\n```yaml\n# In main test\n- runFlow:\n    file: flows/auth-and-return.yaml\n```\n\n### 7. Deep Links (Expo)\n\nUse the Expo scheme from `app.json`, not the bundle ID:\n\n```yaml\n# WRONG\n- openLink: \"com.myapp://profile/settings\"\n\n# CORRECT\n- openLink: \"myapp://profile/settings\"\n```\n\nDeep links must be registered in your app's deep link handler. Unregistered routes silently fail.\n\n### 8. Platform-Specific Logic\n\n```yaml\n- runFlow:\n    when:\n      platform: ios\n    file: flows/ios-specific.yaml\n\n- runFlow:\n    when:\n      platform: android\n    file: flows/android-specific.yaml\n```\n\n### 9. Environment Variables\n\n```yaml\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-test@example.com\n  API_BASE_URL: http://localhost:3000\n---\n- inputText: ${TEST_EMAIL}\n```\n\n### 10. Selector State Properties\n\nUse `enabled`, `selected`, `checked`, and `focused` to target elements by their current state. This is useful for validating interactive element states before or after actions.\n\n```yaml\n# Only tap the submit button if it's enabled\n- tapOn:\n    id: \"submit-button\"\n    enabled: true\n\n# Assert a checkbox is checked\n- assertVisible:\n    id: \"terms-checkbox\"\n    checked: true\n\n# Wait for an input to be focused\n- extendedWaitUntil:\n    visible:\n      id: \"email-input\"\n      focused: true\n    timeout: 3000\n```\n\n| Property | Values | Use Case |\n|----------|--------|----------|\n| `enabled` | `true` / `false` | Buttons that disable during submission or until form is valid |\n| `checked` | `true` / `false` | Checkboxes, toggle switches |\n| `selected` | `true` / `false` | Tab items, segmented controls |\n| `focused` | `true` / `false` | Input fields with auto-focus |\n\n### 11. Relative Position Selectors\n\nDistinguish between similar elements by their spatial relationship to other elements. This is more idiomatic and resilient than index-based selection.\n\n```yaml\n# BAD — fragile, breaks if order changes\n- tapOn:\n    text: \"Add to Basket\"\n    index: 1\n\n# GOOD — contextual, self-documenting\n- tapOn:\n    text: \"Add to Basket\"\n    below:\n      text: \"Awesome Shoes\"\n```\n\nAvailable relative selectors:\n\n```yaml\n# Target element below another\n- tapOn:\n    text: \"Buy Now\"\n    below: \"Product Title\"\n\n# Target element that is a child of a parent\n- tapOn:\n    text: \"Delete\"\n    childOf:\n      id: \"item-card-42\"\n\n# Target a parent that contains a specific child\n- tapOn:\n    containsChild: \"Urgent\"\n\n# Target by multiple descendants\n- tapOn:\n    containsDescendants:\n      - id: title_id\n        text: \"Specific Title\"\n      - \"Another descendant text\"\n\n# Horizontal positioning\n- tapOn:\n    text: \"Edit\"\n    rightOf: \"Username\"\n```\n\n| Selector | Meaning |\n|----------|---------|\n| `below:` | Element is positioned below the referenced element |\n| `above:` | Element is positioned above the referenced element |\n| `leftOf:` | Element is to the left of the referenced element |\n| `rightOf:` | Element is to the right of the referenced element |\n| `childOf:` | Element is a direct child of the referenced parent |\n| `containsChild:` | Element contains a direct child matching the reference |\n| `containsDescendants:` | Element contains all specified descendant elements |\n\n---\n\n## Authentication Testing\n\n### Architecture\n\nTesting OTP or magic-link authentication in E2E requires capturing emails programmatically. The general pattern:\n\n```\n┌─────────────┐     ┌──────────────┐     ┌─────────────────┐\n│  Maestro    │────▶│  Auth        │────▶│  Email Capture  │\n│  Test       │     │  Provider    │     │  Service        │\n└─────────────┘     └──────────────┘     └─────────────────┘\n       │                                        │\n       │         ┌──────────────────────────────┘\n       │         ▼\n       │   ┌─────────────┐\n       └──▶│  REST API   │ ─── GET /api/v1/messages\n           │  (email)    │ ─── Extract OTP code\n           └─────────────┘\n```\n\nCommon email capture services: [Mailpit](https://github.com/axllent/mailpit), [MailHog](https://github.com/mailhog/MailHog), [Ethereal](https://ethereal.email/).\n\n### OTP Fetch Script\n\nFetch OTP codes from your email capture service using Maestro's GraalJS runtime:\n\n```javascript\n// CRITICAL: Maestro uses GraalJS — NO async/await, NO fetch()\nvar email = typeof EMAIL !== \"undefined\" ? EMAIL : \"test@example.com\";\nvar emailServiceUrl = typeof EMAIL_SERVICE_URL !== \"undefined\"\n  ? EMAIL_SERVICE_URL : \"http://localhost:8025\";\n\nvar response = http.get(emailServiceUrl + \"/api/v1/messages\");\nif (!response.ok) {\n  throw new Error(\"Failed to fetch emails: \" + response.status);\n}\n\nvar data = json(response.body);\n// Find the latest email and extract OTP code\nvar body = data.messages[0].Content.Body;\nvar match = body.match(/(\\d{6})/);\noutput.OTP_CODE = match[1];\n```\n\n### OTP Input Strategy\n\nOTP components with auto-focus need individual digit entry. Tap each input before typing:\n\n```yaml\n# Split OTP into digits via helper script\n- runScript:\n    file: scripts/split-otp.js\n    env:\n      OTP_CODE: ${output.OTP_CODE}\n\n# Enter each digit by tapping its input\n- tapOn:\n    id: \"otp-input-0\"\n- inputText: ${output.OTP_0}\n\n- tapOn:\n    id: \"otp-input-1\"\n- inputText: ${output.OTP_1}\n# ... repeat for all digits\n```\n\n> For provider-specific implementations (Supabase + Mailpit, Firebase Auth, Auth0), create a project-level skill that extends this one.\n\n---\n\n## GraalJS Script Rules\n\nMaestro uses the GraalJS runtime. These constraints are non-negotiable:\n\n| Feature | Status |\n|---------|--------|\n| `async/await` | **NOT supported** |\n| `fetch()` | **NOT supported** |\n| `http.get()`, `http.post()` | Use these instead |\n| `json()` | Use to parse response bodies |\n| `output.VAR` | Set variables for use in YAML flow |\n| `var` declarations | Required (use `var`, not `const`/`let` for safety) |\n\n```javascript\n// Script template\nvar response = http.get(\"http://localhost:8025/api/endpoint\");\nif (!response.ok) {\n  throw new Error(\"Request failed: \" + response.status);\n}\nvar data = json(response.body);\noutput.RESULT = data.value;\n```\n\n---\n\n## Critical Gotchas\n\n### clearState Does NOT Clear iOS Keychain\n\n`clearState: true` clears the app sandbox (UserDefaults, files, caches) but does **NOT** clear the iOS Keychain. Auth tokens stored via `expo-secure-store` (or any Keychain-based storage) persist across `clearState` resets and even app reinstalls.\n\n```yaml\n# WRONG — user may still be authenticated\n- launchApp:\n    clearState: true\n- assertVisible: \"Welcome\"  # Fails if Keychain has tokens\n\n# CORRECT — wait for auth resolution, then adapt\n- launchApp\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n```\n\n**Rules:**\n- Never rely on `clearState` to produce guest state on iOS\n- For auth tests: skip `clearState`, use `auth-loaded` pre-flight\n- For guest tests: use adaptive flows that handle both states\n- Never assert guest-only UI after `clearState`\n\n**Note:** On Android, `clearState: true` fully resets app data including credentials. This is an iOS-only gotcha.\n\n### XCTest kAXErrorInvalidUIElement Crash (iOS)\n\nThe XCTest driver may crash if Maestro interacts with the accessibility tree before the first render cycle completes on cold boot.\n\n**Fix:** Add a no-op swipe immediately after `launchApp`:\n\n```yaml\n- launchApp\n- swipe:\n    direction: DOWN\n    duration: 100\n```\n\n### API Server Dependency\n\nMobile apps calling backend APIs on localhost need either the full server or a mock server running. Without it, all API-dependent screens show loading spinners or empty states (queries fail silently).\n\n**Fix:** Start a mock API server before running Maestro tests:\n\n```bash\n# Start mock server (serves canned responses on your API port)\nnpx tsx scripts/mock-api-server.ts &\n\n# Then run tests\nmaestro test .maestro/my-test.yaml\n```\n\nCreate a lightweight mock that returns canned JSON for each endpoint your app calls. This is faster and more deterministic than running your full backend.\n\n### Auth-Aware Tab Bars\n\nTab bars that show different tabs for guest vs authenticated users will cause selector failures:\n\n| State | Typical Tabs |\n|-------|-------------|\n| Guest | home, search, cart, profile |\n| Auth | home, feed, create, messages, profile |\n\nOnly assert tabs that exist in both states, or use adaptive `when:` conditions.\n\n---\n\n## Test File Template\n\n```yaml\n# {Feature} {Action} Test\n#\n# Tests: {what this validates}\n# Prerequisites:\n# - Simulator/emulator running with app installed\n# - Backend or mock server running (if API-dependent)\n\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-{feature}@example.com\n  EMAIL_SERVICE_URL: http://localhost:8025\n---\n# ==========================================\n# STEP 1: LAUNCH + AUTH PRE-FLIGHT\n# ==========================================\n\n- launchApp\n\n- swipe:\n    direction: DOWN\n    duration: 100\n\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n- takeScreenshot: 01-initial-state\n\n# ==========================================\n# STEP 2: {ACTION}\n# ==========================================\n\n- tapOn:\n    id: \"target-element\"\n\n# ==========================================\n# STEP 3: VERIFY\n# ==========================================\n\n- extendedWaitUntil:\n    visible:\n      id: \"expected-result\"\n    timeout: 5000\n\n- takeScreenshot: 02-final-state\n```\n\n---\n\n## Folder Structure\n\n```\n.maestro/\n├── README.md                    # Quick reference + testID inventory\n├── config.yaml                  # Shared configuration\n├── flows/                       # Reusable sub-flows\n│   ├── auth-and-return.yaml\n│   ├── complete-action.yaml\n│   └── verify-result.yaml\n├── scripts/                     # GraalJS helpers\n│   ├── fetch-otp.js\n│   └── split-otp.js\n├── smoke-test.yaml              # Guest navigation\n├── auth-signin.yaml             # OTP sign-in flow\n├── feature-screenshots.yaml     # Screenshot capture flows\n└── feature-action.yaml          # Feature-specific tests\n\nscripts/\n├── mock-api-server.ts           # Lightweight mock for E2E\n└── run-e2e.sh                   # Orchestration script\n```\n\n### Naming Conventions\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Main test | `{feature}-{action}.yaml` | `checkout-purchase.yaml` |\n| Sub-flow | `{action}-{context}.yaml` | `auth-and-return-to-dashboard.yaml` |\n| Script | `{verb}-{noun}.js` | `fetch-otp.js` |\n\n---\n\n## Infrastructure\n\n### Mock Server for API-Dependent Tests\n\nCreate a lightweight mock server that serves canned responses for your API layer. This is faster and more deterministic than running your full backend during E2E tests.\n\n```bash\n# Start mock server before running Maestro tests\nnpx tsx scripts/mock-api-server.ts &\n```\n\n### Orchestration Script\n\nAutomate the full E2E setup with a shell script that:\n1. Starts backend services (database, auth)\n2. Seeds test data\n3. Starts mock API server\n4. Runs Maestro tests\n5. Cleans up all services\n\n```bash\nbash scripts/run-e2e.sh\n```\n\n### Seed Data\n\nTests that depend on specific data require seeded databases. Keep seed scripts alongside your test infrastructure and run them before each test suite.\n\n---\n\n## Android-Specific Patterns\n\n### Emulator Setup\n\nAndroid tests require an emulator or a USB-connected physical device. Maestro auto-detects connected devices.\n\n```bash\n# List available system images\nsdkmanager --list | grep system-images\n\n# Create emulator\navdmanager create avd -n maestro_test \\\n  -k \"system-images;android-34;google_apis;arm64-v8a\"\n\n# Start emulator\nemulator -avd maestro_test\n```\n\n### iOS vs Android Differences\n\n| Aspect | iOS | Android |\n|--------|-----|---------|\n| Device type | Simulator only (no physical) | Emulator + physical via ADB |\n| `clearState` | Does NOT clear Keychain | Fully resets app data |\n| Cold boot crash | XCTest kAXError (add swipe delay) | No equivalent issue |\n| Performance | Runs natively (fast) | ARM emulation (slower on x86) |\n| Permission dialogs | System alerts | System dialogs with different text |\n\n### ADB Debugging\n\n```bash\nadb devices                                    # List connected devices\nadb shell am start -n com.myapp/.MainActivity  # Launch app\nadb logcat | grep Maestro                      # Filter Maestro logs\nadb shell input keyevent 82                    # Unlock screen\n```\n\n### Android Permission Handling\n\nAndroid permissions appear as system dialogs. Dismiss with optional taps:\n\n```yaml\n- tapOn:\n    text: \"Allow\"\n    optional: true\n\n- tapOn:\n    text: \"While using the app\"\n    optional: true\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions with Maestro Cloud\n\nMaestro Cloud provides real devices in CI without local simulators. Use the official action:\n\n```yaml\nname: Mobile E2E Tests\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  maestro-e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # Build Android APK\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '17'\n          distribution: 'temurin'\n\n      - name: Build Android APK\n        run: |\n          cd apps/mobile\n          npx expo prebuild --platform android --no-install\n          cd android && ./gradlew assembleRelease\n\n      - name: Run Maestro Cloud Tests\n        id: maestro\n        uses: mobile-dev-inc/action-maestro-cloud@v2\n        with:\n          api-key: ${{ secrets.MAESTRO_API_KEY }}\n          app-file: apps/mobile/android/app/build/outputs/apk/release/app-release.apk\n          workspace: .maestro\n          include-tags: ci\n\n      # Access results\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_CONSOLE_URL }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}\n```\n\n### Tag-Based Flow Filtering\n\nUse tags to control which tests run in CI vs locally:\n\n```yaml\n# In your flow file header\nappId: com.myapp\ntags:\n  - ci\n  - smoke\n---\n- launchApp\n# ... test steps\n```\n\n```bash\n# Run only CI-tagged flows locally\nmaestro test --include-tags ci .maestro/\n\n# Exclude work-in-progress flows\nmaestro test --exclude-tags wip .maestro/\n```\n\n### Local CI with Docker (Android Only)\n\n```dockerfile\nFROM openjdk:17-slim\n\nRUN curl -Ls \"https://get.maestro.mobile.dev\" | bash\nENV PATH=\"/root/.maestro/bin:${PATH}\"\n\nCOPY .maestro/ /app/.maestro/\nWORKDIR /app\n\nCMD [\"maestro\", \"test\", \".maestro/\"]\n```\n\n**Note:** iOS tests cannot run in Docker (requires macOS). Use Maestro Cloud for iOS in CI.\n\n---\n\n## Maestro Cloud\n\n[Maestro Cloud](https://cloud.maestro.dev/) runs tests on real devices without local simulator setup.\n\n### Setup\n\n1. Create account at cloud.maestro.dev\n2. Generate API key from dashboard\n3. Store as `MAESTRO_API_KEY` secret in your CI provider\n\n### Running from CLI\n\n```bash\n# Upload and run on Maestro Cloud\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  .maestro/\n\n# With tag filtering\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  --include-tags smoke \\\n  .maestro/\n```\n\n### Key Points\n\n- **iOS testing**: Supported on Maestro Cloud (not on local physical devices)\n- **Android testing**: Both local physical devices and Maestro Cloud\n- **Results**: Dashboard with video recordings, logs, and screenshots\n- **CI outputs**: `MAESTRO_CLOUD_CONSOLE_URL`, `MAESTRO_CLOUD_FLOW_RESULTS`\n\n---\n\n## Maestro MCP Server\n\nThe [Maestro MCP server](https://docs.maestro.dev/getting-started/maestro-mcp) exposes Maestro's full command set as Model Context Protocol tools, letting AI agents **execute tests and interact with devices directly** — not just write YAML.\n\n### How It Complements This Skill\n\n| | **This Skill** | **Maestro MCP** |\n|---|---|---|\n| **Role** | Teaches correct patterns | Provides runtime execution |\n| **Layer** | Authoring (write good YAML) | Execution (run, tap, assert, screenshot) |\n| **Output** | Better test files | Live device interaction |\n\nUse both together: this skill ensures the AI writes correct tests; the MCP lets it run them immediately, see failures, and iterate.\n\n### Setup\n\nThe MCP ships with the Maestro CLI — no extra install needed:\n\n```bash\n# Verify it's available\nmaestro mcp\n```\n\n**Claude Code** — add to project `.mcp.json` or global settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\n**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nAlso supported on Cursor, Windsurf, VS Code, and JetBrains IDEs. See the [Maestro MCP docs](https://docs.maestro.dev/getting-started/maestro-mcp) for IDE-specific setup.\n\n### Key Capabilities\n\nThe MCP server exposes 47 tools organized by category:\n\n| Category | Tools | Examples |\n|----------|-------|---------|\n| UI Interaction | tap, swipe, scroll, long press | `tapOn`, `scrollUntilVisible` |\n| Text Input | type, erase, paste, copy | `inputText`, `eraseText` |\n| Assertions | visibility, AI-powered | `assertVisible`, `assertWithAI` |\n| App Lifecycle | launch, stop, clear state | `launchApp`, `clearState` |\n| Device Control | location, orientation, airplane | `setLocation`, `hideKeyboard` |\n| Flow Control | run flows, repeat, eval scripts | `runFlow`, `evalScript` |\n| Media | screenshots, recording | `takeScreenshot`, `startRecording` |\n| AI-Powered | visual assertions, defect detection | `assertWithAI`, `assertNoDefectsWithAI` |\n\n### Write-Run-Fix Loop\n\nWith both this skill and the MCP active, the AI can:\n\n1. **Write** a test YAML using patterns from this skill\n2. **Run** it via MCP's `runFlow` / `launchApp` + interaction tools\n3. **See** failures via screenshots and assertions\n4. **Fix** the YAML and re-run — all in one conversation\n\n---\n\n## Debugging\n\n```bash\nmaestro test --debug .maestro/test.yaml   # Step through interactively\nmaestro record .maestro/test.yaml          # Record as video\nmaestro studio                             # Interactive UI builder\nmaestro hierarchy                          # View element tree\n```\n\nScreenshots saved to `~/.maestro/tests/{timestamp}/`.\n\n---\n\n## Checklist for New Tests\n\n```\n[ ] Unique test email (maestro-{feature}@example.com)\n[ ] Selector strategy chosen (testID for i18n apps, text for single-language — see Pattern 1)\n[ ] Selectors use state properties where relevant (enabled, checked — see Pattern 10)\n[ ] Similar elements distinguished with relative selectors, not index (see Pattern 11)\n[ ] Auth pre-flight pattern used (auth-loaded)\n[ ] Post-launch swipe added (iOS crash prevention)\n[ ] Both auth states handled (adaptive flows)\n[ ] Native alerts dismissed after mutations\n[ ] Short timeouts for optimistic updates (3-5s)\n[ ] Sub-flows created for reusable sequences\n[ ] Descriptive screenshots at key points\n[ ] Header comment with prerequisites\n[ ] Added to README.md test table\n[ ] Mock API server started if backend-dependent\n[ ] Tags added for CI filtering (ci, smoke, wip)\n```\n\n---\n\n## Common Errors\n\n| Error | Cause | Fix |\n|-------|-------|-----|\n| \"Unable to locate Java Runtime\" | Java not in PATH | `export JAVA_HOME=/opt/homebrew/opt/openjdk@17/...` |\n| \"Element not found\" after tap | Native alert blocking | Add `tapOn: text: \"OK\" optional: true` |\n| OTP digits not entering | Auto-focus interference | Use individual `otp-input-N` testIDs |\n| Test passes but nothing happened | `optional: true` misused | Only use optional for truly optional actions |\n| \"Assertion is false\" on visibility | Element not rendered yet | Increase timeout or verify testID exists |\n| Script output empty | Wrong JS API | Use `http.get()` not `fetch()` |\n| Auth state inconsistent after clearState | iOS Keychain not cleared | Don't use `clearState`, use adaptive flows |\n| kAXErrorInvalidUIElement crash | Cold boot race (iOS) | Add post-launch swipe delay |\n| Loading spinners / empty screens | No API server running | Start mock API server before tests |\n| Permission dialog blocking (Android) | System dialog not dismissed | Add `tapOn: text: \"Allow\" optional: true` |\n\n---\n\n## Resources\n\n- [Maestro Documentation](https://docs.maestro.dev/)\n- [Maestro Selectors Reference](https://docs.maestro.dev/api-reference/selectors)\n- [Maestro Cloud](https://cloud.maestro.dev/)\n- [Maestro MCP Server](https://docs.maestro.dev/getting-started/maestro-mcp)\n- [Maestro GitHub](https://github.com/mobile-dev-inc/Maestro)\n- [GraalJS HTTP Requests](https://docs.maestro.dev/advanced/javascript/make-http-s-requests)\n- [Conditions & Adaptive Flows](https://docs.maestro.dev/advanced/conditions)\n- [GitHub Actions Integration](https://docs.maestro.dev/cloud/ci-integration/github-actions)\n- [BrowserStack: Maestro Testing Guide](https://www.browserstack.com/guide/maestro-testing)\n- [DEV.to: Tips & Tricks for Maestro + React Native](https://dev.to/retyui/best-tips-tricks-for-e2e-maestro-with-react-native-2kaa)\n","frontmatter":{"name":"maestro-mobile-testing","description":"Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration","version":"1.1.0","category":"toolchain","author":"tovimx","license":"MIT","progressive_disclosure":{"entry_point":{"summary":"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification","when_to_use":"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps","quick_start":"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test"},"token_estimate":{"entry":150,"full":9000}},"context_limit":900,"tags":["react-native","expo","testing","maestro","e2e","mobile","ios","android","yaml","ci-cd","mcp"],"requires_tools":[]},"isInternal":false,"tokens":6822,"sizeBytes":27876},{"name":"flutter-expert.md","path":".kilocode/skills/flutter-expert/flutter-expert.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/flutter-expert.md","title":"flutter-expert","category":"anthropic-skill","format":"markdown","content":"---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n","frontmatter":{"name":"flutter-expert","description":"Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.","license":"MIT","metadata":{"author":"https://github.com/Jeffallan","version":"1.0.0","domain":"frontend","triggers":"Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform","role":"specialist","scope":"implementation","output-format":"code","related-skills":"react-native-expert, test-master, fullstack-guardian"}},"isInternal":false,"tokens":679,"sizeBytes":3033},{"name":"bloc-state.md","path":".kilocode/skills/flutter-expert/references/bloc-state.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/bloc-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n","isInternal":false,"tokens":1153,"sizeBytes":4836},{"name":"gorouter-navigation.md","path":".kilocode/skills/flutter-expert/references/gorouter-navigation.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/gorouter-navigation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n","isInternal":false,"tokens":630,"sizeBytes":2615},{"name":"performance.md","path":".kilocode/skills/flutter-expert/references/performance.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/performance.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n","isInternal":false,"tokens":494,"sizeBytes":2043},{"name":"project-structure.md","path":".kilocode/skills/flutter-expert/references/project-structure.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/project-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n","isInternal":false,"tokens":735,"sizeBytes":2775},{"name":"riverpod-state.md","path":".kilocode/skills/flutter-expert/references/riverpod-state.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/riverpod-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n","isInternal":false,"tokens":720,"sizeBytes":3102},{"name":"widget-patterns.md","path":".kilocode/skills/flutter-expert/references/widget-patterns.md","rawUrl":"https://raw.githubusercontent.com/bagisto/opensource-ecommerce-mobile-app/HEAD/.kilocode/skills/flutter-expert/references/widget-patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n","isInternal":false,"tokens":574,"sizeBytes":2570}],"systemPromptSnippet":"<yakaai_skills repo=\"bagisto/opensource-ecommerce-mobile-app\">\n<!-- File: CLAUDE.md (Tokens: ~1254 | Category: claude-rule) -->\n# CLAUDE.md\n\nGuidance for Claude Code when working in this repository.\n\n## Mandatory skills: apply to EVERY prompt\n\nThese rules apply to every user prompt in this repo, including simple questions,\none-line fixes, and follow-ups. Do them before any other response or tool call.\n\n1. **`/superpowers:using-superpowers`**: invoke it first on every prompt (if not\n   already loaded this session). Then check each available superpowers skill\n   and invoke every one that could apply, for example:\n   - new feature or behavior change: `superpowers:brainstorming`, then `superpowers:writing-plans`\n   - bug, test failure, unexpected behavior: `superpowers:systematic-debugging`\n   - writing code: `superpowers:test-driven-development`\n   - before claiming \"done\" or committing: `superpowers:verification-before-completion`\n2. **`/caveman:caveman`**: keep caveman mode active for every reply (default\n   level `full`). Chat replies stay terse. Code, comments, commit messages, PR\n   text, docs, and memory files stay normal prose. Only turn caveman off when\n   the user says \"stop caveman\" or \"normal mode\".\n3. **`/graphify`**: invoke the `graphify` skill for every prompt about code,\n   architecture, file relationships, or project content.\n   - If `graphify-out/` exists, query the graph first, before grep or file reads.\n   - If `graphify-out/` does not exist, build it with `/graphify` first.\n   - After significant code changes, update the graph so it stays current.\n\nOrder per prompt: `using-superpowers`, then `caveman`, then `graphify`, then the\ntask-specific skills (for example `flutter-expert`), then the work itself.\n\nDo not skip these because a task \"seems simple\". Only skip a rule when the user\nexplicitly says so for that prompt.\n\n## Owner rules and backend-blocked list\n\nRead `plan.md` at the repo root before any task. Key rules: use only the\nBagisto **GraphQL** API (never the REST `/api/shop/...` endpoints), use only\nlive API data (no hardcoded values), validate every query against the\nconfigured server, and never commit or push without the owner's permission.\n`plan.md` also lists features that are blocked because the GraphQL schema\nlacks the field.\n\n## Project\n\nBagisto open-source e-commerce mobile app. Flutter (Dart SDK ^3.10.8), package\nname `bagisto_flutter`, talks to a Bagisto backend over GraphQL.\n\n## Commands\n\n```bash\nflutter pub get\nflutter run\nflutter analyze\nflutter test                       # all tests\nflutter test test/orders_bloc_test.dart\nflutter gen-l10n                   # after editing any lib/l10n/app_*.arb\n```\n\nMaestro E2E flows live in `maestro/` (see `maestro/LABEL_BASED_INSTRUCTIONS.md`).\n\n## Layout\n\n- `lib/core/`: shared code (`graphql/`, `theme/`, `error/`, `locale/`, `currency/`, `widgets/`, ...)\n- `lib/core/graphql/`: raw GraphQL query/mutation strings in static-only classes (`AccountQueries`, `ReturnsQueries`, ...)\n- `lib/features/<feature>/`: `account`, `auth`, `cart`, `category`, `checkout`, `home`, `product`, `search`, `splash`\n- `lib/l10n/`: 10 `app_*.arb` files plus generated `app_localizations*.dart` (committed)\n- `Docs/`: setup guides; design specs under `Docs/superpowers/specs/`\n- `Docs/ai-memory/`: notes for AI agents on past fix rounds (root causes,\n  gotchas, backend-blocked issues). Read the relevant note before touching\n  those areas, and add a new dated note after a large fix round.\n- `test/`: flat directory of unit and widget tests\n\n## House style\n\n- **State**: `flutter_bloc` + `equatable`. Events, states, and bloc live in ONE\n  file per bloc under `lib/features/<feature>/presentation/bloc/`.\n- **Models**: hand-written defensive `fromJson`. No codegen, no `json_serializable`.\n- **GraphQL**: `graphql_flutter`. Repositories call the client directly (no\n  datasource layer) and throw feature exceptions built with\n  `ErrorMapper.getUserMessage`. Single-entity Bagisto queries need IRI ids\n  (for example `/api/shop/customer-orders/{id}`).\n- **Pagination**: cursor (Relay). Repositories return records\n  `({list, totalCount, hasNextPage, endCursor})`.\n- **Navigation**: no route table or GoRouter. Use imperative\n  `Navigator.push(MaterialPageRoute(...))` with `BlocProvider` created inline.\n  Detail pages expose `static navigate(...)`.\n- **l10n**: every user-facing string goes into all 10 arb files. Key format is\n  camelCase `<feature><Thing>`. Placeholder metadata only in `app_en.arb`.\n  Run `flutter gen-l10n` and commit the generated files.\n- **UI**: widgets branch on `Theme.of(context).brightness` for dark mode.\n  Primary color `AppColors.primary500` (#FF6900). Cards: radius 10, padding 12.\n  Primary buttons: height 48, radius 54.\n- **Tests**: fake repositories subclass the real repository with a dummy\n  `GraphQLClient`. Seed bloc state through a `seedState` subclass\n  (see `test/orders_bloc_test.dart`).\n\n## Known baseline\n\n- `flutter analyze` already reports about 49 info/warnings in category, home,\n  and search files. Do not treat these as regressions.\n- `test/address_debug_payload_test.dart` fails on a clean tree.\n- Image upload for returns/messages is REST multipart only, not in GraphQL.\n\n<!-- File: .agents/skills/flutter-expert/references/bloc-state.md (Tokens: ~1153 | Category: anthropic-skill) -->\n# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n<!-- File: .agents/skills/flutter-expert/references/gorouter-navigation.md (Tokens: ~630 | Category: anthropic-skill) -->\n# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n\n<!-- File: .agents/skills/flutter-expert/references/performance.md (Tokens: ~494 | Category: anthropic-skill) -->\n# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n\n<!-- File: .agents/skills/flutter-expert/references/project-structure.md (Tokens: ~735 | Category: anthropic-skill) -->\n# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n\n<!-- File: .agents/skills/flutter-expert/references/riverpod-state.md (Tokens: ~720 | Category: anthropic-skill) -->\n# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n\n<!-- File: .agents/skills/flutter-expert/references/widget-patterns.md (Tokens: ~574 | Category: anthropic-skill) -->\n# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n\n<!-- File: .agents/skills/flutter-expert/SKILL.md (Tokens: ~679 | Category: anthropic-skill) -->\n---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n\n<!-- File: .agents/skills/maestro-mobile-testing/SKILL.md (Tokens: ~6822 | Category: anthropic-skill) -->\n---\nname: maestro-mobile-testing\ndescription: \"Maestro mobile E2E testing patterns for React Native/Expo apps: YAML test flows, testID selectors, adaptive auth state, optimistic update verification, GraalJS scripting, cross-platform stability, CI/CD integration, Maestro Cloud, and MCP server integration\"\nversion: 1.1.0\ncategory: toolchain\nauthor: tovimx\nlicense: MIT\nprogressive_disclosure:\n  entry_point:\n    summary: \"Write reliable Maestro mobile E2E tests with declarative YAML flows, testID selectors, auth-aware adaptive patterns, and optimistic update verification\"\n    when_to_use: \"When writing mobile E2E tests, debugging flaky flows, testing authentication, verifying optimistic updates, capturing screenshots, or setting up Maestro CI for React Native/Expo apps\"\n    quick_start: \"1. Install Maestro CLI 2. Add testID props to components 3. Write YAML flows with testID selectors 4. Use auth-loaded pre-flight pattern 5. Start mock API server for backend-dependent tests 6. Run with maestro test\"\n  token_estimate:\n    entry: 150\n    full: 9000\ncontext_limit: 900\ntags:\n  - react-native\n  - expo\n  - testing\n  - maestro\n  - e2e\n  - mobile\n  - ios\n  - android\n  - yaml\n  - ci-cd\n  - mcp\nrequires_tools: []\n---\n\n# Maestro Mobile E2E Testing\n\n## Overview\n\nMaestro is a declarative YAML-based mobile E2E testing framework. It provides automatic waiting, built-in retry logic, and fast execution without boilerplate. It's more stable than Detox or Appium for React Native apps.\n\n### Key Features\n\n- **Declarative YAML** — no imperative test code, just steps\n- **Automatic waiting** — no manual `sleep()` or flaky waits\n- **Built-in retry** — reduces test flakiness\n- **Fast execution** — runs quickly without setup overhead\n- **Maestro Studio** — interactive test builder (`maestro studio`)\n- **Sub-flows** — reusable YAML sequences for DRY tests\n- **JavaScript scripting** — GraalJS runtime for HTTP calls and data manipulation\n- **Maestro Cloud** — real device testing in CI without local simulators\n\n## Quick Start\n\n### Install\n\n```bash\ncurl -Ls \"https://get.maestro.mobile.dev\" | bash\nbrew install openjdk@17\nexport JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home\n```\n\n### Minimal test\n\n```yaml\nappId: com.myapp\n---\n- launchApp\n- tapOn:\n    id: \"my-button\"\n- assertVisible: \"Expected Text\"\n```\n\n### Run\n\n```bash\nmaestro test .maestro/smoke-test.yaml\nmaestro test --debug .maestro/smoke-test.yaml  # step through\nmaestro studio                                  # interactive builder\n```\n\n---\n\n## Core Patterns\n\n### 1. Selector Strategy: testID vs Text\n\nChoose your selector approach based on project context. Both are valid — the right choice depends on whether your app is localized and your team's testing philosophy.\n\n| Context | Recommended Selector | Rationale |\n|---------|---------------------|-----------|\n| **Multi-language / i18n** | `id:` (testID) | Stable across translations |\n| **Single language** | Text labels | Human-readable, self-documenting tests |\n| **Agent-maintained tests** | Either — ask the developer | Readability matters less for AI-maintained flows |\n| **System dialogs** | Text (always) | No testID possible on native alerts |\n\n```yaml\n# testID selector — stable across translations\n- tapOn:\n    id: \"submit-button\"\n\n# Text selector — human-readable, self-documenting\n- tapOn: \"Submit\"\n```\n\n**When to prefer testIDs:**\n- App supports multiple languages or will be translated\n- UI text is dynamic or frequently changes\n- Multiple elements share the same visible text\n\n**When to prefer text selectors:**\n- Single-language app with stable copy\n- Readability and self-documentation are a priority\n- Testing user-visible behavior exactly as it appears\n\nIn React Native, add `testID` props when using ID-based selectors:\n\n```tsx\n<TouchableOpacity testID=\"submit-button\" onPress={handleSubmit}>\n  <Text>{t('submit')}</Text>\n</TouchableOpacity>\n```\n\n### testID Naming Convention\n\nWhen using ID-based selectors:\n\n```\n{component}-{action/type}[-{variant}]\n\nExamples:\n- auth-prompt-login-button\n- product-card-{id}\n- otp-input-0\n- tab-home\n- dashboard-loading\n```\n\n### 2. Auth Pre-Flight Pattern\n\nPrevent race conditions where Maestro interacts with the UI before auth state resolves. Add a zero-size `auth-loaded` marker that only renders when auth loading completes:\n\n```tsx\n// In your tab bar or root layout\n{!isLoading && <View testID=\"auth-loaded\" style={{ width: 0, height: 0 }} />}\n```\n\nThen in every test:\n\n```yaml\n- launchApp\n\n# Prevent XCTest crash on cold boot (iOS)\n- swipe:\n    direction: DOWN\n    duration: 100\n\n# Wait for auth state to resolve\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n# Now safe to interact\n- tapOn:\n    id: \"tab-home\"\n```\n\n### 3. Adaptive Tests (Handle Both Auth States)\n\nTests should work regardless of whether the user is authenticated:\n\n```yaml\n# Auth flow — only runs if login prompt is visible\n- runFlow:\n    when:\n      visible: \"Sign In\"\n    file: flows/auth-flow.yaml\n\n# Already authenticated — proceed directly\n- runFlow:\n    when:\n      visible:\n        id: \"tab-home\"\n    file: flows/authenticated-action.yaml\n```\n\n### 4. Testing Optimistic Updates\n\nUse short timeouts to verify UI changes happen before server response:\n\n```yaml\n# Trigger mutation\n- tapOn:\n    id: \"action-button\"\n\n# OPTIMISTIC: UI must change within 3s (not waiting for server)\n- extendedWaitUntil:\n    visible:\n      id: \"undo-button\"\n    timeout: 3000\n\n# Verify derived UI state\n- extendedWaitUntil:\n    visible:\n      id: \"user-indicator\"\n    timeout: 5000\n```\n\n| Action | Expected Change | Timeout |\n|--------|----------------|---------|\n| Mutation trigger | Button state flips | < 3s |\n| List update | Item appears/disappears | < 5s |\n| Re-do action | Proves persistence | < 3s |\n\n### 5. Dismissing Native Alerts\n\nReact Native `Alert.alert()` creates native dialogs that block the UI:\n\n```yaml\n- tapOn:\n    id: \"action-button\"\n\n# Wait for expected state change first\n- extendedWaitUntil:\n    visible:\n      id: \"new-state-element\"\n    timeout: 5000\n\n# Dismiss alert (optional in case it already closed)\n- tapOn:\n    text: \"OK\"\n    optional: true\n\n# Brief delay for alert animation\n- swipe:\n    direction: DOWN\n    duration: 300\n```\n\n### 6. Sub-Flows for Reusability\n\nBreak repeated sequences into sub-flow files:\n\n```\n.maestro/\n├── flows/\n│   ├── auth-and-return.yaml\n│   ├── complete-purchase.yaml\n│   └── verify-result.yaml\n├── smoke-test.yaml\n└── feature-test.yaml\n```\n\n```yaml\n# In main test\n- runFlow:\n    file: flows/auth-and-return.yaml\n```\n\n### 7. Deep Links (Expo)\n\nUse the Expo scheme from `app.json`, not the bundle ID:\n\n```yaml\n# WRONG\n- openLink: \"com.myapp://profile/settings\"\n\n# CORRECT\n- openLink: \"myapp://profile/settings\"\n```\n\nDeep links must be registered in your app's deep link handler. Unregistered routes silently fail.\n\n### 8. Platform-Specific Logic\n\n```yaml\n- runFlow:\n    when:\n      platform: ios\n    file: flows/ios-specific.yaml\n\n- runFlow:\n    when:\n      platform: android\n    file: flows/android-specific.yaml\n```\n\n### 9. Environment Variables\n\n```yaml\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-test@example.com\n  API_BASE_URL: http://localhost:3000\n---\n- inputText: ${TEST_EMAIL}\n```\n\n### 10. Selector State Properties\n\nUse `enabled`, `selected`, `checked`, and `focused` to target elements by their current state. This is useful for validating interactive element states before or after actions.\n\n```yaml\n# Only tap the submit button if it's enabled\n- tapOn:\n    id: \"submit-button\"\n    enabled: true\n\n# Assert a checkbox is checked\n- assertVisible:\n    id: \"terms-checkbox\"\n    checked: true\n\n# Wait for an input to be focused\n- extendedWaitUntil:\n    visible:\n      id: \"email-input\"\n      focused: true\n    timeout: 3000\n```\n\n| Property | Values | Use Case |\n|----------|--------|----------|\n| `enabled` | `true` / `false` | Buttons that disable during submission or until form is valid |\n| `checked` | `true` / `false` | Checkboxes, toggle switches |\n| `selected` | `true` / `false` | Tab items, segmented controls |\n| `focused` | `true` / `false` | Input fields with auto-focus |\n\n### 11. Relative Position Selectors\n\nDistinguish between similar elements by their spatial relationship to other elements. This is more idiomatic and resilient than index-based selection.\n\n```yaml\n# BAD — fragile, breaks if order changes\n- tapOn:\n    text: \"Add to Basket\"\n    index: 1\n\n# GOOD — contextual, self-documenting\n- tapOn:\n    text: \"Add to Basket\"\n    below:\n      text: \"Awesome Shoes\"\n```\n\nAvailable relative selectors:\n\n```yaml\n# Target element below another\n- tapOn:\n    text: \"Buy Now\"\n    below: \"Product Title\"\n\n# Target element that is a child of a parent\n- tapOn:\n    text: \"Delete\"\n    childOf:\n      id: \"item-card-42\"\n\n# Target a parent that contains a specific child\n- tapOn:\n    containsChild: \"Urgent\"\n\n# Target by multiple descendants\n- tapOn:\n    containsDescendants:\n      - id: title_id\n        text: \"Specific Title\"\n      - \"Another descendant text\"\n\n# Horizontal positioning\n- tapOn:\n    text: \"Edit\"\n    rightOf: \"Username\"\n```\n\n| Selector | Meaning |\n|----------|---------|\n| `below:` | Element is positioned below the referenced element |\n| `above:` | Element is positioned above the referenced element |\n| `leftOf:` | Element is to the left of the referenced element |\n| `rightOf:` | Element is to the right of the referenced element |\n| `childOf:` | Element is a direct child of the referenced parent |\n| `containsChild:` | Element contains a direct child matching the reference |\n| `containsDescendants:` | Element contains all specified descendant elements |\n\n---\n\n## Authentication Testing\n\n### Architecture\n\nTesting OTP or magic-link authentication in E2E requires capturing emails programmatically. The general pattern:\n\n```\n┌─────────────┐     ┌──────────────┐     ┌─────────────────┐\n│  Maestro    │────▶│  Auth        │────▶│  Email Capture  │\n│  Test       │     │  Provider    │     │  Service        │\n└─────────────┘     └──────────────┘     └─────────────────┘\n       │                                        │\n       │         ┌──────────────────────────────┘\n       │         ▼\n       │   ┌─────────────┐\n       └──▶│  REST API   │ ─── GET /api/v1/messages\n           │  (email)    │ ─── Extract OTP code\n           └─────────────┘\n```\n\nCommon email capture services: [Mailpit](https://github.com/axllent/mailpit), [MailHog](https://github.com/mailhog/MailHog), [Ethereal](https://ethereal.email/).\n\n### OTP Fetch Script\n\nFetch OTP codes from your email capture service using Maestro's GraalJS runtime:\n\n```javascript\n// CRITICAL: Maestro uses GraalJS — NO async/await, NO fetch()\nvar email = typeof EMAIL !== \"undefined\" ? EMAIL : \"test@example.com\";\nvar emailServiceUrl = typeof EMAIL_SERVICE_URL !== \"undefined\"\n  ? EMAIL_SERVICE_URL : \"http://localhost:8025\";\n\nvar response = http.get(emailServiceUrl + \"/api/v1/messages\");\nif (!response.ok) {\n  throw new Error(\"Failed to fetch emails: \" + response.status);\n}\n\nvar data = json(response.body);\n// Find the latest email and extract OTP code\nvar body = data.messages[0].Content.Body;\nvar match = body.match(/(\\d{6})/);\noutput.OTP_CODE = match[1];\n```\n\n### OTP Input Strategy\n\nOTP components with auto-focus need individual digit entry. Tap each input before typing:\n\n```yaml\n# Split OTP into digits via helper script\n- runScript:\n    file: scripts/split-otp.js\n    env:\n      OTP_CODE: ${output.OTP_CODE}\n\n# Enter each digit by tapping its input\n- tapOn:\n    id: \"otp-input-0\"\n- inputText: ${output.OTP_0}\n\n- tapOn:\n    id: \"otp-input-1\"\n- inputText: ${output.OTP_1}\n# ... repeat for all digits\n```\n\n> For provider-specific implementations (Supabase + Mailpit, Firebase Auth, Auth0), create a project-level skill that extends this one.\n\n---\n\n## GraalJS Script Rules\n\nMaestro uses the GraalJS runtime. These constraints are non-negotiable:\n\n| Feature | Status |\n|---------|--------|\n| `async/await` | **NOT supported** |\n| `fetch()` | **NOT supported** |\n| `http.get()`, `http.post()` | Use these instead |\n| `json()` | Use to parse response bodies |\n| `output.VAR` | Set variables for use in YAML flow |\n| `var` declarations | Required (use `var`, not `const`/`let` for safety) |\n\n```javascript\n// Script template\nvar response = http.get(\"http://localhost:8025/api/endpoint\");\nif (!response.ok) {\n  throw new Error(\"Request failed: \" + response.status);\n}\nvar data = json(response.body);\noutput.RESULT = data.value;\n```\n\n---\n\n## Critical Gotchas\n\n### clearState Does NOT Clear iOS Keychain\n\n`clearState: true` clears the app sandbox (UserDefaults, files, caches) but does **NOT** clear the iOS Keychain. Auth tokens stored via `expo-secure-store` (or any Keychain-based storage) persist across `clearState` resets and even app reinstalls.\n\n```yaml\n# WRONG — user may still be authenticated\n- launchApp:\n    clearState: true\n- assertVisible: \"Welcome\"  # Fails if Keychain has tokens\n\n# CORRECT — wait for auth resolution, then adapt\n- launchApp\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n```\n\n**Rules:**\n- Never rely on `clearState` to produce guest state on iOS\n- For auth tests: skip `clearState`, use `auth-loaded` pre-flight\n- For guest tests: use adaptive flows that handle both states\n- Never assert guest-only UI after `clearState`\n\n**Note:** On Android, `clearState: true` fully resets app data including credentials. This is an iOS-only gotcha.\n\n### XCTest kAXErrorInvalidUIElement Crash (iOS)\n\nThe XCTest driver may crash if Maestro interacts with the accessibility tree before the first render cycle completes on cold boot.\n\n**Fix:** Add a no-op swipe immediately after `launchApp`:\n\n```yaml\n- launchApp\n- swipe:\n    direction: DOWN\n    duration: 100\n```\n\n### API Server Dependency\n\nMobile apps calling backend APIs on localhost need either the full server or a mock server running. Without it, all API-dependent screens show loading spinners or empty states (queries fail silently).\n\n**Fix:** Start a mock API server before running Maestro tests:\n\n```bash\n# Start mock server (serves canned responses on your API port)\nnpx tsx scripts/mock-api-server.ts &\n\n# Then run tests\nmaestro test .maestro/my-test.yaml\n```\n\nCreate a lightweight mock that returns canned JSON for each endpoint your app calls. This is faster and more deterministic than running your full backend.\n\n### Auth-Aware Tab Bars\n\nTab bars that show different tabs for guest vs authenticated users will cause selector failures:\n\n| State | Typical Tabs |\n|-------|-------------|\n| Guest | home, search, cart, profile |\n| Auth | home, feed, create, messages, profile |\n\nOnly assert tabs that exist in both states, or use adaptive `when:` conditions.\n\n---\n\n## Test File Template\n\n```yaml\n# {Feature} {Action} Test\n#\n# Tests: {what this validates}\n# Prerequisites:\n# - Simulator/emulator running with app installed\n# - Backend or mock server running (if API-dependent)\n\nappId: com.myapp\nenv:\n  TEST_EMAIL: maestro-{feature}@example.com\n  EMAIL_SERVICE_URL: http://localhost:8025\n---\n# ==========================================\n# STEP 1: LAUNCH + AUTH PRE-FLIGHT\n# ==========================================\n\n- launchApp\n\n- swipe:\n    direction: DOWN\n    duration: 100\n\n- extendedWaitUntil:\n    visible:\n      id: \"auth-loaded\"\n    timeout: 15000\n\n- takeScreenshot: 01-initial-state\n\n# ==========================================\n# STEP 2: {ACTION}\n# ==========================================\n\n- tapOn:\n    id: \"target-element\"\n\n# ==========================================\n# STEP 3: VERIFY\n# ==========================================\n\n- extendedWaitUntil:\n    visible:\n      id: \"expected-result\"\n    timeout: 5000\n\n- takeScreenshot: 02-final-state\n```\n\n---\n\n## Folder Structure\n\n```\n.maestro/\n├── README.md                    # Quick reference + testID inventory\n├── config.yaml                  # Shared configuration\n├── flows/                       # Reusable sub-flows\n│   ├── auth-and-return.yaml\n│   ├── complete-action.yaml\n│   └── verify-result.yaml\n├── scripts/                     # GraalJS helpers\n│   ├── fetch-otp.js\n│   └── split-otp.js\n├── smoke-test.yaml              # Guest navigation\n├── auth-signin.yaml             # OTP sign-in flow\n├── feature-screenshots.yaml     # Screenshot capture flows\n└── feature-action.yaml          # Feature-specific tests\n\nscripts/\n├── mock-api-server.ts           # Lightweight mock for E2E\n└── run-e2e.sh                   # Orchestration script\n```\n\n### Naming Conventions\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Main test | `{feature}-{action}.yaml` | `checkout-purchase.yaml` |\n| Sub-flow | `{action}-{context}.yaml` | `auth-and-return-to-dashboard.yaml` |\n| Script | `{verb}-{noun}.js` | `fetch-otp.js` |\n\n---\n\n## Infrastructure\n\n### Mock Server for API-Dependent Tests\n\nCreate a lightweight mock server that serves canned responses for your API layer. This is faster and more deterministic than running your full backend during E2E tests.\n\n```bash\n# Start mock server before running Maestro tests\nnpx tsx scripts/mock-api-server.ts &\n```\n\n### Orchestration Script\n\nAutomate the full E2E setup with a shell script that:\n1. Starts backend services (database, auth)\n2. Seeds test data\n3. Starts mock API server\n4. Runs Maestro tests\n5. Cleans up all services\n\n```bash\nbash scripts/run-e2e.sh\n```\n\n### Seed Data\n\nTests that depend on specific data require seeded databases. Keep seed scripts alongside your test infrastructure and run them before each test suite.\n\n---\n\n## Android-Specific Patterns\n\n### Emulator Setup\n\nAndroid tests require an emulator or a USB-connected physical device. Maestro auto-detects connected devices.\n\n```bash\n# List available system images\nsdkmanager --list | grep system-images\n\n# Create emulator\navdmanager create avd -n maestro_test \\\n  -k \"system-images;android-34;google_apis;arm64-v8a\"\n\n# Start emulator\nemulator -avd maestro_test\n```\n\n### iOS vs Android Differences\n\n| Aspect | iOS | Android |\n|--------|-----|---------|\n| Device type | Simulator only (no physical) | Emulator + physical via ADB |\n| `clearState` | Does NOT clear Keychain | Fully resets app data |\n| Cold boot crash | XCTest kAXError (add swipe delay) | No equivalent issue |\n| Performance | Runs natively (fast) | ARM emulation (slower on x86) |\n| Permission dialogs | System alerts | System dialogs with different text |\n\n### ADB Debugging\n\n```bash\nadb devices                                    # List connected devices\nadb shell am start -n com.myapp/.MainActivity  # Launch app\nadb logcat | grep Maestro                      # Filter Maestro logs\nadb shell input keyevent 82                    # Unlock screen\n```\n\n### Android Permission Handling\n\nAndroid permissions appear as system dialogs. Dismiss with optional taps:\n\n```yaml\n- tapOn:\n    text: \"Allow\"\n    optional: true\n\n- tapOn:\n    text: \"While using the app\"\n    optional: true\n```\n\n---\n\n## CI/CD Integration\n\n### GitHub Actions with Maestro Cloud\n\nMaestro Cloud provides real devices in CI without local simulators. Use the official action:\n\n```yaml\nname: Mobile E2E Tests\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  maestro-e2e:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      # Build Android APK\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '17'\n          distribution: 'temurin'\n\n      - name: Build Android APK\n        run: |\n          cd apps/mobile\n          npx expo prebuild --platform android --no-install\n          cd android && ./gradlew assembleRelease\n\n      - name: Run Maestro Cloud Tests\n        id: maestro\n        uses: mobile-dev-inc/action-maestro-cloud@v2\n        with:\n          api-key: ${{ secrets.MAESTRO_API_KEY }}\n          app-file: apps/mobile/android/app/build/outputs/apk/release/app-release.apk\n          workspace: .maestro\n          include-tags: ci\n\n      # Access results\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_CONSOLE_URL }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_UPLOAD_STATUS }}\n      # ${{ steps.maestro.outputs.MAESTRO_CLOUD_FLOW_RESULTS }}\n```\n\n### Tag-Based Flow Filtering\n\nUse tags to control which tests run in CI vs locally:\n\n```yaml\n# In your flow file header\nappId: com.myapp\ntags:\n  - ci\n  - smoke\n---\n- launchApp\n# ... test steps\n```\n\n```bash\n# Run only CI-tagged flows locally\nmaestro test --include-tags ci .maestro/\n\n# Exclude work-in-progress flows\nmaestro test --exclude-tags wip .maestro/\n```\n\n### Local CI with Docker (Android Only)\n\n```dockerfile\nFROM openjdk:17-slim\n\nRUN curl -Ls \"https://get.maestro.mobile.dev\" | bash\nENV PATH=\"/root/.maestro/bin:${PATH}\"\n\nCOPY .maestro/ /app/.maestro/\nWORKDIR /app\n\nCMD [\"maestro\", \"test\", \".maestro/\"]\n```\n\n**Note:** iOS tests cannot run in Docker (requires macOS). Use Maestro Cloud for iOS in CI.\n\n---\n\n## Maestro Cloud\n\n[Maestro Cloud](https://cloud.maestro.dev/) runs tests on real devices without local simulator setup.\n\n### Setup\n\n1. Create account at cloud.maestro.dev\n2. Generate API key from dashboard\n3. Store as `MAESTRO_API_KEY` secret in your CI provider\n\n### Running from CLI\n\n```bash\n# Upload and run on Maestro Cloud\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  .maestro/\n\n# With tag filtering\nmaestro cloud --api-key $MAESTRO_API_KEY \\\n  --app-file ./app-release.apk \\\n  --include-tags smoke \\\n  .maestro/\n```\n\n### Key Points\n\n- **iOS testing**: Supported on Maestro Cloud (not on local physical devices)\n- **Android testing**: Both local physical devices and Maestro Cloud\n- **Results**: Dashboard with video recordings, logs, and screenshots\n- **CI outputs**: `MAESTRO_CLOUD_CONSOLE_URL`, `MAESTRO_CLOUD_FLOW_RESULTS`\n\n---\n\n## Maestro MCP Server\n\nThe [Maestro MCP server](https://docs.maestro.dev/getting-started/maestro-mcp) exposes Maestro's full command set as Model Context Protocol tools, letting AI agents **execute tests and interact with devices directly** — not just write YAML.\n\n### How It Complements This Skill\n\n| | **This Skill** | **Maestro MCP** |\n|---|---|---|\n| **Role** | Teaches correct patterns | Provides runtime execution |\n| **Layer** | Authoring (write good YAML) | Execution (run, tap, assert, screenshot) |\n| **Output** | Better test files | Live device interaction |\n\nUse both together: this skill ensures the AI writes correct tests; the MCP lets it run them immediately, see failures, and iterate.\n\n### Setup\n\nThe MCP ships with the Maestro CLI — no extra install needed:\n\n```bash\n# Verify it's available\nmaestro mcp\n```\n\n**Claude Code** — add to project `.mcp.json` or global settings:\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\n**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"maestro\": {\n      \"command\": \"maestro\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nAlso supported on Cursor, Windsurf, VS Code, and JetBrains IDEs. See the [Maestro MCP docs](https://docs.maestro.dev/getting-started/maestro-mcp) for IDE-specific setup.\n\n### Key Capabilities\n\nThe MCP server exposes 47 tools organized by category:\n\n| Category | Tools | Examples |\n|----------|-------|---------|\n| UI Interaction | tap, swipe, scroll, long press | `tapOn`, `scrollUntilVisible` |\n| Text Input | type, erase, paste, copy | `inputText`, `eraseText` |\n| Assertions | visibility, AI-powered | `assertVisible`, `assertWithAI` |\n| App Lifecycle | launch, stop, clear state | `launchApp`, `clearState` |\n| Device Control | location, orientation, airplane | `setLocation`, `hideKeyboard` |\n| Flow Control | run flows, repeat, eval scripts | `runFlow`, `evalScript` |\n| Media | screenshots, recording | `takeScreenshot`, `startRecording` |\n| AI-Powered | visual assertions, defect detection | `assertWithAI`, `assertNoDefectsWithAI` |\n\n### Write-Run-Fix Loop\n\nWith both this skill and the MCP active, the AI can:\n\n1. **Write** a test YAML using patterns from this skill\n2. **Run** it via MCP's `runFlow` / `launchApp` + interaction tools\n3. **See** failures via screenshots and assertions\n4. **Fix** the YAML and re-run — all in one conversation\n\n---\n\n## Debugging\n\n```bash\nmaestro test --debug .maestro/test.yaml   # Step through interactively\nmaestro record .maestro/test.yaml          # Record as video\nmaestro studio                             # Interactive UI builder\nmaestro hierarchy                          # View element tree\n```\n\nScreenshots saved to `~/.maestro/tests/{timestamp}/`.\n\n---\n\n## Checklist for New Tests\n\n```\n[ ] Unique test email (maestro-{feature}@example.com)\n[ ] Selector strategy chosen (testID for i18n apps, text for single-language — see Pattern 1)\n[ ] Selectors use state properties where relevant (enabled, checked — see Pattern 10)\n[ ] Similar elements distinguished with relative selectors, not index (see Pattern 11)\n[ ] Auth pre-flight pattern used (auth-loaded)\n[ ] Post-launch swipe added (iOS crash prevention)\n[ ] Both auth states handled (adaptive flows)\n[ ] Native alerts dismissed after mutations\n[ ] Short timeouts for optimistic updates (3-5s)\n[ ] Sub-flows created for reusable sequences\n[ ] Descriptive screenshots at key points\n[ ] Header comment with prerequisites\n[ ] Added to README.md test table\n[ ] Mock API server started if backend-dependent\n[ ] Tags added for CI filtering (ci, smoke, wip)\n```\n\n---\n\n## Common Errors\n\n| Error | Cause | Fix |\n|-------|-------|-----|\n| \"Unable to locate Java Runtime\" | Java not in PATH | `export JAVA_HOME=/opt/homebrew/opt/openjdk@17/...` |\n| \"Element not found\" after tap | Native alert blocking | Add `tapOn: text: \"OK\" optional: true` |\n| OTP digits not entering | Auto-focus interference | Use individual `otp-input-N` testIDs |\n| Test passes but nothing happened | `optional: true` misused | Only use optional for truly optional actions |\n| \"Assertion is false\" on visibility | Element not rendered yet | Increase timeout or verify testID exists |\n| Script output empty | Wrong JS API | Use `http.get()` not `fetch()` |\n| Auth state inconsistent after clearState | iOS Keychain not cleared | Don't use `clearState`, use adaptive flows |\n| kAXErrorInvalidUIElement crash | Cold boot race (iOS) | Add post-launch swipe delay |\n| Loading spinners / empty screens | No API server running | Start mock API server before tests |\n| Permission dialog blocking (Android) | System dialog not dismissed | Add `tapOn: text: \"Allow\" optional: true` |\n\n---\n\n## Resources\n\n- [Maestro Documentation](https://docs.maestro.dev/)\n- [Maestro Selectors Reference](https://docs.maestro.dev/api-reference/selectors)\n- [Maestro Cloud](https://cloud.maestro.dev/)\n- [Maestro MCP Server](https://docs.maestro.dev/getting-started/maestro-mcp)\n- [Maestro GitHub](https://github.com/mobile-dev-inc/Maestro)\n- [GraalJS HTTP Requests](https://docs.maestro.dev/advanced/javascript/make-http-s-requests)\n- [Conditions & Adaptive Flows](https://docs.maestro.dev/advanced/conditions)\n- [GitHub Actions Integration](https://docs.maestro.dev/cloud/ci-integration/github-actions)\n- [BrowserStack: Maestro Testing Guide](https://www.browserstack.com/guide/maestro-testing)\n- [DEV.to: Tips & Tricks for Maestro + React Native](https://dev.to/retyui/best-tips-tricks-for-e2e-maestro-with-react-native-2kaa)\n\n<!-- File: .kilocode/skills/flutter-expert/flutter-expert.md (Tokens: ~679 | Category: anthropic-skill) -->\n---\nname: flutter-expert\ndescription: Use when building cross-platform applications with Flutter 3+ and Dart. Invoke for widget development, Riverpod/Bloc state management, GoRouter navigation, platform-specific implementations, performance optimization.\nlicense: MIT\nmetadata:\n  author: https://github.com/Jeffallan\n  version: \"1.0.0\"\n  domain: frontend\n  triggers: Flutter, Dart, widget, Riverpod, Bloc, GoRouter, cross-platform\n  role: specialist\n  scope: implementation\n  output-format: code\n  related-skills: react-native-expert, test-master, fullstack-guardian\n---\n\n# Flutter Expert\n\nSenior mobile engineer building high-performance cross-platform applications with Flutter 3 and Dart.\n\n## Role Definition\n\nYou are a senior Flutter developer with 6+ years of experience. You specialize in Flutter 3.19+, Riverpod 2.0, GoRouter, and building apps for iOS, Android, Web, and Desktop. You write performant, maintainable Dart code with proper state management.\n\n\n## When to Use This Skill\n\n- Building cross-platform Flutter applications\n- Implementing state management (Riverpod, Bloc)\n- Setting up navigation with GoRouter\n- Creating custom widgets and animations\n- Optimizing Flutter performance\n- Platform-specific implementations\n\n## Core Workflow\n\n1. **Setup** - Project structure, dependencies, routing\n2. **State** - Riverpod providers or Bloc setup\n3. **Widgets** - Reusable, const-optimized components\n4. **Test** - Widget tests, integration tests\n5. **Optimize** - Profile, reduce rebuilds\n\n## Reference Guide\n\nLoad detailed guidance based on context:\n\n| Topic | Reference | Load When |\n|-------|-----------|-----------|\n| Riverpod | `references/riverpod-state.md` | State management, providers, notifiers |\n| Bloc | `references/bloc-state.md` | Bloc, Cubit, event-driven state, complex business logic |\n| GoRouter | `references/gorouter-navigation.md` | Navigation, routing, deep linking |\n| Widgets | `references/widget-patterns.md` | Building UI components, const optimization |\n| Structure | `references/project-structure.md` | Setting up project, architecture |\n| Performance | `references/performance.md` | Optimization, profiling, jank fixes |\n\n## Constraints\n\n### MUST DO\n- Use const constructors wherever possible\n- Implement proper keys for lists\n- Use Consumer/ConsumerWidget for state (not StatefulWidget)\n- Follow Material/Cupertino design guidelines\n- Profile with DevTools, fix jank\n- Test widgets with flutter_test\n\n### MUST NOT DO\n- Build widgets inside build() method\n- Mutate state directly (always create new instances)\n- Use setState for app-wide state\n- Skip const on static widgets\n- Ignore platform-specific behavior\n- Block UI thread with heavy computation (use compute())\n\n## Output Templates\n\nWhen implementing Flutter features, provide:\n1. Widget code with proper const usage\n2. Provider/Bloc definitions\n3. Route configuration if needed\n4. Test file structure\n\n## Knowledge Reference\n\nFlutter 3.19+, Dart 3.3+, Riverpod 2.0, Bloc 8.x, GoRouter, freezed, json_serializable, Dio, flutter_hooks\n\n<!-- File: .kilocode/skills/flutter-expert/references/bloc-state.md (Tokens: ~1153 | Category: anthropic-skill) -->\n# Bloc State Management\n\n## When to Use Bloc\n\nUse **Bloc/Cubit** when you need:\n\n* Explicit event → state transitions\n* Complex business logic\n* Predictable, testable flows\n* Clear separation between UI and logic\n\n| Use Case               | Recommended |\n| ---------------------- | ----------- |\n| Simple mutable state   | Riverpod    |\n| Event-driven workflows | Bloc        |\n| Forms, auth, wizards   | Bloc        |\n| Feature modules        | Bloc        |\n\n---\n\n## Core Concepts\n\n| Concept | Description            |\n| ------- | ---------------------- |\n| Event   | User/system input      |\n| State   | Immutable UI state     |\n| Bloc    | Event → State mapper   |\n| Cubit   | State-only (no events) |\n\n---\n\n## Basic Bloc Setup\n\n### Event\n\n```dart\nsealed class CounterEvent {}\n\nfinal class CounterIncremented extends CounterEvent {}\n\nfinal class CounterDecremented extends CounterEvent {}\n```\n\n### State\n\n```dart\nclass CounterState {\n  final int value;\n\n  const CounterState({required this.value});\n\n  CounterState copyWith({int? value}) {\n    return CounterState(value: value ?? this.value);\n  }\n}\n```\n\n### Bloc\n\n```dart\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nclass CounterBloc extends Bloc<CounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(value: 0)) {\n    on<CounterIncremented>((event, emit) {\n      emit(state.copyWith(value: state.value + 1));\n    });\n\n    on<CounterDecremented>((event, emit) {\n      emit(state.copyWith(value: state.value - 1));\n    });\n  }\n}\n```\n\n---\n\n## Cubit (Recommended for Simpler Logic)\n\n```dart\nclass CounterCubit extends Cubit<int> {\n  CounterCubit() : super(0);\n\n  void increment() => emit(state + 1);\n  void decrement() => emit(state - 1);\n}\n```\n\n---\n\n## Providing Bloc to the Widget Tree\n\n```dart\nBlocProvider(\n  create: (_) => CounterBloc(),\n  child: const CounterScreen(),\n);\n```\n\nMultiple blocs:\n\n```dart\nMultiBlocProvider(\n  providers: [\n    BlocProvider(create: (_) => AuthBloc()),\n    BlocProvider(create: (_) => ProfileBloc()),\n  ],\n  child: const AppRoot(),\n);\n```\n\n---\n\n## Using Bloc in Widgets\n\n### BlocBuilder (UI rebuilds)\n\n```dart\nclass CounterScreen extends StatelessWidget {\n  const CounterScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<CounterBloc, CounterState>(\n      buildWhen: (prev, curr) => prev.value != curr.value,\n      builder: (context, state) {\n        return Text(\n          state.value.toString(),\n          style: Theme.of(context).textTheme.displayLarge,\n        );\n      },\n    );\n  }\n}\n```\n\n---\n\n### BlocListener (Side Effects)\n\n```dart\nBlocListener<AuthBloc, AuthState>(\n  listenWhen: (prev, curr) => curr is AuthFailure,\n  listener: (context, state) {\n    if (state is AuthFailure) {\n      ScaffoldMessenger.of(context)\n          .showSnackBar(SnackBar(content: Text(state.message)));\n    }\n  },\n  child: const LoginForm(),\n);\n```\n\n---\n\n### BlocConsumer (Builder + Listener)\n\n```dart\nBlocConsumer<FormBloc, FormState>(\n  listener: (context, state) {\n    if (state.status == FormStatus.success) {\n      context.pop();\n    }\n  },\n  builder: (context, state) {\n    return ElevatedButton(\n      onPressed: state.isValid\n          ? () => context.read<FormBloc>().add(FormSubmitted())\n          : null,\n      child: const Text('Submit'),\n    );\n  },\n);\n```\n\n---\n\n## Accessing Bloc Without Rebuilds\n\n```dart\ncontext.read<CounterBloc>().add(CounterIncremented());\n```\n\n⚠️ **Never use `watch` inside callbacks**\n\n---\n\n## Async Bloc Pattern (API Calls)\n\n```dart\non<UserRequested>((event, emit) async {\n  emit(const UserState.loading());\n\n  try {\n    final user = await repository.fetchUser();\n    emit(UserState.success(user));\n  } catch (e) {\n    emit(UserState.failure(e.toString()));\n  }\n});\n```\n\n---\n\n## Bloc + GoRouter (Auth Guard Example)\n\n```dart\nredirect: (context, state) {\n  final authState = context.read<AuthBloc>().state;\n\n  if (authState is Unauthenticated) {\n    return '/login';\n  }\n  return null;\n}\n```\n\n---\n\n## Testing Bloc\n\n```dart\nblocTest<CounterBloc, CounterState>(\n  'emits incremented value',\n  build: () => CounterBloc(),\n  act: (bloc) => bloc.add(CounterIncremented()),\n  expect: () => [\n    const CounterState(value: 1),\n  ],\n);\n```\n\n---\n\n## Best Practices (MUST FOLLOW)\n\n✅ Immutable states\n✅ Small, focused blocs\n✅ One feature = one bloc\n✅ Use Cubit when possible\n✅ Test all blocs\n\n❌ No UI logic inside blocs\n❌ No context usage inside blocs\n❌ No mutable state\n❌ No massive “god blocs”\n\n---\n\n## Quick Reference\n\n| Widget            | Purpose              |\n| ----------------- | -------------------- |\n| BlocBuilder       | UI rebuild           |\n| BlocListener      | Side effects         |\n| BlocConsumer      | Both                 |\n| BlocProvider      | Dependency injection |\n| MultiBlocProvider | Multiple blocs       |\n\n<!-- File: .kilocode/skills/flutter-expert/references/gorouter-navigation.md (Tokens: ~630 | Category: anthropic-skill) -->\n# GoRouter Navigation\n\n## Basic Setup\n\n```dart\nimport 'package:go_router/go_router.dart';\n\nfinal goRouter = GoRouter(\n  initialLocation: '/',\n  redirect: (context, state) {\n    final isLoggedIn = /* check auth */;\n    if (!isLoggedIn && !state.matchedLocation.startsWith('/auth')) {\n      return '/auth/login';\n    }\n    return null;\n  },\n  routes: [\n    GoRoute(\n      path: '/',\n      builder: (context, state) => const HomeScreen(),\n      routes: [\n        GoRoute(\n          path: 'details/:id',\n          builder: (context, state) {\n            final id = state.pathParameters['id']!;\n            return DetailsScreen(id: id);\n          },\n        ),\n      ],\n    ),\n    GoRoute(\n      path: '/auth/login',\n      builder: (context, state) => const LoginScreen(),\n    ),\n  ],\n);\n\n// In app.dart\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: goRouter,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n    );\n  }\n}\n```\n\n## Navigation Methods\n\n```dart\n// Navigate and replace history\ncontext.go('/details/123');\n\n// Navigate and add to stack\ncontext.push('/details/123');\n\n// Go back\ncontext.pop();\n\n// Replace current route\ncontext.pushReplacement('/home');\n\n// Navigate with extra data\ncontext.push('/details/123', extra: {'title': 'Item'});\n\n// Access extra in destination\nfinal extra = GoRouterState.of(context).extra as Map<String, dynamic>?;\n```\n\n## Shell Routes (Persistent UI)\n\n```dart\nfinal goRouter = GoRouter(\n  routes: [\n    ShellRoute(\n      builder: (context, state, child) {\n        return ScaffoldWithNavBar(child: child);\n      },\n      routes: [\n        GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),\n        GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),\n        GoRoute(path: '/settings', builder: (_, __) => const SettingsScreen()),\n      ],\n    ),\n  ],\n);\n```\n\n## Query Parameters\n\n```dart\nGoRoute(\n  path: '/search',\n  builder: (context, state) {\n    final query = state.uri.queryParameters['q'] ?? '';\n    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1') ?? 1;\n    return SearchScreen(query: query, page: page);\n  },\n),\n\n// Navigate with query params\ncontext.go('/search?q=flutter&page=2');\n```\n\n## Quick Reference\n\n| Method | Behavior |\n|--------|----------|\n| `context.go()` | Navigate, replace stack |\n| `context.push()` | Navigate, add to stack |\n| `context.pop()` | Go back |\n| `context.pushReplacement()` | Replace current |\n| `:param` | Path parameter |\n| `?key=value` | Query parameter |\n\n<!-- File: .kilocode/skills/flutter-expert/references/performance.md (Tokens: ~494 | Category: anthropic-skill) -->\n# Performance Optimization\n\n## Profiling Commands\n\n```bash\n# Run in profile mode\nflutter run --profile\n\n# Analyze performance\nflutter analyze\n\n# DevTools\nflutter pub global activate devtools\nflutter pub global run devtools\n```\n\n## Common Optimizations\n\n### Const Widgets\n```dart\n// ❌ Rebuilds every time\nWidget build(BuildContext context) {\n  return Container(\n    padding: EdgeInsets.all(16),  // Creates new object\n    child: Text('Hello'),\n  );\n}\n\n// ✅ Const prevents rebuilds\nWidget build(BuildContext context) {\n  return Container(\n    padding: const EdgeInsets.all(16),\n    child: const Text('Hello'),\n  );\n}\n```\n\n### Selective Provider Watching\n```dart\n// ❌ Rebuilds on any user change\nfinal user = ref.watch(userProvider);\nreturn Text(user.name);\n\n// ✅ Only rebuilds when name changes\nfinal name = ref.watch(userProvider.select((u) => u.name));\nreturn Text(name);\n```\n\n### RepaintBoundary\n```dart\n// Isolate expensive widgets\nRepaintBoundary(\n  child: ComplexAnimatedWidget(),\n)\n```\n\n### Image Optimization\n```dart\n// Use cached_network_image\nCachedNetworkImage(\n  imageUrl: url,\n  placeholder: (_, __) => const CircularProgressIndicator(),\n  errorWidget: (_, __, ___) => const Icon(Icons.error),\n)\n\n// Resize images\nImage.network(\n  url,\n  cacheWidth: 200,  // Resize in memory\n  cacheHeight: 200,\n)\n```\n\n### Compute for Heavy Operations\n```dart\n// ❌ Blocks UI thread\nfinal result = heavyComputation(data);\n\n// ✅ Runs in isolate\nfinal result = await compute(heavyComputation, data);\n```\n\n## Performance Checklist\n\n| Check | Solution |\n|-------|----------|\n| Unnecessary rebuilds | Add `const`, use `select()` |\n| Large lists | Use `ListView.builder` |\n| Image loading | Use `cached_network_image` |\n| Heavy computation | Use `compute()` |\n| Jank in animations | Use `RepaintBoundary` |\n| Memory leaks | Dispose controllers |\n\n## DevTools Metrics\n\n- **Frame rendering time**: < 16ms for 60fps\n- **Widget rebuilds**: Minimize unnecessary rebuilds\n- **Memory usage**: Watch for leaks\n- **CPU profiler**: Identify bottlenecks\n\n<!-- File: .kilocode/skills/flutter-expert/references/project-structure.md (Tokens: ~735 | Category: anthropic-skill) -->\n# Project Structure\n\n## Feature-Based Structure\n\n```\nlib/\n├── main.dart\n├── app.dart\n├── core/\n│   ├── constants/\n│   │   ├── colors.dart\n│   │   └── strings.dart\n│   ├── theme/\n│   │   ├── app_theme.dart\n│   │   └── text_styles.dart\n│   ├── utils/\n│   │   ├── extensions.dart\n│   │   └── validators.dart\n│   └── errors/\n│       └── failures.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── repositories/\n│   │   │   └── datasources/\n│   │   ├── domain/\n│   │   │   ├── entities/\n│   │   │   └── usecases/\n│   │   ├── presentation/\n│   │   │   ├── screens/\n│   │   │   └── widgets/\n│   │   └── providers/\n│   │       └── auth_provider.dart\n│   └── home/\n│       ├── data/\n│       ├── domain/\n│       ├── presentation/\n│       └── providers/\n├── shared/\n│   ├── widgets/\n│   │   ├── buttons/\n│   │   ├── inputs/\n│   │   └── cards/\n│   ├── services/\n│   │   ├── api_service.dart\n│   │   └── storage_service.dart\n│   └── models/\n│       └── user.dart\n└── routes/\n    └── app_router.dart\n```\n\n## pubspec.yaml Essentials\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  # State Management\n  flutter_riverpod: ^2.5.0\n  riverpod_annotation: ^2.3.0\n  # Navigation\n  go_router: ^14.0.0\n  # Networking\n  dio: ^5.4.0\n  # Code Generation\n  freezed_annotation: ^2.4.0\n  json_annotation: ^4.8.0\n  # Storage\n  shared_preferences: ^2.2.0\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.4.0\n  riverpod_generator: ^2.4.0\n  freezed: ^2.5.0\n  json_serializable: ^6.8.0\n  flutter_lints: ^4.0.0\n```\n\n## Feature Layer Responsibilities\n\n| Layer | Responsibility |\n|-------|----------------|\n| **data/** | API calls, local storage, DTOs |\n| **domain/** | Business logic, entities, use cases |\n| **presentation/** | UI screens, widgets |\n| **providers/** | Riverpod providers for feature |\n\n## Main Entry Point\n\n```dart\n// main.dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Hive.initFlutter();\n  runApp(const ProviderScope(child: MyApp()));\n}\n\n// app.dart\nclass MyApp extends ConsumerWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final router = ref.watch(routerProvider);\n\n    return MaterialApp.router(\n      routerConfig: router,\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: ThemeMode.system,\n    );\n  }\n}\n```\n\n<!-- File: .kilocode/skills/flutter-expert/references/riverpod-state.md (Tokens: ~720 | Category: anthropic-skill) -->\n# Riverpod State Management\n\n## Provider Types\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\n\n// Simple state\nfinal counterProvider = StateProvider<int>((ref) => 0);\n\n// Async state (API calls)\nfinal usersProvider = FutureProvider<List<User>>((ref) async {\n  final api = ref.read(apiProvider);\n  return api.getUsers();\n});\n\n// Stream state (real-time)\nfinal messagesProvider = StreamProvider<List<Message>>((ref) {\n  return ref.read(chatServiceProvider).messagesStream;\n});\n```\n\n## Notifier Pattern (Riverpod 2.0)\n\n```dart\n@riverpod\nclass TodoList extends _$TodoList {\n  @override\n  List<Todo> build() => [];\n\n  void add(Todo todo) {\n    state = [...state, todo];\n  }\n\n  void toggle(String id) {\n    state = [\n      for (final todo in state)\n        if (todo.id == id) todo.copyWith(completed: !todo.completed) else todo,\n    ];\n  }\n\n  void remove(String id) {\n    state = state.where((t) => t.id != id).toList();\n  }\n}\n\n// Async Notifier\n@riverpod\nclass UserProfile extends _$UserProfile {\n  @override\n  Future<User> build() async {\n    return ref.read(apiProvider).getCurrentUser();\n  }\n\n  Future<void> updateName(String name) async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      final updated = await ref.read(apiProvider).updateUser(name: name);\n      return updated;\n    });\n  }\n}\n```\n\n## Usage in Widgets\n\n```dart\n// ConsumerWidget (recommended)\nclass TodoScreen extends ConsumerWidget {\n  const TodoScreen({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(todoListProvider);\n\n    return ListView.builder(\n      itemCount: todos.length,\n      itemBuilder: (context, index) {\n        final todo = todos[index];\n        return ListTile(\n          title: Text(todo.title),\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n        );\n      },\n    );\n  }\n}\n\n// Selective rebuilds with select\nclass UserAvatar extends ConsumerWidget {\n  const UserAvatar({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final avatarUrl = ref.watch(userProvider.select((u) => u?.avatarUrl));\n\n    return CircleAvatar(\n      backgroundImage: avatarUrl != null ? NetworkImage(avatarUrl) : null,\n    );\n  }\n}\n\n// Async state handling\nclass UserProfileScreen extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final userAsync = ref.watch(userProfileProvider);\n\n    return userAsync.when(\n      data: (user) => Text(user.name),\n      loading: () => const CircularProgressIndicator(),\n      error: (err, stack) => Text('Error: $err'),\n    );\n  }\n}\n```\n\n## Quick Reference\n\n| Provider | Use Case |\n|----------|----------|\n| `Provider` | Computed/derived values |\n| `StateProvider` | Simple mutable state |\n| `FutureProvider` | Async operations (one-time) |\n| `StreamProvider` | Real-time data streams |\n| `NotifierProvider` | Complex state with methods |\n| `AsyncNotifierProvider` | Async state with methods |\n\n<!-- File: .kilocode/skills/flutter-expert/references/widget-patterns.md (Tokens: ~574 | Category: anthropic-skill) -->\n# Widget Patterns\n\n## Optimized Widget Pattern\n\n```dart\n// Use const constructors\nclass OptimizedCard extends StatelessWidget {\n  final String title;\n  final VoidCallback onTap;\n\n  const OptimizedCard({\n    super.key,\n    required this.title,\n    required this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Text(title, style: Theme.of(context).textTheme.titleMedium),\n        ),\n      ),\n    );\n  }\n}\n```\n\n## Responsive Layout\n\n```dart\nclass ResponsiveLayout extends StatelessWidget {\n  final Widget mobile;\n  final Widget? tablet;\n  final Widget desktop;\n\n  const ResponsiveLayout({\n    super.key,\n    required this.mobile,\n    this.tablet,\n    required this.desktop,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(\n      builder: (context, constraints) {\n        if (constraints.maxWidth >= 1100) return desktop;\n        if (constraints.maxWidth >= 650) return tablet ?? mobile;\n        return mobile;\n      },\n    );\n  }\n}\n```\n\n## Custom Hooks (flutter_hooks)\n\n```dart\nimport 'package:flutter_hooks/flutter_hooks.dart';\n\nclass CounterWidget extends HookWidget {\n  @override\n  Widget build(BuildContext context) {\n    final counter = useState(0);\n    final controller = useTextEditingController();\n\n    useEffect(() {\n      // Setup\n      return () {\n        // Cleanup\n      };\n    }, []);\n\n    return Column(\n      children: [\n        Text('Count: ${counter.value}'),\n        ElevatedButton(\n          onPressed: () => counter.value++,\n          child: const Text('Increment'),\n        ),\n      ],\n    );\n  }\n}\n```\n\n## Sliver Patterns\n\n```dart\nCustomScrollView(\n  slivers: [\n    SliverAppBar(\n      expandedHeight: 200,\n      pinned: true,\n      flexibleSpace: FlexibleSpaceBar(\n        title: const Text('Title'),\n        background: Image.network(imageUrl, fit: BoxFit.cover),\n      ),\n    ),\n    SliverList(\n      delegate: SliverChildBuilderDelegate(\n        (context, index) => ListTile(title: Text('Item $index')),\n        childCount: 100,\n      ),\n    ),\n  ],\n)\n```\n\n## Key Optimization Patterns\n\n| Pattern | Implementation |\n|---------|----------------|\n| **const widgets** | Add `const` to static widgets |\n| **keys** | Use `Key` for list items |\n| **select** | `ref.watch(provider.select(...))` |\n| **RepaintBoundary** | Isolate expensive repaints |\n| **ListView.builder** | Lazy loading for lists |\n| **const constructors** | Always use when possible |\n</yakaai_skills>"}