A React 19 front end goes inside a Worker you already have, wired end to end. The SPA and the API build together, deploy together, and answer on one origin — with the screens you were going to write already written.
pithy ui add react --worker apiIt scaffolds the client into the Worker's own directory and edits three files to connect them: an assets stanza in wrangler.jsonc with SPA fallback and an allowlist of the API paths the Worker answers itself, a dev block so pithy dev runs Vite for this Worker, and a ui block so pithy deploy builds the client before shipping.
Your Worker entry is untouched, before and after.
$ pithy ui add react --worker api
▸ client apps/api/src
▸ wrangler assets + SPA fallback
▸ dev vite, one process
▸ deploy builds before shipping
Done.
What you get depends on what the Worker already composes — that is the composability doing the work rather than a template menu.
Compose auth and you get the sign-in screens: magic link, the OTP entry, and the callback route. Compose payments and you get the paywall, the pricing screen and the subscription screen. Compose neither and you get a router and one screen of your own.
pithy/sign-in.tsx auth magic link
pithy/otp.tsx auth code entry
pithy/callback.tsx auth the return leg
pithy/paywall.tsx payments gated content
pithy/pricing.tsx payments plans, priced live
pithy/subscription.tsx payments manage, cancel
index.tsx yours
No generated SDK, no types file to regenerate and commit. What the front end knows about each capability comes from virtual modules resolved in memory at build time, so it cannot drift from the Worker it ships beside.
Add a capability and its client projection appears. Remove one and the import stops typechecking, which is the point.
import { auth } from "virtual:pithy/auth";
import { payments } from "virtual:pithy/payments";
// Narrowed to exactly what this Worker composes —
// resolved in memory, never written to disk.
export const pithy = { auth, payments };
The scaffold is a starting point, not a dependency. Rewrite styles.css from scratch, delete a Pithy screen and write your own, change the router. Nothing upstream will argue with you and nothing silently reverts.
It does not overwrite anything, ever — running it twice will not clobber the edits you made.
$ rm apps/api/src/routes/pithy/sign-in.tsx
$ # write your own; nothing upstream objects
$ pithy dev
▸ vite one process, one origin
▸ api same Worker, same port
Done.
What it does not do
It scaffolds into one that exists. pithy worker add makes new ones.
Your Worker's index.ts is identical before and after.
Ever. Run it twice and your edits survive.
No generated SDK or types file on disk — virtual modules, resolved in memory.
Which social providers you offer lives in pithy.config.ts and nowhere else.
Bearer auth is fully supported and documented; this stub is simply not that.
Sign-in, paywall, pricing and subscription screens, scaffolded from what you already compose.