Every package in the workspace, what it owns, and what it deliberately doesn't.

Grouped by layer, outermost first. Click a row to open it β€” key files, the contract it publishes, and the one rule worth remembering about it.

/app

The composition root

The only leaf in the graph, and the only package that knows about concrete _impl packages. Renaming the app, adding a flavor, or registering a feature all happen here.

appcomposition rootβ–Ά

Owns the app widget, flavors, and all composition in bootstrap/. Three files carry // <generated:...> anchor comments that modular new feature writes above β€” never remove them.

lib/ main.dart β†’ initializer() app.dart β†’ RootApp (WidgetsApp.router) bootstrap/ initializer.dart β†’ binds everything together composition_root.dart β†’ runs modules in order, timed dependency_injection_configuration.dart β†’ the one list of modules router_configuration.dart β†’ assembles feature routers flavor/app_config.dart β†’ String.fromEnvironment reads adapters/ β†’ app-owned glue (locale ↔ storage, etc.)

Startup, in order: WidgetsFlutterBinding.ensureInitialized() β†’ DependencyInjectionConfiguration.initialize() β†’ RouterConfiguration.initialize(container) β†’ runApp wrapping DependencyScope β†’ RouterScope β†’ AppThemeScopeWrapper β†’ AppLocaleScopeWrapper β†’ RootApp. Full detail on the architecture page.

/features

One _api + _impl pair per business domain

The template ships two, on purpose β€” counter is the minimal shape modular new feature generates; posts is the same shape with a full data layer over a real backend.

counter_api / counter_implminimal shapeβ–Ά

Two screens, one counter. This is the shape every generated feature starts from β€” deliberately kept small so the wiring is what stands out, not the feature.

counter_api/lib/ counter_api.dart β†’ barrel: exports CounterApi + CounterLauncher src/counter_api.dart β†’ abstract interface class CounterApi src/counter_launcher.dart β†’ counter() Β· details({count}) counter_impl/lib/ counter_impl.dart β†’ barrel: module + module router ONLY src/router/counter_route_info.dart β†’ private static const addresses src/router/counter_module_router.dart β†’ this module's route table src/di/counter_module.dart β†’ registers CounterApi src/di/counter_api_impl.dart β†’ CounterApiImpl src/di/counter_launcher_impl.dart β†’ CounterLauncherImpl src/counter/counter_controller.dart β†’ State/Event/Effect src/counter/counter_screen.dart src/details/details_screen.dart

Cross-feature link: counter_impl depends on posts_api (never posts_impl) to push a NavigateToPosts effect through PostsApi.launcher β€” the worked example of leaving one feature for another. See it on the architecture page.

posts_api / posts_impldata layerGET /postsβ–Ά

A list screen and a details screen backed by JSONPlaceholder β€” GET /posts and GET /posts/{id}. This is the template's one worked example of a data layer, laid out to copy when a feature needs to talk to a backend.

posts_impl/lib/src/ domain/ post.dart β†’ the model the feature works with posts_repository.dart β†’ the feature's own data contract posts_failure.dart β†’ sealed: offline / server / unknown data/ post_dto.dart β†’ wire shape, decoded defensively posts_remote_data_source.dart β†’ the ONLY place a path is named posts_repository_impl.dart β†’ DTO β†’ model, exception β†’ failure posts/ posts_controller.dart Β· posts_screen.dart Β· widgets/ post_details/ post_details_controller.dart Β· post_details_screen.dart shared/ posts_failure_l10n.dart Β· posts_message_view.dart router/ posts_route_info.dart Β· post_details_args.dart Β· posts_module_router.dart di/ posts_module.dart Β· posts_api_impl.dart Β· posts_launcher_impl.dart

Three rules hold it together:

1

The repository throws PostsFailure and nothing else

Mapping AppNetworkException happens once, exhaustively, over the sealed hierarchy β€” a new transport error fails to compile rather than quietly reading "something went wrong".

2

Failures reach the widget as values, not strings

A controller has no BuildContext, so it can't localize. PostsFailureL10n.message(context) turns the failure into a sentence at the only layer that can.

3

Controllers take dependencies in, never resolve them

PostsController({required this._repository}), with the screen passing context.locator() from AppStateProvider.create β€” trivially testable with a fake.

Registration lives in the module, under types declared in lib/src/ β€” shared container, private contracts:

container
  ..registerLazySingleton<PostsRemoteDataSource>(...)
  ..registerLazySingleton<PostsRepository>(...)
  ..registerLazySingleton<PostsApi>((_) => const PostsApiImpl());
Not a mandated shape The _api/_impl split is about module boundaries, not what happens inside one. posts's domain/ + data/ layout is an example, not a rule β€” nothing outside the package can see it either way, so a feature is free to have fewer, more, or different layers.

/base

Cross-feature primitives

Not infrastructure (that's core), not a business domain (that's features) β€” shared concerns every feature might touch: translated strings, and this application's own network contract.

app_localizationen Β· ar Β· az Β· es Β· ru Β· tr Β· zhβ–Ά

ARB-based translations with a locale scope in the widget tree and a notifier in the container β€” so code without a BuildContext (a network interceptor, for instance) can still read the selected language.

lib/src/ l10n/app_<code>.arb β†’ one file per language, en is the template generated/ β†’ gitignored, rebuilt by `flutter pub get` locale/app_locale.dart β†’ enum AppLocale { en, ar, az, es, ru, tr, zh } locale/app_locale_notifier.dart(+impl) locale/app_locale_scope(_wrapper).dart di/app_localization_module.dart

Adding a language means two edits that must agree: a new app_<code>.arb with every key from app_en.arb, and a value on the AppLocale enum β€” supportedLocales derives from that enum. A test fails if the two ever drift apart.

app_network_contractthe file you editβ–Ά

This application's own success/error envelope, built on top of the transport-agnostic network_api. It's the single seam between a generic REST client and your backend's response shape β€” see it worked through in detail below.

lib/src/ app_response.dart β†’ AppResponse { statusCode, data, message } app_network_exception.dart β†’ sealed: request/offline/timeout/cancelled/unknown app_response_parser.dart β†’ AppResponseParser implements NetworkResponseParser<AppResponse>

Registered once, at composition time: NetworkModule<AppResponse>(responseParser: AppResponseParser()). Everything below it β€” network_api, network_impl, every feature β€” stays free of any envelope knowledge.

/core

Infrastructure β€” each its own api/impl pair

Swappable in principle: replace network_impl's dio client, or router_impl's go_router, and nothing above _api notices.

state_managerzero dependenciesβ–Ά

A from-scratch State/Event/Effect implementation β€” no bloc, no riverpod, no provider. AppStateController<S, E, F> is the whole engine; see the loop diagram on the architecture page.

lib/src/ app_state_controller.dart β†’ emit Β· emitEffect Β· onInit Β· dispatch app_state_provider.dart β†’ owns a controller's lifecycle per screen app_controller_builder.dart β†’ rebuilds on every state change app_controller_selector.dart β†’ rebuilds only when a selected slice changes
routerapiimpl β†’ go_routerβ–Ά

router_api defines routes, requests and the navigation service purely in domain terms; router_impl maps them onto go_router. No feature imports go_router β€” everything goes through AppRouteRequest, AppModuleRouter and context.navigation.

router_api/lib/src/ app_route_info.dart β†’ { name, path } β€” a feature's private address app_route_request.dart β†’ what a launcher returns app_navigation_service.dart β†’ goRoute / pushRoute / replaceRoute / popRoute app_module_router.dart β†’ the contract every feature implements app_module_routes.dart β†’ AppPageRoute / AppShellRoute / AppStatefulShellRoute router_impl/lib/src/ core/app_router_config.dart β†’ assembles all feature routers into one GoRouter core/app_route_mapper.dart β†’ AppModuleRoute β†’ go_router's RouteBase service/go_router_navigation_service.dart
dependency_injectionapiimpl β†’ get_itβ–Ά

The abstraction every module registers through, independent of any DI library β€” swapping get_it for something else is one new _impl package.

dependency_injection_api/lib/src/ dependency_locator.dart → get<T>() · call<T>() dependency_container.dart → registerSingleton / registerLazySingleton / registerFactory dependency_injection_api.dart→ abstract interface class DependencyModule dependency_scope.dart → InheritedWidget exposing the locator dependency_injection_impl/lib/src/get_it_container.dart
networkapiimpl β†’ dioβ–Ά

A REST client generic over the success model, so it carries zero knowledge of any backend envelope β€” that knowledge lives entirely in a NetworkResponseParser<TSuccess>, supplied once at composition time by base/app_network_contract.

network_api/lib/src/ network_api.dart β†’ NetworkApi<TSuccess>: get/post/put/patch/delete/send network_response_parser.dart β†’ parseSuccess(Raw) β†’ TSuccess Β· parseFailure(Failure) β†’ Object network_failure.dart β†’ transport-only failure kinds network_header_provider.dart β†’ headers re-read on every request network_impl/lib/src/ core/dio_rest_client.dart β†’ the only place dio is imported network_module_container.dart β†’ NetworkModule<TSuccess>
design_system (+ assets)tokens Β· themingβ–Ά

The token system in full β€” colour, spacing, radius, size, typography β€” but deliberately only six components. Build your own on the tokens rather than extending a component library that wasn't designed for your product.

lib/src/ tokens/ colors/ Β· radius/ Β· size/ Β· spacing/ Β· typography/ theme/ app_theme.dart (light()/dark()) Β· app_theme_scope(_wrapper).dart components/ app_text Β· app_image Β· app_vector_image Β· app_primary_button app_outlined_text_field layouts/ app_scaffold.dart assets/ β†’ separate workspace package; vectors/ + rasters/ generate AppVectorAssets / AppRasterAssets via `modular gen assets`
storagestandard + secureβ–Ά

Two key/value contracts β€” StandardStorageApi for plain preferences (theme, locale) and a secure counterpart for anything sensitive β€” behind one StorageModule.

loggerimpl β†’ talkerβ–Ά

A talker-backed logger with an in-app log screen. The network module's HTTP logging interceptor is built on the same LoggerApi, so requests and app logs land in one place.

biometric_authFace ID / Touch IDβ–Ά

A small, two-method contract for device biometrics β€” availability and a single authenticate call β€” kept deliberately minimal so it's easy to see everywhere it's used.

app_linternot a libraryβ–Ά

An analysis_options.yaml only β€” every other package includes it with include: package:app_linter/analysis_options.yaml. Lint rules change here and nowhere else, so the ruleset can never quietly diverge between packages. Notably strict: strict-casts, strict-inference, strict-raw-types, require_trailing_commas, prefer_relative_imports, implementation_imports: error.