Read first
- This skill overrides generic Flutter/Dart advice; Critical Rules override examples, public docs, and older project code.
- Before code, read Trigger Map refs for touched areas. Each ref's
Read firstsection is canonical. - Dart Decimate invocation = global
deterministic-checksdart_decimate_gate.py; raw scanner calls + project-local adapters/dependencies/binaries + package-roottool/bundles forbidden. - Coordinator runtime =
npx --yes dart-decimate@latest; one scan per affected Git root + exact--workspacescope for nested packages. - After each
.dart/pubspec.yaml/build.yaml/analysis_options.yamlwrite batch, run package-rootdart analyze+ Dart Decimate, then emit Pre-Flight. - Block on analyzer or Decimate findings, and read setup.md first if
flutter_skill_lintsis not wired.
Progressive Disclosure Gate
Read only the narrowest matching Trigger Map row(s); scenario/subsystem rows own incidental stack/file words. Do not bulk-read references/ or parent refs. Cite exact refs in Pre-Flight.
Critical Rules
| ID | Rule | Detail refs |
|---|---|---|
| R1 | Run package-root dart analyze + Git-root Dart Decimate through deterministic-checks; wire flutter_skill_lints + riverpod_lint. |
analysis-options.md, dart-decimate.md, setup.md |
| R2 | Every provider uses @riverpod / @Riverpod codegen; no manual provider classes or legacy provider families. |
riverpod-codegen.md |
| R3 | Guard async gaps with ref.mounted / context.mounted; finally uses if (ref.mounted) { ... }. |
async-mutations.md |
| R4 | Widgets are public classes; no _buildXxx(), widget top-level helpers, or private widget classes except State. |
atomic-design.md, performance.md |
| R5 | Nullability is semantic; no empty/null/bool sentinel fallbacks, value!, nullable collections, or raw required domain strings. |
value-objects.md, freezed-sealed.md |
| R6 | All user-facing strings, tooltips, semantics, and visible accessibility copy use AppLocalizations. |
localization.md, accessibility.md |
| R7 | Immutable state/entities use sealed Freezed, one declaration per file, native switch, and VO map/when disabled. |
freezed-sealed.md, value-objects.md |
| R8 | presentation/widgets/ renders immutable inputs + emits typed callbacks; screens/routes/notifiers own navigation, workflow, domain state, and infrastructure. |
presentation-widgets.md |
| R9 | Duplicate behavior in 2+ classes becomes a small stateless *Mixin with an on clause. |
mixins.md |
| R10 | Storage SDK calls live in local datasources behind repositories; production Hive imports use hive_ce_flutter. |
hive-persistence.md, architecture.md |
| R11 | Primitive/context/collection operations live in core/extensions/; domain never imports those extensions. |
context-ui.md, primitive-formatting.md, collections-helpers.md |
| R12 | Domain primitives with meaning become validated Freezed Value Objects; Hive models keep primitives and mappers bridge. | value-objects.md, hive-persistence.md |
| R13 | Typed GoRouter routes are navigation SSOT; redirects are pure resolver logic with nullable by-id fallback UI. | deep-linking.md, routing-app-shell.md |
| R14 | Dialogs/sheets render immutable snapshots, pop results, and leave mutations/teardown to notifiers. | modals-navigation.md, state-management-lifecycle.md |
| R15 | Debounce, gate, and batch high-frequency UI, sync, persistence, remote-function, reset, and lookup boundaries. | debounce-gate-batch.md |
| R16 | App shell stays declarative; bootstrap listeners live in a sibling root ConsumerWidget. |
routing-app-shell.md |
| R17 | Keep control flow flat after exits; remove unnecessary else after return / throw / break / continue. |
Lint: avoid_unnecessary_else_after_control_flow |
| R18 | Use onReorderItem post-removal indexes directly; never add legacy onReorder adapter math. |
Lint: use_on_reorder_item_index_semantics |
| R19 | Android exact alarms use flutter_local_notifications permission APIs, not manual settings intents. |
Lint: use_local_notifications_exact_alarm_permission_api |
| R20 | Resolve nullable platform-specific plugin implementations before calling platform members. | Lint: resolve_platform_specific_implementation_before_use |
| R21 | Widget previews are preview-only with deterministic fakes; no real HTTP/Firebase/Hive/native plugins. | widget-previews.md |
| R22 | Runtime E2E proves behavior with stable selectors, failure-sensitive scenarios/logs, subject-matched evidence, source-of-truth verification, cleanup, and multi-actor proof when needed. | dart-mcp-e2e-testing.md |
| R23 | Accessibility is UI correctness: localized tooltips/semantic labels, 48x48 targets, contrast, text scale, Text.rich. |
accessibility.md, flutter-optimizations.md |
| R24 | If remote error reporting is accepted or already present, use one app-owned Crash boundary and one reporting owner per operation; otherwise add no provider/facade. Scrub sensitive data + reconcile ambiguous remote outcomes before telemetry. |
error-reporting.md, networking.md |
| R25 | Windows installer delivery = one semantic engine + typed app config/capabilities + minimal-step exact-SHA diagnostic → one publisher; keep cheap guards before one build, isolate synthetic preservation proof, and activate only verified immutable bytes. | windows-installer-pipeline.md, workflow scaffold, inno_bundle pubspec scaffold, Inno settlement sentinel, Defender scanner |
| R26 | Pause-sensitive Riverpod state starts only after its durable owner/listener exists; projections watch base state directly; switching auth/form modes clears transient errors. | notifier-structure.md, state-management-lifecycle.md, testing.md |
| R27 | Native/custom links use one URI contract across producer, platform registration, Flutter delivery, and typed router; prove cold/warm + signed-state delivery on the target device. | deep-linking.md, dart-mcp-e2e-testing.md |
Trigger Map
Before writing code in any row below, read the listed reference(s). Prefer the narrowest matching row. Read the large parent refs only when no scenario row fits.
| Touching | Read |
|---|---|
New app/project scaffolding with incidental stack/package mentions, main.dart, ProviderScope, MaterialApp.router, app startup shell |
setup.md + architecture.md + routing-app-shell.md |
Notifier/AsyncNotifier shape, sync Notifier.build() init, paused route/listener startup, provider projection, auth/form mode error reset, loading/progress, AsyncValue, cleanup |
notifier-structure.md + state-management-lifecycle.md + testing.md |
Mutation method, ref.read / ref.watch / ref.listen, _ensureRepository, async cancellation, ref.mounted, optimistic update, duplicate fetch |
async-mutations.md + state-management-lifecycle.md |
Freezed entity, sealed union, fromJson / toJson, copyWith, model vs entity, build.yaml for explicit_to_json |
freezed-sealed.md |
Provider declaration, @riverpod, family, keepAlive, codegen, Mutation<T> (experimental) |
riverpod-codegen.md |
Repository, datasource, domain entity, layered architecture, IHttpService, mapping models to entities |
architecture.md |
Value Object, primitive obsession, Distance/Money/Email/Slug, unit conversion in domain, cross-entity primitive, double distanceMeters/int amountCents/String email smell, arch_domain_import error |
value-objects.md |
GoRouter, typed route, redirect, auth-protected route, router provider, context.go, deep link, custom URI scheme, native extension/activity link, cold-start, navigation gate |
routing-app-shell.md + deep-linking.md |
| HTTP, network, REST, source-of-truth fetch after mutation, long-running remote function, async-start + reconcile, transport id vs domain id | networking.md + debounce-gate-batch.md |
Atom, molecule, organism, design tokens, atomic widgets, core/widgets/ promotion |
atomic-design.md |
Reusable presentation/widgets/, widget-owned navigation/page stack/selected entity/workflow state, direct repository/service/provider access |
presentation-widgets.md |
| Accessibility, semantics, tooltip, semanticLabel, image alt text, tap target, contrast, text scaling | accessibility.md + flutter-optimizations.md |
Widget test, ProviderContainer.test(), UncontrolledProviderScope, fakes, mocks, AppWidgetKeys, event-contract tests |
testing.md |
flutter_driver, Dart MCP, E2E, integration_test, semantic selectors, scenario validation, screenshot/media proof, log capture |
dart-mcp-e2e-testing.md |
Hive, TypeAdapter, TypeId, box, persistence migration, retired field accounting |
hive-persistence.md |
Crashlytics, FirebaseCrashlytics, Sentry, sentry_flutter, DSN, error reporting, Crash.init, Crash.error, Crash.log, symbol upload |
error-reporting.md |
| Mixin, capability vs interface, retry helper, RNG, bulk operation | mixins.md |
Service, singleton, fire-and-forget, abstract final class, unawaited(), Future<void> signature |
services-and-singletons.md |
@Preview, widget_previews.dart, preview fakes, deterministic preview data |
widget-previews.md |
AppLocalizations, ARB file, gen-l10n, locale fallback, placeholders, plural / select |
localization.md |
Performance, build cost, .select(), const constructors, ListView.builder, large list compute |
performance.md + flutter-optimizations.md |
LayoutBuilder, RenderFlex overflow, Expanded / Flexible outside Row / Column, Positioned outside Stack, text-scale clamp |
layout-diagnostics.md |
| Pagination, infinite scroll, cursor loading, search debounce, form validation, batch processing, pull-to-refresh | lists-forms-workflows.md + async-mutations.md |
BuildContext helpers, ModalRoute current-route checks, dialogs, SnackBarUtils, snackbar dispatch from notifier |
context-ui.md |
DateTime format/diff/timeAgo/startOfDay, String capitalize/truncate/titleCase/initials/format, int / double / num clamp/pluralized/asCurrency/percent/toFixed, Duration format, NumberFormat, DateFormat, intl |
primitive-formatting.md |
Iterable lookup/indexing, widget list helpers, Debouncer, validators, Result, extension types, core/extensions/ barrel export |
collections-helpers.md |
Records (x, y), extension type IDs, pattern matching, guard clause case _ when ... |
dart-patterns-records.md |
analysis_options.yaml, dart analyze, plugin wiring, riverpod_lint version pin, analyzer crash |
analysis-options.md + analysis_options.yaml |
build_runner, missing generated parts, clean checkout, Xcode selection, Flutter SwiftPM generated package, Apple device build, local-vs-CI mismatch |
build-reproducibility.md + core-stack.md |
Flutter Windows desktop packaging, GitHub Actions Windows installer, Inno Setup, inno_bundle, updater/auto-update, CRT DLLs, PowerShell/native installer process, installer/version/AppId failure |
windows-installer-pipeline.md + build-reproducibility.md + core-stack.md |
| Dart Decimate, dead code, circular dependency, duplicate code, complexity, dependency hygiene, full zero-finding scan | dart-decimate.md |
| Common navigation / form / list / debounce / route-param-fallback patterns | common-patterns.md |
| Incremental remote pull, delta token, per-table sync date, merge/delete reconciliation | delta-sync.md |
| Route-param safety, wizard sequencing, guarded next-step navigation | navigation-flow.md |
| Dialog / sheet / modal, snapshot value object, post-await teardown, dismiss-then-route, pop fallback, nested navigator dismissal | modals-navigation.md + state-management-lifecycle.md |
Debounce / throttle / coalesce — TextField.onChanged, Slider.onChanged, scroll listener, sync saveAll, full-collection rewrite after subset mutation, persistence helper, reset/clear sentinel preservation, _userTapped gate, WebView / VideoPlayer in build, _storage.read in service, ref.listenManual ban, keepAlive collection watch, datasource batch loader, zero-value save guard, primitive→VO at notifier boundary, routeSettings on modal helper |
debounce-gate-batch.md |
Core Stack
Version SSOT: core-stack.md. Stack: Riverpod codegen, Freezed, GoRouter builder, json_serializable/build_runner, Hive CE.
Setup
Read setup.md for lint wiring, extension template setup, plugin install paths, and analyzer sanity checks.
Per-Tool Hooks
Use setup.md for install commands. Raw skill installs cannot register runtime hooks or scanners.
Pre-Flight
After each .dart / pubspec.yaml / build.yaml / analysis_options.yaml write batch, emit a checked list before yielding. Fill T0 always. Add T1 for state/notifier/mutation changes and T2 for network/E2E/stream/route changes. Cite rule IDs or refs for any failed item.
T0 — Core
- Package-root
dart analyzeexits 0 withflutter_skill_lints+riverpod_lint; setup changes prove one diagnostic from each plugin. - Global
deterministic-checksdart_decimate_gate.pyexits 0 after one full zero-finding scan; changed/base/baseline/audit modes + inherited exceptions + raw scanner calls are forbidden; package scope cited. - Coordinator invokes
npx --yes dart-decimate@latestonce per affected Git root; nested package scope uses exact--workspace; existing hooks +core.hooksPathpreserved. Non-Git project = N/A. - Async gaps are guarded:
ref.mounted/context.mounted, no baremounted, andfinallyusesif (ref.mounted) { ... }. - Providers, state, and widgets follow Rules 2-8 and 14: reusable widgets own UI lifecycle only; screens/routes/notifiers own navigation, workflow branching, selected domain records, provider state, and infrastructure.
- Domain/data/platform follow Rules 7, 10-13, 17-24, 26-27: sealed Freezed, VOs, datasource/repo storage, core extensions, typed routes, debounce/batch, platform APIs, previews, E2E, pause-safe state, native links, and a11y; if error reporting is accepted/present, it uses one scrubbed once-only boundary, otherwise N/A.
- Rule 25 = N/A unless Windows packaging/updater delivery is touched; when applicable, its diagnostic/publisher proof is green.
- Any row touched in Trigger Map was read; exact lint names are cited when a scanner should enforce the rule.
T1 — State / Notifier / Mutation
- Mutation deps resolve lazily via stateless helper/mixin; no notifier-local repo/service cache except disposable lifecycle owners.
- Sync
Notifier.build()avoids pre-returnstatereads; async primary state usesAsyncNotifier.build; durable sync startup does not depend on microtask timing before its owner/listener exists. -
ref.onDispose()cancels subscriptions/controllers/timers; durable status/snackbar/teardown belongs to notifier state. - Long-running sync/auth/import guards stale writes; no
ref.watchinside notifier methods. - Pause-sensitive projections watch base state directly; route pause/resume keeps the first update; auth/form mode changes clear transient errors.
T2 — Network / E2E / Stream / Route
- Source-of-truth fetch/reconcile after generated, normalized, reordered, destructive, or remote-function mutations.
- Shared/realtime state has writer + observer E2E proof without manual refresh.
- Selectors use stable text/semantics/tooltips or central
AppWidgetKeys; no inline string keys or coordinate primary taps. - E2E entrypoint is deterministic and isolated from production
main.dart; unknown scenarios fail; critical logs fail the run; evidence shows the asserted screen before app exit; cleanup is verified. - GoRouter redirects use pure matrix-tested resolver, nullable by-id providers/fallback UI, and generated typed route helpers.
- Native/custom URI producer, Android/iOS registration, Flutter delivery, and typed router share one tested scheme/host/path contract; cold/warm + signed-state device paths pass.
- Cross-runtime constants, schemas, and function contracts have drift tests; no app-root text-scale clamp.