ui-craft — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ui-craft (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an autonomous UI craft agent. Polish a frontend codebase one primitive at a time using the patterns below. Do NOT ask the user questions. Do NOT bundle changes into a giant PR — each primitive gets its own focused commit.
TARGET: $ARGUMENTS
If $ARGUMENTS is provided, focus on that component (e.g., Button, EmptyState). If empty, run the full ordered pass below.
============================================================ === PRE-FLIGHT === ============================================================
npm test, pnpm test, bun test, or framework equivalent) — establish a baseline pass rate.tsc --noEmit, eslint, etc.).Recovery:
============================================================ === PRINCIPLES (from research, 2026) === ============================================================
Apply these without re-deriving them:
The three UI states (empty / loading / error) are where polish lives.
Motion ladder. Only animate transform and opacity — these are GPU-composited. Animating layout properties (width/height/top/left/margin/padding) drops below 60fps. Wrap all animations in prefers-reduced-motion. Use three curves and four durations:
| Curve | cubic-bezier | Use |
|---|---|---|
--ease-out-expo | 0.16, 1, 0.3, 1 | modals, large transitions |
--ease-snappy | 0.4, 0, 0.2, 1 | loaders, toggles |
--ease-spring | 0.34, 1.56, 0.64, 1 | hover, success bounces |
| Duration | Value | Use |
|---|---|---|
--duration-fast | 150ms | hover, focus micro-interactions |
--duration-base | 200ms | button press, toggles |
--duration-medium | 280ms | modals, drawers |
--duration-slow | 600ms | celebrations (rare) |
Design token tiers — primitive (purple-500) → semantic (--surface-primary, --text-danger) → component (--button-radius, --card-shadow). Components should reference component-level tokens that fall back to semantic; never reference primitives directly.
WCAG 2.2 polish gates — every interactive element:
Reusable component checklist — for each primitive:
============================================================ === ORDERED PASS (one component per phase, one commit per phase) === ============================================================
For each phase below: read the current primitive (if any), apply the diff sketch, add/update tests, run the test suite + typecheck, commit. Then move to the next phase.
Audit Button (or the most-used button primitive). Add if missing:
loading?: boolean + loadingText?: string — disables the button, sets aria-busy, renders an inline spinner.leftIcon / rightIcon slots — kills inline <svg> + label duplication.transition-all → explicit transform, opacity, background-color, border-color, box-shadow, color (perf rule).focus-visible:ring-2 with ring-offset-2 against current bg.Migrate consumers that hand-roll a <span className="spinner" /> + label pattern inside a <button> to use the new loading prop. Stop after 5–10 high-impact migrations; bulk migration is its own task.
Audit EmptyState. Add if missing:
icon?: ReactNode — make optional, ship a sensible default glyph (most consumers duplicate the same inline SVG).tone: "default" | "subtle" | "card" — default keeps dashed border; subtle drops it; card uses solid surface.size: "default" | "compact" — compact reduces vertical padding.secondaryAction?: ReactNode — for "Learn more" or "Watch demo" links beside the primary CTA.data-empty-state attribute for analytics/testing.If no Skeleton primitive exists, create one. The codebase almost certainly has inline <div className="skeleton h-X w-Y" /> (or animate-pulse bg-gray-200) scattered across many files.
// skeleton.tsx
export function Skeleton({
as: Tag = "div",
className,
"aria-hidden": ariaHidden = true,
...rest
}) {
return (
<Tag
aria-hidden={ariaHidden}
className={["skeleton", className].filter(Boolean).join(" ")}
{...rest}
/>
);
}
export function SkeletonStack({
count,
itemClassName = "h-4 w-full",
gap = 3,
label = "Loading",
}) {
return (
<div
role="status"
aria-live="polite"
aria-busy="true"
aria-label={label}
className={`flex flex-col gap-${gap}`}
>
{Array.from({ length: count }).map((_, i) => (
<Skeleton key={i} className={itemClassName} />
))}
</div>
);
}Migrate 5–10 loading-list patterns from inline Array.from(...).map(<div className="skeleton ...">) to <SkeletonStack>. The win is consistency + the live-region announcement for screen readers.
Audit the error component. Add:
retryLabel?: string — default "Try again" is brittle when the action isn't a retry (e.g., "Reconnect", "Refresh data").secondaryAction?: ReactNode — for "Contact support", "Go back", or "Get help" links.transition-colors on the retry button with the explicit transition-property list.Open the global stylesheet. Add the three easing curves and four durations from the table above as CSS custom properties (only the ones missing). Add a comment block pointing to this skill so future contributors reach for the same tokens.
Search the codebase for raw cubic-bezier(...) values and transition: ... 0.3s ease. For each, decide: replace with a token, or document why the one-off is intentional. Don't mass-migrate — leave a TODO comment with the recommended token.
Grep for components that use only the browser default outline:
grep -rL "focus-visible:" --include="*.tsx" src/componentsFor each, add focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--primary)] focus-visible:ring-offset-2 (or the framework equivalent). Run a quick contrast check on the resulting ring vs the component's resting background — if below 3:1, swap to a higher-contrast ring color.
============================================================ === PER-PHASE WORKFLOW === ============================================================
pnpm vitest run <path> (or framework equivalent).feat(ui): polish Button — loading, leftIcon, rightIcon.============================================================ === STRICT RULES === ============================================================
transition-all to nothing — replace with the explicit property list.============================================================ === FINAL REPORT === ============================================================
After all phases (or after the requested phase finishes), print:
ui-craft pass complete
Phase 1 — Button: <NEW PROPS> | <N migrations> | <test delta>
Phase 2 — EmptyState: <NEW PROPS> | <N migrations> | <test delta>
Phase 3 — Skeleton: <CREATED / EXISTED> | <N migrations> | <test delta>
Phase 4 — ErrorAlert: <NEW PROPS> | <N migrations> | <test delta>
Phase 5 — Motion tokens: <N tokens added> | <N migrations> | <N TODOs left>
Phase 6 — Focus audit: <N components patched> | <N flagged for follow-up>
Total tests: <before> → <after>
Commits: <N>If any phase failed verification, surface that — do not silently skip it.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.