@tiledev/live-shopify-ui-kit
v0.4.0
Published
Presentational tiles for Tile storefront apps — props in, callbacks out. Zero runtime dependencies; every native module is a peer pinned by the host, so a kit release is always an OTA and never a store release.
Maintainers
Keywords
Readme
@tiledev/live-shopify-ui-kit
Presentational tiles for Tile storefront apps. Props in, callbacks out. No data fetching, no writes, no navigation, no toasts — a tile draws what it is given and reports what was tapped.
Why it is shaped this way
A brand rewrites its screens. It must not be able to break bidding, carts or checkout by doing so.
So the rules live in the host and the kit has no path to them: there is no import here that reaches
into a consuming app, and CI asserts it (0 app-aliases in src/).
The boundary
| In the kit | In the app |
|---|---|
| tiles, primitives, pure utils, layout hooks | screens, navigation, services, data hooks |
| the theme mechanism (UIKitThemeBridge, useTheme, withAlpha) | the palette — the six tokens |
| shared data shapes (types.ts) | the fetching that produces them |
Six colour tokens, and no seventh: primary/onPrimary, accent/onAccent, background/onBackground.
Resolve at render through useTheme() — never at module load, never inside StyleSheet.create. A
deleted slot returns undefined, which React Native renders as nothing rather than as an error.
Editing, and who does what
T* types (TText, TColor, …) are a developer contract: typing and autocomplete at the
declaration site. They are not how a PM edits a tile. That path is llPath + the schema +
useLLGroup called in the screen, with values arriving here as ordinary props. Nothing in the
PM path reads this package's source, which is why the kit can live in node_modules at all.
Setup
// once, inside your own ThemeProvider
<UIKitThemeBridge getThemeValue={getThemeValue} mode={effectiveTheme} dynamicFontSize={pref}>
{children}
</UIKitThemeBridge>import { ProductCard01 } from "@tiledev/live-shopify-ui-kit/modules/ProductCard01"; // one tile
import { EmptyState01 } from "@tiledev/live-shopify-ui-kit/modules"; // or the barrelImport tiles by their own path where you can. Metro does not tree-shake and Expo sets
inlineRequires: false, so everything reachable from a barrel is shipped and evaluated at boot.
The auction artwork is 333 KB of base64, which is why theme/auction* is reachable only directly
and is never re-exported.
Native
dependencies is empty and must stay empty. Every native module is a peer, pinned exactly by
the host. This is not tidiness: OTA runs on updateStrategy: 'fingerprint', so a native module
added here would change the fingerprint of every app that installs the kit and turn each release
into a store release instead of an over-the-air one.
Resolution during development
The kit resolves by name through extraNodeModules in the app's metro.config.js, not through a
file: dependency — Metro's package-exports resolution through a symlink silently yields
undefined, which surfaces as "Element type is invalid". Keeping the kit inside the app repo is
also what lets tile save capture it: a save only includes files under the app directory.
Tests
npm run build && npm run evals # or just: npm testLayer 1 only — pure logic, no renderer, no device, no network. 27 assertions across two files:
evals/pure.test.mjs— the rules: alpha compositing, the type scale, comment clamping and surrogate safety, merchant-voice classification. Every case is a boundary, an absence, or a coercion, because those are the three shapes that type-check cleanly and fail on a screen.evals/contract.test.mjs— the packaging invariants eighty consumers depend on: zero runtime dependencies, no native artefacts, peers with real upper bounds, everyexportstarget present and non-trivial, and no build-time path alias surviving into the emitted JS.
Each has been watched failing against a deliberate fixture — a runtime dependency, and a real
alias import in dist. A gate nobody has seen go red is not a gate.
What they do not prove: that anything renders. A library cannot be driven on a device on its
own, so the kit's components are exercised by the consuming app's Maestro flows — see
maestro/README.md in an app that installs it.
