file-tech-debt
Files a new tech-debt GitHub issue for an out-of-scope code-review finding. Builds the dedup key, skips an open or…
Builds skeleton loading states that are pixel-perfect matches of real content. Use this skill whenever adding loading states to components, building skeletons for async data, handling pending loader states in route transitions, or implementing the shimmer animation pattern. Also
Claude Code
$ npx -y skills add gaia-harness/gaia --skill skeleton-loaders --agent claude-code
Codex
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a codex -y
Cursor
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a cursor -y
Gemini CLI
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a gemini-cli -y
GitHub Copilot
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a github-copilot -y
OpenCode
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a opencode -y
Windsurf
$ npx skills add gaia-harness/gaia --skill skeleton-loaders -a windsurf -y
name: skeleton-loaders description: Builds skeleton loading states that are pixel-perfect matches of real content. Use this skill whenever adding loading states to components, building skeletons for async data, handling pending loader states in route transitions, or implementing the shimmer animation pattern. Also trigger when the user asks about preventing layout shift during data fetching.
Contents: Transparent Text Technique; 200ms Delay Pattern; Accessibility.
Use real HTML elements (`<p>`, `<span>`, `<h2>`, `<button>`) with the same font classes as the real component, plus shimmer + transparency classes. This makes skeletons inherit exact line-height, font-size, and weight, producing pixel-perfect dimensions without hardcoded `h-*`/`w-*` values.
`animate-shimmer` is not a built-in Tailwind utility. If `app/styles/tailwind.css` has no `--animate-shimmer`, add the token and its keyframes inside the existing `@theme` block before using the class (otherwise `better-tailwindcss/no-unknown-classes` fails lint):
@theme {
--animate-shimmer: shimmer 2s linear infinite;
/* The gradient is 200% wide (bg-size-[200%_100%]); moving background-position from 100% to -100% shifts it one full tile, so the sweep loops seamlessly. */
@keyframes shimmer {
from {
background-position: 100% 0;
}
to {
background-position: -100% 0;
}
}
}Define a shared class string at the top of the skeleton component:
const shimmer = 'animate-shimmer rounded-sm bg-linear-to-r from-muted via-accent to-muted bg-size-[200%_100%] text-transparent select-none';
Copy the real component's element type and font classes, add `shimmer`. How you fill the text depends on whether the real text is static or dynamic:
import {cn} from 'cn';
// Real component
<h2 className="text-lg font-bold text-foreground">{t('profile.heading')}</h2> // static
<p className="truncate text-sm font-semibold text-foreground">{data.name}</p> // dynamic
<p className="text-xs text-muted-foreground">{data.value}</p> // dynamic
// Skeleton
<h2 className={cn('text-lg font-bold', shimmer)}>{t('profile.heading')}</h2> // same t(), exact width
<p className={cn('truncate text-sm font-semibold', shimmer)}>Name</p> // approximate
<p className={cn('text-xs', shimmer)}>Example value</p> // approximateKeep as empty divs with the shimmer class, no text needed:
<div className={cn('size-14 shrink-0', shimmer)} />Use the real element type with `tabIndex={-1}` to prevent focus. Button labels are static text, so use the real `t()` value (exact width on reveal):
<button
className={cn('w-full py-2 text-xs font-medium', shimmer)}
tabIndex={-1}
type="button"
>
{t('common.submit')}
</button>Avoid skeleton flash on fast loads. Show stale/empty content for 200ms before revealing the skeleton:
const [showSkeleton, setShowSkeleton] = useState(false);
// Valid Effect: synchronizing component state with a timer (external system).
// The cleanup prevents a state update after unmount.
useEffect(() => {
const timer = setTimeout(() => setShowSkeleton(true), 200);
return () => clearTimeout(timer);
}, []);
if (!showSkeleton) return null; // or return stale content
return <MySkeleton />;Skeleton text is transparent, but screen readers still announce it (placeholder text and any `t()` labels). Hide the skeleton from assistive tech and announce loading instead:
Claude is raw power. GAIA is order and focus. A serious harness for Claude Code. It keeps Claude-shipped code production-grade as your team scales. The React frontend is handled. You build the rest of your app on top.
Files a new tech-debt GitHub issue for an out-of-scope code-review finding. Builds the dedup key, skips an open or…
Runs GAIA wiki maintenance (sync, consolidate, lint with fixes) in one command and lands it as one commit and PR. Trigger on…
Applies descriptive, self-documenting naming in any language (TypeScript, bash, workflow YAML, Dockerfiles). Use this skill…
Maintainer-only. Rewrites a version's GAIA CHANGELOG entries as plain-language public release notes for the marketing site…
Runs test-driven development with a red-green-refactor loop, an opt-in tool for logic where test-first helps. Use when the user…
Remediates dependencies in place of Dependabot version and security updates. Discovers outdated packages and open security…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.