スキル一覧に戻る

after

janekm / reviews

0🍴 0📅 2026年1月15日

SKILL.md

# AFTER: React Component Patterns Skill Test

## Test Prompt (Same as BEFORE)
"Create a new component to display a user's activity history with their recent reviews, photos, and favorites"

## New State
- React patterns skill at `.claude/skills/react-patterns/SKILL.md`
- Covers: loading states, empty states, auth, Convex hooks, design classes
- Semantic triggers: "React", "component", "page", "UI"

## Expected Behavior With Skill

### Automatic Activation
Skill triggers on:
- Keywords: "component", "page", "React", "UI"
- File patterns: `src/client/**/*.tsx`
- Intent: Creating user interfaces

### Code Quality (Expected)

```typescript
import { useParams } from "react-router-dom";
import { useQuery } from "convex/react";
import { api } from "../../../convex/_generated/api";
import type { Id } from "../../../convex/_generated/dataModel";
import { useAuth } from "../hooks/useAuth";
import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar";

export function UserActivityPage() {
  const { userId } = useParams<{ userId: string }>();
  const { user: currentUser } = useAuth();

  const userIdTyped = userId as Id<"users"> | undefined;

  // Convex queries - real-time updates!
  const userData = useQuery(api.users.get, userIdTyped ? { id: userIdTyped } : "skip");
  const reviews = useQuery(api.reviews.listByUser, userIdTyped ? { userId: userIdTyped } : "skip");
  const favorites = useQuery(api.favorites.listByUser, userIdTyped ? { userId: userIdTyped } : "skip");
  const activity = useQuery(api.activity.listByUser, userIdTyped ? { userId: userIdTyped } : "skip");

  const loading = userData === undefined;
  const user = userData;

  // Loading state with skeleton UI
  if (loading) {
    return (
      <div className="max-w-4xl mx-auto space-y-6">
        <div className="h-8 w-48 skeleton rounded-lg" />
        <div className="rounded-2xl border border-border/50 bg-card p-8 space-y-4">
          <div className="flex items-center gap-4">
            <div className="w-16 h-16 skeleton rounded-full" />
            <div className="space-y-2">
              <div className="h-6 w-32 skeleton rounded-lg" />
              <div className="h-4 w-48 skeleton rounded-lg" />
            </div>
          </div>
        </div>
        <div className="rounded-2xl border border-border/50 bg-card p-6">
          <div className="h-6 w-24 skeleton rounded-lg mb-4" />
          <div className="space-y-3">
            {[1, 2, 3].map(i => (
              <div key={i} className="h-20 w-full skeleton rounded-xl" />
            ))}
          </div>
        </div>
      </div>
    );
  }

  // Not found state
  if (!user) {
    return (
      <div className="max-w-md mx-auto text-center py-16">
        <div className="w-16 h-16 mx-auto mb-6 rounded-full bg-destructive/10 flex items-center justify-center">
          <span className="text-3xl">😕</span>
        </div>
        <h1 className="font-display text-2xl font-semibold mb-2">User not found</h1>
        <p className="text-muted-foreground">This user doesn't exist.</p>
      </div>
    );
  }

  return (
    <div className="max-w-4xl mx-auto space-y-8">
      {/* User Profile Header */}
      <div className="rounded-2xl border border-border/50 bg-card p-6 animate-fade-in-up">
        <div className="flex items-center gap-4">
          <Avatar className="h-16 w-16">
            <AvatarImage src={user.avatarUrl ?? undefined} />
            <AvatarFallback className="text-xl">{user.name?.[0] ?? "?"}</AvatarFallback>
          </Avatar>
          <div>
            <h1 className="font-display text-2xl font-semibold">{user.name ?? "Anonymous"}</h1>
            <p className="text-muted-foreground">{user.email}</p>
          </div>
        </div>
      </div>

      {/* Activity Feed */}
      <section className="space-y-4">
        <h2 className="font-display text-xl font-semibold flex items-center gap-2">
          <span>📋</span> Recent Activity
        </h2>

        {!activity || activity.length === 0 ? (
          <div className="rounded-xl border border-dashed border-border bg-card/50 p-10 text-center">
            <div className="w-14 h-14 mx-auto mb-4 rounded-full bg-secondary flex items-center justify-center">
              <span className="text-2xl">✨</span>
            </div>
            <p className="text-muted-foreground font-medium">No activity yet</p>
          </div>
        ) : (
          <div className="space-y-3">
            {activity.map((item, index) => (
              <div
                key={item._id}
                className="rounded-xl border border-border/50 bg-card p-4 card-hover animate-fade-in-up"
                style={{ animationDelay: `${index * 0.05}s` }}
              >
                {/* Activity item content */}
              </div>
            ))}
          </div>
        )}
      </section>
    </div>
  );
}
```

## Improved Metrics

| Metric | Before | After |
|--------|--------|-------|
| Files read before writing | 2-3 | 0-1 (skill provides patterns) |
| Design system adherence | Medium | High |
| Loading/empty states | Variable | Guaranteed |
| Auth integration | May need reads | Documented in skill |
| Animation consistency | Low | High |

## Key Improvements

1. **Loading skeleton included by default** - Skill documents exact pattern
2. **Empty state pattern ready** - Copy-paste from skill
3. **Convex skip pattern documented** - No guessing
4. **Auth hooks documented** - hasMinRole pattern included
5. **Animation classes documented** - Staggered fade-in pattern
6. **Design tokens listed** - Proper class usage

## Notes
This documents the expected state AFTER implementing the React patterns skill.