Conventions
Code conventions of hoco-mobile: lint rules, naming, folders, commit messages and scopes, pull requests, localisation keys, assets, and how task and screen ids appear in code.
These conventions apply to everything in hoco-mobile. The branch model, merge policy and hooks are shared with hoco and described in the git workflow; review rules are in pull requests and reviews.
Lints and formatting
Recommendation: very_good_analysis (strict, maintained) over flutter_lints (permissive defaults) — one developer and two apps benefit from the analyzer catching what a reviewer would. Root analysis_options.yaml, included by every package:
include: package:very_good_analysis/analysis_options.yaml
formatter:
page_width: 100
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "packages/hoco_api/lib/src/generated/**"
- "packages/hoco_l10n/lib/src/generated/**"
errors:
invalid_annotation_target: ignore # freezed + json_key on constructor parameters
language:
strict-casts: true
strict-raw-types: true
linter:
rules:
public_member_api_docs: false # apps and internal packages; document what is not obvious
lines_longer_than_80_chars: false # the formatter's page width rulesdart format(page width 100) runs in the pre-commit hook and in CI; nobody argues about formatting in review.--fatal-infos: an info-level lint fails CI. Suppress a lint only on the line, with a reason:// ignore: avoid_dynamic_calls — JSON from sync.batch item body.riverpod_lintis recommended for provider mistakes (missingref.mounted, wrong provider types) if it runs with the pinned analyzer.
Naming
| Thing | Convention | Example |
|---|---|---|
| Files, folders, packages | snake_case | order_detail_screen.dart, hoco_offline |
| Classes, enums, extensions, typedefs | UpperCamelCase | OrderDetailScreen, SyncItemResult |
| Members, variables, parameters, enum values | lowerCamelCase | nextCursor, OrderStatus.validated |
| Screens (routes) | <Name>Screen | CheckoutScreen (RA-18) |
| Large sub-trees of a screen | <Name>View, <Name>Section | CartTotalsSection |
| List and grid items | <Name>Tile, <Name>Card, <Name>Row | VisitRow, ProductCard |
| Design-system widgets | Hoco<Name> (inventory name) | HocoQuantityStepper |
| Repositories | <Entity>Repository | OrdersRepository |
| Notifiers (commands, screen state) | Verb or screen name, provider generated | CancelOrder → cancelOrderProvider |
| Read providers | Noun, generated | orderDetail(orderId) → orderDetailProvider |
| Domain models | Business name, no suffix | Order, Visit, ResellerCard |
| Generated DTOs | As generated, never renamed | OrderDto or the schema name |
| Mappers | toDomain() / toRequest() extensions | orderDto.toDomain() |
| Booleans | is, has, can, should | canCancel, hasMore |
| Routes | Lowercase paths shared with the reseller web | /orders/:orderId |
Code identifiers are in English; French appears only in ARB files and in comments quoting the product (« Valider la commande »).
Folders
- Apps:
lib/main.dart,lib/src/{app.dart, bootstrap.dart, router/, features/<feature>/{data,domain,presentation}}(architecture). - A feature folder name is the product area in English:
catalog,cart,checkout,orders,returns,support,tour,visits,prospects,sync_centre. - Code used by two features of one app moves to
lib/src/shared/; code used by both apps moves to a package. Nothing imports another feature'sdata/orpresentation/— go through a provider exposed by that feature'sdomain/or a shared package. - Tests mirror
lib/:test/src/features/orders/data/orders_repository_test.dart. Fixtures intest/fixtures/(JSON responses copied from the API examples).
Commit messages
Conventional Commits, checked by commitlint in the commit-msg hook (same configuration as hoco). The task id goes at the end of the subject when there is one.
| Scope | Covers |
|---|---|
reseller | apps/reseller |
delegate | apps/delegate |
core | packages/hoco_core |
api | packages/hoco_api (regenerations, the pinned contract) |
ui | packages/hoco_ui, apps/gallery |
l10n | packages/hoco_l10n |
offline | packages/hoco_offline |
maps | packages/hoco_maps |
catalog | packages/hoco_catalog |
ci | .github/ workflows |
workspace, android, ios, deps, release | Root tooling, platform folders, dependency bumps, version bumps (shared list) |
feat(delegate): offline visit check-in with GPS tolerance [MDA-05]
fix(offline): reset in-flight outbox items after a crash [MOB-09]
feat(reseller): guest price lock and sign-up sheet [MOB-06]
chore(api): contract 1.3.0 [MOB-03]
feat(ui): availability indicator without quantities [MOB-04]
build(ci): upload staging APKs to App Distribution [MOB-01]Types: feat, fix, perf, refactor, test, docs, build, ci, chore, revert. Subject in the imperative, lowercase, no final period, header at most 100 characters.
Pull requests
- Branch
feature/<TASK-ID>-short-desc(feature/MRA-04-checkout) intodev; title in Conventional Commit form with the task id; squash merge. - Small and reviewable: under about 400 changed lines excluding generated files; a task longer than two days ships as several PRs, unfinished screens behind a release flag (feature flags).
- Description: task link, screen ids with links to their specs, screenshots or a short screen recording (before/after for UI), test notes (mock, staging, device), contract version if it changed, the review checklist.
- Labels:
reseller,delegate,design-qa(Akram reviews the gallery or the screenshots),contract(regeneration),pilot,hotfix. - Sensitive paths (
hoco_api,hoco_core,hoco_offline, platform folders,.github) wait for Anis's approval; elsewhere Imad may merge his own PR once CI is green (branch protection).
Localisation keys
ARB files live in hoco_l10n (app_fr.arb is the template; app_ar.arb and app_en.arb are scaffolded). Generated with flutter gen-l10n (melos run l10n).
| Kind | Pattern | Example |
|---|---|---|
| Screen text | <feature><Element><Variant> | checkoutSubmitButton: « Valider la commande » |
| Shared actions | common<Action> | commonRetry: « Réessayer » |
| Enum labels | enum<EnumName><Value> — generated from enums.json by tool/gen_enum_l10n.dart, never edited by hand | enumOrderStatusValidated: « Validée », enumCustomerTypeDemiGros: « Demi-gros » |
| Client-side error messages | error<Code> for codes the app words itself | errorNetworkOffline, errorGpsOutOfTolerance |
| Notification channels | channel<Name> | channelTransactional |
{
"cartMinAmountRemaining": "Plus que {amount} pour commander",
"@cartMinAmountRemaining": {
"description": "Cart progress bar under the minimum order amount (RA-17). amount is formatted, e.g. 1 545 DA.",
"placeholders": { "amount": { "type": "String" } }
},
"syncPendingCount": "{count, plural, =0{Synchronisé} =1{1 action en attente} other{{count} actions en attente}}",
"@syncPendingCount": {
"description": "Sync status chip (DA-27)",
"placeholders": { "count": { "type": "int" } }
}
}- Every key has a
@description saying where it appears (screen id) — translators and Akram need the context. - Money and dates are formatted by the helpers and passed as
Stringplaceholders; ICU plurals for counts. - French typography: non-breaking space before
:,;,!,?and inside« »; DA after amounts. - The server already localises error
messages; the app only words network errors and the codes it presents specially.
Assets
| Asset | Location | Naming |
|---|---|---|
| Icons (custom) | hoco_ui/assets/icons/ (SVG, currentColor) | ic_<name>.svg — ic_price_lock.svg |
| Illustrations (empty states, onboarding) | hoco_ui/assets/illustrations/ (SVG or WebP) | illus_<context>.svg — illus_empty_orders.svg |
| Raster images | assets/images/ of the app, WebP with 2.0x/ and 3.0x/ variants | img_<feature>_<name>.webp |
| Fonts | hoco_ui/assets/fonts/ (subset) | Family folders, as declared in pubspec.yaml |
| Map marker art | Drawn in code from tokens | — |
| Launcher icons per flavor | Generated into android/app/src/<flavor>/res by flutter_launcher_icons | — |
Assets come from Akram's handoff (handoff › assets export); no asset is redrawn or recoloured in code.
Task and screen ids in code
Ids belong to commits, branches and PRs. In code they appear sparingly, only where they help the next reader find the specification:
- Route declarations carry the screen id:
GoRoute(path: '/checkout', builder: …), // RA-18. - Business rules cite the requirement where they are implemented:
// FLD-07: arrival only within field.gpsToleranceMeters. - Golden and integration test names use the screen id:
testWidgets('RA-18 shows the pending-validation banner', …). - Not in class names, file names or every widget; never a task id in code (
MRA-04) — task ids go stale when the plan changes. TODOalways points to an issue:// TODO(#57): remove after contract 1.4 adds deliveryWindow— never a bareTODO.
Build and release
Versions, release trains aligned with the plan, the Play Console setup, tracks and staged rollouts, store assets, data-safety answers and declarations, signing, forced updates, the pilot hotfix procedure and the post-pilot iOS path.
Planning method
How effort was estimated, how the scheduler turns it into dates, and how to change the plan.