Create feature
Skill anoopsg/agent_rules/src/exclusive/skills/create-feature
Behavioral blueprints for agents.
npx -y skills add anoopsg/agent_rules --skill create-featureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when asked to create a new feature or sub-feature following the DDD-lite architecture, or set up state, view, and page layers for a domain.
SKILL.md
6.3 KB, as published. Nobody here has run it
Feature Creation Skill
This skill outlines the standard process for creating a new feature in the project, following the DDD-lite architecture. This guide covers both simple domains (like Settings) and complex domains (like Auth).
1. Directory Structure
Features are located in lib/src/features/. The structure depends on the
complexity of the domain.
A. Simple Domain (e.g., Settings, Explore)
If the feature is self-contained and doesn't require separate sub-modules,
keep it flat under the feature root folder. No _self folder is used.
lib/src/features/settings/
├── state/ # Domain-specific state
│ └── settings_notifier.dart
├── view/ # UI implementation
│ ├── settings_page.dart # Entry point & Bindings
│ ├── settings_view.dart # Mobile UI
│ └── settings_view_web.dart # Web UI
└── settings.dart # Feature barrel file
B. Complex Domain (e.g., Auth, Account)
If the feature has multiple sub-domains, it is structured as a complex
domain. The _self folder is used for root-level domain control, shared
domain state (e.g., auth status, user metadata, cache), and layout/shell
views that host or wrap the sub-features.
Example: Auth Domain
lib/src/features/auth/
├── _self/ # Shared domain logic & state
│ └── state/
│ └── auth_notifier.dart # Global auth status
├── login/ # Sub-domain feature
│ ├── state/ # Local feature state
│ │ └── login_notifier.dart
│ ├── view/ # Local UI
│ │ ├── login_page.dart
│ │ ├── login_view.dart
│ │ └── login_view_web.dart
│ └── login.dart # Sub-domain barrel
└── auth.dart # Domain barrel (exports _self and sub-domains)
2. Naming Conventions
| Entity | Convention | Example |
|---|---|---|
| Folder | snake_case | auth_login |
| File | snake_case | login_page.dart |
| Page Class | PascalCase + Page | LoginPage |
| View Class | PascalCase + View | LoginView |
| Web View Class | PascalCase + ViewWeb | LoginViewWeb |
| Props Class | PascalCase + Props | LoginProps |
3. Creating the View Layer
3.1 The Page & Bindings
The Page widget delegates logic to _Bindings, which prepares the
ViewProps.
class MyFeaturePage extends StatelessWidget {
const MyFeaturePage({super.key});
@override
Widget build(BuildContext context) {
return _Bindings(
viewBuilder: (_, props) {
// Simple pages can return the view directly.
// Complex UIs should use MemoizedView for performance.
if (isComplex) {
return MemoizedView(
props: props,
builder: (context, memProps) => kIsWeb
? MyFeatureViewWeb(props: memProps)
: MyFeatureView(props: memProps),
);
}
return kIsWeb
? MyFeatureViewWeb(props: props)
: MyFeatureView(props: props);
},
);
}
}
3.2 ViewProps & Stale Closures
ViewProps separates Data (used for equality) from Callbacks
(ignored for equality).
[!IMPORTANT] CRITICAL RULE FOR AI: NEVER include callback functions (like
VoidCallback,Function, etc.) in theequalityPropertieslist of aViewPropssubclass. The base class has a runtime assertion that will crash the app if functions are included. ONLY include data fields.
[!CAUTION] STALE CLOSURES: Because
MemoizedViewignores callbacks in equality checks, your UI might hold a reference to an "old" closure. Never capture variables fromref.watchin a callback. Always useref.readinside the closure to ensure you have the fresh state.
final class MyProps extends ViewProps {
const MyProps({required this.data, required this.onAction});
final String data;
final VoidCallback onAction;
// CRITICAL: Only include data fields. Do NOT include functions/callbacks here!
@override
List<Object?> get equalityProperties => [data]; // Exclude onAction
}
// ... inside _Bindings ...
return viewBuilder(
context,
MyProps(
data: ref.watch(myProvider).data, // Data for equality
onAction: () {
// SAFE: ref.read ensures fresh state when callback is executed
ref.read(myProvider.notifier).doSomething();
// DANGEROUS: capturing 'data' here will cause stale closure bugs
// print(data);
},
),
);
3.3 Feature State & Notifiers
Feature state and notifiers live in the state/ directory of a feature.
- State Location: Define the state class in the same file as the provider or notifier. Do not create a separate file for the state class.
- Data Modeling: Always use
dart_mappable(@MappableClass()) for state classes to get built-in immutability (copyWith) and equality.
import 'package:dart_mappable/dart_mappable.dart';
import 'package:framework/framework.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'login_notifier.mapper.dart';
part 'login_notifier.g.dart';
/// Local form state tracking for the Auth flow.
@MappableClass()
class LoginState with LoginStateMappable implements FailureInterface {
const LoginState({this.isLoggingIn = false, this.failure});
factory LoginState.loading() => const LoginState(isLoggingIn: true);
final bool isLoggingIn;
@override
final Failure? failure;
}
@riverpod
class LoginNotifier extends _$LoginNotifier {
@override
LoginState build() => const LoginState();
void login() {
state = LoginState.loading();
// Action logic here...
}
}
4. Routing
Follow the Routing Management Skill to define the route
class and register it in the appropriate group (open, unauthenticated,
or authenticated).
5. Barrel Files (Exports)
- Sub-domain level: Export the notifier and the page.
- Domain level: Export
_selfand all sub-domain barrels. - Global level (
features/features.dart): Export all domain barrels.