用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ubaniak/scoreboard --skill frontend-style命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | frontend-style |
| description | when developing a front-end feature follow this style guide |
You are helping the user add or modify React/TypeScript frontend code in the scoreboard project. Apply the patterns below exactly. Read existing files before writing anything new.
src/
api/ ← one file per domain, all React Query hooks + type exports
entities/ ← shared TypeScript types (mirrors backend response shapes)
pages/ ← one file per route, data-fetching only (no markup)
components/ ← UI, grouped by domain folder
layouts/ ← page shell components (PageLayout, AppLayout, etc.)
providers/ ← React context (login, timer)
hooks/ ← shared custom hooks
Small and single-use. Each component file does one thing. If a component is getting large, split it: extract a named sub-component into its own file in the same folder. Do not define multiple exported components in one file. Do not build generic/reusable abstractions unless the same markup is genuinely needed in 3+ places.
No data fetching inside components. All useQuery / useMutation calls live in the page or in a component that is explicitly a "container" (rare). Regular UI components receive data and callbacks as props only.
Props type at the top of the file, unexported:
type FooProps = {
name: string;
onSubmit: (value: string) => void;
};
export const Foo = ({ name, onSubmit }: FooProps) => { … };
Scoreboard / public display components (under components/current/) use inline style objects — no Ant Design. Admin UI components use Ant Design.
src/api/<domain>.ts)One file per backend domain. Structure:
// 1. query key factory (keeps invalidation consistent)
const keys = {
all: (token: string) => ["domain", token] as const,
list: (token: string) => [...keys.all(token), "list"] as const,
get: (token: string, id: string) =>
[...keys.all(token), `get-${id}`] as const,
};
// 2. query hooks — useGet*, useList*
export const useListFoos = (props: TokenBase) =>
useQuery({
queryKey: keys.list(props.token),
enabled: !!props.token,
queryFn: () =>
fetchClient<Foo[]>(`${baseUrl}/api/foos`, {
headers: { Authorization: `Bearer ${props.token}` },
}),
});
// 3. mutation hooks — useMutate*
= () => {
queryClient = ();
({
:
(, {
: ,
: {
: ,
: ,
},
: .(body),
}),
:
queryClient.({ : keys.(props.) }),
});
};
Authorization: Bearer ${token} in headers.Content-Type: application/json only for JSON bodies — omit it for FormData.onSuccess invalidates the narrowest relevant key (prefer keys.list over keys.all).export type CreateFooProps = { … }.src/api/entities.ts:
TokenBase, CardRequestType, BoutRequestType, RoundRequestType.src/entities/<domain>.ts)Plain TypeScript types mirroring backend JSON response shapes. No classes, no methods. Optional fields use ?, never | undefined explicitly.
export type Foo = {
id: string;
name: string;
status: FooStatus;
imageUrl?: string;
};
export type FooStatus = "active" | "completed";
src/pages/<name>.tsx)Pages are the only place that call hooks from src/api/. They assemble data and pass it down as props. Keep pages thin — no significant markup, no business logic beyond wiring callbacks.
export const FooPage = () => {
const { token } = useProfile();
const foos = useListFoos({ token });
const createFoo = useMutateCreateFoo({ token });
return (
<PageLayout
title="Foos"
breadCrumbs={[{ title: <a href="/">home</a> }, { title: "foos" }]}
>
<FooList
foos={foos.data ?? []}
loading={foos.isLoading}
onCreate={(v) => createFoo.mutate(v)}
/>
</PageLayout>
);
};
const { cardId } = useParams({ strict: false }).const { token } = useProfile().src/App.tsx as createRoute(…) entries.Use PageLayout for standard admin pages:
<PageLayout
title="Page Title"
subTitle={<SomeSummaryComponent />}
action={<SomeActionButtons />}
breadCrumbs={[{ title: <a href="/">home</a> }, { title: "current" }]}
>
{/* page body */}
</PageLayout>
Scoreboard / public display routes get a full-screen fixed-inset div with background: "#0b0f1a" — no PageLayout.
| Thing | Convention |
|---|---|
| Query hook | useGet<Entity>, useList<Entities> |
| Mutation hook | useMutate<Verb><Entity> |
| Component | PascalCase matching the filename |
| Props type | <ComponentName>Props, unexported |
| Page component | <Name>Page |
| Entity type file | lowercase singular: bout.ts, card.ts |
| API file | lowercase plural: bouts.ts, cards.ts |
useQuery or useMutation inside a UI component — only in pages.components/current/).Content-Type: application/json when the body is FormData.src/entities/.useState for data that comes from the server — that's what React Query is for.