Skip to content

[EPIC] Coins single source of truth: move the journeys into packages so both shells get one implementation #1379

Description

@ucguy4u

Raised from device QA: the super app's Coins tab has expenses, budgets, and groups/split, while the standalone Airo Coins app has only the vault. Same product name, two different feature sets. Here is why, measured on origin/main, and what it takes to fix.

Where Coins code actually lives

Location Files Contents
app/lib/features/coins/ 81 dashboard, add_expense, add_budget, budget_management, groups_list, group_detail, add_split_expense — app-only
app/lib/features/bill_split/ ~10 split domain + services + receipt OCR — app-only
packages/feature_coin/ 21 vault only
packages/feature_coins_core/ 59 services (ingestion, review, …) — already packaged ✓
packages/feature_billsplit_core/ 2 migration started and abandoned

main_coins.dart registers exactly one module, CoinVaultModule, so the standalone app is vault-only by construction. And CoinVaultModule itself lives in app/lib/core/coins/coin_vault_module.dart, not in the package it registers — packages/feature_coin declares zero GoRoutes of its own. So even the wiring is app-owned.

Per ADR-0010 app/lib/features/coins is a frozen migration source, so this is a migration that stalled after the services layer, not a deliberate split.

The constraint any plan has to respect

app/lib/features/bill_split imports google_mlkit_text_recognition and image_picker for receipt OCR. The Coins profile has a 25 MB enforced release budget against a 19.78 MB baseline — about 5 MB of headroom. MLKit text recognition alone is well past that, which is exactly why the TV profile swaps it for packages/stubs/mlkit_stub.

So "move everything into the package and register it everywhere" would break the Coins budget on the first build. Split has to arrive as an optional capability, with OCR behind the existing stub seam.

Suggested order

  1. Move CoinVaultModule into packages/feature_coin. A module belongs with the feature it registers. Small, mechanical, unblocks the rest.
  2. Move dashboard / add_expense / add_budget / budget_management into packages/feature_coin and add them to the module's routes. Both shells pick them up from one place; the super app keeps mounting at /money, the standalone at /vault's sibling routes.
  3. Finish packages/feature_billsplit_core: split domain + services out of app/lib/features/bill_split, UI into a feature package, exposed as a BillSplitModule. Register it unconditionally in the super app. Register it in the Coins flavour only with OCR resolved through mlkit_stub, and verify against the 25 MB gate before it lands.
  4. Delete app/lib/features/coins once empty — ADR-0010 already names removal as the end state.

Each step is independently shippable and leaves both apps working.

Why it is worth doing

This is the same class of problem as #1362's forced-portrait bug, where feature_iptv had the fix and the super app's own shell copy did not. IPTV is the one domain that finished its migration, and it is the one where a fix reaches both apps for free. Coins currently needs every fix applied twice, and the standalone app silently lacks features users expect from the name.

Filed from a QA pass rather than started as code: steps 2 and 3 move ~90 files across a module boundary and need a coins/architecture owner on the route contract and on whether split ships in the standalone flavour at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions