Scaffolders
These skills create new files in the React app that follow the project’s conventions. They run GAIA’s scaffolding command for you, so you don’t need to remember its options: describe what you want, and the skill asks for anything missing. The files land in the frontend/ package, and paths below are relative to frontend/.
Files and folders under app/components, app/pages, and app/hooks are kebab-case (lowercase words joined by hyphens, like price-card), and @gaia-react/lint enforces it.
New Component
Section titled “New Component”It first checks whether shadcn/ui (opens in a new tab) already offers the component (a dialog, tabs, a popover). If it does, it runs pnpm shadcn add <name>, which copies the component’s source into your project as a single file, app/components/ui/<name>.tsx; GAIA never edits those files. Otherwise it creates a component folder: app/components/<name>/index.tsx and tests/index.stories.tsx, a story whose play function is the component’s test. There is no separate test file.
Confirms: name (kebab-case or PascalCase; the folder uses the kebab-case form), parent folder (default app/components/), and props.
Triggers when you ask to create a component, scaffold a card, make a button, or add a new component.
Example phrasing: “create a price-card component”, “scaffold a user-avatar with a name and url prop”.
Props can use most TypeScript types, including ones that contain commas, such as Record<string, unknown>, function types, and tuples. A component cannot be created inside app/components/ui, which holds only shadcn/ui files.
New Hook
Section titled “New Hook”Creates a custom React hook, app/hooks/use-<name>.ts (named export useName), with a vitest-browser-react test at app/hooks/tests/use-<name>.test.ts.
Confirms: name (use-kebab or useCamel), parameters, and return type.
Triggers when you ask to create a hook, make a useFoo hook, or describe state or effect logic you want to reuse.
Example phrasing: “make a useCountdown hook”, “extract this into a useFormSubmission hook”.
New Route
Section titled “New Route”Creates a route file, app/routes/<group>.<name>.tsx (named with the flat file convention of @react-router/fs-routes), and its page, app/pages/<name>/page.tsx, with tests/page.stories.tsx. The story is the page’s test. The route file stays small (loader, action, meta, and a one-line default export); the UI lives in the page.
When you connect it to a service, it writes everything from loading the data to showing it on the page. Ask for the data to load in one of three ways:
| Ask for the data to load | What it writes |
|---|---|
| on the server | a server loader the page reads with useLoaderData |
| in the browser | a clientLoader and a HydrateFallback |
| through TanStack Query | a clientLoader that loads the data into the TanStack Query (opens in a new tab) cache, and a page that reads it with useSuspenseQuery (needs Query installed) |
Ask for a list page or a detail page. A list page is <group>.<name>.tsx, and a detail page is <group>.<name>_.$id.tsx, which takes an id from the URL. The page story gets its data from MSW (opens in a new tab) mocks. To see the files a route would create without writing them, ask Claude to show them first.
Confirms: name (kebab-case), group (_public or _session), and the data, action, and i18n options.
Triggers when you ask to create a route, add a page, scaffold a path like /dashboard, or wire up a route under _public or _session.
Example phrasing: “add a /pricing page in _public”, “scaffold a projects list page in _session backed by the projects service”.
A route that is not connected to a service can still get a small loader for the page title and meta tags, and an empty action to fill in. If you ask for an action on a connected route, it writes a create action for a list page or an update action for a detail page, checked against the service’s input schema. With translations, it adds the page’s locale file to the locale barrel (the index file that re-exports the locale files); if the barrel is missing, the scaffold stops with an error that says how to fix it.
New Service
Section titled “New Service”Creates an API service in your API’s folder under app/services/ (shipped as gaia/, often renamed to the company or API name). It writes request functions built on Ky (opens in a new tab) that check responses against Zod (opens in a new tab) schemas, plus the schemas, URL constants, and types, and optionally MSW mock handlers under test/mocks/<name>/. When @tanstack/react-query is installed and the service has a get, it also writes queries.ts with query keys and queryOptions factories.
Confirms: name (kebab-case), endpoints (any of get, post, put, delete), the schema as name:type pairs, and whether to include mocks.
Triggers when you ask to add a service, create the API for a resource, or wire up CRUD (create, read, update, delete) for a resource.
Example phrasing: “add a projects service with get and post”, “wire up CRUD for tasks with mocks”.
Schema fields can be text, numbers, true/false values, dates with times, or one of a fixed set of values, and any field can be optional. For example: “a tasks service with get and post, where a task has an id, a title, an optional due date, and a status of open or done”. To add queries.ts to a service you already have, ask for the Query options for that service.
The skill does not connect the service to a page or hook, so you decide where it belongs. New Route, connected to the service, does that for a route.