You need: a scaffolded project.
One command
What a Pithy front end is is the map, adding React is the detail, one origin is why the Worker serves it, and language in the browser is what a second locale costs.
pithy ui add reactIt scaffolds the app, wires the Vite plugin, and writes the screens you asked for.
One origin, and that is the point
Your front end and your API answer on the same origin.
No CORS to configure. No preflight. A session cookie just works, because there is no cross-site boundary for a browser to be careful about.
The asset allowlist is derived, not maintained
A Worker serving assets needs to know which paths it owns and which the static handler should answer.
That list is computed from the real route table — every path every composed capability mounts, plus your own.
Which is why you never keep one by hand, and why adding a capability does not mean remembering to add its prefix somewhere.
pithy ui add writes a file once
From that moment the file is yours.
There is no update path that pushes new code into a screen you already have. A later release changing the kit’s sign-in screen changes the template in the package, not your copy.
What upgrades on its own is everything on the other side of the seam — the negotiation, the merge order, the kit’s translations, the formatting, the client projection. The screen is what you consume it with.
The generated types are copied too
client-env.d.ts is generated in the kit from each capability’s declared client projection and copied by pithy ui add.
So an existing project’s copy is hand-maintained. A new declare module block will not arrive on its own — add it by hand, or scaffold a throwaway Worker and copy the block across.
Until you do, the virtual module is unresolved to your compiler and serves correctly at runtime, because the plugin builds it from your composed config either way.
The one React rule
Language in the browser
useNegotiatedLocale walks the browser chain — ?lang=, the account, this device’s storage, the browser’s own languages, the server’s answer, the default.
Pass persist so a signed-in reader’s choice reaches their account, not only this device. Otherwise a reader who picks Spanish on their phone reads French on their laptop.
Screens are fetched one locale at a time as their own chunk, so adding a language does not grow the bundle every reader downloads.
Building and deploying
One deploy. The assets and the Worker ship together, and the route table decides which answers what.