srouter-frontend
Skill24 starsChanged 16 days agoMITView on GitHub
Use when editing `client/` — the SRouter admin dashboard (React 19, Vite, TanStack Router/Query, Tailwind v4, shadcn on Base UI). Covers new routes, the `/v1` fetch layer, the admin session gate, wire types generated from Rust, and client verification. Trigger on changes under `client/src`, on login/session behavior, or when a task mentions the dashboard, theme, or shadcn component work.
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 2 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Not for server changes: routes, guards, and error envelopes live in server/src (see AGENTS.md).
- Very specific: line 32
Stay same-origin: No API base URL, no cross-origin dev server, never strip Origin in the proxy.
- Very specific: line 35
One network layer: All requests go through request<T>() in client/src/api/client.ts.
- Distinctive: line 66
If a generated file must be touched at all, generate it, then git diff it to confirm the change came from the generator and not from you.
- Very specific: line 70
Read references/architecture.md (relative to this skill directory) for where the piece belongs before writing a file.
- Very specific: line 71
Check whether the endpoint already exists in references/api.md.
- Very specific: line 73
Verify with references/verification.md — at minimum bun run typecheck, and a real browser check for anything visual.
- Very specific: line 82
Buttons with actions use
ActionButton: Every button performing an action must use @/components/arc/action-button/action-button rather … - Very specific: line 83
Copy operations require
useClipboard: All copy interactions must use useClipboard from @/hooks/useClipboard (hooks/useClipboard.ts) … - Very specific: line 87
Forms and submission: No external form libraries (like TanStack Form).
- Distinctive: line 111
If neither registry carries it either, stop and tell the user which component is missing and what you propose to add.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SRouter Frontend
Work inside client/. The dashboard is the operator UI for the SRouter gateway; the Rust server in server/ owns every wire shape, status code, and cookie.
Process is not optional. Every change runs through the superpowers workflow (AGENTS.md → ## Workflow: Superpowers Skills (Mandatory)): brainstorming before design, writing-plans before multi-step work, test-driven-development before product code (the client has no unit-test runner: substitute bun run typecheck && bun run lint && bun run build plus a browser pass, and say so; the Playwright suite is bun run e2e), systematic-debugging before any fix, verification-before-completion before claiming done, requesting-code-review before merge. Any *.tsx file also loads vercel-react-best-practices before the first edit: filename trigger, no exception. TanStack work loads its library skill: tanstack-query for src/api/** and every queryOptions/useQuery/useMutation/QueryClient call, tanstack-router for src/routes/**, routeTree.gen.ts, createFileRoute, validateSearch, and route loaders.
Current state: every route is wired to the API. /login (first-run setup, password sign-in, access-token sign-in), /portal (customer console: own account, keys, and logs behind the user session), and under _authenticated/: / (overview: analyticsQuery from @/api/logs plus live SSE via useLogEvents), providers + providers/$providerId (providersListQuery, providerDetailQuery, the provider mutations, and the Command Code sign-in), keys (keyListQuery, usersQuery, create/rotate/delete, model allowlist), logs (logsQuery, live prepend, LogDetailSheet), usage (analyticsQuery, usageStatsQuery, keyListQuery), users (customer accounts and their one-time tokens), and settings (settingsQuery, systemQuery, password change, database export/import). Do not assume a screen is wired because a type exists: check for an @/api import. The operator session is shared through components/admin-auth-provider.tsx (useAdminAuth()).
When to Use
- Editing anything under
client/src,client/vite.config.ts, orclient/package.json. - Adding a route, a query, a mutation, or a component.
- Anything touching the admin session, the
/v1proxy, or generated wire types. - Not for server changes: routes, guards, and error envelopes live in
server/src(seeAGENTS.md).
Non-negotiables
- NEVER edit a generated file.
client/src/generated/**,client/src/routeTree.gen.ts, and their server twinserver/bindings.tsare build output. See Never hand-edit generated files — no exception, not even to make a check pass. - Stay same-origin. No API base URL, no cross-origin dev server, never strip
Originin the proxy. The session is an HttpOnlySameSite=Laxcookie and the server CSRF-checks theOriginheader. credentials: "include"on every request — without it the session silently does not persist.- Wire types come from Rust — requests included. Need a new shape? Add it in
server/srcwithspecta::Type, runcargo run --manifest-path server/Cargo.toml --bin export_ts, commit both files. A request body type (AdminSetupInput,CreateAPIKeyInput) is declared the same way; a hand-written request shape or an inlinebody: { ... }literal inclient/srcis a defect. - One network layer. All requests go through
request<T>()inclient/src/api/client.ts. Components never callfetchdirectly. - Derive types, never duplicate them. A client shape is a view of a generated wire type —
Pick/Omit/Partial/Excludeover one, not a re-declared field list. Key lookup tables by their union (Record<FontFamily, string>), not bystring. Noenum—erasableSyntaxOnlybans it; use a literal union plus a const object withsatisfies Record<Union, V>. See Type discipline. - No
any, and nounknown.anyswitches the checker off;unknownmerely defers every decision to a laterinstanceof. Use the narrowest true type —Error | nullfor a TanStack Query error,objectfor a JSON body, the generated wire type for a response — and guard at the boundary.neveris exempt: it is the exhaustiveness tool, not an escape hatch. See Type discipline. - A button that runs an action MUST use Arc's
ActionButton. When a button executes an action (sync or async), useActionButtonfrom@/components/arc/action-button/action-button(default export) — never a rawui/Buttonwith a hand-rolleduseStatepending flag. Reserveui/Buttonstrictly for controls with no action to report (navigation links, toggles, dialog triggers). - Copy operations MUST use
useClipboard. Any copy action or clipboard access is required to useuseClipboardfrom@/hooks/useClipboard(client/src/hooks/useClipboard.ts) — never call rawnavigator.clipboarddirectly. (For a standalone copy trigger,CopyButtonfrom@/components/arc/copy-button/copy-buttonis available and delegates to this copy feedback flow). - Acronym and identifier casing:
APIandIDmust be uppercase. In all code identifiers (types, hooks, functions, variables, props), always writeAPI(neverApi) andID(neverIdor camelCaseid). Examples:useAPIKey(notuseApiKey),providerID(notproviderId),keyID(notkeyId),userID(notuserId),APIError(notApiError). The only exception is server wire fields which are frozen lowercasesnake_case(e.g.provider_id). - New
.tsxfiles use PascalCase filenames with no hyphens. - A file that exports one function uses
export defaultalone — no named export beside it — and a file with several exports uses named exports only. Applies to new hooks, utilities, and components; existing named exports stay as they are. Applies everywhere underclient/src, except the frozen vendor folderscomponents/ui/andcomponents/arc/; never rename files in those folders. Keep existing files and TanStack route filenames unchanged unless a separate task explicitly requests a rename. Examples:AddCustomProviderDialog.tsx, notadd-custom-provider-dialog.tsx.
Never hand-edit generated files
client/src/generated/ is off-limits entirely — the whole directory is build output, not just the one file in it today. Same for client/src/routeTree.gen.ts and its server twin server/bindings.ts.
| Path | Generated by | Regenerate with |
client/src/routeTree.gen.ts | TanStack Router — the Vite plugin, or tsr | bun run generate-routes (or any script that runs tsr generate first) |
client/src/generated/** (currently typed.ts) | specta, from the Rust wire types | cargo run --manifest-path server/Cargo.toml --bin export_ts |
server/bindings.ts | the same export | the same command |
Every edit to these files is thrown away by the next generation. Both are committed on purpose: routeTree.gen.ts so a fresh clone typechecks before any generation, typed.ts so the dashboard builds without a Rust toolchain. .gitignore ignores client/.tanstack/ (the plugin's scratch directory), not these files.
The fix is never in the generated file:
| Symptom | Wrong move | Right move |
routeTree.gen.ts has no entry for a new screen | Add the entry by hand | The route file's createFileRoute("<id>") id does not match its path — fix the file, then regenerate |
typed.ts is missing a shape | Declare the type in TypeScript | Add it in server/src with #[derive(Serialize, specta::Type)], then export_ts |
A field is string | null and you want string | Edit typed.ts | Change the Rust type; also pair #[serde(skip_serializing_if)] with #[specta(optional)] |
| A generated file looks buggy | Patch it | Fix what feeds it — the route tree, or the specta render |
server/tests/bindings.rs fails on drift | Update the committed copy by hand | export_ts writes both copies; commit them |
If a generated file must be touched at all, generate it, then git diff it to confirm the change came from the generator and not from you.
Workflow
- Read
references/architecture.md(relative to this skill directory) for where the piece belongs before writing a file. - Check whether the endpoint already exists in
references/api.md; if the response shape is missing there, read it out ofclient/src/generated/typed.ts. - Implement, following
references/conventions.md. - Verify with
references/verification.md— at minimumbun run typecheck, and a real browser check for anything visual.
Working rules
- Routes are thin. A route file declares
createFileRoute, validates search params, and renders a component. Data orchestration belongs inclient/src/api/, not in the route body. - Protected screens go under
client/src/routes/_authenticated/, never at the routes root — that is where the session gate lives. - Server state is TanStack Query only. Declare a
queryOptions()factory next to its endpoint inclient/src/api/<domain>.ts; local UI state isuseState. NouseEffect+fetch. - Errors are toasted through the in-house stack.
toast.error(...)/toast.success(...)from@/components/arc/toast-stack/toast-stack— the provider and the stack itself are mounted once inroutes/__root.tsx. No third-party toast library is installed; never add one. Branch onAPIError.status/.code, never on message text. - Reuse before adding — see Component sourcing:
arc/first,ui/second, install from the@uiarc/shadcn registries when something is missing, and ask the user before writing a bespoke component or adding a dependency. - Buttons with actions use
ActionButton. Every button performing an action must use@/components/arc/action-button/action-buttonrather than a standardui/Buttonwith custom pending state. - Copy operations require
useClipboard. All copy interactions must useuseClipboardfrom@/hooks/useClipboard(hooks/useClipboard.ts) rather than directnavigator.clipboard. - Naming casing: Format acronyms and IDs in uppercase:
API(notApi) andID(notId/id, e.g.providerID,keyID,useAPIKey). {arc, ui}undercomponents/are off-limits: never edit, rename, or move anything in them. Those two folders are library/vendor code —ui/is shadcn generated,arc/is UIARC — so their file names, directory names, and exported identifiers are frozen. Do not rename a file, a folder, or an identifier there, and do not "tidy" their contents while making an unrelated change. Work inside them only when the task is explicitly about that component, and even then keep every existing name.- Everywhere else under
components/, naming is PascalCase for exported identifiers. Forlayout/,providers/, and the root files (theme-provider.tsx,admin-auth-provider.tsx), a component, hook, context, or type is PascalCase (AppShell,SectionHeader,ProviderLogo,APIKeyPanel,OAuthPanel,ConnectAccountModal,useAdminAuth). This is the rule for new code and the direction existing code is moving; do not rename an existing identifier to apply it unless the task asks for that rename. - Forms and submission: No external form libraries (like TanStack Form). For simple forms use controlled
useState; for grouped fields and submit status (pending, error), use React 19's built-inuseActionState— zero new dependencies, safe against repo rules. - Export shape, all files: a file that exports one function uses
export defaultalone — hooks, utilities, and components alike (owner's rule, 2026-10-18). A file that exports more than one thing uses named exports only, and noexport default. Callers follow suit:import useClipboard from "@/hooks/useClipboard",import { useA, useB } from "@/hooks/useAB",import ResponsiveDialog from "@/components/seavx/ResponsiveDialog". Vendor files undercomponents/{arc,ui}/keep whatever they ship. Existing named exports are not renamed to comply. - Serial, not clever. The formatter is prettier with the Tailwind plugin: no semicolons, double quotes, 80 columns. Do not reformat unrelated code.
Component sourcing
Two component libraries are installed and registered. The order is fixed:
components/arc/— UIARC (primary vocabulary). Copy-paste components with CSS modules and semantic tokens (foundation.css, installed once;data-theme="light|dark"on<html>). Docs: the library index is https://uiarc.dev/llms.txt; every component has its own markdown page (when to use / when not to, props, keyboard, accessibility, motion, responsive). Read that page before using a component, and use only documented props.components/ui/— shadcn/ui on Base UI. Docs: https://ui.shadcn.com/docs (full component directory; machine-readable index at https://ui.shadcn.com/llms.txt). Compose with Base UI'srenderprop, never RadixasChild.
When a needed component does not exist in either folder:
- First try to compose it from what is there — that is what the Arc blocks are for.
- Otherwise install it from the registry, do not hand-roll it:
bunx shadcn@latest add @uiarc/<name>(the@uiarcregistry is already declared inclient/components.json) orbunx shadcn@latest add <name>for shadcn/ui. Arc free items install the same way; Arc Pro items are licensed — never reconstruct their source, offer the free alternative instead. - If neither registry carries it either, stop and tell the user which component is missing and what you propose to add. Do not invent a bespoke component or add a dependency without that answer.
Arc house rules (from the library's agent rules): semantic tokens only (--surface, --foreground,
--text-secondary, --border, --accent, --success/warning/danger), motion through the presets in
components/arc/lib/motion-tokens.ts with a reduced-motion branch, sentence-case copy, regular/medium
weights only, no decorative gradients or glows, no focus rings on pointer focus.
Verification
cd client
bun run typecheck # tsr generate && tsc --noEmit — always run
bun run lint # eslint .
bun run build # production bundle; run when the change affects routing or the shellA green typecheck is not proof. For any UI change, load the app in a browser and exercise the changed path — the session gate, the proxy, and the cookie behaviour only fail at runtime.
References
| File | Read it for |
references/architecture.md | Route tree, provider nesting, data flow, where files belong |
references/api.md | The fetch layer, error contract, endpoint table, wire types, SSE |
references/conventions.md | TypeScript, React, styling, forms, state, and naming rules |
references/verification.md | Commands, dev proxy, serving the built dashboard, manual smoke checks |
In this repository3 other files it gives its agents.
- AGENTS.mdAGENTS.md
- llms.txtllms.txt
- srouter-serverSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

