Claude Cursor Skill

senior-frontend

Frontend development for React 19, Next.js 15, TypeScript, and Tailwind CSS v4 / Pengembangan frontend dengan React 19, Next.js 15, TypeScript, dan Tailwind CSS v4.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download roedyrustam-vibes-plug-skills_senior-frontend-5a27cfb.zip · 35 KB
Part of roedyrustam/vibes-plug — 10 skills

Install

skills CLI npx skills add https://github.com/roedyrustam/vibes-plug/tree/main/skills/senior-frontend
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install roedyrustam-vibes-plug@llmmart
Git git clone https://github.com/roedyrustam/vibes-plug.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole roedyrustam/vibes-plug collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Senior Frontend Specialist (2026 Edition)

English | Bahasa Indonesia


English

Description

Production-grade frontend development patterns, performance optimization, and modern ecosystem integrations for React 19 / Next.js 15 applications with Tailwind CSS v4 and TypeScript. Covers React 19 Compiler, Partial Prerendering (PPR), View Transitions API, and SPA/MPA hybrid strategies with automated self-healing testing loops.

Orchestration & Integration

  • design-system-architect: For building robust UI components (Radix, Base UI, shadcn) and design tokens.
  • tailwind-expert: For styling and custom @theme configurations.
  • svg-animation-motion-expert: For advanced animations, GSAP timelines, and motion physics.
  • nextjs-app-router-expert: For RSC, caching, and Next.js routing patterns.
  • state-management-expert: For client-side state (Zustand, Jotai).
  • tanstack-query-expert: For advanced data fetching and server state caching.
  • spa-orchestrator: For Single-Page Application architectures.
  • mpa-orchestrator: For Multi-Page Application architectures.
  • autonomous-tdd-debugger: For automated unit/component test execution and stack-trace self-healing.

Trigger Conditions

  • Scaffold a new React or Next.js 15 project with TypeScript and Tailwind CSS v4.
  • Generate new components, custom hooks, or Server Actions.
  • Analyze and optimize bundle sizes and Core Web Vitals.
  • Implement advanced React 19 patterns (useActionState, useOptimistic, use()).
  • Build SPAs — coordinate with spa-orchestrator.
  • Ensure accessibility compliance (WCAG 2.2) and testing.

Technical Guidelines & Best Practices

1. Next.js 15: Server vs Client Components

Use Server Components by default for performance and SEO. Use 'use client' strictly for:

  • State (useState, useEffect), event handlers (onClick), browser APIs.
  • Interactive UI needing client hydration.

Next.js 15.2+ Requirements:

  • Await Promise-based params and searchParams before accessing properties.
  • Use Partial Prerendering (PPR) natively. Wrap dynamic sections in <Suspense>.

2. React 19 Compiler & Hooks

Do not use useMemo, useCallback, or React.memo unless explicitly required; rely on the React 19 compiler.

Mandatory Hooks:

  • useActionState: Manage form state, pending indicators, and action results with Server Actions.
  • useOptimistic: Update UI instantly before server confirmation.
  • useFormStatus: Read form submission state in child components.
  • use(promise): Unwrap promises or context inside render with Suspense.

3. View Transitions API

Implement native View Transitions API for smooth page transitions. Use Next.js 15 <Link viewTransition> natively.

4. Tailwind CSS v4

Use CSS-first configuration. Define custom tokens with @theme and register plugins with @plugin in the main CSS file. Never create tailwind.config.js.

5. Accessibility (WCAG 2.2) & Self-Healing Testing

  • Enforce semantic HTML (<button>, <nav>, <main>, <article>).
  • Enforce full keyboard navigability and valid aria-* labels.
  • Run typecheck (tsc --noEmit) to verify zero compiler diagnostics.
  • Write unit tests using Vitest and React Testing Library.
  • When tests fail, activate autonomous-tdd-debugger to self-heal until 100% passing.

6. Architecture Integration

  • SPA (spa-orchestrator): Use TanStack Router for type-safe client routing and TanStack Query v5 for server state. Never use bare useEffect for data fetching.
  • MPA (mpa-orchestrator): Use Alpine.js or HTMX for micro-interactions. Apply Progressive Enhancement.

7. Animations & Motion

For web animations, scroll-driven timelines, GSAP choreographies, and Framer Motion spring physics, delegate directly to svg-animation-motion-expert.


Bahasa Indonesia

Deskripsi

Pola pengembangan frontend tingkat produksi, optimasi performa, dan integrasi ekosistem modern untuk aplikasi React 19 / Next.js 15 dengan Tailwind CSS v4 dan TypeScript. Mencakup React 19 Compiler, Partial Prerendering (PPR), View Transitions API, dan strategi hybrid SPA/MPA dengan loop pengujian perbaikan mandiri (self-healing).

Integrasi Orkestrasi

  • design-system-architect: Untuk membangun komponen UI yang kuat (Radix, Base UI, shadcn) dan design token.
  • tailwind-expert: Untuk styling dan konfigurasi @theme kustom.
  • svg-animation-motion-expert: Untuk animasi web, timeline GSAP, dan fisika spring.
  • nextjs-app-router-expert: Untuk RSC, caching, dan pola routing Next.js.
  • state-management-expert: Untuk state client-side (Zustand, Jotai).
  • tanstack-query-expert: Untuk fetching data lanjutan dan caching server state.
  • spa-orchestrator: Untuk arsitektur Single-Page Application.
  • mpa-orchestrator: Untuk arsitektur Multi-Page Application.
  • autonomous-tdd-debugger: Untuk eksekusi pengujian otomatis dan perbaikan mandiri berdasarkan stack trace terminal.

Kondisi Pemicu

  • Buat proyek React atau Next.js 15 baru dengan TypeScript dan Tailwind CSS v4.
  • Buat komponen baru, custom hooks, atau Server Actions.
  • Analisis dan optimalkan ukuran bundle dan Core Web Vitals.
  • Terapkan pola React 19 lanjutan (useActionState, useOptimistic, use()).
  • Bangun SPA — koordinasikan dengan spa-orchestrator.
  • Pastikan kepatuhan aksesibilitas (WCAG 2.2) dan pengujian.

Panduan Teknis & Praktik Terbaik

1. Next.js 15: Server vs Client Components

Gunakan Server Components secara default. Gunakan 'use client' hanya untuk:

  • State (useState, useEffect), event handler, API browser.
  • UI interaktif yang membutuhkan hidrasi klien.

Persyaratan Next.js 15.2+:

  • Wajib await pada params dan searchParams yang kini bertipe Promise.
  • Gunakan Partial Prerendering (PPR) secara native. Bungkus bagian dinamis dengan <Suspense>.

2. React 19 Compiler & Hook Baru

Jangan gunakan useMemo, useCallback, atau React.memo kecuali sangat diperlukan; andalkan compiler React 19.

Hook Wajib:

  • useActionState: Kelola state form, indikator loading, dan hasil action dengan Server Actions.
  • useOptimistic: Perbarui UI secara instan sebelum server mengonfirmasi.
  • useFormStatus: Baca status form di dalam komponen anak.
  • use(promise): Buka promise atau context langsung di dalam render dengan Suspense.

3. View Transitions API

Terapkan View Transitions API bawaan browser untuk transisi halaman. Gunakan <Link viewTransition> pada Next.js 15.

4. Tailwind CSS v4

Gunakan konfigurasi CSS-first. Definisikan token kustom dengan @theme dan plugin dengan @plugin di file CSS utama (lihat tailwind-expert). Jangan pernah membuat file tailwind.config.js.

5. Aksesibilitas (WCAG 2.2) & Pengujian Self-Healing

  • Wajib gunakan HTML semantik (<button>, <nav>, <main>).
  • Wajib pastikan navigasi keyboard dengan label aria-* yang valid.
  • Jalankan pemeriksaan tipe (tsc --noEmit) untuk memastikan nol eror diagnostik.
  • Tulis pengujian unit dengan Vitest dan React Testing Library.
  • Jika pengujian gagal, aktifkan autonomous-tdd-debugger untuk memulihkan kode hingga 100% lolos.

6. Integrasi Arsitektur

  • SPA (spa-orchestrator): Gunakan TanStack Router untuk routing klien dan TanStack Query v5 untuk server state. Jangan gunakan useEffect murni untuk fetching data.
  • MPA (mpa-orchestrator): Gunakan Alpine.js atau HTMX untuk interaksi mikro. Terapkan Progressive Enhancement.

7. Animasi & Motion

Untuk animasi web, timeline scroll-driven, koreografi GSAP, dan fisika spring Framer Motion, delegasikan langsung ke svg-animation-motion-expert.

Files (vibes-plug)
  • references
    • frontend_best_practices.md 19.3 KB
      # Frontend Best Practices
      
      Modern frontend development standards for accessibility, testing, TypeScript, and Tailwind CSS.
      
      ---
      
      ## Table of Contents
      
      - [Accessibility (a11y)](#accessibility-a11y)
      - [Testing Strategies](#testing-strategies)
      - [TypeScript Patterns](#typescript-patterns)
      - [Tailwind CSS](#tailwind-css)
      - [Project Structure](#project-structure)
      - [Security](#security)
      
      ---
      
      ## Accessibility (a11y)
      
      ### Semantic HTML
      
      ```tsx
      // BAD - Divs for everything
      <div onClick={handleClick}>Click me</div>
      <div class="header">...</div>
      <div class="nav">...</div>
      
      // GOOD - Semantic elements
      <button onClick={handleClick}>Click me</button>
      <header>...</header>
      <nav>...</nav>
      <main>...</main>
      <article>...</article>
      <aside>...</aside>
      <footer>...</footer>
      ```
      
      ### Keyboard Navigation
      
      ```tsx
      // Ensure all interactive elements are keyboard accessible
      function Modal({ isOpen, onClose, children }: ModalProps) {
        const modalRef = useRef<HTMLDivElement>(null);
      
        useEffect(() => {
          if (isOpen) {
            // Focus first focusable element
            const focusable = modalRef.current?.querySelectorAll(
              'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
            );
            (focusable?.[0] as HTMLElement)?.focus();
      
            // Trap focus within modal
            const handleTab = (e: KeyboardEvent) => {
              if (e.key === 'Tab' && focusable) {
                const first = focusable[0] as HTMLElement;
                const last = focusable[focusable.length - 1] as HTMLElement;
      
                if (e.shiftKey && document.activeElement === first) {
                  e.preventDefault();
                  last.focus();
                } else if (!e.shiftKey && document.activeElement === last) {
                  e.preventDefault();
                  first.focus();
                }
              }
      
              if (e.key === 'Escape') {
                onClose();
              }
            };
      
            document.addEventListener('keydown', handleTab);
            return () => document.removeEventListener('keydown', handleTab);
          }
        }, [isOpen, onClose]);
      
        if (!isOpen) return null;
      
        return (
          <div
            ref={modalRef}
            role="dialog"
            aria-modal="true"
            aria-labelledby="modal-title"
          >
            {children}
          </div>
        );
      }
      ```
      
      ### ARIA Attributes
      
      ```tsx
      // Live regions for dynamic content
      <div aria-live="polite" aria-atomic="true">
        {status && <p>{status}</p>}
      </div>
      
      // Loading states
      <button disabled={isLoading} aria-busy={isLoading}>
        {isLoading ? 'Loading...' : 'Submit'}
      </button>
      
      // Form labels
      <label htmlFor="email">Email address</label>
      <input
        id="email"
        type="email"
        aria-required="true"
        aria-invalid={!!errors.email}
        aria-describedby={errors.email ? 'email-error' : undefined}
      />
      {errors.email && (
        <p id="email-error" role="alert">
          {errors.email}
        </p>
      )}
      
      // Navigation
      <nav aria-label="Main navigation">
        <ul>
          <li><a href="/" aria-current={isHome ? 'page' : undefined}>Home</a></li>
          <li><a href="/about" aria-current={isAbout ? 'page' : undefined}>About</a></li>
        </ul>
      </nav>
      
      // Toggle buttons
      <button
        aria-pressed={isEnabled}
        onClick={() => setIsEnabled(!isEnabled)}
      >
        {isEnabled ? 'Enabled' : 'Disabled'}
      </button>
      
      // Expandable sections
      <button
        aria-expanded={isOpen}
        aria-controls="content-panel"
        onClick={() => setIsOpen(!isOpen)}
      >
        Show details
      </button>
      <div id="content-panel" hidden={!isOpen}>
        Content here
      </div>
      ```
      
      ### Color Contrast
      
      ```tsx
      // Ensure 4.5:1 contrast ratio for text (WCAG AA)
      // Use tools like @axe-core/react for testing
      
      // tailwind.config.js - Define accessible colors
      module.exports = {
        theme: {
          colors: {
            // Primary with proper contrast
            primary: {
              DEFAULT: '#2563eb', // Blue 600
              foreground: '#ffffff',
            },
            // Error state
            error: {
              DEFAULT: '#dc2626', // Red 600
              foreground: '#ffffff',
            },
            // Text colors with proper contrast
            foreground: '#0f172a', // Slate 900
            muted: '#64748b', // Slate 500 - minimum 4.5:1 on white
          },
        },
      };
      
      // Never rely on color alone
      <span className="text-red-600">
        <ErrorIcon aria-hidden="true" />
        <span>Error: Invalid input</span>
      </span>
      ```
      
      ### Screen Reader Only Content
      
      ```tsx
      // Visually hidden but accessible to screen readers
      const srOnly = 'absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0';
      
      // Skip link for keyboard users
      <a href="#main-content" className={srOnly + ' focus:not-sr-only focus:absolute focus:top-0'}>
        Skip to main content
      </a>
      
      // Icon buttons need labels
      <button aria-label="Close menu">
        <XIcon aria-hidden="true" />
      </button>
      
      // Or use visually hidden text
      <button>
        <XIcon aria-hidden="true" />
        <span className={srOnly}>Close menu</span>
      </button>
      ```
      
      ---
      
      ## Testing Strategies
      
      ### Component Testing with Testing Library
      
      ```tsx
      // Button.test.tsx
      import { render, screen, fireEvent } from '@testing-library/react';
      import userEvent from '@testing-library/user-event';
      import { Button } from './Button';
      
      describe('Button', () => {
        it('renders with correct text', () => {
          render(<Button>Click me</Button>);
          expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
        });
      
        it('calls onClick when clicked', async () => {
          const user = userEvent.setup();
          const handleClick = jest.fn();
      
          render(<Button onClick={handleClick}>Click me</Button>);
          await user.click(screen.getByRole('button'));
      
          expect(handleClick).toHaveBeenCalledTimes(1);
        });
      
        it('is disabled when loading', () => {
          render(<Button isLoading>Submit</Button>);
          expect(screen.getByRole('button')).toBeDisabled();
          expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
        });
      
        it('shows loading text when loading', () => {
          render(<Button isLoading loadingText="Submitting...">Submit</Button>);
          expect(screen.getByText('Submitting...')).toBeInTheDocument();
        });
      });
      ```
      
      ### Hook Testing
      
      ```tsx
      // useCounter.test.ts
      import { renderHook, act } from '@testing-library/react';
      import { useCounter } from './useCounter';
      
      describe('useCounter', () => {
        it('initializes with default value', () => {
          const { result } = renderHook(() => useCounter());
          expect(result.current.count).toBe(0);
        });
      
        it('initializes with custom value', () => {
          const { result } = renderHook(() => useCounter(10));
          expect(result.current.count).toBe(10);
        });
      
        it('increments count', () => {
          const { result } = renderHook(() => useCounter());
      
          act(() => {
            result.current.increment();
          });
      
          expect(result.current.count).toBe(1);
        });
      
        it('resets to initial value', () => {
          const { result } = renderHook(() => useCounter(5));
      
          act(() => {
            result.current.increment();
            result.current.increment();
            result.current.reset();
          });
      
          expect(result.current.count).toBe(5);
        });
      });
      ```
      
      ### Integration Testing
      
      ```tsx
      // LoginForm.test.tsx
      import { render, screen, waitFor } from '@testing-library/react';
      import userEvent from '@testing-library/user-event';
      import { LoginForm } from './LoginForm';
      import { AuthProvider } from '@/contexts/AuthContext';
      
      const mockLogin = jest.fn();
      
      jest.mock('@/lib/auth', () => ({
        login: (...args: unknown[]) => mockLogin(...args),
      }));
      
      describe('LoginForm', () => {
        beforeEach(() => {
          mockLogin.mockReset();
        });
      
        it('submits form with valid credentials', async () => {
          const user = userEvent.setup();
          mockLogin.mockResolvedValueOnce({ user: { id: '1', name: 'Test' } });
      
          render(
            <AuthProvider>
              <LoginForm />
            </AuthProvider>
          );
      
          await user.type(screen.getByLabelText(/email/i), 'test@example.com');
          await user.type(screen.getByLabelText(/password/i), 'password123');
          await user.click(screen.getByRole('button', { name: /sign in/i }));
      
          await waitFor(() => {
            expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123');
          });
        });
      
        it('shows validation errors for empty fields', async () => {
          const user = userEvent.setup();
      
          render(
            <AuthProvider>
              <LoginForm />
            </AuthProvider>
          );
      
          await user.click(screen.getByRole('button', { name: /sign in/i }));
      
          expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
          expect(await screen.findByText(/password is required/i)).toBeInTheDocument();
          expect(mockLogin).not.toHaveBeenCalled();
        });
      });
      ```
      
      ### E2E Testing with Playwright
      
      ```typescript
      // e2e/checkout.spec.ts
      import { test, expect } from '@playwright/test';
      
      test.describe('Checkout flow', () => {
        test.beforeEach(async ({ page }) => {
          await page.goto('/');
          await page.click('[data-testid="product-1"] button');
          await page.click('[data-testid="cart-button"]');
        });
      
        test('completes checkout with valid payment', async ({ page }) => {
          await page.click('text=Proceed to Checkout');
      
          // Fill shipping info
          await page.fill('[name="email"]', 'test@example.com');
          await page.fill('[name="address"]', '123 Test St');
          await page.fill('[name="city"]', 'Test City');
          await page.selectOption('[name="state"]', 'CA');
          await page.fill('[name="zip"]', '90210');
      
          await page.click('text=Continue to Payment');
          await page.click('text=Place Order');
      
          // Verify success
          await expect(page).toHaveURL(/\/order\/confirmation/);
          await expect(page.locator('h1')).toHaveText('Order Confirmed!');
        });
      });
      ```
      
      ---
      
      ## TypeScript Patterns
      
      ### Component Props
      
      ```tsx
      // Use interface for component props
      interface ButtonProps {
        variant?: 'primary' | 'secondary' | 'ghost';
        size?: 'sm' | 'md' | 'lg';
        isLoading?: boolean;
        children: React.ReactNode;
        onClick?: () => void;
      }
      
      // Extend HTML attributes
      interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
        variant?: 'primary' | 'secondary';
        isLoading?: boolean;
      }
      
      function Button({ variant = 'primary', isLoading, children, ...props }: ButtonProps) {
        return (
          <button
            {...props}
            disabled={props.disabled || isLoading}
            className={cn(variants[variant], props.className)}
          >
            {isLoading ? <Spinner /> : children}
          </button>
        );
      }
      
      // Polymorphic components
      type PolymorphicProps<E extends React.ElementType> = {
        as?: E;
      } & React.ComponentPropsWithoutRef<E>;
      
      function Box<E extends React.ElementType = 'div'>({
        as,
        children,
        ...props
      }: PolymorphicProps<E>) {
        const Component = as || 'div';
        return <Component {...props}>{children}</Component>;
      }
      
      // Usage
      <Box as="section" id="hero">Content</Box>
      <Box as="article">Article content</Box>
      ```
      
      ### Discriminated Unions
      
      ```tsx
      // State machines with exhaustive type checking
      type AsyncState<T> =
        | { status: 'idle' }
        | { status: 'loading' }
        | { status: 'success'; data: T }
        | { status: 'error'; error: Error };
      
      function DataDisplay<T>({ state, render }: {
        state: AsyncState<T>;
        render: (data: T) => React.ReactNode;
      }) {
        switch (state.status) {
          case 'idle':
            return null;
          case 'loading':
            return <Spinner />;
          case 'success':
            return <>{render(state.data)}</>;
          case 'error':
            return <ErrorMessage error={state.error} />;
          // TypeScript ensures all cases are handled
        }
      }
      ```
      
      ### Generic Components
      
      ```tsx
      // Generic list component
      interface ListProps<T> {
        items: T[];
        renderItem: (item: T, index: number) => React.ReactNode;
        keyExtractor: (item: T) => string;
        emptyMessage?: string;
      }
      
      function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
        if (items.length === 0) {
          return <p className="text-muted">{emptyMessage || 'No items'}</p>;
        }
      
        return (
          <ul>
            {items.map((item, index) => (
              <li key={keyExtractor(item)}>{renderItem(item, index)}</li>
            ))}
          </ul>
        );
      }
      
      // Usage
      <List
        items={users}
        keyExtractor={(user) => user.id}
        renderItem={(user) => <UserCard user={user} />}
      />
      ```
      
      ### Type Guards
      
      ```tsx
      // User-defined type guards
      interface User {
        id: string;
        name: string;
        email: string;
      }
      
      interface Admin extends User {
        role: 'admin';
        permissions: string[];
      }
      
      function isAdmin(user: User): user is Admin {
        return 'role' in user && user.role === 'admin';
      }
      
      function UserBadge({ user }: { user: User }) {
        if (isAdmin(user)) {
          // TypeScript knows user is Admin here
          return <Badge variant="admin">Admin ({user.permissions.length} perms)</Badge>;
        }
      
        return <Badge>User</Badge>;
      }
      
      // API response type guards
      interface ApiSuccess<T> {
        success: true;
        data: T;
      }
      
      interface ApiError {
        success: false;
        error: string;
      }
      
      type ApiResponse<T> = ApiSuccess<T> | ApiError;
      
      function isApiSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
        return response.success === true;
      }
      ```
      
      ---
      
      ## Tailwind CSS
      
      ### Component Variants with CVA
      
      ```tsx
      import { cva, type VariantProps } from 'class-variance-authority';
      import { cn } from '@/lib/utils';
      
      const buttonVariants = cva(
        // Base styles
        'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
        {
          variants: {
            variant: {
              primary: 'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500',
              secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500',
              ghost: 'hover:bg-gray-100 hover:text-gray-900',
              destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
            },
            size: {
              sm: 'h-8 px-3 text-sm',
              md: 'h-10 px-4 text-sm',
              lg: 'h-12 px-6 text-base',
              icon: 'h-10 w-10',
            },
          },
          defaultVariants: {
            variant: 'primary',
            size: 'md',
          },
        }
      );
      
      interface ButtonProps
        extends React.ButtonHTMLAttributes<HTMLButtonElement>,
          VariantProps<typeof buttonVariants> {}
      
      function Button({ className, variant, size, ...props }: ButtonProps) {
        return (
          <button
            className={cn(buttonVariants({ variant, size }), className)}
            {...props}
          />
        );
      }
      
      // Usage
      <Button variant="primary" size="lg">Large Primary</Button>
      <Button variant="ghost" size="icon"><MenuIcon /></Button>
      ```
      
      ### Responsive Design
      
      ```tsx
      // Mobile-first responsive design
      <div className="
        grid
        grid-cols-1          {/* Mobile: 1 column */}
        sm:grid-cols-2       {/* 640px+: 2 columns */}
        lg:grid-cols-3       {/* 1024px+: 3 columns */}
        xl:grid-cols-4       {/* 1280px+: 4 columns */}
        gap-4
        sm:gap-6
        lg:gap-8
      ">
        {products.map(product => <ProductCard key={product.id} product={product} />)}
      </div>
      
      // Container with responsive padding
      <div className="container mx-auto px-4 sm:px-6 lg:px-8">
        Content
      </div>
      
      // Hide/show based on breakpoint
      <nav className="hidden md:flex">Desktop nav</nav>
      <button className="md:hidden">Mobile menu</button>
      ```
      
      ### Animation Utilities
      
      ```tsx
      // Skeleton loading
      <div className="animate-pulse space-y-4">
        <div className="h-4 bg-gray-200 rounded w-3/4" />
        <div className="h-4 bg-gray-200 rounded w-1/2" />
      </div>
      
      // Transitions
      <button className="
        transition-all
        duration-200
        ease-in-out
        hover:scale-105
        active:scale-95
      ">
        Hover me
      </button>
      
      // Custom animations in tailwind.config.js
      module.exports = {
        theme: {
          extend: {
            animation: {
              'fade-in': 'fadeIn 0.3s ease-out',
              'slide-up': 'slideUp 0.3s ease-out',
              'spin-slow': 'spin 3s linear infinite',
            },
            keyframes: {
              fadeIn: {
                '0%': { opacity: '0' },
                '100%': { opacity: '1' },
              },
              slideUp: {
                '0%': { transform: 'translateY(10px)', opacity: '0' },
                '100%': { transform: 'translateY(0)', opacity: '1' },
              },
            },
          },
        },
      };
      
      // Usage
      <div className="animate-fade-in">Fading in</div>
      ```
      
      ---
      
      ## Project Structure
      
      ### Feature-Based Structure
      
      ```
      src/
      ├── app/                    # Next.js App Router
      │   ├── (auth)/             # Auth route group
      │   │   ├── login/
      │   │   └── register/
      │   ├── dashboard/
      │   │   ├── page.tsx
      │   │   └── layout.tsx
      │   └── layout.tsx
      ├── components/
      │   ├── ui/                 # Shared UI components
      │   │   ├── Button.tsx
      │   │   ├── Input.tsx
      │   │   └── index.ts
      │   └── features/           # Feature-specific components
      │       ├── auth/
      │       │   ├── LoginForm.tsx
      │       │   └── RegisterForm.tsx
      │       └── dashboard/
      │           ├── StatsCard.tsx
      │           └── RecentActivity.tsx
      ├── hooks/                  # Custom React hooks
      │   ├── useAuth.ts
      │   ├── useDebounce.ts
      │   └── useLocalStorage.ts
      ├── lib/                    # Utilities and configs
      │   ├── utils.ts
      │   ├── api.ts
      │   └── constants.ts
      ├── types/                  # TypeScript types
      │   ├── user.ts
      │   └── api.ts
      └── styles/
          └── globals.css
      ```
      
      ### Barrel Exports
      
      ```tsx
      // components/ui/index.ts
      export { Button } from './Button';
      export { Input } from './Input';
      export { Card, CardHeader, CardContent, CardFooter } from './Card';
      export { Dialog, DialogTrigger, DialogContent } from './Dialog';
      
      // Usage
      import { Button, Input, Card } from '@/components/ui';
      ```
      
      ---
      
      ## Security
      
      ### XSS Prevention
      
      React escapes content by default, which prevents most XSS attacks. When you need to render HTML content:
      
      1. **Avoid rendering raw HTML** when possible
      2. **Sanitize with DOMPurify** for trusted content sources
      3. **Use allow-lists** for permitted tags and attributes
      
      ```tsx
      // React escapes by default - this is safe
      <div>{userInput}</div>
      
      // When you must render HTML, sanitize first
      import DOMPurify from 'dompurify';
      
      function SafeHTML({ html }: { html: string }) {
        const sanitized = DOMPurify.sanitize(html, {
          ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
          ALLOWED_ATTR: ['href'],
        });
      
        return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
      }
      ```
      
      ### Input Validation
      
      ```tsx
      import { z } from 'zod';
      import { useForm } from 'react-hook-form';
      import { zodResolver } from '@hookform/resolvers/zod';
      
      const schema = z.object({
        email: z.string().email('Invalid email address'),
        password: z.string()
          .min(8, 'Password must be at least 8 characters')
          .regex(/[A-Z]/, 'Password must contain uppercase letter')
          .regex(/[0-9]/, 'Password must contain number'),
        confirmPassword: z.string(),
      }).refine((data) => data.password === data.confirmPassword, {
        message: 'Passwords do not match',
        path: ['confirmPassword'],
      });
      
      type FormData = z.infer<typeof schema>;
      
      function RegisterForm() {
        const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
          resolver: zodResolver(schema),
        });
      
        return (
          <form onSubmit={handleSubmit(onSubmit)}>
            <Input {...register('email')} error={errors.email?.message} />
            <Input type="password" {...register('password')} error={errors.password?.message} />
            <Input type="password" {...register('confirmPassword')} error={errors.confirmPassword?.message} />
            <Button type="submit">Register</Button>
          </form>
        );
      }
      ```
      
      ### Secure API Calls
      
      ```tsx
      // Use environment variables for API endpoints
      const API_URL = process.env.NEXT_PUBLIC_API_URL;
      
      // Never include secrets in client code - use server-side API routes
      // app/api/data/route.ts
      export async function GET() {
        const response = await fetch('https://api.example.com/data', {
          headers: {
            'Authorization': `Bearer ${process.env.API_SECRET}`, // Server-side only
          },
        });
      
        return Response.json(await response.json());
      }
      ```
      
    • nextjs_optimization_guide.md 14.8 KB
      # Next.js Optimization Guide
      
      Performance optimization techniques for Next.js 15 applications with React Server Components, Turbopack, and modern caching strategies.
      
      ---
      
      ## Table of Contents
      
      - [Rendering Strategies](#rendering-strategies)
      - [Image Optimization](#image-optimization)
      - [Code Splitting](#code-splitting)
      - [Data Fetching](#data-fetching)
      - [Caching Strategies](#caching-strategies)
      - [Bundle Optimization](#bundle-optimization)
      - [Core Web Vitals](#core-web-vitals)
      
      ---
      
      ## Rendering Strategies
      
      ### Server Components (Default)
      
      Server Components render on the server and send HTML to the client. Use for data-heavy, non-interactive content.
      
      ```tsx
      // app/products/page.tsx - Server Component (default)
      async function ProductsPage() {
        // This runs on the server - no client bundle impact
        const products = await db.products.findMany();
      
        return (
          <div className="grid grid-cols-3 gap-4">
            {products.map(product => (
              <ProductCard key={product.id} product={product} />
            ))}
          </div>
        );
      }
      ```
      
      ### Client Components
      
      Use `'use client'` only when you need:
      - Event handlers (onClick, onChange)
      - State (useState, useReducer)
      - Effects (useEffect)
      - Browser APIs (window, document)
      
      ```tsx
      'use client';
      
      import { useState } from 'react';
      
      function AddToCartButton({ productId }: { productId: string }) {
        const [isAdding, setIsAdding] = useState(false);
      
        async function handleClick() {
          setIsAdding(true);
          await addToCart(productId);
          setIsAdding(false);
        }
      
        return (
          <button onClick={handleClick} disabled={isAdding}>
            {isAdding ? 'Adding...' : 'Add to Cart'}
          </button>
        );
      }
      ```
      
      ### Mixing Server and Client Components
      
      ```tsx
      // app/products/[id]/page.tsx - Server Component
      async function ProductPage({ params }: { params: { id: string } }) {
        const product = await getProduct(params.id);
      
        return (
          <div>
            {/* Server-rendered content */}
            <h1>{product.name}</h1>
            <p>{product.description}</p>
      
            {/* Client component for interactivity */}
            <AddToCartButton productId={product.id} />
      
            {/* Server component for reviews */}
            <ProductReviews productId={product.id} />
          </div>
        );
      }
      ```
      
      ### Static vs Dynamic Rendering
      
      ```tsx
      // Force static generation at build time
      export const dynamic = 'force-static';
      
      // Force dynamic rendering at request time
      export const dynamic = 'force-dynamic';
      
      // Revalidate every 60 seconds (ISR)
      export const revalidate = 60;
      
      // Revalidate on-demand
      import { revalidatePath, revalidateTag } from 'next/cache';
      
      async function updateProduct(id: string, data: ProductData) {
        await db.products.update({ where: { id }, data });
      
        // Revalidate specific path
        revalidatePath(`/products/${id}`);
      
        // Or revalidate by tag
        revalidateTag('products');
      }
      ```
      
      ---
      
      ## Image Optimization
      
      ### Next.js Image Component
      
      ```tsx
      import Image from 'next/image';
      
      // Basic optimized image
      <Image
        src="/hero.jpg"
        alt="Hero image"
        width={1200}
        height={600}
        priority // Load immediately for LCP
      />
      
      // Responsive image
      <Image
        src="/product.jpg"
        alt="Product"
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover"
      />
      
      // With placeholder blur
      import productImage from '@/public/product.jpg';
      
      <Image
        src={productImage}
        alt="Product"
        placeholder="blur" // Uses imported image data
      />
      ```
      
      ### Remote Images Configuration
      
      ```js
      // next.config.js
      module.exports = {
        images: {
          remotePatterns: [
            {
              protocol: 'https',
              hostname: 'cdn.example.com',
              pathname: '/images/**',
            },
            {
              protocol: 'https',
              hostname: '*.cloudinary.com',
            },
          ],
          // Image formats (webp is default)
          formats: ['image/avif', 'image/webp'],
          // Device sizes for srcset
          deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
          // Image sizes for srcset
          imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
        },
      };
      ```
      
      ### Lazy Loading Patterns
      
      ```tsx
      // Images below the fold - lazy load (default)
      <Image
        src="/gallery/photo1.jpg"
        alt="Gallery photo"
        width={400}
        height={300}
        loading="lazy" // Default behavior
      />
      
      // Above the fold - load immediately
      <Image
        src="/hero.jpg"
        alt="Hero"
        width={1200}
        height={600}
        priority
        loading="eager"
      />
      ```
      
      ---
      
      ## Code Splitting
      
      ### Dynamic Imports
      
      ```tsx
      import dynamic from 'next/dynamic';
      
      // Basic dynamic import
      const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
        loading: () => <ChartSkeleton />,
      });
      
      // Disable SSR for client-only components
      const MapComponent = dynamic(() => import('@/components/Map'), {
        ssr: false,
        loading: () => <div className="h-[400px] bg-gray-100" />,
      });
      
      // Named exports
      const Modal = dynamic(() =>
        import('@/components/ui').then(mod => mod.Modal)
      );
      
      // With suspense
      const DashboardCharts = dynamic(() => import('@/components/DashboardCharts'), {
        loading: () => <Suspense fallback={<ChartsSkeleton />} />,
      });
      ```
      
      ### Route-Based Splitting
      
      ```tsx
      // app/dashboard/analytics/page.tsx
      // This page only loads when /dashboard/analytics is visited
      import { Suspense } from 'react';
      import AnalyticsCharts from './AnalyticsCharts';
      
      export default function AnalyticsPage() {
        return (
          <Suspense fallback={<AnalyticsSkeleton />}>
            <AnalyticsCharts />
          </Suspense>
        );
      }
      ```
      
      ### Parallel Routes for Code Splitting
      
      ```
      app/
      ├── dashboard/
      │   ├── @analytics/
      │   │   └── page.tsx    # Loaded in parallel
      │   ├── @metrics/
      │   │   └── page.tsx    # Loaded in parallel
      │   ├── layout.tsx
      │   └── page.tsx
      ```
      
      ```tsx
      // app/dashboard/layout.tsx
      export default function DashboardLayout({
        children,
        analytics,
        metrics,
      }: {
        children: React.ReactNode;
        analytics: React.ReactNode;
        metrics: React.ReactNode;
      }) {
        return (
          <div className="grid grid-cols-2 gap-4">
            {children}
            <Suspense fallback={<AnalyticsSkeleton />}>{analytics}</Suspense>
            <Suspense fallback={<MetricsSkeleton />}>{metrics}</Suspense>
          </div>
        );
      }
      ```
      
      ---
      
      ## Data Fetching
      
      ### Server-Side Data Fetching
      
      ```tsx
      // Parallel data fetching
      async function Dashboard() {
        // Start both requests simultaneously
        const [user, stats, notifications] = await Promise.all([
          getUser(),
          getStats(),
          getNotifications(),
        ]);
      
        return (
          <div>
            <UserHeader user={user} />
            <StatsPanel stats={stats} />
            <NotificationList notifications={notifications} />
          </div>
        );
      }
      ```
      
      ### Streaming with Suspense
      
      ```tsx
      import { Suspense } from 'react';
      
      async function ProductPage({ params }: { params: { id: string } }) {
        const product = await getProduct(params.id);
      
        return (
          <div>
            {/* Immediate content */}
            <h1>{product.name}</h1>
            <p>{product.description}</p>
      
            {/* Stream reviews - don't block page */}
            <Suspense fallback={<ReviewsSkeleton />}>
              <Reviews productId={params.id} />
            </Suspense>
      
            {/* Stream recommendations */}
            <Suspense fallback={<RecommendationsSkeleton />}>
              <Recommendations productId={params.id} />
            </Suspense>
          </div>
        );
      }
      
      // Slow data component
      async function Reviews({ productId }: { productId: string }) {
        const reviews = await getReviews(productId); // Slow query
        return <ReviewList reviews={reviews} />;
      }
      ```
      
      ### Request Memoization
      
      ```tsx
      // Next.js automatically dedupes identical requests
      async function Layout({ children }) {
        const user = await getUser(); // Request 1
        return <div>{children}</div>;
      }
      
      async function Header() {
        const user = await getUser(); // Same request - cached!
        return <div>Hello, {user.name}</div>;
      }
      
      // Both components call getUser() but only one request is made
      ```
      
      ---
      
      ## Caching Strategies
      
      ### Fetch Cache Options
      
      ```tsx
      // Cache indefinitely (default for static)
      fetch('https://api.example.com/data');
      
      // No cache - always fresh
      fetch('https://api.example.com/data', { cache: 'no-store' });
      
      // Revalidate after time
      fetch('https://api.example.com/data', {
        next: { revalidate: 3600 } // 1 hour
      });
      
      // Tag-based revalidation
      fetch('https://api.example.com/products', {
        next: { tags: ['products'] }
      });
      
      // Later, revalidate by tag
      import { revalidateTag } from 'next/cache';
      revalidateTag('products');
      ```
      
      ### Route Segment Config
      
      ```tsx
      // app/products/page.tsx
      
      // Revalidate every hour
      export const revalidate = 3600;
      
      // Or force dynamic
      export const dynamic = 'force-dynamic';
      
      // Generate static params at build
      export async function generateStaticParams() {
        const products = await getProducts();
        return products.map(p => ({ id: p.id }));
      }
      ```
      
      ### unstable_cache for Custom Caching
      
      ```tsx
      import { unstable_cache } from 'next/cache';
      
      const getCachedUser = unstable_cache(
        async (userId: string) => {
          const user = await db.users.findUnique({ where: { id: userId } });
          return user;
        },
        ['user-cache'],
        {
          revalidate: 3600, // 1 hour
          tags: ['users'],
        }
      );
      
      // Usage
      const user = await getCachedUser(userId);
      ```
      
      ---
      
      ## Bundle Optimization
      
      ### Analyze Bundle Size
      
      ```bash
      # Install analyzer
      npm install @next/bundle-analyzer
      
      # Update next.config.js
      const withBundleAnalyzer = require('@next/bundle-analyzer')({
        enabled: process.env.ANALYZE === 'true',
      });
      
      module.exports = withBundleAnalyzer({
        // config
      });
      
      # Run analysis
      ANALYZE=true npm run build
      ```
      
      ### Tree Shaking Imports
      
      ```tsx
      // BAD - Imports entire library
      import _ from 'lodash';
      const result = _.debounce(fn, 300);
      
      // GOOD - Import only what you need
      import debounce from 'lodash/debounce';
      const result = debounce(fn, 300);
      
      // GOOD - Named imports (tree-shakeable)
      import { debounce } from 'lodash-es';
      ```
      
      ### Optimize Dependencies
      
      ```js
      // next.config.js
      module.exports = {
        // Transpile specific packages
        transpilePackages: ['ui-library', 'shared-utils'],
      
        // Optimize package imports
        experimental: {
          optimizePackageImports: ['lucide-react', '@heroicons/react'],
        },
      
        // External packages for server
        serverExternalPackages: ['sharp', 'bcrypt'],
      };
      ```
      
      ### Font Optimization
      
      ```tsx
      // app/layout.tsx
      import { Inter, Roboto_Mono } from 'next/font/google';
      
      const inter = Inter({
        subsets: ['latin'],
        display: 'swap',
        variable: '--font-inter',
      });
      
      const robotoMono = Roboto_Mono({
        subsets: ['latin'],
        display: 'swap',
        variable: '--font-roboto-mono',
      });
      
      export default function RootLayout({ children }) {
        return (
          <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
            <body className="font-sans">{children}</body>
          </html>
        );
      }
      ```
      
      ---
      
      ## Core Web Vitals
      
      ### Largest Contentful Paint (LCP)
      
      ```tsx
      // Optimize LCP hero image
      import Image from 'next/image';
      
      export default function Hero() {
        return (
          <section className="relative h-[600px]">
            <Image
              src="/hero.jpg"
              alt="Hero"
              fill
              priority // Preload for LCP
              sizes="100vw"
              className="object-cover"
            />
            <div className="relative z-10">
              <h1>Welcome</h1>
            </div>
          </section>
        );
      }
      
      // Preload critical resources in layout
      export default function RootLayout({ children }) {
        return (
          <html>
            <head>
              <link rel="preload" href="/hero.jpg" as="image" />
              <link rel="preconnect" href="https://fonts.googleapis.com" />
            </head>
            <body>{children}</body>
          </html>
        );
      }
      ```
      
      ### Cumulative Layout Shift (CLS)
      
      ```tsx
      // Prevent CLS with explicit dimensions
      <Image
        src="/product.jpg"
        alt="Product"
        width={400}
        height={300}
      />
      
      // Or use aspect ratio
      <div className="aspect-video relative">
        <Image src="/video-thumb.jpg" alt="Video" fill />
      </div>
      
      // Skeleton placeholders
      function ProductCard({ product }: { product?: Product }) {
        if (!product) {
          return (
            <div className="animate-pulse">
              <div className="h-48 bg-gray-200 rounded" />
              <div className="h-4 bg-gray-200 rounded mt-2 w-3/4" />
              <div className="h-4 bg-gray-200 rounded mt-1 w-1/2" />
            </div>
          );
        }
      
        return (
          <div>
            <Image src={product.image} alt={product.name} width={300} height={200} />
            <h3>{product.name}</h3>
            <p>{product.price}</p>
          </div>
        );
      }
      ```
      
      ### First Input Delay (FID) / Interaction to Next Paint (INP)
      
      ```tsx
      // Defer non-critical JavaScript
      import Script from 'next/script';
      
      export default function Layout({ children }) {
        return (
          <html>
            <body>
              {children}
      
              {/* Load analytics after page is interactive */}
              <Script
                src="https://analytics.example.com/script.js"
                strategy="afterInteractive"
              />
      
              {/* Load chat widget when idle */}
              <Script
                src="https://chat.example.com/widget.js"
                strategy="lazyOnload"
              />
            </body>
          </html>
        );
      }
      
      // Use web workers for heavy computation
      // app/components/DataProcessor.tsx
      'use client';
      
      import { useEffect, useState } from 'react';
      
      function DataProcessor({ data }: { data: number[] }) {
        const [result, setResult] = useState<number | null>(null);
      
        useEffect(() => {
          const worker = new Worker(new URL('../workers/processor.js', import.meta.url));
      
          worker.postMessage(data);
          worker.onmessage = (e) => setResult(e.data);
      
          return () => worker.terminate();
        }, [data]);
      
        return <div>Result: {result}</div>;
      }
      ```
      
      ### Measuring Performance
      
      ```tsx
      // app/components/PerformanceMonitor.tsx
      'use client';
      
      import { useReportWebVitals } from 'next/web-vitals';
      
      export function PerformanceMonitor() {
        useReportWebVitals((metric) => {
          switch (metric.name) {
            case 'LCP':
              console.log('LCP:', metric.value);
              break;
            case 'FID':
              console.log('FID:', metric.value);
              break;
            case 'CLS':
              console.log('CLS:', metric.value);
              break;
            case 'TTFB':
              console.log('TTFB:', metric.value);
              break;
          }
      
          // Send to analytics
          analytics.track('web-vital', {
            name: metric.name,
            value: metric.value,
            id: metric.id,
          });
        });
      
        return null;
      }
      ```
      
      ---
      
      ## Quick Reference
      
      ### Performance Checklist
      
      | Area | Optimization | Impact |
      |------|-------------|--------|
      | Images | Use next/image with priority for LCP | High |
      | Fonts | Use next/font with display: swap | Medium |
      | Code | Dynamic imports for heavy components | High |
      | Data | Parallel fetching with Promise.all | High |
      | Render | Server Components by default | High |
      | Cache | Configure revalidate appropriately | Medium |
      | Bundle | Tree-shake imports, analyze size | Medium |
      
      ### Config Template
      
      ```js
      // next.config.js
      /** @type {import('next').NextConfig} */
      const nextConfig = {
        images: {
          remotePatterns: [{ hostname: 'cdn.example.com' }],
          formats: ['image/avif', 'image/webp'],
        },
        experimental: {
          optimizePackageImports: ['lucide-react'],
        },
        headers: async () => [
          {
            source: '/(.*)',
            headers: [
              { key: 'X-Content-Type-Options', value: 'nosniff' },
              { key: 'X-Frame-Options', value: 'DENY' },
            ],
          },
        ],
      };
      
      module.exports = nextConfig;
      ```
      
    • react_patterns.md 16.9 KB
      # React Patterns
      
      Production-ready patterns for building scalable React applications with TypeScript.
      
      ---
      
      ## Table of Contents
      
      - [Component Composition](#component-composition)
      - [Custom Hooks](#custom-hooks)
      - [State Management](#state-management)
      - [Performance Patterns](#performance-patterns)
      - [Error Boundaries](#error-boundaries)
      - [Anti-Patterns](#anti-patterns)
      
      ---
      
      ## Component Composition
      
      ### Compound Components
      
      Use compound components when building reusable UI components with multiple related parts.
      
      ```tsx
      // Compound component pattern for a Select
      interface SelectContextType {
        value: string;
        onChange: (value: string) => void;
      }
      
      const SelectContext = createContext<SelectContextType | null>(null);
      
      function Select({ children, value, onChange }: {
        children: React.ReactNode;
        value: string;
        onChange: (value: string) => void;
      }) {
        return (
          <SelectContext.Provider value={{ value, onChange }}>
            <div className="relative">{children}</div>
          </SelectContext.Provider>
        );
      }
      
      function SelectTrigger({ children }: { children: React.ReactNode }) {
        const context = useContext(SelectContext);
        if (!context) throw new Error('SelectTrigger must be used within Select');
      
        return (
          <button className="flex items-center gap-2 px-4 py-2 border rounded">
            {children}
          </button>
        );
      }
      
      function SelectOption({ value, children }: { value: string; children: React.ReactNode }) {
        const context = useContext(SelectContext);
        if (!context) throw new Error('SelectOption must be used within Select');
      
        return (
          <div
            onClick={() => context.onChange(value)}
            className={`px-4 py-2 cursor-pointer hover:bg-gray-100 ${
              context.value === value ? 'bg-blue-50' : ''
            }`}
          >
            {children}
          </div>
        );
      }
      
      // Attach sub-components
      Select.Trigger = SelectTrigger;
      Select.Option = SelectOption;
      
      // Usage
      <Select value={selected} onChange={setSelected}>
        <Select.Trigger>Choose option</Select.Trigger>
        <Select.Option value="a">Option A</Select.Option>
        <Select.Option value="b">Option B</Select.Option>
      </Select>
      ```
      
      ### Render Props
      
      Use render props when you need to share behavior with flexible rendering.
      
      ```tsx
      interface MousePosition {
        x: number;
        y: number;
      }
      
      function MouseTracker({ render }: { render: (pos: MousePosition) => React.ReactNode }) {
        const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
      
        useEffect(() => {
          const handleMouseMove = (e: MouseEvent) => {
            setPosition({ x: e.clientX, y: e.clientY });
          };
          window.addEventListener('mousemove', handleMouseMove);
          return () => window.removeEventListener('mousemove', handleMouseMove);
        }, []);
      
        return <>{render(position)}</>;
      }
      
      // Usage
      <MouseTracker
        render={({ x, y }) => (
          <div>Mouse position: {x}, {y}</div>
        )}
      />
      ```
      
      ### Higher-Order Components (HOC)
      
      Use HOCs for cross-cutting concerns like authentication or logging.
      
      ```tsx
      function withAuth<P extends object>(WrappedComponent: React.ComponentType<P>) {
        return function AuthenticatedComponent(props: P) {
          const { user, isLoading } = useAuth();
      
          if (isLoading) return <LoadingSpinner />;
          if (!user) return <Navigate to="/login" />;
      
          return <WrappedComponent {...props} />;
        };
      }
      
      // Usage
      const ProtectedDashboard = withAuth(Dashboard);
      ```
      
      ---
      
      ## Custom Hooks
      
      ### useAsync - Handle async operations
      
      ```tsx
      interface AsyncState<T> {
        data: T | null;
        error: Error | null;
        status: 'idle' | 'loading' | 'success' | 'error';
      }
      
      function useAsync<T>(asyncFn: () => Promise<T>, deps: any[] = []) {
        const [state, setState] = useState<AsyncState<T>>({
          data: null,
          error: null,
          status: 'idle',
        });
      
        const execute = useCallback(async () => {
          setState({ data: null, error: null, status: 'loading' });
          try {
            const data = await asyncFn();
            setState({ data, error: null, status: 'success' });
          } catch (error) {
            setState({ data: null, error: error as Error, status: 'error' });
          }
        }, deps);
      
        useEffect(() => {
          execute();
        }, [execute]);
      
        return { ...state, refetch: execute };
      }
      
      // Usage
      function UserProfile({ userId }: { userId: string }) {
        const { data: user, status, error, refetch } = useAsync(
          () => fetchUser(userId),
          [userId]
        );
      
        if (status === 'loading') return <Spinner />;
        if (status === 'error') return <Error message={error?.message} />;
        if (!user) return null;
      
        return <Profile user={user} />;
      }
      ```
      
      ### useDebounce - Debounce values
      
      ```tsx
      function useDebounce<T>(value: T, delay: number): T {
        const [debouncedValue, setDebouncedValue] = useState(value);
      
        useEffect(() => {
          const timer = setTimeout(() => setDebouncedValue(value), delay);
          return () => clearTimeout(timer);
        }, [value, delay]);
      
        return debouncedValue;
      }
      
      // Usage
      function SearchInput() {
        const [query, setQuery] = useState('');
        const debouncedQuery = useDebounce(query, 300);
      
        useEffect(() => {
          if (debouncedQuery) {
            searchAPI(debouncedQuery);
          }
        }, [debouncedQuery]);
      
        return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
      }
      ```
      
      ### useLocalStorage - Persist state
      
      ```tsx
      function useLocalStorage<T>(key: string, initialValue: T) {
        const [storedValue, setStoredValue] = useState<T>(() => {
          if (typeof window === 'undefined') return initialValue;
          try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
          } catch {
            return initialValue;
          }
        });
      
        const setValue = useCallback((value: T | ((val: T) => T)) => {
          try {
            const valueToStore = value instanceof Function ? value(storedValue) : value;
            setStoredValue(valueToStore);
            if (typeof window !== 'undefined') {
              window.localStorage.setItem(key, JSON.stringify(valueToStore));
            }
          } catch (error) {
            console.error('Error saving to localStorage:', error);
          }
        }, [key, storedValue]);
      
        return [storedValue, setValue] as const;
      }
      
      // Usage
      const [theme, setTheme] = useLocalStorage('theme', 'light');
      ```
      
      ### useMediaQuery - Responsive design
      
      ```tsx
      function useMediaQuery(query: string): boolean {
        const [matches, setMatches] = useState(false);
      
        useEffect(() => {
          const media = window.matchMedia(query);
          setMatches(media.matches);
      
          const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
          media.addEventListener('change', listener);
          return () => media.removeEventListener('change', listener);
        }, [query]);
      
        return matches;
      }
      
      // Usage
      function ResponsiveNav() {
        const isMobile = useMediaQuery('(max-width: 768px)');
        return isMobile ? <MobileNav /> : <DesktopNav />;
      }
      ```
      
      ### usePrevious - Track previous values
      
      ```tsx
      function usePrevious<T>(value: T): T | undefined {
        const ref = useRef<T>();
      
        useEffect(() => {
          ref.current = value;
        }, [value]);
      
        return ref.current;
      }
      
      // Usage
      function Counter() {
        const [count, setCount] = useState(0);
        const prevCount = usePrevious(count);
      
        return (
          <div>
            Current: {count}, Previous: {prevCount}
          </div>
        );
      }
      ```
      
      ---
      
      ## State Management
      
      ### Context with Reducer
      
      For complex state that multiple components need to access.
      
      ```tsx
      // types.ts
      interface CartItem {
        id: string;
        name: string;
        price: number;
        quantity: number;
      }
      
      interface CartState {
        items: CartItem[];
        total: number;
      }
      
      type CartAction =
        | { type: 'ADD_ITEM'; payload: CartItem }
        | { type: 'REMOVE_ITEM'; payload: string }
        | { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
        | { type: 'CLEAR_CART' };
      
      // reducer.ts
      function cartReducer(state: CartState, action: CartAction): CartState {
        switch (action.type) {
          case 'ADD_ITEM': {
            const existingItem = state.items.find(i => i.id === action.payload.id);
            if (existingItem) {
              return {
                ...state,
                items: state.items.map(item =>
                  item.id === action.payload.id
                    ? { ...item, quantity: item.quantity + 1 }
                    : item
                ),
              };
            }
            return {
              ...state,
              items: [...state.items, { ...action.payload, quantity: 1 }],
            };
          }
          case 'REMOVE_ITEM':
            return {
              ...state,
              items: state.items.filter(i => i.id !== action.payload),
            };
          case 'UPDATE_QUANTITY':
            return {
              ...state,
              items: state.items.map(item =>
                item.id === action.payload.id
                  ? { ...item, quantity: action.payload.quantity }
                  : item
              ),
            };
          case 'CLEAR_CART':
            return { items: [], total: 0 };
          default:
            return state;
        }
      }
      
      // context.tsx
      const CartContext = createContext<{
        state: CartState;
        dispatch: React.Dispatch<CartAction>;
      } | null>(null);
      
      function CartProvider({ children }: { children: React.ReactNode }) {
        const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
      
        // Compute total whenever items change
        const stateWithTotal = useMemo(() => ({
          ...state,
          total: state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
        }), [state.items]);
      
        return (
          <CartContext.Provider value={{ state: stateWithTotal, dispatch }}>
            {children}
          </CartContext.Provider>
        );
      }
      
      function useCart() {
        const context = useContext(CartContext);
        if (!context) throw new Error('useCart must be used within CartProvider');
        return context;
      }
      ```
      
      ### Zustand (Lightweight Alternative)
      
      ```tsx
      import { create } from 'zustand';
      import { persist } from 'zustand/middleware';
      
      interface AuthStore {
        user: User | null;
        token: string | null;
        login: (email: string, password: string) => Promise<void>;
        logout: () => void;
      }
      
      const useAuthStore = create<AuthStore>()(
        persist(
          (set) => ({
            user: null,
            token: null,
            login: async (email, password) => {
              const { user, token } = await authAPI.login(email, password);
              set({ user, token });
            },
            logout: () => set({ user: null, token: null }),
          }),
          { name: 'auth-storage' }
        )
      );
      
      // Usage
      function Profile() {
        const { user, logout } = useAuthStore();
        return user ? <div>{user.name} <button onClick={logout}>Logout</button></div> : null;
      }
      ```
      
      ---
      
      ## Performance Patterns
      
      ### React.memo with Custom Comparison
      
      ```tsx
      interface ListItemProps {
        item: { id: string; name: string; count: number };
        onSelect: (id: string) => void;
      }
      
      const ListItem = React.memo(
        function ListItem({ item, onSelect }: ListItemProps) {
          return (
            <div onClick={() => onSelect(item.id)}>
              {item.name} ({item.count})
            </div>
          );
        },
        (prevProps, nextProps) => {
          // Only re-render if item data changed
          return (
            prevProps.item.id === nextProps.item.id &&
            prevProps.item.name === nextProps.item.name &&
            prevProps.item.count === nextProps.item.count
          );
        }
      );
      ```
      
      ### useMemo for Expensive Calculations
      
      ```tsx
      function DataTable({ data, sortColumn, filterText }: {
        data: Item[];
        sortColumn: string;
        filterText: string;
      }) {
        const processedData = useMemo(() => {
          // Filter
          let result = data.filter(item =>
            item.name.toLowerCase().includes(filterText.toLowerCase())
          );
      
          // Sort
          result = [...result].sort((a, b) => {
            const aVal = a[sortColumn as keyof Item];
            const bVal = b[sortColumn as keyof Item];
            return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
          });
      
          return result;
        }, [data, sortColumn, filterText]);
      
        return (
          <table>
            {processedData.map(item => (
              <tr key={item.id}>{/* ... */}</tr>
            ))}
          </table>
        );
      }
      ```
      
      ### useCallback for Stable References
      
      ```tsx
      function ParentComponent() {
        const [items, setItems] = useState<Item[]>([]);
      
        // Stable reference - won't cause child re-renders
        const handleItemClick = useCallback((id: string) => {
          setItems(prev => prev.map(item =>
            item.id === id ? { ...item, selected: !item.selected } : item
          ));
        }, []);
      
        const handleAddItem = useCallback((newItem: Item) => {
          setItems(prev => [...prev, newItem]);
        }, []);
      
        return (
          <>
            <ItemList items={items} onItemClick={handleItemClick} />
            <AddItemForm onAdd={handleAddItem} />
          </>
        );
      }
      ```
      
      ### Virtualization for Long Lists
      
      ```tsx
      import { useVirtualizer } from '@tanstack/react-virtual';
      
      function VirtualList({ items }: { items: Item[] }) {
        const parentRef = useRef<HTMLDivElement>(null);
      
        const virtualizer = useVirtualizer({
          count: items.length,
          getScrollElement: () => parentRef.current,
          estimateSize: () => 50, // estimated row height
          overscan: 5,
        });
      
        return (
          <div ref={parentRef} className="h-[400px] overflow-auto">
            <div
              style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}
            >
              {virtualizer.getVirtualItems().map(virtualRow => (
                <div
                  key={virtualRow.key}
                  style={{
                    position: 'absolute',
                    top: 0,
                    left: 0,
                    width: '100%',
                    height: `${virtualRow.size}px`,
                    transform: `translateY(${virtualRow.start}px)`,
                  }}
                >
                  {items[virtualRow.index].name}
                </div>
              ))}
            </div>
          </div>
        );
      }
      ```
      
      ---
      
      ## Error Boundaries
      
      ### Class-Based Error Boundary
      
      ```tsx
      interface ErrorBoundaryProps {
        children: React.ReactNode;
        fallback?: React.ReactNode;
        onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
      }
      
      interface ErrorBoundaryState {
        hasError: boolean;
        error: Error | null;
      }
      
      class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
        state: ErrorBoundaryState = { hasError: false, error: null };
      
        static getDerivedStateFromError(error: Error): ErrorBoundaryState {
          return { hasError: true, error };
        }
      
        componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
          this.props.onError?.(error, errorInfo);
          // Log to error reporting service
          console.error('Error caught:', error, errorInfo);
        }
      
        render() {
          if (this.state.hasError) {
            return this.props.fallback || (
              <div className="p-4 bg-red-50 border border-red-200 rounded">
                <h2 className="text-red-800 font-bold">Something went wrong</h2>
                <p className="text-red-600">{this.state.error?.message}</p>
                <button
                  onClick={() => this.setState({ hasError: false, error: null })}
                  className="mt-2 px-4 py-2 bg-red-600 text-white rounded"
                >
                  Try Again
                </button>
              </div>
            );
          }
      
          return this.props.children;
        }
      }
      
      // Usage
      <ErrorBoundary
        fallback={<ErrorFallback />}
        onError={(error) => trackError(error)}
      >
        <MyComponent />
      </ErrorBoundary>
      ```
      
      ### Suspense with Error Boundary
      
      ```tsx
      function DataComponent() {
        return (
          <ErrorBoundary fallback={<ErrorMessage />}>
            <Suspense fallback={<LoadingSpinner />}>
              <AsyncDataLoader />
            </Suspense>
          </ErrorBoundary>
        );
      }
      ```
      
      ---
      
      ## Anti-Patterns
      
      ### Avoid: Inline Object/Array Creation in JSX
      
      ```tsx
      // BAD - Creates new object every render, causes re-renders
      <Component style={{ color: 'red' }} items={[1, 2, 3]} />
      
      // GOOD - Define outside or use useMemo
      const style = { color: 'red' };
      const items = [1, 2, 3];
      <Component style={style} items={items} />
      
      // Or with useMemo for dynamic values
      const style = useMemo(() => ({ color: theme.primary }), [theme.primary]);
      ```
      
      ### Avoid: Index as Key for Dynamic Lists
      
      ```tsx
      // BAD - Index keys break with reordering/filtering
      {items.map((item, index) => (
        <Item key={index} data={item} />
      ))}
      
      // GOOD - Use stable unique ID
      {items.map(item => (
        <Item key={item.id} data={item} />
      ))}
      ```
      
      ### Avoid: Prop Drilling
      
      ```tsx
      // BAD - Passing props through many levels
      <App user={user}>
        <Layout user={user}>
          <Sidebar user={user}>
            <UserInfo user={user} />
          </Sidebar>
        </Layout>
      </App>
      
      // GOOD - Use Context
      const UserContext = createContext<User | null>(null);
      
      function App() {
        return (
          <UserContext.Provider value={user}>
            <Layout>
              <Sidebar>
                <UserInfo />
              </Sidebar>
            </Layout>
          </UserContext.Provider>
        );
      }
      
      function UserInfo() {
        const user = useContext(UserContext);
        return <div>{user?.name}</div>;
      }
      ```
      
      ### Avoid: Mutating State Directly
      
      ```tsx
      // BAD - Mutates state directly
      const addItem = (item: Item) => {
        items.push(item); // WRONG
        setItems(items);  // Won't trigger re-render
      };
      
      // GOOD - Create new array
      const addItem = (item: Item) => {
        setItems(prev => [...prev, item]);
      };
      
      // GOOD - For objects
      const updateUser = (field: string, value: string) => {
        setUser(prev => ({ ...prev, [field]: value }));
      };
      ```
      
      ### Avoid: useEffect for Derived State
      
      ```tsx
      // BAD - Unnecessary effect and extra render
      const [items, setItems] = useState<Item[]>([]);
      const [total, setTotal] = useState(0);
      
      useEffect(() => {
        setTotal(items.reduce((sum, item) => sum + item.price, 0));
      }, [items]);
      
      // GOOD - Compute during render
      const [items, setItems] = useState<Item[]>([]);
      const total = items.reduce((sum, item) => sum + item.price, 0);
      
      // Or useMemo for expensive calculations
      const total = useMemo(
        () => items.reduce((sum, item) => sum + item.price, 0),
        [items]
      );
      ```
      
  • scripts
    • bundle_analyzer.py 13 KB
      #!/usr/bin/env python3
      """
      Frontend Bundle Analyzer
      
      Analyzes package.json and project structure for bundle optimization opportunities,
      heavy dependencies, and best practice recommendations.
      
      Usage:
          python bundle_analyzer.py <project_dir>
          python bundle_analyzer.py . --json
          python bundle_analyzer.py /path/to/project --verbose
      """
      
      import argparse
      import json
      import os
      import re
      import sys
      from pathlib import Path
      from typing import Dict, List, Optional, Any, Tuple
      
      
      # Known heavy packages and their lighter alternatives
      HEAVY_PACKAGES = {
          "moment": {
              "size": "290KB",
              "alternative": "date-fns (12KB) or dayjs (2KB)",
              "reason": "Large locale files bundled by default"
          },
          "lodash": {
              "size": "71KB",
              "alternative": "lodash-es with tree-shaking or individual imports (lodash/get)",
              "reason": "Full library often imported when only few functions needed"
          },
          "jquery": {
              "size": "87KB",
              "alternative": "Native DOM APIs or React/Vue patterns",
              "reason": "Rarely needed in modern frameworks"
          },
          "axios": {
              "size": "14KB",
              "alternative": "Native fetch API (0KB) or ky (3KB)",
              "reason": "Fetch API covers most use cases"
          },
          "underscore": {
              "size": "17KB",
              "alternative": "Native ES6+ methods or lodash-es",
              "reason": "Most utilities now in standard JavaScript"
          },
          "chart.js": {
              "size": "180KB",
              "alternative": "recharts (bundled with React) or lightweight-charts",
              "reason": "Consider if you need all chart types"
          },
          "three": {
              "size": "600KB",
              "alternative": "None - use dynamic import for 3D features",
              "reason": "Very large, should be lazy-loaded"
          },
          "firebase": {
              "size": "400KB+",
              "alternative": "Import specific modules (firebase/auth, firebase/firestore)",
              "reason": "Modular imports significantly reduce size"
          },
          "material-ui": {
              "size": "Large",
              "alternative": "shadcn/ui (copy-paste components) or Tailwind",
              "reason": "Heavy runtime, consider headless alternatives"
          },
          "@mui/material": {
              "size": "Large",
              "alternative": "shadcn/ui or Radix UI + Tailwind",
              "reason": "Heavy runtime, consider headless alternatives"
          },
          "antd": {
              "size": "Large",
              "alternative": "shadcn/ui or Radix UI + Tailwind",
              "reason": "Heavy runtime, consider headless alternatives"
          }
      }
      
      # Recommended optimizations by package
      PACKAGE_OPTIMIZATIONS = {
          "react-icons": "Import individual icons: import { FaHome } from 'react-icons/fa'",
          "date-fns": "Use tree-shaking: import { format } from 'date-fns'",
          "@heroicons/react": "Already tree-shakeable, good choice",
          "lucide-react": "Already tree-shakeable, add to optimizePackageImports in next.config.js",
          "framer-motion": "Use dynamic import for non-critical animations",
          "recharts": "Consider lazy loading for dashboard charts",
      }
      
      # Development dependencies that should not be in dependencies
      DEV_ONLY_PACKAGES = [
          "typescript", "@types/", "eslint", "prettier", "jest", "vitest",
          "@testing-library", "cypress", "playwright", "storybook", "@storybook",
          "webpack", "vite", "rollup", "esbuild", "tailwindcss", "postcss",
          "autoprefixer", "sass", "less", "husky", "lint-staged"
      ]
      
      
      def load_package_json(project_dir: Path) -> Optional[Dict]:
          """Load and parse package.json."""
          package_path = project_dir / "package.json"
          if not package_path.exists():
              return None
      
          try:
              with open(package_path) as f:
                  return json.load(f)
          except json.JSONDecodeError:
              return None
      
      
      def analyze_dependencies(package_json: Dict) -> Dict:
          """Analyze dependencies for issues."""
          deps = package_json.get("dependencies", {})
          dev_deps = package_json.get("devDependencies", {})
      
          issues = []
          warnings = []
          optimizations = []
      
          # Check for heavy packages
          for pkg, info in HEAVY_PACKAGES.items():
              if pkg in deps:
                  issues.append({
                      "package": pkg,
                      "type": "heavy_dependency",
                      "size": info["size"],
                      "alternative": info["alternative"],
                      "reason": info["reason"]
                  })
      
          # Check for dev dependencies in production
          for pkg in deps.keys():
              for dev_pattern in DEV_ONLY_PACKAGES:
                  if dev_pattern in pkg:
                      warnings.append({
                          "package": pkg,
                          "type": "dev_in_production",
                          "message": f"{pkg} should be in devDependencies, not dependencies"
                      })
      
          # Check for optimization opportunities
          for pkg in deps.keys():
              for opt_pkg, opt_tip in PACKAGE_OPTIMIZATIONS.items():
                  if opt_pkg in pkg:
                      optimizations.append({
                          "package": pkg,
                          "tip": opt_tip
                      })
      
          # Check for outdated React patterns
          if "prop-types" in deps and ("typescript" in dev_deps or "@types/react" in dev_deps):
              warnings.append({
                  "package": "prop-types",
                  "type": "redundant",
                  "message": "prop-types is redundant when using TypeScript"
              })
      
          # Check for multiple state management libraries
          state_libs = ["redux", "@reduxjs/toolkit", "mobx", "zustand", "jotai", "recoil", "valtio"]
          found_state_libs = [lib for lib in state_libs if lib in deps]
          if len(found_state_libs) > 1:
              warnings.append({
                  "packages": found_state_libs,
                  "type": "multiple_state_libs",
                  "message": f"Multiple state management libraries found: {', '.join(found_state_libs)}"
              })
      
          return {
              "total_dependencies": len(deps),
              "total_dev_dependencies": len(dev_deps),
              "issues": issues,
              "warnings": warnings,
              "optimizations": optimizations
          }
      
      
      def check_nextjs_config(project_dir: Path) -> Dict:
          """Check Next.js configuration for optimizations."""
          config_paths = [
              project_dir / "next.config.js",
              project_dir / "next.config.mjs",
              project_dir / "next.config.ts"
          ]
      
          for config_path in config_paths:
              if config_path.exists():
                  try:
                      content = config_path.read_text()
                      suggestions = []
      
                      # Check for image optimization
                      if "images" not in content:
                          suggestions.append("Configure images.remotePatterns for optimized image loading")
      
                      # Check for package optimization
                      if "optimizePackageImports" not in content:
                          suggestions.append("Add experimental.optimizePackageImports for lucide-react, @heroicons/react")
      
                      # Check for transpilePackages
                      if "transpilePackages" not in content and "swc" not in content:
                          suggestions.append("Consider transpilePackages for monorepo packages")
      
                      return {
                          "found": True,
                          "path": str(config_path),
                          "suggestions": suggestions
                      }
                  except Exception:
                      pass
      
          return {
              "found": False,
              "suggestions": ["Create next.config.js with image and bundle optimizations"]
          }
      
      
      def analyze_imports(project_dir: Path) -> Dict:
          """Analyze import patterns in source files."""
          issues = []
          src_dirs = [project_dir / "src", project_dir / "app", project_dir / "pages"]
      
          patterns_to_check = [
              (r"import\s+\*\s+as\s+\w+\s+from\s+['\"]lodash['\"]", "Avoid import * from lodash, use individual imports"),
              (r"import\s+moment\s+from\s+['\"]moment['\"]", "Consider replacing moment with date-fns or dayjs"),
              (r"import\s+\{\s*\w+(?:,\s*\w+){5,}\s*\}\s+from\s+['\"]react-icons", "Import icons from specific icon sets (react-icons/fa)"),
          ]
      
          files_checked = 0
          for src_dir in src_dirs:
              if not src_dir.exists():
                  continue
      
              for ext in ["*.ts", "*.tsx", "*.js", "*.jsx"]:
                  for file_path in src_dir.glob(f"**/{ext}"):
                      if "node_modules" in str(file_path):
                          continue
      
                      files_checked += 1
                      try:
                          content = file_path.read_text()
                          for pattern, message in patterns_to_check:
                              if re.search(pattern, content):
                                  issues.append({
                                      "file": str(file_path.relative_to(project_dir)),
                                      "issue": message
                                  })
                      except Exception:
                          continue
      
          return {
              "files_checked": files_checked,
              "issues": issues
          }
      
      
      def calculate_score(analysis: Dict) -> Tuple[int, str]:
          """Calculate bundle health score."""
          score = 100
      
          # Deduct for heavy dependencies
          score -= len(analysis["dependencies"]["issues"]) * 10
      
          # Deduct for dev deps in production
          score -= len([w for w in analysis["dependencies"]["warnings"]
                        if w.get("type") == "dev_in_production"]) * 5
      
          # Deduct for import issues
          score -= len(analysis.get("imports", {}).get("issues", [])) * 3
      
          # Deduct for missing Next.js optimizations
          if not analysis.get("nextjs", {}).get("found", True):
              score -= 10
      
          score = max(0, min(100, score))
      
          if score >= 90:
              grade = "A"
          elif score >= 80:
              grade = "B"
          elif score >= 70:
              grade = "C"
          elif score >= 60:
              grade = "D"
          else:
              grade = "F"
      
          return score, grade
      
      
      def print_report(analysis: Dict) -> None:
          """Print human-readable report."""
          score, grade = calculate_score(analysis)
      
          print("=" * 60)
          print("FRONTEND BUNDLE ANALYSIS REPORT")
          print("=" * 60)
          print(f"\nBundle Health Score: {score}/100 ({grade})")
      
          deps = analysis["dependencies"]
          print(f"\nDependencies: {deps['total_dependencies']} production, {deps['total_dev_dependencies']} dev")
      
          # Heavy dependencies
          if deps["issues"]:
              print("\n--- HEAVY DEPENDENCIES ---")
              for issue in deps["issues"]:
                  print(f"\n  {issue['package']} ({issue['size']})")
                  print(f"    Reason: {issue['reason']}")
                  print(f"    Alternative: {issue['alternative']}")
      
          # Warnings
          if deps["warnings"]:
              print("\n--- WARNINGS ---")
              for warning in deps["warnings"]:
                  if "package" in warning:
                      print(f"  - {warning['package']}: {warning['message']}")
                  else:
                      print(f"  - {warning['message']}")
      
          # Optimizations
          if deps["optimizations"]:
              print("\n--- OPTIMIZATION TIPS ---")
              for opt in deps["optimizations"]:
                  print(f"  - {opt['package']}: {opt['tip']}")
      
          # Next.js config
          if "nextjs" in analysis:
              nextjs = analysis["nextjs"]
              if nextjs.get("suggestions"):
                  print("\n--- NEXT.JS CONFIG ---")
                  for suggestion in nextjs["suggestions"]:
                      print(f"  - {suggestion}")
      
          # Import issues
          if analysis.get("imports", {}).get("issues"):
              print("\n--- IMPORT ISSUES ---")
              for issue in analysis["imports"]["issues"][:10]:  # Limit to 10
                  print(f"  - {issue['file']}: {issue['issue']}")
      
          # Summary
          print("\n--- RECOMMENDATIONS ---")
          if score >= 90:
              print("  Bundle is well-optimized!")
          elif deps["issues"]:
              print("  1. Replace heavy dependencies with lighter alternatives")
          if deps["warnings"]:
              print("  2. Move dev-only packages to devDependencies")
          if deps["optimizations"]:
              print("  3. Apply import optimizations for tree-shaking")
      
          print("\n" + "=" * 60)
      
      
      def main():
          parser = argparse.ArgumentParser(
              description="Analyze frontend project for bundle optimization opportunities"
          )
          parser.add_argument(
              "project_dir",
              nargs="?",
              default=".",
              help="Project directory to analyze (default: current directory)"
          )
          parser.add_argument(
              "--json",
              action="store_true",
              help="Output in JSON format"
          )
          parser.add_argument(
              "--verbose", "-v",
              action="store_true",
              help="Include detailed import analysis"
          )
      
          args = parser.parse_args()
          project_dir = Path(args.project_dir).resolve()
      
          if not project_dir.exists():
              print(f"Error: Directory not found: {project_dir}", file=sys.stderr)
              sys.exit(1)
      
          package_json = load_package_json(project_dir)
          if not package_json:
              print("Error: No valid package.json found", file=sys.stderr)
              sys.exit(1)
      
          analysis = {
              "project": str(project_dir),
              "dependencies": analyze_dependencies(package_json),
              "nextjs": check_nextjs_config(project_dir)
          }
      
          if args.verbose:
              analysis["imports"] = analyze_imports(project_dir)
      
          analysis["score"], analysis["grade"] = calculate_score(analysis)
      
          if args.json:
              print(json.dumps(analysis, indent=2))
          else:
              print_report(analysis)
      
      
      if __name__ == "__main__":
          main()
      
    • component_generator.py 8.5 KB
      #!/usr/bin/env python3
      """
      React Component Generator
      
      Generates React/Next.js component files with TypeScript, Tailwind CSS,
      and optional test files following best practices.
      
      Usage:
          python component_generator.py Button --dir src/components/ui
          python component_generator.py ProductCard --type client --with-test
          python component_generator.py UserProfile --type server --with-story
      """
      
      import argparse
      import os
      import sys
      from pathlib import Path
      from datetime import datetime
      
      
      # Component templates
      TEMPLATES = {
          "client": '''\'use client\';
      
      import {{ useState }} from 'react';
      import {{ cn }} from '@/lib/utils';
      
      interface {name}Props {{
        className?: string;
        children?: React.ReactNode;
      }}
      
      export function {name}({{ className, children }}: {name}Props) {{
        return (
          <div className={{cn('', className)}}>
            {{children}}
          </div>
        );
      }}
      ''',
      
          "server": '''import {{ cn }} from '@/lib/utils';
      
      interface {name}Props {{
        className?: string;
        children?: React.ReactNode;
      }}
      
      export async function {name}({{ className, children }}: {name}Props) {{
        return (
          <div className={{cn('', className)}}>
            {{children}}
          </div>
        );
      }}
      ''',
      
          "hook": '''import {{ useState, useEffect, useCallback }} from 'react';
      
      interface Use{name}Options {{
        // Add options here
      }}
      
      interface Use{name}Return {{
        // Add return type here
        isLoading: boolean;
        error: Error | null;
      }}
      
      export function use{name}(options: Use{name}Options = {{}}): Use{name}Return {{
        const [isLoading, setIsLoading] = useState(false);
        const [error, setError] = useState<Error | null>(null);
      
        useEffect(() => {{
          // Effect logic here
        }}, []);
      
        return {{
          isLoading,
          error,
        }};
      }}
      ''',
      
          "test": '''import {{ render, screen }} from '@testing-library/react';
      import userEvent from '@testing-library/user-event';
      import {{ {name} }} from './{name}';
      
      describe('{name}', () => {{
        it('renders correctly', () => {{
          render(<{name}>Test content</{name}>);
          expect(screen.getByText('Test content')).toBeInTheDocument();
        }});
      
        it('applies custom className', () => {{
          render(<{name} className="custom-class">Content</{name}>);
          expect(screen.getByText('Content').parentElement).toHaveClass('custom-class');
        }});
      
        // Add more tests here
      }});
      ''',
      
          "story": '''import type {{ Meta, StoryObj }} from '@storybook/react';
      import {{ {name} }} from './{name}';
      
      const meta: Meta<typeof {name}> = {{
        title: 'Components/{name}',
        component: {name},
        tags: ['autodocs'],
        argTypes: {{
          className: {{
            control: 'text',
            description: 'Additional CSS classes',
          }},
        }},
      }};
      
      export default meta;
      type Story = StoryObj<typeof {name}>;
      
      export const Default: Story = {{
        args: {{
          children: 'Default content',
        }},
      }};
      
      export const WithCustomClass: Story = {{
        args: {{
          className: 'bg-blue-100 p-4',
          children: 'Styled content',
        }},
      }};
      ''',
      
          "index": '''export {{ {name} }} from './{name}';
      export type {{ {name}Props }} from './{name}';
      ''',
      }
      
      
      def to_pascal_case(name: str) -> str:
          """Convert string to PascalCase."""
          # Handle kebab-case and snake_case
          words = name.replace('-', '_').split('_')
          return ''.join(word.capitalize() for word in words)
      
      
      def to_kebab_case(name: str) -> str:
          """Convert PascalCase to kebab-case."""
          result = []
          for i, char in enumerate(name):
              if char.isupper() and i > 0:
                  result.append('-')
              result.append(char.lower())
          return ''.join(result)
      
      
      def generate_component(
          name: str,
          output_dir: Path,
          component_type: str = "client",
          with_test: bool = False,
          with_story: bool = False,
          with_index: bool = True,
          flat: bool = False,
      ) -> dict:
          """Generate component files."""
          pascal_name = to_pascal_case(name)
          kebab_name = to_kebab_case(pascal_name)
      
          # Determine output path
          if flat:
              component_dir = output_dir
          else:
              component_dir = output_dir / pascal_name
      
          files_created = []
      
          # Create directory
          component_dir.mkdir(parents=True, exist_ok=True)
      
          # Generate main component file
          if component_type == "hook":
              main_file = component_dir / f"use{pascal_name}.ts"
              template = TEMPLATES["hook"]
          else:
              main_file = component_dir / f"{pascal_name}.tsx"
              template = TEMPLATES[component_type]
      
          content = template.format(name=pascal_name)
          main_file.write_text(content)
          files_created.append(str(main_file))
      
          # Generate test file
          if with_test and component_type != "hook":
              test_file = component_dir / f"{pascal_name}.test.tsx"
              test_content = TEMPLATES["test"].format(name=pascal_name)
              test_file.write_text(test_content)
              files_created.append(str(test_file))
      
          # Generate story file
          if with_story and component_type != "hook":
              story_file = component_dir / f"{pascal_name}.stories.tsx"
              story_content = TEMPLATES["story"].format(name=pascal_name)
              story_file.write_text(story_content)
              files_created.append(str(story_file))
      
          # Generate index file
          if with_index and not flat:
              index_file = component_dir / "index.ts"
              index_content = TEMPLATES["index"].format(name=pascal_name)
              index_file.write_text(index_content)
              files_created.append(str(index_file))
      
          return {
              "name": pascal_name,
              "type": component_type,
              "directory": str(component_dir),
              "files": files_created,
          }
      
      
      def print_result(result: dict, verbose: bool = False) -> None:
          """Print generation result."""
          print(f"\n{'='*50}")
          print(f"Component Generated: {result['name']}")
          print(f"{'='*50}")
          print(f"Type: {result['type']}")
          print(f"Directory: {result['directory']}")
          print(f"\nFiles created:")
          for file in result['files']:
              print(f"  - {file}")
          print(f"{'='*50}\n")
      
          # Print usage hint
          if result['type'] != 'hook':
              print("Usage:")
              print(f"  import {{ {result['name']} }} from '@/components/{result['name']}';")
              print(f"\n  <{result['name']}>Content</{result['name']}>")
          else:
              print("Usage:")
              print(f"  import {{ use{result['name']} }} from '@/hooks/use{result['name']}';")
              print(f"\n  const {{ isLoading, error }} = use{result['name']}();")
      
      
      def main():
          parser = argparse.ArgumentParser(
              description="Generate React/Next.js components with TypeScript and Tailwind CSS"
          )
          parser.add_argument(
              "name",
              help="Component name (PascalCase or kebab-case)"
          )
          parser.add_argument(
              "--dir", "-d",
              default="src/components",
              help="Output directory (default: src/components)"
          )
          parser.add_argument(
              "--type", "-t",
              choices=["client", "server", "hook"],
              default="client",
              help="Component type (default: client)"
          )
          parser.add_argument(
              "--with-test",
              action="store_true",
              help="Generate test file"
          )
          parser.add_argument(
              "--with-story",
              action="store_true",
              help="Generate Storybook story file"
          )
          parser.add_argument(
              "--no-index",
              action="store_true",
              help="Skip generating index.ts file"
          )
          parser.add_argument(
              "--flat",
              action="store_true",
              help="Create files directly in output dir without subdirectory"
          )
          parser.add_argument(
              "--dry-run",
              action="store_true",
              help="Show what would be generated without creating files"
          )
          parser.add_argument(
              "--verbose", "-v",
              action="store_true",
              help="Enable verbose output"
          )
      
          args = parser.parse_args()
      
          output_dir = Path(args.dir)
          pascal_name = to_pascal_case(args.name)
      
          if args.dry_run:
              print(f"\nDry run - would generate:")
              print(f"  Component: {pascal_name}")
              print(f"  Type: {args.type}")
              print(f"  Directory: {output_dir / pascal_name if not args.flat else output_dir}")
              print(f"  Test: {'Yes' if args.with_test else 'No'}")
              print(f"  Story: {'Yes' if args.with_story else 'No'}")
              return
      
          try:
              result = generate_component(
                  name=args.name,
                  output_dir=output_dir,
                  component_type=args.type,
                  with_test=args.with_test,
                  with_story=args.with_story,
                  with_index=not args.no_index,
                  flat=args.flat,
              )
              print_result(result, args.verbose)
      
          except Exception as e:
              print(f"Error: {e}", file=sys.stderr)
              sys.exit(1)
      
      
      if __name__ == "__main__":
          main()
      
    • frontend_scaffolder.py 27.4 KB
      #!/usr/bin/env python3
      """
      Frontend Project Scaffolder
      
      Generates a complete Next.js/React project structure with TypeScript,
      Tailwind CSS, and best practice configurations.
      
      Usage:
          python frontend_scaffolder.py my-app --template nextjs
          python frontend_scaffolder.py dashboard --template react --features auth,api
          python frontend_scaffolder.py landing --template nextjs --dry-run
      """
      
      import argparse
      import json
      import os
      import sys
      from pathlib import Path
      from typing import Dict, List, Optional
      
      
      # Project templates
      TEMPLATES = {
          "nextjs": {
              "name": "Next.js 15+ App Router",
              "description": "Modern Next.js with App Router, Server Components, and TypeScript",
              "structure": {
                  "app": {
                      "layout.tsx": "ROOT_LAYOUT",
                      "page.tsx": "HOME_PAGE",
                      "globals.css": "GLOBALS_CSS",
                      "(auth)": {
                          "login": {"page.tsx": "AUTH_PAGE"},
                          "register": {"page.tsx": "AUTH_PAGE"},
                      },
                      "api": {
                          "health": {"route.ts": "HEALTH_ROUTE"},
                      },
                  },
                  "components": {
                      "ui": {
                          "button.tsx": "UI_BUTTON",
                          "input.tsx": "UI_INPUT",
                          "card.tsx": "UI_CARD",
                          "index.ts": "UI_INDEX",
                      },
                      "layout": {
                          "header.tsx": "LAYOUT_HEADER",
                          "footer.tsx": "LAYOUT_FOOTER",
                          "sidebar.tsx": "LAYOUT_SIDEBAR",
                      },
                  },
                  "lib": {
                      "utils.ts": "UTILS",
                      "constants.ts": "CONSTANTS",
                  },
                  "hooks": {
                      "use-debounce.ts": "HOOK_DEBOUNCE",
                      "use-local-storage.ts": "HOOK_LOCAL_STORAGE",
                  },
                  "types": {
                      "index.ts": "TYPES_INDEX",
                  },
                  "public": {
                      ".gitkeep": "EMPTY",
                  },
              },
              "config_files": [
                  "next.config.js",
                  "tailwind.config.ts",
                  "tsconfig.json",
                  "postcss.config.js",
                  ".eslintrc.json",
                  ".prettierrc",
                  ".gitignore",
                  "package.json",
              ],
          },
          "react": {
              "name": "React + Vite",
              "description": "Modern React with Vite, TypeScript, and Tailwind CSS",
              "structure": {
                  "src": {
                      "App.tsx": "REACT_APP",
                      "main.tsx": "REACT_MAIN",
                      "index.css": "GLOBALS_CSS",
                      "components": {
                          "ui": {
                              "button.tsx": "UI_BUTTON",
                              "input.tsx": "UI_INPUT",
                              "card.tsx": "UI_CARD",
                              "index.ts": "UI_INDEX",
                          },
                      },
                      "hooks": {
                          "use-debounce.ts": "HOOK_DEBOUNCE",
                          "use-local-storage.ts": "HOOK_LOCAL_STORAGE",
                      },
                      "lib": {
                          "utils.ts": "UTILS",
                      },
                      "types": {
                          "index.ts": "TYPES_INDEX",
                      },
                  },
                  "public": {
                      ".gitkeep": "EMPTY",
                  },
              },
              "config_files": [
                  "vite.config.ts",
                  "tailwind.config.ts",
                  "tsconfig.json",
                  "postcss.config.js",
                  ".eslintrc.json",
                  ".prettierrc",
                  ".gitignore",
                  "package.json",
                  "index.html",
              ],
          },
      }
      
      # Feature modules that can be added
      FEATURES = {
          "auth": {
              "description": "Authentication with session management",
              "files": {
                  "lib/auth.ts": "AUTH_LIB",
                  "middleware.ts": "AUTH_MIDDLEWARE",
                  "components/auth/login-form.tsx": "LOGIN_FORM",
                  "components/auth/register-form.tsx": "REGISTER_FORM",
              },
              "dependencies": ["next-auth", "@auth/core"],
          },
          "api": {
              "description": "API client with React Query",
              "files": {
                  "lib/api-client.ts": "API_CLIENT",
                  "lib/query-client.ts": "QUERY_CLIENT",
                  "providers/query-provider.tsx": "QUERY_PROVIDER",
              },
              "dependencies": ["@tanstack/react-query", "axios"],
          },
          "forms": {
              "description": "Form handling with React Hook Form + Zod",
              "files": {
                  "lib/form-utils.ts": "FORM_UTILS",
                  "components/forms/form-field.tsx": "FORM_FIELD",
              },
              "dependencies": ["react-hook-form", "@hookform/resolvers", "zod"],
          },
          "testing": {
              "description": "Testing setup with Vitest and Testing Library",
              "files": {
                  "vitest.config.ts": "VITEST_CONFIG",
                  "src/test/setup.ts": "TEST_SETUP",
                  "src/test/utils.tsx": "TEST_UTILS",
              },
              "dependencies": ["vitest", "@testing-library/react", "@testing-library/jest-dom"],
          },
          "storybook": {
              "description": "Component documentation with Storybook",
              "files": {
                  ".storybook/main.ts": "STORYBOOK_MAIN",
                  ".storybook/preview.ts": "STORYBOOK_PREVIEW",
              },
              "dependencies": ["@storybook/react-vite", "@storybook/addon-essentials"],
          },
      }
      
      # File content templates
      FILE_CONTENTS = {
          "ROOT_LAYOUT": '''import type { Metadata } from 'next';
      import { Inter } from 'next/font/google';
      import './globals.css';
      
      const inter = Inter({ subsets: ['latin'], variable: '--font-inter' });
      
      export const metadata: Metadata = {
        title: 'My App',
        description: 'Built with Next.js',
      };
      
      export default function RootLayout({
        children,
      }: {
        children: React.ReactNode;
      }) {
        return (
          <html lang="en">
            <body className={`${inter.variable} font-sans antialiased`}>
              {children}
            </body>
          </html>
        );
      }
      ''',
          "HOME_PAGE": '''export default function Home() {
        return (
          <main className="flex min-h-screen flex-col items-center justify-center p-24">
            <h1 className="text-4xl font-bold">Welcome</h1>
            <p className="mt-4 text-lg text-gray-600">
              Get started by editing app/page.tsx
            </p>
          </main>
        );
      }
      ''',
          "GLOBALS_CSS": '''@tailwind base;
      @tailwind components;
      @tailwind utilities;
      
      @layer base {
        :root {
          --background: 0 0% 100%;
          --foreground: 222.2 84% 4.9%;
          --primary: 222.2 47.4% 11.2%;
          --primary-foreground: 210 40% 98%;
          --secondary: 210 40% 96.1%;
          --secondary-foreground: 222.2 47.4% 11.2%;
          --muted: 210 40% 96.1%;
          --muted-foreground: 215.4 16.3% 46.9%;
          --accent: 210 40% 96.1%;
          --accent-foreground: 222.2 47.4% 11.2%;
          --destructive: 0 84.2% 60.2%;
          --destructive-foreground: 210 40% 98%;
          --border: 214.3 31.8% 91.4%;
          --ring: 222.2 84% 4.9%;
          --radius: 0.5rem;
        }
      
        .dark {
          --background: 222.2 84% 4.9%;
          --foreground: 210 40% 98%;
        }
      }
      
      @layer base {
        * {
          @apply border-border;
        }
        body {
          @apply bg-background text-foreground;
        }
      }
      ''',
          "UI_BUTTON": '''import { forwardRef } from 'react';
      import { cn } from '@/lib/utils';
      
      interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
        variant?: 'default' | 'destructive' | 'outline' | 'ghost';
        size?: 'default' | 'sm' | 'lg';
      }
      
      const Button = forwardRef<HTMLButtonElement, ButtonProps>(
        ({ className, variant = 'default', size = 'default', ...props }, ref) => {
          return (
            <button
              className={cn(
                'inline-flex items-center justify-center rounded-md font-medium transition-colors',
                'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
                'disabled:pointer-events-none disabled:opacity-50',
                {
                  'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
                  'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
                  'border border-input bg-background hover:bg-accent': variant === 'outline',
                  'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
                },
                {
                  'h-10 px-4 py-2': size === 'default',
                  'h-9 px-3': size === 'sm',
                  'h-11 px-8': size === 'lg',
                },
                className
              )}
              ref={ref}
              {...props}
            />
          );
        }
      );
      
      Button.displayName = 'Button';
      
      export { Button, type ButtonProps };
      ''',
          "UI_INPUT": '''import { forwardRef } from 'react';
      import { cn } from '@/lib/utils';
      
      interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
        error?: string;
      }
      
      const Input = forwardRef<HTMLInputElement, InputProps>(
        ({ className, error, ...props }, ref) => {
          return (
            <div className="w-full">
              <input
                className={cn(
                  'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2',
                  'text-sm ring-offset-background file:border-0 file:bg-transparent',
                  'file:text-sm file:font-medium placeholder:text-muted-foreground',
                  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
                  'disabled:cursor-not-allowed disabled:opacity-50',
                  error && 'border-destructive focus-visible:ring-destructive',
                  className
                )}
                ref={ref}
                {...props}
              />
              {error && <p className="mt-1 text-sm text-destructive">{error}</p>}
            </div>
          );
        }
      );
      
      Input.displayName = 'Input';
      
      export { Input, type InputProps };
      ''',
          "UI_CARD": '''import { cn } from '@/lib/utils';
      
      interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
      
      function Card({ className, ...props }: CardProps) {
        return (
          <div
            className={cn(
              'rounded-lg border bg-card text-card-foreground shadow-sm',
              className
            )}
            {...props}
          />
        );
      }
      
      function CardHeader({ className, ...props }: CardProps) {
        return <div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />;
      }
      
      function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
        return <h3 className={cn('text-2xl font-semibold leading-none', className)} {...props} />;
      }
      
      function CardContent({ className, ...props }: CardProps) {
        return <div className={cn('p-6 pt-0', className)} {...props} />;
      }
      
      function CardFooter({ className, ...props }: CardProps) {
        return <div className={cn('flex items-center p-6 pt-0', className)} {...props} />;
      }
      
      export { Card, CardHeader, CardTitle, CardContent, CardFooter };
      ''',
          "UI_INDEX": '''export { Button } from './button';
      export { Input } from './input';
      export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './card';
      ''',
          "UTILS": '''import { type ClassValue, clsx } from 'clsx';
      import { twMerge } from 'tailwind-merge';
      
      export function cn(...inputs: ClassValue[]) {
        return twMerge(clsx(inputs));
      }
      
      export function formatDate(date: Date | string): string {
        return new Intl.DateTimeFormat('en-US', {
          month: 'short',
          day: 'numeric',
          year: 'numeric',
        }).format(new Date(date));
      }
      
      export function sleep(ms: number): Promise<void> {
        return new Promise((resolve) => setTimeout(resolve, ms));
      }
      ''',
          "CONSTANTS": '''export const APP_NAME = 'My App';
      export const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api';
      
      export const ROUTES = {
        home: '/',
        login: '/login',
        register: '/register',
        dashboard: '/dashboard',
      } as const;
      
      export const QUERY_KEYS = {
        user: ['user'],
        products: ['products'],
      } as const;
      ''',
          "HOOK_DEBOUNCE": '''import { useState, useEffect } from 'react';
      
      export function useDebounce<T>(value: T, delay: number = 500): T {
        const [debouncedValue, setDebouncedValue] = useState<T>(value);
      
        useEffect(() => {
          const timer = setTimeout(() => setDebouncedValue(value), delay);
          return () => clearTimeout(timer);
        }, [value, delay]);
      
        return debouncedValue;
      }
      ''',
          "HOOK_LOCAL_STORAGE": '''import { useState, useEffect } from 'react';
      
      export function useLocalStorage<T>(
        key: string,
        initialValue: T
      ): [T, (value: T | ((prev: T) => T)) => void] {
        const [storedValue, setStoredValue] = useState<T>(() => {
          if (typeof window === 'undefined') return initialValue;
      
          try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
          } catch {
            return initialValue;
          }
        });
      
        useEffect(() => {
          if (typeof window !== 'undefined') {
            window.localStorage.setItem(key, JSON.stringify(storedValue));
          }
        }, [key, storedValue]);
      
        return [storedValue, setStoredValue];
      }
      ''',
          "TYPES_INDEX": '''export interface User {
        id: string;
        email: string;
        name: string;
        createdAt: Date;
      }
      
      export interface ApiResponse<T> {
        data: T;
        message?: string;
        error?: string;
      }
      
      export interface PaginatedResponse<T> {
        data: T[];
        total: number;
        page: number;
        pageSize: number;
        totalPages: number;
      }
      ''',
          "HEALTH_ROUTE": '''import { NextResponse } from 'next/server';
      
      export async function GET() {
        return NextResponse.json({
          status: 'ok',
          timestamp: new Date().toISOString(),
        });
      }
      ''',
          "AUTH_PAGE": ''''use client';
      
      export default function AuthPage() {
        return (
          <div className="flex min-h-screen items-center justify-center">
            <div className="w-full max-w-md p-8">
              <h1 className="text-2xl font-bold text-center">Authentication</h1>
            </div>
          </div>
        );
      }
      ''',
          "LAYOUT_HEADER": '''import Link from 'next/link';
      
      export function Header() {
        return (
          <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur">
            <div className="container flex h-14 items-center">
              <Link href="/" className="font-bold">
                Logo
              </Link>
              <nav className="ml-auto flex gap-4">
                <Link href="/about" className="text-sm text-muted-foreground hover:text-foreground">
                  About
                </Link>
              </nav>
            </div>
          </header>
        );
      }
      ''',
          "LAYOUT_FOOTER": '''export function Footer() {
        return (
          <footer className="border-t py-6">
            <div className="container text-center text-sm text-muted-foreground">
              <p>&copy; {new Date().getFullYear()} My App. All rights reserved.</p>
            </div>
          </footer>
        );
      }
      ''',
          "LAYOUT_SIDEBAR": '''interface SidebarProps {
        children?: React.ReactNode;
      }
      
      export function Sidebar({ children }: SidebarProps) {
        return (
          <aside className="fixed left-0 top-14 z-30 h-[calc(100vh-3.5rem)] w-64 border-r bg-background">
            <div className="p-4">{children}</div>
          </aside>
        );
      }
      ''',
          "REACT_APP": '''import { Button } from './components/ui';
      
      function App() {
        return (
          <main className="flex min-h-screen flex-col items-center justify-center p-24">
            <h1 className="text-4xl font-bold">Welcome</h1>
            <p className="mt-4 text-lg text-gray-600">
              Get started by editing src/App.tsx
            </p>
            <Button className="mt-6">Get Started</Button>
          </main>
        );
      }
      
      export default App;
      ''',
          "REACT_MAIN": '''import React from 'react';
      import ReactDOM from 'react-dom/client';
      import App from './App';
      import './index.css';
      
      ReactDOM.createRoot(document.getElementById('root')!).render(
        <React.StrictMode>
          <App />
        </React.StrictMode>
      );
      ''',
          "EMPTY": "",
      }
      
      
      def generate_structure(
          base_path: Path,
          structure: Dict,
          dry_run: bool = False
      ) -> List[str]:
          """Generate directory structure recursively."""
          created_files = []
      
          for name, content in structure.items():
              current_path = base_path / name
      
              if isinstance(content, dict):
                  # It's a directory
                  if not dry_run:
                      current_path.mkdir(parents=True, exist_ok=True)
                  created_files.extend(generate_structure(current_path, content, dry_run))
              else:
                  # It's a file
                  if not dry_run:
                      current_path.parent.mkdir(parents=True, exist_ok=True)
                      file_content = FILE_CONTENTS.get(content, "")
                      current_path.write_text(file_content)
                  created_files.append(str(current_path))
      
          return created_files
      
      
      def generate_config_files(
          project_path: Path,
          template: str,
          project_name: str,
          features: List[str],
          dry_run: bool = False
      ) -> List[str]:
          """Generate configuration files."""
          created_files = []
          config_templates = get_config_templates(project_name, template, features)
      
          template_config = TEMPLATES[template]
          for config_file in template_config["config_files"]:
              file_path = project_path / config_file
              if config_file in config_templates:
                  if not dry_run:
                      file_path.write_text(config_templates[config_file])
                  created_files.append(str(file_path))
      
          return created_files
      
      
      def get_config_templates(name: str, template: str, features: List[str]) -> Dict[str, str]:
          """Get configuration file contents."""
          deps = {
              "nextjs": {
                  "dependencies": {
                      "next": "^14.0.0",
                      "react": "^18.2.0",
                      "react-dom": "^18.2.0",
                      "clsx": "^2.0.0",
                      "tailwind-merge": "^2.0.0",
                  },
                  "devDependencies": {
                      "@types/node": "^20.0.0",
                      "@types/react": "^18.2.0",
                      "@types/react-dom": "^18.2.0",
                      "autoprefixer": "^10.0.0",
                      "eslint": "^8.0.0",
                      "eslint-config-next": "^14.0.0",
                      "postcss": "^8.0.0",
                      "prettier": "^3.0.0",
                      "tailwindcss": "^3.4.0",
                      "typescript": "^5.0.0",
                  },
              },
              "react": {
                  "dependencies": {
                      "react": "^18.2.0",
                      "react-dom": "^18.2.0",
                      "clsx": "^2.0.0",
                      "tailwind-merge": "^2.0.0",
                  },
                  "devDependencies": {
                      "@types/react": "^18.2.0",
                      "@types/react-dom": "^18.2.0",
                      "@vitejs/plugin-react": "^4.0.0",
                      "autoprefixer": "^10.0.0",
                      "eslint": "^8.0.0",
                      "postcss": "^8.0.0",
                      "prettier": "^3.0.0",
                      "tailwindcss": "^3.4.0",
                      "typescript": "^5.0.0",
                      "vite": "^5.0.0",
                  },
              },
          }
      
          # Add feature dependencies
          for feature in features:
              if feature in FEATURES:
                  for dep in FEATURES[feature].get("dependencies", []):
                      deps[template]["dependencies"][dep] = "latest"
      
          package_json = {
              "name": name,
              "version": "0.1.0",
              "private": True,
              "scripts": {
                  "dev": "next dev" if template == "nextjs" else "vite",
                  "build": "next build" if template == "nextjs" else "vite build",
                  "start": "next start" if template == "nextjs" else "vite preview",
                  "lint": "eslint . --ext .ts,.tsx",
                  "format": "prettier --write .",
              },
              "dependencies": deps[template]["dependencies"],
              "devDependencies": deps[template]["devDependencies"],
          }
      
          return {
              "package.json": json.dumps(package_json, indent=2),
              "tsconfig.json": '''{
        "compilerOptions": {
          "target": "ES2020",
          "lib": ["dom", "dom.iterable", "esnext"],
          "allowJs": true,
          "skipLibCheck": true,
          "strict": true,
          "noEmit": true,
          "esModuleInterop": true,
          "module": "esnext",
          "moduleResolution": "bundler",
          "resolveJsonModule": true,
          "isolatedModules": true,
          "jsx": "preserve",
          "incremental": true,
          "plugins": [{ "name": "next" }],
          "paths": {
            "@/*": ["./*"]
          }
        },
        "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
        "exclude": ["node_modules"]
      }
      ''',
              "tailwind.config.ts": '''import type { Config } from 'tailwindcss';
      
      const config: Config = {
        content: [
          './pages/**/*.{js,ts,jsx,tsx,mdx}',
          './components/**/*.{js,ts,jsx,tsx,mdx}',
          './app/**/*.{js,ts,jsx,tsx,mdx}',
          './src/**/*.{js,ts,jsx,tsx,mdx}',
        ],
        theme: {
          extend: {
            colors: {
              background: 'hsl(var(--background))',
              foreground: 'hsl(var(--foreground))',
              primary: {
                DEFAULT: 'hsl(var(--primary))',
                foreground: 'hsl(var(--primary-foreground))',
              },
              secondary: {
                DEFAULT: 'hsl(var(--secondary))',
                foreground: 'hsl(var(--secondary-foreground))',
              },
              destructive: {
                DEFAULT: 'hsl(var(--destructive))',
                foreground: 'hsl(var(--destructive-foreground))',
              },
              muted: {
                DEFAULT: 'hsl(var(--muted))',
                foreground: 'hsl(var(--muted-foreground))',
              },
              accent: {
                DEFAULT: 'hsl(var(--accent))',
                foreground: 'hsl(var(--accent-foreground))',
              },
              border: 'hsl(var(--border))',
              ring: 'hsl(var(--ring))',
            },
            borderRadius: {
              lg: 'var(--radius)',
              md: 'calc(var(--radius) - 2px)',
              sm: 'calc(var(--radius) - 4px)',
            },
          },
        },
        plugins: [],
      };
      
      export default config;
      ''',
              "postcss.config.js": '''module.exports = {
        plugins: {
          tailwindcss: {},
          autoprefixer: {},
        },
      };
      ''',
              "next.config.js": '''/** @type {import('next').NextConfig} */
      const nextConfig = {
        images: {
          remotePatterns: [],
          formats: ['image/avif', 'image/webp'],
        },
        experimental: {
          optimizePackageImports: ['lucide-react'],
        },
      };
      
      module.exports = nextConfig;
      ''',
              "vite.config.ts": '''import { defineConfig } from 'vite';
      import react from '@vitejs/plugin-react';
      import path from 'path';
      
      export default defineConfig({
        plugins: [react()],
        resolve: {
          alias: {
            '@': path.resolve(__dirname, './src'),
          },
        },
      });
      ''',
              ".eslintrc.json": '''{
        "extends": ["next/core-web-vitals", "prettier"],
        "rules": {
          "react/no-unescaped-entities": "off"
        }
      }
      ''',
              ".prettierrc": '''{
        "semi": true,
        "singleQuote": true,
        "tabWidth": 2,
        "trailingComma": "es5",
        "printWidth": 100
      }
      ''',
              ".gitignore": '''# Dependencies
      node_modules/
      .pnp
      .pnp.js
      
      # Build
      .next/
      out/
      dist/
      build/
      
      # Environment
      .env
      .env.local
      .env.*.local
      
      # IDE
      .vscode/
      .idea/
      
      # Debug
      npm-debug.log*
      yarn-debug.log*
      yarn-error.log*
      
      # OS
      .DS_Store
      Thumbs.db
      
      # Testing
      coverage/
      ''',
              "index.html": '''<!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <link rel="icon" type="image/svg+xml" href="/vite.svg" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>''' + name + '''</title>
        </head>
        <body>
          <div id="root"></div>
          <script type="module" src="/src/main.tsx"></script>
        </body>
      </html>
      ''',
          }
      
      
      def scaffold_project(
          name: str,
          output_dir: Path,
          template: str = "nextjs",
          features: Optional[List[str]] = None,
          dry_run: bool = False,
      ) -> Dict:
          """Scaffold a complete frontend project."""
          features = features or []
          project_path = output_dir / name
      
          if project_path.exists() and not dry_run:
              return {"error": f"Directory already exists: {project_path}"}
      
          template_config = TEMPLATES.get(template)
          if not template_config:
              return {"error": f"Unknown template: {template}"}
      
          created_files = []
      
          # Create project directory
          if not dry_run:
              project_path.mkdir(parents=True, exist_ok=True)
      
          # Generate base structure
          created_files.extend(
              generate_structure(project_path, template_config["structure"], dry_run)
          )
      
          # Generate config files
          created_files.extend(
              generate_config_files(project_path, template, name, features, dry_run)
          )
      
          # Add feature files
          for feature in features:
              if feature in FEATURES:
                  for file_path, content_key in FEATURES[feature]["files"].items():
                      full_path = project_path / file_path
                      if not dry_run:
                          full_path.parent.mkdir(parents=True, exist_ok=True)
                          content = FILE_CONTENTS.get(content_key, f"export const {content_key.replace('-', '_')} = () => {{\n  /* Functional implementation for {content_key} */\n}};\n")
                          full_path.write_text(content)
                      created_files.append(str(full_path))
      
          return {
              "name": name,
              "template": template,
              "template_name": template_config["name"],
              "features": features,
              "path": str(project_path),
              "files_created": len(created_files),
              "files": created_files,
              "next_steps": [
                  f"cd {name}",
                  "npm install",
                  "npm run dev",
              ],
          }
      
      
      def print_result(result: Dict) -> None:
          """Print scaffolding result."""
          if "error" in result:
              print(f"Error: {result['error']}", file=sys.stderr)
              return
      
          print(f"\n{'='*60}")
          print(f"Project Scaffolded: {result['name']}")
          print(f"{'='*60}")
          print(f"Template: {result['template_name']}")
          print(f"Location: {result['path']}")
          print(f"Files Created: {result['files_created']}")
      
          if result["features"]:
              print(f"Features: {', '.join(result['features'])}")
      
          print(f"\nNext Steps:")
          for step in result["next_steps"]:
              print(f"  $ {step}")
      
          print(f"{'='*60}\n")
      
      
      def main():
          parser = argparse.ArgumentParser(
              description="Scaffold a frontend project with best practices"
          )
          parser.add_argument(
              "name",
              help="Project name (kebab-case recommended)"
          )
          parser.add_argument(
              "--dir", "-d",
              default=".",
              help="Output directory (default: current directory)"
          )
          parser.add_argument(
              "--template", "-t",
              choices=list(TEMPLATES.keys()),
              default="nextjs",
              help="Project template (default: nextjs)"
          )
          parser.add_argument(
              "--features", "-f",
              help="Comma-separated features to add (auth,api,forms,testing,storybook)"
          )
          parser.add_argument(
              "--list-templates",
              action="store_true",
              help="List available templates"
          )
          parser.add_argument(
              "--list-features",
              action="store_true",
              help="List available features"
          )
          parser.add_argument(
              "--dry-run",
              action="store_true",
              help="Show what would be created without creating files"
          )
          parser.add_argument(
              "--json",
              action="store_true",
              help="Output in JSON format"
          )
      
          args = parser.parse_args()
      
          if args.list_templates:
              print("\nAvailable Templates:")
              for key, template in TEMPLATES.items():
                  print(f"  {key}: {template['name']}")
                  print(f"    {template['description']}")
              return
      
          if args.list_features:
              print("\nAvailable Features:")
              for key, feature in FEATURES.items():
                  print(f"  {key}: {feature['description']}")
                  deps = ", ".join(feature.get("dependencies", []))
                  if deps:
                      print(f"    Adds: {deps}")
              return
      
          features = []
          if args.features:
              features = [f.strip() for f in args.features.split(",")]
              invalid = [f for f in features if f not in FEATURES]
              if invalid:
                  print(f"Unknown features: {', '.join(invalid)}", file=sys.stderr)
                  print(f"Valid features: {', '.join(FEATURES.keys())}")
                  sys.exit(1)
      
          result = scaffold_project(
              name=args.name,
              output_dir=Path(args.dir),
              template=args.template,
              features=features,
              dry_run=args.dry_run,
          )
      
          if args.json:
              print(json.dumps(result, indent=2))
          else:
              print_result(result)
      
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 7.9 KB
    ---
    name: senior-frontend
    description: "Frontend development for React 19, Next.js 15, TypeScript, and Tailwind CSS v4 / Pengembangan frontend dengan React 19, Next.js 15, TypeScript, dan Tailwind CSS v4."
    author: "Roedy Rustam"
    version: "4.0.0"
    ---
    
    # Senior Frontend Specialist (2026 Edition)
    
    [English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
    
    ---
    
    <a name="english"></a>
    ## English
    
    ### Description
    Production-grade frontend development patterns, performance optimization, and modern ecosystem integrations for React 19 / Next.js 15 applications with Tailwind CSS v4 and TypeScript. Covers React 19 Compiler, Partial Prerendering (PPR), View Transitions API, and SPA/MPA hybrid strategies with automated self-healing testing loops.
    
    ### Orchestration & Integration
    - `design-system-architect`: For building robust UI components (Radix, Base UI, shadcn) and design tokens.
    - `tailwind-expert`: For styling and custom @theme configurations.
    - `svg-animation-motion-expert`: For advanced animations, GSAP timelines, and motion physics.
    - `nextjs-app-router-expert`: For RSC, caching, and Next.js routing patterns.
    - `state-management-expert`: For client-side state (Zustand, Jotai).
    - `tanstack-query-expert`: For advanced data fetching and server state caching.
    - `spa-orchestrator`: For Single-Page Application architectures.
    - `mpa-orchestrator`: For Multi-Page Application architectures.
    - `autonomous-tdd-debugger`: For automated unit/component test execution and stack-trace self-healing.
    
    ### Trigger Conditions
    - Scaffold a new React or Next.js 15 project with TypeScript and Tailwind CSS v4.
    - Generate new components, custom hooks, or Server Actions.
    - Analyze and optimize bundle sizes and Core Web Vitals.
    - Implement advanced React 19 patterns (`useActionState`, `useOptimistic`, `use()`).
    - Build SPAs — coordinate with `spa-orchestrator`.
    - Ensure accessibility compliance (WCAG 2.2) and testing.
    
    ### Technical Guidelines & Best Practices
    
    #### 1. Next.js 15: Server vs Client Components
    Use **Server Components** by default for performance and SEO. Use `'use client'` strictly for:
    - State (`useState`, `useEffect`), event handlers (`onClick`), browser APIs.
    - Interactive UI needing client hydration.
    
    *Next.js 15.2+ Requirements:*
    - Await `Promise`-based `params` and `searchParams` before accessing properties.
    - Use **Partial Prerendering (PPR)** natively. Wrap dynamic sections in `<Suspense>`.
    
    #### 2. React 19 Compiler & Hooks
    Do not use `useMemo`, `useCallback`, or `React.memo` unless explicitly required; rely on the React 19 compiler.
    
    **Mandatory Hooks:**
    - **`useActionState`**: Manage form state, pending indicators, and action results with Server Actions.
    - **`useOptimistic`**: Update UI instantly before server confirmation.
    - **`useFormStatus`**: Read form submission state in child components.
    - **`use(promise)`**: Unwrap promises or context inside render with Suspense.
    
    #### 3. View Transitions API
    Implement native View Transitions API for smooth page transitions. Use Next.js 15 `<Link viewTransition>` natively.
    
    #### 4. Tailwind CSS v4
    Use CSS-first configuration. Define custom tokens with `@theme` and register plugins with `@plugin` in the main CSS file. Never create `tailwind.config.js`.
    
    #### 5. Accessibility (WCAG 2.2) & Self-Healing Testing
    - Enforce semantic HTML (`<button>`, `<nav>`, `<main>`, `<article>`).
    - Enforce full keyboard navigability and valid `aria-*` labels.
    - Run typecheck (`tsc --noEmit`) to verify zero compiler diagnostics.
    - Write unit tests using **Vitest** and **React Testing Library**.
    - When tests fail, activate `autonomous-tdd-debugger` to self-heal until 100% passing.
    
    #### 6. Architecture Integration
    - **SPA (`spa-orchestrator`)**: Use **TanStack Router** for type-safe client routing and **TanStack Query v5** for server state. Never use bare `useEffect` for data fetching.
    - **MPA (`mpa-orchestrator`)**: Use **Alpine.js** or **HTMX** for micro-interactions. Apply Progressive Enhancement.
    
    #### 7. Animations & Motion
    For web animations, scroll-driven timelines, GSAP choreographies, and Framer Motion spring physics, delegate directly to `svg-animation-motion-expert`.
    
    ---
    
    <a name="bahasa-indonesia"></a>
    ## Bahasa Indonesia
    
    ### Deskripsi
    Pola pengembangan frontend tingkat produksi, optimasi performa, dan integrasi ekosistem modern untuk aplikasi React 19 / Next.js 15 dengan Tailwind CSS v4 dan TypeScript. Mencakup React 19 Compiler, Partial Prerendering (PPR), View Transitions API, dan strategi hybrid SPA/MPA dengan loop pengujian perbaikan mandiri (self-healing).
    
    ### Integrasi Orkestrasi
    - `design-system-architect`: Untuk membangun komponen UI yang kuat (Radix, Base UI, shadcn) dan design token.
    - `tailwind-expert`: Untuk styling dan konfigurasi @theme kustom.
    - `svg-animation-motion-expert`: Untuk animasi web, timeline GSAP, dan fisika spring.
    - `nextjs-app-router-expert`: Untuk RSC, caching, dan pola routing Next.js.
    - `state-management-expert`: Untuk state client-side (Zustand, Jotai).
    - `tanstack-query-expert`: Untuk fetching data lanjutan dan caching server state.
    - `spa-orchestrator`: Untuk arsitektur Single-Page Application.
    - `mpa-orchestrator`: Untuk arsitektur Multi-Page Application.
    - `autonomous-tdd-debugger`: Untuk eksekusi pengujian otomatis dan perbaikan mandiri berdasarkan stack trace terminal.
    
    ### Kondisi Pemicu
    - Buat proyek React atau Next.js 15 baru dengan TypeScript dan Tailwind CSS v4.
    - Buat komponen baru, custom hooks, atau Server Actions.
    - Analisis dan optimalkan ukuran bundle dan Core Web Vitals.
    - Terapkan pola React 19 lanjutan (`useActionState`, `useOptimistic`, `use()`).
    - Bangun SPA — koordinasikan dengan `spa-orchestrator`.
    - Pastikan kepatuhan aksesibilitas (WCAG 2.2) dan pengujian.
    
    ### Panduan Teknis & Praktik Terbaik
    
    #### 1. Next.js 15: Server vs Client Components
    Gunakan **Server Components** secara default. Gunakan `'use client'` hanya untuk:
    - State (`useState`, `useEffect`), event handler, API browser.
    - UI interaktif yang membutuhkan hidrasi klien.
    
    *Persyaratan Next.js 15.2+:*
    - Wajib `await` pada `params` dan `searchParams` yang kini bertipe `Promise`.
    - Gunakan **Partial Prerendering (PPR)** secara native. Bungkus bagian dinamis dengan `<Suspense>`.
    
    #### 2. React 19 Compiler & Hook Baru
    Jangan gunakan `useMemo`, `useCallback`, atau `React.memo` kecuali sangat diperlukan; andalkan compiler React 19.
    
    **Hook Wajib:**
    - **`useActionState`**: Kelola state form, indikator loading, dan hasil action dengan Server Actions.
    - **`useOptimistic`**: Perbarui UI secara instan sebelum server mengonfirmasi.
    - **`useFormStatus`**: Baca status form di dalam komponen anak.
    - **`use(promise)`**: Buka promise atau context langsung di dalam render dengan Suspense.
    
    #### 3. View Transitions API
    Terapkan View Transitions API bawaan browser untuk transisi halaman. Gunakan `<Link viewTransition>` pada Next.js 15.
    
    #### 4. Tailwind CSS v4
    Gunakan konfigurasi CSS-first. Definisikan token kustom dengan `@theme` dan plugin dengan `@plugin` di file CSS utama (lihat `tailwind-expert`). Jangan pernah membuat file `tailwind.config.js`.
    
    #### 5. Aksesibilitas (WCAG 2.2) & Pengujian Self-Healing
    - Wajib gunakan HTML semantik (`<button>`, `<nav>`, `<main>`).
    - Wajib pastikan navigasi keyboard dengan label `aria-*` yang valid.
    - Jalankan pemeriksaan tipe (`tsc --noEmit`) untuk memastikan nol eror diagnostik.
    - Tulis pengujian unit dengan **Vitest** dan **React Testing Library**.
    - Jika pengujian gagal, aktifkan `autonomous-tdd-debugger` untuk memulihkan kode hingga 100% lolos.
    
    #### 6. Integrasi Arsitektur
    - **SPA (`spa-orchestrator`)**: Gunakan **TanStack Router** untuk routing klien dan **TanStack Query v5** untuk server state. Jangan gunakan `useEffect` murni untuk fetching data.
    - **MPA (`mpa-orchestrator`)**: Gunakan **Alpine.js** atau **HTMX** untuk interaksi mikro. Terapkan Progressive Enhancement.
    
    #### 7. Animasi & Motion
    Untuk animasi web, timeline scroll-driven, koreografi GSAP, dan fisika spring Framer Motion, delegasikan langsung ke `svg-animation-motion-expert`.

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related