Packages
The native kit and the shared catalogs — the workspace packages this app takes, and the reason the native app cannot use the web one.
| Package | What this app takes from it |
|---|---|
@statshub/ui-native | Every shared primitive, built for React Native |
statshub-api-client | URL building, JSON transport, cancellation, and typed HTTP errors shared with web |
statshub-i18n | The en / es / pt catalogs and the formatting helpers |
Two UI kits, not one
@statshub/ui-web is not importable here and never
will be. A <div> does not exist in React Native, Tailwind class strings are
resolved by a different engine, and the platform's own controls have no web
equivalent — a shared kit would be a lowest common denominator neither app
wants.
What the two kits do share is the vocabulary: a component with the same job has the same name, the same variant names and the same prop shapes in both. So a change on one side has an obvious counterpart on the other even though no code crosses.
@statshub/ui-native also ships things the web kit has no reason to — glass
effects, haptics wiring, sheet presentation — because those are the platform,
not the design system.
The catalogs are genuinely shared
statshub-i18n is platform-neutral on purpose: it imports no UI framework, and
statshub-i18n/react holds the hook both apps use. A string translated for the
website is translated for the app, which is the whole reason the package exists
rather than two copies of the same JSON.
The feature catalogue is not a package
src/lib/config/ holds what tools exist, what they are called, which group they
list under and which of them this platform carries. It was statshub-config and
both apps imported it; now apps/web-app-router/src/lib/config/ is a second
copy of the same catalogue. Add a tool to one and it does not appear in the
other — that is the cost of the split, and the header of each index.ts says so.
Where a component belongs
src/components/features/<area>/— parts one screen or one board uses.src/composed/— assembled pieces two screens share.@statshub/ui-native— a primitive with no StatsHub domain knowledge in it. If it mentions a fixture, a prop or a hit rate, it is not a primitive.
Not a package: the backends
Convex lives in convex/ inside this app rather than in packages/. It is this
app's account store — the profile table and Better Auth — and nothing else reads
it. Match data comes from the API like everywhere else; see
Data.