Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill zenrouter명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | zenrouter |
| description | > Use when this capability is needed. |
This project uses zenrouter — a Flutter router that supports imperative, declarative, and coordinator-based navigation. This skill covers the Coordinator + RouteModule pattern, which is the right choice when you need deep linking, URL sync, layouts, and modular feature organisation.
Deep-dive references — read these only when you need the specific topic:
File When to read ADVANCED.md Coordinator-as-Module, tab navigation, composable redirect rules, parameter route examples MIXIN.md Full reference for RouteGuard,RouteRedirect,RouteDeepLink,RouteTransition,RouteQueryParameters,RouteRestorableNAVIGATION.md When to use pushvsnavigatevsreplaceand other navigation methods
| Type | Package | Purpose |
|---|---|---|
RouteTarget | zenrouter_core | Base class for all routes; identity via props |
RouteUnique | zenrouter_core | Mixin — adds URI identity; required for coordinator routes |
Coordinator<T> | zenrouter | Central hub: owns NavigationPaths, parses URIs, drives Flutter Router |
CoordinatorModular<T> | zenrouter_core | Mixin on Coordinator — splits route parsing across RouteModules |
RouteModule<T> | zenrouter_core | Handles one feature's URI patterns and navigation paths |
NavigationPath<T> | zenrouter | Mutable stack of routes; one per layout group |
IndexedStackPath<T> | zenrouter | Fixed set of routes for tab-bar style navigation |
RouteLayout<T> | zenrouter | Mixin — layout route that wraps nested routes (shell, tab bar, drawer, etc.) |
RouteRedirectRule<T> | zenrouter_core | Mixin — delegates redirect logic to a list of RedirectRules |
RedirectRule<T> | zenrouter_core | Single composable guard; returns continueRedirect, redirectTo, or stop |
All routes in a coordinator must extend RouteTarget with RouteUnique:
abstract class AppRoute extends RouteTarget with RouteUnique {
@override
Widget build(covariant Coordinator coordinator, BuildContext context);
}
class AppCoordinator extends Coordinator<AppRoute> {
late final homeStack = NavigationPath<AppRoute>.createWith(
label: 'home',
coordinator: this,
)..bindLayout(HomeLayout.new);
@override
List<StackPath> get paths => [...super.paths, homeStack];
@override
AppRoute parseRouteFromUri(Uri uri) => switch (uri.pathSegments) {
[] || ['home'] => HomeRoute(),
['product', final id] => ProductRoute(id: id),
_ => NotFoundRoute(uri: uri),
};
}
// Wire up:
MaterialApp.router(routerConfig: AppCoordinator())
Add CoordinatorModular<T> to delegate URI parsing across feature modules:
class AppCoordinator extends Coordinator<AppRoute>
with CoordinatorModular<AppRoute> {
@override
Set<RouteModule<AppRoute>> defineModules() => {
AuthModule(this),
ShopModule(this),
ProfileModule(this),
};
@override
AppRoute notFoundRoute(Uri uri) => NotFoundRoute(uri: uri);
}
Rules:
defineModules() determines parsing priority — first non-null result wins.CoordinatorModular overrides parseRouteFromUri automatically; do not override it.notFoundRoute is called when all modules return null.For nested feature groups with sub-modules, see Coordinator as Module in ADVANCED.md.
class ShopModule extends RouteModule<AppRoute> {
ShopModule(super.coordinator);
late final shopStack = NavigationPath<AppRoute>.createWith(
coordinator: coordinator, // ← always the inherited `coordinator` field (= root)
label: 'shop',
)..bindLayout(ShopLayout.new);
@override
List<StackPath> get paths => [shopStack];
@override
FutureOr<AppRoute?> parseRouteFromUri(Uri uri) => switch (uri.pathSegments) {
['shop'] => ShopHomeRoute(),
['shop', 'products', final id] => ProductDetailRoute(id: id),
_ => null, // ← MUST return null for unrecognised URIs
};
}
Rules:
null for unrecognised URIs so other modules can claim them.coordinator (inherited field) for NavigationPath.createWith — it always
refers to the root coordinator that owns the navigation state.bindLayout(LayoutClass.new) takes the constructor, not an instance.// Standard route
class ShopHomeRoute extends AppRoute {
@override
Object? get parentLayoutKey => ShopLayout; // matches RouteLayout.layoutKey (default: runtimeType)
@override
Uri toUri() => Uri.parse('/shop');
@override
Widget build(covariant AppCoordinator coordinator, BuildContext context) {
return ShopHomePage(
onProductTap: (id) => coordinator.push(ProductDetailRoute(id: id)),
);
}
}
// Route with parameters — must override props
class ProductDetailRoute extends AppRoute {
ProductDetailRoute({required this.id});
final String id;
@override
List<Object?> get props => [id];
@override
Object? get parentLayoutKey => ShopLayout;
@override
Uri toUri() => Uri.parse('/shop/products/$id');
@override
Widget build(covariant AppCoordinator coordinator, BuildContext context) =>
ProductDetailPage(id: id);
}
Rules:
parentLayoutKey must exactly match layoutKey of the target RouteLayout.
Default layoutKey is runtimeType, so using the layout class Type is simplest.props for routes with parameters.toUri() is used for deep linking and URL sync.For redirect-only routes, see RedirectRule in ADVANCED.md.
class ShopLayout extends AppRoute with RouteLayout<AppRoute> {
@override
StackPath<AppRoute> resolvePath(covariant AppCoordinator coordinator) =>
coordinator.getModule<ShopCoordinator>().shopStack;
// layoutKey defaults to runtimeType — override only if you need a custom value
// @override Object get layoutKey => 'ShopLayout';
@override
Widget build(covariant AppCoordinator coordinator, BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Shop')),
body: buildPath(coordinator), // renders the active child route
);
}
}
Rules:
buildPath(coordinator) to render child routes — do not call super.build().resolvePath must return the exact NavigationPath that was bindLayout-ed in the module.coordinator.push(ProductDetailRoute(id: '42')); // add to stack
coordinator.navigate(ShopHomeRoute()); // pop-to-existing or push new
coordinator.replace(SettingsRoute()); // full state reset
coordinator.pop(); // pop top route
// Navigate from a URI string (e.g. deep link):
final route = await coordinator.parseRouteFromUri(Uri.parse('/shop/products/42'));
coordinator.push(route!);
Redirect rules run automatically on every navigation call.
For full details on
pushReplacement,pushOrMoveToTop,tryPop,recover, and decision flowcharts, see NAVIGATION.md.
lib/src/router/
├── coordinator.dart ← Root Coordinator or Coordinator-as-Module
├── route.dart ← Base route type (e.g. AppRoute)
├── _public.dart ← Barrel: export module + public routes + public rules
├── rules/
│ ├── auth_required.dart
│ └── force_redirect.dart
└── routes/
├── (auth)/ ← Route group (shares a layout)
│ ├── _layout.dart ← Layout for this group
│ ├── sign_in.dart ← /sign-in
│ └── forgot_password.dart
├── (dashboard)/
│ ├── _layout.dart
│ ├── _index.dart ← Index / redirect-only route
│ └── transactions/ ← URI segment directory
│ ├── _index.dart ← /transactions
│ └── [id].dart ← /transactions/:id (named parameter route)
│ └── blog/
│ ├── _layout.dart ← blog layout
│ └── [...slug].dart ← /blog/* (catch-all parameter route)
└── not_found.dart
Conventions:
(group)/ — parenthesised directories are layout groups (organisational only). They do not appear in the URI. Routes inside share a layout.group/ — bare directories (no parentheses) do appear in the URI. transactions/ → the URI includes /transactions/...._layout.dart — the RouteLayout for its group; prefixed with _ because it's structural, not a user-facing route._index.dart — the index route for a directory (often a redirect-only route).[param].dart — a named parameter route file. The brackets mirror dynamic URI segments (e.g. [id].dart → /transactions/:id).[...param].dart — a catch-all parameter route file. Captures all remaining URI segments as a single list (e.g. [...slug].dart → /blog/*)._public.dart — barrel file that exports only public symbols.rules/ — reusable RedirectRule implementations.For detailed examples of parameter route classes, see Named Parameter Routes and Catch-All Parameter Routes in ADVANCED.md.
| Pattern | Example | When |
|---|---|---|
<Feature>Route | SignInRoute, TransactionRoute | Standard page route |
<Feature>DetailRoute | TransactionDetailRoute | Detail page with [id] |
<Feature>IndexRoute | DashboardIndexRoute | Index / redirect-only route |
<Feature>Tab | HomeTab, ShopTab | Tab in an IndexedStackPath |
NotFoundRoute | NotFoundRoute | 404 catch-all |
| Pattern | Example | When |
|---|---|---|
<Feature>Layout | AuthLayout, DashboardLayout | Layout shell |
<Feature>Module | AuthModule, ShopModule | Simple RouteModule |
<Feature>Coordinator | ShopCoordinator | Coordinator-as-Module (has sub-modules) |
<Condition>Rule | AuthRequiredRule, AlreadyAuthRule | Redirect rule |
| Pattern | Example | Route |
|---|---|---|
/feature | /sign-in | SignInRoute |
/feature/:id | /transaction/abc123 | TransactionDetailRoute(id: 'abc123') |
/group/feature | /shop/products | ProductListRoute |
/group/feature/:id | /shop/products/42 | ProductDetailRoute(id: '42') |
Use kebab-case for URI segments. Use singular nouns for resource detail paths (/transaction/:id not /transactions/:id).
NavigationPath labels use kebab-case: 'auth', 'dashboard', 'shop-products'.
parentLayoutKey, toUri(), build(), props.parseRouteFromUri of the owning module._public.dart barrel if navigated to from outside.(group)/_layout.dart with RouteLayout, new NavigationPath with bindLayout, add to paths.module.dart extending RouteModule<T>, register in parent's defineModules().| Mistake | Fix |
|---|---|
parseRouteFromUri in a module not returning null for non-owned URIs | Must return null so other modules can claim the URI |
parentLayoutKey doesn't match layoutKey | Default layoutKey is usually runtimeType — use the layout class Type as parentLayoutKey |
Forgetting ...super.paths when overriding paths | Always spread super.paths to include root and inherited module paths |
Overriding parseRouteFromUri on a CoordinatorModular coordinator | Don't — CoordinatorModular handles it; override notFoundRoute instead |
Standalone Coordinator returning null from parseRouteFromUri | Standalone coordinators must never return null; add a catch-all case |
Source: definev/zenrouter — distributed by TomeVault.