ソース情報
- リポジトリ
- ubaniak/scoreboard
- ソースの最終更新活動
- 2026年4月25日 12:58
- 検出された SKILL.md の言語
- 英語
- スター
- 0
- フォーク
- 1
インストール方法
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
ソースファイルを確認
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
メニュー
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/ubaniak/scoreboard --skill frontend-styleコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
SOC 職業分類に基づく
SKILL.md を表示中
| 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.