pithy ui add react --worker apiYou need: a Worker that exists. This scaffolds into one rather than creating one.
Pithy writes a file once, and then it is yours
pithy ui add is the command, the ui-react components are what land in your tree, and one origin, one deploy is why the Worker serves them.
That is the whole ownership model, and it is worth stating before the file list.
- A file is created only if it does not exist. An existing one is left byte-for-byte alone and reported as kept.
- Nothing is regenerated, reformatted, merged or patched on any later run.
- A future release may add a new stub file. It may never rewrite one that does exist.
- Your own routes directory is written exactly once, at the initial scaffold, and never again. It is your application, and Pithy has no business in it.
The practical upshot: edit anything. Delete the sign-in screen and write your own. Rewrite the stylesheet from scratch. Nothing upstream will argue with you, and nothing will silently revert.
Two stylesheets, and why
One is yours — the palette tokens, the reset, the body rules.
One is Pithy’s, defining every class name a Pithy screen renders. Pithy’s screens import it themselves.
They are two files because ownership says they must be. Adding the sign-in screens to a project that already has a stylesheet correctly skips yours — and when one file held both, that run produced a sign-in screen whose classes nothing defined and reported it as created.
A screen and the rules it needs are one artifact. Splitting them by ownership is what lets each be written on its own schedule.
The tests that arrive beside some files
They are yours too, and they are not there for coverage.
Each one watches a contract your own next edit can break without anything going red: the sign-in link returning to the path the callback screen declares, the widget solving for the action your config names, the app mounting into a node it creates, your palette being declared as a set.
Every one of those failures builds clean, typechecks, lints, and is first visible in production — or on a page you do not look at because you are signed in.
The kit used to keep these gates for itself. That meant they held right up to the moment the file became yours and stopped holding immediately after — which is the moment it starts being edited.
Delete them if you want. But each is roughly the ten minutes it would take you to work out the same failure from a 403 in production.
Every client file is .tsx
Two structural rules the stub depends on, worth knowing before you move a file.
The Worker’s existing type config includes plain TypeScript files, which does not match the JSX extension — so the Worker’s program ignores the client entirely and needs no edit. For the same reason the ambient declarations file sits at the Worker root rather than under the source directory, where it would match.
No file in the Worker program may import a client file. The seam between a Worker and its client is runtime-only, and one import across it pulls the browser build into the Worker’s type program, at which point the rule above stops holding.
The sign-in screens
Scaffolded when the Worker composes auth — asked once, and skipped entirely when it does not, because there is nothing for a sign-in screen to call.
There are no provider flags. Which social providers you offer lives in your config and nowhere else, and the screens read the composed config at runtime — so enabling one stays a one-line edit and a redeploy rather than a regeneration.
Its catalog is baked into the file
Each screen carries its own English inside the file, so from the moment it is written those sentences are yours like the rest of it. A later release adding a line to the kit’s sign-in screen changes the template in the package, never your copy.
And no release will ever add a locale file to your repository. The translations ship inside the i18n package and arrive as an upgrade — so there is no locale directory to review, no merge to take, and no file here that a future release wants back.
What it wires
The asset stanza, with the allowlist derived from the real route table.
The dev block, joining this Worker to the dev set — one process serving the SPA and the API together.
The build block, so deploy builds the client before shipping.
The dependencies, at pinned versions.
The client’s two programs, appended to the root solution file — because a program nothing references is a program nothing checks, and the scaffold used to leave exactly that.
Those are extended, never created: a project scaffolded before the solution-file rule has Workers whose programs are not composite, and the build refuses a reference to one of those outright.
Then run it
pithy devThe root is the SPA. The API paths are the Worker. One process, one port, HMR against real bindings.
Check it worked
- The printed URL serves the SPA and the API paths reach the Worker
- Editing a component hot-reloads
- The health route answers rather than returning the shell
pithy ui sync --checkreports nothing uncovered