Packages
Shared packages and their current consumers.
A package belongs under packages/ when another workspace imports it. Check
the package manifests before documenting a consumer.
| Package | Current consumer | Purpose |
|---|---|---|
@statshub/ui-web | Docs | React 19 DOM components |
@statshub/ui-native | Expo, docs previews | React Native component kit |
statshub-api-client | Expo | HTTP transport and typed errors |
statshub-i18n | Expo | Locale catalogs and React bindings |
statshub-api-contract | Tooling | Records web API responses and compares Go |
statshub-tooling | Docs and root scripts | App registry and repository utilities |
statshub-api-contract lives at packages/tests/api-contract; its package name
is unchanged, so pnpm --filter statshub-api-contract still finds it.
The current Next.js 14 web app predates these shared client packages and does not
import them. Do not claim cross-app sharing until its package.json declares
the dependency.
Web and native UI remain separate because one renders DOM and the other React Native. Share transport and domain rules where consumers actually use them.
Two things that used to be packages
The feature catalogue and the Team Screener filter semantics were
statshub-config and statshub-filters. Each app now keeps its own copy:
| Was | apps/expo | apps/web-app-router |
|---|---|---|
statshub-config | src/lib/config/ | src/lib/config/ |
statshub-filters | src/lib/api/team-screener/filter-semantics.ts | src/lib/team-screener-filter-semantics.ts |
Copies drift, which is exactly what the config package existed to stop, so each file names its counterpart in its header. A tool added, renamed, hidden or re-categorised in one catalogue has to be changed in the other, and a change to what a filter means is a change in both filter modules. Nothing enforces it but the comment and whoever is reading the diff.