archestra-dev-frontend
GitHubArchestra前端开发技能,指导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.
This is the skill for day-to-day platform UI work. For visual design direction on a greenfield, user-facing surface — a marketing or landing page, a generated app, a standalone page — see design-taste-frontend instead.
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.
Notices, warnings, and announcements
Render every notice, warning, error, or announcement through one of two shared components. Do not hand-roll a notice. Do not restyle one with your own colour, border, radius, or padding classes.
InlineNotice(components/ui/inline-notice.tsx) is the default choice. Use it for a notice about the surface it sits on. Examples: a form that cannot reach a repository, a read-only panel, a validation failure. The variants arewarning(the default),error,info, andneutral. Compose it in this order: the icon, a<span className="font-medium">title, an<InlineNoticeText>explanation, then an optional action withclassName="ml-auto". Add thefloatingprop when the notice sits over content that scrolls under it.Alert(components/ui/alert.tsx) is a page-level banner. It is the content of its own row. Its body can hold several sentences, lists, or links. Its padding is too large inside a form or a dialog. UseInlineNoticethere.
Extend InlineNotice when a new case does not fit. Add a variant, or let the
variant own the new colour. Do not add utility classes at the call site. Do
not write another bespoke notice div.
The app once carried about forty hand-rolled copies of these two components. Those copies used four amber palettes and three paddings. This rule prevents that.
The variant owns every colour, including the colour of the explanation. Never give a child its own amber, red, or blue class. The two values drift apart when a palette moves.
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
-
35d3fd2
Current 2026-09-22 01:21
重构通知组件:将分散的手动实现统一为InlineNotice和Alert,规范样式与变体。
- 6290aab 2026-09-11 16:19


