English
Coding conventions
These conventions are what keep more than 30 packages consistent as they evolve. Please read through them before you open a PR.
Layers and imports
- Respect the
foundation → core → feature_base → feature → appsdirection;tool/check_layers.dartwill catch violations. - Product code may only
import 'package:mui/mui.dart'— never importmaterialdirectly. muicomplements Material rather than replacing it: use Material when it is enough, and only add tomui(with anMprefix) when it is not.- Themes are constructed in exactly one place,
buildMuiTheme()inmui; read values fromColorScheme/TextThemeandMuiTokens.
Barrel exports and visibility
- Package barrels use bare exports (no
show). - Prefix anything you do not want to share with
_. - Mark symbols that are needed inside the package but should not be public with
@internal, andhidethem in the barrel. - Mark test-only symbols with
@visibleForTesting. - Reserve
showfor the cases where it genuinely cannot be avoided (FRB output, third-party re-exports, picker skins).
Dependency injection: get_it + injectable
- Put binding annotations on the implementation class (
@Singleton(as:)and friends). - There is a single
configureDependenciesin the whole repository (mobile/lib/app/di/di.dart); infrastructure packages hook in as micro-modules. - Always resolve with
getIt<X>(); never hand-writegetIt.register*(the one exception is the sync session scope). - Do not use
@PostConstruct— startup logic belongs in the bootstrap inmain. - After changing an annotation you must run
dart tool/task.dart build-runner, and the generated files need to be committed.
Routing: go_router + extra
- Route classes all live in
moodiary_router, and each route holds alocationandparams. - The app does not target the web, so path and query parameters are not used; parameters are passed through
extrawith snake_case keys. - Keep
paramsto JSON scalars (ids, booleans and so on) — objects get snapshotted and go stale when state is restored. - Every page provides a
factory X.fromRoute(GoRouterState).
Internationalization: slang
- Use
context.l10n.xxxfor copy (it refreshes when the language changes); use the top-levell10n.xxxin services and callbacks. - Pass parameters by name and spell keys out in full — local aliases make the analyzer report keys as dead.
- After editing a
*.i18n.json, rundart tool/task.dart i18nand commit the generated files. - Text aimed at models (prompts, tool descriptions) is hardcoded in English and stays out of i18n.
muiships its own separate slang output (context.muiL10n).
KV storage
IKVStorage.set / remove / clearare synchronous and returnvoid.- Keys only support
int / bool / double / String / List<String>. - Secrets (PINs, API keys) go in
MoodiarySecureKVs. - The app lock passcode always goes through
AppLockPin— never read or writepassworddirectly.
Rust and native libraries
- Every native package owns its crate, native library, build hook,
rust-toolchain.tomlandCargo.lock. [workspace.dependencies]is not used: shared crates are pinned separately in each package, andtool/check_generated.dartverifies that the versions match.- After changing
rust/src/apiyou must rundart tool/task.dart gen-rust. - Opaque handles (such as
CancelToken) cannot cross.soboundaries: each library constructs its own, and only afterawait ensureInitialized(). - Every package exposes
Xxx.ensureInitialized(), which is safe to call repeatedly.
Commits and versions
- Commit messages follow Conventional Commits, which is how CI generates the CHANGELOG.
- To keep a PR out of the CHANGELOG, put
Changelog: skipin the body of the squashed commit, or use one of the skipped scopes (chore(deps|readme|pr|pull)). - Every version is pinned exactly. The one exception is melos in the root
pubspec.yaml.