π¨ Frontend: assemble or write¶
Two ways to build React UI. SLMCode picks one from evidence, tells you which, and lets you override it in the request.
The three methods¶
| Method | Agent | What it does |
|---|---|---|
| shadcn/ui assembler | shadcn-worker | npx shadcn@latest add β¦, then wires and styles |
| Untitled UI assembler | untitledui-worker | npx untitledui@latest add β¦, then wires and styles |
| From scratch | react-worker / worker | Writes components by hand, matching the project's patterns |
Nothing to install and nothing to enable. Both assemblers ship bundled, are registered at startup, and their install commands are on the shell allow-list by default.
How the method is chosen¶
Highest precedence first:
- Your request. Name a library and you get it. Say from scratch (or "no component library", "hand-write", "vanilla react") and you get neither.
- The project. A
components.jsonbeside acomponents/ui/tree means this project already chose shadcn;@untitledui/iconsinpackage.jsonmeans it chose Untitled UI. Adding a hand-written Button next to twenty installed ones is a duplicate, not a style. - Greenfield defaults to assembling. New frontend work has nothing to match and nothing to override, and this is where reuse buys the most.
- Everything else writes by hand. An existing React app with no library markers has house patterns to match; introducing a component library into it is a migration nobody asked for.
The run says which it took, and why:
Β· init frontend: shadcn-worker β this project already uses shadcn/ui
Β· init frontend: shadcn-worker β new frontend β assembling from shadcn/ui
instead of writing components by hand (say "from scratch" to opt out)
Choosing explicitly¶
slmcode run "build the settings page with shadcn"
slmcode run "build the settings page with Untitled UI"
slmcode run "build the settings page from scratch"
To pin a project permanently β pipeline, quality gates, reviewer and all:
What the assemblers know¶
Both agents carry the library's real CLI contract, because getting it wrong costs a whole turn on a local model:
- shadcn's flag defaults are inverted between commands.
addneeds-y;initalready has it. Andinitneeds an explicit-bβ without it the CLI stops on an interactive Select a component library menu even with-y, and the turn hangs until it times out. - Untitled UI matches names fuzzily and wrongly.
buttonsinstallsapp-store-buttons;badgeinstallsbadge-groups. Neither errors β you get a different working component. Exact names only. data-table,date-pickerandtypographyare shadcn documentation guides, not installable names.toastis deprecated in favour ofsonner.- Untitled UI's page templates are a paid tier. The free CLI covers 122 components; the assembler never depends on
untitledui example.
Whole pages often install as one block β login-01, sidebar-01, dashboard-01, calendar-01 β which is the single biggest saving available.
The reviewers¶
Each assembler pack ships a reviewer whose job is the one failure this feature exists to prevent: a component written by hand that the registry already ships. They reject a local Button, Dialog, Card, Table or Select built from divs, and they reject edits to components/ui/* made to restyle one page β per-page styling belongs at the call site, via className or the component's own variant props.
They deliberately do not review the installed component's own code. It is upstream's, already reviewed, and not the task's work.
Security¶
npx as a whole remains an executor that needs explicit operator approval. What is allowed by default is five exact subcommands of two named packages:
Both CLIs accept a URL or an @registry reference in the same argument position as a component name, which resolves to a registry that is not the official one and writes whatever files it serves into your repository. SLMCode refuses those:
npx shadcn@latest add @acme/button β refused
npx shadcn@latest add https://β¦/payload.json β refused
Destructive subcommands stay behind explicit approval, because they rewrite config and existing files rather than adding new ones: shadcn's eject and migrate, Untitled UI's upgrade and example.
Why typecheck is the QA gate¶
Both assembler packs gate on npx tsc --noEmit, not npm test. A freshly scaffolded UI project ships dev, build, lint and typecheck scripts and no test script β measured on a real shadcn init -t vite. There, npm test --silent exits 1 with empty output, which is indistinguishable from a real failure, and a gate that can never go green spends the entire run in correction loops instead of building anything.
Typecheck is also the honest question for assembly work: the components were tested upstream, and what a run can actually get wrong is whether they resolve, whether the props match, and whether the page compiles. A task that asks for tests still gets them β the tester runs npm test when a script exists, and per-task acceptance criteria verify them by name.
Requirements¶
Node β₯ 20.18.1, and network access to the npm registry plus each library's component registry β both CLIs fetch on every invocation. Both libraries target React 19 and Tailwind CSS v4, and both require path aliases (@/components/β¦) in tsconfig.json.