Packages
Shared code under packages/ — what each one is for, who consumes it, and why there is no shared component or logic layer.
A package is code more than one app imports. One consumer is not a package; it
is a directory in that app. Every package is named for its directory — statshub-<name> — flat under
packages/.
ui-web
Design-system primitives on Base UI. Consumed by web and admin.
ui-native
The React Native component kit. Consumed by expo.
api-contract
Records and replays API responses to prove the Go port faithful.
Two UI kits, and no third
There is no shared component layer and there cannot be one: expo is React
Native, web and admin target the DOM, and there is no react-native-web in
this repository. A component cannot cross that line, so the kits are parallel
rather than shared.
There is no shared logic package either
Logic could cross that line, and for a while packages/lib was reserved for
it. It never got anything, and it has since been removed.
When the design system was lifted into ui-native,
the mobile and web codebases were compared for shared logic. They had
essentially none — the filename overlaps between apps/statshub-expo/src/lib and
apps/statshub-web/src/lib turned out to be coincidental: different directories, a
handful of shared lines, independent implementations of the same ideas.
So nothing was moved. A package with one consumer is indirection without sharing, and an empty package reserved for a future need makes duplication harder to see rather than easier. If genuinely shared, platform-agnostic TypeScript appears — domain rules, formatting, parsing, pure calculation — it earns a package then.
Two things that look shareable and are not
The Expo error layer reaches into apps/statshub-expo/src/lib/api/errors for ApiError
and uses AsyncStorage: that is native app infrastructure, not shared logic. And
UI, obviously — see above.
Conventions
Publish TypeScript source, not build output. Every consumer bundles it, so a build step is only a cache to invalidate.
"dependencies": { "@statshub/ui-web": "workspace:*" }Turbo infers build order from the dependency, so ^build already covers it.
Add a package has the full walkthrough, including
the Metro rules that make native packages different.