Skip to content

build(melos): add melos workspace skeleton (useRootAsPackage) - #2276

Merged
ethicnology merged 10 commits into
developfrom
chore/melos-skeleton
Jun 11, 2026
Merged

build(melos): add melos workspace skeleton (useRootAsPackage)#2276
ethicnology merged 10 commits into
developfrom
chore/melos-skeleton

Conversation

@ethicnology

@ethicnology ethicnology commented Jun 10, 2026

Copy link
Copy Markdown
Member

What

Groundwork for the monorepo migration — no app code moved, no feature sliced, no workspace: member added. Two coordinated pieces:

1. Architecture baseline (docs). Consolidate ARCHITECTURE.md, AGENTS.md and FEATURES.md on one layered pattern and document it as an onboarding guide, so every future extraction has a clear contract to converge toward.

  • One pattern everywhere: ui → bloc → usecase → repository → datasource in iteration-1 vocabulary (no port/adapter/application/framework renaming).
  • Where logic lives: invariants → domain entities, data-shaping → repository, orchestration → thin use-cases. Per-layer Owns / Never / Smell, the execute(...) use-case convention, and an end-to-end vertical slice.
  • Two boundary rules: a repository exposes only domain types; a facade exposes only the feature's published types. Entity and model are always two separate types; entities are self-validating.
  • Errors: one sealed family per feature, toTranslated(context) user messages via context.loc, the end user never sees a dev string (generic catch-all).
  • Repository-vs-capability-Port distinction; convergence table for the ~6 drifted hexagonal modules; a compiler-enforcement section.
  • Onboarding: reading order + repo doc-map, an "Entry points & code tour" (main → locator → router), and an "Adding a feature" checklist; a Security section (secrets, sealed UI, capture blocking).
  • Agent contract: refuse-and-suggest on rule breaks, and offer a scoped refactor (separate commit) when touching non-compliant code. Framed as a target we converge toward, not a finished state.
  • FEATURES.md prose aligned to the real graph edges + a melos note; README.md gains a "Development" signpost to these docs.

2. Melos workspace skeleton (build/config).

  • melos 7.8.1 as a dev_dependency.
  • melos: block in pubspec.yaml: useRootAsPackage: true (app stays at repo root), IntelliJ file generation off, command.bootstrap.enforceLockfile: true.
  • packages/ and features/ created as reserved homes for future members (documented exception to AGENTS.md rule 14).
  • make bootstrap (wraps fvm dart run melos bootstrap) and make analyze (the CI/pre-commit analyze check, previously missing), with the pre-commit hook and CI routed through make analyze as the single source.

Why

We're migrating to a melos monorepo incrementally — extracting lib/core/ into packages/ and lib/features/ into features/ over time, one reviewed slice at a time. A migration like that needs a documented target architecture and a workspace skeleton before any code moves; this PR lays both, and nothing else.

We deliberately keep the app at the repo root via useRootAsPackage: true rather than relocating it to apps/bull/ (the layout most large melos repos use): moving ios/ android/ lib/ would disturb the reproducible-build chain (Containerfile /app paths, SOURCE_DATE_EPOCH, makefile output paths) — high risk and out of scope for a skeleton. useRootAsPackage coexisting with future workspace: members is supported (melos PR #927).

Target architecture (documented, not built yet)

  • root = app shell — thin: routing, DI/get_it wiring, composition of feature packages.
  • features/ — Flutter packages per user-facing flow (send, receive, buy, sell, recoverbull, …), each behind its public/ facade, mounted into the shell.
  • packages/ — pure-Dart shared foundation: shared domain (wallet, secrets) + infrastructure (storage, electrum, blockchain, …).
  • Dependencies point one way, acyclic: shell → features → packages.
  • Enforced encapsulation: curated lib/<name>.dart (export 'src/…' show …); lib/src/ is package-internal; cross-package src/ imports fail via the implementation_imports lint + analyze --fatal-infos (CI forbids // ignore).
  • Sealed UI (security): secrets exposes a MnemonicView widget but no function returning the words — the seed never crosses the package boundary. This is the one reason a package may depend on Flutter. (Seal is programmatic-only — paired with screenshot blocking / semantics exclusion / ephemeral handling.)

Reproducible build — unaffected

melos is a dev_dependency, never compiled into the app. The pubspec.lock change is purely additive (only melos's own dev-tooling transitives; async stays 2.13.1, no runtime dependency shifted). The container build (make android) never invokes melos. The released APK/AAB hash is therefore expected to be byte-identical. make deps is unchanged (fvm flutter pub get --enforce-lockfile).

Not in this PR

No code moved, no workspace: members, no changes to the makefile build path / Containerfiles / devcontainer. Security/bitcoin audit deferred to the first real extraction (when crypto code actually moves into a package). When the first member lands: re-verify analyze coverage across members and the enforceLockfile + per-member-lock interaction (noted in AGENTS.md).

Install melos 7.8.1 as a dev_dependency and configure the repo as a
single-package pub-workspace: the app stays at the repo root via
useRootAsPackage, with packages/ and features/ reserved as homes for
future workspace members. melos is dev-only and never compiled into the
app, so the reproducible APK is unaffected (async stays 2.13.1; the lock
diff is purely additive dev-tooling).
Add a Monorepo / melos section to AGENTS.md (always invoke via fvm dart
run melos, makefile stays canonical, packages/+features/ reserved) plus
an explicit exception to rule #14 for the pre-created dirs. Add a
forward-looking Monorepo Migration section to ARCHITECTURE.md explaining
how pub-workspace boundaries turn the dependency rules into compile-time
guarantees.
Correct the make deps command to include --enforce-lockfile, and note
that enforceLockfile requires each future workspace member to commit a
pubspec.lock (or bootstrap with --no-enforce-lockfile until they do).
Both surfaced by audit of the skeleton.
Hide the long 'fvm dart run melos' invocation behind 'make bootstrap',
and add the missing 'make analyze' target (fvm flutter analyze
--fatal-warnings --fatal-infos) that mirrors CI and the pre-commit hook.
Keeps the makefile the single canonical entry point — no one types bare
melos or long fvm commands.
PR self-review consistency: the toolchain bullet now shows make deps as
--enforce-lockfile (matching the makefile), and the pubspec melos comment
points at make bootstrap instead of the raw fvm dart run melos command.
…ealed UI)

Expand the migration docs with concrete intent: root = thin app shell;
features/ = Flutter feature packages (send/receive/buy/sell/recoverbull)
mounted in the shell; packages/ = pure-Dart shared foundation (wallet,
secrets, infra). Document enforced encapsulation (lib/src + curated
export + implementation_imports + CI) and the sealed-UI pattern — e.g.
secrets exposes a MnemonicView widget without exposing the mnemonic, the
one reason a package may depend on Flutter.
@ethicnology
ethicnology requested a review from i5hi June 10, 2026 07:22
@ethicnology ethicnology self-assigned this Jun 10, 2026
@claude

This comment was marked as resolved.

The analyze flags (--fatal-warnings --fatal-infos) lived in three places:
CI (raw), the makefile, and implicitly the pre-commit hook (which ran bare
flutter analyze, so info lints passed locally but failed CI). Make 'make
analyze' the single definition and call it from both the pre-commit hook
and the CI workflow, so the floor provably equals CI and there's nothing
to drift. Surfaced in the #2276 review.
Rewrite ARCHITECTURE around one layered pattern in iteration-1 vocabulary
(no port/adapter/application/framework renaming). Add the "where does logic
live" rule (invariants→domain, data-shaping→repository, orchestration→usecase),
repository as abstract interface in domain/ + impl in data/, models cap at two,
one sealed error family per feature, Result at the repo boundary, melos
convergence table, and an enforcement section (class modifiers, sealed,
@useResult, analyzer plugin, custom_lint, melos boundaries).

Align AGENTS rules and naming tables to the consolidated vocabulary: use-case
always present and thin, bloc never imports a repository/datasource, repository
abstract in domain + impl in data, deprecate the hexagonal folders.

FEATURES: align Key Dependency Patterns prose to the real graph edges and add
the melos packages/<domain> note; dependency graph unchanged.

Grounded in docs.flutter.dev/app-architecture.
Sharpen ARCHITECTURE.md and AGENTS.md so a new contributor can design
features correctly and find their way around the code.

- attribute recommendations to the Flutter team, not Google
- errors: document toTranslated(context) for user-facing messages via
  context.loc; end user never sees a dev string; catch-all variant must
  return a generic localized message, not the raw text
- add the repository boundary rule (public signatures carry domain types
  only) and make entity/model always two separate types
- give each layer an Owns/Never/Smell block; document execute() as the
  use-case entry method; add an end-to-end vertical slice
- explain the facade as the inter-feature contract (concrete class, no
  extra interface) and what types may cross it
- distinguish a repository from a non-repository capability Port
- onboarding: reading order + repo doc-map, "Entry points & code tour"
  (main -> locator -> router), and an "Adding a feature" checklist
- add a Security section (secrets, sealed UI, capture blocking) and
  expand Tests with layout and tooling
- frame the docs as a target we converge toward, not a finished state
- agents: when touching non-compliant code, offer a scoped refactor as a
  separate change and let the developer decide
- add model/mapper naming rows; fix l10n convention to context.loc
@ethicnology
ethicnology merged commit 5f916a5 into develop Jun 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant