archestra-dev-frontend
GitHub用于修改 Archestra 前端 Next.js/React 代码,规范 UI 组件、TanStack Query 数据获取及 API 客户端使用。
Trigger Scenarios
Install
npx skills add archestra-ai/archestra --skill archestra-dev-frontend -g -y
SKILL.md
Frontmatter
{
"name": "archestra-dev-frontend",
"description": "Use when modifying Archestra frontend Next.js\/React code, UI components, forms, TanStack Query hooks, generated API client usage, frontend copy, or documentation links."
}
Archestra Frontend Development
Use this skill before changing files under platform/frontend/ or frontend-facing shared code.
Commands
Run commands from platform/ unless specifically instructed otherwise.
pnpm codegen # regenerates the OpenAPI spec and the API client
pnpm type-check
pnpm lint
pnpm test
pnpm knip # flags unused exports; part of frontend check:ci
Data fetching
- Use TanStack Query for data fetching.
- Prefer
useQueryoveruseSuspenseQuerywith explicit loading states. - Prefer TanStack Query over prop drilling when a component can fetch data by identifier itself.
- Only pass minimal identifiers, such as
catalogId, needed for child components to fetch or filter their own data. - TanStack Query caching prevents duplicate requests when multiple components use the same query.
API clients
- Frontend
.query.tsfiles should never call the Archestra backend withfetch()directly — use the generated SDK. Rawfetch()is only for third-party APIs the SDK does not cover (e.g. GitHub, seelib/github/*.query.ts). - Run
pnpm codegenfirst to ensure the generated SDK is up to date (codegen:api-clientalone only exists inside@archestra/sharedand needs the env var:CODEGEN=true pnpm --filter @archestra/shared codegen:api-client— withoutCODEGEN=trueit reads a livelocalhost:9000instead of the committed spec). - Use generated SDK methods instead of manual API calls for type safety and consistency.
- Reuse API types from
@archestra/shared, especiallyarchestraApiTypestypes such asarchestraApiTypes.CreateXxxData["body"]andarchestraApiTypes.GetXxxResponses["200"]. - Do not define duplicate frontend API types when generated/shared types already exist.
Query error handling
- Handle toasts in
.query.tsfiles, not in components. - Define mutation success/error toasts in
onSuccessandonErrorcallbacks. - Queries must fail loud: call
throwOnApiError(error)after the SDK call so the query enters its error state, then keep the existing success return (return data ?? []). Swallowing an error into a default makes an outage indistinguishable from a genuinely empty result, which is how an offline app showed "Add an LLM Provider Key". throwOnApiError(error)toasts viahandleApiErrorby default. Screens that render their own error state (e.g. aQueryLoadErrorretry panel gated onisLoadingError) pass{ toastOnError: false }to avoid a redundant toast and a fresh toast on every retry. Detail endpoints where a 404 means "does not exist" rather than an outage pass{ allowNotFound: true }and keep returning theirnulldefault for that case.- Mutations keep
handleApiError(error)+throw toApiError(error)in themutationFn. - Components should not use
try/catchfor API calls; API error handling belongs in.query.tsfiles.
UI components
- Use shadcn/ui components only.
- Add shadcn/ui components with
npx shadcn@latest add <component>. - Prefer components from
frontend/src/components/uiover plain HTML elements when a component exists. - Use
Buttonover raw<button>,Inputover raw<input>, and the matching UI component for selects and other controls. - Keep components small and focused, with extracted business logic where it improves clarity.
- Keep frontend files flat where practical and avoid barrel files.
- Only export what is needed externally.
Text nodes and machine translation
Chrome page-translate re-parents bare text nodes into <font> wrappers. React still holds the original nodes, so deleting one — or inserting an element before it — throws NotFoundError and crashes the page (facebook/react#11538, no upstream fix). Never let React add, remove, or replace a bare text node: wrap conditional text in an element so only elements move.
biome-plugins/no-conditional-bare-jsx-text.grit fails the build on the shapes below, and its diagnostics cannot be suppressed with biome-ignore — write them wrapped in the first place:
{cond ? <Icon /> : "More"}→{cond ? <Icon /> : <span>More</span>}.{cond ? (<><Loader2 />Loading…</>) : ("Load more")}→ wrap both branches; the fragment's own bare text is deleted when the branch flips, so<span>Loading…</span>inside it and<span>Load more</span>for the string.{saved && "Saved!"}→{saved && <span>Saved!</span>}.{n > 0 ? " and more" : ""}→{n > 0 ? <span> and more</span> : null}— returnnull, never"", and keep the padding spaces inside the span.<Button>{pending ? <Loader2 /> : <Icon />} Save</Button>→ wrap the label:<span>Save</span>. Same for a label expression:<span>{agent ? "Update" : "Create"}</span>.
The rule cannot see these; apply the convention by hand:
- A
ReactNodeprop or variable rendered next to a conditional sibling ({icon}{label}) — wrap it:{icon}<span>{label}</span>(app/messaging-channels/layout.tsx). - Loading/empty/data branches whose roots are the same tag — React reconciles the element and deletes the bare status text in place. Wrap each branch's text (
<span>Loading tools…</span>) or give the branches distinctkeys. - A shared component rendering a
ReactNodeslot inside an element that persists across content changes — key the wrapper by the content, ascomponents/form-dialog.tsxandcomponents/ui/searchable-select.tsxdo.
Safe, do not churn: text→text updates ({saving ? "Saving…" : "Save"}), whole-element unmounts, and strings in attributes.
Wrapping splits a string across sibling elements, so Testing Library's default getByText stops matching. Scope to a container with toHaveTextContent, or use a function matcher constrained by tag — do not unwrap the span to satisfy a test.
Forms
- Prefer
useFormfromreact-hook-formover multipleuseStatehooks for form state. - Pass form objects to child components as
form: UseFormReturn<FormValues>rather than passing individual setters. - Parent components should handle mutations and submission.
- Form components should focus on rendering and validation UI.
Copy and documentation links
- Do not hardcode
Archestrain frontend UI copy. - Use
const appName = useAppName();and interpolate the app name so white-labeled deployments render correctly. - Always use
getDocsUrl(DocsPage.PageName, "optional-anchor")from@archestra/sharedfor documentation links. - Never hardcode documentation URLs.
Test mocking
- Frequently-mocked modules have Jest-style
__mocks__canonical mocks — activate with a barevi.mock("<specifier>");and configure per test viavi.mocked(...). Covered:@/lib/auth/auth.query,@/lib/organization.query,@/lib/config/config.query,@/lib/teams/team.query,@/lib/hooks/use-app-name,@/lib/clients/auth/auth-client(a memoized proxy — every path likeauthClient.signIn.emailis a stablevi.fn()), plus root-level__mocks__/fornext/navigationandsonner. - Do not write a bespoke partial factory for those specifiers. Exception: a file that partially mocks
@/lib/config/configmay keep factories for the query mocks — the canonical mocks'importActualchain eagerly loadsauth-client→config/configand breaks under a partial config mock. - The
@alias must stay declared invitest.config.tsresolve.aliaswith an absolute path — tsconfig-paths-only aliasing silently breaks__mocks__resolution (vitest-dev/vitest#8343).
Version History
- 3053975 Current 2026-08-12 09:04


