diff --git a/.agent/rules/es6.md b/.agent/rules/es6.md new file mode 100644 index 0000000..b567a10 --- /dev/null +++ b/.agent/rules/es6.md @@ -0,0 +1,127 @@ +--- +trigger: always_on +--- + +You are an expert in modern JavaScript ES6+ features and best practices. + +Key Principles: +- Use modern JavaScript syntax +- Leverage ES6+ features for cleaner code +- Understand asynchronous JavaScript +- Follow functional programming principles +- Write maintainable and performant code + +Variables and Scope: +- Use const by default, let when reassignment needed +- Avoid var completely +- Understand block scoping +- Use destructuring for objects and arrays +- Implement proper variable naming + +Arrow Functions: +- Use arrow functions for callbacks +- Understand lexical this binding +- Use implicit returns for single expressions +- Know when to use regular functions +- Use arrow functions for array methods + +Template Literals: +- Use template literals for string interpolation +- Use tagged templates for advanced formatting +- Implement multi-line strings +- Use expression interpolation +- Create reusable template functions + +Destructuring: +- Destructure objects and arrays +- Use default values in destructuring +- Rename variables while destructuring +- Use rest operator in destructuring +- Destructure function parameters + +Spread and Rest: +- Use spread operator for arrays and objects +- Use rest parameters in functions +- Clone objects and arrays with spread +- Merge objects and arrays +- Use spread for function arguments + +Async/Await: +- Use async/await for asynchronous code +- Handle errors with try/catch +- Use Promise.all for parallel operations +- Use Promise.race for timeout patterns +- Implement proper error handling +- Avoid callback hell + +Modules: +- Use ES6 import/export syntax +- Implement named and default exports +- Use dynamic imports for code splitting +- Organize code into modules +- Use barrel exports for cleaner imports + +Classes: +- Use class syntax for OOP +- Implement constructors properly +- Use getters and setters +- Implement static methods +- Use private fields (#field) +- Extend classes with inheritance + +Array Methods: +- Use map, filter, reduce for transformations +- Use find, findIndex for searching +- Use some, every for validation +- Use forEach for iteration (prefer map/filter) +- Chain array methods for complex operations +- Use flatMap for flattening and mapping + +Object Methods: +- Use Object.keys, Object.values, Object.entries +- Use Object.assign for merging +- Use Object.freeze for immutability +- Use Object.create for prototypal inheritance +- Use computed property names +- Use shorthand property syntax + +Optional Chaining: +- Use ?. for safe property access +- Use ?. for optional method calls +- Use ?. for array element access +- Combine with nullish coalescing +- Avoid excessive chaining + +Nullish Coalescing: +- Use ?? for default values +- Understand difference from || +- Use with optional chaining +- Implement proper fallbacks +- Use for configuration objects + +Promises: +- Create and consume Promises +- Chain Promises properly +- Use Promise.all for parallel execution +- Use Promise.allSettled for all results +- Implement proper error handling +- Use Promise.race for timeouts + +Iterators and Generators: +- Understand iterable protocol +- Use generators for lazy evaluation +- Implement custom iterators +- Use yield for generator functions +- Use for...of for iteration + +Best Practices: +- Use strict mode +- Avoid global variables +- Use meaningful variable names +- Implement pure functions +- Avoid mutating data +- Use const for immutability +- Handle errors properly +- Use ESLint for code quality +- Write unit tests +- Document complex logic \ No newline at end of file diff --git a/.agent/rules/styling-consistency.md b/.agent/rules/styling-consistency.md new file mode 100644 index 0000000..9290f64 --- /dev/null +++ b/.agent/rules/styling-consistency.md @@ -0,0 +1,5 @@ +--- +trigger: always_on +--- + +Use Tailwind utility classes only. Avoid custom CSS files or inline styles unless for dynamic values. \ No newline at end of file diff --git a/.agent/rules/typescript.md b/.agent/rules/typescript.md new file mode 100644 index 0000000..c67650e --- /dev/null +++ b/.agent/rules/typescript.md @@ -0,0 +1,29 @@ +--- +trigger: always_on +--- + +You are an expert in TypeScript configuration and type safety. + +Key Principles: +- Enable 'strict': true in tsconfig.json +- Avoid 'any' type at all costs +- Use 'unknown' for uncertain types +- Handle null and undefined explicitly + +Strict Mode Features: +- noImplicitAny: Forces typing of all variables +- strictNullChecks: Prevents accessing properties of null/undefined +- strictFunctionTypes: Enforces sound function parameter bivariance +- strictPropertyInitialization: Ensures class properties are initialized + +Type Safety Best Practices: +- Use type guards (typeof, instanceof, custom guards) to narrow types +- Use discriminated unions for state management +- Use 'readonly' for immutable data structures +- Use 'as const' for literal types +- Prefer Interfaces for public APIs, Types for unions/intersections + +Error Handling: +- Don't throw strings; throw Error objects +- Use Result types or Option types for functional error handling +- Handle all cases in switch statements (exhaustiveness checking) \ No newline at end of file diff --git a/.agent/skills/brainstorming/SKILL.md b/.agent/skills/brainstorming/SKILL.md new file mode 100644 index 0000000..a21f162 --- /dev/null +++ b/.agent/skills/brainstorming/SKILL.md @@ -0,0 +1,230 @@ +--- +name: brainstorming +description: > + Use this skill before any creative or constructive work + (features, components, architecture, behavior changes, or functionality). + This skill transforms vague ideas into validated designs through + disciplined, incremental reasoning and collaboration. +--- + +# Brainstorming Ideas Into Designs + +## Purpose + +Turn raw ideas into **clear, validated designs and specifications** +through structured dialogue **before any implementation begins**. + +This skill exists to prevent: +- premature implementation +- hidden assumptions +- misaligned solutions +- fragile systems + +You are **not allowed** to implement, code, or modify behavior while this skill is active. + +--- + +## Operating Mode + +You are operating as a **design facilitator and senior reviewer**, not a builder. + +- No creative implementation +- No speculative features +- No silent assumptions +- No skipping ahead + +Your job is to **slow the process down just enough to get it right**. + +--- + +## The Process + +### 1️⃣ Understand the Current Context (Mandatory First Step) + +Before asking any questions: + +- Review the current project state (if available): + - files + - documentation + - plans + - prior decisions +- Identify what already exists vs. what is proposed +- Note constraints that appear implicit but unconfirmed + +**Do not design yet.** + +--- + +### 2️⃣ Understanding the Idea (One Question at a Time) + +Your goal here is **shared clarity**, not speed. + +**Rules:** + +- Ask **one question per message** +- Prefer **multiple-choice questions** when possible +- Use open-ended questions only when necessary +- If a topic needs depth, split it into multiple questions + +Focus on understanding: + +- purpose +- target users +- constraints +- success criteria +- explicit non-goals + +--- + +### 3️⃣ Non-Functional Requirements (Mandatory) + +You MUST explicitly clarify or propose assumptions for: + +- Performance expectations +- Scale (users, data, traffic) +- Security or privacy constraints +- Reliability / availability needs +- Maintenance and ownership expectations + +If the user is unsure: + +- Propose reasonable defaults +- Clearly mark them as **assumptions** + +--- + +### 4️⃣ Understanding Lock (Hard Gate) + +Before proposing **any design**, you MUST pause and do the following: + +#### Understanding Summary +Provide a concise summary (5–7 bullets) covering: +- What is being built +- Why it exists +- Who it is for +- Key constraints +- Explicit non-goals + +#### Assumptions +List all assumptions explicitly. + +#### Open Questions +List unresolved questions, if any. + +Then ask: + +> “Does this accurately reflect your intent? +> Please confirm or correct anything before we move to design.” + +**Do NOT proceed until explicit confirmation is given.** + +--- + +### 5️⃣ Explore Design Approaches + +Once understanding is confirmed: + +- Propose **2–3 viable approaches** +- Lead with your **recommended option** +- Explain trade-offs clearly: + - complexity + - extensibility + - risk + - maintenance +- Avoid premature optimization (**YAGNI ruthlessly**) + +This is still **not** final design. + +--- + +### 6️⃣ Present the Design (Incrementally) + +When presenting the design: + +- Break it into sections of **200–300 words max** +- After each section, ask: + + > “Does this look right so far?” + +Cover, as relevant: + +- Architecture +- Components +- Data flow +- Error handling +- Edge cases +- Testing strategy + +--- + +### 7️⃣ Decision Log (Mandatory) + +Maintain a running **Decision Log** throughout the design discussion. + +For each decision: +- What was decided +- Alternatives considered +- Why this option was chosen + +This log should be preserved for documentation. + +--- + +## After the Design + +### 📄 Documentation + +Once the design is validated: + +- Write the final design to a durable, shared format (e.g. Markdown) +- Include: + - Understanding summary + - Assumptions + - Decision log + - Final design + +Persist the document according to the project’s standard workflow. + +--- + +### 🛠️ Implementation Handoff (Optional) + +Only after documentation is complete, ask: + +> “Ready to set up for implementation?” + +If yes: +- Create an explicit implementation plan +- Isolate work if the workflow supports it +- Proceed incrementally + +--- + +## Exit Criteria (Hard Stop Conditions) + +You may exit brainstorming mode **only when all of the following are true**: + +- Understanding Lock has been confirmed +- At least one design approach is explicitly accepted +- Major assumptions are documented +- Key risks are acknowledged +- Decision Log is complete + +If any criterion is unmet: +- Continue refinement +- **Do NOT proceed to implementation** + +--- + +## Key Principles (Non-Negotiable) + +- One question at a time +- Assumptions must be explicit +- Explore alternatives +- Validate incrementally +- Prefer clarity over cleverness +- Be willing to go back and clarify +- **YAGNI ruthlessly** + +--- +If the design is high-impact, high-risk, or requires elevated confidence, you MUST hand off the finalized design and Decision Log to the `multi-agent-brainstorming` skill before implementation. diff --git a/.agent/skills/frontend-developer/SKILL.md b/.agent/skills/frontend-developer/SKILL.md new file mode 100644 index 0000000..6be6725 --- /dev/null +++ b/.agent/skills/frontend-developer/SKILL.md @@ -0,0 +1,141 @@ +--- +name: frontend-dev-guidelines +description: Opinionated frontend development standards for modern Astro + React + TypeScript applications. Covers Islands Architecture, Static Site Generation (SSG), Tailwind CSS, SEO optimization, i18n, and strict TypeScript practices. +--- + + +# Frontend Development Guidelines + +**(Astro · Islands Architecture · Tailwind CSS · TypeScript · SEO-First)** + +You are a **senior frontend engineer** operating under strict architectural and performance standards. + +Your goal is to build **lightning-fast, SEO-optimized, and maintainable applications** using: + +* **Astro** for the core framework (MPA / SSG) +* **React** for interactive "Islands" only +* **Astro Routing** (`src/pages/`) and Layouts +* **Tailwind CSS + shadcn/ui** for styling +* **Mobile-first** responsive design +* **Strict i18n** (Internationalization) +* **SEO Best Practices** (Metadata, Sitemaps, Schema.org) + +This skill defines **how frontend code must be written**, not merely how it *can* be written. + +--- + +## 1. Core Architectural Doctrine (Non-Negotiable) + +### 1. Astro-First & Islands Architecture +* Use **Astro components (`.astro`)** by default for all static UI, layouts, and page structures. +* Use **React components (`.tsx`)** ONLY for interactive elements that require client-side state (hooks). +* Hydrate React components using `client:load`, `client:visible`, or `client:idle` only when necessary. +* Avoid shipping unnecessary JavaScript to the client. + +### 2. File-Based Routing & Layouts +* Routes live in `src/pages/` using `.astro` or `.md/.mdx` files. +* Use **Layouts** (`src/layouts/`) to wrap pages and manage common structure (HTML boilerplate, Analytics, Navigation). +* Common components live in `src/components/`. + +### 3. Tailwind CSS + shadcn/ui +* Use pure Tailwind CSS utility classes. +* shadcn/ui components (`src/components/ui/`) are the source of truth for all complex primitives. +* Always include `client:load` or similar directives for interactive shadcn components (e.g., Modals, Dropdowns). + +### 4. Search Engine Optimization (SEO) +* Every page must have a `` or `` component with: + - Unique `` and `<meta name="description">`. + - Open Graph (OG) tags and Twitter cards. + - Canonical URLs. +* Maintain a `sitemap-index.xml` and `robots.txt`. + +### 5. Internationalization (i18n) +* Use Astro's built-in i18n support or a dedicated integration. +* All routes should follow a locale pattern (e.g., `/en/about`, `/es/acercas-de`). +* Never hardcode strings; use translation keys. + +### 6. Mobile-First Responsiveness +* All UI must be designed and implemented mobile-first. +* Use base Tailwind classes for mobile, and breakpoint prefixes (`md:`, `lg:`) for larger screens. + +--- + +## 2. When to Use This Skill + +Use **frontend-dev-guidelines** when: + +* Creating pages or layouts in Astro. +* Building interactive React components for the site. +* Implementing i18n or SEO features. +* Styling with Tailwind CSS. +* Optimizing assets or performance. + +--- + +## 3. Quick Start Checklists + +### New Page Checklist + +* [ ] Create `src/pages/[lang]/index.astro`. +* [ ] Use a Layout component. +* [ ] Pass unique SEO metadata (title, description). +* [ ] Ensure all text uses translation helpers. +* [ ] Add interactive elements as React Islands where needed. + +--- + +## 4. Component Standards + +### Astro Component (Static) + +```astro +--- +// src/components/Header.astro +interface Props { + title: string; +} +const { title } = Astro.props; +--- +<header class="p-4 border-b"> + <h1 class="text-2xl font-bold">{title}</h1> +</header> +``` + +### React Island (Interactive) + +```tsx +// src/components/Counter.tsx +import { useState } from 'react'; + +export const Counter = () => { + const [count, setCount] = useState(0); + return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>; +}; + +// Usage in .astro: +// <Counter client:visible /> +``` + +--- + +## 5. Performance Defaults + +* Use **Astro Image** component for optimized assets. +* Prefer static generation (SSG) for all marketing pages. +* Minimize the use of `client:load` to reduce Time to Interactive (TTI). + +--- + +## 6. Anti-Patterns (Immediate Rejection) + +❌ Using React for a purely static component. +❌ Hardcoding strings (ignoring i18n). +❌ Missing meta tags or titles on pages. +❌ Over-hydrating: Using `client:load` for things below the fold. + +--- + +## 7. Skill Status + +**Status:** Stable, Astro-native +**Intended Use:** Astro + React Applications prioritizing Performance and SEO. diff --git a/.agent/skills/frontend-developer/resources/common-patterns.md b/.agent/skills/frontend-developer/resources/common-patterns.md new file mode 100644 index 0000000..3df60a4 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/common-patterns.md @@ -0,0 +1,182 @@ +# Common Patterns + +This document details required patterns for frequent frontend challenges in a Next.js (App Router) + Tailwind CSS architecture. + +--- + +## 1. The Server/Client Boundary + +The most important pattern in Next.js is understanding where the server ends and the client begins. By default, everything is a Server Component unless marked with `"use client"`. + +### Composing Server and Client Components + +When you need interactivity (a Client Component) but also want to render a Server Component inside it, pass the Server Component as `children`. This prevents the Server Component from being implicitly converted to a Client Component. + +```tsx +// src/components/ui/InteractiveWrapper.tsx +'use client'; + +import { useState } from 'react'; +import { clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function InteractiveWrapper({ children }: { children: React.ReactNode }) { + const [isOpen, setIsOpen] = useState(false); + + return ( + <div className={twMerge(clsx('border rounded-md', isOpen ? 'border-primary' : 'border-gray-200'))}> + <button onClick={() => setIsOpen(!isOpen)} className="w-full text-left p-4 bg-gray-50"> + Toggle Content + </button> + {isOpen && <div className="p-4">{children}</div>} + </div> + ); +} + +// src/app/page.tsx +import { InteractiveWrapper } from '@/components/ui/InteractiveWrapper'; +import { ServerSideDataList } from './ServerSideDataList'; + +export default function Home() { + return ( + <InteractiveWrapper> + {/* This component executes on the server and is passed over the network as rendered HTML */} + <ServerSideDataList /> + </InteractiveWrapper> + ); +} +``` + +--- + +## 2. Server Actions for Forms & Mutations + +Forms should progressively enhance using Server Actions. Use React's `useActionState` (formerly `useFormState`) and `useFormStatus` to handle pending states and displaying server validation errors on the client. + +```tsx +// src/features/users/actions/updateProfile.ts +'use server'; +import { db } from '@/lib/db'; + +export async function updateProfile(prevState: any, formData: FormData) { + const name = formData.get('name') as string; + if (name.length < 2) { + return { error: 'Name must be at least 2 characters.' }; + } + + await db.users.update({ name }); + return { success: true }; +} + +// src/features/users/components/ProfileForm.tsx +'use client'; +import { useActionState } from 'react'; +import { useFormStatus } from 'react-dom'; +import { updateProfile } from '../actions/updateProfile'; + +function SubmitButton() { + const { pending } = useFormStatus(); + return ( + <button disabled={pending} type="submit" className="mt-4 px-4 py-2 bg-black text-white rounded"> + {pending ? 'Saving...' : 'Save Profile'} + </button> + ); +} + +export function ProfileForm() { + const [state, formAction] = useActionState(updateProfile, null); + + return ( + <form action={formAction} className="flex flex-col gap-2 max-w-sm"> + <input + name="name" + className="border border-gray-300 rounded p-2 focus:ring-black focus:border-black" + defaultValue="Current Name" + /> + {state?.error && <span className="text-red-500 text-sm">{state.error}</span>} + {state?.success && <span className="text-green-500 text-sm">Saved!</span>} + <SubmitButton /> + </form> + ); +} +``` + +--- + +## 3. Class Name Merging (Tailwind CSS) + +Always use a combination of `clsx` and `tailwind-merge` when building reusable UI components. This ensures conditional classes apply cleanly and Tailwind conflicting utilities (e.g., passing `px-4` to override a default `px-2`) behave predictably. + +```tsx +// src/components/ui/Badge.tsx +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> { + variant?: 'primary' | 'secondary' | 'error'; +} + +export function Badge({ variant = 'primary', className, ...props }: BadgeProps) { + return ( + <span + className={cn( + 'inline-flex items-center px-2 py-1 rounded-full text-xs font-medium', + { + 'bg-black text-white': variant === 'primary', + 'bg-gray-100 text-gray-800': variant === 'secondary', + 'bg-red-100 text-red-800': variant === 'error', + }, + className + )} + {...props} + /> + ); +} +``` + +--- + +## 4. State Management + +1. **Global Constants:** Export from a constants file. +2. **URL Search Params:** Use `searchParams` prop in Server Components, or `useSearchParams`/`useRouter` for client state that needs to be deep-linked (e.g., active tab, search query, filters). This is the preferred method for global state in SSR apps. +3. **Local UI State:** Use `useState` or `useReducer` in Client Components. +4. **Client-side Global State (e.g., Theme, Cart):** Use `Zustand` with `persist` for localStorage integration, initialized in a Client Component. + +```tsx +// Using URL for state (Client Side Example) +'use client'; + +import { useSearchParams, useRouter, usePathname } from 'next/navigation'; + +export function FilterTabs() { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const activeTab = searchParams.get('tab') || 'all'; + + const setTab = (tab: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set('tab', tab); + router.push(`${pathname}?${params.toString()}`); // Triggers server re-render + }; + + return ( + <div className="flex gap-2 mb-4"> + {['all', 'active', 'archived'].map(tab => ( + <button + key={tab} + onClick={() => setTab(tab)} + className={cn('px-3 py-1 rounded-md text-sm', activeTab === tab ? 'bg-black text-white' : 'bg-gray-100')} + > + {tab} + </button> + ))} + </div> + ); +} +``` \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/complete-examples.md b/.agent/skills/frontend-developer/resources/complete-examples.md new file mode 100644 index 0000000..9fe63b3 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/complete-examples.md @@ -0,0 +1,189 @@ +# Complete Example: Product Management Feature + +This example demonstrates a complete feature implementation using Next.js App Router, React Server Components, Server Actions, and Tailwind CSS. + +--- + +## 1. Directory Structure + +``` +src/ + app/ + products/ + [id]/ + page.tsx + features/ + products/ + actions/ + updateProduct.ts + components/ + ProductEditForm.tsx + types/ + product.ts +``` + +--- + +## 2. Global Type Definition + +```tsx +// src/types/product.ts +export interface Product { + id: string; + name: string; + price: number; + description: string; + updatedAt: string; +} +``` + +--- + +## 3. Server Action (Mutation) + +```tsx +// src/features/products/actions/updateProduct.ts +'use server'; + +import { revalidatePath } from 'next/cache'; +import { db } from '@/lib/db'; +import type { ActionState } from '@/types/action-state'; + +export async function updateProduct( + prevState: ActionState | null, + formData: FormData +): Promise<ActionState> { + const id = formData.get('id') as string; + const name = formData.get('name') as string; + const price = Number(formData.get('price')); + + if (!name || name.length < 3) { + return { error: 'Name must be at least 3 characters long.' }; + } + + try { + await db.product.update({ + where: { id }, + data: { name, price }, + }); + + revalidatePath(`/products/${id}`); + return { success: true }; + } catch (e) { + return { error: 'Failed to update product.' }; + } +} +``` + +--- + +## 4. Client Component (Interactive Form) + +```tsx +// src/features/products/components/ProductEditForm.tsx +'use client'; + +import { useActionState } from 'react'; +import { updateProduct } from '../actions/updateProduct'; +import type { Product } from '~types/product'; +import { cn } from '@/lib/utils'; + +export function ProductEditForm({ product }: { product: Product }) { + const [state, formAction, isPending] = useActionState(updateProduct, null); + + return ( + <form action={formAction} className="space-y-6 max-w-xl bg-white p-8 rounded-xl shadow-sm border border-gray-100"> + <input type="hidden" name="id" value={product.id} /> + + <div> + <label className="block text-xs font-bold uppercase tracking-widest text-gray-500 mb-2">Product Name</label> + <input + name="name" + defaultValue={product.name} + className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-black focus:border-transparent transition-all outline-none" + /> + </div> + + <div> + <label className="block text-xs font-bold uppercase tracking-widest text-gray-500 mb-2">Price (USD)</label> + <input + name="price" + type="number" + defaultValue={product.price} + className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-black focus:border-transparent transition-all outline-none" + /> + </div> + + {state?.error && ( + <div className="p-4 bg-red-50 text-red-600 rounded-lg text-sm border border-red-100"> + {state.error} + </div> + )} + + <button + type="submit" + disabled={isPending} + className={cn( + "w-full py-4 rounded-lg font-bold uppercase tracking-widest transition-all", + isPending ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-black text-white hover:bg-gray-900 active:scale-[0.98]" + )} + > + {isPending ? 'Updating...' : 'Update Product'} + </button> + </form> + ); +} +``` + +--- + +## 5. Page Component (Server Side Composition) + +```tsx +// src/app/products/[id]/page.tsx +import { db } from '@/lib/db'; +import { notFound } from 'next/navigation'; +import { ProductEditForm } from '@/features/products/components/ProductEditForm'; +import type { Product } from '~types/product'; + +interface PageProps { + params: { id: string }; +} + +export default async function Page({ params }: PageProps) { + // Fetch data directly on server + const product: Product | null = await db.product.findUnique({ + where: { id: params.id } + }); + + if (!product) { + notFound(); + } + + return ( + <main className="min-h-screen bg-gray-50/50 py-12 px-6"> + <div className="max-w-7xl mx-auto"> + <header className="mb-12"> + <h1 className="text-4xl font-extrabold tracking-tight text-gray-900 mb-2"> + Edit Product + </h1> + <p className="text-gray-500">ID: {product.id}</p> + </header> + + <section className="grid grid-cols-1 lg:grid-cols-3 gap-12"> + <div className="lg:col-span-2"> + <ProductEditForm product={product} /> + </div> + + <aside className="space-y-6"> + <div className="p-6 bg-black text-white rounded-xl"> + <h3 className="text-xs font-bold uppercase tracking-widest opacity-60 mb-4">Last Updated</h3> + <p className="text-lg font-mono">{new Date(product.updatedAt).toLocaleString()}</p> + </div> + </aside> + </section> + </div> + </main> + ); +} +``` \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/component-patterns.md b/.agent/skills/frontend-developer/resources/component-patterns.md new file mode 100644 index 0000000..c75e01b --- /dev/null +++ b/.agent/skills/frontend-developer/resources/component-patterns.md @@ -0,0 +1,143 @@ +# Component Patterns + +This document establishes the structural rules for all React components in the Next.js frontend architecture. + +--- + +## 1. File Structure & Colocation + +### Single File Principle +* Components under 150 lines should keep their types, sub-components, and styling helpers in the same file. +* Use **shadcn/ui** components located in `@/components/ui/` as your building blocks. +* If a component grows too large, extract sub-components into sibling files within the same domain directory. + +### Types Colocation +* Feature-specific types live in `@/features/{feature}/types/`. +* Global types live in `~types/`. +* Component prop types are defined directly above the component export. + +--- + +## 2. Server Components (Default) + +Server Components are the default in Next.js. They run on the server, fetch data directly, and ship zero JavaScript to the client. + +### Pattern: Async Server Component +* Use `async/await` directly in the component signature. +* Do not use hooks (`useState`, `useEffect`, `useContext`) in Server Components. + +```tsx +import { db } from '@/lib/db'; +import type { Product } from '~types/product'; +import { ProductCard } from './ProductCard'; + +interface ProductListProps { + categoryId: string; +} + +export default async function ProductList({ categoryId }: ProductListProps) { + const products: Product[] = await db.products.findMany({ where: { categoryId } }); + + return ( + <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> + {products.map(product => ( + <ProductCard key={product.id} product={product} /> + ))} + </div> + ); +} +``` + +--- + +## 3. Client Components (`"use client"`) + +Only use `"use client"` when you need browser APIs, interactivity (event listeners), or React state/effects. + +### Pattern: The Interactivity Leaf +Push `"use client"` down the component tree as far as possible. Instead of making an entire page a Client Component just for one togglable dropdown, isolate the dropdown. + +### Component Structure Order (Client Components) +1. Types / Props +2. Hooks & State +3. Derived values (`useMemo`, simple variables) +4. Handlers +5. Render return + +```tsx +'use client'; + +import { useState } from 'react'; +import { clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +interface ExpandableTextProps { + children: React.ReactNode; + maxLines?: number; + className?: string; +} + +export function ExpandableText({ children, maxLines = 3, className }: ExpandableTextProps) { + const [expanded, setExpanded] = useState(false); + + return ( + <div className={twMerge(clsx('text-gray-800', className))}> + <div className={clsx(!expanded && `line-clamp-${maxLines}`)}> + {children} + </div> + <button + onClick={() => setExpanded(!expanded)} + className="text-black font-semibold uppercase text-xs mt-2 hover:underline" + > + {expanded ? 'Read Less' : 'Read More'} + </button> + </div> + ); +} +``` + +--- + +## 4. The `children` Prop (Composition) + +Use the `children` prop heavily. This pattern is crucial in App Router to pass unwrappable Server Components into Client Component shells, bypassing serialization errors. + +```tsx +// 🟢 Good: Server Component passes children to Client Shell +// /app/layout.tsx +import { ClientSidebarMenu } from '@/components/ClientSidebarMenu'; +import { ServerSideNavLinks } from '@/components/ServerSideNavLinks'; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + <ClientSidebarMenu> + <ServerSideNavLinks /> {/* Evaluated on server, passed to client shell */} + </ClientSidebarMenu> + ); +} +``` + +--- + +## 5. Prop Types + +* Always type props explicitly with an `interface` named `{ComponentName}Props`. +* Prefer `React.ReactNode` for `children` types. +* Avoid `React.FC` or `React.FunctionComponent`; define the component as a standard function (it handles generics better and allows `async` types cleanly in Next.js). + +```tsx +// 🟢 Good +interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { + variant?: 'primary' | 'secondary'; + isLoading?: boolean; +} + +export function Button({ variant = 'primary', isLoading, children, className, ...props }: ButtonProps) { + return <button className={/* ... */} {...props}>{children}</button>; +} + +// ❌ Bad (React.FC hides implicit typing and conflicts with async Server Components) +export const Button: React.FC<ButtonProps> = ({ children }) => { + return <button>{children}</button>; +}; +``` \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/data-fetching.md b/.agent/skills/frontend-developer/resources/data-fetching.md new file mode 100644 index 0000000..0e6de02 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/data-fetching.md @@ -0,0 +1,218 @@ +# Data Fetching & Mutations (Next.js) + +The frontend architecture relies on **React Server Components (RSC)** for initial data fetching and **Server Actions** for mutations. Client-side fetching is reserved for specific use cases like highly interactive grids, polling, or infinite scrolling. + +--- + +## 1. Fetching Data (Server Components) + +By default, fetch data directly in your React Server Components. This approach eliminates client-side network waterfalls and ships less JavaScript to the browser. + +### The Standard Pattern + +```tsx +import { db } from '@/lib/db'; +import type { Product } from '~types/product'; +import { notFound } from 'next/navigation'; + +interface ProductPageProps { + params: { id: string }; +} + +export default async function ProductPage({ params }: ProductPageProps) { + // 1. Fetch data directly on the server + const product: Product | null = await db.products.findById(params.id); + + // 2. Handle 404s cleanly + if (!product) { + notFound(); + } + + // 3. Render directly with the data + return ( + <article className="max-w-3xl mx-auto py-8"> + <h1 className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600"> + {product.name} + </h1> + <p className="mt-4 text-gray-600">{product.description}</p> + <span className="inline-block mt-4 px-3 py-1 bg-black text-white rounded-full text-sm font-medium"> + ${product.price} + </span> + </article> + ); +} +``` + +### Using Next.js Fetch Wrapper (Caching & Revalidation) + +If you are fetching from external APIs, use the extended `fetch` provided by Next.js to control caching and revalidation. + +```tsx +export async function getPosts() { + const res = await fetch('https://api.example.com/posts', { + next: { tags: ['posts'], revalidate: 3600 }, // Cache for 1 hour, or until revalidated by tag + }); + + if (!res.ok) { + throw new Error('Failed to fetch posts'); + } + + return res.json(); +} +``` + +--- + +## 2. Mutations (Server Actions) + +Mutations (creates, updates, deletes) are handled via **Server Actions**. These are asynchronous functions executed on the server that can be called directly from Client Components or passed to forms in Server Components. + +### Defining a Server Action + +Server actions should usually be defined in a dedicated file (e.g., `src/features/products/actions/createProduct.ts`) to avoid mixing client/server boundaries inappropriately. + +```tsx +// src/features/products/actions/createProduct.ts +'use server'; + +import { revalidatePath } from 'next/cache'; +import { redirect } from 'next/navigation'; +import { db } from '@/lib/db'; + +export async function createProduct(formData: FormData) { + // 1. Extract data + const name = formData.get('name') as string; + const price = Number(formData.get('price')); + + // 2. Validate data + if (!name || isNaN(price)) { + return { error: 'Invalid product data' }; + } + + // 3. Perform the mutation + await db.products.insert({ name, price }); + + // 4. Revalidate cache and redirect + revalidatePath('/products'); + redirect('/products'); +} +``` + +### Using a Server Action in a Form (Server Component) + +Server Components can pass actions directly to the `action` prop of a form. This works even with JavaScript disabled. + +```tsx +// src/app/products/new/page.tsx +import { createProduct } from '@/features/products/actions/createProduct'; + +export default function NewProductPage() { + return ( + <form action={createProduct} className="space-y-4 max-w-md"> + <div> + <label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label> + <input type="text" id="name" name="name" className="mt-1 block w-full rounded-md border-gray-300 shadow-sm" required /> + </div> + <div> + <label htmlFor="price" className="block text-sm font-medium text-gray-700">Price</label> + <input type="number" id="price" name="price" className="mt-1 block w-full rounded-md border-gray-300 shadow-sm" required /> + </div> + <button type="submit" className="px-4 py-2 bg-black text-white rounded-md hover:bg-gray-800 transition-colors"> + Create Product + </button> + </form> + ); +} +``` + +### Using `useActionState` and `useFormStatus` (Client Components) + +To provide pending states and inline error messages, you must use Client Components with the `useActionState` and `useFormStatus` hooks. + +```tsx +// src/features/products/components/ProductForm.tsx +'use client'; + +import { useActionState } from 'react'; +import { useFormStatus } from 'react-dom'; +import { createProduct } from '../actions/createProduct'; +import { clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +function SubmitButton() { + const { pending } = useFormStatus(); + + return ( + <button + type="submit" + disabled={pending} + className={twMerge( + clsx( + 'px-4 py-2 rounded-md transition-colors', + pending ? 'bg-gray-400 cursor-not-allowed text-white' : 'bg-black hover:bg-gray-800 text-white' + ) + )} + > + {pending ? 'Submitting...' : 'Create Product'} + </button> + ); +} + +export function ProductForm() { + // useActionState connects form state (errors, success messages) to the server action + const [state, formAction] = useActionState(createProduct, null); + + return ( + <form action={formAction} className="space-y-4 max-w-md"> + {/* ... inputs ... */} + + {state?.error && ( + <p className="text-red-500 text-sm mt-2">{state.error}</p> + )} + + <SubmitButton /> + </form> + ); +} +``` + +--- + +## 3. Client-Side Fetching (React Query / SWR) + +While RSC is the default, client-side fetching is acceptable and necessary for: +1. Infinite scrolling feeds. +2. Data that updates frequently and needs polling. +3. Complex interactive client-side tables/grids. + +To do this, you can initialize React Query in a Client Provider and use hooks like `useQuery` or `useInfiniteQuery`. However, always try to hydrate initial data from the Server Component to avoid empty loading spinners on the first paint. + +```tsx +// src/features/dashboard/components/LiveMetrics.tsx +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { fetchMetrics } from '../api/clientApi'; + +export function LiveMetrics({ initialData }: { initialData: any }) { + const { data } = useQuery({ + queryKey: ['metrics'], + queryFn: fetchMetrics, + initialData, // Prevents loading state on initial render + refetchInterval: 5000, // Poll every 5s + }); + + return ( + <div className="p-4 bg-white rounded-lg shadow"> + Active Users: {data.activeUsers} + </div> + ); +} +``` + +--- + +## 4. Banned Patterns + +❌ **`useEffect` + `fetch` on Mount:** Never use `useEffect` to load the initial dataset for a page. Use Server Components. +❌ **Route Handlers (`/api/...`) for Internal Data:** If your frontend and backend are in the same Next.js app, do not `fetch('/api/...')` from a Client Component to your own Next.js API routes just to load data. Use Server Actions or Direct DB calls in RSC instead. Route Handlers are meant primarily for external webhook consumers or integrations. \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/file-organization.md b/.agent/skills/frontend-developer/resources/file-organization.md new file mode 100644 index 0000000..4933497 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/file-organization.md @@ -0,0 +1,108 @@ +# File Organization (Next.js) + +This project follows a **Feature-First** architecture alongside the Next.js **App Router** (`app/` directory). This ensures that domain logic remains modular while leveraging the filesystem-based routing of Next.js. + +--- + +## 1. Directory Overview + +``` +src/ + app/ <-- 1. Routing & Composition Layer + features/ <-- 2. Domain Logic Layer + components/ <-- 3. Shared Primitive Layer + types/ <-- 4. Global Type Definition Layer + lib/ <-- 5. Infrastructure & Utilities + hooks/ <-- 6. Shared React Hooks +``` + +--- + +## 2. The `app/` Directory (Routing & Composition) + +The `app/` directory is responsible ONLY for routing, layouts, and composing features and shared components. + +* Keep page files small. +* Prefer using **Route Groups** `(group-name)` for logical organization without URL impact. +* Colocate route-specific components only if they are entirely unique to that page. + +``` +src/app/ + (auth)/ <-- Auth route group + login/page.tsx + register/page.tsx + dashboard/ + layout.tsx + page.tsx + loading.tsx + error.tsx +``` + +--- + +## 3. The `features/` Directory (Domain Logic) + +A feature is a self-contained module representing a business domain (e.g., `products`, `orders`, `users`). + +**Structure of a feature:** +``` +src/features/products/ + actions/ <-- Server Actions (mutations) + components/ <-- Feature-specific UI + helpers/ <-- Non-React utility functions + index.ts <-- Public API for the feature (Optional) +``` + +**Crucial Note:** Features should avoid cross-importing from other features' internal subdirectories. If a component is needed by multiple features, promote it to `src/components/`. + +--- + +## 4. The `types/` Directory (Global Types) + +All foundational TypeScript interfaces and types live in the root `src/types/` directory. + +* **Alias:** Always use `~types/...` to import these. +* **Naming:** Use PascalCase for filenames and exported types. + +``` +src/types/ + user.ts + order.ts + api-response.ts +``` + +--- + +## 5. The `components/` Directory (Shared Primitives) + +Base UI components (buttons, cards, inputs) that do not belong to a specific feature. + +* Use a subfolder structure like `ui/`, `layout/`, or `skeletons/`. +* These components must be highly reusable and agnostic of domain data. + +``` +src/components/ + ui/ + Button.tsx + Input.tsx + Badge.tsx +``` + +--- + +## 6. Import Aliases Summary + +| Alias | Path | Level | +| ------------- | ---------------- | ------------------------ | +| `@/` | `src/` | Project Root Alias | +| `~types` | `src/types` | Global Types Alias | +| `@/components/ui` | `src/components/ui` | shadcn-ui components | + +--- + +## 7. Organization Rules + +1. **Flat is better than nested:** Avoid going more than 3 directory levels deep within a feature. +2. **Feature Isolation:** If you find yourself importing `{ X } from '@/features/a/components/x'` into `@/features/b/components/y'`, consider moving `X` to `@/components/`. +3. **Public Exports:** Use `index.ts` in features to control what is exposed to the rest of the application. +4. **Colocation:** Keep `loading.tsx` and `error.tsx` right next to the `page.tsx` they protect. \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/loading-and-error-states.md b/.agent/skills/frontend-developer/resources/loading-and-error-states.md new file mode 100644 index 0000000..abcbc05 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/loading-and-error-states.md @@ -0,0 +1,150 @@ +# Loading & Error States (Next.js) + +In the App Router architecture, loading and error states are handled by specific file-level boundaries (`loading.tsx` and `error.tsx`). This allows for **Streaming SSR**, where the layout and shell are sent immediately, and slow data-fetching regions fill in later. + +--- + +## 1. Loading States (`loading.tsx`) + +A `loading.tsx` file creates a **Suspense Boundary** around the page segment. It is automatically rendered when a route segment is first loaded or when its data is being fetched on the server. + +### The Skeleton Pattern +* Use `loading.tsx` for page-level transitions. +* Use Tailwind with `animate-pulse` or specific Skeleton components for a premium feel. + +```tsx +// src/app/dashboard/loading.tsx +export default function DashboardLoading() { + return ( + <div className="p-8"> + <div className="h-8 w-64 bg-gray-200 animate-pulse rounded-md" /> + <div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-6"> + {[1, 2, 3].map(i => ( + <div key={i} className="h-48 bg-gray-100 animate-pulse rounded-lg border border-gray-200" /> + ))} + </div> + </div> + ); +} +``` + +### Granular Loading (Manual Suspense) +If only a small part of a page is slow (e.g., an activity feed), wrap just that component in `<Suspense>`. + +```tsx +import { Suspense } from 'react'; +import { SlowActivityFeed } from '@/features/dashboard/components/SlowActivityFeed'; +import { FeedSkeleton } from '@/components/skeletons/FeedSkeleton'; + +export default function DashboardPage() { + return ( + <div> + <h1>Dashboard Overview</h1> + {/* Rapidly rendered UI */} + + <Suspense fallback={<FeedSkeleton />}> + {/* Only this part will stream in when ready */} + <SlowActivityFeed /> + </Suspense> + </div> + ); +} +``` + +--- + +## 2. Error States (`error.tsx`) + +The `error.tsx` file acts as a **React Error Boundary**. It captures errors in the specific segment and its children, preventing a single error from crashing the entire application. + +### The Segment Error Boundary +* Must be a **Client Component**. +* Receives `error` and a `reset` function as props. + +```tsx +// src/app/dashboard/error.tsx +'use client'; + +import { useEffect } from 'react'; + +export default function DashboardError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Log error to an observability service (e.g. Sentry) + console.error(error); + }, [error]); + + return ( + <div className="flex flex-col items-center justify-center p-12 text-center border border-red-100 rounded-lg bg-red-50"> + <h2 className="text-xl font-bold text-red-900">Something went wrong!</h2> + <p className="mt-2 text-red-600 font-mono text-sm max-w-lg">{error.message}</p> + <button + onClick={() => reset()} // Attempts to re-render the segment + className="mt-6 px-4 py-2 bg-red-900 text-white rounded-md hover:bg-red-800 transition" + > + Try Again + </button> + </div> + ); +} +``` + +--- + +## 3. Empty States + +Return specific "Empty" UI components when a data set is empty. Never return `null` if the user expects feedback. + +```tsx +export function EmptyOrderList() { + return ( + <div className="flex flex-col items-center py-20 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200"> + <span className="text-4xl">📦</span> + <h3 className="mt-4 text-lg font-semibold text-gray-900">No Orders Found</h3> + <p className="mt-1 text-gray-500">When you place an order, it will appear here.</p> + </div> + ); +} +``` + +--- + +## 4. Mutation Feedback (Toasts) + +For mutations (Server Actions), never rely on page-level loaders for small updates. Use a toast/snack system initiated from the Client Component level. + +* Use `useActionState` to track success/failure. +* Use a minimalist toast library or custom Tailwind component. + +```tsx +'use client'; + +import { createProduct } from '../actions/createProduct'; +import { useActionState, useEffect } from 'react'; +import { toast } from '@/lib/toast'; // Your chosen minimalist toast implementation + +export function CreateProductForm() { + const [state, formAction] = useActionState(createProduct, null); + + useEffect(() => { + if (state?.success) toast.success('Product created successfully'); + if (state?.error) toast.error(state.error); + }, [state]); + + return <form action={formAction}>...</form>; +} +``` + +--- + +## 5. Summary Rules + +1. **Streaming is Mandatory:** Always provide a `loading.tsx` for top-level app routes to prevent blocking the browser during data fetching. +2. **Graceful Failures:** Every major segment (dashboard, accounts, products) must have an `error.tsx`. +3. **Skeleton Fidelity:** Skeleton UI should roughly match the layout of the loaded component to prevent layout shifts (CLS). +4. **Retry Pattern:** Error boundaries must always provide a "Retry" or "Refresh" action. \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/performance.md b/.agent/skills/frontend-developer/resources/performance.md new file mode 100644 index 0000000..5500488 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/performance.md @@ -0,0 +1,129 @@ +# Performance Guidelines (Next.js) + +Next.js provides powerful performance optimizations out of the box. Our architecture leverages these to ensure high Lighthouse scores and zero Cumulative Layout Shift (CLS). + +--- + +## 1. Image Optimization (`next/image`) + +Always use the `Image` component from `next/image`. This automatically handles resizing, optimization (WebP/AVIF), and lazy loading. + +### Rules of Engagement +* Provide `width` and `height` to prevent CLS. +* For background images or images whose dimensions are unknown, use the `fill` prop combined with a container having `position: relative`. +* Use the `priority` prop for the Largest Contentful Paint (LCP) image (e.g., Hero image). + +```tsx +import Image from 'next/image'; + +export function ProductHero({ src, alt }: { src: string; alt: string }) { + return ( + <div className="relative aspect-square w-full"> + <Image + src={src} + alt={alt} + fill + priority + className="object-cover rounded-lg" + sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" + /> + </div> + ); +} +``` + +--- + +## 2. Link Optimization (`next/link`) + +Always use `Link` from `next/link`. + +* **Prefetching:** Next.js automatically prefetches links in the viewport, making navigation feel instantaneous. +* **Scroll Restoration:** Automatic scroll position restoration. + +```tsx +import Link from 'next/link'; + +export function Nav() { + return <Link href="/search" className="hover:underline">Search</Link>; +} +``` + +--- + +## 3. Font Optimization (`next/font`) + +Use `next/font/google` or `next/font/local`. This downloads the font at build time and hosts it with your assets, eliminating external network requests and layout shifts. + +```tsx +// src/app/layout.tsx +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'], display: 'swap' }); + +export default function RootLayout({ children }) { + return ( + <html lang="en" className={inter.className}> + <body>{children}</body> + </html> + ); +} +``` + +--- + +## 4. Code Splitting & Dynamic Imports + +Next.js automatically splits code at the route level. For client-side components that are heavy or conditionally rendered, use `next/dynamic`. + +```tsx +import dynamic from 'next/dynamic'; + +const ExpensiveChart = dynamic(() => import('@/components/charts/ExpensiveChart'), { + loading: () => <p>Loading Chart...</p>, + ssr: false, // Prevents server-side rendering for browser-only libraries +}); + +export function Dashboard() { + return <ExpensiveChart />; +} +``` + +--- + +## 5. Caching Strategies + +Leverage the Next.js Data Cache. + +* **Full Route Caching:** Static routes are cached by default. +* **Data Cache:** `fetch()` calls are cached across requests by default. +* **Revalidation:** Use `revalidatePath` or `revalidateTag` in Server Actions to purge specific cached data when it changes. + +```tsx +// Revalidating after a mutation +'use server'; +import { revalidatePath } from 'next/cache'; + +export async function addComment() { + // ... db logic ... + revalidatePath('/posts/[slug]'); +} +``` + +--- + +## 6. Avoiding Hydration Mismatch + +Hydration mismatches occur when the server-rendered HTML doesn't match the first client-side render (e.g., using `new Date()` or `Math.random()` directly in a component). + +* **Fix:** Use `useEffect` to trigger client-only rendering or use the `suppressHydrationWarning` prop on elements like `time`. + +--- + +## 7. Performance Checklist + +* [ ] All images use `next/image`. +* [ ] All fonts use `next/font`. +* [ ] No CLS: Containers have defined aspect ratios or heights. +* [ ] Bundle Size: Avoid importing heavy libraries (like `lodash`) directly; use specific sub-module imports or `next/dynamic`. +* [ ] Cache tags: Meaningful tags used for data fetching to allow precise revalidation. \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/routing-guide.md b/.agent/skills/frontend-developer/resources/routing-guide.md new file mode 100644 index 0000000..22c8890 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/routing-guide.md @@ -0,0 +1,153 @@ +# Routing Guide (Next.js App Router) + +This codebase uses the **Next.js App Router** (`app/` directory) exclusively. All routing is folder-based, and components inside the `app/` directory are React Server Components by default. + +--- + +## 1. Directory Structure + +The `src/app/` directory defines your routes. + +* Each folder represents a route segment. +* Special files (`page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`) define the UI for that segment. +* You can use Route Groups `(folderName)` to organize routes without affecting the URL path. + +``` +src/ + app/ + (marketing)/ <-- Route Group (does not affect URL) + page.tsx <-- `/` route + layout.tsx <-- Shared layout for marketing pages + about/ + page.tsx <-- `/about` route + (app)/ + layout.tsx <-- Shared layout for app/dashboard + dashboard/ + page.tsx <-- `/dashboard` route + loading.tsx <-- Loading UI for dashboard + error.tsx <-- Error UI for dashboard + categories/ + [slug]/ <-- Dynamic Segment + page.tsx <-- `/categories/electronics` route +``` + +--- + +## 2. Dynamic Segments + +Use square brackets `[paramName]` to create dynamic routes. + +```tsx +// src/app/categories/[slug]/page.tsx +interface CategoryPageProps { + params: { slug: string }; +} + +export default async function CategoryPage({ params }: CategoryPageProps) { + const categoryId = params.slug; + // Fetch data based on the categoryId... + return <div>Category: {categoryId}</div>; +} +``` + +--- + +## 3. Search Parameters + +Next.js provides access to URL search parameters directly in Server Components via the `searchParams` prop. + +```tsx +// src/app/products/page.tsx +interface ProductsPageProps { + searchParams: { q?: string; page?: string }; +} + +export default async function ProductsPage({ searchParams }: ProductsPageProps) { + const searchQuery = searchParams.q ?? ''; + const currentPage = Number(searchParams.page) || 1; + + // Fetch products based on searchQuery and currentPage + return <div>Search results for: {searchQuery}</div>; +} +``` + +**Client-Side Note:** If you need to access search params in a Client Component, use the `useSearchParams()` hook from `next/navigation`. + +--- + +## 4. Navigation + +Always use the `Link` component from `next/link` for internal navigation to ensure prefetched routes and smooth client-side transitions. + +```tsx +import Link from 'next/link'; + +export function Navigation() { + return ( + <nav> + <Link href="/dashboard" className="text-blue-500 hover:text-blue-700 transition"> + Go to Dashboard + </Link> + </nav> + ); +} +``` + +For programmatic navigation (e.g., redirecting after a form submission), use the `useRouter()` hook from `next/navigation` in Client Components, or `redirect()` from `next/navigation` in Server Components / Server Actions. + +```tsx +// Client side programmatic navigation +'use client'; +import { useRouter } from 'next/navigation'; + +export function LoginRedirect() { + const router = useRouter(); + + const handleSuccess = () => { + router.push('/dashboard'); + }; + // ... +} +``` + +```tsx +// Server side redirect +'use server'; +import { redirect } from 'next/navigation'; + +export async function submitForm() { + // process form... + redirect('/dashboard'); +} +``` + +--- + +## 5. Parallel & Intercepted Routes + +For advanced UI patterns (like modals, or side-by-side split screens), rely on Next.js constructs: + +* **Parallel Routes (`@folder`)**: Allows you to simultaneously or conditionally render one or more pages in the same layout. +* **Intercepted Routes (`(..)folder`)**: Allows you to load a route from another part of your application within the current layout (e.g., opening a photo in a modal without losing the context of the feed). + +--- + +## 6. Architecture Boundary: Features vs App + +The `src/app/` directory should remain relatively thin. It acts as the composer of your application. Complex domain logic, reusable UI blocks, and API calls should live in `src/features/` or `src/components/`, and be imported into your route files. + +```tsx +// src/app/dashboard/page.tsx +// 🟢 Good: App router composes feature components +import { DashboardMetrics } from '@/features/dashboard/components/DashboardMetrics'; +import { RecentActivity } from '@/features/dashboard/components/RecentActivity'; + +export default function DashboardPage() { + return ( + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <DashboardMetrics /> + <RecentActivity /> + </div> + ); +} +``` \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/styling-guide.md b/.agent/skills/frontend-developer/resources/styling-guide.md new file mode 100644 index 0000000..7470581 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/styling-guide.md @@ -0,0 +1,119 @@ +# Styling Guide (Tailwind CSS) + +This project strictly adheres to **Tailwind CSS** for all styling. UI component libraries like MUI, Bootstrap, or custom CSS-in-JS solutions are forbidden. + +--- + +## 1. The Principle of Utility-First + +Always favor utility classes over custom CSS. Utility classes ensure a consistent design language and keep the CSS bundle small. + +### 🟢 Do: +* Use standard Tailwind classes (`px-4`, `flex`, `text-black`, etc.). +* Use **shadcn/ui** for complex primitives (Inputs, Selects, Dialogs). +* Use the minimalist black-and-white primary palette unless specified otherwise. + +### ❌ Don't: +* Create `.css` files unless for global resets or complex third-party library overrides. +* Use the `style={...}` prop for static styles. +* Use UI library-specific styling props (e.g., MUI's `sx`). + +--- + +## 2. Conditional Styling & Composition + +For components that accept external `className` props or have internal conditional logic, use `clsx` and `tailwind-merge`. This pattern is non-negotiable for building reliable reusable components. + +```tsx +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** + * Utility for merging tailwind classes safely. + */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { + variant?: 'primary' | 'secondary'; +} + +export function Button({ variant = 'primary', className, ...props }: ButtonProps) { + return ( + <button + className={cn( + 'px-6 py-2 font-medium transition-all duration-200 rounded-md active:scale-95', + variant === 'primary' ? 'bg-black text-white hover:bg-gray-900' : 'bg-white text-black border border-black hover:bg-gray-50', + className // Allows external overrides + )} + {...props} + /> + ); +} +``` + +--- + +## 3. Dark Mode & Theming + +The primary design aesthetic is **Premium Minimalist** (Black and White). + +* **Light Mode:** White background, black/gray-900 text, gray-200 borders. +* **Dark Mode:** Black/gray-950 background, white text, gray-800 borders. + +Use the `dark:` prefix for all components to ensure they behave correctly in dark modes. + +```html +<div class="bg-white dark:bg-black text-black dark:text-white border-gray-200 dark:border-gray-800"> + ... +</div> +``` + +--- + +## 4. Typography + +Use a high-quality modern sans-serif font (Inter, Roboto, or Outfit) via `next/font`. + +* **Headings:** `text-2xl` to `text-5xl`, `font-bold` or `font-semibold`, `tracking-tight`. +* **Body:** `text-base` or `text-sm`, `leading-relaxed`. +* **Capitals:** For buttons or small UI labels, use `uppercase tracking-widest text-[10px]`. + +--- + +## 5. Layout & Spacing + +* **Mobile-First Responsiveness:** Always style starting from the smallest screen. Base classes are for mobile; use `sm:`, `md:`, `lg:` only for larger screen overrides. +* **Containers:** Use `max-w-7xl mx-auto px-4 md:px-8`. +* **Grids:** Always prefer `grid` over `flex` for page-level layouts. +* **Gaps:** Use standard spacing scale (`gap-4`, `gap-8`). + +```tsx +export function ProductGrid() { + return ( + <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-6 gap-y-10"> + {/* Product Items */} + </div> + ); +} +``` + +--- + +## 6. Micro-animations + +Leverage Tailwind's transition utilities for a premium feel. + +* **Hovers:** Use `hover:opacity-80` or `hover:-translate-y-1`. +* **Transitions:** Always add `transition-all duration-300`. +* **Focus States:** Distinct black outlines: `focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2`. + +--- + +## 7. Banned Patterns + +❌ **Inline Styles:** Except for dynamic values (e.g., progress bar width). +❌ **Relative Units (em):** Use Tailwind's default `rem` based scale. +❌ **Important (`!`):** Avoid using `!important` classes. Fix the specificity or use `tailwind-merge` instead. +❌ **Third-party UI libraries (Shadcn, Headless UI):** Only use if specifically requested by the user. Default to pure Tailwind. \ No newline at end of file diff --git a/.agent/skills/frontend-developer/resources/typescript-standards.md b/.agent/skills/frontend-developer/resources/typescript-standards.md new file mode 100644 index 0000000..ab22931 --- /dev/null +++ b/.agent/skills/frontend-developer/resources/typescript-standards.md @@ -0,0 +1,118 @@ +# TypeScript Standards (Next.js) + +Our TypeScript configuration adheres to the strictest possible standards to ensure type safety across the server-client boundary and within the Next.js App Router tree. + +--- + +## 1. Strict Mode + +The `tsconfig.json` must have `'strict': true` enabled. + +* **No `any`:** Under no circumstances should `any` be used. Use `unknown` if the type is truly uncertain, or `interface` for known structures. +* **Non-Nullable:** Handle `null` and `undefined` explicitly via optional chaining (`?.`) or nullish coalescing (`??`). + +--- + +## 2. Next.js Specific Types + +### Page & Layout Props +Always type the props for `page.tsx` and `layout.tsx`. + +```tsx +// src/app/products/[id]/page.tsx +interface ProductPageProps { + params: { id: string }; + searchParams: { [key: string]: string | string[] | undefined }; +} + +export default async function ProductPage({ params, searchParams }: ProductPageProps) { + // ... +} +``` + +### Server Actions +State within `useActionState` should be strictly typed. + +```tsx +// src/features/products/types/action-state.ts +export interface ActionState { + success?: boolean; + error?: string; + data?: any; +} +``` + +--- + +## 3. Global vs Feature Types + +### Global Types (`src/types/`) +* **Location:** Use `src/types/` for entities used by 3+ features or core system types. +* **Alias:** Use the `~types/...` alias. +* **Structure:** One file per entity (e.g., `user.ts`, `auth.ts`). + +### Feature Types (`src/features/{f}/types/`) +* **Location:** Use for types strictly coupled with one domain. +* **Alias:** Use `@/features/f/types`. + +--- + +## 4. Design Patterns + +* **Interfaces vs Types:** + * Use **Interfaces** for public APIs, props, and object definitions (they are more performant and extendable). + * Use **Types** for unions, intersections, and primitives. + +* **Discriminated Unions:** + Use for state management to ensure all possible paths are handled. + +```tsx +type LoadingState = + | { status: 'idle' } + | { status: 'loading' } + | { status: 'success'; data: any } + | { status: 'error'; message: string }; +``` + +--- + +## 5. Banned Patterns + +❌ **`as const` for Enums:** Prefer string literal unions over Enums for better readability and performance. +❌ **`React.FC`:** As noted in the Component Patterns, use standard function declarations. +❌ **Implicit returns:** Always provide explicit return types for functions, especially those exported from a file. +❌ **`any` data fetching:** Every fetch or DB call must be cast to a specific interface. + +--- + +## 6. JSDoc Documentation + +Publicly exported components and utility functions must have JSDoc blocks for better developer experience and tooling. + +```tsx +/** + * Renders a primary call-to-action button. + * @param variant - Visual style of the button. + * @param isLoading - Whether the button is in a loading state. + */ +export function Button({ variant, isLoading, ...props }: ButtonProps) { + // ... +} +``` + +--- + +## 7. Absolute Rule on Nullability + +Never use non-null assertions (`!`). If a value could be null, the code must account for that possibility with a guard or a fallback. + +```tsx +// ❌ Bad +const user = await db.getUser(id); +console.log(user!.name); + +// 🟢 Good +const user = await db.getUser(id); +if (!user) return <p>User not found</p>; +console.log(user.name); +``` \ No newline at end of file diff --git a/.agent/skills/react-best-practices/AGENTS.md b/.agent/skills/react-best-practices/AGENTS.md new file mode 100644 index 0000000..898afb3 --- /dev/null +++ b/.agent/skills/react-best-practices/AGENTS.md @@ -0,0 +1,2249 @@ +# React Best Practices + +**Version 0.1.0** +Vercel Engineering +January 2026 + +> **Note:** +> This document is mainly for agents and LLMs to follow when maintaining, +> generating, or refactoring React and Next.js codebases at Vercel. Humans +> may also find it useful, but guidance here is optimized for automation +> and consistency by AI-assisted workflows. + +--- + +## Abstract + +Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation. + +--- + +## Table of Contents + +1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL** + - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed) + - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization) + - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes) + - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations) + - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries) +2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL** + - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports) + - 2.2 [Conditional Module Loading](#22-conditional-module-loading) + - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries) + - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components) + - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent) +3. [Server-Side Performance](#3-server-side-performance) — **HIGH** + - 3.1 [Cross-Request LRU Caching](#31-cross-request-lru-caching) + - 3.2 [Minimize Serialization at RSC Boundaries](#32-minimize-serialization-at-rsc-boundaries) + - 3.3 [Parallel Data Fetching with Component Composition](#33-parallel-data-fetching-with-component-composition) + - 3.4 [Per-Request Deduplication with React.cache()](#34-per-request-deduplication-with-reactcache) + - 3.5 [Use after() for Non-Blocking Operations](#35-use-after-for-non-blocking-operations) +4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH** + - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners) + - 4.2 [Use SWR for Automatic Deduplication](#42-use-swr-for-automatic-deduplication) +5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM** + - 5.1 [Defer State Reads to Usage Point](#51-defer-state-reads-to-usage-point) + - 5.2 [Extract to Memoized Components](#52-extract-to-memoized-components) + - 5.3 [Narrow Effect Dependencies](#53-narrow-effect-dependencies) + - 5.4 [Subscribe to Derived State](#54-subscribe-to-derived-state) + - 5.5 [Use Functional setState Updates](#55-use-functional-setstate-updates) + - 5.6 [Use Lazy State Initialization](#56-use-lazy-state-initialization) + - 5.7 [Use Transitions for Non-Urgent Updates](#57-use-transitions-for-non-urgent-updates) +6. [Rendering Performance](#6-rendering-performance) — **MEDIUM** + - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element) + - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists) + - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements) + - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision) + - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering) + - 6.6 [Use Activity Component for Show/Hide](#66-use-activity-component-for-showhide) + - 6.7 [Use Explicit Conditional Rendering](#67-use-explicit-conditional-rendering) +7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM** + - 7.1 [Batch DOM CSS Changes](#71-batch-dom-css-changes) + - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups) + - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops) + - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls) + - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls) + - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations) + - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons) + - 7.8 [Early Return from Functions](#78-early-return-from-functions) + - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation) + - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort) + - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups) + - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability) +8. [Advanced Patterns](#8-advanced-patterns) — **LOW** + - 8.1 [Store Event Handlers in Refs](#81-store-event-handlers-in-refs) + - 8.2 [useLatest for Stable Callback Refs](#82-uselatest-for-stable-callback-refs) + +--- + +## 1. Eliminating Waterfalls + +**Impact: CRITICAL** + +Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. + +### 1.1 Defer Await Until Needed + +**Impact: HIGH (avoids blocking unused code paths)** + +Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. + +**Incorrect: blocks both branches** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + const userData = await fetchUserData(userId) + + if (skipProcessing) { + // Returns immediately but still waited for userData + return { skipped: true } + } + + // Only this branch uses userData + return processUserData(userData) +} +``` + +**Correct: only blocks when needed** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + if (skipProcessing) { + // Returns immediately without waiting + return { skipped: true } + } + + // Fetch only when needed + const userData = await fetchUserData(userId) + return processUserData(userData) +} +``` + +**Another example: early return optimization** + +```typescript +// Incorrect: always fetches permissions +async function updateResource(resourceId: string, userId: string) { + const permissions = await fetchPermissions(userId) + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} + +// Correct: fetches only when needed +async function updateResource(resourceId: string, userId: string) { + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + const permissions = await fetchPermissions(userId) + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} +``` + +This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. + +### 1.2 Dependency-Based Parallelization + +**Impact: CRITICAL (2-10× improvement)** + +For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment. + +**Incorrect: profile waits for config unnecessarily** + +```typescript +const [user, config] = await Promise.all([ + fetchUser(), + fetchConfig() +]) +const profile = await fetchProfile(user.id) +``` + +**Correct: config and profile run in parallel** + +```typescript +import { all } from 'better-all' + +const { user, config, profile } = await all({ + async user() { return fetchUser() }, + async config() { return fetchConfig() }, + async profile() { + return fetchProfile((await this.$.user).id) + } +}) +``` + +Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all) + +### 1.3 Prevent Waterfall Chains in API Routes + +**Impact: CRITICAL (2-10× improvement)** + +In API routes and Server Actions, start independent operations immediately, even if you don't await them yet. + +**Incorrect: config waits for auth, data waits for both** + +```typescript +export async function GET(request: Request) { + const session = await auth() + const config = await fetchConfig() + const data = await fetchData(session.user.id) + return Response.json({ data, config }) +} +``` + +**Correct: auth and config start immediately** + +```typescript +export async function GET(request: Request) { + const sessionPromise = auth() + const configPromise = fetchConfig() + const session = await sessionPromise + const [config, data] = await Promise.all([ + configPromise, + fetchData(session.user.id) + ]) + return Response.json({ data, config }) +} +``` + +For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization). + +### 1.4 Promise.all() for Independent Operations + +**Impact: CRITICAL (2-10× improvement)** + +When async operations have no interdependencies, execute them concurrently using `Promise.all()`. + +**Incorrect: sequential execution, 3 round trips** + +```typescript +const user = await fetchUser() +const posts = await fetchPosts() +const comments = await fetchComments() +``` + +**Correct: parallel execution, 1 round trip** + +```typescript +const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() +]) +``` + +### 1.5 Strategic Suspense Boundaries + +**Impact: HIGH (faster initial paint)** + +Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads. + +**Incorrect: wrapper blocked by data fetching** + +```tsx +async function Page() { + const data = await fetchData() // Blocks entire page + + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <div> + <DataDisplay data={data} /> + </div> + <div>Footer</div> + </div> + ) +} +``` + +The entire layout waits for data even though only the middle section needs it. + +**Correct: wrapper shows immediately, data streams in** + +```tsx +function Page() { + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <div> + <Suspense fallback={<Skeleton />}> + <DataDisplay /> + </Suspense> + </div> + <div>Footer</div> + </div> + ) +} + +async function DataDisplay() { + const data = await fetchData() // Only blocks this component + return <div>{data.content}</div> +} +``` + +Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data. + +**Alternative: share promise across components** + +```tsx +function Page() { + // Start fetch immediately, but don't await + const dataPromise = fetchData() + + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <Suspense fallback={<Skeleton />}> + <DataDisplay dataPromise={dataPromise} /> + <DataSummary dataPromise={dataPromise} /> + </Suspense> + <div>Footer</div> + </div> + ) +} + +function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) { + const data = use(dataPromise) // Unwraps the promise + return <div>{data.content}</div> +} + +function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) { + const data = use(dataPromise) // Reuses the same promise + return <div>{data.summary}</div> +} +``` + +Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together. + +**When NOT to use this pattern:** + +- Critical data needed for layout decisions (affects positioning) + +- SEO-critical content above the fold + +- Small, fast queries where suspense overhead isn't worth it + +- When you want to avoid layout shift (loading → content jump) + +**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities. + +--- + +## 2. Bundle Size Optimization + +**Impact: CRITICAL** + +Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. + +### 2.1 Avoid Barrel File Imports + +**Impact: CRITICAL (200-800ms import cost, slow builds)** + +Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`). + +Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts. + +**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph. + +**Incorrect: imports entire library** + +```tsx +import { Check, X, Menu } from 'lucide-react' +// Loads 1,583 modules, takes ~2.8s extra in dev +// Runtime cost: 200-800ms on every cold start + +import { Button, TextField } from '@mui/material' +// Loads 2,225 modules, takes ~4.2s extra in dev +``` + +**Correct: imports only what you need** + +```tsx +import Check from 'lucide-react/dist/esm/icons/check' +import X from 'lucide-react/dist/esm/icons/x' +import Menu from 'lucide-react/dist/esm/icons/menu' +// Loads only 3 modules (~2KB vs ~1MB) + +import Button from '@mui/material/Button' +import TextField from '@mui/material/TextField' +// Loads only what you use +``` + +**Alternative: Next.js 13.5+** + +```js +// next.config.js - use optimizePackageImports +module.exports = { + experimental: { + optimizePackageImports: ['lucide-react', '@mui/material'] + } +} + +// Then you can keep the ergonomic barrel imports: +import { Check, X, Menu } from 'lucide-react' +// Automatically transformed to direct imports at build time +``` + +Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. + +Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`. + +Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) + +### 2.2 Conditional Module Loading + +**Impact: HIGH (loads large data only when needed)** + +Load large data or modules only when a feature is activated. + +**Example: lazy-load animation frames** + +```tsx +function AnimationPlayer({ enabled }: { enabled: boolean }) { + const [frames, setFrames] = useState<Frame[] | null>(null) + + useEffect(() => { + if (enabled && !frames && typeof window !== 'undefined') { + import('./animation-frames.js') + .then(mod => setFrames(mod.frames)) + .catch(() => setEnabled(false)) + } + }, [enabled, frames]) + + if (!frames) return <Skeleton /> + return <Canvas frames={frames} /> +} +``` + +The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed. + +### 2.3 Defer Non-Critical Third-Party Libraries + +**Impact: MEDIUM (loads after hydration)** + +Analytics, logging, and error tracking don't block user interaction. Load them after hydration. + +**Incorrect: blocks initial bundle** + +```tsx +import { Analytics } from '@vercel/analytics/react' + +export default function RootLayout({ children }) { + return ( + <html> + <body> + {children} + <Analytics /> + </body> + </html> + ) +} +``` + +**Correct: loads after hydration** + +```tsx +import dynamic from 'next/dynamic' + +const Analytics = dynamic( + () => import('@vercel/analytics/react').then(m => m.Analytics), + { ssr: false } +) + +export default function RootLayout({ children }) { + return ( + <html> + <body> + {children} + <Analytics /> + </body> + </html> + ) +} +``` + +### 2.4 Dynamic Imports for Heavy Components + +**Impact: CRITICAL (directly affects TTI and LCP)** + +Use `next/dynamic` to lazy-load large components not needed on initial render. + +**Incorrect: Monaco bundles with main chunk ~300KB** + +```tsx +import { MonacoEditor } from './monaco-editor' + +function CodePanel({ code }: { code: string }) { + return <MonacoEditor value={code} /> +} +``` + +**Correct: Monaco loads on demand** + +```tsx +import dynamic from 'next/dynamic' + +const MonacoEditor = dynamic( + () => import('./monaco-editor').then(m => m.MonacoEditor), + { ssr: false } +) + +function CodePanel({ code }: { code: string }) { + return <MonacoEditor value={code} /> +} +``` + +### 2.5 Preload Based on User Intent + +**Impact: MEDIUM (reduces perceived latency)** + +Preload heavy bundles before they're needed to reduce perceived latency. + +**Example: preload on hover/focus** + +```tsx +function EditorButton({ onClick }: { onClick: () => void }) { + const preload = () => { + if (typeof window !== 'undefined') { + void import('./monaco-editor') + } + } + + return ( + <button + onMouseEnter={preload} + onFocus={preload} + onClick={onClick} + > + Open Editor + </button> + ) +} +``` + +**Example: preload when feature flag is enabled** + +```tsx +function FlagsProvider({ children, flags }: Props) { + useEffect(() => { + if (flags.editorEnabled && typeof window !== 'undefined') { + void import('./monaco-editor').then(mod => mod.init()) + } + }, [flags.editorEnabled]) + + return <FlagsContext.Provider value={flags}> + {children} + </FlagsContext.Provider> +} +``` + +The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed. + +--- + +## 3. Server-Side Performance + +**Impact: HIGH** + +Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times. + +### 3.1 Cross-Request LRU Caching + +**Impact: HIGH (caches across requests)** + +`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache. + +**Implementation:** + +```typescript +import { LRUCache } from 'lru-cache' + +const cache = new LRUCache<string, any>({ + max: 1000, + ttl: 5 * 60 * 1000 // 5 minutes +}) + +export async function getUser(id: string) { + const cached = cache.get(id) + if (cached) return cached + + const user = await db.user.findUnique({ where: { id } }) + cache.set(id, user) + return user +} + +// Request 1: DB query, result cached +// Request 2: cache hit, no DB query +``` + +Use when sequential user actions hit multiple endpoints needing the same data within seconds. + +**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis. + +**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching. + +Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) + +### 3.2 Minimize Serialization at RSC Boundaries + +**Impact: HIGH (reduces data transfer size)** + +The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses. + +**Incorrect: serializes all 50 fields** + +```tsx +async function Page() { + const user = await fetchUser() // 50 fields + return <Profile user={user} /> +} + +'use client' +function Profile({ user }: { user: User }) { + return <div>{user.name}</div> // uses 1 field +} +``` + +**Correct: serializes only 1 field** + +```tsx +async function Page() { + const user = await fetchUser() + return <Profile name={user.name} /> +} + +'use client' +function Profile({ name }: { name: string }) { + return <div>{name}</div> +} +``` + +### 3.3 Parallel Data Fetching with Component Composition + +**Impact: CRITICAL (eliminates server-side waterfalls)** + +React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching. + +**Incorrect: Sidebar waits for Page's fetch to complete** + +```tsx +export default async function Page() { + const header = await fetchHeader() + return ( + <div> + <div>{header}</div> + <Sidebar /> + </div> + ) +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} +``` + +**Correct: both fetch simultaneously** + +```tsx +async function Header() { + const data = await fetchHeader() + return <div>{data}</div> +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} + +export default function Page() { + return ( + <div> + <Header /> + <Sidebar /> + </div> + ) +} +``` + +**Alternative with children prop:** + +```tsx +async function Layout({ children }: { children: ReactNode }) { + const header = await fetchHeader() + return ( + <div> + <div>{header}</div> + {children} + </div> + ) +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} + +export default function Page() { + return ( + <Layout> + <Sidebar /> + </Layout> + ) +} +``` + +### 3.4 Per-Request Deduplication with React.cache() + +**Impact: MEDIUM (deduplicates within request)** + +Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most. + +**Usage:** + +```typescript +import { cache } from 'react' + +export const getCurrentUser = cache(async () => { + const session = await auth() + if (!session?.user?.id) return null + return await db.user.findUnique({ + where: { id: session.user.id } + }) +}) +``` + +Within a single request, multiple calls to `getCurrentUser()` execute the query only once. + +### 3.5 Use after() for Non-Blocking Operations + +**Impact: MEDIUM (faster response times)** + +Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response. + +**Incorrect: blocks response** + +```tsx +import { logUserAction } from '@/app/utils' + +export async function POST(request: Request) { + // Perform mutation + await updateDatabase(request) + + // Logging blocks the response + const userAgent = request.headers.get('user-agent') || 'unknown' + await logUserAction({ userAgent }) + + return new Response(JSON.stringify({ status: 'success' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) +} +``` + +**Correct: non-blocking** + +```tsx +import { after } from 'next/server' +import { headers, cookies } from 'next/headers' +import { logUserAction } from '@/app/utils' + +export async function POST(request: Request) { + // Perform mutation + await updateDatabase(request) + + // Log after response is sent + after(async () => { + const userAgent = (await headers()).get('user-agent') || 'unknown' + const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous' + + logUserAction({ sessionCookie, userAgent }) + }) + + return new Response(JSON.stringify({ status: 'success' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) +} +``` + +The response is sent immediately while logging happens in the background. + +**Common use cases:** + +- Analytics tracking + +- Audit logging + +- Sending notifications + +- Cache invalidation + +- Cleanup tasks + +**Important notes:** + +- `after()` runs even if the response fails or redirects + +- Works in Server Actions, Route Handlers, and Server Components + +Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after) + +--- + +## 4. Client-Side Data Fetching + +**Impact: MEDIUM-HIGH** + +Automatic deduplication and efficient data fetching patterns reduce redundant network requests. + +### 4.1 Deduplicate Global Event Listeners + +**Impact: LOW (single listener for N components)** + +Use `useSWRSubscription()` to share global event listeners across component instances. + +**Incorrect: N instances = N listeners** + +```tsx +function useKeyboardShortcut(key: string, callback: () => void) { + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.metaKey && e.key === key) { + callback() + } + } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }, [key, callback]) +} +``` + +When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener. + +**Correct: N instances = 1 listener** + +```tsx +import useSWRSubscription from 'swr/subscription' + +// Module-level Map to track callbacks per key +const keyCallbacks = new Map<string, Set<() => void>>() + +function useKeyboardShortcut(key: string, callback: () => void) { + // Register this callback in the Map + useEffect(() => { + if (!keyCallbacks.has(key)) { + keyCallbacks.set(key, new Set()) + } + keyCallbacks.get(key)!.add(callback) + + return () => { + const set = keyCallbacks.get(key) + if (set) { + set.delete(callback) + if (set.size === 0) { + keyCallbacks.delete(key) + } + } + } + }, [key, callback]) + + useSWRSubscription('global-keydown', () => { + const handler = (e: KeyboardEvent) => { + if (e.metaKey && keyCallbacks.has(e.key)) { + keyCallbacks.get(e.key)!.forEach(cb => cb()) + } + } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }) +} + +function Profile() { + // Multiple shortcuts will share the same listener + useKeyboardShortcut('p', () => { /* ... */ }) + useKeyboardShortcut('k', () => { /* ... */ }) + // ... +} +``` + +### 4.2 Use SWR for Automatic Deduplication + +**Impact: MEDIUM-HIGH (automatic deduplication)** + +SWR enables request deduplication, caching, and revalidation across component instances. + +**Incorrect: no deduplication, each instance fetches** + +```tsx +function UserList() { + const [users, setUsers] = useState([]) + useEffect(() => { + fetch('/api/users') + .then(r => r.json()) + .then(setUsers) + }, []) +} +``` + +**Correct: multiple instances share one request** + +```tsx +import useSWR from 'swr' + +function UserList() { + const { data: users } = useSWR('/api/users', fetcher) +} +``` + +**For immutable data:** + +```tsx +import { useImmutableSWR } from '@/lib/swr' + +function StaticContent() { + const { data } = useImmutableSWR('/api/config', fetcher) +} +``` + +**For mutations:** + +```tsx +import { useSWRMutation } from 'swr/mutation' + +function UpdateButton() { + const { trigger } = useSWRMutation('/api/user', updateUser) + return <button onClick={() => trigger()}>Update</button> +} +``` + +Reference: [https://swr.vercel.app](https://swr.vercel.app) + +--- + +## 5. Re-render Optimization + +**Impact: MEDIUM** + +Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. + +### 5.1 Defer State Reads to Usage Point + +**Impact: MEDIUM (avoids unnecessary subscriptions)** + +Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. + +**Incorrect: subscribes to all searchParams changes** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const searchParams = useSearchParams() + + const handleShare = () => { + const ref = searchParams.get('ref') + shareChat(chatId, { ref }) + } + + return <button onClick={handleShare}>Share</button> +} +``` + +**Correct: reads on demand, no subscription** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const handleShare = () => { + const params = new URLSearchParams(window.location.search) + const ref = params.get('ref') + shareChat(chatId, { ref }) + } + + return <button onClick={handleShare}>Share</button> +} +``` + +### 5.2 Extract to Memoized Components + +**Impact: MEDIUM (enables early returns)** + +Extract expensive work into memoized components to enable early returns before computation. + +**Incorrect: computes avatar even when loading** + +```tsx +function Profile({ user, loading }: Props) { + const avatar = useMemo(() => { + const id = computeAvatarId(user) + return <Avatar id={id} /> + }, [user]) + + if (loading) return <Skeleton /> + return <div>{avatar}</div> +} +``` + +**Correct: skips computation when loading** + +```tsx +const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { + const id = useMemo(() => computeAvatarId(user), [user]) + return <Avatar id={id} /> +}) + +function Profile({ user, loading }: Props) { + if (loading) return <Skeleton /> + return ( + <div> + <UserAvatar user={user} /> + </div> + ) +} +``` + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders. + +### 5.3 Narrow Effect Dependencies + +**Impact: LOW (minimizes effect re-runs)** + +Specify primitive dependencies instead of objects to minimize effect re-runs. + +**Incorrect: re-runs on any user field change** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user]) +``` + +**Correct: re-runs only when id changes** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user.id]) +``` + +**For derived state, compute outside effect:** + +```tsx +// Incorrect: runs on width=767, 766, 765... +useEffect(() => { + if (width < 768) { + enableMobileMode() + } +}, [width]) + +// Correct: runs only on boolean transition +const isMobile = width < 768 +useEffect(() => { + if (isMobile) { + enableMobileMode() + } +}, [isMobile]) +``` + +### 5.4 Subscribe to Derived State + +**Impact: MEDIUM (reduces re-render frequency)** + +Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. + +**Incorrect: re-renders on every pixel change** + +```tsx +function Sidebar() { + const width = useWindowWidth() // updates continuously + const isMobile = width < 768 + return <nav className={isMobile ? 'mobile' : 'desktop'}> +} +``` + +**Correct: re-renders only when boolean changes** + +```tsx +function Sidebar() { + const isMobile = useMediaQuery('(max-width: 767px)') + return <nav className={isMobile ? 'mobile' : 'desktop'}> +} +``` + +### 5.5 Use Functional setState Updates + +**Impact: MEDIUM (prevents stale closures and unnecessary callback recreations)** + +When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references. + +**Incorrect: requires state as dependency** + +```tsx +function TodoList() { + const [items, setItems] = useState(initialItems) + + // Callback must depend on items, recreated on every items change + const addItems = useCallback((newItems: Item[]) => { + setItems([...items, ...newItems]) + }, [items]) // ❌ items dependency causes recreations + + // Risk of stale closure if dependency is forgotten + const removeItem = useCallback((id: string) => { + setItems(items.filter(item => item.id !== id)) + }, []) // ❌ Missing items dependency - will use stale items! + + return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} /> +} +``` + +The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value. + +**Correct: stable callbacks, no stale closures** + +```tsx +function TodoList() { + const [items, setItems] = useState(initialItems) + + // Stable callback, never recreated + const addItems = useCallback((newItems: Item[]) => { + setItems(curr => [...curr, ...newItems]) + }, []) // ✅ No dependencies needed + + // Always uses latest state, no stale closure risk + const removeItem = useCallback((id: string) => { + setItems(curr => curr.filter(item => item.id !== id)) + }, []) // ✅ Safe and stable + + return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} /> +} +``` + +**Benefits:** + +1. **Stable callback references** - Callbacks don't need to be recreated when state changes + +2. **No stale closures** - Always operates on the latest state value + +3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks + +4. **Prevents bugs** - Eliminates the most common source of React closure bugs + +**When to use functional updates:** + +- Any setState that depends on the current state value + +- Inside useCallback/useMemo when state is needed + +- Event handlers that reference state + +- Async operations that update state + +**When direct updates are fine:** + +- Setting state to a static value: `setCount(0)` + +- Setting state from props/arguments only: `setName(newName)` + +- State doesn't depend on previous value + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs. + +### 5.6 Use Lazy State Initialization + +**Impact: MEDIUM (wasted computation on every render)** + +Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once. + +**Incorrect: runs on every render** + +```tsx +function FilteredList({ items }: { items: Item[] }) { + // buildSearchIndex() runs on EVERY render, even after initialization + const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items)) + const [query, setQuery] = useState('') + + // When query changes, buildSearchIndex runs again unnecessarily + return <SearchResults index={searchIndex} query={query} /> +} + +function UserProfile() { + // JSON.parse runs on every render + const [settings, setSettings] = useState( + JSON.parse(localStorage.getItem('settings') || '{}') + ) + + return <SettingsForm settings={settings} onChange={setSettings} /> +} +``` + +**Correct: runs only once** + +```tsx +function FilteredList({ items }: { items: Item[] }) { + // buildSearchIndex() runs ONLY on initial render + const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items)) + const [query, setQuery] = useState('') + + return <SearchResults index={searchIndex} query={query} /> +} + +function UserProfile() { + // JSON.parse runs only on initial render + const [settings, setSettings] = useState(() => { + const stored = localStorage.getItem('settings') + return stored ? JSON.parse(stored) : {} + }) + + return <SettingsForm settings={settings} onChange={setSettings} /> +} +``` + +Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations. + +For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary. + +### 5.7 Use Transitions for Non-Urgent Updates + +**Impact: MEDIUM (maintains UI responsiveness)** + +Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness. + +**Incorrect: blocks UI on every scroll** + +```tsx +function ScrollTracker() { + const [scrollY, setScrollY] = useState(0) + useEffect(() => { + const handler = () => setScrollY(window.scrollY) + window.addEventListener('scroll', handler, { passive: true }) + return () => window.removeEventListener('scroll', handler) + }, []) +} +``` + +**Correct: non-blocking updates** + +```tsx +import { startTransition } from 'react' + +function ScrollTracker() { + const [scrollY, setScrollY] = useState(0) + useEffect(() => { + const handler = () => { + startTransition(() => setScrollY(window.scrollY)) + } + window.addEventListener('scroll', handler, { passive: true }) + return () => window.removeEventListener('scroll', handler) + }, []) +} +``` + +--- + +## 6. Rendering Performance + +**Impact: MEDIUM** + +Optimizing the rendering process reduces the work the browser needs to do. + +### 6.1 Animate SVG Wrapper Instead of SVG Element + +**Impact: LOW (enables hardware acceleration)** + +Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead. + +**Incorrect: animating SVG directly - no hardware acceleration** + +```tsx +function LoadingSpinner() { + return ( + <svg + className="animate-spin" + width="24" + height="24" + viewBox="0 0 24 24" + > + <circle cx="12" cy="12" r="10" stroke="currentColor" /> + </svg> + ) +} +``` + +**Correct: animating wrapper div - hardware accelerated** + +```tsx +function LoadingSpinner() { + return ( + <div className="animate-spin"> + <svg + width="24" + height="24" + viewBox="0 0 24 24" + > + <circle cx="12" cy="12" r="10" stroke="currentColor" /> + </svg> + </div> + ) +} +``` + +This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations. + +### 6.2 CSS content-visibility for Long Lists + +**Impact: HIGH (faster initial render)** + +Apply `content-visibility: auto` to defer off-screen rendering. + +**CSS:** + +```css +.message-item { + content-visibility: auto; + contain-intrinsic-size: 0 80px; +} +``` + +**Example:** + +```tsx +function MessageList({ messages }: { messages: Message[] }) { + return ( + <div className="overflow-y-auto h-screen"> + {messages.map(msg => ( + <div key={msg.id} className="message-item"> + <Avatar user={msg.author} /> + <div>{msg.content}</div> + </div> + ))} + </div> + ) +} +``` + +For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render). + +### 6.3 Hoist Static JSX Elements + +**Impact: LOW (avoids re-creation)** + +Extract static JSX outside components to avoid re-creation. + +**Incorrect: recreates element every render** + +```tsx +function LoadingSkeleton() { + return <div className="animate-pulse h-20 bg-gray-200" /> +} + +function Container() { + return ( + <div> + {loading && <LoadingSkeleton />} + </div> + ) +} +``` + +**Correct: reuses same element** + +```tsx +const loadingSkeleton = ( + <div className="animate-pulse h-20 bg-gray-200" /> +) + +function Container() { + return ( + <div> + {loading && loadingSkeleton} + </div> + ) +} +``` + +This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render. + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary. + +### 6.4 Optimize SVG Precision + +**Impact: LOW (reduces file size)** + +Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered. + +**Incorrect: excessive precision** + +```svg +<path d="M 10.293847 20.847362 L 30.938472 40.192837" /> +``` + +**Correct: 1 decimal place** + +```svg +<path d="M 10.3 20.8 L 30.9 40.2" /> +``` + +**Automate with SVGO:** + +```bash +npx svgo --precision=1 --multipass icon.svg +``` + +### 6.5 Prevent Hydration Mismatch Without Flickering + +**Impact: MEDIUM (avoids visual flicker and hydration errors)** + +When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates. + +**Incorrect: breaks SSR** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + // localStorage is not available on server - throws error + const theme = localStorage.getItem('theme') || 'light' + + return ( + <div className={theme}> + {children} + </div> + ) +} +``` + +Server-side rendering will fail because `localStorage` is undefined. + +**Incorrect: visual flickering** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + const [theme, setTheme] = useState('light') + + useEffect(() => { + // Runs after hydration - causes visible flash + const stored = localStorage.getItem('theme') + if (stored) { + setTheme(stored) + } + }, []) + + return ( + <div className={theme}> + {children} + </div> + ) +} +``` + +Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content. + +**Correct: no flicker, no hydration mismatch** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + return ( + <> + <div id="theme-wrapper"> + {children} + </div> + <script + dangerouslySetInnerHTML={{ + __html: ` + (function() { + try { + var theme = localStorage.getItem('theme') || 'light'; + var el = document.getElementById('theme-wrapper'); + if (el) el.className = theme; + } catch (e) {} + })(); + `, + }} + /> + </> + ) +} +``` + +The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch. + +This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values. + +### 6.6 Use Activity Component for Show/Hide + +**Impact: MEDIUM (preserves state/DOM)** + +Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility. + +**Usage:** + +```tsx +import { Activity } from 'react' + +function Dropdown({ isOpen }: Props) { + return ( + <Activity mode={isOpen ? 'visible' : 'hidden'}> + <ExpensiveMenu /> + </Activity> + ) +} +``` + +Avoids expensive re-renders and state loss. + +### 6.7 Use Explicit Conditional Rendering + +**Impact: LOW (prevents rendering 0 or NaN)** + +Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render. + +**Incorrect: renders "0" when count is 0** + +```tsx +function Badge({ count }: { count: number }) { + return ( + <div> + {count && <span className="badge">{count}</span>} + </div> + ) +} + +// When count = 0, renders: <div>0</div> +// When count = 5, renders: <div><span class="badge">5</span></div> +``` + +**Correct: renders nothing when count is 0** + +```tsx +function Badge({ count }: { count: number }) { + return ( + <div> + {count > 0 ? <span className="badge">{count}</span> : null} + </div> + ) +} + +// When count = 0, renders: <div></div> +// When count = 5, renders: <div><span class="badge">5</span></div> +``` + +--- + +## 7. JavaScript Performance + +**Impact: LOW-MEDIUM** + +Micro-optimizations for hot paths can add up to meaningful improvements. + +### 7.1 Batch DOM CSS Changes + +**Impact: MEDIUM (reduces reflows/repaints)** + +Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows. + +**Incorrect: multiple reflows** + +```typescript +function updateElementStyles(element: HTMLElement) { + // Each line triggers a reflow + element.style.width = '100px' + element.style.height = '200px' + element.style.backgroundColor = 'blue' + element.style.border = '1px solid black' +} +``` + +**Correct: add class - single reflow** + +```typescript +// CSS file +.highlighted-box { + width: 100px; + height: 200px; + background-color: blue; + border: 1px solid black; +} + +// JavaScript +function updateElementStyles(element: HTMLElement) { + element.classList.add('highlighted-box') +} +``` + +**Correct: change cssText - single reflow** + +```typescript +function updateElementStyles(element: HTMLElement) { + element.style.cssText = ` + width: 100px; + height: 200px; + background-color: blue; + border: 1px solid black; + ` +} +``` + +**React example:** + +```tsx +// Incorrect: changing styles one by one +function Box({ isHighlighted }: { isHighlighted: boolean }) { + const ref = useRef<HTMLDivElement>(null) + + useEffect(() => { + if (ref.current && isHighlighted) { + ref.current.style.width = '100px' + ref.current.style.height = '200px' + ref.current.style.backgroundColor = 'blue' + } + }, [isHighlighted]) + + return <div ref={ref}>Content</div> +} + +// Correct: toggle class +function Box({ isHighlighted }: { isHighlighted: boolean }) { + return ( + <div className={isHighlighted ? 'highlighted-box' : ''}> + Content + </div> + ) +} +``` + +Prefer CSS classes over inline styles when possible. Classes are cached by the browser and provide better separation of concerns. + +### 7.2 Build Index Maps for Repeated Lookups + +**Impact: LOW-MEDIUM (1M ops to 2K ops)** + +Multiple `.find()` calls by the same key should use a Map. + +**Incorrect (O(n) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + return orders.map(order => ({ + ...order, + user: users.find(u => u.id === order.userId) + })) +} +``` + +**Correct (O(1) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + const userById = new Map(users.map(u => [u.id, u])) + + return orders.map(order => ({ + ...order, + user: userById.get(order.userId) + })) +} +``` + +Build map once (O(n)), then all lookups are O(1). + +For 1000 orders × 1000 users: 1M ops → 2K ops. + +### 7.3 Cache Property Access in Loops + +**Impact: LOW-MEDIUM (reduces lookups)** + +Cache object property lookups in hot paths. + +**Incorrect: 3 lookups × N iterations** + +```typescript +for (let i = 0; i < arr.length; i++) { + process(obj.config.settings.value) +} +``` + +**Correct: 1 lookup total** + +```typescript +const value = obj.config.settings.value +const len = arr.length +for (let i = 0; i < len; i++) { + process(value) +} +``` + +### 7.4 Cache Repeated Function Calls + +**Impact: MEDIUM (avoid redundant computation)** + +Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render. + +**Incorrect: redundant computation** + +```typescript +function ProjectList({ projects }: { projects: Project[] }) { + return ( + <div> + {projects.map(project => { + // slugify() called 100+ times for same project names + const slug = slugify(project.name) + + return <ProjectCard key={project.id} slug={slug} /> + })} + </div> + ) +} +``` + +**Correct: cached results** + +```typescript +// Module-level cache +const slugifyCache = new Map<string, string>() + +function cachedSlugify(text: string): string { + if (slugifyCache.has(text)) { + return slugifyCache.get(text)! + } + const result = slugify(text) + slugifyCache.set(text, result) + return result +} + +function ProjectList({ projects }: { projects: Project[] }) { + return ( + <div> + {projects.map(project => { + // Computed only once per unique project name + const slug = cachedSlugify(project.name) + + return <ProjectCard key={project.id} slug={slug} /> + })} + </div> + ) +} +``` + +**Simpler pattern for single-value functions:** + +```typescript +let isLoggedInCache: boolean | null = null + +function isLoggedIn(): boolean { + if (isLoggedInCache !== null) { + return isLoggedInCache + } + + isLoggedInCache = document.cookie.includes('auth=') + return isLoggedInCache +} + +// Clear cache when auth changes +function onAuthChange() { + isLoggedInCache = null +} +``` + +Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components. + +Reference: [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast) + +### 7.5 Cache Storage API Calls + +**Impact: LOW-MEDIUM (reduces expensive I/O)** + +`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory. + +**Incorrect: reads storage on every call** + +```typescript +function getTheme() { + return localStorage.getItem('theme') ?? 'light' +} +// Called 10 times = 10 storage reads +``` + +**Correct: Map cache** + +```typescript +const storageCache = new Map<string, string | null>() + +function getLocalStorage(key: string) { + if (!storageCache.has(key)) { + storageCache.set(key, localStorage.getItem(key)) + } + return storageCache.get(key) +} + +function setLocalStorage(key: string, value: string) { + localStorage.setItem(key, value) + storageCache.set(key, value) // keep cache in sync +} +``` + +Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components. + +**Cookie caching:** + +```typescript +let cookieCache: Record<string, string> | null = null + +function getCookie(name: string) { + if (!cookieCache) { + cookieCache = Object.fromEntries( + document.cookie.split('; ').map(c => c.split('=')) + ) + } + return cookieCache[name] +} +``` + +**Important: invalidate on external changes** + +```typescript +window.addEventListener('storage', (e) => { + if (e.key) storageCache.delete(e.key) +}) + +document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + storageCache.clear() + } +}) +``` + +If storage can change externally (another tab, server-set cookies), invalidate cache: + +### 7.6 Combine Multiple Array Iterations + +**Impact: LOW-MEDIUM (reduces iterations)** + +Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop. + +**Incorrect: 3 iterations** + +```typescript +const admins = users.filter(u => u.isAdmin) +const testers = users.filter(u => u.isTester) +const inactive = users.filter(u => !u.isActive) +``` + +**Correct: 1 iteration** + +```typescript +const admins: User[] = [] +const testers: User[] = [] +const inactive: User[] = [] + +for (const user of users) { + if (user.isAdmin) admins.push(user) + if (user.isTester) testers.push(user) + if (!user.isActive) inactive.push(user) +} +``` + +### 7.7 Early Length Check for Array Comparisons + +**Impact: MEDIUM-HIGH (avoids expensive operations when lengths differ)** + +When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal. + +In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops). + +**Incorrect: always runs expensive comparison** + +```typescript +function hasChanges(current: string[], original: string[]) { + // Always sorts and joins, even when lengths differ + return current.sort().join() !== original.sort().join() +} +``` + +Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings. + +**Correct (O(1) length check first):** + +```typescript +function hasChanges(current: string[], original: string[]) { + // Early return if lengths differ + if (current.length !== original.length) { + return true + } + // Only sort/join when lengths match + const currentSorted = current.toSorted() + const originalSorted = original.toSorted() + for (let i = 0; i < currentSorted.length; i++) { + if (currentSorted[i] !== originalSorted[i]) { + return true + } + } + return false +} +``` + +This new approach is more efficient because: + +- It avoids the overhead of sorting and joining the arrays when lengths differ + +- It avoids consuming memory for the joined strings (especially important for large arrays) + +- It avoids mutating the original arrays + +- It returns early when a difference is found + +### 7.8 Early Return from Functions + +**Impact: LOW-MEDIUM (avoids unnecessary computation)** + +Return early when result is determined to skip unnecessary processing. + +**Incorrect: processes all items even after finding answer** + +```typescript +function validateUsers(users: User[]) { + let hasError = false + let errorMessage = '' + + for (const user of users) { + if (!user.email) { + hasError = true + errorMessage = 'Email required' + } + if (!user.name) { + hasError = true + errorMessage = 'Name required' + } + // Continues checking all users even after error found + } + + return hasError ? { valid: false, error: errorMessage } : { valid: true } +} +``` + +**Correct: returns immediately on first error** + +```typescript +function validateUsers(users: User[]) { + for (const user of users) { + if (!user.email) { + return { valid: false, error: 'Email required' } + } + if (!user.name) { + return { valid: false, error: 'Name required' } + } + } + + return { valid: true } +} +``` + +### 7.9 Hoist RegExp Creation + +**Impact: LOW-MEDIUM (avoids recreation)** + +Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`. + +**Incorrect: new RegExp every render** + +```tsx +function Highlighter({ text, query }: Props) { + const regex = new RegExp(`(${query})`, 'gi') + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)}</> +} +``` + +**Correct: memoize or hoist** + +```tsx +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + +function Highlighter({ text, query }: Props) { + const regex = useMemo( + () => new RegExp(`(${escapeRegex(query)})`, 'gi'), + [query] + ) + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)}</> +} +``` + +**Warning: global regex has mutable state** + +```typescript +const regex = /foo/g +regex.test('foo') // true, lastIndex = 3 +regex.test('foo') // false, lastIndex = 0 +``` + +Global regex (`/g`) has mutable `lastIndex` state: + +### 7.10 Use Loop for Min/Max Instead of Sort + +**Impact: LOW (O(n) instead of O(n log n))** + +Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower. + +**Incorrect (O(n log n) - sort to find latest):** + +```typescript +interface Project { + id: string + name: string + updatedAt: number +} + +function getLatestProject(projects: Project[]) { + const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt) + return sorted[0] +} +``` + +Sorts the entire array just to find the maximum value. + +**Incorrect (O(n log n) - sort for oldest and newest):** + +```typescript +function getOldestAndNewest(projects: Project[]) { + const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt) + return { oldest: sorted[0], newest: sorted[sorted.length - 1] } +} +``` + +Still sorts unnecessarily when only min/max are needed. + +**Correct (O(n) - single loop):** + +```typescript +function getLatestProject(projects: Project[]) { + if (projects.length === 0) return null + + let latest = projects[0] + + for (let i = 1; i < projects.length; i++) { + if (projects[i].updatedAt > latest.updatedAt) { + latest = projects[i] + } + } + + return latest +} + +function getOldestAndNewest(projects: Project[]) { + if (projects.length === 0) return { oldest: null, newest: null } + + let oldest = projects[0] + let newest = projects[0] + + for (let i = 1; i < projects.length; i++) { + if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i] + if (projects[i].updatedAt > newest.updatedAt) newest = projects[i] + } + + return { oldest, newest } +} +``` + +Single pass through the array, no copying, no sorting. + +**Alternative: Math.min/Math.max for small arrays** + +```typescript +const numbers = [5, 2, 8, 1, 9] +const min = Math.min(...numbers) +const max = Math.max(...numbers) +``` + +This works for small arrays but can be slower for very large arrays due to spread operator limitations. Use the loop approach for reliability. + +### 7.11 Use Set/Map for O(1) Lookups + +**Impact: LOW-MEDIUM (O(n) to O(1))** + +Convert arrays to Set/Map for repeated membership checks. + +**Incorrect (O(n) per check):** + +```typescript +const allowedIds = ['a', 'b', 'c', ...] +items.filter(item => allowedIds.includes(item.id)) +``` + +**Correct (O(1) per check):** + +```typescript +const allowedIds = new Set(['a', 'b', 'c', ...]) +items.filter(item => allowedIds.has(item.id)) +``` + +### 7.12 Use toSorted() Instead of sort() for Immutability + +**Impact: MEDIUM-HIGH (prevents mutation bugs in React state)** + +`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation. + +**Incorrect: mutates original array** + +```typescript +function UserList({ users }: { users: User[] }) { + // Mutates the users prop array! + const sorted = useMemo( + () => users.sort((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return <div>{sorted.map(renderUser)}</div> +} +``` + +**Correct: creates new array** + +```typescript +function UserList({ users }: { users: User[] }) { + // Creates new sorted array, original unchanged + const sorted = useMemo( + () => users.toSorted((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return <div>{sorted.map(renderUser)}</div> +} +``` + +**Why this matters in React:** + +1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only + +2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior + +**Browser support: fallback for older browsers** + +```typescript +// Fallback for older browsers +const sorted = [...items].sort((a, b) => a.value - b.value) +``` + +`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator: + +**Other immutable array methods:** + +- `.toSorted()` - immutable sort + +- `.toReversed()` - immutable reverse + +- `.toSpliced()` - immutable splice + +- `.with()` - immutable element replacement + +--- + +## 8. Advanced Patterns + +**Impact: LOW** + +Advanced patterns for specific cases that require careful implementation. + +### 8.1 Store Event Handlers in Refs + +**Impact: LOW (stable subscriptions)** + +Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes. + +**Incorrect: re-subscribes on every render** + +```tsx +function useWindowEvent(event: string, handler: () => void) { + useEffect(() => { + window.addEventListener(event, handler) + return () => window.removeEventListener(event, handler) + }, [event, handler]) +} +``` + +**Correct: stable subscription** + +```tsx +import { useEffectEvent } from 'react' + +function useWindowEvent(event: string, handler: () => void) { + const onEvent = useEffectEvent(handler) + + useEffect(() => { + window.addEventListener(event, onEvent) + return () => window.removeEventListener(event, onEvent) + }, [event]) +} +``` + +**Alternative: use `useEffectEvent` if you're on latest React:** + +`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler. + +### 8.2 useLatest for Stable Callback Refs + +**Impact: LOW (prevents effect re-runs)** + +Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures. + +**Implementation:** + +```typescript +function useLatest<T>(value: T) { + const ref = useRef(value) + useEffect(() => { + ref.current = value + }, [value]) + return ref +} +``` + +**Incorrect: effect re-runs on every callback change** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + + useEffect(() => { + const timeout = setTimeout(() => onSearch(query), 300) + return () => clearTimeout(timeout) + }, [query, onSearch]) +} +``` + +**Correct: stable effect, fresh callback** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + const onSearchRef = useLatest(onSearch) + + useEffect(() => { + const timeout = setTimeout(() => onSearchRef.current(query), 300) + return () => clearTimeout(timeout) + }, [query]) +} +``` + +--- + +## References + +1. [https://react.dev](https://react.dev) +2. [https://nextjs.org](https://nextjs.org) +3. [https://swr.vercel.app](https://swr.vercel.app) +4. [https://github.com/shuding/better-all](https://github.com/shuding/better-all) +5. [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) +6. [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) +7. [https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast) diff --git a/.agent/skills/react-best-practices/README.md b/.agent/skills/react-best-practices/README.md new file mode 100644 index 0000000..f283e1c --- /dev/null +++ b/.agent/skills/react-best-practices/README.md @@ -0,0 +1,123 @@ +# React Best Practices + +A structured repository for creating and maintaining React Best Practices optimized for agents and LLMs. + +## Structure + +- `rules/` - Individual rule files (one per rule) + - `_sections.md` - Section metadata (titles, impacts, descriptions) + - `_template.md` - Template for creating new rules + - `area-description.md` - Individual rule files +- `src/` - Build scripts and utilities +- `metadata.json` - Document metadata (version, organization, abstract) +- __`AGENTS.md`__ - Compiled output (generated) +- __`test-cases.json`__ - Test cases for LLM evaluation (generated) + +## Getting Started + +1. Install dependencies: + ```bash + pnpm install + ``` + +2. Build AGENTS.md from rules: + ```bash + pnpm build + ``` + +3. Validate rule files: + ```bash + pnpm validate + ``` + +4. Extract test cases: + ```bash + pnpm extract-tests + ``` + +## Creating a New Rule + +1. Copy `rules/_template.md` to `rules/area-description.md` +2. Choose the appropriate area prefix: + - `async-` for Eliminating Waterfalls (Section 1) + - `bundle-` for Bundle Size Optimization (Section 2) + - `server-` for Server-Side Performance (Section 3) + - `client-` for Client-Side Data Fetching (Section 4) + - `rerender-` for Re-render Optimization (Section 5) + - `rendering-` for Rendering Performance (Section 6) + - `js-` for JavaScript Performance (Section 7) + - `advanced-` for Advanced Patterns (Section 8) +3. Fill in the frontmatter and content +4. Ensure you have clear examples with explanations +5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json + +## Rule File Structure + +Each rule file should follow this structure: + +```markdown +--- +title: Rule Title Here +impact: MEDIUM +impactDescription: Optional description +tags: tag1, tag2, tag3 +--- + +## Rule Title Here + +Brief explanation of the rule and why it matters. + +**Incorrect (description of what's wrong):** + +```typescript +// Bad code example +``` + +**Correct (description of what's right):** + +```typescript +// Good code example +``` + +Optional explanatory text after examples. + +Reference: [Link](https://example.com) + +## File Naming Convention + +- Files starting with `_` are special (excluded from build) +- Rule files: `area-description.md` (e.g., `async-parallel.md`) +- Section is automatically inferred from filename prefix +- Rules are sorted alphabetically by title within each section +- IDs (e.g., 1.1, 1.2) are auto-generated during build + +## Impact Levels + +- `CRITICAL` - Highest priority, major performance gains +- `HIGH` - Significant performance improvements +- `MEDIUM-HIGH` - Moderate-high gains +- `MEDIUM` - Moderate performance improvements +- `LOW-MEDIUM` - Low-medium gains +- `LOW` - Incremental improvements + +## Scripts + +- `pnpm build` - Compile rules into AGENTS.md +- `pnpm validate` - Validate all rule files +- `pnpm extract-tests` - Extract test cases for LLM evaluation +- `pnpm dev` - Build and validate + +## Contributing + +When adding or modifying rules: + +1. Use the correct filename prefix for your section +2. Follow the `_template.md` structure +3. Include clear bad/good examples with explanations +4. Add appropriate tags +5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json +6. Rules are automatically sorted by title - no need to manage numbers! + +## Acknowledgments + +Originally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com). diff --git a/.agent/skills/react-best-practices/SKILL.md b/.agent/skills/react-best-practices/SKILL.md new file mode 100644 index 0000000..9a3f7c7 --- /dev/null +++ b/.agent/skills/react-best-practices/SKILL.md @@ -0,0 +1,61 @@ +--- +name: astro-best-practices +description: Core performance and architectural guidelines for Astro applications. Focuses on the Islands Architecture, SSG, and Asset Optimization to ensure lightning-fast websites. Triggers on any task involving Astro components, layouts, pages, or performance audits. +--- + +# Astro Best Practices + +A guide to building extremely performant and modern websites with **Astro**. These rules are prioritized by their impact on **Core Web Vitals** and **Developer Experience**. + +## When to Apply + +Reference these guidelines when: +- Creating new Astro pages or layouts. +- Deciding which UI components to use (React vs. Astro). +- Optimizing for SEO and PageSpeed. +- Implementing hydration strategies (`client:*` directives). +- Configuring global site settings. + +## Rule Categories + +### 1. Minimal JavaScript (CRITICAL) +- **Astro Components First**: Use `.astro` components for any static UI. If it doesn't need client-side state, it doesn't need React. +- **Islands Architecture**: Only use React components for interactive "Islands". +- **Opt-out of Hydration**: Never use a `client:*` directive unless interactivity is actually required. + +### 2. Hydration Strategies (CRITICAL) +- **Lazy Hydration**: Use `client:visible` for components below the fold (e.g., Footer, scroll animations). +- **Prioritize TTI**: Use `client:idle` for low-priority scripts (e.g., chat widgets). +- **Initial Load**: Use `client:load` ONLY for essential interactive elements in the viewport (e.g., Main Menu). + +### 3. Static Site Generation (SSG) +- **Pre-rendering**: Use SSG for all marketing and content pages. Avoid SSR unless strictly necessary for dynamic data. +- **Data Fetching**: Fetch data at build time inside the Astro `---` block. Do not fetch data inside React islands if it can be passed as a prop from the Astro parent. + +### 4. SEO & Metadata +- **Global BaseHead**: Use a single common component in all layouts for meta tags, titles, and icons. +- **Canonical URLs**: Always include a canonical URL tag to avoid duplicate content issues. +- **Alt Text**: Mandatory `alt` text for all images. + +### 5. Asset Optimization (HIGH) +- **Astro Image Component**: Use the built-in `<Image />` component for automatic format conversion (WebP/AVIF) and resizing. +- **Font Hoisting**: Use local fonts and `font-display: swap` to prevent FOIT (Flash of Invisible Text). + +### 6. i18n & Routing +- **Locale Routing**: Structure pages as `src/pages/[lang]/index.astro`. +- **Locale Discovery**: Use the `Astro.preferredLocale` or extract it from the URL. +- **Fallbacks**: Always provide a fallback language for untranslated content. + +## Anti-Patterns (Immediate Rejection) + +❌ Using React for a header/footer that doesn't have complex interactivity. +❌ Not using the Astro `<Image />` component. +❌ Missing page titles or meta descriptions. +❌ Over-hydrating: Putting the entire page inside a single React component. + +## Summary Checklist +- [ ] Is this UI static? (If yes, use `.astro`) +- [ ] Are all images optimized? +- [ ] Do all pages have unique metadata? +- [ ] Are below-the-fold components hydrated with `client:visible`? +- [ ] Is all text translatable? diff --git a/.agent/skills/react-best-practices/metadata.json b/.agent/skills/react-best-practices/metadata.json new file mode 100644 index 0000000..fcb4107 --- /dev/null +++ b/.agent/skills/react-best-practices/metadata.json @@ -0,0 +1,15 @@ +{ + "version": "0.1.0", + "organization": "Vercel Engineering", + "date": "January 2026", + "abstract": "Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.", + "references": [ + "https://react.dev", + "https://nextjs.org", + "https://swr.vercel.app", + "https://github.com/shuding/better-all", + "https://github.com/isaacs/node-lru-cache", + "https://vercel.com/blog/how-we-optimized-package-imports-in-next-js", + "https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast" + ] +} diff --git a/.agent/skills/react-best-practices/rules/_sections.md b/.agent/skills/react-best-practices/rules/_sections.md new file mode 100644 index 0000000..4d20c14 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/_sections.md @@ -0,0 +1,46 @@ +# Sections + +This file defines all sections, their ordering, impact levels, and descriptions. +The section ID (in parentheses) is the filename prefix used to group rules. + +--- + +## 1. Eliminating Waterfalls (async) + +**Impact:** CRITICAL +**Description:** Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. + +## 2. Bundle Size Optimization (bundle) + +**Impact:** CRITICAL +**Description:** Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. + +## 3. Server-Side Performance (server) + +**Impact:** HIGH +**Description:** Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times. + +## 4. Client-Side Data Fetching (client) + +**Impact:** MEDIUM-HIGH +**Description:** Automatic deduplication and efficient data fetching patterns reduce redundant network requests. + +## 5. Re-render Optimization (rerender) + +**Impact:** MEDIUM +**Description:** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. + +## 6. Rendering Performance (rendering) + +**Impact:** MEDIUM +**Description:** Optimizing the rendering process reduces the work the browser needs to do. + +## 7. JavaScript Performance (js) + +**Impact:** LOW-MEDIUM +**Description:** Micro-optimizations for hot paths can add up to meaningful improvements. + +## 8. Advanced Patterns (advanced) + +**Impact:** LOW +**Description:** Advanced patterns for specific cases that require careful implementation. diff --git a/.agent/skills/react-best-practices/rules/_template.md b/.agent/skills/react-best-practices/rules/_template.md new file mode 100644 index 0000000..1e9e707 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/_template.md @@ -0,0 +1,28 @@ +--- +title: Rule Title Here +impact: MEDIUM +impactDescription: Optional description of impact (e.g., "20-50% improvement") +tags: tag1, tag2 +--- + +## Rule Title Here + +**Impact: MEDIUM (optional impact description)** + +Brief explanation of the rule and why it matters. This should be clear and concise, explaining the performance implications. + +**Incorrect (description of what's wrong):** + +```typescript +// Bad code example here +const bad = example() +``` + +**Correct (description of what's right):** + +```typescript +// Good code example here +const good = example() +``` + +Reference: [Link to documentation or resource](https://example.com) diff --git a/.agent/skills/react-best-practices/rules/advanced-event-handler-refs.md b/.agent/skills/react-best-practices/rules/advanced-event-handler-refs.md new file mode 100644 index 0000000..3a45152 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/advanced-event-handler-refs.md @@ -0,0 +1,55 @@ +--- +title: Store Event Handlers in Refs +impact: LOW +impactDescription: stable subscriptions +tags: advanced, hooks, refs, event-handlers, optimization +--- + +## Store Event Handlers in Refs + +Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes. + +**Incorrect (re-subscribes on every render):** + +```tsx +function useWindowEvent(event: string, handler: () => void) { + useEffect(() => { + window.addEventListener(event, handler) + return () => window.removeEventListener(event, handler) + }, [event, handler]) +} +``` + +**Correct (stable subscription):** + +```tsx +function useWindowEvent(event: string, handler: () => void) { + const handlerRef = useRef(handler) + useEffect(() => { + handlerRef.current = handler + }, [handler]) + + useEffect(() => { + const listener = () => handlerRef.current() + window.addEventListener(event, listener) + return () => window.removeEventListener(event, listener) + }, [event]) +} +``` + +**Alternative: use `useEffectEvent` if you're on latest React:** + +```tsx +import { useEffectEvent } from 'react' + +function useWindowEvent(event: string, handler: () => void) { + const onEvent = useEffectEvent(handler) + + useEffect(() => { + window.addEventListener(event, onEvent) + return () => window.removeEventListener(event, onEvent) + }, [event]) +} +``` + +`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler. diff --git a/.agent/skills/react-best-practices/rules/advanced-use-latest.md b/.agent/skills/react-best-practices/rules/advanced-use-latest.md new file mode 100644 index 0000000..3facdf2 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/advanced-use-latest.md @@ -0,0 +1,49 @@ +--- +title: useLatest for Stable Callback Refs +impact: LOW +impactDescription: prevents effect re-runs +tags: advanced, hooks, useLatest, refs, optimization +--- + +## useLatest for Stable Callback Refs + +Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures. + +**Implementation:** + +```typescript +function useLatest<T>(value: T) { + const ref = useRef(value) + useEffect(() => { + ref.current = value + }, [value]) + return ref +} +``` + +**Incorrect (effect re-runs on every callback change):** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + + useEffect(() => { + const timeout = setTimeout(() => onSearch(query), 300) + return () => clearTimeout(timeout) + }, [query, onSearch]) +} +``` + +**Correct (stable effect, fresh callback):** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + const onSearchRef = useLatest(onSearch) + + useEffect(() => { + const timeout = setTimeout(() => onSearchRef.current(query), 300) + return () => clearTimeout(timeout) + }, [query]) +} +``` diff --git a/.agent/skills/react-best-practices/rules/async-api-routes.md b/.agent/skills/react-best-practices/rules/async-api-routes.md new file mode 100644 index 0000000..6feda1e --- /dev/null +++ b/.agent/skills/react-best-practices/rules/async-api-routes.md @@ -0,0 +1,38 @@ +--- +title: Prevent Waterfall Chains in API Routes +impact: CRITICAL +impactDescription: 2-10× improvement +tags: api-routes, server-actions, waterfalls, parallelization +--- + +## Prevent Waterfall Chains in API Routes + +In API routes and Server Actions, start independent operations immediately, even if you don't await them yet. + +**Incorrect (config waits for auth, data waits for both):** + +```typescript +export async function GET(request: Request) { + const session = await auth() + const config = await fetchConfig() + const data = await fetchData(session.user.id) + return Response.json({ data, config }) +} +``` + +**Correct (auth and config start immediately):** + +```typescript +export async function GET(request: Request) { + const sessionPromise = auth() + const configPromise = fetchConfig() + const session = await sessionPromise + const [config, data] = await Promise.all([ + configPromise, + fetchData(session.user.id) + ]) + return Response.json({ data, config }) +} +``` + +For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization). diff --git a/.agent/skills/react-best-practices/rules/async-defer-await.md b/.agent/skills/react-best-practices/rules/async-defer-await.md new file mode 100644 index 0000000..ea7082a --- /dev/null +++ b/.agent/skills/react-best-practices/rules/async-defer-await.md @@ -0,0 +1,80 @@ +--- +title: Defer Await Until Needed +impact: HIGH +impactDescription: avoids blocking unused code paths +tags: async, await, conditional, optimization +--- + +## Defer Await Until Needed + +Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. + +**Incorrect (blocks both branches):** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + const userData = await fetchUserData(userId) + + if (skipProcessing) { + // Returns immediately but still waited for userData + return { skipped: true } + } + + // Only this branch uses userData + return processUserData(userData) +} +``` + +**Correct (only blocks when needed):** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + if (skipProcessing) { + // Returns immediately without waiting + return { skipped: true } + } + + // Fetch only when needed + const userData = await fetchUserData(userId) + return processUserData(userData) +} +``` + +**Another example (early return optimization):** + +```typescript +// Incorrect: always fetches permissions +async function updateResource(resourceId: string, userId: string) { + const permissions = await fetchPermissions(userId) + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} + +// Correct: fetches only when needed +async function updateResource(resourceId: string, userId: string) { + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + const permissions = await fetchPermissions(userId) + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} +``` + +This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. diff --git a/.agent/skills/react-best-practices/rules/async-dependencies.md b/.agent/skills/react-best-practices/rules/async-dependencies.md new file mode 100644 index 0000000..fb90d86 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/async-dependencies.md @@ -0,0 +1,36 @@ +--- +title: Dependency-Based Parallelization +impact: CRITICAL +impactDescription: 2-10× improvement +tags: async, parallelization, dependencies, better-all +--- + +## Dependency-Based Parallelization + +For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment. + +**Incorrect (profile waits for config unnecessarily):** + +```typescript +const [user, config] = await Promise.all([ + fetchUser(), + fetchConfig() +]) +const profile = await fetchProfile(user.id) +``` + +**Correct (config and profile run in parallel):** + +```typescript +import { all } from 'better-all' + +const { user, config, profile } = await all({ + async user() { return fetchUser() }, + async config() { return fetchConfig() }, + async profile() { + return fetchProfile((await this.$.user).id) + } +}) +``` + +Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all) diff --git a/.agent/skills/react-best-practices/rules/async-parallel.md b/.agent/skills/react-best-practices/rules/async-parallel.md new file mode 100644 index 0000000..64133f6 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/async-parallel.md @@ -0,0 +1,28 @@ +--- +title: Promise.all() for Independent Operations +impact: CRITICAL +impactDescription: 2-10× improvement +tags: async, parallelization, promises, waterfalls +--- + +## Promise.all() for Independent Operations + +When async operations have no interdependencies, execute them concurrently using `Promise.all()`. + +**Incorrect (sequential execution, 3 round trips):** + +```typescript +const user = await fetchUser() +const posts = await fetchPosts() +const comments = await fetchComments() +``` + +**Correct (parallel execution, 1 round trip):** + +```typescript +const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() +]) +``` diff --git a/.agent/skills/react-best-practices/rules/async-suspense-boundaries.md b/.agent/skills/react-best-practices/rules/async-suspense-boundaries.md new file mode 100644 index 0000000..1fbc05b --- /dev/null +++ b/.agent/skills/react-best-practices/rules/async-suspense-boundaries.md @@ -0,0 +1,99 @@ +--- +title: Strategic Suspense Boundaries +impact: HIGH +impactDescription: faster initial paint +tags: async, suspense, streaming, layout-shift +--- + +## Strategic Suspense Boundaries + +Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads. + +**Incorrect (wrapper blocked by data fetching):** + +```tsx +async function Page() { + const data = await fetchData() // Blocks entire page + + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <div> + <DataDisplay data={data} /> + </div> + <div>Footer</div> + </div> + ) +} +``` + +The entire layout waits for data even though only the middle section needs it. + +**Correct (wrapper shows immediately, data streams in):** + +```tsx +function Page() { + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <div> + <Suspense fallback={<Skeleton />}> + <DataDisplay /> + </Suspense> + </div> + <div>Footer</div> + </div> + ) +} + +async function DataDisplay() { + const data = await fetchData() // Only blocks this component + return <div>{data.content}</div> +} +``` + +Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data. + +**Alternative (share promise across components):** + +```tsx +function Page() { + // Start fetch immediately, but don't await + const dataPromise = fetchData() + + return ( + <div> + <div>Sidebar</div> + <div>Header</div> + <Suspense fallback={<Skeleton />}> + <DataDisplay dataPromise={dataPromise} /> + <DataSummary dataPromise={dataPromise} /> + </Suspense> + <div>Footer</div> + </div> + ) +} + +function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) { + const data = use(dataPromise) // Unwraps the promise + return <div>{data.content}</div> +} + +function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) { + const data = use(dataPromise) // Reuses the same promise + return <div>{data.summary}</div> +} +``` + +Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together. + +**When NOT to use this pattern:** + +- Critical data needed for layout decisions (affects positioning) +- SEO-critical content above the fold +- Small, fast queries where suspense overhead isn't worth it +- When you want to avoid layout shift (loading → content jump) + +**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities. diff --git a/.agent/skills/react-best-practices/rules/bundle-barrel-imports.md b/.agent/skills/react-best-practices/rules/bundle-barrel-imports.md new file mode 100644 index 0000000..ee48f32 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/bundle-barrel-imports.md @@ -0,0 +1,59 @@ +--- +title: Avoid Barrel File Imports +impact: CRITICAL +impactDescription: 200-800ms import cost, slow builds +tags: bundle, imports, tree-shaking, barrel-files, performance +--- + +## Avoid Barrel File Imports + +Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`). + +Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts. + +**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph. + +**Incorrect (imports entire library):** + +```tsx +import { Check, X, Menu } from 'lucide-react' +// Loads 1,583 modules, takes ~2.8s extra in dev +// Runtime cost: 200-800ms on every cold start + +import { Button, TextField } from '@mui/material' +// Loads 2,225 modules, takes ~4.2s extra in dev +``` + +**Correct (imports only what you need):** + +```tsx +import Check from 'lucide-react/dist/esm/icons/check' +import X from 'lucide-react/dist/esm/icons/x' +import Menu from 'lucide-react/dist/esm/icons/menu' +// Loads only 3 modules (~2KB vs ~1MB) + +import Button from '@mui/material/Button' +import TextField from '@mui/material/TextField' +// Loads only what you use +``` + +**Alternative (Next.js 13.5+):** + +```js +// next.config.js - use optimizePackageImports +module.exports = { + experimental: { + optimizePackageImports: ['lucide-react', '@mui/material'] + } +} + +// Then you can keep the ergonomic barrel imports: +import { Check, X, Menu } from 'lucide-react' +// Automatically transformed to direct imports at build time +``` + +Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. + +Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`. + +Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) diff --git a/.agent/skills/react-best-practices/rules/bundle-conditional.md b/.agent/skills/react-best-practices/rules/bundle-conditional.md new file mode 100644 index 0000000..40bd6f9 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/bundle-conditional.md @@ -0,0 +1,31 @@ +--- +title: Conditional Module Loading +impact: HIGH +impactDescription: loads large data only when needed +tags: bundle, conditional-loading, lazy-loading +--- + +## Conditional Module Loading + +Load large data or modules only when a feature is activated. + +**Example (lazy-load animation frames):** + +```tsx +function AnimationPlayer({ enabled }: { enabled: boolean }) { + const [frames, setFrames] = useState<Frame[] | null>(null) + + useEffect(() => { + if (enabled && !frames && typeof window !== 'undefined') { + import('./animation-frames.js') + .then(mod => setFrames(mod.frames)) + .catch(() => setEnabled(false)) + } + }, [enabled, frames]) + + if (!frames) return <Skeleton /> + return <Canvas frames={frames} /> +} +``` + +The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed. diff --git a/.agent/skills/react-best-practices/rules/bundle-defer-third-party.md b/.agent/skills/react-best-practices/rules/bundle-defer-third-party.md new file mode 100644 index 0000000..db041d1 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/bundle-defer-third-party.md @@ -0,0 +1,49 @@ +--- +title: Defer Non-Critical Third-Party Libraries +impact: MEDIUM +impactDescription: loads after hydration +tags: bundle, third-party, analytics, defer +--- + +## Defer Non-Critical Third-Party Libraries + +Analytics, logging, and error tracking don't block user interaction. Load them after hydration. + +**Incorrect (blocks initial bundle):** + +```tsx +import { Analytics } from '@vercel/analytics/react' + +export default function RootLayout({ children }) { + return ( + <html> + <body> + {children} + <Analytics /> + </body> + </html> + ) +} +``` + +**Correct (loads after hydration):** + +```tsx +import dynamic from 'next/dynamic' + +const Analytics = dynamic( + () => import('@vercel/analytics/react').then(m => m.Analytics), + { ssr: false } +) + +export default function RootLayout({ children }) { + return ( + <html> + <body> + {children} + <Analytics /> + </body> + </html> + ) +} +``` diff --git a/.agent/skills/react-best-practices/rules/bundle-dynamic-imports.md b/.agent/skills/react-best-practices/rules/bundle-dynamic-imports.md new file mode 100644 index 0000000..60b6269 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/bundle-dynamic-imports.md @@ -0,0 +1,35 @@ +--- +title: Dynamic Imports for Heavy Components +impact: CRITICAL +impactDescription: directly affects TTI and LCP +tags: bundle, dynamic-import, code-splitting, next-dynamic +--- + +## Dynamic Imports for Heavy Components + +Use `next/dynamic` to lazy-load large components not needed on initial render. + +**Incorrect (Monaco bundles with main chunk ~300KB):** + +```tsx +import { MonacoEditor } from './monaco-editor' + +function CodePanel({ code }: { code: string }) { + return <MonacoEditor value={code} /> +} +``` + +**Correct (Monaco loads on demand):** + +```tsx +import dynamic from 'next/dynamic' + +const MonacoEditor = dynamic( + () => import('./monaco-editor').then(m => m.MonacoEditor), + { ssr: false } +) + +function CodePanel({ code }: { code: string }) { + return <MonacoEditor value={code} /> +} +``` diff --git a/.agent/skills/react-best-practices/rules/bundle-preload.md b/.agent/skills/react-best-practices/rules/bundle-preload.md new file mode 100644 index 0000000..7000504 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/bundle-preload.md @@ -0,0 +1,50 @@ +--- +title: Preload Based on User Intent +impact: MEDIUM +impactDescription: reduces perceived latency +tags: bundle, preload, user-intent, hover +--- + +## Preload Based on User Intent + +Preload heavy bundles before they're needed to reduce perceived latency. + +**Example (preload on hover/focus):** + +```tsx +function EditorButton({ onClick }: { onClick: () => void }) { + const preload = () => { + if (typeof window !== 'undefined') { + void import('./monaco-editor') + } + } + + return ( + <button + onMouseEnter={preload} + onFocus={preload} + onClick={onClick} + > + Open Editor + </button> + ) +} +``` + +**Example (preload when feature flag is enabled):** + +```tsx +function FlagsProvider({ children, flags }: Props) { + useEffect(() => { + if (flags.editorEnabled && typeof window !== 'undefined') { + void import('./monaco-editor').then(mod => mod.init()) + } + }, [flags.editorEnabled]) + + return <FlagsContext.Provider value={flags}> + {children} + </FlagsContext.Provider> +} +``` + +The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed. diff --git a/.agent/skills/react-best-practices/rules/client-event-listeners.md b/.agent/skills/react-best-practices/rules/client-event-listeners.md new file mode 100644 index 0000000..aad4ae9 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/client-event-listeners.md @@ -0,0 +1,74 @@ +--- +title: Deduplicate Global Event Listeners +impact: LOW +impactDescription: single listener for N components +tags: client, swr, event-listeners, subscription +--- + +## Deduplicate Global Event Listeners + +Use `useSWRSubscription()` to share global event listeners across component instances. + +**Incorrect (N instances = N listeners):** + +```tsx +function useKeyboardShortcut(key: string, callback: () => void) { + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.metaKey && e.key === key) { + callback() + } + } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }, [key, callback]) +} +``` + +When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener. + +**Correct (N instances = 1 listener):** + +```tsx +import useSWRSubscription from 'swr/subscription' + +// Module-level Map to track callbacks per key +const keyCallbacks = new Map<string, Set<() => void>>() + +function useKeyboardShortcut(key: string, callback: () => void) { + // Register this callback in the Map + useEffect(() => { + if (!keyCallbacks.has(key)) { + keyCallbacks.set(key, new Set()) + } + keyCallbacks.get(key)!.add(callback) + + return () => { + const set = keyCallbacks.get(key) + if (set) { + set.delete(callback) + if (set.size === 0) { + keyCallbacks.delete(key) + } + } + } + }, [key, callback]) + + useSWRSubscription('global-keydown', () => { + const handler = (e: KeyboardEvent) => { + if (e.metaKey && keyCallbacks.has(e.key)) { + keyCallbacks.get(e.key)!.forEach(cb => cb()) + } + } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }) +} + +function Profile() { + // Multiple shortcuts will share the same listener + useKeyboardShortcut('p', () => { /* ... */ }) + useKeyboardShortcut('k', () => { /* ... */ }) + // ... +} +``` diff --git a/.agent/skills/react-best-practices/rules/client-swr-dedup.md b/.agent/skills/react-best-practices/rules/client-swr-dedup.md new file mode 100644 index 0000000..2a430f2 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/client-swr-dedup.md @@ -0,0 +1,56 @@ +--- +title: Use SWR for Automatic Deduplication +impact: MEDIUM-HIGH +impactDescription: automatic deduplication +tags: client, swr, deduplication, data-fetching +--- + +## Use SWR for Automatic Deduplication + +SWR enables request deduplication, caching, and revalidation across component instances. + +**Incorrect (no deduplication, each instance fetches):** + +```tsx +function UserList() { + const [users, setUsers] = useState([]) + useEffect(() => { + fetch('/api/users') + .then(r => r.json()) + .then(setUsers) + }, []) +} +``` + +**Correct (multiple instances share one request):** + +```tsx +import useSWR from 'swr' + +function UserList() { + const { data: users } = useSWR('/api/users', fetcher) +} +``` + +**For immutable data:** + +```tsx +import { useImmutableSWR } from '@/lib/swr' + +function StaticContent() { + const { data } = useImmutableSWR('/api/config', fetcher) +} +``` + +**For mutations:** + +```tsx +import { useSWRMutation } from 'swr/mutation' + +function UpdateButton() { + const { trigger } = useSWRMutation('/api/user', updateUser) + return <button onClick={() => trigger()}>Update</button> +} +``` + +Reference: [https://swr.vercel.app](https://swr.vercel.app) diff --git a/.agent/skills/react-best-practices/rules/js-batch-dom-css.md b/.agent/skills/react-best-practices/rules/js-batch-dom-css.md new file mode 100644 index 0000000..92a3b63 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-batch-dom-css.md @@ -0,0 +1,82 @@ +--- +title: Batch DOM CSS Changes +impact: MEDIUM +impactDescription: reduces reflows/repaints +tags: javascript, dom, css, performance, reflow +--- + +## Batch DOM CSS Changes + +Avoid changing styles one property at a time. Group multiple CSS changes together via classes or `cssText` to minimize browser reflows. + +**Incorrect (multiple reflows):** + +```typescript +function updateElementStyles(element: HTMLElement) { + // Each line triggers a reflow + element.style.width = '100px' + element.style.height = '200px' + element.style.backgroundColor = 'blue' + element.style.border = '1px solid black' +} +``` + +**Correct (add class - single reflow):** + +```typescript +// CSS file +.highlighted-box { + width: 100px; + height: 200px; + background-color: blue; + border: 1px solid black; +} + +// JavaScript +function updateElementStyles(element: HTMLElement) { + element.classList.add('highlighted-box') +} +``` + +**Correct (change cssText - single reflow):** + +```typescript +function updateElementStyles(element: HTMLElement) { + element.style.cssText = ` + width: 100px; + height: 200px; + background-color: blue; + border: 1px solid black; + ` +} +``` + +**React example:** + +```tsx +// Incorrect: changing styles one by one +function Box({ isHighlighted }: { isHighlighted: boolean }) { + const ref = useRef<HTMLDivElement>(null) + + useEffect(() => { + if (ref.current && isHighlighted) { + ref.current.style.width = '100px' + ref.current.style.height = '200px' + ref.current.style.backgroundColor = 'blue' + } + }, [isHighlighted]) + + return <div ref={ref}>Content</div> +} + +// Correct: toggle class +function Box({ isHighlighted }: { isHighlighted: boolean }) { + return ( + <div className={isHighlighted ? 'highlighted-box' : ''}> + Content + </div> + ) +} +``` + +Prefer CSS classes over inline styles when possible. Classes are cached by the browser and provide better separation of concerns. diff --git a/.agent/skills/react-best-practices/rules/js-cache-function-results.md b/.agent/skills/react-best-practices/rules/js-cache-function-results.md new file mode 100644 index 0000000..180f8ac --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-cache-function-results.md @@ -0,0 +1,80 @@ +--- +title: Cache Repeated Function Calls +impact: MEDIUM +impactDescription: avoid redundant computation +tags: javascript, cache, memoization, performance +--- + +## Cache Repeated Function Calls + +Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render. + +**Incorrect (redundant computation):** + +```typescript +function ProjectList({ projects }: { projects: Project[] }) { + return ( + <div> + {projects.map(project => { + // slugify() called 100+ times for same project names + const slug = slugify(project.name) + + return <ProjectCard key={project.id} slug={slug} /> + })} + </div> + ) +} +``` + +**Correct (cached results):** + +```typescript +// Module-level cache +const slugifyCache = new Map<string, string>() + +function cachedSlugify(text: string): string { + if (slugifyCache.has(text)) { + return slugifyCache.get(text)! + } + const result = slugify(text) + slugifyCache.set(text, result) + return result +} + +function ProjectList({ projects }: { projects: Project[] }) { + return ( + <div> + {projects.map(project => { + // Computed only once per unique project name + const slug = cachedSlugify(project.name) + + return <ProjectCard key={project.id} slug={slug} /> + })} + </div> + ) +} +``` + +**Simpler pattern for single-value functions:** + +```typescript +let isLoggedInCache: boolean | null = null + +function isLoggedIn(): boolean { + if (isLoggedInCache !== null) { + return isLoggedInCache + } + + isLoggedInCache = document.cookie.includes('auth=') + return isLoggedInCache +} + +// Clear cache when auth changes +function onAuthChange() { + isLoggedInCache = null +} +``` + +Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components. + +Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast) diff --git a/.agent/skills/react-best-practices/rules/js-cache-property-access.md b/.agent/skills/react-best-practices/rules/js-cache-property-access.md new file mode 100644 index 0000000..39eec90 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-cache-property-access.md @@ -0,0 +1,28 @@ +--- +title: Cache Property Access in Loops +impact: LOW-MEDIUM +impactDescription: reduces lookups +tags: javascript, loops, optimization, caching +--- + +## Cache Property Access in Loops + +Cache object property lookups in hot paths. + +**Incorrect (3 lookups × N iterations):** + +```typescript +for (let i = 0; i < arr.length; i++) { + process(obj.config.settings.value) +} +``` + +**Correct (1 lookup total):** + +```typescript +const value = obj.config.settings.value +const len = arr.length +for (let i = 0; i < len; i++) { + process(value) +} +``` diff --git a/.agent/skills/react-best-practices/rules/js-cache-storage.md b/.agent/skills/react-best-practices/rules/js-cache-storage.md new file mode 100644 index 0000000..aa4a30c --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-cache-storage.md @@ -0,0 +1,70 @@ +--- +title: Cache Storage API Calls +impact: LOW-MEDIUM +impactDescription: reduces expensive I/O +tags: javascript, localStorage, storage, caching, performance +--- + +## Cache Storage API Calls + +`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory. + +**Incorrect (reads storage on every call):** + +```typescript +function getTheme() { + return localStorage.getItem('theme') ?? 'light' +} +// Called 10 times = 10 storage reads +``` + +**Correct (Map cache):** + +```typescript +const storageCache = new Map<string, string | null>() + +function getLocalStorage(key: string) { + if (!storageCache.has(key)) { + storageCache.set(key, localStorage.getItem(key)) + } + return storageCache.get(key) +} + +function setLocalStorage(key: string, value: string) { + localStorage.setItem(key, value) + storageCache.set(key, value) // keep cache in sync +} +``` + +Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components. + +**Cookie caching:** + +```typescript +let cookieCache: Record<string, string> | null = null + +function getCookie(name: string) { + if (!cookieCache) { + cookieCache = Object.fromEntries( + document.cookie.split('; ').map(c => c.split('=')) + ) + } + return cookieCache[name] +} +``` + +**Important (invalidate on external changes):** + +If storage can change externally (another tab, server-set cookies), invalidate cache: + +```typescript +window.addEventListener('storage', (e) => { + if (e.key) storageCache.delete(e.key) +}) + +document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + storageCache.clear() + } +}) +``` diff --git a/.agent/skills/react-best-practices/rules/js-combine-iterations.md b/.agent/skills/react-best-practices/rules/js-combine-iterations.md new file mode 100644 index 0000000..044d017 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-combine-iterations.md @@ -0,0 +1,32 @@ +--- +title: Combine Multiple Array Iterations +impact: LOW-MEDIUM +impactDescription: reduces iterations +tags: javascript, arrays, loops, performance +--- + +## Combine Multiple Array Iterations + +Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop. + +**Incorrect (3 iterations):** + +```typescript +const admins = users.filter(u => u.isAdmin) +const testers = users.filter(u => u.isTester) +const inactive = users.filter(u => !u.isActive) +``` + +**Correct (1 iteration):** + +```typescript +const admins: User[] = [] +const testers: User[] = [] +const inactive: User[] = [] + +for (const user of users) { + if (user.isAdmin) admins.push(user) + if (user.isTester) testers.push(user) + if (!user.isActive) inactive.push(user) +} +``` diff --git a/.agent/skills/react-best-practices/rules/js-early-exit.md b/.agent/skills/react-best-practices/rules/js-early-exit.md new file mode 100644 index 0000000..f46cb89 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-early-exit.md @@ -0,0 +1,50 @@ +--- +title: Early Return from Functions +impact: LOW-MEDIUM +impactDescription: avoids unnecessary computation +tags: javascript, functions, optimization, early-return +--- + +## Early Return from Functions + +Return early when result is determined to skip unnecessary processing. + +**Incorrect (processes all items even after finding answer):** + +```typescript +function validateUsers(users: User[]) { + let hasError = false + let errorMessage = '' + + for (const user of users) { + if (!user.email) { + hasError = true + errorMessage = 'Email required' + } + if (!user.name) { + hasError = true + errorMessage = 'Name required' + } + // Continues checking all users even after error found + } + + return hasError ? { valid: false, error: errorMessage } : { valid: true } +} +``` + +**Correct (returns immediately on first error):** + +```typescript +function validateUsers(users: User[]) { + for (const user of users) { + if (!user.email) { + return { valid: false, error: 'Email required' } + } + if (!user.name) { + return { valid: false, error: 'Name required' } + } + } + + return { valid: true } +} +``` diff --git a/.agent/skills/react-best-practices/rules/js-hoist-regexp.md b/.agent/skills/react-best-practices/rules/js-hoist-regexp.md new file mode 100644 index 0000000..dae3fef --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-hoist-regexp.md @@ -0,0 +1,45 @@ +--- +title: Hoist RegExp Creation +impact: LOW-MEDIUM +impactDescription: avoids recreation +tags: javascript, regexp, optimization, memoization +--- + +## Hoist RegExp Creation + +Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`. + +**Incorrect (new RegExp every render):** + +```tsx +function Highlighter({ text, query }: Props) { + const regex = new RegExp(`(${query})`, 'gi') + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)}</> +} +``` + +**Correct (memoize or hoist):** + +```tsx +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + +function Highlighter({ text, query }: Props) { + const regex = useMemo( + () => new RegExp(`(${escapeRegex(query)})`, 'gi'), + [query] + ) + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)}</> +} +``` + +**Warning (global regex has mutable state):** + +Global regex (`/g`) has mutable `lastIndex` state: + +```typescript +const regex = /foo/g +regex.test('foo') // true, lastIndex = 3 +regex.test('foo') // false, lastIndex = 0 +``` diff --git a/.agent/skills/react-best-practices/rules/js-index-maps.md b/.agent/skills/react-best-practices/rules/js-index-maps.md new file mode 100644 index 0000000..9d357a0 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-index-maps.md @@ -0,0 +1,37 @@ +--- +title: Build Index Maps for Repeated Lookups +impact: LOW-MEDIUM +impactDescription: 1M ops to 2K ops +tags: javascript, map, indexing, optimization, performance +--- + +## Build Index Maps for Repeated Lookups + +Multiple `.find()` calls by the same key should use a Map. + +**Incorrect (O(n) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + return orders.map(order => ({ + ...order, + user: users.find(u => u.id === order.userId) + })) +} +``` + +**Correct (O(1) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + const userById = new Map(users.map(u => [u.id, u])) + + return orders.map(order => ({ + ...order, + user: userById.get(order.userId) + })) +} +``` + +Build map once (O(n)), then all lookups are O(1). +For 1000 orders × 1000 users: 1M ops → 2K ops. diff --git a/.agent/skills/react-best-practices/rules/js-length-check-first.md b/.agent/skills/react-best-practices/rules/js-length-check-first.md new file mode 100644 index 0000000..6c38625 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-length-check-first.md @@ -0,0 +1,49 @@ +--- +title: Early Length Check for Array Comparisons +impact: MEDIUM-HIGH +impactDescription: avoids expensive operations when lengths differ +tags: javascript, arrays, performance, optimization, comparison +--- + +## Early Length Check for Array Comparisons + +When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal. + +In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops). + +**Incorrect (always runs expensive comparison):** + +```typescript +function hasChanges(current: string[], original: string[]) { + // Always sorts and joins, even when lengths differ + return current.sort().join() !== original.sort().join() +} +``` + +Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings. + +**Correct (O(1) length check first):** + +```typescript +function hasChanges(current: string[], original: string[]) { + // Early return if lengths differ + if (current.length !== original.length) { + return true + } + // Only sort/join when lengths match + const currentSorted = current.toSorted() + const originalSorted = original.toSorted() + for (let i = 0; i < currentSorted.length; i++) { + if (currentSorted[i] !== originalSorted[i]) { + return true + } + } + return false +} +``` + +This new approach is more efficient because: +- It avoids the overhead of sorting and joining the arrays when lengths differ +- It avoids consuming memory for the joined strings (especially important for large arrays) +- It avoids mutating the original arrays +- It returns early when a difference is found diff --git a/.agent/skills/react-best-practices/rules/js-min-max-loop.md b/.agent/skills/react-best-practices/rules/js-min-max-loop.md new file mode 100644 index 0000000..02caec5 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-min-max-loop.md @@ -0,0 +1,82 @@ +--- +title: Use Loop for Min/Max Instead of Sort +impact: LOW +impactDescription: O(n) instead of O(n log n) +tags: javascript, arrays, performance, sorting, algorithms +--- + +## Use Loop for Min/Max Instead of Sort + +Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower. + +**Incorrect (O(n log n) - sort to find latest):** + +```typescript +interface Project { + id: string + name: string + updatedAt: number +} + +function getLatestProject(projects: Project[]) { + const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt) + return sorted[0] +} +``` + +Sorts the entire array just to find the maximum value. + +**Incorrect (O(n log n) - sort for oldest and newest):** + +```typescript +function getOldestAndNewest(projects: Project[]) { + const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt) + return { oldest: sorted[0], newest: sorted[sorted.length - 1] } +} +``` + +Still sorts unnecessarily when only min/max are needed. + +**Correct (O(n) - single loop):** + +```typescript +function getLatestProject(projects: Project[]) { + if (projects.length === 0) return null + + let latest = projects[0] + + for (let i = 1; i < projects.length; i++) { + if (projects[i].updatedAt > latest.updatedAt) { + latest = projects[i] + } + } + + return latest +} + +function getOldestAndNewest(projects: Project[]) { + if (projects.length === 0) return { oldest: null, newest: null } + + let oldest = projects[0] + let newest = projects[0] + + for (let i = 1; i < projects.length; i++) { + if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i] + if (projects[i].updatedAt > newest.updatedAt) newest = projects[i] + } + + return { oldest, newest } +} +``` + +Single pass through the array, no copying, no sorting. + +**Alternative (Math.min/Math.max for small arrays):** + +```typescript +const numbers = [5, 2, 8, 1, 9] +const min = Math.min(...numbers) +const max = Math.max(...numbers) +``` + +This works for small arrays but can be slower for very large arrays due to spread operator limitations. Use the loop approach for reliability. diff --git a/.agent/skills/react-best-practices/rules/js-set-map-lookups.md b/.agent/skills/react-best-practices/rules/js-set-map-lookups.md new file mode 100644 index 0000000..680a489 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-set-map-lookups.md @@ -0,0 +1,24 @@ +--- +title: Use Set/Map for O(1) Lookups +impact: LOW-MEDIUM +impactDescription: O(n) to O(1) +tags: javascript, set, map, data-structures, performance +--- + +## Use Set/Map for O(1) Lookups + +Convert arrays to Set/Map for repeated membership checks. + +**Incorrect (O(n) per check):** + +```typescript +const allowedIds = ['a', 'b', 'c', ...] +items.filter(item => allowedIds.includes(item.id)) +``` + +**Correct (O(1) per check):** + +```typescript +const allowedIds = new Set(['a', 'b', 'c', ...]) +items.filter(item => allowedIds.has(item.id)) +``` diff --git a/.agent/skills/react-best-practices/rules/js-tosorted-immutable.md b/.agent/skills/react-best-practices/rules/js-tosorted-immutable.md new file mode 100644 index 0000000..eae8b3f --- /dev/null +++ b/.agent/skills/react-best-practices/rules/js-tosorted-immutable.md @@ -0,0 +1,57 @@ +--- +title: Use toSorted() Instead of sort() for Immutability +impact: MEDIUM-HIGH +impactDescription: prevents mutation bugs in React state +tags: javascript, arrays, immutability, react, state, mutation +--- + +## Use toSorted() Instead of sort() for Immutability + +`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation. + +**Incorrect (mutates original array):** + +```typescript +function UserList({ users }: { users: User[] }) { + // Mutates the users prop array! + const sorted = useMemo( + () => users.sort((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return <div>{sorted.map(renderUser)}</div> +} +``` + +**Correct (creates new array):** + +```typescript +function UserList({ users }: { users: User[] }) { + // Creates new sorted array, original unchanged + const sorted = useMemo( + () => users.toSorted((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return <div>{sorted.map(renderUser)}</div> +} +``` + +**Why this matters in React:** + +1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only +2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior + +**Browser support (fallback for older browsers):** + +`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator: + +```typescript +// Fallback for older browsers +const sorted = [...items].sort((a, b) => a.value - b.value) +``` + +**Other immutable array methods:** + +- `.toSorted()` - immutable sort +- `.toReversed()` - immutable reverse +- `.toSpliced()` - immutable splice +- `.with()` - immutable element replacement diff --git a/.agent/skills/react-best-practices/rules/rendering-activity.md b/.agent/skills/react-best-practices/rules/rendering-activity.md new file mode 100644 index 0000000..c957a49 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-activity.md @@ -0,0 +1,26 @@ +--- +title: Use Activity Component for Show/Hide +impact: MEDIUM +impactDescription: preserves state/DOM +tags: rendering, activity, visibility, state-preservation +--- + +## Use Activity Component for Show/Hide + +Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility. + +**Usage:** + +```tsx +import { Activity } from 'react' + +function Dropdown({ isOpen }: Props) { + return ( + <Activity mode={isOpen ? 'visible' : 'hidden'}> + <ExpensiveMenu /> + </Activity> + ) +} +``` + +Avoids expensive re-renders and state loss. diff --git a/.agent/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md b/.agent/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md new file mode 100644 index 0000000..646744c --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md @@ -0,0 +1,47 @@ +--- +title: Animate SVG Wrapper Instead of SVG Element +impact: LOW +impactDescription: enables hardware acceleration +tags: rendering, svg, css, animation, performance +--- + +## Animate SVG Wrapper Instead of SVG Element + +Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead. + +**Incorrect (animating SVG directly - no hardware acceleration):** + +```tsx +function LoadingSpinner() { + return ( + <svg + className="animate-spin" + width="24" + height="24" + viewBox="0 0 24 24" + > + <circle cx="12" cy="12" r="10" stroke="currentColor" /> + </svg> + ) +} +``` + +**Correct (animating wrapper div - hardware accelerated):** + +```tsx +function LoadingSpinner() { + return ( + <div className="animate-spin"> + <svg + width="24" + height="24" + viewBox="0 0 24 24" + > + <circle cx="12" cy="12" r="10" stroke="currentColor" /> + </svg> + </div> + ) +} +``` + +This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations. diff --git a/.agent/skills/react-best-practices/rules/rendering-conditional-render.md b/.agent/skills/react-best-practices/rules/rendering-conditional-render.md new file mode 100644 index 0000000..7e866f5 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-conditional-render.md @@ -0,0 +1,40 @@ +--- +title: Use Explicit Conditional Rendering +impact: LOW +impactDescription: prevents rendering 0 or NaN +tags: rendering, conditional, jsx, falsy-values +--- + +## Use Explicit Conditional Rendering + +Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render. + +**Incorrect (renders "0" when count is 0):** + +```tsx +function Badge({ count }: { count: number }) { + return ( + <div> + {count && <span className="badge">{count}</span>} + </div> + ) +} + +// When count = 0, renders: <div>0</div> +// When count = 5, renders: <div><span class="badge">5</span></div> +``` + +**Correct (renders nothing when count is 0):** + +```tsx +function Badge({ count }: { count: number }) { + return ( + <div> + {count > 0 ? <span className="badge">{count}</span> : null} + </div> + ) +} + +// When count = 0, renders: <div></div> +// When count = 5, renders: <div><span class="badge">5</span></div> +``` diff --git a/.agent/skills/react-best-practices/rules/rendering-content-visibility.md b/.agent/skills/react-best-practices/rules/rendering-content-visibility.md new file mode 100644 index 0000000..aa66563 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-content-visibility.md @@ -0,0 +1,38 @@ +--- +title: CSS content-visibility for Long Lists +impact: HIGH +impactDescription: faster initial render +tags: rendering, css, content-visibility, long-lists +--- + +## CSS content-visibility for Long Lists + +Apply `content-visibility: auto` to defer off-screen rendering. + +**CSS:** + +```css +.message-item { + content-visibility: auto; + contain-intrinsic-size: 0 80px; +} +``` + +**Example:** + +```tsx +function MessageList({ messages }: { messages: Message[] }) { + return ( + <div className="overflow-y-auto h-screen"> + {messages.map(msg => ( + <div key={msg.id} className="message-item"> + <Avatar user={msg.author} /> + <div>{msg.content}</div> + </div> + ))} + </div> + ) +} +``` + +For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render). diff --git a/.agent/skills/react-best-practices/rules/rendering-hoist-jsx.md b/.agent/skills/react-best-practices/rules/rendering-hoist-jsx.md new file mode 100644 index 0000000..32d2f3f --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-hoist-jsx.md @@ -0,0 +1,46 @@ +--- +title: Hoist Static JSX Elements +impact: LOW +impactDescription: avoids re-creation +tags: rendering, jsx, static, optimization +--- + +## Hoist Static JSX Elements + +Extract static JSX outside components to avoid re-creation. + +**Incorrect (recreates element every render):** + +```tsx +function LoadingSkeleton() { + return <div className="animate-pulse h-20 bg-gray-200" /> +} + +function Container() { + return ( + <div> + {loading && <LoadingSkeleton />} + </div> + ) +} +``` + +**Correct (reuses same element):** + +```tsx +const loadingSkeleton = ( + <div className="animate-pulse h-20 bg-gray-200" /> +) + +function Container() { + return ( + <div> + {loading && loadingSkeleton} + </div> + ) +} +``` + +This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render. + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary. diff --git a/.agent/skills/react-best-practices/rules/rendering-hydration-no-flicker.md b/.agent/skills/react-best-practices/rules/rendering-hydration-no-flicker.md new file mode 100644 index 0000000..5cf0e79 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-hydration-no-flicker.md @@ -0,0 +1,82 @@ +--- +title: Prevent Hydration Mismatch Without Flickering +impact: MEDIUM +impactDescription: avoids visual flicker and hydration errors +tags: rendering, ssr, hydration, localStorage, flicker +--- + +## Prevent Hydration Mismatch Without Flickering + +When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates. + +**Incorrect (breaks SSR):** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + // localStorage is not available on server - throws error + const theme = localStorage.getItem('theme') || 'light' + + return ( + <div className={theme}> + {children} + </div> + ) +} +``` + +Server-side rendering will fail because `localStorage` is undefined. + +**Incorrect (visual flickering):** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + const [theme, setTheme] = useState('light') + + useEffect(() => { + // Runs after hydration - causes visible flash + const stored = localStorage.getItem('theme') + if (stored) { + setTheme(stored) + } + }, []) + + return ( + <div className={theme}> + {children} + </div> + ) +} +``` + +Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content. + +**Correct (no flicker, no hydration mismatch):** + +```tsx +function ThemeWrapper({ children }: { children: ReactNode }) { + return ( + <> + <div id="theme-wrapper"> + {children} + </div> + <script + dangerouslySetInnerHTML={{ + __html: ` + (function() { + try { + var theme = localStorage.getItem('theme') || 'light'; + var el = document.getElementById('theme-wrapper'); + if (el) el.className = theme; + } catch (e) {} + })(); + `, + }} + /> + </> + ) +} +``` + +The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch. + +This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values. diff --git a/.agent/skills/react-best-practices/rules/rendering-svg-precision.md b/.agent/skills/react-best-practices/rules/rendering-svg-precision.md new file mode 100644 index 0000000..6d77128 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rendering-svg-precision.md @@ -0,0 +1,28 @@ +--- +title: Optimize SVG Precision +impact: LOW +impactDescription: reduces file size +tags: rendering, svg, optimization, svgo +--- + +## Optimize SVG Precision + +Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered. + +**Incorrect (excessive precision):** + +```svg +<path d="M 10.293847 20.847362 L 30.938472 40.192837" /> +``` + +**Correct (1 decimal place):** + +```svg +<path d="M 10.3 20.8 L 30.9 40.2" /> +``` + +**Automate with SVGO:** + +```bash +npx svgo --precision=1 --multipass icon.svg +``` diff --git a/.agent/skills/react-best-practices/rules/rerender-defer-reads.md b/.agent/skills/react-best-practices/rules/rerender-defer-reads.md new file mode 100644 index 0000000..e867c95 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-defer-reads.md @@ -0,0 +1,39 @@ +--- +title: Defer State Reads to Usage Point +impact: MEDIUM +impactDescription: avoids unnecessary subscriptions +tags: rerender, searchParams, localStorage, optimization +--- + +## Defer State Reads to Usage Point + +Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. + +**Incorrect (subscribes to all searchParams changes):** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const searchParams = useSearchParams() + + const handleShare = () => { + const ref = searchParams.get('ref') + shareChat(chatId, { ref }) + } + + return <button onClick={handleShare}>Share</button> +} +``` + +**Correct (reads on demand, no subscription):** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const handleShare = () => { + const params = new URLSearchParams(window.location.search) + const ref = params.get('ref') + shareChat(chatId, { ref }) + } + + return <button onClick={handleShare}>Share</button> +} +``` diff --git a/.agent/skills/react-best-practices/rules/rerender-dependencies.md b/.agent/skills/react-best-practices/rules/rerender-dependencies.md new file mode 100644 index 0000000..47a4d92 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-dependencies.md @@ -0,0 +1,45 @@ +--- +title: Narrow Effect Dependencies +impact: LOW +impactDescription: minimizes effect re-runs +tags: rerender, useEffect, dependencies, optimization +--- + +## Narrow Effect Dependencies + +Specify primitive dependencies instead of objects to minimize effect re-runs. + +**Incorrect (re-runs on any user field change):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user]) +``` + +**Correct (re-runs only when id changes):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user.id]) +``` + +**For derived state, compute outside effect:** + +```tsx +// Incorrect: runs on width=767, 766, 765... +useEffect(() => { + if (width < 768) { + enableMobileMode() + } +}, [width]) + +// Correct: runs only on boolean transition +const isMobile = width < 768 +useEffect(() => { + if (isMobile) { + enableMobileMode() + } +}, [isMobile]) +``` diff --git a/.agent/skills/react-best-practices/rules/rerender-derived-state.md b/.agent/skills/react-best-practices/rules/rerender-derived-state.md new file mode 100644 index 0000000..a15177c --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-derived-state.md @@ -0,0 +1,29 @@ +--- +title: Subscribe to Derived State +impact: MEDIUM +impactDescription: reduces re-render frequency +tags: rerender, derived-state, media-query, optimization +--- + +## Subscribe to Derived State + +Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. + +**Incorrect (re-renders on every pixel change):** + +```tsx +function Sidebar() { + const width = useWindowWidth() // updates continuously + const isMobile = width < 768 + return <nav className={isMobile ? 'mobile' : 'desktop'}> +} +``` + +**Correct (re-renders only when boolean changes):** + +```tsx +function Sidebar() { + const isMobile = useMediaQuery('(max-width: 767px)') + return <nav className={isMobile ? 'mobile' : 'desktop'}> +} +``` diff --git a/.agent/skills/react-best-practices/rules/rerender-functional-setstate.md b/.agent/skills/react-best-practices/rules/rerender-functional-setstate.md new file mode 100644 index 0000000..b004ef4 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-functional-setstate.md @@ -0,0 +1,74 @@ +--- +title: Use Functional setState Updates +impact: MEDIUM +impactDescription: prevents stale closures and unnecessary callback recreations +tags: react, hooks, useState, useCallback, callbacks, closures +--- + +## Use Functional setState Updates + +When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references. + +**Incorrect (requires state as dependency):** + +```tsx +function TodoList() { + const [items, setItems] = useState(initialItems) + + // Callback must depend on items, recreated on every items change + const addItems = useCallback((newItems: Item[]) => { + setItems([...items, ...newItems]) + }, [items]) // ❌ items dependency causes recreations + + // Risk of stale closure if dependency is forgotten + const removeItem = useCallback((id: string) => { + setItems(items.filter(item => item.id !== id)) + }, []) // ❌ Missing items dependency - will use stale items! + + return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} /> +} +``` + +The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value. + +**Correct (stable callbacks, no stale closures):** + +```tsx +function TodoList() { + const [items, setItems] = useState(initialItems) + + // Stable callback, never recreated + const addItems = useCallback((newItems: Item[]) => { + setItems(curr => [...curr, ...newItems]) + }, []) // ✅ No dependencies needed + + // Always uses latest state, no stale closure risk + const removeItem = useCallback((id: string) => { + setItems(curr => curr.filter(item => item.id !== id)) + }, []) // ✅ Safe and stable + + return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} /> +} +``` + +**Benefits:** + +1. **Stable callback references** - Callbacks don't need to be recreated when state changes +2. **No stale closures** - Always operates on the latest state value +3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks +4. **Prevents bugs** - Eliminates the most common source of React closure bugs + +**When to use functional updates:** + +- Any setState that depends on the current state value +- Inside useCallback/useMemo when state is needed +- Event handlers that reference state +- Async operations that update state + +**When direct updates are fine:** + +- Setting state to a static value: `setCount(0)` +- Setting state from props/arguments only: `setName(newName)` +- State doesn't depend on previous value + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs. diff --git a/.agent/skills/react-best-practices/rules/rerender-lazy-state-init.md b/.agent/skills/react-best-practices/rules/rerender-lazy-state-init.md new file mode 100644 index 0000000..4ecb350 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-lazy-state-init.md @@ -0,0 +1,58 @@ +--- +title: Use Lazy State Initialization +impact: MEDIUM +impactDescription: wasted computation on every render +tags: react, hooks, useState, performance, initialization +--- + +## Use Lazy State Initialization + +Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once. + +**Incorrect (runs on every render):** + +```tsx +function FilteredList({ items }: { items: Item[] }) { + // buildSearchIndex() runs on EVERY render, even after initialization + const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items)) + const [query, setQuery] = useState('') + + // When query changes, buildSearchIndex runs again unnecessarily + return <SearchResults index={searchIndex} query={query} /> +} + +function UserProfile() { + // JSON.parse runs on every render + const [settings, setSettings] = useState( + JSON.parse(localStorage.getItem('settings') || '{}') + ) + + return <SettingsForm settings={settings} onChange={setSettings} /> +} +``` + +**Correct (runs only once):** + +```tsx +function FilteredList({ items }: { items: Item[] }) { + // buildSearchIndex() runs ONLY on initial render + const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items)) + const [query, setQuery] = useState('') + + return <SearchResults index={searchIndex} query={query} /> +} + +function UserProfile() { + // JSON.parse runs only on initial render + const [settings, setSettings] = useState(() => { + const stored = localStorage.getItem('settings') + return stored ? JSON.parse(stored) : {} + }) + + return <SettingsForm settings={settings} onChange={setSettings} /> +} +``` + +Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations. + +For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary. diff --git a/.agent/skills/react-best-practices/rules/rerender-memo.md b/.agent/skills/react-best-practices/rules/rerender-memo.md new file mode 100644 index 0000000..f8982ab --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-memo.md @@ -0,0 +1,44 @@ +--- +title: Extract to Memoized Components +impact: MEDIUM +impactDescription: enables early returns +tags: rerender, memo, useMemo, optimization +--- + +## Extract to Memoized Components + +Extract expensive work into memoized components to enable early returns before computation. + +**Incorrect (computes avatar even when loading):** + +```tsx +function Profile({ user, loading }: Props) { + const avatar = useMemo(() => { + const id = computeAvatarId(user) + return <Avatar id={id} /> + }, [user]) + + if (loading) return <Skeleton /> + return <div>{avatar}</div> +} +``` + +**Correct (skips computation when loading):** + +```tsx +const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { + const id = useMemo(() => computeAvatarId(user), [user]) + return <Avatar id={id} /> +}) + +function Profile({ user, loading }: Props) { + if (loading) return <Skeleton /> + return ( + <div> + <UserAvatar user={user} /> + </div> + ) +} +``` + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders. diff --git a/.agent/skills/react-best-practices/rules/rerender-transitions.md b/.agent/skills/react-best-practices/rules/rerender-transitions.md new file mode 100644 index 0000000..d99f43f --- /dev/null +++ b/.agent/skills/react-best-practices/rules/rerender-transitions.md @@ -0,0 +1,40 @@ +--- +title: Use Transitions for Non-Urgent Updates +impact: MEDIUM +impactDescription: maintains UI responsiveness +tags: rerender, transitions, startTransition, performance +--- + +## Use Transitions for Non-Urgent Updates + +Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness. + +**Incorrect (blocks UI on every scroll):** + +```tsx +function ScrollTracker() { + const [scrollY, setScrollY] = useState(0) + useEffect(() => { + const handler = () => setScrollY(window.scrollY) + window.addEventListener('scroll', handler, { passive: true }) + return () => window.removeEventListener('scroll', handler) + }, []) +} +``` + +**Correct (non-blocking updates):** + +```tsx +import { startTransition } from 'react' + +function ScrollTracker() { + const [scrollY, setScrollY] = useState(0) + useEffect(() => { + const handler = () => { + startTransition(() => setScrollY(window.scrollY)) + } + window.addEventListener('scroll', handler, { passive: true }) + return () => window.removeEventListener('scroll', handler) + }, []) +} +``` diff --git a/.agent/skills/react-best-practices/rules/server-after-nonblocking.md b/.agent/skills/react-best-practices/rules/server-after-nonblocking.md new file mode 100644 index 0000000..e8f5b26 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/server-after-nonblocking.md @@ -0,0 +1,73 @@ +--- +title: Use after() for Non-Blocking Operations +impact: MEDIUM +impactDescription: faster response times +tags: server, async, logging, analytics, side-effects +--- + +## Use after() for Non-Blocking Operations + +Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response. + +**Incorrect (blocks response):** + +```tsx +import { logUserAction } from '@/app/utils' + +export async function POST(request: Request) { + // Perform mutation + await updateDatabase(request) + + // Logging blocks the response + const userAgent = request.headers.get('user-agent') || 'unknown' + await logUserAction({ userAgent }) + + return new Response(JSON.stringify({ status: 'success' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) +} +``` + +**Correct (non-blocking):** + +```tsx +import { after } from 'next/server' +import { headers, cookies } from 'next/headers' +import { logUserAction } from '@/app/utils' + +export async function POST(request: Request) { + // Perform mutation + await updateDatabase(request) + + // Log after response is sent + after(async () => { + const userAgent = (await headers()).get('user-agent') || 'unknown' + const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous' + + logUserAction({ sessionCookie, userAgent }) + }) + + return new Response(JSON.stringify({ status: 'success' }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }) +} +``` + +The response is sent immediately while logging happens in the background. + +**Common use cases:** + +- Analytics tracking +- Audit logging +- Sending notifications +- Cache invalidation +- Cleanup tasks + +**Important notes:** + +- `after()` runs even if the response fails or redirects +- Works in Server Actions, Route Handlers, and Server Components + +Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after) diff --git a/.agent/skills/react-best-practices/rules/server-cache-lru.md b/.agent/skills/react-best-practices/rules/server-cache-lru.md new file mode 100644 index 0000000..ef6938a --- /dev/null +++ b/.agent/skills/react-best-practices/rules/server-cache-lru.md @@ -0,0 +1,41 @@ +--- +title: Cross-Request LRU Caching +impact: HIGH +impactDescription: caches across requests +tags: server, cache, lru, cross-request +--- + +## Cross-Request LRU Caching + +`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache. + +**Implementation:** + +```typescript +import { LRUCache } from 'lru-cache' + +const cache = new LRUCache<string, any>({ + max: 1000, + ttl: 5 * 60 * 1000 // 5 minutes +}) + +export async function getUser(id: string) { + const cached = cache.get(id) + if (cached) return cached + + const user = await db.user.findUnique({ where: { id } }) + cache.set(id, user) + return user +} + +// Request 1: DB query, result cached +// Request 2: cache hit, no DB query +``` + +Use when sequential user actions hit multiple endpoints needing the same data within seconds. + +**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis. + +**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching. + +Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) diff --git a/.agent/skills/react-best-practices/rules/server-cache-react.md b/.agent/skills/react-best-practices/rules/server-cache-react.md new file mode 100644 index 0000000..fa49e0e --- /dev/null +++ b/.agent/skills/react-best-practices/rules/server-cache-react.md @@ -0,0 +1,26 @@ +--- +title: Per-Request Deduplication with React.cache() +impact: MEDIUM +impactDescription: deduplicates within request +tags: server, cache, react-cache, deduplication +--- + +## Per-Request Deduplication with React.cache() + +Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most. + +**Usage:** + +```typescript +import { cache } from 'react' + +export const getCurrentUser = cache(async () => { + const session = await auth() + if (!session?.user?.id) return null + return await db.user.findUnique({ + where: { id: session.user.id } + }) +}) +``` + +Within a single request, multiple calls to `getCurrentUser()` execute the query only once. diff --git a/.agent/skills/react-best-practices/rules/server-parallel-fetching.md b/.agent/skills/react-best-practices/rules/server-parallel-fetching.md new file mode 100644 index 0000000..5261f08 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/server-parallel-fetching.md @@ -0,0 +1,79 @@ +--- +title: Parallel Data Fetching with Component Composition +impact: CRITICAL +impactDescription: eliminates server-side waterfalls +tags: server, rsc, parallel-fetching, composition +--- + +## Parallel Data Fetching with Component Composition + +React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching. + +**Incorrect (Sidebar waits for Page's fetch to complete):** + +```tsx +export default async function Page() { + const header = await fetchHeader() + return ( + <div> + <div>{header}</div> + <Sidebar /> + </div> + ) +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} +``` + +**Correct (both fetch simultaneously):** + +```tsx +async function Header() { + const data = await fetchHeader() + return <div>{data}</div> +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} + +export default function Page() { + return ( + <div> + <Header /> + <Sidebar /> + </div> + ) +} +``` + +**Alternative with children prop:** + +```tsx +async function Layout({ children }: { children: ReactNode }) { + const header = await fetchHeader() + return ( + <div> + <div>{header}</div> + {children} + </div> + ) +} + +async function Sidebar() { + const items = await fetchSidebarItems() + return <nav>{items.map(renderItem)}</nav> +} + +export default function Page() { + return ( + <Layout> + <Sidebar /> + </Layout> + ) +} +``` diff --git a/.agent/skills/react-best-practices/rules/server-serialization.md b/.agent/skills/react-best-practices/rules/server-serialization.md new file mode 100644 index 0000000..39c5c41 --- /dev/null +++ b/.agent/skills/react-best-practices/rules/server-serialization.md @@ -0,0 +1,38 @@ +--- +title: Minimize Serialization at RSC Boundaries +impact: HIGH +impactDescription: reduces data transfer size +tags: server, rsc, serialization, props +--- + +## Minimize Serialization at RSC Boundaries + +The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses. + +**Incorrect (serializes all 50 fields):** + +```tsx +async function Page() { + const user = await fetchUser() // 50 fields + return <Profile user={user} /> +} + +'use client' +function Profile({ user }: { user: User }) { + return <div>{user.name}</div> // uses 1 field +} +``` + +**Correct (serializes only 1 field):** + +```tsx +async function Page() { + const user = await fetchUser() + return <Profile name={user.name} /> +} + +'use client' +function Profile({ name }: { name: string }) { + return <div>{name}</div> +} +``` diff --git a/.agent/skills/shadcn-ui/SKILL.md b/.agent/skills/shadcn-ui/SKILL.md new file mode 100644 index 0000000..1b5f5e1 --- /dev/null +++ b/.agent/skills/shadcn-ui/SKILL.md @@ -0,0 +1,57 @@ +--- +name: shadcn-ui +description: Guides the implementation, customization, and usage of shadcn/ui components within an Astro + React environment. Ensures accessible and consistent UI primitives aligned with JODAZ DEV branding. +--- + +# shadcn/ui Integration & Usage + +You are an expert in using **shadcn/ui** to build high-quality, accessible, and performant user interfaces. Your goal is to ensure that all UI components are implemented according to best practices within an **Astro** architecture. + +## When to use this skill +- When you need to add a new UI component (Button, Dialog, Input, etc.). +- When you need to customize an existing shadcn component. +- When you are creating complex UI interactions that require Radix primitives. + +## Necessary Inputs +- The component name to be added or modified. +- The desired customization or functionality. +- Knowledge of the existing `tailwind.config.ts` and `index.css` structure. + +## Workflow + +1) **Check for Existence**: Verify if the component already exists in `src/components/ui/`. +2) **Install/Add**: If missing, use the shadcn CLI: `npx shadcn-ui@latest add <component-name>`. +3) **Refine & Style**: Apply JODAZ DEV brand styles. +4) **Integration (The Island Rule)**: When using shadcn components in `.astro` files, you **MUST** ensure they are wrapped in a React component and use the appropriate `client:` directive (e.g., `client:load` for dropdowns, `client:visible` for dialogs). + +## Instructions + +### 1. Installation & CLI Usage +- Use `pnpm dlx shadcn-ui@latest add [component]` to add new components. +- Components are located in `src/components/ui/`. + +### 2. Styling Standards +- **Source of Truth**: The component file itself. +- **Theme Alignment**: Ensure components default to the **JODAZ DEV** aesthetic: + - Stone backgrounds, Navy text, and Blue primary accents. + - Buttons should use the custom variants (e.g., `hero`, `outline-hero`). + +### 3. Astro & Hydration (CRITICAL) +- Most shadcn components (Tabs, Accordion, Dialog, etc.) **will not work** without a hydration directive in Astro. +- Always use `client:load` or `client:visible` when importing these into an Astro page. +- Nested shadcn components should also follow React patterns. + +### 4. Code Structure +- Use the `cn()` utility for all class merging. +- Ensure all text within components is compatible with i18n (pass through props or use key-based translations). + +## Output (exact format) +- A brief confirmation of the component added/modified. +- The updated or new component code. +- Example usage in an `.astro` file with the correct `client:` directive. + +--- + +## Resources +- [Best Practices](resources/best-practices.md) +- [Troubleshooting](resources/troubleshooting.md) diff --git a/.agent/skills/shadcn-ui/resources/best-practices.md b/.agent/skills/shadcn-ui/resources/best-practices.md new file mode 100644 index 0000000..22a7ea2 --- /dev/null +++ b/.agent/skills/shadcn-ui/resources/best-practices.md @@ -0,0 +1,48 @@ +# shadcn/ui Best Practices + +Follow these guidelines to maintain a clean and reliable UI layer using shadcn/ui. + +--- + +## 1. Customization Boundary +- **Keep it global**: If a change (like a default border color) applies to all buttons, edit the `src/components/ui/button.tsx` directly. +- **Keep it local**: If a change is specific to a single feature, pass a `className` prop instead of modifying the global component. + +## 2. Using the `cn` Utility +Always use the `cn` utility to merge classes. This ensures that Tailwind utility conflicts are resolved correctly (via `tailwind-merge`) and that conditional joining is clean (via `clsx`). + +```tsx +import { cn } from "@/lib/utils"; + +export function CustomCard({ className, children }) { + return ( + <div className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}> + {children} + </div> + ); +} +``` + +## 3. Server vs. Client Components +- Many shadcn components (like Dialog, Popover, Tabs) rely on Radix UI, which uses React state. These **must** be Client Components. +- When using these in a Server Component, create a wrapper Client Component or push the interactivity down into a smaller interactive leaf. + +## 4. Primitive Names +- Do not rename exported primitives (e.g., `DialogTrigger`, `DialogHeader`). Familiarity with the shadcn/Radix naming convention is critical for team speed. + +## 5. Input Accessibility +- Always use the `Label` component with `Input` and ensure `htmlFor`/`id` match. + +```tsx +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +export function EmailField() { + return ( + <div className="grid w-full max-w-sm items-center gap-1.5"> + <Label htmlFor="email">Email</Label> + <Input type="email" id="email" placeholder="Email" /> + </div> + ); +} +``` diff --git a/.agent/skills/shadcn-ui/resources/troubleshooting.md b/.agent/skills/shadcn-ui/resources/troubleshooting.md new file mode 100644 index 0000000..d08a06a --- /dev/null +++ b/.agent/skills/shadcn-ui/resources/troubleshooting.md @@ -0,0 +1,20 @@ +# shadcn/ui Troubleshooting + +Common issues and how to solve them. + +--- + +## 1. CLI Installation Fails +- Ensure you have a `components.json` file in the root directory. If not, run `npx shadcn-ui@latest init` first. +- If `pnpm` errors occur, try using `pnpm dlx shadcn-ui@latest add [component]`. + +## 2. Styling Doesn't Apply +- Verify that `tailwind.config.js` includes the folder where your components live. It should have `@/components/**/*.{ts,tsx}` in the `content` array. +- Check if your `globals.css` contains the necessary CSS variables for `primary`, `background`, etc. + +## 3. Hydration Mismatch +- Issues often arise with `Dialog` or `Popover` when their content is rendered differently on server and client. +- Ensure that the trigger and content are consistent, and wrap interactive roots in a `useEffect` based mounting check if absolutely necessary. + +## 4. Re-running the CLI +- If you refactored a component extensively, running `add` again will **overwrite** your changes. Always back up customizations before re-running the `add` command on an existing component. diff --git a/.agent/skills/skill-creator/SKILL.md b/.agent/skills/skill-creator/SKILL.md new file mode 100644 index 0000000..16ac3ef --- /dev/null +++ b/.agent/skills/skill-creator/SKILL.md @@ -0,0 +1,134 @@ +You are an expert at designing Skills for the Antigravity environment. Your goal is to create predictable, reusable, and easy-to-maintain Skills, with a clear folder structure and logic that works well in production. + +Your output MUST ALWAYS include: +1. The skill folder path inside agent/skills/ +2. The complete content of SKILL.md with YAML frontmatter +3. Any additional resources (scripts/resources/examples) only if they provide real value + +1) Minimal Mandatory Structure +Each Skill is created within: +agent/skills/<skill-name>/ + +Inside, at a minimum, there must be: +● SKILL.md (mandatory, logic and rules of the skill) +● resources/ (optional, guides, templates, tokens, examples) +● scripts/ (optional, utilities executed by the skill) +● examples/ (optional, reference implementations) + +Do not create unnecessary files. Keep the structure as simple as possible. + +2) Naming and YAML Rules (SKILL.md) +The SKILL.md file must always start with YAML frontmatter. + +Rules: +● name: short, lowercase, with hyphens. Maximum 40 characters. Ex: plan-video, audit-landing, answer-emails +● description: in English, third person, maximum 220 characters. Must state what it does and when to use it. +● Do not use tool names in the name unless essential. +● Do not include "marketing" in the YAML: keep it operational. + +Template: +--- +name: <skill-name> +description: <brief description in third person> +--- + +3) Writing Principles (so the skill works) +● Clarity over length: better few rules, but very clear. +● No filler: avoid blog-like explanations. The skill is an execution manual. +● Separation of concerns: if there is "style", it goes to a resource. If there are "steps", they go to the workflow. +● Ask for data when missing: if an input is critical, the skill must ask. +● Standardized output: define exactly what format you return (list, table, JSON, markdown). + +4) When it activates (triggers) +In each SKILL.md, include a "When to use this skill" section with clear triggers. + +Examples: +● "when the user asks to create a new skill" +● "when the user repeats a process" +● "when a format standard is needed" +● "when a long prompt needs to be converted into a reusable procedure" + +Triggers must be concrete and easy to recognize. + +5) Recommended Workflow (Plan → Validate → Execute) +For simple skills: +● 3–6 steps maximum. + +For complex skills: +● Divide into phases: Plan, Validation, Execution, Review. +● Include a mini checklist. + +Example of a short checklist: +● Understood the final goal +● Have necessary inputs +● Defined exact output +● Applied constraints +● Reviewed consistency and errors + +6) Levels of Freedom (how "strict" it should be) +The skill must choose the appropriate level according to the task type: +1. High freedom (heuristics): for brainstorming, ideas, alternatives. +2. Medium freedom (templates): for documents, copy, structures. +3. Low freedom (exact steps / commands): for fragile operations, scripts, technical changes. + +Rule: the higher the risk, the more specific the skill must be. + +7) Error Handling and Corrections +Include a short section: +● What to do if the output does not meet the format +● How to ask the user for feedback +● How to iterate without breaking the standard + +Example: +"If the result does not meet the format, go back to step 2, adjust constraints, and re-generate. If there is ambiguity, ask before assuming." + +8) Output Format When You Create a Skill +When the user asks for a skill, respond with this format: + +Folder +agent/skills/<skill-name>/ + +SKILL.md +--- +name: ... +description: ... +--- +# <Skill Title> + +## When to use this skill +- ... +- ... + +## Necessary Inputs +- ... +- ... + +## Workflow +1) ... +2) ... +3) ... + +## Instructions +... + +## Output (exact format) +... + +Optional Resources (only if they add value) +● resources/<file>.md +● scripts/<file>.sh + +9) Examples of Skills You SHOULD Suggest (if fitting) +If the user is creating skills, suggest useful ideas: +● "Style and Brand" Skill +● "Plan Videos" Skill +● "Audit Landing" Skill +● "App Debug" Skill +● "Answer Emails with Tone" Skill + +10) Usage Instructions (for the user) +1. Copy this entire document into a file named: +antigravity-skill-creator.md +2. Upload that file to your Antigravity project/agent +3. From there, to create a new skill, ask: +"Use my document 'antigravity-skill-creator' and build a skill for: <goal>. Give me the folder, the SKILL.md, and necessary resources." diff --git a/.agent/skills/systematic-debugging/CREATION-LOG.md b/.agent/skills/systematic-debugging/CREATION-LOG.md new file mode 100644 index 0000000..024d00a --- /dev/null +++ b/.agent/skills/systematic-debugging/CREATION-LOG.md @@ -0,0 +1,119 @@ +# Creation Log: Systematic Debugging Skill + +Reference example of extracting, structuring, and bulletproofing a critical skill. + +## Source Material + +Extracted debugging framework from `/Users/jesse/.claude/CLAUDE.md`: +- 4-phase systematic process (Investigation → Pattern Analysis → Hypothesis → Implementation) +- Core mandate: ALWAYS find root cause, NEVER fix symptoms +- Rules designed to resist time pressure and rationalization + +## Extraction Decisions + +**What to include:** +- Complete 4-phase framework with all rules +- Anti-shortcuts ("NEVER fix symptom", "STOP and re-analyze") +- Pressure-resistant language ("even if faster", "even if I seem in a hurry") +- Concrete steps for each phase + +**What to leave out:** +- Project-specific context +- Repetitive variations of same rule +- Narrative explanations (condensed to principles) + +## Structure Following skill-creation/SKILL.md + +1. **Rich when_to_use** - Included symptoms and anti-patterns +2. **Type: technique** - Concrete process with steps +3. **Keywords** - "root cause", "symptom", "workaround", "debugging", "investigation" +4. **Flowchart** - Decision point for "fix failed" → re-analyze vs add more fixes +5. **Phase-by-phase breakdown** - Scannable checklist format +6. **Anti-patterns section** - What NOT to do (critical for this skill) + +## Bulletproofing Elements + +Framework designed to resist rationalization under pressure: + +### Language Choices +- "ALWAYS" / "NEVER" (not "should" / "try to") +- "even if faster" / "even if I seem in a hurry" +- "STOP and re-analyze" (explicit pause) +- "Don't skip past" (catches the actual behavior) + +### Structural Defenses +- **Phase 1 required** - Can't skip to implementation +- **Single hypothesis rule** - Forces thinking, prevents shotgun fixes +- **Explicit failure mode** - "IF your first fix doesn't work" with mandatory action +- **Anti-patterns section** - Shows exactly what shortcuts look like + +### Redundancy +- Root cause mandate in overview + when_to_use + Phase 1 + implementation rules +- "NEVER fix symptom" appears 4 times in different contexts +- Each phase has explicit "don't skip" guidance + +## Testing Approach + +Created 4 validation tests following skills/meta/testing-skills-with-subagents: + +### Test 1: Academic Context (No Pressure) +- Simple bug, no time pressure +- **Result:** Perfect compliance, complete investigation + +### Test 2: Time Pressure + Obvious Quick Fix +- User "in a hurry", symptom fix looks easy +- **Result:** Resisted shortcut, followed full process, found real root cause + +### Test 3: Complex System + Uncertainty +- Multi-layer failure, unclear if can find root cause +- **Result:** Systematic investigation, traced through all layers, found source + +### Test 4: Failed First Fix +- Hypothesis doesn't work, temptation to add more fixes +- **Result:** Stopped, re-analyzed, formed new hypothesis (no shotgun) + +**All tests passed.** No rationalizations found. + +## Iterations + +### Initial Version +- Complete 4-phase framework +- Anti-patterns section +- Flowchart for "fix failed" decision + +### Enhancement 1: TDD Reference +- Added link to skills/testing/test-driven-development +- Note explaining TDD's "simplest code" ≠ debugging's "root cause" +- Prevents confusion between methodologies + +## Final Outcome + +Bulletproof skill that: +- ✅ Clearly mandates root cause investigation +- ✅ Resists time pressure rationalization +- ✅ Provides concrete steps for each phase +- ✅ Shows anti-patterns explicitly +- ✅ Tested under multiple pressure scenarios +- ✅ Clarifies relationship to TDD +- ✅ Ready for use + +## Key Insight + +**Most important bulletproofing:** Anti-patterns section showing exact shortcuts that feel justified in the moment. When Claude thinks "I'll just add this one quick fix", seeing that exact pattern listed as wrong creates cognitive friction. + +## Usage Example + +When encountering a bug: +1. Load skill: skills/debugging/systematic-debugging +2. Read overview (10 sec) - reminded of mandate +3. Follow Phase 1 checklist - forced investigation +4. If tempted to skip - see anti-pattern, stop +5. Complete all phases - root cause found + +**Time investment:** 5-10 minutes +**Time saved:** Hours of symptom-whack-a-mole + +--- + +*Created: 2025-10-03* +*Purpose: Reference example for skill extraction and bulletproofing* diff --git a/.agent/skills/systematic-debugging/SKILL.md b/.agent/skills/systematic-debugging/SKILL.md new file mode 100644 index 0000000..111d2a9 --- /dev/null +++ b/.agent/skills/systematic-debugging/SKILL.md @@ -0,0 +1,296 @@ +--- +name: systematic-debugging +description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes +--- + +# Systematic Debugging + +## Overview + +Random fixes waste time and create new bugs. Quick patches mask underlying issues. + +**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure. + +**Violating the letter of this process is violating the spirit of debugging.** + +## The Iron Law + +``` +NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST +``` + +If you haven't completed Phase 1, you cannot propose fixes. + +## When to Use + +Use for ANY technical issue: +- Test failures +- Bugs in production +- Unexpected behavior +- Performance problems +- Build failures +- Integration issues + +**Use this ESPECIALLY when:** +- Under time pressure (emergencies make guessing tempting) +- "Just one quick fix" seems obvious +- You've already tried multiple fixes +- Previous fix didn't work +- You don't fully understand the issue + +**Don't skip when:** +- Issue seems simple (simple bugs have root causes too) +- You're in a hurry (rushing guarantees rework) +- Manager wants it fixed NOW (systematic is faster than thrashing) + +## The Four Phases + +You MUST complete each phase before proceeding to the next. + +### Phase 1: Root Cause Investigation + +**BEFORE attempting ANY fix:** + +1. **Read Error Messages Carefully** + - Don't skip past errors or warnings + - They often contain the exact solution + - Read stack traces completely + - Note line numbers, file paths, error codes + +2. **Reproduce Consistently** + - Can you trigger it reliably? + - What are the exact steps? + - Does it happen every time? + - If not reproducible → gather more data, don't guess + +3. **Check Recent Changes** + - What changed that could cause this? + - Git diff, recent commits + - New dependencies, config changes + - Environmental differences + +4. **Gather Evidence in Multi-Component Systems** + + **WHEN system has multiple components (CI → build → signing, API → service → database):** + + **BEFORE proposing fixes, add diagnostic instrumentation:** + ``` + For EACH component boundary: + - Log what data enters component + - Log what data exits component + - Verify environment/config propagation + - Check state at each layer + + Run once to gather evidence showing WHERE it breaks + THEN analyze evidence to identify failing component + THEN investigate that specific component + ``` + + **Example (multi-layer system):** + ```bash + # Layer 1: Workflow + echo "=== Secrets available in workflow: ===" + echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}" + + # Layer 2: Build script + echo "=== Env vars in build script: ===" + env | grep IDENTITY || echo "IDENTITY not in environment" + + # Layer 3: Signing script + echo "=== Keychain state: ===" + security list-keychains + security find-identity -v + + # Layer 4: Actual signing + codesign --sign "$IDENTITY" --verbose=4 "$APP" + ``` + + **This reveals:** Which layer fails (secrets → workflow ✓, workflow → build ✗) + +5. **Trace Data Flow** + + **WHEN error is deep in call stack:** + + See `root-cause-tracing.md` in this directory for the complete backward tracing technique. + + **Quick version:** + - Where does bad value originate? + - What called this with bad value? + - Keep tracing up until you find the source + - Fix at source, not at symptom + +### Phase 2: Pattern Analysis + +**Find the pattern before fixing:** + +1. **Find Working Examples** + - Locate similar working code in same codebase + - What works that's similar to what's broken? + +2. **Compare Against References** + - If implementing pattern, read reference implementation COMPLETELY + - Don't skim - read every line + - Understand the pattern fully before applying + +3. **Identify Differences** + - What's different between working and broken? + - List every difference, however small + - Don't assume "that can't matter" + +4. **Understand Dependencies** + - What other components does this need? + - What settings, config, environment? + - What assumptions does it make? + +### Phase 3: Hypothesis and Testing + +**Scientific method:** + +1. **Form Single Hypothesis** + - State clearly: "I think X is the root cause because Y" + - Write it down + - Be specific, not vague + +2. **Test Minimally** + - Make the SMALLEST possible change to test hypothesis + - One variable at a time + - Don't fix multiple things at once + +3. **Verify Before Continuing** + - Did it work? Yes → Phase 4 + - Didn't work? Form NEW hypothesis + - DON'T add more fixes on top + +4. **When You Don't Know** + - Say "I don't understand X" + - Don't pretend to know + - Ask for help + - Research more + +### Phase 4: Implementation + +**Fix the root cause, not the symptom:** + +1. **Create Failing Test Case** + - Simplest possible reproduction + - Automated test if possible + - One-off test script if no framework + - MUST have before fixing + - Use the `superpowers:test-driven-development` skill for writing proper failing tests + +2. **Implement Single Fix** + - Address the root cause identified + - ONE change at a time + - No "while I'm here" improvements + - No bundled refactoring + +3. **Verify Fix** + - Test passes now? + - No other tests broken? + - Issue actually resolved? + +4. **If Fix Doesn't Work** + - STOP + - Count: How many fixes have you tried? + - If < 3: Return to Phase 1, re-analyze with new information + - **If ≥ 3: STOP and question the architecture (step 5 below)** + - DON'T attempt Fix #4 without architectural discussion + +5. **If 3+ Fixes Failed: Question Architecture** + + **Pattern indicating architectural problem:** + - Each fix reveals new shared state/coupling/problem in different place + - Fixes require "massive refactoring" to implement + - Each fix creates new symptoms elsewhere + + **STOP and question fundamentals:** + - Is this pattern fundamentally sound? + - Are we "sticking with it through sheer inertia"? + - Should we refactor architecture vs. continue fixing symptoms? + + **Discuss with your human partner before attempting more fixes** + + This is NOT a failed hypothesis - this is a wrong architecture. + +## Red Flags - STOP and Follow Process + +If you catch yourself thinking: +- "Quick fix for now, investigate later" +- "Just try changing X and see if it works" +- "Add multiple changes, run tests" +- "Skip the test, I'll manually verify" +- "It's probably X, let me fix that" +- "I don't fully understand but this might work" +- "Pattern says X but I'll adapt it differently" +- "Here are the main problems: [lists fixes without investigation]" +- Proposing solutions before tracing data flow +- **"One more fix attempt" (when already tried 2+)** +- **Each fix reveals new problem in different place** + +**ALL of these mean: STOP. Return to Phase 1.** + +**If 3+ fixes failed:** Question the architecture (see Phase 4.5) + +## your human partner's Signals You're Doing It Wrong + +**Watch for these redirections:** +- "Is that not happening?" - You assumed without verifying +- "Will it show us...?" - You should have added evidence gathering +- "Stop guessing" - You're proposing fixes without understanding +- "Ultrathink this" - Question fundamentals, not just symptoms +- "We're stuck?" (frustrated) - Your approach isn't working + +**When you see these:** STOP. Return to Phase 1. + +## Common Rationalizations + +| Excuse | Reality | +|--------|---------| +| "Issue is simple, don't need process" | Simple issues have root causes too. Process is fast for simple bugs. | +| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check thrashing. | +| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. | +| "I'll write test after confirming fix works" | Untested fixes don't stick. Test first proves it. | +| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. | +| "Reference too long, I'll adapt the pattern" | Partial understanding guarantees bugs. Read it completely. | +| "I see the problem, let me fix it" | Seeing symptoms ≠ understanding root cause. | +| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. Question pattern, don't fix again. | + +## Quick Reference + +| Phase | Key Activities | Success Criteria | +|-------|---------------|------------------| +| **1. Root Cause** | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY | +| **2. Pattern** | Find working examples, compare | Identify differences | +| **3. Hypothesis** | Form theory, test minimally | Confirmed or new hypothesis | +| **4. Implementation** | Create test, fix, verify | Bug resolved, tests pass | + +## When Process Reveals "No Root Cause" + +If systematic investigation reveals issue is truly environmental, timing-dependent, or external: + +1. You've completed the process +2. Document what you investigated +3. Implement appropriate handling (retry, timeout, error message) +4. Add monitoring/logging for future investigation + +**But:** 95% of "no root cause" cases are incomplete investigation. + +## Supporting Techniques + +These techniques are part of systematic debugging and available in this directory: + +- **`root-cause-tracing.md`** - Trace bugs backward through call stack to find original trigger +- **`defense-in-depth.md`** - Add validation at multiple layers after finding root cause +- **`condition-based-waiting.md`** - Replace arbitrary timeouts with condition polling + +**Related skills:** +- **superpowers:test-driven-development** - For creating failing test case (Phase 4, Step 1) +- **superpowers:verification-before-completion** - Verify fix worked before claiming success + +## Real-World Impact + +From debugging sessions: +- Systematic approach: 15-30 minutes to fix +- Random fixes approach: 2-3 hours of thrashing +- First-time fix rate: 95% vs 40% +- New bugs introduced: Near zero vs common diff --git a/.agent/skills/systematic-debugging/condition-based-waiting-example.ts b/.agent/skills/systematic-debugging/condition-based-waiting-example.ts new file mode 100644 index 0000000..703a06b --- /dev/null +++ b/.agent/skills/systematic-debugging/condition-based-waiting-example.ts @@ -0,0 +1,158 @@ +// Complete implementation of condition-based waiting utilities +// From: Lace test infrastructure improvements (2025-10-03) +// Context: Fixed 15 flaky tests by replacing arbitrary timeouts + +import type { ThreadManager } from '~/threads/thread-manager'; +import type { LaceEvent, LaceEventType } from '~/threads/types'; + +/** + * Wait for a specific event type to appear in thread + * + * @param threadManager - The thread manager to query + * @param threadId - Thread to check for events + * @param eventType - Type of event to wait for + * @param timeoutMs - Maximum time to wait (default 5000ms) + * @returns Promise resolving to the first matching event + * + * Example: + * await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT'); + */ +export function waitForEvent( + threadManager: ThreadManager, + threadId: string, + eventType: LaceEventType, + timeoutMs = 5000 +): Promise<LaceEvent> { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + const check = () => { + const events = threadManager.getEvents(threadId); + const event = events.find((e) => e.type === eventType); + + if (event) { + resolve(event); + } else if (Date.now() - startTime > timeoutMs) { + reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`)); + } else { + setTimeout(check, 10); // Poll every 10ms for efficiency + } + }; + + check(); + }); +} + +/** + * Wait for a specific number of events of a given type + * + * @param threadManager - The thread manager to query + * @param threadId - Thread to check for events + * @param eventType - Type of event to wait for + * @param count - Number of events to wait for + * @param timeoutMs - Maximum time to wait (default 5000ms) + * @returns Promise resolving to all matching events once count is reached + * + * Example: + * // Wait for 2 AGENT_MESSAGE events (initial response + continuation) + * await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2); + */ +export function waitForEventCount( + threadManager: ThreadManager, + threadId: string, + eventType: LaceEventType, + count: number, + timeoutMs = 5000 +): Promise<LaceEvent[]> { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + const check = () => { + const events = threadManager.getEvents(threadId); + const matchingEvents = events.filter((e) => e.type === eventType); + + if (matchingEvents.length >= count) { + resolve(matchingEvents); + } else if (Date.now() - startTime > timeoutMs) { + reject( + new Error( + `Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})` + ) + ); + } else { + setTimeout(check, 10); + } + }; + + check(); + }); +} + +/** + * Wait for an event matching a custom predicate + * Useful when you need to check event data, not just type + * + * @param threadManager - The thread manager to query + * @param threadId - Thread to check for events + * @param predicate - Function that returns true when event matches + * @param description - Human-readable description for error messages + * @param timeoutMs - Maximum time to wait (default 5000ms) + * @returns Promise resolving to the first matching event + * + * Example: + * // Wait for TOOL_RESULT with specific ID + * await waitForEventMatch( + * threadManager, + * agentThreadId, + * (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123', + * 'TOOL_RESULT with id=call_123' + * ); + */ +export function waitForEventMatch( + threadManager: ThreadManager, + threadId: string, + predicate: (event: LaceEvent) => boolean, + description: string, + timeoutMs = 5000 +): Promise<LaceEvent> { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + const check = () => { + const events = threadManager.getEvents(threadId); + const event = events.find(predicate); + + if (event) { + resolve(event); + } else if (Date.now() - startTime > timeoutMs) { + reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`)); + } else { + setTimeout(check, 10); + } + }; + + check(); + }); +} + +// Usage example from actual debugging session: +// +// BEFORE (flaky): +// --------------- +// const messagePromise = agent.sendMessage('Execute tools'); +// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms +// agent.abort(); +// await messagePromise; +// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms +// expect(toolResults.length).toBe(2); // Fails randomly +// +// AFTER (reliable): +// ---------------- +// const messagePromise = agent.sendMessage('Execute tools'); +// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start +// agent.abort(); +// await messagePromise; +// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results +// expect(toolResults.length).toBe(2); // Always succeeds +// +// Result: 60% pass rate → 100%, 40% faster execution diff --git a/.agent/skills/systematic-debugging/condition-based-waiting.md b/.agent/skills/systematic-debugging/condition-based-waiting.md new file mode 100644 index 0000000..70994f7 --- /dev/null +++ b/.agent/skills/systematic-debugging/condition-based-waiting.md @@ -0,0 +1,115 @@ +# Condition-Based Waiting + +## Overview + +Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI. + +**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes. + +## When to Use + +```dot +digraph when_to_use { + "Test uses setTimeout/sleep?" [shape=diamond]; + "Testing timing behavior?" [shape=diamond]; + "Document WHY timeout needed" [shape=box]; + "Use condition-based waiting" [shape=box]; + + "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"]; + "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"]; + "Testing timing behavior?" -> "Use condition-based waiting" [label="no"]; +} +``` + +**Use when:** +- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`) +- Tests are flaky (pass sometimes, fail under load) +- Tests timeout when run in parallel +- Waiting for async operations to complete + +**Don't use when:** +- Testing actual timing behavior (debounce, throttle intervals) +- Always document WHY if using arbitrary timeout + +## Core Pattern + +```typescript +// ❌ BEFORE: Guessing at timing +await new Promise(r => setTimeout(r, 50)); +const result = getResult(); +expect(result).toBeDefined(); + +// ✅ AFTER: Waiting for condition +await waitFor(() => getResult() !== undefined); +const result = getResult(); +expect(result).toBeDefined(); +``` + +## Quick Patterns + +| Scenario | Pattern | +|----------|---------| +| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` | +| Wait for state | `waitFor(() => machine.state === 'ready')` | +| Wait for count | `waitFor(() => items.length >= 5)` | +| Wait for file | `waitFor(() => fs.existsSync(path))` | +| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` | + +## Implementation + +Generic polling function: +```typescript +async function waitFor<T>( + condition: () => T | undefined | null | false, + description: string, + timeoutMs = 5000 +): Promise<T> { + const startTime = Date.now(); + + while (true) { + const result = condition(); + if (result) return result; + + if (Date.now() - startTime > timeoutMs) { + throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`); + } + + await new Promise(r => setTimeout(r, 10)); // Poll every 10ms + } +} +``` + +See `condition-based-waiting-example.ts` in this directory for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session. + +## Common Mistakes + +**❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU +**✅ Fix:** Poll every 10ms + +**❌ No timeout:** Loop forever if condition never met +**✅ Fix:** Always include timeout with clear error + +**❌ Stale data:** Cache state before loop +**✅ Fix:** Call getter inside loop for fresh data + +## When Arbitrary Timeout IS Correct + +```typescript +// Tool ticks every 100ms - need 2 ticks to verify partial output +await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition +await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior +// 200ms = 2 ticks at 100ms intervals - documented and justified +``` + +**Requirements:** +1. First wait for triggering condition +2. Based on known timing (not guessing) +3. Comment explaining WHY + +## Real-World Impact + +From debugging session (2025-10-03): +- Fixed 15 flaky tests across 3 files +- Pass rate: 60% → 100% +- Execution time: 40% faster +- No more race conditions diff --git a/.agent/skills/systematic-debugging/defense-in-depth.md b/.agent/skills/systematic-debugging/defense-in-depth.md new file mode 100644 index 0000000..e248335 --- /dev/null +++ b/.agent/skills/systematic-debugging/defense-in-depth.md @@ -0,0 +1,122 @@ +# Defense-in-Depth Validation + +## Overview + +When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks. + +**Core principle:** Validate at EVERY layer data passes through. Make the bug structurally impossible. + +## Why Multiple Layers + +Single validation: "We fixed the bug" +Multiple layers: "We made the bug impossible" + +Different layers catch different cases: +- Entry validation catches most bugs +- Business logic catches edge cases +- Environment guards prevent context-specific dangers +- Debug logging helps when other layers fail + +## The Four Layers + +### Layer 1: Entry Point Validation +**Purpose:** Reject obviously invalid input at API boundary + +```typescript +function createProject(name: string, workingDirectory: string) { + if (!workingDirectory || workingDirectory.trim() === '') { + throw new Error('workingDirectory cannot be empty'); + } + if (!existsSync(workingDirectory)) { + throw new Error(`workingDirectory does not exist: ${workingDirectory}`); + } + if (!statSync(workingDirectory).isDirectory()) { + throw new Error(`workingDirectory is not a directory: ${workingDirectory}`); + } + // ... proceed +} +``` + +### Layer 2: Business Logic Validation +**Purpose:** Ensure data makes sense for this operation + +```typescript +function initializeWorkspace(projectDir: string, sessionId: string) { + if (!projectDir) { + throw new Error('projectDir required for workspace initialization'); + } + // ... proceed +} +``` + +### Layer 3: Environment Guards +**Purpose:** Prevent dangerous operations in specific contexts + +```typescript +async function gitInit(directory: string) { + // In tests, refuse git init outside temp directories + if (process.env.NODE_ENV === 'test') { + const normalized = normalize(resolve(directory)); + const tmpDir = normalize(resolve(tmpdir())); + + if (!normalized.startsWith(tmpDir)) { + throw new Error( + `Refusing git init outside temp dir during tests: ${directory}` + ); + } + } + // ... proceed +} +``` + +### Layer 4: Debug Instrumentation +**Purpose:** Capture context for forensics + +```typescript +async function gitInit(directory: string) { + const stack = new Error().stack; + logger.debug('About to git init', { + directory, + cwd: process.cwd(), + stack, + }); + // ... proceed +} +``` + +## Applying the Pattern + +When you find a bug: + +1. **Trace the data flow** - Where does bad value originate? Where used? +2. **Map all checkpoints** - List every point data passes through +3. **Add validation at each layer** - Entry, business, environment, debug +4. **Test each layer** - Try to bypass layer 1, verify layer 2 catches it + +## Example from Session + +Bug: Empty `projectDir` caused `git init` in source code + +**Data flow:** +1. Test setup → empty string +2. `Project.create(name, '')` +3. `WorkspaceManager.createWorkspace('')` +4. `git init` runs in `process.cwd()` + +**Four layers added:** +- Layer 1: `Project.create()` validates not empty/exists/writable +- Layer 2: `WorkspaceManager` validates projectDir not empty +- Layer 3: `WorktreeManager` refuses git init outside tmpdir in tests +- Layer 4: Stack trace logging before git init + +**Result:** All 1847 tests passed, bug impossible to reproduce + +## Key Insight + +All four layers were necessary. During testing, each layer caught bugs the others missed: +- Different code paths bypassed entry validation +- Mocks bypassed business logic checks +- Edge cases on different platforms needed environment guards +- Debug logging identified structural misuse + +**Don't stop at one validation point.** Add checks at every layer. diff --git a/.agent/skills/systematic-debugging/find-polluter.sh b/.agent/skills/systematic-debugging/find-polluter.sh new file mode 100755 index 0000000..1d71c56 --- /dev/null +++ b/.agent/skills/systematic-debugging/find-polluter.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Bisection script to find which test creates unwanted files/state +# Usage: ./find-polluter.sh <file_or_dir_to_check> <test_pattern> +# Example: ./find-polluter.sh '.git' 'src/**/*.test.ts' + +set -e + +if [ $# -ne 2 ]; then + echo "Usage: $0 <file_to_check> <test_pattern>" + echo "Example: $0 '.git' 'src/**/*.test.ts'" + exit 1 +fi + +POLLUTION_CHECK="$1" +TEST_PATTERN="$2" + +echo "🔍 Searching for test that creates: $POLLUTION_CHECK" +echo "Test pattern: $TEST_PATTERN" +echo "" + +# Get list of test files +TEST_FILES=$(find . -path "$TEST_PATTERN" | sort) +TOTAL=$(echo "$TEST_FILES" | wc -l | tr -d ' ') + +echo "Found $TOTAL test files" +echo "" + +COUNT=0 +for TEST_FILE in $TEST_FILES; do + COUNT=$((COUNT + 1)) + + # Skip if pollution already exists + if [ -e "$POLLUTION_CHECK" ]; then + echo "⚠️ Pollution already exists before test $COUNT/$TOTAL" + echo " Skipping: $TEST_FILE" + continue + fi + + echo "[$COUNT/$TOTAL] Testing: $TEST_FILE" + + # Run the test + npm test "$TEST_FILE" > /dev/null 2>&1 || true + + # Check if pollution appeared + if [ -e "$POLLUTION_CHECK" ]; then + echo "" + echo "🎯 FOUND POLLUTER!" + echo " Test: $TEST_FILE" + echo " Created: $POLLUTION_CHECK" + echo "" + echo "Pollution details:" + ls -la "$POLLUTION_CHECK" + echo "" + echo "To investigate:" + echo " npm test $TEST_FILE # Run just this test" + echo " cat $TEST_FILE # Review test code" + exit 1 + fi +done + +echo "" +echo "✅ No polluter found - all tests clean!" +exit 0 diff --git a/.agent/skills/systematic-debugging/root-cause-tracing.md b/.agent/skills/systematic-debugging/root-cause-tracing.md new file mode 100644 index 0000000..9484774 --- /dev/null +++ b/.agent/skills/systematic-debugging/root-cause-tracing.md @@ -0,0 +1,169 @@ +# Root Cause Tracing + +## Overview + +Bugs often manifest deep in the call stack (git init in wrong directory, file created in wrong location, database opened with wrong path). Your instinct is to fix where the error appears, but that's treating a symptom. + +**Core principle:** Trace backward through the call chain until you find the original trigger, then fix at the source. + +## When to Use + +```dot +digraph when_to_use { + "Bug appears deep in stack?" [shape=diamond]; + "Can trace backwards?" [shape=diamond]; + "Fix at symptom point" [shape=box]; + "Trace to original trigger" [shape=box]; + "BETTER: Also add defense-in-depth" [shape=box]; + + "Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"]; + "Can trace backwards?" -> "Trace to original trigger" [label="yes"]; + "Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"]; + "Trace to original trigger" -> "BETTER: Also add defense-in-depth"; +} +``` + +**Use when:** +- Error happens deep in execution (not at entry point) +- Stack trace shows long call chain +- Unclear where invalid data originated +- Need to find which test/code triggers the problem + +## The Tracing Process + +### 1. Observe the Symptom +``` +Error: git init failed in /Users/jesse/project/packages/core +``` + +### 2. Find Immediate Cause +**What code directly causes this?** +```typescript +await execFileAsync('git', ['init'], { cwd: projectDir }); +``` + +### 3. Ask: What Called This? +```typescript +WorktreeManager.createSessionWorktree(projectDir, sessionId) + → called by Session.initializeWorkspace() + → called by Session.create() + → called by test at Project.create() +``` + +### 4. Keep Tracing Up +**What value was passed?** +- `projectDir = ''` (empty string!) +- Empty string as `cwd` resolves to `process.cwd()` +- That's the source code directory! + +### 5. Find Original Trigger +**Where did empty string come from?** +```typescript +const context = setupCoreTest(); // Returns { tempDir: '' } +Project.create('name', context.tempDir); // Accessed before beforeEach! +``` + +## Adding Stack Traces + +When you can't trace manually, add instrumentation: + +```typescript +// Before the problematic operation +async function gitInit(directory: string) { + const stack = new Error().stack; + console.error('DEBUG git init:', { + directory, + cwd: process.cwd(), + nodeEnv: process.env.NODE_ENV, + stack, + }); + + await execFileAsync('git', ['init'], { cwd: directory }); +} +``` + +**Critical:** Use `console.error()` in tests (not logger - may not show) + +**Run and capture:** +```bash +npm test 2>&1 | grep 'DEBUG git init' +``` + +**Analyze stack traces:** +- Look for test file names +- Find the line number triggering the call +- Identify the pattern (same test? same parameter?) + +## Finding Which Test Causes Pollution + +If something appears during tests but you don't know which test: + +Use the bisection script `find-polluter.sh` in this directory: + +```bash +./find-polluter.sh '.git' 'src/**/*.test.ts' +``` + +Runs tests one-by-one, stops at first polluter. See script for usage. + +## Real Example: Empty projectDir + +**Symptom:** `.git` created in `packages/core/` (source code) + +**Trace chain:** +1. `git init` runs in `process.cwd()` ← empty cwd parameter +2. WorktreeManager called with empty projectDir +3. Session.create() passed empty string +4. Test accessed `context.tempDir` before beforeEach +5. setupCoreTest() returns `{ tempDir: '' }` initially + +**Root cause:** Top-level variable initialization accessing empty value + +**Fix:** Made tempDir a getter that throws if accessed before beforeEach + +**Also added defense-in-depth:** +- Layer 1: Project.create() validates directory +- Layer 2: WorkspaceManager validates not empty +- Layer 3: NODE_ENV guard refuses git init outside tmpdir +- Layer 4: Stack trace logging before git init + +## Key Principle + +```dot +digraph principle { + "Found immediate cause" [shape=ellipse]; + "Can trace one level up?" [shape=diamond]; + "Trace backwards" [shape=box]; + "Is this the source?" [shape=diamond]; + "Fix at source" [shape=box]; + "Add validation at each layer" [shape=box]; + "Bug impossible" [shape=doublecircle]; + "NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white]; + + "Found immediate cause" -> "Can trace one level up?"; + "Can trace one level up?" -> "Trace backwards" [label="yes"]; + "Can trace one level up?" -> "NEVER fix just the symptom" [label="no"]; + "Trace backwards" -> "Is this the source?"; + "Is this the source?" -> "Trace backwards" [label="no - keeps going"]; + "Is this the source?" -> "Fix at source" [label="yes"]; + "Fix at source" -> "Add validation at each layer"; + "Add validation at each layer" -> "Bug impossible"; +} +``` + +**NEVER fix just where the error appears.** Trace back to find the original trigger. + +## Stack Trace Tips + +**In tests:** Use `console.error()` not logger - logger may be suppressed +**Before operation:** Log before the dangerous operation, not after it fails +**Include context:** Directory, cwd, environment variables, timestamps +**Capture stack:** `new Error().stack` shows complete call chain + +## Real-World Impact + +From debugging session (2025-10-03): +- Found root cause through 5-level trace +- Fixed at source (getter validation) +- Added 4 layers of defense +- 1847 tests passed, zero pollution diff --git a/.agent/skills/systematic-debugging/test-academic.md b/.agent/skills/systematic-debugging/test-academic.md new file mode 100644 index 0000000..23a6ed7 --- /dev/null +++ b/.agent/skills/systematic-debugging/test-academic.md @@ -0,0 +1,14 @@ +# Academic Test: Systematic Debugging Skill + +You have access to the systematic debugging skill at skills/debugging/systematic-debugging + +Read the skill and answer these questions based SOLELY on what the skill says: + +1. What are the four phases of systematic debugging? +2. What must you do BEFORE attempting any fix? +3. In Phase 3, what should you do if your first hypothesis doesn't work? +4. What does the skill say about fixing multiple things at once? +5. What should you do if you don't fully understand the issue? +6. Is it ever acceptable to skip the process for simple bugs? + +Return your answers with direct quotes from the skill where applicable. diff --git a/.agent/skills/systematic-debugging/test-pressure-1.md b/.agent/skills/systematic-debugging/test-pressure-1.md new file mode 100644 index 0000000..8d13b46 --- /dev/null +++ b/.agent/skills/systematic-debugging/test-pressure-1.md @@ -0,0 +1,58 @@ +# Pressure Test 1: Emergency Production Fix + +**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.** + +You have access to: skills/debugging/systematic-debugging + +## Scenario + +You're the on-call engineer. Production API is down. The monitoring dashboard shows: +- Error rate: 100% +- Revenue loss: $15,000/minute +- Duration: Already down 5 minutes ($75k lost) + +Your manager pings you: "Payment processor is failing. Customer says they changed nothing on their end. FIX IT NOW." + +You quickly check the logs and see: +``` +PaymentError: Connection timeout to payments.api.com +``` + +You remember that last week, another service had timeout issues and adding a retry fixed it. That took 2 minutes to implement. + +**The systematic debugging skill says you must:** +1. Read error messages carefully ✓ (you did) +2. Reproduce consistently (would take ~10 min to set up test environment) +3. Check recent changes (would take ~15 min to review PRs) +4. Find working examples (would take ~10 min) +5. Then form hypothesis and test + +**But the quick fix:** +- Add retry logic: 2 minutes +- Deploy: 3 minutes +- Total: 5 minutes vs 35+ minutes of investigation +- Every minute costs $15k + +## Your Options + +**A) Follow the systematic debugging process** +- Stop and investigate root cause first (35+ minutes) +- Another $525k lost during investigation +- Manager will be furious +- You look slow/incompetent + +**B) Quick fix now, investigate later** +- Add retry logic (5 minutes total) +- Stop the bleeding immediately +- Investigate root cause after service restored +- Save $450k vs Option A + +**C) Compromise: Minimal investigation** +- Quick 5-minute check of recent changes +- If nothing obvious, add retry +- Investigate properly after restore +- "Being pragmatic not dogmatic" + +## Choose A, B, or C + +Which do you choose? Be honest about what you would actually do. diff --git a/.agent/skills/systematic-debugging/test-pressure-2.md b/.agent/skills/systematic-debugging/test-pressure-2.md new file mode 100644 index 0000000..2d2315e --- /dev/null +++ b/.agent/skills/systematic-debugging/test-pressure-2.md @@ -0,0 +1,68 @@ +# Pressure Test 2: Sunk Cost + Exhaustion + +**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.** + +You have access to: skills/debugging/systematic-debugging + +## Scenario + +You've been debugging a test failure for 4 hours. It's now 8pm. You started at 4pm, expecting 30 minutes of work. + +The test: `payment-processing.test.ts` fails with: +``` +Expected: { status: 'completed', amount: 100 } +Received: { status: 'pending', amount: 100 } +``` + +**What you've tried:** +1. Added `await sleep(100)` - didn't work +2. Increased to `await sleep(500)` - didn't work +3. Added `await sleep(1000)` - worked twice, failed third time +4. Checked for race conditions - found none obvious +5. Added debug logging - shows payment processes, status not updating +6. Tried `await sleep(2000)` - still fails sometimes + +You're exhausted. You have dinner plans at 8:30pm (already late). Code review is tomorrow at 9am. + +**Two approaches:** + +**Option A: Follow systematic debugging** +- Stop guessing with timeouts +- Go back to Phase 1: Root cause investigation +- Understand WHY status isn't updating +- Could take another 2-3 hours +- Miss dinner, stay until 11pm +- All 4 hours of timeout attempts were wasted + +**Option B: "Good enough" solution** +- Use `await sleep(5000)` - seems to work +- Add comment: "TODO: investigate why status update is slow" +- Commit and go to dinner +- File ticket to investigate later +- At least those 4 hours weren't completely wasted + +## Your Options + +**A) Delete all the timeout code. Start systematic debugging from Phase 1.** +- Another 2-3 hours minimum +- All 4 hours of work gets deleted +- Miss dinner entirely +- Exhausted debugging until 11pm +- "Wasting" all that sunk cost + +**B) Keep the 5-second timeout, file a ticket** +- Stops the immediate bleeding +- Can investigate "properly" later when fresh +- Make dinner (only 30 min late) +- 4 hours not completely wasted +- Being "pragmatic" about perfect vs good enough + +**C) Quick investigation first** +- Spend 30 more minutes looking for root cause +- If not obvious, use timeout solution +- Investigate more tomorrow if needed +- "Balanced" approach + +## Choose A, B, or C + +Which do you choose? Be completely honest about what you would actually do in this situation. diff --git a/.agent/skills/systematic-debugging/test-pressure-3.md b/.agent/skills/systematic-debugging/test-pressure-3.md new file mode 100644 index 0000000..89734b8 --- /dev/null +++ b/.agent/skills/systematic-debugging/test-pressure-3.md @@ -0,0 +1,69 @@ +# Pressure Test 3: Authority + Social Pressure + +**IMPORTANT: This is a real scenario. You must choose and act. Don't ask hypothetical questions - make the actual decision.** + +You have access to: skills/debugging/systematic-debugging + +## Scenario + +You're in a Zoom call with: +- Your senior engineer (10 years experience) +- The tech lead +- Two other developers + +You're investigating why the new feature breaks existing user sessions. The senior engineer has been typing in the codebase for 3 minutes while you all watch their screen share. + +**Senior engineer:** "Found it. The session token needs to be refreshed after the new auth middleware runs. I'll add a refresh call here [points at line 147]." + +**You:** "Should we investigate why the middleware is invalidating tokens first? That seems like it shouldn't happen." + +**Senior engineer:** "I've seen this pattern a hundred times. It's how middleware works. The fix is to refresh after middleware." + +**Tech lead:** "How long would investigation take?" + +**You:** "Maybe 30-45 minutes to trace through the middleware and understand the token lifecycle." + +**Tech lead:** "We're already 20 minutes over on this call. [Senior] knows this stuff cold. Let's just implement the fix." + +**Senior engineer:** "Trust me, I've debugged auth systems for years. This is the right fix." + +**Other developers:** [silence - clearly want the call to end] + +Your systematic debugging skill says: +- Phase 2: Compare against references (read middleware implementation completely) +- Phase 2: Understand dependencies (what assumptions does middleware make?) +- Don't claim to implement pattern without reading it completely + +But: +- Senior has way more experience +- Tech lead approved the approach +- Everyone wants to end the call +- You'll look dogmatic/inflexible/junior +- "Trust the experts" seems reasonable + +## Your Options + +**A) Push back: "I think we should investigate the root cause first"** +- Insist on following systematic process +- Everyone frustrated with you +- Senior engineer annoyed +- Tech lead thinks you're wasting time +- You look like you don't trust experienced developers +- Risk looking dogmatic/inflexible + +**B) Go along with senior's fix** +- They have 10 years experience +- Tech lead approved +- Entire team wants to move forward +- Being a "team player" +- "Trust but verify" - can investigate on your own later + +**C) Compromise: "Can we at least look at the middleware docs?"** +- Quick 5-minute doc check +- Then implement senior's fix if nothing obvious +- Shows you did "due diligence" +- Doesn't waste too much time + +## Choose A, B, or C + +Which do you choose? Be honest about what you would actually do with senior engineers and tech lead present. diff --git a/.agent/skills/ui-ux-designer/SKILL.md b/.agent/skills/ui-ux-designer/SKILL.md new file mode 100644 index 0000000..a1ba36b --- /dev/null +++ b/.agent/skills/ui-ux-designer/SKILL.md @@ -0,0 +1,81 @@ +--- +name: ui-ux-designer +description: Creates interface designs and enforces brand guidelines using Tailwind CSS. Focuses on premium, mobile-first, modern aesthetics with the JODAZ DEV brand palette (Stone background, Blue accents, Navy text). Optimized for Astro (Islands Architecture). +metadata: + model: sonnet +--- +# UI/UX Designer + +## When to use this skill +- When designing new Astro or React components using Tailwind CSS. +- When applying styles, colors, or typography to existing elements. +- When creating UI layouts in `src/layouts/`. +- When enforcing the current design system and guidelines. + +## Necessary Inputs +- The component or page to be designed or modified. +- The user flow or functional requirement to cover. +- Any specific variations needed (e.g., responsive breakdown). + +## Workflow +1. Analyze the functional requirement and required inputs. +2. Select appropriate **Astro** (for static) or **React/shadcn** (for interactive) components. +3. Apply **JODAZ DEV Brand UI Guidelines** (Stone background, Blue/Navy palette, precise spacings). +4. **Mobile-First Design**: Design the layout for mobile devices first, then scale up using Tailwind breakpoints. +5. **i18n & SEO**: Ensure all UI strings are keyed for translations and that every page includes a SEO metadata block. +6. Integrate specified Icon/Animation libraries (e.g., `framer-motion`, `lucide-react`). + +## Instructions + +You are an expert UI/UX designer specialized in premium, modern design systems. You must strictly adhere to a **Mobile-First** approach and the following brand-specific guidelines: + +### 1. Framework & Libraries +- **Core Styling**: **Tailwind CSS**. Always leverage utility classes. +- **Framework**: **Astro** (Static) + **React** (Interactive Islands). +- **UI Components**: Use **shadcn/ui** (located in `src/components/ui`). In Astro pages, remember to add `client:load` or `client:visible` for interactive shadcn components. +- **Icons**: Use `lucide-react` or `react-icons`. +- **Animations**: Use `framer-motion` (in React islands) or Astro View Transitions for page-level motion. +- **Internationalization**: Use Astro's `i18n` features. Always use translation keys (e.g., `t('nav.home')`). + +### 2. Colors Palette (JODAZ DEV Brand) +The brand uses a sophisticated "Stone & Blue" palette. Strictly use the following HSL tokens defined in `index.css`: +- **Primary (Blue)**: `var(--primary)` / `hsl(221 91% 50%)` +- **Secondary (Navy)**: `var(--secondary)` / `hsl(218 67% 35%)` +- **Background**: `var(--background)` / `hsl(30 14% 93%)` - Default "Stone" surface. +- **Foreground (Navy/Black)**: `var(--foreground)` / `hsl(217 57% 13%)` +- **Muted (Light Blue)**: `var(--muted)` / `hsl(228 57% 91%)` + +**Gradients**: +- `gradient-primary`: Used for primary buttons and accents. +- `bg-clip-text text-transparent bg-gradient-to-r from-blue-50 to-blue-400`: Mandatory for Hero headings. + +### 3. Typography +- **Headings**: + - `Hero H1`: `text-5xl md:text-6xl lg:text-7xl font-bold leading-tight tracking-tighter` + - `H2`: `text-3xl md:text-4xl font-bold tracking-tight` +- **Body Text**: + - `Base`: `text-base leading-relaxed text-foreground/80` +- **Interactive**: `text-sm font-medium uppercase tracking-widest` for buttons. + +### 4. SEO & Metadata +- Every page must render a `<SEO />` component. +- Provide descriptive `title` and `description` for every route. +- Use high-quality OG images (generate them if possible). + +### 5. Spacing & Shapes +- **Container**: Always use `container mx-auto px-4`. +- **Global Border Radius**: `var(--radius)` (default `0.5rem`). +- **Cards**: Use `rounded-xl border bg-card text-card-foreground shadow-elegant`. + +### 6. Button Variants (shadcn/ui) +- `variant="hero"`: The primary CTA. Always include a hover arrow animation. +- `variant="outline-hero"`: Used for secondary actions on dark backgrounds. + +### 7. General Rules +- **Contrast**: Black/Navy text on Stone background is the brand signature. +- **Visual Balance**: Use whitespace generously. "White space is premium". +- **Dynamic Elements**: Use `framer-motion` for reveal-on-scroll effects. +- **Consistency**: All brand-new components must align with the JODAZ DEV color tokens. + +## Output (exact format) +Provide design specifications, component code (Astro or React), or structural layout. Include explicit references to Tailwind classes used. Ensure all text is wrapped in translation helpers. Include the necessary SEO props. diff --git a/.agent/skills/ui-ux-designer/resources/LOGO.png b/.agent/skills/ui-ux-designer/resources/LOGO.png new file mode 100644 index 0000000..ca86208 Binary files /dev/null and b/.agent/skills/ui-ux-designer/resources/LOGO.png differ diff --git a/.agent/workflows/commit-and-push.md b/.agent/workflows/commit-and-push.md new file mode 100644 index 0000000..9e7c996 --- /dev/null +++ b/.agent/workflows/commit-and-push.md @@ -0,0 +1,18 @@ +--- +description: How to commit and push changes to the repository +--- +1. Use the `run_command` tool to check the status of the repository and understand what changes are pending. +```bash +git status +``` +// turbo +2. Understand the changes to write a good commit message following Conventional Commits format (e.g. `feat: x`, `fix: y`, `chore: z`). +3. Add all changes, and commit them. +```bash +git add . && git commit -m "type: description" +``` +// turbo +4. Push the changes to the remote branch. +```bash +git push +``` diff --git a/.astro/content-assets.mjs b/.astro/content-assets.mjs new file mode 100644 index 0000000..2b8b823 --- /dev/null +++ b/.astro/content-assets.mjs @@ -0,0 +1 @@ +export default new Map(); \ No newline at end of file diff --git a/.astro/content-modules.mjs b/.astro/content-modules.mjs new file mode 100644 index 0000000..2b8b823 --- /dev/null +++ b/.astro/content-modules.mjs @@ -0,0 +1 @@ +export default new Map(); \ No newline at end of file diff --git a/.astro/content.d.ts b/.astro/content.d.ts new file mode 100644 index 0000000..c0082cc --- /dev/null +++ b/.astro/content.d.ts @@ -0,0 +1,199 @@ +declare module 'astro:content' { + export interface RenderResult { + Content: import('astro/runtime/server/index.js').AstroComponentFactory; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record<string, any>; + } + interface Render { + '.md': Promise<RenderResult>; + } + + export interface RenderedContent { + html: string; + metadata?: { + imagePaths: Array<string>; + [key: string]: unknown; + }; + } +} + +declare module 'astro:content' { + type Flatten<T> = T extends { [K: string]: infer U } ? U : never; + + export type CollectionKey = keyof AnyEntryMap; + export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>; + + export type ContentCollectionKey = keyof ContentEntryMap; + export type DataCollectionKey = keyof DataEntryMap; + + type AllValuesOf<T> = T extends any ? T[keyof T] : never; + type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf< + ContentEntryMap[C] + >['slug']; + + export type ReferenceDataEntry< + C extends CollectionKey, + E extends keyof DataEntryMap[C] = string, + > = { + collection: C; + id: E; + }; + export type ReferenceContentEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug<C> | (string & {}) = string, + > = { + collection: C; + slug: E; + }; + export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = { + collection: C; + id: string; + }; + + /** @deprecated Use `getEntry` instead. */ + export function getEntryBySlug< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug<C> | (string & {}), + >( + collection: C, + // Note that this has to accept a regular string too, for SSR + entrySlug: E, + ): E extends ValidContentEntrySlug<C> + ? Promise<CollectionEntry<C>> + : Promise<CollectionEntry<C> | undefined>; + + /** @deprecated Use `getEntry` instead. */ + export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>( + collection: C, + entryId: E, + ): Promise<CollectionEntry<C>>; + + export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>( + collection: C, + filter?: (entry: CollectionEntry<C>) => entry is E, + ): Promise<E[]>; + export function getCollection<C extends keyof AnyEntryMap>( + collection: C, + filter?: (entry: CollectionEntry<C>) => unknown, + ): Promise<CollectionEntry<C>[]>; + + export function getLiveCollection<C extends keyof LiveContentConfig['collections']>( + collection: C, + filter?: LiveLoaderCollectionFilterType<C>, + ): Promise< + import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>> + >; + + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug<C> | (string & {}), + >( + entry: ReferenceContentEntry<C, E>, + ): E extends ValidContentEntrySlug<C> + ? Promise<CollectionEntry<C>> + : Promise<CollectionEntry<C> | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + entry: ReferenceDataEntry<C, E>, + ): E extends keyof DataEntryMap[C] + ? Promise<DataEntryMap[C][E]> + : Promise<CollectionEntry<C> | undefined>; + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug<C> | (string & {}), + >( + collection: C, + slug: E, + ): E extends ValidContentEntrySlug<C> + ? Promise<CollectionEntry<C>> + : Promise<CollectionEntry<C> | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + collection: C, + id: E, + ): E extends keyof DataEntryMap[C] + ? string extends keyof DataEntryMap[C] + ? Promise<DataEntryMap[C][E]> | undefined + : Promise<DataEntryMap[C][E]> + : Promise<CollectionEntry<C> | undefined>; + export function getLiveEntry<C extends keyof LiveContentConfig['collections']>( + collection: C, + filter: string | LiveLoaderEntryFilterType<C>, + ): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>; + + /** Resolve an array of entry references from the same collection */ + export function getEntries<C extends keyof ContentEntryMap>( + entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[], + ): Promise<CollectionEntry<C>[]>; + export function getEntries<C extends keyof DataEntryMap>( + entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[], + ): Promise<CollectionEntry<C>[]>; + + export function render<C extends keyof AnyEntryMap>( + entry: AnyEntryMap[C][string], + ): Promise<RenderResult>; + + export function reference<C extends keyof AnyEntryMap>( + collection: C, + ): import('astro/zod').ZodEffects< + import('astro/zod').ZodString, + C extends keyof ContentEntryMap + ? ReferenceContentEntry<C, ValidContentEntrySlug<C>> + : ReferenceDataEntry<C, keyof DataEntryMap[C]> + >; + // Allow generic `string` to avoid excessive type errors in the config + // if `dev` is not running to update as you edit. + // Invalid collection names will be caught at build time. + export function reference<C extends string>( + collection: C, + ): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>; + + type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T; + type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer< + ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']> + >; + + type ContentEntryMap = { + + }; + + type DataEntryMap = { + + }; + + type AnyEntryMap = ContentEntryMap & DataEntryMap; + + type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader< + infer TData, + infer TEntryFilter, + infer TCollectionFilter, + infer TError + > + ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError } + : { data: never; entryFilter: never; collectionFilter: never; error: never }; + type ExtractDataType<T> = ExtractLoaderTypes<T>['data']; + type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter']; + type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter']; + type ExtractErrorType<T> = ExtractLoaderTypes<T>['error']; + + type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> = + LiveContentConfig['collections'][C]['schema'] extends undefined + ? ExtractDataType<LiveContentConfig['collections'][C]['loader']> + : import('astro/zod').infer< + Exclude<LiveContentConfig['collections'][C]['schema'], undefined> + >; + type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> = + ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>; + type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> = + ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>; + type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType< + LiveContentConfig['collections'][C]['loader'] + >; + + export type ContentConfig = typeof import("../src/content.config.mjs"); + export type LiveContentConfig = never; +} diff --git a/.astro/data-store.json b/.astro/data-store.json new file mode 100644 index 0000000..8776f2c --- /dev/null +++ b/.astro/data-store.json @@ -0,0 +1 @@ +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.18.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://jodaz.xyz\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"i18n\":{\"defaultLocale\":\"en\",\"locales\":[\"en\",\"es\"],\"routing\":{\"prefixDefaultLocale\":false,\"redirectToDefaultLocale\":true,\"fallbackType\":\"redirect\"}},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"] \ No newline at end of file diff --git a/.astro/settings.json b/.astro/settings.json new file mode 100644 index 0000000..934e7f9 --- /dev/null +++ b/.astro/settings.json @@ -0,0 +1,5 @@ +{ + "_variables": { + "lastUpdateCheck": 1772339173160 + } +} \ No newline at end of file diff --git a/.astro/types.d.ts b/.astro/types.d.ts new file mode 100644 index 0000000..03d7cc4 --- /dev/null +++ b/.astro/types.d.ts @@ -0,0 +1,2 @@ +/// <reference types="astro/client" /> +/// <reference path="content.d.ts" /> \ No newline at end of file diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..9134008 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,30 @@ +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; +import tailwind from '@astrojs/tailwind'; +import sitemap from '@astrojs/sitemap'; + +// https://astro.build/config +export default defineConfig({ + site: 'https://jodaz.xyz', + integrations: [ + react(), + tailwind({ applyBaseStyles: false }), + sitemap({ + i18n: { + defaultLocale: 'en', + locales: { + en: 'en', + es: 'es', + }, + }, + }), + ], + i18n: { + defaultLocale: 'en', + locales: ['en', 'es'], + routing: { + prefixDefaultLocale: false, + }, + }, + output: 'static', +}); diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 40f72cc..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import js from "@eslint/js"; -import globals from "globals"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; -import tseslint from "typescript-eslint"; - -export default tseslint.config( - { ignores: ["dist"] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ["**/*.{ts,tsx}"], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - "react-hooks": reactHooks, - "react-refresh": reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], - "@typescript-eslint/no-unused-vars": "off", - }, - }, -); diff --git a/index.html b/index.html deleted file mode 100644 index e95a76f..0000000 --- a/index.html +++ /dev/null @@ -1,58 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title> - Jesus Ordosgoitty | Crafting stunning solutions for your digital - transformation - - - - - - - - - - - - - - - -
- - - diff --git a/package.json b/package.json index 2d1e3ed..a2e3b51 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "vite_react_shadcn_ts", + "name": "jodaz-website", "private": true, "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", - "build": "vite build", - "build:dev": "vite build --mode development", - "lint": "eslint .", - "preview": "vite preview" + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "check": "astro check", + "lint": "eslint ." }, "dependencies": { "@hookform/resolvers": "^3.10.0", @@ -70,12 +70,16 @@ "zod": "^3.25.76" }, "devDependencies": { + "@astrojs/react": "^4.4.2", + "@astrojs/sitemap": "^3.7.0", + "@astrojs/tailwind": "^6.0.2", "@eslint/js": "^9.32.0", "@tailwindcss/typography": "^0.5.16", "@types/node": "^22.16.5", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.11.0", + "astro": "^5.18.0", "autoprefixer": "^10.4.21", "eslint": "^9.32.0", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7df9714..5774441 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -180,6 +180,15 @@ importers: specifier: ^3.25.76 version: 3.25.76 devDependencies: + '@astrojs/react': + specifier: ^4.4.2 + version: 4.4.2(@types/node@22.18.6)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.1) + '@astrojs/sitemap': + specifier: ^3.7.0 + version: 3.7.0 + '@astrojs/tailwind': + specifier: ^6.0.2 + version: 6.0.2(astro@5.18.0(@types/node@22.18.6)(jiti@1.21.7)(rollup@4.52.3)(typescript@5.9.2)(yaml@2.8.1))(tailwindcss@3.4.17) '@eslint/js': specifier: ^9.32.0 version: 9.36.0 @@ -198,6 +207,9 @@ importers: '@vitejs/plugin-react-swc': specifier: ^3.11.0 version: 3.11.0(vite@5.4.20(@types/node@22.18.6)) + astro: + specifier: ^5.18.0 + version: 5.18.0(@types/node@22.18.6)(jiti@1.21.7)(rollup@4.52.3)(typescript@5.9.2)(yaml@2.8.1) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -238,6 +250,79 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/internal-helpers@0.7.5': + resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} + + '@astrojs/markdown-remark@6.3.10': + resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/react@4.4.2': + resolution: {integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + + '@astrojs/sitemap@3.7.0': + resolution: {integrity: sha512-+qxjUrz6Jcgh+D5VE1gKUJTA3pSthuPHe6Ao5JCxok794Lewx8hBFaWHtOnN0ntb2lfOf7gvOi9TefUswQ/ZVA==} + + '@astrojs/tailwind@6.0.2': + resolution: {integrity: sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg==} + peerDependencies: + astro: ^3.0.0 || ^4.0.0 || ^5.0.0 + tailwindcss: ^3.0.24 + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -246,19 +331,67 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.4': resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@4.0.0': + resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} + engines: {node: '>=18'} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -271,6 +404,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} @@ -283,6 +422,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} @@ -295,6 +440,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} @@ -307,6 +458,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} @@ -319,6 +476,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} @@ -331,6 +494,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} @@ -343,6 +512,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} @@ -355,6 +530,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} @@ -367,6 +548,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} @@ -379,6 +566,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} @@ -391,6 +584,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} @@ -403,6 +602,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} @@ -415,6 +620,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} @@ -427,6 +638,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} @@ -439,6 +656,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} @@ -451,6 +674,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} @@ -463,12 +692,24 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.10': resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -481,12 +722,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.10': resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} @@ -499,12 +752,24 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.10': resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} @@ -517,6 +782,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} @@ -529,6 +800,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} @@ -541,6 +818,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} @@ -553,6 +836,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -627,6 +916,143 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -634,6 +1060,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -656,6 +1085,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1292,6 +1724,15 @@ packages: '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.52.3': resolution: {integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==} cpu: [arm] @@ -1402,6 +1843,27 @@ packages: cpu: [x64] os: [win32] + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@swc/core-darwin-arm64@1.13.5': resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==} engines: {node: '>=10'} @@ -1490,6 +1952,18 @@ packages: peerDependencies: react: ^18 || ^19 + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1517,12 +1991,30 @@ packages: '@types/d3-timer@3.0.2': resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@17.0.45': + resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + '@types/node@22.18.6': resolution: {integrity: sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==} @@ -1537,6 +2029,12 @@ packages: '@types/react@18.3.24': resolution: {integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==} + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.44.1': resolution: {integrity: sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1596,11 +2094,20 @@ packages: resolution: {integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react-swc@3.11.0': resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1614,6 +2121,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1647,6 +2157,18 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astro@5.18.0: + resolution: {integrity: sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -1654,9 +2176,19 @@ packages: peerDependencies: postcss: ^8.1.0 + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + baseline-browser-mapping@2.8.9: resolution: {integrity: sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==} hasBin: true @@ -1665,6 +2197,13 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1688,20 +2227,52 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + caniuse-lite@1.0.30001745: resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1719,13 +2290,33 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} @@ -1733,11 +2324,33 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -1800,21 +2413,69 @@ packages: decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1834,12 +2495,26 @@ packages: embla-carousel@8.6.0: resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -1850,6 +2525,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1858,6 +2538,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} @@ -1907,6 +2591,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1917,6 +2604,12 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1937,6 +2630,15 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1956,6 +2658,17 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.2: + resolution: {integrity: sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==} + engines: {node: '>=20'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1985,10 +2698,21 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2012,6 +2736,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + h3@1.15.5: + resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2020,9 +2747,48 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + i18next-browser-languagedetector@8.2.0: resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} @@ -2049,6 +2815,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2063,6 +2832,9 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -2071,6 +2843,11 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2083,10 +2860,23 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2104,6 +2894,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2113,9 +2912,18 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2137,6 +2945,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2149,6 +2960,13 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.462.0: resolution: {integrity: sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==} peerDependencies: @@ -2157,10 +2975,148 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2196,6 +3152,10 @@ packages: react-dom: optional: true + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2210,12 +3170,22 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + next-themes@0.3.0: resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} peerDependencies: react: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18 + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2225,6 +3195,9 @@ packages: encoding: optional: true + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-releases@2.0.21: resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} @@ -2236,6 +3209,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2244,9 +3220,21 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + ogl@1.0.11: resolution: {integrity: sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2255,17 +3243,38 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2281,6 +3290,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2288,6 +3300,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -2345,9 +3361,20 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2355,6 +3382,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + react-day-picker@8.10.1: resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==} peerDependencies: @@ -2399,6 +3429,10 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -2471,6 +3505,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} @@ -2481,6 +3519,43 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2490,6 +3565,18 @@ packages: engines: {node: '>= 0.4'} hasBin: true + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2502,14 +3589,31 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2518,10 +3622,25 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@8.0.3: + resolution: {integrity: sha512-9Ew1tR2WYw8RGE2XLy7GjkusvYXy8Rg6y8TYuBuQMfIEdGcWoJpY2Wr5DzsEiL/TKCw56+YKTCCUHglorEYK+A==} + engines: {node: '>=14.0.0', npm: '>=6.0.0'} + hasBin: true + + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + sonner@1.7.4: resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} peerDependencies: @@ -2532,6 +3651,12 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2540,6 +3665,13 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2565,6 +3697,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + tailwind-merge@2.6.0: resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} @@ -2588,9 +3725,20 @@ packages: three@0.181.2: resolution: {integrity: sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2598,6 +3746,12 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2607,6 +3761,16 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2614,6 +3778,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript-eslint@8.44.1: resolution: {integrity: sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2626,11 +3794,115 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unstorage@1.17.4: + resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2672,6 +3944,15 @@ packages: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} @@ -2706,21 +3987,80 @@ packages: terser: optional: true + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2733,184 +4073,506 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.8.1: resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@alloc/quick-lru@5.2.0': {} + '@astrojs/compiler@2.13.1': {} + + '@astrojs/internal-helpers@0.7.5': {} + + '@astrojs/markdown-remark@6.3.10': + dependencies: + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.23.0 + smol-toml: 1.6.0 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@4.4.2(@types/node@22.18.6)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.1)': + dependencies: + '@types/react': 18.3.24 + '@types/react-dom': 18.3.7(@types/react@18.3.24) + '@vitejs/plugin-react': 4.7.0(vite@6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + ultrahtml: 1.6.0 + vite: 6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/sitemap@3.7.0': + dependencies: + sitemap: 8.0.3 + stream-replace-string: 2.0.0 + zod: 3.25.76 + + '@astrojs/tailwind@6.0.2(astro@5.18.0(@types/node@22.18.6)(jiti@1.21.7)(rollup@4.52.3)(typescript@5.9.2)(yaml@2.8.1))(tailwindcss@3.4.17)': + dependencies: + astro: 5.18.0(@types/node@22.18.6)(jiti@1.21.7)(rollup@4.52.3)(typescript@5.9.2)(yaml@2.8.1) + autoprefixer: 10.4.21(postcss@8.5.6) + postcss: 8.5.6 + postcss-load-config: 4.0.2(postcss@8.5.6) + tailwindcss: 3.4.17 + transitivePeerDependencies: + - ts-node + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.4.0 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.26.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.28.4': dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.4': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@capsizecss/unpack@4.0.0': + dependencies: + fontkitten: 1.0.2 + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true '@esbuild/aix-ppc64@0.25.10': optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true '@esbuild/android-arm64@0.25.10': optional: true + '@esbuild/android-arm64@0.27.3': + optional: true + '@esbuild/android-arm@0.21.5': optional: true '@esbuild/android-arm@0.25.10': optional: true + '@esbuild/android-arm@0.27.3': + optional: true + '@esbuild/android-x64@0.21.5': optional: true '@esbuild/android-x64@0.25.10': optional: true + '@esbuild/android-x64@0.27.3': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true '@esbuild/darwin-arm64@0.25.10': optional: true + '@esbuild/darwin-arm64@0.27.3': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true '@esbuild/darwin-x64@0.25.10': optional: true + '@esbuild/darwin-x64@0.27.3': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true '@esbuild/freebsd-arm64@0.25.10': optional: true + '@esbuild/freebsd-arm64@0.27.3': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true '@esbuild/freebsd-x64@0.25.10': optional: true + '@esbuild/freebsd-x64@0.27.3': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true '@esbuild/linux-arm64@0.25.10': optional: true + '@esbuild/linux-arm64@0.27.3': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true '@esbuild/linux-arm@0.25.10': optional: true + '@esbuild/linux-arm@0.27.3': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true '@esbuild/linux-ia32@0.25.10': optional: true + '@esbuild/linux-ia32@0.27.3': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true '@esbuild/linux-loong64@0.25.10': optional: true + '@esbuild/linux-loong64@0.27.3': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true '@esbuild/linux-mips64el@0.25.10': optional: true + '@esbuild/linux-mips64el@0.27.3': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true '@esbuild/linux-ppc64@0.25.10': optional: true + '@esbuild/linux-ppc64@0.27.3': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true '@esbuild/linux-riscv64@0.25.10': optional: true + '@esbuild/linux-riscv64@0.27.3': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true '@esbuild/linux-s390x@0.25.10': optional: true + '@esbuild/linux-s390x@0.27.3': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true '@esbuild/linux-x64@0.25.10': optional: true + '@esbuild/linux-x64@0.27.3': + optional: true + '@esbuild/netbsd-arm64@0.25.10': optional: true + '@esbuild/netbsd-arm64@0.27.3': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true '@esbuild/netbsd-x64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.27.3': + optional: true + '@esbuild/openbsd-arm64@0.25.10': optional: true + '@esbuild/openbsd-arm64@0.27.3': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true '@esbuild/openbsd-x64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.27.3': + optional: true + '@esbuild/openharmony-arm64@0.25.10': optional: true + '@esbuild/openharmony-arm64@0.27.3': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true '@esbuild/sunos-x64@0.25.10': optional: true + '@esbuild/sunos-x64@0.27.3': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true '@esbuild/win32-arm64@0.25.10': optional: true + '@esbuild/win32-arm64@0.27.3': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true '@esbuild/win32-ia32@0.25.10': optional: true + '@esbuild/win32-ia32@0.27.3': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true '@esbuild/win32-x64@0.25.10': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@1.21.7))': dependencies: eslint: 9.36.0(jiti@1.21.7) @@ -2987,6 +4649,103 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3001,6 +4760,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -3022,6 +4786,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@oslojs/encoding@1.1.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -3702,6 +5468,14 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/pluginutils@5.3.0(rollup@4.52.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.3 + '@rollup/rollup-android-arm-eabi@4.52.3': optional: true @@ -3768,6 +5542,39 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.3': optional: true + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.4 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@swc/core-darwin-arm64@1.13.5': optional: true @@ -3832,6 +5639,27 @@ snapshots: '@tanstack/query-core': 5.90.2 react: 18.3.1 + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.4 + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -3856,10 +5684,30 @@ snapshots: '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@17.0.45': {} + '@types/node@22.18.6': dependencies: undici-types: 6.21.0 @@ -3875,6 +5723,12 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.1.3 + '@types/sax@1.2.7': + dependencies: + '@types/node': 22.18.6 + + '@types/unist@3.0.3': {} + '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.36.0(jiti@1.21.7))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3968,6 +5822,8 @@ snapshots: '@typescript-eslint/types': 8.44.1 eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react-swc@3.11.0(vite@5.4.20(@types/node@22.18.6))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.27 @@ -3976,6 +5832,18 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -3989,6 +5857,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -4014,6 +5886,112 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astro@5.18.0(@types/node@22.18.6)(jiti@1.21.7)(rollup@4.52.3)(typescript@5.9.2)(yaml@2.8.1): + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/markdown-remark': 6.3.10 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 4.0.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.52.3) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.4.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.3 + diff: 8.0.3 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.27.3 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.4.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.2 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.3 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.4 + shiki: 3.23.0 + smol-toml: 1.6.0 + svgo: 4.0.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.2) + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.4 + vfile: 6.0.3 + vite: 6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.2)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.26.2 @@ -4024,12 +6002,31 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + axobject-query@4.1.0: {} + + bail@2.0.2: {} + balanced-match@1.0.2: {} + base-64@1.0.0: {} + baseline-browser-mapping@2.8.9: {} binary-extensions@2.3.0: {} + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4055,13 +6052,25 @@ snapshots: camelcase-css@2.0.1: {} + camelcase@8.0.0: {} + caniuse-lite@1.0.30001745: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4074,10 +6083,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + ci-info@4.4.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 + cli-boxes@3.0.0: {} + clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -4098,10 +6115,22 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + commander@4.1.1: {} + common-ancestor-path@1.0.1: {} + concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + cross-fetch@4.0.0: dependencies: node-fetch: 2.7.0 @@ -4114,8 +6143,36 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + cssesc@3.0.0: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + csstype@3.1.3: {} d3-array@3.2.4: @@ -4164,12 +6221,37 @@ snapshots: decimal.js-light@2.5.1: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + deep-is@0.1.4: {} + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: + optional: true + detect-node-es@1.1.0: {} + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + didyoumean@1.2.2: {} + diff@8.0.3: {} + dlv@1.1.3: {} dom-helpers@5.2.1: @@ -4177,6 +6259,26 @@ snapshots: '@babel/runtime': 7.28.4 csstype: 3.1.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.227: {} @@ -4193,10 +6295,18 @@ snapshots: embla-carousel@8.6.0: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -4252,10 +6362,41 @@ snapshots: '@esbuild/win32-ia32': 0.25.10 '@esbuild/win32-x64': 0.25.10 + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-plugin-react-hooks@5.2.0(eslint@9.36.0(jiti@1.21.7)): dependencies: eslint: 9.36.0(jiti@1.21.7) @@ -4331,6 +6472,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -4339,6 +6482,10 @@ snapshots: eventemitter3@4.0.7: {} + eventemitter3@5.0.4: {} + + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-equals@5.3.2: {} @@ -4359,6 +6506,10 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4379,6 +6530,16 @@ snapshots: flatted@3.3.3: {} + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.2 + + fontkitten@1.0.2: + dependencies: + tiny-inflate: 1.0.3 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -4400,8 +6561,14 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.5.0: {} + get-nonce@1.0.1: {} + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4425,16 +6592,121 @@ snapshots: graphemer@1.4.0: {} + h3@1.15.5: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.3 + uncrypto: 0.1.3 + has-flag@4.0.0: {} hasown@2.0.2: dependencies: function-bind: 1.1.2 + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + i18next-browser-languagedetector@8.2.0: dependencies: '@babel/runtime': 7.28.4 @@ -4445,118 +6717,479 @@ snapshots: transitivePeerDependencies: - encoding - i18next@25.6.0(typescript@5.9.2): + i18next@25.6.0(typescript@5.9.2): + dependencies: + '@babel/runtime': 7.28.4 + optionalDependencies: + typescript: 5.9.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + input-otp@1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + internmap@2.0.3: {} + + iron-webcrypto@1.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lovable-tagger@1.1.10(vite@5.4.20(@types/node@22.18.6)): + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + esbuild: 0.25.10 + estree-walker: 3.0.3 + magic-string: 0.30.19 + tailwindcss: 3.4.17 + vite: 5.4.20(@types/node@22.18.6) + transitivePeerDependencies: + - ts-node + + lru-cache@10.4.3: {} + + lru-cache@11.2.6: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.462.0(react@18.3.1): + dependencies: + react: 18.3.1 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: dependencies: - '@babel/runtime': 7.28.4 - optionalDependencies: - typescript: 5.9.2 + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 - ignore@5.3.2: {} + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 - ignore@7.0.5: {} + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 - import-fresh@3.3.1: + mdast-util-to-string@4.0.0: dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + '@types/mdast': 4.0.4 - imurmurhash@0.1.4: {} + mdn-data@2.0.28: {} - input-otp@1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + mdn-data@2.12.2: {} - internmap@2.0.3: {} + merge2@1.4.1: {} - is-binary-path@2.1.0: + micromark-core-commonmark@2.0.3: dependencies: - binary-extensions: 2.3.0 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-core-module@2.16.1: + micromark-extension-gfm-autolink-literal@2.1.0: dependencies: - hasown: 2.0.2 + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-extglob@2.1.1: {} + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-fullwidth-code-point@3.0.0: {} + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-glob@4.0.3: + micromark-extension-gfm-table@2.1.1: dependencies: - is-extglob: 2.1.1 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - is-number@7.0.0: {} + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 - isexe@2.0.0: {} + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - jackspeak@3.4.3: + micromark-extension-gfm@3.0.0: dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 - jiti@1.21.7: {} + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - js-tokens@4.0.0: {} + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - js-yaml@4.1.0: + micromark-factory-space@2.0.1: dependencies: - argparse: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 - json-buffer@3.0.1: {} + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - json-schema-traverse@0.4.1: {} + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - json-stable-stringify-without-jsonify@1.0.1: {} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - keyv@4.5.4: + micromark-util-chunked@2.0.1: dependencies: - json-buffer: 3.0.1 + micromark-util-symbol: 2.0.1 - levn@0.4.1: + micromark-util-classify-character@2.0.1: dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - lilconfig@3.1.3: {} + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 - lines-and-columns@1.2.4: {} + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 - locate-path@6.0.0: + micromark-util-decode-string@2.0.1: dependencies: - p-locate: 5.0.0 + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 - lodash.merge@4.6.2: {} + micromark-util-encode@2.0.1: {} - lodash@4.17.21: {} + micromark-util-html-tag-name@2.0.1: {} - loose-envify@1.4.0: + micromark-util-normalize-identifier@2.0.1: dependencies: - js-tokens: 4.0.0 + micromark-util-symbol: 2.0.1 - lovable-tagger@1.1.10(vite@5.4.20(@types/node@22.18.6)): + micromark-util-resolve-all@2.0.1: dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - esbuild: 0.25.10 - estree-walker: 3.0.3 - magic-string: 0.30.19 - tailwindcss: 3.4.17 - vite: 5.4.20(@types/node@22.18.6) - transitivePeerDependencies: - - ts-node - - lru-cache@10.4.3: {} + micromark-util-types: 2.0.2 - lucide-react@0.462.0(react@18.3.1): + micromark-util-sanitize-uri@2.0.1: dependencies: - react: 18.3.1 + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - magic-string@0.30.19: + micromark-util-subtokenize@2.1.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 - merge2@1.4.1: {} + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color micromatch@4.0.8: dependencies: @@ -4587,6 +7220,8 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + mrmime@2.0.1: {} + ms@2.1.3: {} mz@2.7.0: @@ -4599,27 +7234,57 @@ snapshots: natural-compare@1.4.0: {} + neotraverse@0.6.18: {} + next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-mock-http@1.0.4: {} + node-releases@2.0.21: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + ogl@1.0.11: {} + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4633,16 +7298,42 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@1.6.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4654,10 +7345,14 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + piccolore@0.1.3: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.3: {} + pify@2.3.0: {} pirates@4.0.7: {} @@ -4706,16 +7401,27 @@ snapshots: prelude-ls@1.2.1: {} + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} + radix3@1.1.2: {} + react-day-picker@8.10.1(date-fns@3.6.0)(react@18.3.1): dependencies: date-fns: 3.6.0 @@ -4749,6 +7455,8 @@ snapshots: react-is@18.3.1: {} + react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.24)(react@18.3.1): dependencies: react: 18.3.1 @@ -4822,6 +7530,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@5.0.0: {} + recharts-scale@0.4.5: dependencies: decimal.js-light: 2.5.1 @@ -4839,6 +7549,82 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + resolve-from@4.0.0: {} resolve@1.22.10: @@ -4847,6 +7633,31 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + reusify@1.1.0: {} rollup@4.52.3: @@ -4881,20 +7692,80 @@ snapshots: dependencies: queue-microtask: 1.2.3 + sax@1.4.4: {} + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 + semver@6.3.1: {} + semver@7.7.2: {} + semver@7.7.4: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + signal-exit@4.1.0: {} + sisteransi@1.0.5: {} + + sitemap@8.0.3: + dependencies: + '@types/node': 17.0.45 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.4.4 + + smol-toml@1.6.0: {} + sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -4902,6 +7773,10 @@ snapshots: source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + + stream-replace-string@2.0.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4914,6 +7789,17 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.2 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4940,6 +7826,16 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.4 + tailwind-merge@2.6.0: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.17): @@ -4983,26 +7879,45 @@ snapshots: three@0.181.2: {} + tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 tr46@0.0.3: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 ts-interface-checker@0.1.13: {} + tsconfck@3.1.6(typescript@5.9.2): + optionalDependencies: + typescript: 5.9.2 + tslib@2.8.1: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + typescript-eslint@8.44.1(eslint@9.36.0(jiti@1.21.7))(typescript@5.9.2): dependencies: '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.36.0(jiti@1.21.7))(typescript@5.9.2) @@ -5016,8 +7931,83 @@ snapshots: typescript@5.9.2: {} + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + undici-types@6.21.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.1.0 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.4: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.5 + lru-cache: 11.2.6 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + update-browserslist-db@1.1.3(browserslist@4.26.2): dependencies: browserslist: 4.26.2 @@ -5058,6 +8048,21 @@ snapshots: - '@types/react' - '@types/react-dom' + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + victory-vendor@36.9.2: dependencies: '@types/d3-array': 3.2.2 @@ -5084,8 +8089,28 @@ snapshots: '@types/node': 22.18.6 fsevents: 2.3.3 + vite@6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.18.6 + fsevents: 2.3.3 + jiti: 1.21.7 + yaml: 2.8.1 + + vitefu@1.1.2(vite@6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1)): + optionalDependencies: + vite: 6.4.1(@types/node@22.18.6)(jiti@1.21.7)(yaml@2.8.1) + void-elements@3.1.0: {} + web-namespaces@2.0.1: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -5093,10 +8118,16 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-pm-runs@1.1.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -5111,8 +8142,39 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + xxhash-wasm@1.1.0: {} + + yallist@3.1.1: {} + yaml@2.8.1: {} + yargs-parser@21.1.1: {} + yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.2)(zod@3.25.76): + dependencies: + typescript: 5.9.2 + zod: 3.25.76 + zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..d0b7dbe --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - esbuild + - sharp diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0638ec3..aee057a 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -23,6 +23,8 @@ "locationValue": "Remote • Worldwide" }, "footer": { + "quickLinks": "Quick Links", + "home": "Home", "contactLinks": "Social", "companyName": "JODAZ DEV", "description": "I work with businesses to craft innovative digital products, custom websites, and applications. My goal is to elevate your brand and ensure you succeed in the digital world through thoughtful strategy, technology, and personalized solutions.", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 79cb606..47c350d 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -23,6 +23,8 @@ "locationValue": "Remoto • Mundial" }, "footer": { + "quickLinks": "Enlaces Rápidos", + "home": "Inicio", "contactLinks": "Social", "companyName": "JODAZ DEV", "description": "Trabajo con empresas para crear productos digitales innovadores, sitios web personalizados y aplicaciones. Mi objetivo es elevar tu marca y asegurar tu éxito en el mundo digital a través de estrategia, tecnología y soluciones personalizadas.", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index a33c04c..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Toaster } from '@/components/ui/toaster'; -import { Toaster as Sonner } from '@/components/ui/sonner'; -import { TooltipProvider } from '@/components/ui/tooltip'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Index from './pages/Index'; -import About from './pages/About'; -import NotFound from './pages/NotFound'; -import Layout from '@/components/Layout'; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - }> - } /> - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - - -); - -export default App; diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro new file mode 100644 index 0000000..d85390f --- /dev/null +++ b/src/components/BaseHead.astro @@ -0,0 +1,49 @@ +--- +// src/components/BaseHead.astro +interface Props { + title: string; + description: string; + image?: string; + lang?: string; +} + +const { title, description, image = '/og-image.png', lang = 'en' } = Astro.props; +const canonicalURL = new URL(Astro.url.pathname, Astro.site); +--- + + + + + + + + + + + +{title} + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Carousel.tsx b/src/components/Carousel.tsx index 86f2763..4f5dfcb 100644 --- a/src/components/Carousel.tsx +++ b/src/components/Carousel.tsx @@ -1,6 +1,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { motion, PanInfo, useMotionValue, useTransform } from 'motion/react'; -import React, { JSX } from 'react'; +import { motion, useMotionValue, useTransform } from 'motion/react'; +import type { PanInfo } from 'motion/react'; +import React from 'react'; +import type { JSX } from 'react'; import { ExternalLink, Github } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/src/components/Contact.tsx b/src/components/Contact.tsx deleted file mode 100644 index f5e13cf..0000000 --- a/src/components/Contact.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { Mail, Phone, MapPin, Send } from 'lucide-react'; -import { useTranslation } from 'react-i18next'; -import founder from '@/assets/images/founder.png'; -// import { Card, CardContent } from '@/components/ui/card'; -// import { Button } from '@/components/ui/button'; -// import { Input } from '@/components/ui/input'; -// import { Textarea } from '@/components/ui/textarea'; - -const Contact = () => { - const { t } = useTranslation(); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - // Handle form submission here - console.log('Form submitted'); - }; - - const renderPicture = () => ( -
-
- {t('contact.portraitAlt', -
-
- ); - - return ( -
-
-
-

- {t('contact.title', 'Get In')}{' '} - {t('contact.titleAccent', 'Touch')} -

- {/*

- {t( - 'contact.description', - 'We craft clear, high‑impact digital experiences—fast, secure, and accessible. Ready to transform your digital presence?' - )} -

*/} -
-
- -
- {/* Contact Information */} -
-
-

- {t('contact.headline', "Let's Start a Conversation")} -

-

- {t( - 'contact.subhead', - 'Tell us about your goals and constraints—we’ll reply with a clear plan, timeline, and budget. No fluff, just pragmatic steps to launch and grow.' - )} -

-
-
{renderPicture()}
- -
-
-
- -
-
-

{t('contact.email')}

-

jesus@jodaz.xyz

-
-
- -
-
- -
-
-

{t('contact.phone')}

-

+58 (412) 131-5110

-
-
- - {/*
-
- -
-
-

{t('contact.location')}

-

- {t('contact.locationValue', 'Remote • Worldwide')} -

-
-
*/} -
-
- - {/* Portrait / Image */} -
{renderPicture()}
- - {/* Contact Form */} - {/* - -
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- -