一键导入
new-component
Scaffold a new accessible frontend component (shadcn/Radix/CVA/Tailwind) plus a test stub, following Butler's UI and a11y conventions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Scaffold a new accessible frontend component (shadcn/Radix/CVA/Tailwind) plus a test stub, following Butler's UI and a11y conventions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Scaffold a new golden case for @butler/evals — validate and append the case to golden.jsonl, re-capture the baseline, and confirm the eval gate still passes. Use when a real agent failure or escaped bug should become a regression benchmark.
Work the Butler ready-for-agent issue queue in priority order — select the highest-priority unblocked issue (P0 > P1 > P2) and run /implement-issue on it, looping up to a per-run cap. Use ONLY when explicitly asked to "work the queue" / "implement the next issue(s)", or when fired by the scheduled routine. Do not auto-run.
Break a plan, spec, or PRD into independently-grabbable issues on the project issue tracker using tracer-bullet vertical slices. Use when user wants to convert a plan into issues, create implementation tickets, or break down work into issues.
Turn the current conversation context into a PRD and publish it to the project issue tracker. Use when user wants to create a PRD from the current context.
End-to-end implementation of a GitHub issue — from reading the issue through TDD, code review, PR creation, CI monitoring, and merge. Use when the user says "implement issue
Phase 9 retrospective — read a window or set of recently-merged PRs, classify each harness/run finding with the eval-failure taxonomy, and append tagged learnings to docs/retro/learnings.md. Propose-only — writes ONLY under docs/retro/. Use when explicitly asked to "run a retrospective", "retro the last N PRs", or to review a batch of merged work for harness lessons. Do not auto-run.
| name | new-component |
| description | Scaffold a new accessible frontend component (shadcn/Radix/CVA/Tailwind) plus a test stub, following Butler's UI and a11y conventions |
| disable-model-invocation | true |
Scaffold a new @butler/frontend component that already follows the project stack
(shadcn-style + Radix UI Slot + class-variance-authority + Tailwind v4) and the
mandated accessibility conventions, together with a co-located test stub that matches
the repo's Testing Library pattern.
The generated files pass pnpm --filter @butler/frontend lint and type-check as-is.
A PascalCase component name (e.g., StatCard, MemberBadge, BookingBanner).
Derive the kebab-case slug from it (StatCard → stat-card).
Name — the PascalCase name (e.g., StatCard)slug — kebab-case (e.g., stat-card)packages/frontend/src/components/<slug>.tsx from the
Component template below, replacing __Name__ and __slug__.packages/frontend/src/__tests__/<slug>.test.tsx from the
Test template below (the repo keeps tests under src/__tests__/, importing the
component with a .js ESM extension — match that, do not invent a sibling test file).<div> where a semantic
element fits — Butler requires semantic HTML over generic containers.pnpm --filter @butler/frontend lint and pnpm --filter @butler/frontend type-check
to confirm the scaffold is clean, then fill in the real markup/variants.import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import { cn } from "../lib/utils.js";
const __Name__Variants = cva(
// Base styles. The focus-visible ring is required: keyboard users must always
// get a visible focus indicator (Butler a11y rule). Keep it on the base layer.
"relative rounded-lg outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
{
variants: {
variant: {
default: "border border-border bg-card text-card-foreground",
muted: "border border-transparent bg-muted text-muted-foreground",
},
size: {
sm: "p-3 text-sm",
default: "p-4",
lg: "p-6",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
type __Name__Props = React.ComponentProps<"section"> &
VariantProps<typeof __Name__Variants> & {
// Render as the single child element instead of a <section> (Radix Slot).
// When asChild is true, pass exactly one child element.
asChild?: boolean;
};
function __Name__({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: __Name__Props) {
// Default to a semantic <section>. Swap to the element that fits the content:
// interactive -> "button" / asChild to <a> (native keyboard handling for free)
// list -> "ul" / "ol"
// navigation -> "nav"
// Always give the element an accessible name via children, aria-label, or
// aria-labelledby — props below are spread through so callers can pass them.
const Comp = asChild ? Slot.Root : "section";
return (
<Comp
data-slot="__slug__"
data-variant={variant}
data-size={size}
className={cn(__Name__Variants({ variant, size, className }))}
{...props}
/>
);
}
export { __Name__, __Name__Variants };
export type { __Name__Props };
// @vitest-environment jsdom — required for workspace mode (vitest.workspace.ts)
import { describe, it, expect, afterEach } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import { __Name__ } from "../components/__slug__.js";
describe("__Name__", () => {
afterEach(() => {
cleanup();
});
it("renders its children", () => {
render(<__Name__ aria-label="example __slug__">content</__Name__>);
expect(screen.getByText("content")).toBeDefined();
});
it("exposes an accessible name", () => {
render(<__Name__ aria-label="example __slug__">content</__Name__>);
expect(screen.getByLabelText("example __slug__")).toBeDefined();
});
it("applies the requested variant", () => {
render(
<__Name__ aria-label="m" variant="muted">
x
</__Name__>,
);
expect(screen.getByLabelText("m").dataset.variant).toBe("muted");
});
});
button, nav, section, ul, article, …) — never a
bare <div>/<span> where a semantic element fits.aria-label,
or aria-labelledby).<button>, <a>) so
Enter/Space/Tab work for free; if you must use a non-native control, add tabIndex,
role, and key handlers.focus-visible:ring-* classes in the template base must survive
any restyle.aria-hidden="true" unless they carry meaning (then label them)..js ESM imports (../lib/utils.js, ../components/x.js) like the
rest of the app code — the @/ alias is configured for the app bundle but not for
Vitest, so @/-imported components are not testable. Only vendored components/ui/**
(coverage-excluded) uses @/.forwardRef); type props with
React.ComponentProps<"el"> and merge VariantProps, mirroring
components/ui/button.tsx.cva variants.