Wrap third-party libs below the apps
An app's package.json lists workspace packages and the exceptions below (framework singletons, the design system, tooling). A library that serves one capability (QR code, signature check, storage, id generation) belongs to a package below the app, which exposes a small API of our own.
Why
- Every direct app dependency is a potential silent override. The renderer's
resolve.modulesputs the app'snode_modulesfirst, so the app's copy wins over the version a library declared for itself (@noble/curvesv1 forced onto a v2 consumer crashed Desktop at boot). - Desktop and mobile each pick a different lib for the same job (
qrcodevsreact-native-qrcode-svg). One owner gives one behaviour and one test. - Bumping or swapping a lib touches one package, not two apps.
Rule
- Before adding a dependency to
apps/*, look for an existing owner package. If none, create one. - Place the owner in the lowest valid layer of the structure-flow table (
shared,domain,features/platform,features/flow), for example:- business-agnostic (QR code, clipboard, linking, list reorder):
shared/<name>, e.g.@shared/ui-qr-code - capability shared across flows or apps:
features/platform/<name> - used by one flow or one domain: that
features/flowordomainpackage
- business-agnostic (QR code, clipboard, linking, list reorder):
- The package declares the third-party lib in its own
dependencies(catalog:when a catalog entry exists). Apps and other packages import the package by name, never the lib. - The public API hides the lib: no lib types or instances in exports, so the lib can be replaced without touching callers.
- Add a test in the package that exercises the real lib, including the success path.
Examples
Existing app dependencies predate this rule and are migrated over time; do not add new ones.
shared/ui-qr-code(intent of the pattern; its API and placement are still under platform review): ownsqrcode; apps render<QrCode />(Desktop still declaresqrcodeuntil migrated).- #23097:
@noble/curvesand@trust/keytomoved out of Desktop into@features/platform-app-update;sslHelper.verifybecame a wrapper. Precedent for isolating a lib an app used once; the package is Desktop-only today, so it is not a placement model for new packages.
Exceptions
Keep a direct app dependency for libraries that must be a singleton across the bundle (react, react-dom, redux, react-router, styled-components, i18next), for the design system (@ledgerhq/lumen-*), and for build or tooling packages. Anything else goes behind a package; if you think it is an exception, ask in the PR before adding it.
Review
- Does the PR add a runtime dependency to
apps/*/package.json? Ask which package should own it. - Does an app import a library that a
shared/orfeatures/platform/package already wraps? Use the package. - Do two apps use different libs for the same job? Move both behind one package.


