# Pathrule Pattern: Flutter (1.0.0)
# ::pathrule:package:flutter

### [RULE] Dispose every controller, stream, and listener  (path: /lib)
<!-- scope: folder | priority: high | strict -->

Flutter does not garbage-collect your subscriptions. A controller or stream you create and forget keeps firing after the widget is gone - a leak, a memory climb, and often a `setState() called after dispose` crash.

- For every `TextEditingController`, `AnimationController`, `ScrollController`, `FocusNode`, `StreamSubscription`, or `addListener` you create in a `StatefulWidget`, release it in `dispose()` (`controller.dispose()`, `subscription.cancel()`, `removeListener(...)`).
- Create these in `initState` (or as late finals), not in `build()`; creating a controller in `build` makes a new one every frame and leaks all of them.
- Guard async callbacks that touch state with a `mounted` check before `setState`, so a response that arrives after the widget is disposed does not crash.
- Prefer Riverpod providers or `AutomaticKeepAlive`/hooks that manage disposal for you when the lifecycle is non-trivial; the rule is that nothing with a lifecycle is left un-released.

---

### [RULE] Keep build() pure and widgets small; use const  (path: /lib)
<!-- scope: folder | priority: medium | advisory -->

`build()` can run many times per second. Anything expensive or stateful inside it runs that often, which is where jank comes from.

- `build()` is a pure function of the widget's inputs and state: read data and return widgets. Do not perform network calls, heavy computation, or side effects in it; move those to `initState`, an event handler, or a provider.
- Mark widgets and subtrees `const` wherever the inputs are constant. A `const` widget is built once and skipped on rebuild, which is the cheapest performance win in Flutter and the one most often missed.
- Split large `build` methods into small, focused widget classes rather than private `_buildX()` helper methods. Real widgets get their own rebuild boundary and can be `const`; helper methods rebuild with the whole parent.
- Use `Key`s when reordering or conditionally swapping widgets of the same type so Flutter preserves state correctly instead of mismatching elements.

---

### [MEMORY] State management with Riverpod  (path: /lib)

We use Riverpod as the state-management boundary so business logic lives outside the widget tree and can be tested without pumping widgets.

- Keep mutable and async state in providers, not in widget `setState`. `setState` is fine for purely local, ephemeral UI state (a toggled expansion, a hover); anything shared, fetched, or business-relevant belongs in a provider.
- Use the provider type that fits: a plain provider for derived values, an async notifier for fetched state (it exposes loading/error/data so the UI renders all three), a notifier for mutable state with methods.
- Read providers with `ref.watch` in `build` to rebuild on change, and `ref.read` in callbacks for one-off actions. Don't `watch` inside a callback or `read` something the UI must react to.
- Keep providers small and composable; one provider per concern, composed via `ref.watch` of other providers, beats one giant app-state object.

See /lib for the feature-first structure and go_router navigation memories.

---

### [MEMORY] Feature-first project structure  (path: /lib)

We organize `lib/` by feature, not by global technical layer, so a feature's code lives together and stays easy to find, change, and delete.

- Structure: `lib/features/<feature>/` each containing its own `presentation/` (widgets/screens), `application/` or `providers/` (state), and `data/` (repositories/models). Shared building blocks live in `lib/core/` or `lib/shared/`.
- Avoid the layer-first anti-pattern (`lib/widgets/`, `lib/models/`, `lib/services/` spanning every feature); it scatters one feature across the tree and makes ownership unclear.
- Keep cross-feature dependencies explicit and one-directional through shared/core; a feature should not reach into another feature's internals.
- Co-locate tests with the feature (or mirror the structure under `test/`) so behavior and its tests evolve together.

See /lib for the Riverpod state and go_router navigation memories.

---

### [MEMORY] Navigation with go_router  (path: /lib)

We route with `go_router` so navigation is declarative, deep-linkable, and consistent across mobile and web.

- Define routes declaratively in one `GoRouter` configuration with named or typed routes; navigate with `context.go(...)` / `context.push(...)` instead of building `Navigator.push(MaterialPageRoute(...))` ad hoc throughout the app.
- Centralize auth and access logic in the router's `redirect`, returning the login route when unauthenticated, so guarding is in one place rather than checked in every screen.
- Use typed/named routes and pass parameters through the route definition (path/query params) so deep links and state restoration work and route changes are refactor-safe.
- Keep route definitions out of widgets; the router config is app-level configuration, and screens just request navigation.

See /lib for the feature-first structure and Riverpod state memories.

---

### [SKILL] flutter-screen-checklist  (path: /)

---
name: flutter-screen-checklist
description: Checklist for adding or changing a Flutter screen or feature. Run before merging any widget, provider, or route change.
---

# Flutter screen/feature checklist

- [ ] Every controller, animation controller, stream subscription, focus node, and listener created is released in `dispose()`.
- [ ] Controllers are created in `initState`/as fields, never in `build()`; async callbacks check `mounted` before `setState`.
- [ ] `build()` is pure - no network, no heavy compute, no side effects; that work is in `initState`, handlers, or providers.
- [ ] Constant subtrees are `const`; large builds are split into real widget classes, not `_buildX()` helpers.
- [ ] `Key`s are set where same-type widgets are reordered or swapped so state is preserved.
- [ ] Shared/async/business state is in Riverpod providers (loading/error/data handled); `setState` is only for local ephemeral UI.
- [ ] `ref.watch` drives rebuilds in `build`; `ref.read` is used in callbacks.
- [ ] Code lives under `lib/features/<feature>/` with presentation/state/data separated; shared code in core/shared.
- [ ] Navigation goes through go_router (`context.go/push`, typed/named routes); auth handled in the router `redirect`.
- [ ] `flutter analyze` is clean; widget/unit tests cover the new behavior.
