.claude/skills/configure-navigation/SKILL.md
Configures GoRouter navigation for Flutter following the project architecture. Use whenever adding routes, navigation guards, deep links, or modifying lib/config/routes/**. Covers AppRoutes constants, GoRoute setup, push/go/pop patterns, navigation from BlocListener, guards, and common mistakes. Activate even when the user says 'add a new screen to the app routing', 'how do I navigate to another screen', 'how do I pass parameters between pages', 'back button not working', 'redirect to login if not authenticated', 'deep link is not working', or 'I need to add a route' without explicitly mentioning GoRouter or AppRoutes.
npx skillsauth add andrelucassvt/CleanMacForDevsWeb configure-navigationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
app_routes.dart e adicione GoRoute em app_router.dart.context.push/go/pop/replace — SEMPRE na View, nunca no Cubit.LoginNavigateToHome) e reaja via BlocListener na View.:id na rota e acesse via state.pathParameters['id']!.extra no context.push e recupere em state.extra as T.lib/config/routes/
├── app_routes.dart # Constantes de rotas
└── app_router.dart # Configuração do GoRouter
class AppRoutes {
AppRoutes._();
static const String splash = '/';
static const String home = '/home';
// Feature: Auth
static const String login = '/login';
static const String register = '/register';
// Feature: Profile
static const String profile = '/profile';
static const String editProfile = '/profile/edit';
// Feature: Products
static const String products = '/products';
static const String productDetails = '/products/:id';
}
Regras:
static const String/profile/edit/products/:idimport 'package:go_router/go_router.dart';
final GoRouter appRouter = GoRouter(
initialLocation: AppRoutes.splash,
routes: [
GoRoute(
path: AppRoutes.splash,
builder: (context, state) => const SplashView(),
),
GoRoute(
path: AppRoutes.home,
builder: (context, state) => const HomeView(),
),
],
);
GoRoute(
path: AppRoutes.productDetails, // '/products/:id'
builder: (context, state) {
final id = state.pathParameters['id']!; // ✅ Non-null
return ProductDetailsView(productId: id);
},
),
GoRoute(
path: AppRoutes.profile,
builder: (context, state) => const ProfileView(),
routes: [
GoRoute(
path: 'edit', // ⚠️ SEM barra inicial em sub-rotas
builder: (context, state) => const EditProfileView(),
),
],
),
context.push(AppRoutes.home)
context.push('/products/${product.id}')
context.push('/products?category=electronics')
context.go(AppRoutes.home)
context.replace(AppRoutes.login)
context.pop()
context.pop('resultado') // Passa resultado para quem chamou
final result = await context.push<String>(AppRoutes.editProfile);
if (result != null) { /* usa resultado */ }
class HomeCubit extends Cubit<HomeState> {
void navigateToDetails(BuildContext context) {
context.push('/details'); // ❌ Nunca
}
}
ElevatedButton(
onPressed: () {
_cubit.selectProduct(product); // Lógica no Cubit
context.push('/products/${product.id}'); // Navegação na View
},
child: Text(l10n.viewDetailsButton),
)
// State
class HomeNavigateToDetails extends HomeState {
const HomeNavigateToDetails(this.productId);
final String productId;
}
// Cubit
void selectProduct(String id) => emit(HomeNavigateToDetails(id));
// View
BlocListener<HomeCubit, HomeState>(
listener: (context, state) {
if (state is HomeNavigateToDetails) {
context.push('/products/${state.productId}');
}
},
child: BlocBuilder<HomeCubit, HomeState>(
builder: (context, state) { /* ... */ },
),
)
BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) context.go(AppRoutes.home);
},
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) { /* ... */ },
),
)
// Navegação
context.push(AppRoutes.productDetails, extra: product);
// No router
GoRoute(
path: AppRoutes.productDetails,
builder: (context, state) {
final product = state.extra as ProductEntity;
return ProductDetailsView(product: product);
},
),
final GoRouter appRouter = GoRouter(
initialLocation: AppRoutes.splash,
redirect: (context, state) {
final isLoggedIn = _checkIfLoggedIn();
final isGoingToLogin = state.matchedLocation == AppRoutes.login;
if (!isLoggedIn && !isGoingToLogin) return AppRoutes.login;
if (isLoggedIn && isGoingToLogin) return AppRoutes.home;
return null;
},
routes: [ /* ... */ ],
);
final GoRouter appRouter = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) => MainScaffold(child: child),
routes: [
GoRoute(path: AppRoutes.home, builder: (_, __) => const HomeView()),
GoRoute(path: AppRoutes.products, builder: (_, __) => const ProductsView()),
GoRoute(path: AppRoutes.profile, builder: (_, __) => const ProfileView()),
],
),
],
);
app_routes.dartGoRoute em app_router.dartBlocListener| Erro | Correto |
|---|---|
| Navigator.of(context).push(...) | context.push(AppRoutes.home) |
| Barra inicial em sub-rota: path: '/edit' | path: 'edit' (sem barra) |
| state.pathParameters['id'] sem ! | state.pathParameters['id']! |
| Navegação no Cubit com BuildContext | Navegação na View ou BlocListener |
Última atualização: 28 de março de 2026
testing
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Activate even when the user says 'create a skill for X', 'the skill is not triggering', 'improve this skill description', 'the agent is not using the skill', 'add a skill to teach the agent how to do X', 'this skill is wrong', or 'update the skill' without explicitly mentioning evals or benchmark.
development
Implements Flutter reusable widgets following the project architecture. Use whenever creating or modifying widgets in presentation/<feature>/widgets/, presentation/<feature>/content/, or common/widgets/. Covers StatelessWidget vs StatefulWidget decision, Entity as parameter, i18n, dispose, componentization rules, and when to access the Cubit via context.read. Activate even when the user says 'extract this to a widget', 'create a list item widget', 'build a reusable card', 'factor out this UI block', 'create a component for this', or 'this View is getting too big' without explicitly mentioning StatelessWidget or reusable components.
tools
Implements Flutter View screens following the project architecture. Use whenever creating or modifying a View (StatefulWidget + Cubit + BlocBuilder), adding a new screen, wiring up BlocBuilder/BlocConsumer/BlocListener, setting up SafeArea, or navigating from the View. Covers State, Cubit, View file, route, DI registration, and common mistakes. Activate even when the user just says "create a screen" or "add a new page", without explicitly mentioning Cubit or BLoC.
testing
Implements Flutter Cubit and State (View Model layer) following the project architecture. Use whenever creating or modifying a Cubit or State class, adding an async method to a Cubit, handling form submission or validation, implementing debounce search, managing loading/error/navigation states, or wiring a Cubit to a Repository or StorageService. Covers sealed States, async patterns with Result<T>, CRUD Cubits, local persistence via StorageService, navigation states, debounce, and common mistakes. Activate even when the user says "add a method", "handle the loading state", or "save locally" without explicitly mentioning Cubit or BLoC.