diff --git a/agents/.agents/.gitignore b/agents/.agents/.gitignore new file mode 100644 index 0000000..34f46fb --- /dev/null +++ b/agents/.agents/.gitignore @@ -0,0 +1 @@ +.skill-lock.json diff --git a/agents/.agents/skills/find-skills/SKILL.md b/agents/.agents/skills/find-skills/SKILL.md new file mode 100644 index 0000000..a41bdd0 --- /dev/null +++ b/agents/.agents/skills/find-skills/SKILL.md @@ -0,0 +1,141 @@ +--- +name: find-skills +description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill. +--- + +# Find Skills + +This skill helps you discover and install skills from the open agent skills ecosystem. + +## When to Use This Skill + +Use this skill when the user: + +- Asks "how do I do X" where X might be a common task with an existing skill +- Says "find a skill for X" or "is there a skill for X" +- Asks "can you do X" where X is a specialized capability +- Expresses interest in extending agent capabilities +- Wants to search for tools, templates, or workflows +- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.) + +## What is the Skills CLI? + +The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools. + +**Key commands:** + +- `npx skills find [query] [--owner ]` - Search for skills interactively or by keyword, optionally scoped to a GitHub owner +- `npx skills add ` - Install a skill from GitHub or other sources +- `npx skills update` - Update all installed skills + +**Browse skills at:** https://skills.sh/ + +## How to Help Users Find Skills + +### Step 1: Understand What They Need + +When a user asks for help with something, identify: + +1. The domain (e.g., React, testing, design, deployment) +2. The specific task (e.g., writing tests, creating animations, reviewing PRs) +3. Whether this is a common enough task that a skill likely exists + +### Step 2: Check the Leaderboard First + +Before running a CLI search, check the [skills.sh leaderboard](https://skills.sh/) to see if a well-known skill already exists for the domain. The leaderboard ranks skills by total installs, surfacing the most popular and battle-tested options. + +For example, top skills for web development include: +- `vercel-labs/agent-skills` — React, Next.js, web design (100K+ installs each) +- `anthropics/skills` — Frontend design, document processing (100K+ installs) + +### Step 3: Search for Skills + +If the leaderboard doesn't cover the user's need, run the find command: + +```bash +npx skills find [query] [--owner ] +``` + +For example: + +- User asks "how do I make my React app faster?" → `npx skills find react performance` +- User asks "can you help me with PR reviews?" → `npx skills find pr review` +- User asks "I need to create a changelog" → `npx skills find changelog` + +### Step 4: Verify Quality Before Recommending + +**Do not recommend a skill based solely on search results.** Always verify: + +1. **Install count** — Prefer skills with 1K+ installs. Be cautious with anything under 100. +2. **Source reputation** — Official sources (`vercel-labs`, `anthropics`, `microsoft`) are more trustworthy than unknown authors. +3. **GitHub stars** — Check the source repository. A skill from a repo with <100 stars should be treated with skepticism. + +### Step 5: Present Options to the User + +When you find relevant skills, present them to the user with: + +1. The skill name and what it does +2. The install count and source +3. The install command they can run +4. A link to learn more at skills.sh + +Example response: + +``` +I found a skill that might help! The "react-best-practices" skill provides +React and Next.js performance optimization guidelines from Vercel Engineering. +(185K installs) + +To install it: +npx skills add vercel-labs/agent-skills@react-best-practices + +Learn more: https://skills.sh/vercel-labs/agent-skills/react-best-practices +``` + +### Step 6: Offer to Install + +If the user wants to proceed, you can install the skill for them: + +```bash +npx skills add -g -y +``` + +The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts. + +## Common Skill Categories + +When searching, consider these common categories: + +| Category | Example Queries | +| --------------- | ---------------------------------------- | +| Web Development | react, nextjs, typescript, css, tailwind | +| Testing | testing, jest, playwright, e2e | +| DevOps | deploy, docker, kubernetes, ci-cd | +| Documentation | docs, readme, changelog, api-docs | +| Code Quality | review, lint, refactor, best-practices | +| Design | ui, ux, design-system, accessibility | +| Productivity | workflow, automation, git | + +## Tips for Effective Searches + +1. **Use specific keywords**: "react testing" is better than just "testing" +2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd" +3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills` + +## When No Skills Are Found + +If no relevant skills exist: + +1. Acknowledge that no existing skill was found +2. Offer to help with the task directly using your general capabilities +3. Suggest the user could create their own skill with `npx skills init` + +Example: + +``` +I searched for skills related to "xyz" but didn't find any matches. +I can still help you with this task directly! Would you like me to proceed? + +If this is something you do often, you could create your own skill: +npx skills init my-xyz-skill +``` diff --git a/agents/.agents/skills/nuxt-ui/SKILL.md b/agents/.agents/skills/nuxt-ui/SKILL.md new file mode 100644 index 0000000..b0e39aa --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/SKILL.md @@ -0,0 +1,181 @@ +--- +name: nuxt-ui +description: Build UIs with @nuxt/ui v4 — 125+ accessible Vue components with Tailwind CSS theming. Use when creating interfaces, customizing themes to match a brand, building forms, or composing layouts like dashboards, docs sites, and chat interfaces. +--- + +# Nuxt UI + +Vue component library built on [Reka UI](https://reka-ui.com/) + [Tailwind CSS](https://tailwindcss.com/) + [Tailwind Variants](https://www.tailwind-variants.org/). Works with Nuxt, Vue (Vite), Laravel (Vite + Inertia), and AdonisJS (Vite + Inertia). + +## MCP Server + +For component API details (props, slots, events, full documentation, examples), use the [Nuxt UI MCP server](https://ui.nuxt.com/docs/getting-started/ai/mcp). If not already configured, add it: + +**Cursor** — `.cursor/mcp.json`: + +```json +{ "mcpServers": { "nuxt-ui": { "type": "http", "url": "https://ui.nuxt.com/mcp" } } } +``` + +**Claude Code**: + +```bash +claude mcp add --transport http nuxt-ui https://ui.nuxt.com/mcp +``` + +Key MCP tools: + +- `search-components` — find components by name, category, or intent (no params = list all) +- `search-composables` — find composables by name or description (no params = list all) +- `search-icons` — search Iconify icons (defaults to `lucide`), returns `i-{prefix}-{name}` names +- `get-component` — full component documentation with usage examples +- `get-component-metadata` — props, slots, events (lightweight, no docs content) +- `get-example` — real-world code examples + +When you need to know **what a component accepts** or **how its API works**, use the MCP. This skill teaches you **when to use which component** and **how to build well**. + +## Core rules (always apply) + +1. **Always wrap the app in `UApp`** — required for toasts, tooltips, and programmatic overlays. Accepts a `locale` prop for i18n. +2. **Always use semantic colors** — `text-default`, `bg-elevated`, `border-muted`, etc. Never use raw Tailwind palette colors like `text-gray-500`. +3. **Read generated theme files for slot names** — Nuxt: `.nuxt/ui/.ts`, Vue: `node_modules/.nuxt-ui/ui/.ts`. These show every slot, variant, and default class for any component. +4. **Override priority** (highest wins): `ui` prop / `class` prop → global config → theme defaults. +5. **Icons use `i-{collection}-{name}` format** — `lucide` is the default collection. Use the MCP `search-icons` tool to find icons, or browse at [icones.js.org](https://icones.js.org). + +## How to use this skill + +Based on the task, load the relevant reference files **before writing any code**. Don't load everything — only what's needed. + +### Reference files + +**Guidelines** — design decisions and conventions: + +- [design-system](references/guidelines/design-system.md) — semantic colors, theming, brand customization, variants, the `ui` prop +- [component-selection](references/guidelines/component-selection.md) — decision matrices: when to use Modal vs Slideover, Select vs SelectMenu, Toast vs Alert, etc. +- [conventions](references/guidelines/conventions.md) — coding patterns, slot naming, items arrays, composables, keyboard shortcuts +- [forms](references/guidelines/forms.md) — form validation, field layout, error handling, Standard Schema + +**Layouts** — full page structure patterns: + +- [landing](references/layouts/landing.md) — landing pages, blog, changelog, pricing +- [dashboard](references/layouts/dashboard.md) — admin UI with sidebar and panels +- [docs](references/layouts/docs.md) — documentation sites with navigation and TOC +- [chat](references/layouts/chat.md) — AI chat with Vercel AI SDK +- [editor](references/layouts/editor.md) — rich text editor with toolbars + +**Recipes** — complete patterns for common tasks: + +- [data-tables](references/recipes/data-tables.md) — tables with filters, pagination, sorting, selection +- [auth](references/recipes/auth.md) — login, signup, forgot password forms +- [overlays](references/recipes/overlays.md) — modals, slideovers, drawers, command palette +- [navigation](references/recipes/navigation.md) — headers, sidebars, breadcrumbs, tabs + +**Quick reference:** + +- [components](references/components.md) — categorized component index for finding the right component name + +### Routing table + +| Task | Load these references | +| --------------------------------- | --------------------------------------------- | +| Build a landing page | design-system, conventions, landing | +| Build a dashboard / admin UI | conventions, component-selection, dashboard | +| Add a settings page | conventions, forms | +| Create a login / signup form | conventions, forms, auth | +| Display data in a table | conventions, component-selection, data-tables | +| Customize theme / brand colors | design-system | +| Add a chat interface | conventions, chat | +| Add a modal, slideover, or drawer | conventions, component-selection, overlays | +| Build site navigation | conventions, component-selection, navigation | +| Build a documentation site | conventions, docs | +| Render markdown | component-selection, components, docs | +| Add a rich text editor | conventions, editor | +| General UI work | conventions, component-selection | + +## Installation + +### Nuxt + +```bash +pnpm add @nuxt/ui tailwindcss +``` + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@nuxt/ui'], + css: ['~/assets/css/main.css'] +}) +``` + +```css +/* app/assets/css/main.css */ +@import "tailwindcss"; +@import "@nuxt/ui"; +``` + +```vue + + +``` + +### Vue (Vite) + +```bash +pnpm add @nuxt/ui tailwindcss +``` + +```ts +// vite.config.ts +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import ui from '@nuxt/ui/vite' + +export default defineConfig({ + plugins: [ + vue(), + ui() + ] +}) +``` + +```ts +// src/main.ts +import './assets/css/main.css' +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import ui from '@nuxt/ui/vue-plugin' +import App from './App.vue' + +const app = createApp(App) +const router = createRouter({ + routes: [], + history: createWebHistory() +}) + +app.use(router) +app.use(ui) +app.mount('#app') +``` + +```css +/* src/assets/css/main.css */ +@import "tailwindcss"; +@import "@nuxt/ui"; +``` + +```vue + + +``` + +> Add `class="isolate"` to your root `
` in `index.html`. +> For Inertia: use `ui({ router: 'inertia' })` in `vite.config.ts`. diff --git a/agents/.agents/skills/nuxt-ui/references/components.md b/agents/.agents/skills/nuxt-ui/references/components.md new file mode 100644 index 0000000..0d5cbb9 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/components.md @@ -0,0 +1,245 @@ +# Components + +Quick-reference index of all 125+ components. For full API docs (props, slots, events, examples), use the MCP `get-component` or `get-component-metadata` tools. + +## Layout + +| Component | Purpose | +| ---------------- | ------------------------------------------------------------ | +| `UApp` | **Required** root wrapper — toasts, tooltips, overlays, i18n | +| `UHeader` | Responsive header with mobile menu | +| `UFooter` | Footer with left/right/top/bottom slots | +| `UFooterColumns` | Multi-column footer with link groups | +| `UMain` | Main content area | +| `UContainer` | Centered max-width container | +| `USplitter` | Resizable panels separated by draggable handles | +| `ULink` | Enhanced link — NuxtLink/RouterLink with active states | + +## Element + +| Component | Purpose | +| ---------------- | ---------------------------------------------------- | +| `UButton` | Buttons — links, actions, icons, loading states | +| `UBadge` | Labels, tags, status indicators | +| `UAvatar` | User photos, initials, icons | +| `UAvatarGroup` | Stacked avatars with `max` limit | +| `UIcon` | Iconify icons (`i-{collection}-{name}`) | +| `UCard` | Bordered container with header/body/footer | +| `UAlert` | Inline messages — info, warning, error, success | +| `UBanner` | App-wide sticky announcement bar | +| `UChip` | Notification dot overlay on children | +| `UKbd` | Keyboard key display | +| `USeparator` | Divider line with optional label | +| `USkeleton` | Loading placeholder | +| `UProgress` | Progress bar | +| `UProgressGroup` | Segmented progress bar with a list of items | +| `UToast` | Toast notification (shown via `useToast`) | +| `UCalendar` | Date calendar (single, range, multiple) | +| `UCollapsible` | Animated expand/collapse | +| `UFieldGroup` | Group form inputs horizontally | +| `UMarquee` | Scrolling content ticker | +| `UCarousel` | Image/content carousel with autoplay | +| `UEmpty` | Empty state placeholder with icon, title, actions | +| `UError` | Error display with retry action | +| `UScrollArea` | Scrollable area with custom scrollbar | +| `UTimeline` | Timeline display for events and activity | +| `UUser` | User display — avatar + name + description | +| `UTheme` | Theme provider — scoped color overrides for children | + +## Form + +| Component | Purpose | +| ---------------- | ---------------------------------------------- | +| `UAuthForm` | Pre-built auth form with social providers | +| `UInput` | Text input — text, email, password, search | +| `UTextarea` | Multi-line text with autoresize | +| `USelect` | Native-like dropdown for small lists | +| `USelectMenu` | Rich searchable dropdown, multi-select, groups | +| `UInputMenu` | Autocomplete / combobox | +| `UInputNumber` | Numeric input with +/- controls | +| `UInputDate` | Date picker with calendar | +| `UInputTime` | Time picker (12/24h) | +| `UInputTags` | Tag/chip input | +| `UPinInput` | Verification code input | +| `UCheckbox` | Single boolean checkbox | +| `UCheckboxGroup` | Multiple checkboxes | +| `URadioGroup` | Radio button group | +| `USwitch` | Toggle switch | +| `USlider` | Range slider | +| `UColorPicker` | Color picker (hex/rgb/hsl) | +| `UFileUpload` | File upload (button or drop area) | +| `UForm` | Validation wrapper with Standard Schema | +| `UFormField` | Field wrapper with label, hint, errors | + +## Overlay + +| Component | Purpose | +| ----------------- | -------------------------------------- | +| `UModal` | Centered dialog — confirmations, forms | +| `USlideover` | Side panel — details, editing | +| `UDrawer` | Bottom sheet — mobile actions | +| `UPopover` | Contextual popup attached to trigger | +| `UTooltip` | Hover/focus hint (non-interactive) | +| `UContextMenu` | Right-click menu | +| `UCommandPalette` | Search + keyboard navigation (Cmd+K) | + +## Navigation + +| Component | Purpose | +| ----------------- | ------------------------------------------ | +| `USidebar` | Standalone sidebar with header/body/footer | +| `UNavigationMenu` | Primary nav — horizontal or vertical | +| `UTabs` | Tab switcher within a page | +| `UBreadcrumb` | Location hierarchy | +| `UDropdownMenu` | Action menu on a trigger | +| `UPagination` | Page navigation | +| `UStepper` | Multi-step wizard | +| `UAccordion` | Collapsible sections | + +## Data + +| Component | Purpose | +| --------- | ------------------------------------------------------------ | +| `UTable` | Data table (TanStack Table) with sorting, selection, pinning | +| `UTree` | Hierarchical tree view | + +## Dashboard + +| Component | Purpose | +| --------------------------- | ------------------------------------- | +| `UDashboardGroup` | Root dashboard wrapper | +| `UDashboardSidebar` | Resizable, collapsible sidebar | +| `UDashboardPanel` | Content panel with header/body/footer | +| `UDashboardNavbar` | Panel header bar | +| `UDashboardToolbar` | Filter/action bar below navbar | +| `UDashboardResizeHandle` | Resize handle between panels | +| `UDashboardSidebarToggle` | Mobile sidebar toggle button | +| `UDashboardSearchButton` | Search button for sidebar | +| `UDashboardSearch` | Dashboard-level search overlay | +| `UDashboardSidebarCollapse` | Collapse button for sidebar | + +## Page (marketing) + +| Component | Purpose | +| -------------- | ----------------------------------------------- | +| `UPage` | Multi-column layout with left/right sidebars | +| `UPageHero` | Hero section — title, description, links, media | +| `UPageSection` | Content section with features grid | +| `UPageCTA` | Call to action block | +| `UPageHeader` | Page title and description | +| `UPageBody` | Main content area | +| `UPageGrid` | Card grid layout | +| `UPageColumns` | Multi-column layout | +| `UPageCard` | Content card for grids | +| `UPageFeature` | Feature item | +| `UPageLogos` | Logo cloud | +| `UPageAside` | Sticky sidebar wrapper | +| `UPageAnchors` | Simple anchor links | +| `UPageLinks` | Related resource links | +| `UPageList` | List layout for page items | + +## Blog & Changelog + +| Component | Purpose | +| -------------------- | -------------------------- | +| `UBlogPosts` | Blog post grid | +| `UBlogPost` | Individual post card | +| `UChangelogVersions` | Changelog list | +| `UChangelogVersion` | Individual changelog entry | + +## Pricing + +| Component | Purpose | +| --------------- | ---------------------------- | +| `UPricingPlans` | Pricing plan cards | +| `UPricingPlan` | Individual pricing plan card | +| `UPricingTable` | Feature comparison table | + +## Prose — Base Typography + +Standard Markdown elements auto-resolved by Comark/Content/MDC. No `::` prefix needed — they map directly from markdown syntax (`# Heading` → `ProseH1`, `**bold**` → `ProseStrong`, etc.). Themed via `appConfig.ui.prose.`. + +| Component | Renders | Notable | +| -------------------------------------- | --------------- | ----------------------------------------------------------------- | +| `H1` `H2` `H3` `H4` | Headings | H1–H3 get anchor links + TOC entries | +| `P` | Paragraph | | +| `A` | Link | External links get target/rel handling | +| `Strong` | Bold | | +| `Em` | Italic | | +| `Blockquote` | Blockquote | | +| `Hr` | Horizontal rule | | +| `Ul` `Ol` `Li` | Lists | Supports nesting and mixed lists | +| `Table` `Thead` `Tbody` `Tr` `Th` `Td` | Tables | | +| `Img` | Image | Zoom on click (`:zoom="false"` to disable), `@nuxt/image` support | +| `Pre` | Code block | Copy button, filename + icon, line highlighting (`{2,4-6}`), diff | +| `Code` | Inline code | `color` and `lang` props | + +## Prose — Feature Components + +Nuxt UI-specific Prose components. In markdown files they are used **without the `Prose` prefix** (e.g. `::callout`, `::steps`). In Vue they are referenced as `ProseCallout`, `ProseSteps`, etc. Comark resolves them automatically when `@nuxt/ui` is installed. + +Nuxt UI also registers shorthand aliases for `Callout`: `::note`, `::tip`, `::warning`, `::caution` (preset `color` + `icon`). + +| Component | Purpose | +| --------------------------- | --------------------------------------------------------------------- | +| `Callout` | Highlighted note/warning/tip (`color`, `icon`, `to`) | +| `Badge` | Inline badge/tag | +| `Kbd` | Keyboard key | +| `Icon` | Inline Iconify icon | +| `Prompt` | Terminal prompt block | +| `Card` `CardGroup` | Content card and card grid | +| `Steps` | Numbered step list (`level` prop sets heading depth) | +| `Tabs` `TabsItem` | Tabbed content (`sync` for localStorage, `hash` for scroll-on-change) | +| `Accordion` `AccordionItem` | Collapsible accordion sections | +| `Collapsible` | Single collapsible section | +| `Field` `FieldGroup` | Form field display | +| `CodeGroup` | Tabbed code blocks | +| `CodeCollapse` | Collapsible code block | +| `CodeIcon` | File-type icon in code headers | +| `CodePreview` | Code + live rendered preview side by side | +| `CodeTree` | File tree display | +| `Script` | Script injection | + +## Content (Nuxt Content) + +| Component | Purpose | +| ---------------------- | ------------------------------- | +| `UContentNavigation` | Sidebar navigation from content | +| `UContentToc` | Table of contents | +| `UContentSurround` | Prev/next navigation | +| `UContentSearch` | Search command palette | +| `UContentSearchButton` | Trigger for content search | + +## Chat (AI) + +| Component | Purpose | +| ------------------- | ------------------------------ | +| `UChatMessages` | Scrollable message list | +| `UChatMessage` | Individual message bubble | +| `UChatReasoning` | Collapsible AI reasoning block | +| `UChatTool` | Tool invocation status | +| `UChatShimmer` | Streaming text animation | +| `UChatPrompt` | Enhanced textarea for prompts | +| `UChatPromptSubmit` | Submit button with status | +| `UChatPalette` | Chat layout for overlays | + +## Editor + +| Component | Purpose | +| ----------------------- | ------------------------------------- | +| `UEditor` | Rich text editor (JSON/HTML/Markdown) | +| `UEditorToolbar` | Toolbar (fixed/bubble/floating) | +| `UEditorDragHandle` | Block drag-and-drop | +| `UEditorSuggestionMenu` | Slash command menu | +| `UEditorMentionMenu` | @ mention menu | +| `UEditorEmojiMenu` | Emoji picker | + +## Color Mode + +| Component | Purpose | +| ------------------ | ----------------------------------- | +| `UColorModeButton` | Toggle button (light/dark) | +| `UColorModeSwitch` | Toggle switch (light/dark) | +| `UColorModeSelect` | Dropdown (light/dark/system) | +| `UColorModeAvatar` | Avatar that changes with color mode | +| `UColorModeImage` | Image that changes with color mode | diff --git a/agents/.agents/skills/nuxt-ui/references/guidelines/component-selection.md b/agents/.agents/skills/nuxt-ui/references/guidelines/component-selection.md new file mode 100644 index 0000000..d5558d5 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/guidelines/component-selection.md @@ -0,0 +1,130 @@ +# Component Selection + +Decision matrices for choosing the right component. When in doubt, use the MCP `search-components` tool. + +## Overlays + +| Need | Component | Why | +| ----------------------------------------- | ------------ | ------------------------------------------------------- | +| Confirmation dialog, focused task, form | `UModal` | Blocks page interaction, centered, draws focus | +| Detail panel, settings, secondary content | `USlideover` | Slides from edge, doesn't feel as interruptive as modal | +| Mobile-first bottom sheet | `UDrawer` | Natural mobile pattern, swipe to dismiss | +| Contextual info attached to a trigger | `UPopover` | No backdrop, positioned relative to trigger | +| Simple hover hint | `UTooltip` | Non-interactive, hover/focus only | + +### Rules + +- Use `UModal` for destructive confirmations ("Are you sure you want to delete?") +- Use `USlideover` for detail views in dashboards (email preview, user profile) +- Use `UDrawer` for mobile navigation or action sheets +- Use `UDashboardSidebar` with `mode="drawer"` for a drawer-based mobile menu; `UModal` and `USlideover` don't expose a `mode` prop +- For programmatic overlays, use `useOverlay()` instead of `v-model:open` +- Never put interactive content (buttons, links) inside `UTooltip` + +## Navigation + +| Need | Component | Why | +| --------------------------------------- | ----------------- | --------------------------------------------- | +| Primary site/app navigation | `UNavigationMenu` | Horizontal (header) or vertical (sidebar) | +| Switch between views on same page | `UTabs` | Content stays on page, no route change needed | +| Show current location in hierarchy | `UBreadcrumb` | Nested page structures | +| Search + keyboard-driven navigation | `UCommandPalette` | Power users, global search | +| Contextual actions on a trigger element | `UDropdownMenu` | Right-click menus, action buttons | +| Step-by-step process | `UStepper` | Multi-step forms, wizards | + +### Rules + +- Use `UNavigationMenu` with `orientation="vertical"` in sidebars, default horizontal in headers +- Use `UTabs` when switching views that don't need their own URL +- Use route-based navigation (`to` prop) when views should have shareable URLs +- `UCommandPalette` is typically opened via `Cmd+K` shortcut — use `defineShortcuts` to wire it up + +## Inputs + +| Need | Component | Why | +| ------------------------------------------- | ---------------- | ------------------------------------------------------ | +| Small fixed list (< 10 items) | `USelect` | Native-like, simple, lightweight | +| Searchable list, multiple selection, groups | `USelectMenu` | Rich dropdown with search, multi-select, grouped items | +| Autocomplete / combobox (type + select) | `UInputMenu` | User can type freely AND pick from suggestions | +| Free text entry | `UInput` | Plain text, email, password, search | +| Multi-line text | `UTextarea` | With `autoresize` and `maxrows` | +| Numeric value with +/- controls | `UInputNumber` | Min/max/step constraints | +| Date selection | `UInputDate` | Calendar dropdown, supports ranges | +| Time selection | `UInputTime` | Hour/minute picker, 12/24 hour | +| Tags / multi-value free text | `UInputTags` | Chip-style input with max limit | +| Verification code | `UPinInput` | Fixed-length code entry | +| Boolean toggle | `USwitch` | On/off, enable/disable | +| Boolean checkbox | `UCheckbox` | Single option with label | +| Multiple choices from a list | `UCheckboxGroup` | Multiple selection, vertical or horizontal | +| Single choice from a list (visible) | `URadioGroup` | All options visible, one selected | +| Range value | `USlider` | Min/max with visual track | +| Color value | `UColorPicker` | Hex/RGB/HSL picker | +| File upload | `UFileUpload` | Button or drop area variants | + +### Rules + +- Use `UAuthForm` for login/signup pages — handles fields, social providers, validation, and layout out of the box +- Use `USelect` for short, known lists (country, status, role) +- Use `USelectMenu` when the list is long or needs search +- Use `UInputMenu` when the user might want to type a value that's not in the list +- Wrap all form inputs in `UFormField` for labels, descriptions, hints, and validation errors +- Group related inline inputs with `UFieldGroup` + +## Feedback + +| Need | Component | Why | +| ----------------------------------- | ---------------- | -------------------------------------------------------- | +| Ephemeral notification after action | `useToast()` | Auto-dismisses, stacks, non-blocking | +| Inline persistent message | `UAlert` | Stays visible, in-page context | +| App-wide announcement | `UBanner` | Sticky top bar, dismissible | +| Loading state | `USkeleton` | Placeholder shimmer while loading | +| Progress indicator | `UProgress` | Determinate or indeterminate progress | +| Breakdown of a total | `UProgressGroup` | One bar split into colored segments that add up to `max` | + +### Rules + +- Use `useToast()` for action feedback: "Item saved", "Email sent", "Error occurred" +- Use `UAlert` for contextual warnings in forms or sections +- Use `UBanner` for site-wide messages (maintenance, new feature) +- Never use a toast for information the user needs to act on — use an alert or modal instead + +## Markdown + +When rendering Markdown (for instance with Comark), **prefer Prose components** — they are styled and tuned for Markdown contexts. Generic Nuxt UI components can also be used. `` (or ``) auto-resolves `ProseX` components when `@nuxt/ui` is installed. In Markdown, the `Prose` prefix can be omitted (`::callout`, `::steps`, etc.). + +| Need | Use | Not | +| -------------------- | --------------------------- | ----------------------------- | +| Note / warning / tip | `Callout` | `UAlert` | +| Tabbed content | `Tabs` + `TabsItem` | `UTabs` | +| Step-by-step list | `Steps` | custom list | +| Content card grid | `Card` + `CardGroup` | `UCard` | +| Collapsible section | `Collapsible` / `Accordion` | `UCollapsible` / `UAccordion` | +| Tabbed code blocks | `CodeGroup` | manual tabs | + +### Rules + +- Prose components use native Vue slots — Comark maps named `#slot` blocks directly to `` +- Theme via `appConfig.ui.prose.` using the same override pattern as other Nuxt UI components +- `Callout` colors: `neutral` (default), `primary`, `secondary`, `info`, `success`, `warning`, `error` + +## Layout containers + +| Need | Component | Why | +| ----------------------------------------- | ------------------------- | ---------------------------------------------------------------- | +| Grouped content with header/body/footer | `UCard` | Bordered/shadow container with slots | +| Rich content card with icon, badge, links | `UPageCard` | Extended card for grids — supports icon, badge, highlight, links | +| Marketing page section | `UPageSection` | Full-width section with headline, title, features | +| Page hero | `UPageHero` | Title + description + links + optional media | +| Call to action | `UPageCTA` | Highlighted section with action links | +| Feature grid | `UPageGrid` + `UPageCard` | Multi-column card grid | +| Centered content wrapper | `UContainer` | Max-width container | +| Collapsible section | `UCollapsible` | Animated expand/collapse | +| Accordion (multiple collapsibles) | `UAccordion` | FAQ, grouped collapsible content | +| Resizable side-by-side panes | `USplitter` | IDE-style layouts, resizable sidebars | + +### Rules + +- Don't overuse `UCard` — plain content with spacing is often better than wrapping everything in cards +- Use `UPageCard` instead of `UCard` when you need icon, badge, highlight, or links — it's designed for feature grids and landing pages +- Use `UPageSection` for marketing/landing page sections, not for app UI +- Use `UContainer` inside `UDashboardPanel` body for consistent content width diff --git a/agents/.agents/skills/nuxt-ui/references/guidelines/conventions.md b/agents/.agents/skills/nuxt-ui/references/guidelines/conventions.md new file mode 100644 index 0000000..47351aa --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/guidelines/conventions.md @@ -0,0 +1,379 @@ +# Conventions + +Coding patterns specific to Nuxt UI. + +## Auto-registered modules + +Nuxt UI automatically registers `@nuxt/icon`, `@nuxt/fonts`, and `@nuxtjs/color-mode`. Do **not** add them to your `modules` array. Configure them via root-level keys in `nuxt.config.ts`: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@nuxt/ui'], + icon: { /* @nuxt/icon options */ }, + fonts: { /* @nuxt/fonts options */ }, + colorMode: { /* @nuxtjs/color-mode options */ } +}) +``` + +Disable any of them: `ui: { fonts: false }`, `ui: { colorMode: false }`. + +## Content module integration + +When using `@nuxt/content`, it **must** come after `@nuxt/ui` in the `modules` array — otherwise prose components won't be available: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@nuxt/ui', '@nuxt/content'] +}) +``` + +Add `@source` in your CSS so Tailwind generates classes used in markdown/MDC: + +```css +/* app/assets/css/main.css */ +@import "tailwindcss"; +@import "@nuxt/ui"; + +@source "../../../content/**/*"; +``` + +Use `mapContentNavigation` to transform content navigation for components like `UBreadcrumb`: + +```ts +import { mapContentNavigation } from '@nuxt/ui/utils/content' +import { findPageBreadcrumb } from '@nuxt/content/utils' + +const breadcrumb = computed(() => + mapContentNavigation(findPageBreadcrumb(navigation.value, page.value?.path)) +) +``` + +## IDE setup + +Recommended `.vscode/settings.json` for Tailwind IntelliSense autocomplete with Nuxt UI: + +```json +{ + "files.associations": { "*.css": "tailwindcss" }, + "editor.quickSuggestions": { "strings": "on" }, + "tailwindCSS.classAttributes": ["class", "ui"], + "tailwindCSS.classFunctions": ["defineAppConfig"] +} +``` + +## UApp wrapper + +Always wrap your app in `UApp` — it provides: + +- Toast container (`useToast`) +- Tooltip provider +- Programmatic overlay context (`useOverlay`) +- i18n locale support + +```vue + + + +``` + +## Icons + +Nuxt UI registers `@nuxt/icon` automatically. Format: `i-{collection}-{name}`. Prefer `lucide` collection. + +```vue + + + +``` + +Install collections locally for reliable SSR and best performance: + +```bash +pnpm i @iconify-json/lucide +pnpm i @iconify-json/simple-icons +``` + +Custom local collections (Nuxt only): + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + icon: { + customCollections: [{ + prefix: 'custom', + dir: './app/assets/icons' + }] + } +}) +``` + +### Default icon overrides + +Components like `Modal`, `Select`, `Accordion`, etc. use default icons from `appConfig.ui.icons`. Override them globally: + +```ts +// app.config.ts +export default defineAppConfig({ + ui: { + icons: { + loading: 'i-lucide-refresh-cw', + close: 'i-lucide-x', + check: 'i-lucide-check', + chevronDown: 'i-lucide-chevron-down', + chevronRight: 'i-lucide-chevron-right', + arrowLeft: 'i-lucide-arrow-left', + arrowRight: 'i-lucide-arrow-right' + } + } +}) +``` + +## Slot patterns + +Most components follow consistent slot naming: + +| Slot | Used by | Purpose | +| ----------- | -------------------------------------- | ------------------------------- | +| `#header` | Card, Modal, Slideover, DashboardPanel | Top section | +| `#body` | DashboardPanel | Scrollable content area | +| `#footer` | Card, Modal, Slideover, DashboardPanel | Bottom section | +| `#left` | Page, DashboardNavbar | Left sidebar or content | +| `#right` | Page, DashboardNavbar, Header | Right sidebar or content | +| `#leading` | Input, Button, Alert | Before main content (icon area) | +| `#trailing` | Input, Button | After main content (icon area) | +| `#content` | Modal, Slideover, Popover, Tooltip | Full content override | +| `#default` | Most components | Main content area | + +## Items arrays + +Many components accept an `items` prop. Two patterns: + +**Flat array** — plain list: + +```ts +const items = [ + { label: 'Edit', icon: 'i-lucide-pencil' }, + { label: 'Delete', icon: 'i-lucide-trash', color: 'error' } +] +``` + +**Nested array** — groups with automatic separators between them: + +```ts +const items = [ + [ + { label: 'Edit', icon: 'i-lucide-pencil' }, + { label: 'Duplicate', icon: 'i-lucide-copy' } + ], + [ + { label: 'Delete', icon: 'i-lucide-trash', color: 'error' } + ] +] +``` + +Components supporting nested arrays: `UDropdownMenu`, `UContextMenu`, `UNavigationMenu`. +`UCommandPalette` uses a `groups` prop instead, with an `items` array on each group. + +## Composables + +### useToast + +```ts +const toast = useToast() + +toast.add({ + title: 'Success', + description: 'Item saved', + color: 'success', + icon: 'i-lucide-check-circle', + duration: 5000, + actions: [{ label: 'Undo', onClick: () => {} }] +}) + +toast.remove('toast-id') +toast.clear() +``` + +### useOverlay + +Programmatic modals, slideovers, drawers — no template `v-model` needed. See [overlays recipe](../recipes/overlays.md) for full patterns. + +```ts +const overlay = useOverlay() +const modal = overlay.create(MyComponent) +const instance = modal.open({ title: 'Confirm?' }) +if (await instance.result) { /* confirmed */ } +``` + +### defineShortcuts + +```ts +defineShortcuts({ + meta_k: () => openSearch(), + escape: () => close(), + meta_enter: { + handler: () => submit(), + whenever: [isFormValid] + } +}) +``` + +Keys: `meta` (Cmd/Ctrl), `ctrl`, `alt`, `shift`. Separator: `_`. + +### extractShortcuts + +Wire up keyboard shortcuts from menu items: + +```ts +const items = [ + { label: 'New file', kbds: ['meta', 'n'], onSelect: () => newFile() }, + { label: 'Save', kbds: ['meta', 's'], onSelect: () => save() } +] + +defineShortcuts(extractShortcuts(items)) +``` + +### Internationalization (i18n) + +Nuxt UI supports 50+ locales. Set the locale on `UApp` — all components inherit it. + +#### Static locale + +```vue + + + +``` + +#### Extend a built-in locale + +`extendLocale` is auto-imported. Override specific messages or the `code` (affects date/time formatting in Calendar, InputDate, InputTime): + +```ts +import { en } from '@nuxt/ui/locale' + +const locale = extendLocale(en, { + code: 'en-AU', + messages: { + commandPalette: { placeholder: 'Search a component...' } + } +}) +``` + +#### Custom locale from scratch + +```ts +import type { Messages } from '@nuxt/ui' + +const locale = defineLocale({ + name: 'My locale', + code: 'en', + dir: 'ltr', + messages: { + // all component message keys + } +}) +``` + +#### Dynamic locale with @nuxtjs/i18n + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@nuxt/ui', '@nuxtjs/i18n'], + i18n: { + locales: [ + { code: 'en', name: 'English' }, + { code: 'fr', name: 'Français' }, + { code: 'ar', name: 'العربية' } + ] + } +}) +``` + +```vue + + + +``` + +Each locale has a `dir` property (`'ltr'` or `'rtl'`). `UApp` uses it to set directionality on all components. Use `useHead` to propagate `lang` and `dir` to the `` element. + +## Color mode + +Nuxt UI registers `@nuxtjs/color-mode` automatically. Built-in components for switching: + +- `UColorModeButton` — single button toggle (light/dark) +- `UColorModeSwitch` — toggle switch +- `UColorModeSelect` — dropdown with system/light/dark options +- `UColorModeAvatar` — displays different avatar per mode +- `UColorModeImage` — displays different image per mode + +For custom color mode UI, use `useColorMode` with `ClientOnly` to avoid hydration mismatch: + +```vue + + + +``` + +## Official templates + +Bootstrap a project from a template instead of starting from scratch: + +```bash +npx nuxi@latest init -t ui # Starter +npx nuxi@latest init -t ui/dashboard # Dashboard +npx nuxi@latest init -t ui/docs # Docs (Nuxt Content) +npx nuxi@latest init -t ui/landing # Landing page +npx nuxi@latest init -t ui/saas # SaaS (landing + pricing + docs + blog) +npx nuxi@latest init -t ui/chat # AI chat (Vercel AI SDK) +npx nuxi@latest init -t ui/editor # Rich text editor +npx nuxi@latest init -t ui/portfolio # Portfolio +npx nuxi@latest init -t ui/changelog # Changelog +npx nuxi@latest init -t ui/calendar # Calendar +``` + +## Responsive patterns + +- Dashboard sidebar hides on mobile, shows a slideover/drawer via `UDashboardSidebar` `mode` prop +- `UHeader` body slot is the mobile menu content (shown when hamburger is tapped) +- Most components handle responsiveness automatically — avoid manual breakpoint classes unless needed +- Use `UPageAside` for sidebars that should hide below `lg` breakpoint diff --git a/agents/.agents/skills/nuxt-ui/references/guidelines/design-system.md b/agents/.agents/skills/nuxt-ui/references/guidelines/design-system.md new file mode 100644 index 0000000..b4c1cc9 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/guidelines/design-system.md @@ -0,0 +1,384 @@ +# Design System + +## Semantic colors + +Nuxt UI uses 7 semantic colors. Never use raw Tailwind palette colors in components — always use these semantic names. + +| Color | Default | When to use | +| ----------- | ------- | --------------------------------------------------- | +| `primary` | green | CTAs, active states, brand accent, links | +| `secondary` | blue | Secondary actions, complementary highlights | +| `success` | green | Success messages, confirmations, positive states | +| `info` | blue | Informational alerts, tips, neutral highlights | +| `warning` | yellow | Warnings, caution states, pending actions | +| `error` | red | Errors, destructive actions, validation failures | +| `neutral` | slate | Text, borders, backgrounds, disabled states, chrome | + +### Choosing colors for components + +- **Primary action** on a page (submit, save, confirm) → `color="primary"` +- **Secondary actions** (cancel, back, alternative) → `color="neutral"` with `variant="outline"` or `"ghost"` +- **Destructive actions** (delete, remove) → `color="error"` +- **Status indicators** → match the semantic meaning: `success`, `warning`, `error`, `info` +- **Navigation and chrome** → `color="neutral"` + +### Configuring colors + +```ts +// Nuxt — app.config.ts +export default defineAppConfig({ + ui: { + colors: { + primary: 'indigo', + secondary: 'violet', + success: 'emerald', + error: 'rose', + neutral: 'zinc' + } + } +}) +``` + +```ts +// Vue — vite.config.ts +ui({ + ui: { + colors: { primary: 'indigo', secondary: 'violet', neutral: 'zinc' } + } +}) +``` + +Only colors that exist in your theme work — either Tailwind's defaults or custom colors defined with `@theme`. + +Available color palettes: + +- **Standard Tailwind**: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose +- **Neutral palettes** (for `neutral` key — pick one that matches the aesthetic): + - `slate` — cool blue-gray, professional (default) + - `gray` — true neutral, clean + - `zinc` — slightly cool, modern, techy + - `neutral` — perfectly balanced + - `stone` — warm gray, earthy + - `taupe` — warm brown-gray, sophisticated + - `mauve` — purple-tinted gray, elegant + - `mist` — soft blue-gray, airy + - `olive` — green-tinted gray, natural + +### Adding custom brand colors + +1. Define all 11 shades in CSS: + +```css +/* app/assets/css/main.css */ +@theme static { + --color-brand-50: #fef2f2; + --color-brand-100: #fee2e2; + --color-brand-200: #fecaca; + --color-brand-300: #fca5a5; + --color-brand-400: #f87171; + --color-brand-500: #ef4444; + --color-brand-600: #dc2626; + --color-brand-700: #b91c1c; + --color-brand-800: #991b1b; + --color-brand-900: #7f1d1d; + --color-brand-950: #450a0a; +} +``` + +2. Assign it: `ui: { colors: { primary: 'brand' } }` + +### Extending with new semantic color names + +To add a color beyond the 7 defaults (e.g., `tertiary`), register it in `theme.colors`: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + ui: { + theme: { + colors: ['primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error'] + } + } +}) +``` + +## Semantic utility classes + +Use these everywhere instead of raw palette colors: + +### Text + +- `text-default` — primary body text +- `text-muted` — secondary text (descriptions, hints) +- `text-toned` — medium-emphasis text (between muted and default) +- `text-dimmed` — tertiary text (placeholders, disabled) +- `text-highlighted` — emphasized text (headings, important labels) +- `text-inverted` — text on inverted backgrounds (pair with `bg-inverted`) + +### Backgrounds + +- `bg-default` — page background +- `bg-muted` — subtle backgrounds (hover states, alternating rows) +- `bg-elevated` — raised surfaces (cards, dropdowns) +- `bg-accented` — accent backgrounds (active states, selected items) +- `bg-inverted` — inverse background (dark on light, light on dark) + +### Borders + +- `border-default` — standard borders +- `border-muted` — subtle borders (dividers, separators) +- `border-accented` — accent borders (active states) +- `border-inverted` — inverse borders + +## Variants + +Most components accept a `variant` prop. Choose based on visual weight: + +| Variant | Weight | When to use | +| --------- | ---------- | ---------------------------------------------- | +| `solid` | Highest | Primary actions, main CTAs | +| `outline` | Medium | Secondary actions, form fields | +| `soft` | Medium-low | Tags, badges, subtle buttons | +| `subtle` | Low | Background highlights, less prominent actions | +| `ghost` | Lowest | Inline actions, icon buttons, navigation items | +| `link` | Lowest | Text-only links inside content | + +### Rules + +- **One solid primary button per view** — everything else should be lower weight +- **Destructive buttons** use `color="error"` but not necessarily `variant="solid"` — use `variant="soft"` or `"outline"` unless it's the primary action on a confirmation dialog +- **Button groups** should use consistent variants — don't mix `solid` and `outline` siblings + +## Customizing components + +### `ui` prop + +Override theme **slots** on a single instance — wins over global config and variants. + +```vue + + +``` + +Rules for `ui` overrides: + +- **Prefer `defaultVariants`** over slot class overrides when possible (e.g., changing default button variant/size). +- **Don't duplicate default classes** — check the generated theme file first to see what's already there. +- Border radius defaults come from `--ui-radius`, but you can override with `rounded-*` classes in `ui` or `class` when you need a specific radius on a component. + +### `class` prop + +Override the **root** (or `base`) slot only — simpler than `ui` for single-slot changes. + +```vue + +``` + +### Finding slot names + +Read the generated theme file for any component: + +- **Nuxt**: `.nuxt/ui/.ts` +- **Vue**: `node_modules/.nuxt-ui/ui/.ts` + +These files show every available slot name, variant combination, and default class. + +### Global config + +Override `slots`, `variants`, `compoundVariants`, and `defaultVariants` globally in `app.config.ts` (Nuxt) or `vite.config.ts` (Vue): + +```ts +// Nuxt — app.config.ts +export default defineAppConfig({ + ui: { + button: { + slots: { + base: 'font-bold' + }, + compoundVariants: [{ + color: 'neutral', + variant: 'outline', + class: 'ring-default hover:bg-accented' + }], + defaultVariants: { + color: 'neutral', + variant: 'outline' + } + } + } +}) +``` + +Tailwind Variants uses `tailwind-merge` under the hood — conflicting classes are resolved automatically. + +### Replace instead of merge + +Classes from the `ui` prop, the `class` prop, and global config are merged onto the component defaults. To replace them instead, set the slot to a function, which receives the default classes as its argument so you can reuse part of them. + +In global config it replaces the slot's own classes, so `variants` and `compoundVariants` still apply on top. In the `ui` and `class` props it runs after the variants, so it replaces the resolved classes, variants included. + +```vue + +``` + +```ts +// app.config.ts, applies to every instance +export default defineAppConfig({ + ui: { + button: { + slots: { + label: () => 'text-base font-bold' + } + } + } +}) +``` + +### Theme component + +Override theme for a section of the component tree without affecting the rest of the app. Renders no DOM element — uses `provide`/`inject`: + +```vue + + + + +``` + +### Global `defaultVariants` + +Override default `size` and `color` for **all** components at once: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + ui: { + theme: { + defaultVariants: { + size: 'lg', + color: 'neutral' + } + } + } +}) +``` + +### `theme.transitions` + +Controls whether interactive components get `transition-colors`. Enabled by default. + +```ts +// nuxt.config.ts — disable transitions +export default defineNuxtConfig({ + ui: { + theme: { + transitions: false + } + } +}) +``` + +### `theme.prefix` + +When using Tailwind CSS with a prefix, configure the same prefix in Nuxt UI so component classes match: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + ui: { + theme: { + prefix: 'tw' + } + } +}) +``` + +```css +/* app/assets/css/main.css */ +@import "tailwindcss" prefix(tw); +@import "@nuxt/ui"; +``` + +### Tree-shaking with `experimental.componentDetection` + +Enable automatic component detection to only generate CSS for components you actually use: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + ui: { + experimental: { + componentDetection: true + } + } +}) +``` + +For dynamic components (e.g., ``), pass an array of component names to guarantee they're included: + +```ts +componentDetection: ['Modal', 'Dropdown', 'Popover'] +``` + +## CSS `@theme` customization + +Customize Tailwind design tokens in `main.css`: + +### Fonts + +```css +@theme { + --font-sans: 'Public Sans', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', monospace; +} +``` + +In Nuxt, fonts defined here are automatically loaded by `@nuxt/fonts`. + +### Breakpoints + +```css +@theme { + --breakpoint-3xl: 1920px; +} +``` + +## CSS variables + +Nuxt UI exposes CSS variables you can override in `main.css`: + +```css +:root { + --ui-radius: 0.25rem; + --ui-container: 80rem; + --ui-header-height: 4rem; +} +``` + +### Color shade overrides + +Each semantic color defaults to shade 500 in light mode, 400 in dark mode. Override per-mode: + +```css +:root { + --ui-primary: var(--ui-color-primary-700); +} +.dark { + --ui-primary: var(--ui-color-primary-200); +} +``` + +You can use `var(--ui-color--)` to reference shades from the active palette (e.g., `var(--ui-color-neutral-800)` maps to whichever neutral palette is configured). + +### Black/white as primary + +`black` and `white` have no shades, so they can't be used in config. Set them directly: + +```css +:root { + --ui-primary: black; +} +.dark { + --ui-primary: white; +} +``` diff --git a/agents/.agents/skills/nuxt-ui/references/guidelines/forms.md b/agents/.agents/skills/nuxt-ui/references/guidelines/forms.md new file mode 100644 index 0000000..a01ef4e --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/guidelines/forms.md @@ -0,0 +1,235 @@ +# Forms + +## Basic pattern + +Nuxt UI forms use `UForm` + `UFormField` + Standard Schema validation (Zod, Valibot, Yup, or Joi). + +```vue + + + +``` + +## Key rules + +- Always use `UFormField` around inputs — it connects validation errors via the `name` prop +- The `name` prop on `UFormField` must match the schema field name exactly +- Use `reactive>({})` for state — `Partial` allows empty initial values +- `@submit` only fires when validation passes +- For nested objects, use dot notation: `name="address.city"` + +## UFormField props + +| Prop | Purpose | +| ------------- | ------------------------------------------- | +| `name` | Links to schema field for validation errors | +| `label` | Visible label text | +| `description` | Help text below the input | +| `hint` | Right-aligned hint text (e.g., "Optional") | +| `required` | Shows required indicator | +| `size` | Inherits to child input | + +## Field layout patterns + +### Vertical stack (default) + +```vue + + + + + + + + +``` + +### Inline fields with UFieldGroup + +```vue + + + + + + + + +``` + +### Grid layout + +```vue + + + + + + + + + + + +``` + +## Common field patterns + +### Select + +```vue + + + +``` + +### Checkbox + +```vue + + + +``` + +### Radio group + +```vue + + + +``` + +### Switch + +```vue + + + +``` + +### Textarea + +```vue + + + +``` + +### File upload + +```vue + + + + + + + + +``` + +### Date + +```vue + + + + + + + + +``` + +## Programmatic validation + +```vue + + + +``` + +By default, `validate()` throws a `FormValidationException` when validation fails. Pass `{ silent: true }` when you want it to return `false` instead. Use `clear()` to remove validation errors. + +## Form in a modal + +Use `#footer="{ close }"` scoped slot for cancel/submit actions. Wrap the modal body in `UForm` with a `type="submit"` button in the footer so validation runs on submit. + +```vue + + + + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/layouts/chat.md b/agents/.agents/skills/nuxt-ui/references/layouts/chat.md new file mode 100644 index 0000000..070f4ab --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/layouts/chat.md @@ -0,0 +1,263 @@ +# Chat Layout + +Build AI chat interfaces with message streams, reasoning, tool calling, and Vercel AI SDK integration. + +## When to use + +- AI chatbot interfaces +- Customer support chat +- Any conversational UI with streaming responses + +## Setup + +### Install dependencies + +**Nuxt:** + +```bash +pnpm add ai @ai-sdk/gateway @ai-sdk/vue @comark/nuxt +``` + +**Vue (Vite):** + +```bash +pnpm add ai @ai-sdk/gateway @ai-sdk/vue @comark/vue +``` + +### Register Comark module + +**Nuxt:** + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + modules: [ + '@nuxt/ui', + '@comark/nuxt' + ] +}) +``` + +**Vue (Vite):** No module registration needed, import directly from `@comark/vue`. + +> `@comark/nuxt` (or `@comark/vue` for Vue projects) provides the `Comark` component used to render AI responses as streaming Markdown, it incrementally renders tokens as they arrive and automatically enables Nuxt UI's prose components. + +### Dark mode for syntax highlighting + +When using the `highlight` plugin, add the following CSS to your stylesheet: + +```css [main.css] +html.dark .shiki span { + color: var(--shiki-dark) !important; + background-color: var(--shiki-dark-bg) !important; + font-style: var(--shiki-dark-font-style) !important; + font-weight: var(--shiki-dark-font-weight) !important; + text-decoration: var(--shiki-dark-text-decoration) !important; +} +``` + +### Server endpoint + +Using [Vercel AI Gateway](https://vercel.com/ai-gateway) (recommended): + +```ts [server/api/chat.post.ts] +import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai' +import { gateway } from '@ai-sdk/gateway' + +export default defineEventHandler(async (event) => { + const { messages } = await readBody(event) + + const result = streamText({ + model: gateway('anthropic/claude-sonnet-5'), + instructions: 'You are a helpful assistant.', + messages: await convertToModelMessages(messages) + }) + + const stream = toUIMessageStream({ stream: result.stream }) + return createUIMessageStreamResponse({ stream }) +}) +``` + +Or with a direct provider (e.g., `pnpm add @ai-sdk/openai`): + +```ts [server/api/chat.post.ts] +import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai' +import { openai } from '@ai-sdk/openai' + +export default defineEventHandler(async (event) => { + const { messages } = await readBody(event) + + const result = streamText({ + model: openai('gpt-5-nano'), + instructions: 'You are a helpful assistant.', + messages: await convertToModelMessages(messages) + }) + + const stream = toUIMessageStream({ stream: result.stream }) + return createUIMessageStreamResponse({ stream }) +}) +``` + +## Component tree + +``` +UDashboardPanel +├── #header → UDashboardNavbar +├── #body → UContainer → UChatMessages +│ ├── #content → UChatReasoning, UChatTool, Comark +│ └── #indicator (loading) +└── #footer → UContainer → UChatPrompt + └── UChatPromptSubmit +``` + +## Full page chat + +```vue [pages/chat/[id].vue] + + + +``` + +## Key components + +- `UChatMessages` — scrollable message list with auto-scroll. Props: `messages`, `status`. Slots: `#content` (per message), `#actions`, `#indicator`. +- `UChatMessage` — individual bubble. Props: `message`, `side` (`'left'`/`'right'`). +- `UChatReasoning` — collapsible reasoning block. Auto-opens during streaming, auto-closes when done. Use `isPartStreaming(part)` from `@nuxt/ui/utils/ai`. +- `UChatTool` — tool invocation status. Use `isToolStreaming(part)`. Variants: `'inline'` (default), `'card'`. +- `UChatPrompt` — enhanced textarea for chat. Its typed API exposes a selected subset of `UTextarea` props, forwards additional attributes to the underlying textarea, and adds chat-specific props such as `error` and `submitOnEnter`. +- `UChatPromptSubmit` — submit button with automatic status handling (send/stop/reload). +- `UChatPalette` — layout wrapper for chat inside overlays. + +## Chat in a modal + +```vue + + + +``` + +## With model selector + +```vue + + + + + +``` + +## Conversation sidebar + +Combine with dashboard layout for a ChatGPT-like interface: + +```vue [layouts/dashboard.vue] + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/layouts/dashboard.md b/agents/.agents/skills/nuxt-ui/references/layouts/dashboard.md new file mode 100644 index 0000000..ef3934a --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/layouts/dashboard.md @@ -0,0 +1,233 @@ +# Dashboard Layout + +Build admin interfaces with resizable sidebars, multi-panel layouts, and toolbars. + +## When to use + +- Admin panels, back-office UIs +- Email clients, project management tools +- Any app with a persistent sidebar and content panels +- Combine with chat or editor layouts for specialized dashboards + +## Component tree + +``` +UApp +└── NuxtLayout (dashboard) + └── UDashboardGroup + ├── UDashboardSidebar + │ ├── #header (logo, search button) + │ ├── #default (navigation) — receives { collapsed } slot prop + │ └── #footer (user menu) + └── NuxtPage + └── UDashboardPanel + ├── #header → UDashboardNavbar + UDashboardToolbar + ├── #body (scrollable content) + └── #footer (optional) +``` + +## Layout + +```vue [layouts/dashboard.vue] + + + +``` + +## Page + +```vue [pages/dashboard/index.vue] + + + +``` + +### Common mistakes + +- Forgetting `definePageMeta({ layout: 'dashboard' })` — the page won't use the dashboard layout without it. +- Putting content directly in `UDashboardPanel` without using `#body` slot — content won't scroll properly. +- Not handling the `collapsed` slot prop — sidebar content should adapt when collapsed (hide labels, center icons). + +## Key components + +### DashboardGroup + +Root wrapper. Manages sidebar state and persistence. + +| Prop | Default | Purpose | +| ------------- | ------------- | ------------------------------------- | +| `storage` | `'cookie'` | `'cookie'`, `'localStorage'`, `false` | +| `storage-key` | `'dashboard'` | Storage key name | + +### DashboardSidebar + +Resizable, collapsible sidebar. Must be inside `DashboardGroup`. + +| Prop | Default | Purpose | +| ------------- | ------------- | -------------------------------------------- | +| `resizable` | `false` | Drag to resize | +| `collapsible` | `false` | Collapse when dragged to edge | +| `side` | `'left'` | `'left'` or `'right'` | +| `mode` | `'slideover'` | Mobile: `'modal'`, `'slideover'`, `'drawer'` | + +All slots receive `{ collapsed, collapse }` — `collapsed` is the boolean state, `collapse(value)` toggles it programmatically. Use `v-model:collapsed` and `v-model:open` (mobile) for state control. + +### DashboardPanel + +Content panel with `#header`, `#body` (scrollable), `#footer`, and `#default` (raw, no scroll) slots. + +### DashboardNavbar / DashboardToolbar + +Navbar: `#leading`, `#left`, `#default`, `#right` slots + `title` prop. Use `UDashboardSidebarCollapse` in `#leading` to toggle sidebar on mobile. +Toolbar: same slots, sits below navbar for filters/actions. + +### UNavigationMenu in sidebar + +Always pass `:collapsed="collapsed"` to `UNavigationMenu` inside a collapsible sidebar — it auto-hides labels and centers icons. Use `NavigationMenuItem[][]` (array of arrays) for separate groups (main nav + footer links). + +## Multi-panel (list-detail) + +```vue [pages/dashboard/inbox.vue] + + + +``` + +## With toolbar + +```vue + + + +``` + +## With search + +```vue [layouts/dashboard.vue] + +``` + +## Right sidebar + +```vue + + + + + + + + + + + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/layouts/docs.md b/agents/.agents/skills/nuxt-ui/references/layouts/docs.md new file mode 100644 index 0000000..e539897 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/layouts/docs.md @@ -0,0 +1,152 @@ +# Docs Layout + +Build documentation sites with sidebar navigation, table of contents, and surround links. + +## When to use + +- Technical documentation sites +- Knowledge bases, help centers +- Any content-heavy site with hierarchical navigation + +> Requires `@nuxt/content` — see [conventions](../guidelines/conventions.md#content-module-integration) for setup (module order + `@source`). + +## Component tree + +``` +UApp +├── UHeader +├── UMain +│ └── NuxtLayout (docs) +│ └── UPage +│ ├── #left → UPageAside → UContentNavigation +│ └── NuxtPage +│ ├── UPageHeader +│ ├── UPageBody → ContentRenderer + UContentSurround +│ └── #right → UContentToc +└── UFooter +``` + +## App shell + +```vue [app.vue] + + + +``` + +## Layout + +```vue [layouts/docs.vue] + + + +``` + +## Page + +```vue [pages/docs/[...slug].vue] + + + +``` + +### How nesting works + +The outer `UPage` in the layout handles the **left sidebar**. The inner `UPage` in the page handles the **right sidebar**. They nest correctly — this is intentional. + +### Common mistakes + +- Not providing navigation via `provide`/`inject` — the layout needs it from the app shell. +- Forgetting `UContentSearch` in app.vue — search won't work without it. +- Using `UContentSearchButton` without `UContentSearch` — the button opens search, but the search component must exist. + +## Key components + +- `UPage` — multi-column grid with `#left`, `#default`, `#right` slots +- `UPageAside` — sticky sidebar wrapper (visible from `lg` breakpoint) +- `UContentNavigation` — sidebar navigation tree from Nuxt Content +- `UContentToc` — table of contents from page headings +- `UContentSurround` — prev/next links +- `UContentSearch` / `UContentSearchButton` — search command palette +- `UPageAnchors` — simpler alternative to full TOC diff --git a/agents/.agents/skills/nuxt-ui/references/layouts/editor.md b/agents/.agents/skills/nuxt-ui/references/layouts/editor.md new file mode 100644 index 0000000..eb224a6 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/layouts/editor.md @@ -0,0 +1,153 @@ +# Editor Layout + +Build a rich text editor with toolbars, slash commands, mentions, and drag-and-drop. + +## When to use + +- Note-taking apps, CMS editors +- Collaborative editing interfaces +- Any rich text editing need (supports JSON, HTML, and Markdown) + +## Component tree + +``` +UEditor +├── UEditorToolbar (fixed / bubble / floating) +├── UEditorDragHandle +├── UEditorSuggestionMenu +├── UEditorMentionMenu +└── UEditorEmojiMenu +``` + +## Basic editor + +```vue + + + +``` + +> If you encounter prosemirror-related errors, add prosemirror packages to `vite.optimizeDeps.include` in `nuxt.config.ts`. + +## Key components + +- `UEditor` — rich text editor. `v-model` accepts JSON (default), HTML, or Markdown via `content-type` prop. Default slot provides `{ editor, handlers }` — `editor` is the Tiptap instance, `handlers` contains action functions for toolbar/menus. +- `UEditorToolbar` — toolbar with `layout`: `'fixed'` (default), `'bubble'` (on selection), `'floating'` (on empty lines). +- `UEditorDragHandle` — block drag-and-drop handle. +- `UEditorSuggestionMenu` — slash command menu (type `/` to open). +- `UEditorMentionMenu` — `@` mention menu. +- `UEditorEmojiMenu` — emoji picker (type `:` to open). + +## Toolbar modes + +```vue + + + + + + + + +``` + +## Content types + +```vue + + + + + + + + +``` + +## With document sidebar + +Combine with Dashboard layout for a multi-document editor: + +```vue [layouts/editor.vue] + +``` + +```vue [pages/editor/[id].vue] + + + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/layouts/landing.md b/agents/.agents/skills/nuxt-ui/references/layouts/landing.md new file mode 100644 index 0000000..a0d231a --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/layouts/landing.md @@ -0,0 +1,185 @@ +# Landing Page Layout + +Build public-facing pages — landing, blog, changelog, pricing — using the Header + Main + Footer shell with Page components. + +## When to use + +- Marketing sites, product pages, company sites +- Blog and content pages +- Pricing, changelog, portfolio pages +- Any public-facing page that isn't a dashboard or documentation + +## App shell + +```vue [app.vue] + + + +``` + +### Common mistakes + +- Forgetting the `#body` slot on `UHeader` — this is the mobile menu content. Without it, mobile users have no navigation. +- Using `variant="solid"` for both header and hero buttons — the header button should be lower weight than the hero CTA. + +## Landing page + +```vue [pages/index.vue] + +``` + +## Key components + +- `UPageHero` — hero with title, description, links, and optional media. Use `orientation="horizontal"` for side-by-side layout. +- `UPageSection` — content section with headline, title, description, and `features` grid. Use `id` for anchor links. +- `UPageCTA` — call to action block. +- `UPageGrid` / `UPageCard` — card grid for features, testimonials, etc. +- `UPageFeature` — individual feature item. +- `UPageLogos` — logo wall for social proof. +- `UPricingPlans` / `UPricingTable` — pricing cards and comparison tables. +- `UFooterColumns` — multi-column footer with link groups (used inside `UFooter`). + +## Variations + +### Alternating feature sections + +```vue + + + + + + + +``` + +### Blog listing + +```vue [pages/blog/index.vue] + + + +``` + +### Changelog + +```vue [pages/changelog.vue] + + + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/recipes/auth.md b/agents/.agents/skills/nuxt-ui/references/recipes/auth.md new file mode 100644 index 0000000..5f3b87f --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/recipes/auth.md @@ -0,0 +1,162 @@ +# Auth Forms + +## UAuthForm (recommended) + +`UAuthForm` provides a complete auth form with fields, providers, validation, and submit — no manual `UForm` + `UFormField` wiring needed. Wrap it in `UPageCard` for a polished look. + +```vue [pages/login.vue] + + + +``` + +### UAuthForm key props + +| Prop | Purpose | +| ------------------------------ | ------------------------------------------------------------------------------- | +| `title`, `description`, `icon` | Header content | +| `fields` | `AuthFormField[]` — each has `name`, `type`, `label`, `placeholder`, `required` | +| `providers` | `ButtonProps[]` — social login buttons shown above/below the form | +| `schema` | Zod/Valibot schema for validation | +| `submit` | Customize submit button: `{ label: 'Sign in', block: true }` | +| `separator` | Text between providers and fields (default: `'or'`) | + +### UAuthForm key slots + +| Slot | Purpose | +| ---------------- | --------------------------------------------- | +| `#description` | Override description (e.g., add sign-up link) | +| `#password-hint` | "Forgot password?" link on password field | +| `#validation` | Custom error display (e.g., `UAlert`) | +| `#footer` | Terms of service, sign-up link | +| `#-field` | Override a specific field's rendering | + +## Custom auth layout + +For layouts where `UAuthForm` is too opinionated, use `UCard` + `UForm` + `UFormField` directly. + +```vue [pages/login.vue] + + + +``` + +## Tips + +- Prefer `UAuthForm` with `UPageCard` for standard auth pages — handles layout, providers, validation, and submit +- Use `import * as z from 'zod'` and `z.email()` (Zod 4 syntax) +- Type the submit handler: `function onSubmit(event: FormSubmitEvent)` — access validated data via `event.data` +- Center auth forms with `flex min-h-dvh items-center justify-center` +- Place "Forgot password?" link as `#password-hint` slot on `UAuthForm`, or `#hint` slot on `UFormField` +- Social login buttons: use `providers` prop on `UAuthForm`, or add manually with `` diff --git a/agents/.agents/skills/nuxt-ui/references/recipes/data-tables.md b/agents/.agents/skills/nuxt-ui/references/recipes/data-tables.md new file mode 100644 index 0000000..f5c81f8 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/recipes/data-tables.md @@ -0,0 +1,225 @@ +# Data Tables + +Complete patterns for displaying and managing tabular data. + +## Basic table + +```vue + + + +``` + +## With search and filters (dashboard) + +```vue + + + +``` + +## With row selection + +Row selection uses TanStack Table's `rowSelection` state — a `Record` keyed by row index. + +```vue + + + +``` + +Add a checkbox column using the `h` function. Use tri-state `modelValue` (`true`, `false`, or `'indeterminate'`) for the "select all" header: + +```ts +import { h } from 'vue' + +const UCheckbox = resolveComponent('UCheckbox') + +const columns: TableColumn[] = [{ + id: 'select', + header: ({ table }) => h(UCheckbox, { + 'modelValue': table.getIsSomePageRowsSelected() ? 'indeterminate' : table.getIsAllPageRowsSelected(), + 'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value), + 'aria-label': 'Select all' + }), + cell: ({ row }) => h(UCheckbox, { + 'modelValue': row.getIsSelected(), + 'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value), + 'aria-label': 'Select row' + }) +}, +// ... other columns +] +``` + +## With pagination + +Use `v-model:pagination` on `UTable` with TanStack's `getPaginationRowModel`, then wire `UPagination` to the table API. `UPagination`'s `total` is total **items** (not pages) — it calculates page count from `total / items-per-page`. + +```vue + + + +``` + +## With async data (Nuxt) + +Use `status === 'pending' || status === 'idle'` for loading state — `idle` covers the initial render before `useLazyFetch` starts. + +```vue + + + +``` + +For server-side pagination: + +```vue + + + +``` + +## Tips + +- Table is built on [TanStack Table v8](https://tanstack.com/table/v8) — columns use `ColumnDef` format with `accessorKey`, `header`, `cell` +- Use `#-cell` and `#-header` template slots to customize rendering with Vue templates +- Alternatively, use the `h` function inside `header` and `cell` column properties for inline rendering +- Row data in slots is accessed via `row.original` (not `row` directly) +- Use `v-model:row-selection` for selection, `v-model:sorting` for sort state +- Wrap tables in `UDashboardPanel` with `#header` toolbar for the dashboard pattern +- For empty states, use the `#empty` slot diff --git a/agents/.agents/skills/nuxt-ui/references/recipes/navigation.md b/agents/.agents/skills/nuxt-ui/references/recipes/navigation.md new file mode 100644 index 0000000..0e362f2 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/recipes/navigation.md @@ -0,0 +1,90 @@ +# Navigation + +Patterns for headers, sidebars, breadcrumbs, and tab navigation. + +## Header with mobile menu + +`UHeader` default slot is desktop nav, `#body` is the mobile menu. Without `#body`, mobile users have no navigation. + +```vue + + + + + + + + + +``` + +> Full app shell example in [landing layout](../layouts/landing.md). + +## Sidebar navigation (dashboard) + +See [dashboard layout](../layouts/dashboard.md) for the full sidebar pattern with `UDashboardSidebar` + `UNavigationMenu`. Key points: + +- Pass `:collapsed="collapsed"` to `UNavigationMenu` inside collapsible sidebars +- Use `NavigationMenuItem[][]` (nested arrays) for separate nav groups +- Use `#footer` slot for user menu with `UDropdownMenu` + +## Breadcrumbs + +```vue + + + +``` + +## Tab navigation (within a page) + +```vue + + + +``` diff --git a/agents/.agents/skills/nuxt-ui/references/recipes/overlays.md b/agents/.agents/skills/nuxt-ui/references/recipes/overlays.md new file mode 100644 index 0000000..9d5d4e5 --- /dev/null +++ b/agents/.agents/skills/nuxt-ui/references/recipes/overlays.md @@ -0,0 +1,172 @@ +# Overlays + +Patterns for modals, slideovers, drawers, and command palettes. + +## Confirmation dialog + +```vue + + + +``` + +## Programmatic confirmation (useOverlay) + +Reusable pattern — no template state needed at the call site. + +```vue [components/ConfirmModal.vue] + + + +``` + +```ts +// Usage anywhere +const overlay = useOverlay() +const confirm = overlay.create(ConfirmModal) + +async function deleteItem(item) { + const instance = confirm.open({ + title: 'Delete item', + description: `Are you sure you want to delete "${item.name}"?` + }) + + if (await instance.result) { + // user confirmed + } +} +``` + +## Form in a slideover + +```vue + + + +``` + +## Command palette + +```vue + + + +``` + +## Drawer (bottom sheet) + +```vue + + + +``` diff --git a/agents/.agents/skills/nuxt/SKILL.md b/agents/.agents/skills/nuxt/SKILL.md new file mode 100644 index 0000000..37e60bd --- /dev/null +++ b/agents/.agents/skills/nuxt/SKILL.md @@ -0,0 +1,57 @@ +--- +name: nuxt +description: Nuxt full-stack Vue framework with SSR, auto-imports, and file-based routing. Use when working with Nuxt apps, server routes, useFetch, middleware, or hybrid rendering. +metadata: + author: Anthony Fu + version: "2026.6.22" + source: Generated from https://github.com/nuxt/nuxt, scripts located at https://github.com/antfu/skills +--- + +Nuxt is a full-stack Vue framework that provides server-side rendering, file-based routing, auto-imports, and a powerful module system. It uses Nitro as its server engine for universal deployment across Node.js, serverless, and edge platforms. + +> The skill is based on Nuxt 4.x, generated at 2026-06-22. + +> **Nuxt 4 note:** the default `srcDir` is `app/` — Vue app code (`app.vue`, `components/`, `composables/`, `pages/`, etc.) lives under `app/`, while `server/`, `shared/`, `public/`, `modules/`, `layers/` and `nuxt.config.ts` stay at the project root. The `~`/`@` aliases now point at `app/`; use `~~`/`@@` for the root. + +## Core + +| Topic | Description | Reference | +| ------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| Directory Structure | Nuxt 4 `app/` srcDir, `shared/`, aliases, conventions | [core-directory-structure](references/core-directory-structure.md) | +| Configuration | nuxt.config.ts, app.config.ts, aliases, compatibilityVersion, experimental | [core-config](references/core-config.md) | +| CLI Commands | Dev server, build, generate, preview, and utility commands | [core-cli](references/core-cli.md) | +| Routing | File-based routing, dynamic routes, named views, layout props, middleware | [core-routing](references/core-routing.md) | +| Data Fetching | useFetch, useAsyncData, $fetch, createUseFetch factories, caching | [core-data-fetching](references/core-data-fetching.md) | +| Modules | Creating and using Nuxt modules, Nuxt Kit utilities | [core-modules](references/core-modules.md) | +| Deployment | Platform-agnostic deployment with Nitro, Vercel, Netlify, Cloudflare | [core-deployment](references/core-deployment.md) | + +## Features + +| Topic | Description | Reference | +| ------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| Composables Auto-imports | Vue/Nuxt composables, custom composables, `shared/`, useAnnouncer | [features-composables](references/features-composables.md) | +| Components Auto-imports | Component naming, lazy loading, hydration strategies | [features-components-autoimport](references/features-components-autoimport.md) | +| Built-in Components | NuxtLink, NuxtPage, NuxtLayout, NuxtAnnouncer, ClientOnly, and more | [features-components](references/features-components.md) | +| State Management | useState composable, SSR-friendly state, Pinia integration | [features-state](references/features-state.md) | +| Server Routes | API routes, server middleware, Nitro server engine | [features-server](references/features-server.md) | + +## Rendering + +| Topic | Description | Reference | +| --------------- | ----------------------------------------------------------------- | ------------------------------------------------ | +| Rendering Modes | Universal (SSR), client-side (SPA), hybrid rendering, route rules | [rendering-modes](references/rendering-modes.md) | + +## Best Practices + +| Topic | Description | Reference | +| ---------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------- | +| Data Fetching Patterns | Efficient fetching, caching, parallel requests, error handling | [best-practices-data-fetching](references/best-practices-data-fetching.md) | +| SSR & Hydration | Avoiding context leaks, hydration mismatches, composable patterns | [best-practices-ssr](references/best-practices-ssr.md) | + +## Advanced + +| Topic | Description | Reference | +| ---------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | +| Layers | Extending applications with reusable layers | [advanced-layers](references/advanced-layers.md) | +| Lifecycle Hooks | Build-time, runtime, and server hooks | [advanced-hooks](references/advanced-hooks.md) | +| Module Authoring | Publishable modules with Nuxt Kit, keyed composables, dependencies | [advanced-module-authoring](references/advanced-module-authoring.md) | diff --git a/agents/.agents/skills/nuxt/references/advanced-hooks.md b/agents/.agents/skills/nuxt/references/advanced-hooks.md new file mode 100644 index 0000000..6106c07 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/advanced-hooks.md @@ -0,0 +1,290 @@ +--- +name: lifecycle-hooks +description: Nuxt and Nitro hooks for extending build-time and runtime behavior +--- + +# Lifecycle Hooks + +Nuxt provides hooks to tap into the build process, application lifecycle, and server runtime. + +## Build-time Hooks (Nuxt) + +Used in `nuxt.config.ts` or modules: + +### In nuxt.config.ts + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + hooks: { + 'build:before': () => { + console.log('Build starting...') + }, + 'pages:extend': (pages) => { + // Add custom pages + pages.push({ + name: 'custom', + path: '/custom', + file: '~/pages/custom.vue', + }) + }, + 'components:dirs': (dirs) => { + // Add component directories + dirs.push({ path: '~/extra-components' }) + }, + }, +}) +``` + +### In Modules + +```ts +// modules/my-module.ts +export default defineNuxtModule({ + setup(options, nuxt) { + nuxt.hook('ready', async (nuxt) => { + console.log('Nuxt is ready') + }) + + nuxt.hook('close', async (nuxt) => { + console.log('Nuxt is closing') + }) + + nuxt.hook('modules:done', () => { + console.log('All modules loaded') + }) + }, +}) +``` + +### Common Build Hooks + +| Hook | When | +| ------------------- | ------------------------------------------------ | +| `ready` | Nuxt initialization complete | +| `close` | Nuxt is closing | +| `modules:done` | All modules installed | +| `build:before` | Before build starts | +| `build:done` | Build complete | +| `pages:extend` | Add/modify routes (before meta scan) | +| `pages:resolved` | After page meta is scanned (with `scanPageMeta`) | +| `components:dirs` | Component dirs being resolved | +| `imports:extend` | Auto-imports being resolved | +| `nitro:config` | Before Nitro config finalized | +| `vite:extend` | Vite context created | +| `vite:extendConfig` | Before Vite config finalized | + +## App Hooks (Runtime) + +Used in plugins and composables: + +### In Plugins + +```ts +// plugins/lifecycle.ts +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.hook('app:created', (vueApp) => { + console.log('Vue app created') + }) + + nuxtApp.hook('app:mounted', (vueApp) => { + console.log('App mounted') + }) + + nuxtApp.hook('page:start', () => { + console.log('Page navigation starting') + }) + + nuxtApp.hook('page:finish', () => { + console.log('Page navigation finished') + }) + + nuxtApp.hook('page:loading:start', () => { + console.log('Page loading started') + }) + + nuxtApp.hook('page:loading:end', () => { + console.log('Page loading ended') + }) +}) +``` + +### Common App Hooks + +| Hook | When | +| -------------------- | ----------------------------- | +| `app:created` | Vue app created | +| `app:mounted` | Vue app mounted (client only) | +| `app:error` | Fatal error occurred | +| `page:start` | Page navigation starting | +| `page:finish` | Page navigation finished | +| `page:loading:start` | Loading indicator should show | +| `page:loading:end` | Loading indicator should hide | +| `link:prefetch` | Link is being prefetched | + +### Using Runtime Hooks + +```ts +// composables/usePageTracking.ts +export function usePageTracking() { + const nuxtApp = useNuxtApp() + + nuxtApp.hook('page:finish', () => { + trackPageView(useRoute().path) + }) +} +``` + +## Server Hooks (Nitro) + +Used in server plugins: + +```ts +// server/plugins/hooks.ts +export default defineNitroPlugin((nitroApp) => { + // Modify HTML before sending + nitroApp.hooks.hook('render:html', (html, { event }) => { + html.head.push('') + html.bodyAppend.push('') + }) + + // Modify response + nitroApp.hooks.hook('render:response', (response, { event }) => { + console.log('Sending response:', response.statusCode) + }) + + // Before request + nitroApp.hooks.hook('request', (event) => { + console.log('Request:', event.path) + }) + + // After response + nitroApp.hooks.hook('afterResponse', (event) => { + console.log('Response sent') + }) +}) +``` + +### Common Nitro Hooks + +| Hook | When | +| ----------------- | ---------------------------- | +| `request` | Request received | +| `beforeResponse` | Before sending response | +| `afterResponse` | After response sent | +| `render:html` | Before HTML is sent | +| `render:response` | Before response is finalized | +| `error` | Error occurred | + +## Custom Hooks + +### Define Custom Hook Types + +```ts +// types/hooks.d.ts +import type { HookResult } from '@nuxt/schema' + +declare module '#app' { + interface RuntimeNuxtHooks { + 'my-app:event': (data: MyEventData) => HookResult + } +} + +declare module '@nuxt/schema' { + interface NuxtHooks { + 'my-module:init': () => HookResult + } +} + +declare module 'nitropack/types' { + interface NitroRuntimeHooks { + 'my-server:event': (data: any) => void + } +} +``` + +### Call Custom Hooks + +```ts +// In a plugin +export default defineNuxtPlugin((nuxtApp) => { + // Call custom hook + nuxtApp.callHook('my-app:event', { type: 'custom' }) +}) + +// In a module +export default defineNuxtModule({ + setup(options, nuxt) { + nuxt.callHook('my-module:init') + }, +}) +``` + +## useRuntimeHook + +Call hooks at runtime from components: + +```vue + +``` + +## Hook Examples + +### Page View Tracking + +```ts +// plugins/analytics.client.ts +export default defineNuxtPlugin((nuxtApp) => { + nuxtApp.hook('page:finish', () => { + const route = useRoute() + analytics.track('pageview', { + path: route.path, + title: document.title, + }) + }) +}) +``` + +### Performance Monitoring + +```ts +// plugins/performance.client.ts +export default defineNuxtPlugin((nuxtApp) => { + let navigationStart: number + + nuxtApp.hook('page:start', () => { + navigationStart = performance.now() + }) + + nuxtApp.hook('page:finish', () => { + const duration = performance.now() - navigationStart + console.log(`Navigation took ${duration}ms`) + }) +}) +``` + +### Inject HTML + +```ts +// server/plugins/inject.ts +export default defineNitroPlugin((nitroApp) => { + nitroApp.hooks.hook('render:html', (html) => { + html.head.push(` + + `) + }) +}) +``` + + diff --git a/agents/.agents/skills/nuxt/references/advanced-layers.md b/agents/.agents/skills/nuxt/references/advanced-layers.md new file mode 100644 index 0000000..b50df76 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/advanced-layers.md @@ -0,0 +1,300 @@ +--- +name: nuxt-layers +description: Extending Nuxt applications with layers for code sharing and reusability +--- + +# Nuxt Layers + +Layers allow sharing and reusing partial Nuxt applications across projects. They can include components, composables, pages, layouts, and configuration. + +## Using Layers + +### From npm Package + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + extends: [ + '@my-org/base-layer', + '@nuxtjs/ui-layer', + ], +}) +``` + +### From Git Repository + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + extends: [ + 'github:username/repo', + 'github:username/repo/base', // Subdirectory + 'github:username/repo#v1.0', // Specific tag + 'github:username/repo#dev', // Branch + 'gitlab:username/repo', + 'bitbucket:username/repo', + ], +}) +``` + +### From Local Directory + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + extends: [ + '../base-layer', + './layers/shared', + ], +}) +``` + +### Auto-scanned Layers + +Place in `layers/` directory for automatic discovery: + +``` +my-app/ +├── layers/ +│ ├── base/ +│ │ └── nuxt.config.ts +│ └── ui/ +│ └── nuxt.config.ts +└── nuxt.config.ts +``` + +## Creating a Layer + +Minimal layer structure: + +``` +my-layer/ +├── nuxt.config.ts # Required +├── app/ +│ ├── components/ # Auto-merged +│ ├── composables/ # Auto-merged +│ ├── layouts/ # Auto-merged +│ ├── middleware/ # Auto-merged +│ ├── pages/ # Auto-merged +│ ├── plugins/ # Auto-merged +│ └── app.config.ts # Merged +├── server/ # Auto-merged +└── package.json +``` + +### Layer nuxt.config.ts + +```ts +// my-layer/nuxt.config.ts +export default defineNuxtConfig({ + // Layer configuration + app: { + head: { + title: 'My Layer App', + }, + }, + // Shared modules + modules: ['@nuxt/ui'], +}) +``` + +### Layer Components + +```vue + + +``` + +Use in consuming project: + +```vue + +``` + +### Layer Composables + +```ts +// my-layer/app/composables/useTheme.ts +export function useTheme() { + const isDark = useState('theme-dark', () => false) + const toggle = () => isDark.value = !isDark.value + return { isDark, toggle } +} +``` + +## Layer Priority + +Override order (highest to lowest): + +1. Your project files +2. Auto-scanned layers (alphabetically, Z > A) +3. `extends` array (first > last) + +Control order with prefixes: + +``` +layers/ +├── 1.base/ # Lower priority +└── 2.theme/ # Higher priority +``` + +## Layer Aliases + +Access layer files: + +```ts +// Auto-scanned layers get aliases +import Component from '#layers/base/components/Component.vue' +``` + +Named aliases: + +```ts +// my-layer/nuxt.config.ts +export default defineNuxtConfig({ + $meta: { + name: 'my-layer', + }, +}) +``` + +```ts +// In consuming project +import { something } from '#layers/my-layer/utils' +``` + +## Publishing Layers + +### As npm Package + +```json +{ + "name": "my-nuxt-layer", + "version": "1.0.0", + "type": "module", + "main": "./nuxt.config.ts", + "dependencies": { + "@nuxt/ui": "^2.0.0" + }, + "devDependencies": { + "nuxt": "^3.0.0" + } +} +``` + +### Private Layers + +For private git repos: + +```bash +export GIGET_AUTH= +``` + +## Layer Best Practices + +### Use Resolved Paths + +```ts +// my-layer/nuxt.config.ts +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' + +const currentDir = dirname(fileURLToPath(import.meta.url)) + +export default defineNuxtConfig({ + css: [ + join(currentDir, './assets/main.css'), + ], +}) +``` + +### Install Dependencies + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + extends: [ + ['github:user/layer', { install: true }], + ], +}) +``` + +### Disable Layer Modules + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + extends: ['./base-layer'], + // Disable modules from layer + image: false, // Disables @nuxt/image + pinia: false, // Disables @pinia/nuxt +}) +``` + +## Starter Template + +Create a new layer: + +```bash +npx nuxi init --template layer my-layer +``` + +## Example: Theme Layer + +``` +theme-layer/ +├── nuxt.config.ts +├── app/ +│ ├── app.config.ts +│ ├── components/ +│ │ ├── ThemeButton.vue +│ │ └── ThemeCard.vue +│ ├── composables/ +│ │ └── useTheme.ts +│ └── assets/ +│ └── theme.css +└── package.json +``` + +```ts +// theme-layer/nuxt.config.ts +export default defineNuxtConfig({ + css: ['~/assets/theme.css'], +}) +``` + +```ts +// theme-layer/app/app.config.ts +export default defineAppConfig({ + theme: { + primaryColor: '#00dc82', + darkMode: false, + }, +}) +``` + +```ts +// consuming-app/nuxt.config.ts +export default defineNuxtConfig({ + extends: ['theme-layer'], +}) + +// consuming-app/app/app.config.ts +export default defineAppConfig({ + theme: { + primaryColor: '#ff0000', // Override + }, +}) +``` + + diff --git a/agents/.agents/skills/nuxt/references/advanced-module-authoring.md b/agents/.agents/skills/nuxt/references/advanced-module-authoring.md new file mode 100644 index 0000000..920c2b7 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/advanced-module-authoring.md @@ -0,0 +1,581 @@ +--- +name: module-authoring +description: Complete guide to creating publishable Nuxt modules with best practices +--- + +# Module Authoring + +This guide covers creating publishable Nuxt modules with proper structure, type safety, and best practices. + +## Module Structure + +Recommended structure for a publishable module: + +``` +my-nuxt-module/ +├── src/ +│ ├── module.ts # Module entry +│ └── runtime/ +│ ├── components/ # Vue components +│ ├── composables/ # Composables +│ ├── plugins/ # Nuxt plugins +│ └── server/ # Server handlers +├── playground/ # Development app +├── package.json +└── tsconfig.json +``` + +## Module Definition + +### Basic Module with Type-safe Options + +```ts +// src/module.ts +import { defineNuxtModule, createResolver, addPlugin, addComponent, addImports } from '@nuxt/kit' + +export interface ModuleOptions { + prefix?: string + apiKey: string + enabled?: boolean +} + +export default defineNuxtModule({ + meta: { + name: 'my-module', + configKey: 'myModule', + compatibility: { + nuxt: '>=3.0.0', + }, + }, + defaults: { + prefix: 'My', + enabled: true, + }, + setup(options, nuxt) { + if (!options.enabled) return + + const { resolve } = createResolver(import.meta.url) + + // Module setup logic here + }, +}) +``` + +### Using `.with()` for Strict Type Inference + +When you need TypeScript to infer that default values are always present: + +```ts +import { defineNuxtModule } from '@nuxt/kit' + +interface ModuleOptions { + apiKey: string + baseURL: string + timeout?: number +} + +export default defineNuxtModule().with({ + meta: { + name: '@nuxtjs/my-api', + configKey: 'myApi', + }, + defaults: { + baseURL: 'https://api.example.com', + timeout: 5000, + }, + setup(resolvedOptions, nuxt) { + // resolvedOptions.baseURL is guaranteed to be string (not undefined) + // resolvedOptions.timeout is guaranteed to be number (not undefined) + }, +}) +``` + +## Adding Runtime Assets + +### Components + +```ts +import { addComponent, addComponentsDir, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + + // Single component + addComponent({ + name: 'MyButton', + filePath: resolve('./runtime/components/MyButton.vue'), + }) + + // Component directory with prefix + addComponentsDir({ + path: resolve('./runtime/components'), + prefix: 'My', + pathPrefix: false, + }) + }, +}) +``` + +### Composables and Auto-imports + +```ts +import { addImports, addImportsDir, addImportsSources, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + + // Single import + addImports({ + name: 'useMyUtil', + from: resolve('./runtime/composables/useMyUtil'), + }) + + // Directory of composables + addImportsDir(resolve('./runtime/composables')) + + // Auto-import from packages (Nuxt 4: accepts an array + package presets) + addImportsSources([ + { package: '@vueuse/core' }, // all of the package's documented exports + { from: 'my-lib', imports: ['useThing', 'useOther'] }, + ]) + }, +}) +``` + +### Keyed Composables (SSR/client key injection) + +If your composable needs a stable key shared between server and client (like `useState`/`useAsyncData`), register it so Nuxt's compiler injects a unique key when called with fewer than `argumentLength` args: + +```ts +export default defineNuxtModule({ + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url) + nuxt.options.optimization.keyedComposables.push({ + name: 'useMyState', + source: resolve('./runtime/composables/state'), // exact source file (no barrels) + argumentLength: 2, + }) + }, +}) +``` + +The call must be statically analyzable and imported directly from `source` (barrel re-exports, dynamic/indirect calls, and reassignments are not transformed). + +### Plugins + +```ts +import { addPlugin, addPluginTemplate, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup(options) { + const { resolve } = createResolver(import.meta.url) + + // Static plugin file + addPlugin({ + src: resolve('./runtime/plugins/myPlugin'), + mode: 'client', // 'client', 'server', or 'all' + }) + + // Dynamic plugin with generated code + addPluginTemplate({ + filename: 'my-module-plugin.mjs', + getContents: () => ` +import { defineNuxtPlugin } from '#app/nuxt' + +export default defineNuxtPlugin({ + name: 'my-module', + setup() { + const config = ${JSON.stringify(options)} + // Plugin logic + } +})`, + }) + }, +}) +``` + +## Server Extensions + +### Server Handlers + +```ts +import { addServerHandler, addServerScanDir, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + + // Single handler + addServerHandler({ + route: '/api/my-endpoint', + handler: resolve('./runtime/server/api/my-endpoint'), + }) + + // Scan entire server directory (api/, routes/, middleware/, utils/) + addServerScanDir(resolve('./runtime/server')) + }, +}) +``` + +### Server Composables + +```ts +import { addServerImports, addServerImportsDir, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + + // Single server import + addServerImports({ + name: 'useServerUtil', + from: resolve('./runtime/server/utils/useServerUtil'), + }) + + // Server composables directory + addServerImportsDir(resolve('./runtime/server/composables')) + }, +}) +``` + +### Nitro Plugin + +```ts +import { addServerPlugin, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + addServerPlugin(resolve('./runtime/server/plugin')) + }, +}) +``` + +```ts +// runtime/server/plugin.ts +import { defineNitroPlugin } from 'nitropack/runtime' + +export default defineNitroPlugin((nitroApp) => { + nitroApp.hooks.hook('request', (event) => { + console.log('Request:', event.path) + }) +}) +``` + +## Templates and Virtual Files + +### Generate Virtual Files + +```ts +import { addTemplate, addTypeTemplate, addServerTemplate, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url) + + // Client/build virtual file (accessible via #build/my-config.mjs) + addTemplate({ + filename: 'my-config.mjs', + getContents: () => `export default ${JSON.stringify(options)}`, + }) + + // Type declarations + addTypeTemplate({ + filename: 'types/my-module.d.ts', + getContents: () => ` +declare module '#my-module' { + export interface Config { + apiKey: string + } +}`, + }) + + // Nitro virtual file (accessible in server routes) + addServerTemplate({ + filename: '#my-module/config.mjs', + getContents: () => `export const config = ${JSON.stringify(options)}`, + }) + }, +}) +``` + +### Access Virtual Files + +```ts +// In runtime plugin +// @ts-expect-error - virtual file +import config from '#build/my-config.mjs' + +// In server routes +import { config } from '#my-module/config.js' +``` + +## Extending Pages and Routes + +```ts +import { extendPages, extendRouteRules, addRouteMiddleware, createResolver } from '@nuxt/kit' + +export default defineNuxtModule({ + setup() { + const { resolve } = createResolver(import.meta.url) + + // Add pages + extendPages((pages) => { + pages.push({ + name: 'my-page', + path: '/my-route', + file: resolve('./runtime/pages/MyPage.vue'), + }) + }) + + // Add route rules (caching, redirects, etc.) + extendRouteRules('/api/**', { + cache: { maxAge: 60 }, + }) + + // Add middleware + addRouteMiddleware({ + name: 'my-middleware', + path: resolve('./runtime/middleware/myMiddleware'), + global: true, + }) + }, +}) +``` + +## Module Dependencies + +Declare dependencies on other modules with version constraints: + +```ts +export default defineNuxtModule({ + meta: { + name: 'my-module', + }, + moduleDependencies: { + '@nuxtjs/tailwindcss': { + version: '>=6.0.0', + // Set defaults (user can override) + defaults: { + exposeConfig: true, + }, + // Force specific options + overrides: { + viewer: false, + }, + }, + '@nuxtjs/i18n': { + optional: true, // Won't fail if not installed + defaults: { + defaultLocale: 'en', + }, + }, + }, + setup() { + // Dependencies are guaranteed to be set up before this runs + }, +}) +``` + +### Dynamic Dependencies + +```ts +moduleDependencies(nuxt) { + const deps: Record = { + '@nuxtjs/tailwindcss': { version: '>=6.0.0' }, + } + + if (nuxt.options.ssr) { + deps['@nuxtjs/html-validator'] = { optional: true } + } + + return deps +} +``` + +## Lifecycle Hooks + +Requires `meta.name` and `meta.version`: + +```ts +export default defineNuxtModule({ + meta: { + name: 'my-module', + version: '1.2.0', + }, + onInstall(nuxt) { + // First-time setup + console.log('Module installed for the first time') + }, + onUpgrade(nuxt, options, previousVersion) { + // Version upgrade migrations + console.log(`Upgrading from ${previousVersion}`) + }, + setup(options, nuxt) { + // Regular setup runs every build + }, +}) +``` + +## Extending Configuration + +```ts +export default defineNuxtModule({ + setup(options, nuxt) { + // Add CSS + nuxt.options.css.push('my-module/styles.css') + + // Add runtime config + nuxt.options.runtimeConfig.public.myModule = { + apiUrl: options.apiUrl, + } + + // Extend Vite config + nuxt.options.vite.optimizeDeps ||= {} + nuxt.options.vite.optimizeDeps.include ||= [] + nuxt.options.vite.optimizeDeps.include.push('some-package') + + // Add build transpile + nuxt.options.build.transpile.push('my-package') + }, +}) +``` + +## Using Hooks + +```ts +export default defineNuxtModule({ + // Declarative hooks + hooks: { + 'components:dirs': (dirs) => { + dirs.push({ path: '~/extra' }) + }, + }, + + setup(options, nuxt) { + // Programmatic hooks + nuxt.hook('pages:extend', (pages) => { + // Modify pages + }) + + nuxt.hook('imports:extend', (imports) => { + imports.push({ name: 'myHelper', from: 'my-package' }) + }) + + nuxt.hook('nitro:config', (config) => { + // Modify Nitro config + }) + + nuxt.hook('vite:extendConfig', (config) => { + // Modify Vite config + }) + }, +}) +``` + +## Path Resolution + +```ts +import { createResolver, resolvePath, findPath } from '@nuxt/kit' + +export default defineNuxtModule({ + async setup(options, nuxt) { + // Resolver relative to module + const { resolve } = createResolver(import.meta.url) + + const pluginPath = resolve('./runtime/plugin') + + // Resolve with extensions and aliases + const entrypoint = await resolvePath('@some/package') + + // Find first existing file + const configPath = await findPath([ + resolve('./config.ts'), + resolve('./config.js'), + ]) + }, +}) +``` + +## Module Package.json + +```json +{ + "name": "my-nuxt-module", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/module.mjs", + "require": "./dist/module.cjs" + } + }, + "main": "./dist/module.cjs", + "module": "./dist/module.mjs", + "types": "./dist/types.d.ts", + "files": ["dist"], + "scripts": { + "dev": "nuxi dev playground", + "build": "nuxt-module-build build", + "prepare": "nuxt-module-build build --stub" + }, + "dependencies": { + "@nuxt/kit": "^4.0.0" + }, + "devDependencies": { + "@nuxt/module-builder": "latest", + "nuxt": "^4.0.0" + } +} +``` + +## Disabling Modules + +Users can disable a module via config key: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + // Disable entirely + myModule: false, + + // Or with options + myModule: { + enabled: false, + }, +}) +``` + +## Development Workflow + +1. **Create module**: `npx nuxi init -t module my-module` +2. **Develop**: `npm run dev` (runs playground) +3. **Build**: `npm run build` +4. **Test**: `npm run test` + +## Best Practices + +- Use `createResolver(import.meta.url)` for all path resolution +- Prefix components to avoid naming conflicts +- Make options type-safe with `ModuleOptions` interface +- Use `moduleDependencies` instead of `installModule` +- Provide sensible defaults for all options +- Add compatibility requirements in `meta.compatibility` +- Use virtual files for dynamic configuration +- Separate client/server plugins appropriately + + diff --git a/agents/.agents/skills/nuxt/references/best-practices-data-fetching.md b/agents/.agents/skills/nuxt/references/best-practices-data-fetching.md new file mode 100644 index 0000000..275b9e6 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/best-practices-data-fetching.md @@ -0,0 +1,402 @@ +--- +name: data-fetching-best-practices +description: Patterns and best practices for efficient data fetching in Nuxt +--- + +# Data Fetching Best Practices + +Effective data fetching patterns for SSR-friendly, performant Nuxt applications. + +## Choose the Right Tool + +| Scenario | Use | +| ---------------------------------------- | ----------------------------------- | +| Component initial data | `useFetch` or `useAsyncData` | +| User interactions (clicks, forms) | `$fetch` | +| Third-party SDK/API | `useAsyncData` with custom function | +| Multiple parallel requests | `useAsyncData` with `Promise.all` | +| Reusable API client with shared defaults | `createUseFetch` factory | + +## Await vs Non-Await Usage + +The `await` keyword controls whether data fetching **blocks navigation**: + +### With `await` - Blocking Navigation + +```vue + +``` + +- **Server**: Fetches data and includes it in the payload +- **Client hydration**: Uses payload data, no re-fetch +- **Client navigation**: Blocks until data is ready + +### Without `await` - Non-Blocking (Lazy) + +```vue + + + +``` + +Equivalent to using `useLazyFetch`: + +```vue + +``` + +### When to Use Each + +| Pattern | Use Case | +| -------------------------- | ----------------------------------------------- | +| `await useFetch()` | Critical data needed for SEO/initial render | +| `useFetch({ lazy: true })` | Non-critical data, better perceived performance | +| `await useLazyFetch()` | Same as lazy, await only ensures initialization | + +## Avoid Double Fetching + +### ❌ Wrong: Using $fetch Alone in Setup + +```vue + +``` + +### ✅ Correct: Use useFetch + +```vue + +``` + +## Use Explicit Cache Keys + +### ❌ Avoid: Auto-generated Keys + +```vue + +``` + +### ✅ Better: Explicit Keys + +```vue + +``` + +## Handle Loading States Properly + +```vue + + + +``` + +## Use Lazy Fetching for Non-critical Data + +```vue + + + +``` + +## Minimize Payload Size + +### Use `pick` for Simple Filtering + +```vue + +``` + +### Use `transform` for Complex Transformations + +```vue + +``` + +## Parallel Fetching + +### Fetch Independent Data with useAsyncData + +```vue + +``` + +### Multiple useFetch Calls + +```vue + +``` + +## Efficient Refresh Patterns + +### Watch Reactive Dependencies + +```vue + +``` + +### Manual Refresh + +```vue + +``` + +### Conditional Fetching + +```vue + +``` + +## Server-only Fetching + +```vue + +``` + +## Error Handling + +```vue + + + +``` + +## Shared Data Across Components + +```vue + + + + + +``` + +## Centralize API Config with createUseFetch + +Instead of hand-rolling a wrapper around `useFetch` (and worrying about whether to `await` it), use the `createUseFetch` factory. It produces a fully typed composable with shared `baseURL`, headers, and interceptors: + +```ts +// app/composables/useAPI.ts +export const useAPI = createUseFetch({ + baseURL: 'https://api.nuxt.com', + onRequest({ options }) { + const { session } = useUserSession() + if (session.value?.token) { + options.headers.set('Authorization', `Bearer ${session.value.token}`) + } + }, + async onResponseError({ response }) { + if (response.status === 401) await navigateTo('/login') + }, +}) +``` + +```vue + +``` + +For lower-level control, create a custom `$fetch` instance in a plugin and wrap it with `useAsyncData` (or pass it to `createUseFetch`) — this avoids double fetching during SSR: + +```ts +// app/plugins/api.ts +export default defineNuxtPlugin(() => { + const api = $fetch.create({ baseURL: 'https://api.nuxt.com' }) + return { provide: { api } } +}) +``` + +```vue + +``` + +## Avoid useAsyncData for Side Effects + +### ❌ Wrong: Side Effects in useAsyncData + +```vue + +``` + +### ✅ Correct: Use callOnce for Side Effects + +```vue + +``` + + diff --git a/agents/.agents/skills/nuxt/references/best-practices-ssr.md b/agents/.agents/skills/nuxt/references/best-practices-ssr.md new file mode 100644 index 0000000..feeb4f2 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/best-practices-ssr.md @@ -0,0 +1,357 @@ +--- +name: ssr-best-practices +description: Avoiding SSR context leaks, hydration mismatches, and proper composable usage +--- + +# SSR Best Practices + +Patterns for avoiding common SSR pitfalls: context leaks, hydration mismatches, and composable errors. + +## The "Nuxt Instance Unavailable" Error + +This error occurs when calling Nuxt composables outside the proper context. + +### ❌ Wrong: Composable Outside Setup + +```ts +// composables/bad.ts +// Called at module level - no Nuxt context! +const config = useRuntimeConfig() + +export function useMyComposable() { + return config.public.apiBase +} +``` + +### ✅ Correct: Composable Inside Function + +```ts +// composables/good.ts +export function useMyComposable() { + // Called inside the composable - has context + const config = useRuntimeConfig() + return config.public.apiBase +} +``` + +### Valid Contexts for Composables + +Nuxt composables work in: + +- ` +``` + +### ✅ Correct: Use SSR-safe Alternatives + +```vue + +``` + +### ❌ Wrong: Random/Time-based Values + +```vue + +``` + +### ✅ Correct: Use useState for Consistency + +```vue + + + +``` + +### ❌ Wrong: Conditional Rendering on Client State + +```vue + +``` + +### ✅ Correct: Use CSS or ClientOnly + +```vue + +``` + +## Browser-only Code + +### Use `import.meta.client` + +```vue + +``` + +### Use `onMounted` for DOM Access + +```vue + +``` + +### Dynamic Imports for Browser Libraries + +```vue + +``` + +## Server-only Code + +### Use `import.meta.server` + +```vue + +``` + +### Server Components + +```vue + + + + +``` + +## Async Composable Patterns + +### ❌ Wrong: Await Before Composable + +```vue + +``` + +### ✅ Correct: Get Context First + +```vue + +``` + +## Plugin Best Practices + +### Client-only Plugins + +```ts +// plugins/analytics.client.ts +export default defineNuxtPlugin(() => { + // Only runs on client + initAnalytics() +}) +``` + +### Server-only Plugins + +```ts +// plugins/server-init.server.ts +export default defineNuxtPlugin(() => { + // Only runs on server + initServerConnections() +}) +``` + +### Provide/Inject Pattern + +```ts +// plugins/api.ts +export default defineNuxtPlugin(() => { + const api = createApiClient() + + return { + provide: { + api, + }, + } +}) +``` + +```vue + +``` + +## Third-party Library Integration + +### ❌ Wrong: Import at Top Level + +```vue + +``` + +### ✅ Correct: Dynamic Import + +```vue + +``` + +### Use ClientOnly Component + +```vue + +``` + +## Debugging SSR Issues + +### Check Rendering Context + +```vue + +``` + +### Use Nuxt DevTools + +DevTools shows payload data and hydration state. + +### Common Error Messages + +| Error | Cause | +| --------------------------- | --------------------------------------- | +| "Nuxt instance unavailable" | Composable called outside setup context | +| "Hydration mismatch" | Server/client HTML differs | +| "window is not defined" | Browser API used during SSR | +| "document is not defined" | DOM access during SSR | + + diff --git a/agents/.agents/skills/nuxt/references/core-cli.md b/agents/.agents/skills/nuxt/references/core-cli.md new file mode 100644 index 0000000..9ffec5a --- /dev/null +++ b/agents/.agents/skills/nuxt/references/core-cli.md @@ -0,0 +1,264 @@ +--- +name: cli-commands +description: Nuxt CLI commands for development, building, and project management +--- + +# CLI Commands + +Nuxt provides CLI commands via `nuxi` (or `npx nuxt`) for development, building, and project management. + +## Project Initialization + +### Create New Project + +```bash +# Interactive project creation +npx nuxi@latest init my-app + +# With specific package manager +npx nuxi@latest init my-app --packageManager pnpm + +# With modules +npx nuxi@latest init my-app --modules "@nuxt/ui,@nuxt/image" + +# From template +npx nuxi@latest init my-app --template v3 + +# Skip module selection prompt +npx nuxi@latest init my-app --no-modules +``` + +**Options:** +| Option | Description | +|--------|-------------| +| `-t, --template` | Template name | +| `--packageManager` | npm, pnpm, yarn, or bun | +| `-M, --modules` | Modules to install (comma-separated) | +| `--gitInit` | Initialize git repository | +| `--no-install` | Skip installing dependencies | + +## Development + +### Start Dev Server + +```bash +# Start development server (default: http://localhost:3000) +npx nuxt dev + +# Custom port +npx nuxt dev --port 4000 + +# Open in browser +npx nuxt dev --open + +# Listen on all interfaces (for mobile testing) +npx nuxt dev --host 0.0.0.0 + +# With HTTPS +npx nuxt dev --https + +# Clear console on restart +npx nuxt dev --clear + +# Create public tunnel +npx nuxt dev --tunnel +``` + +**Options:** +| Option | Description | +|--------|-------------| +| `-p, --port` | Port to listen on | +| `-h, --host` | Host to listen on | +| `-o, --open` | Open in browser | +| `--https` | Enable HTTPS | +| `--tunnel` | Create public tunnel (via untun) | +| `--qr` | Show QR code for mobile | +| `--clear` | Clear console on restart | + +**Environment Variables:** + +- `NUXT_PORT` or `PORT` - Default port +- `NUXT_HOST` or `HOST` - Default host + +## Building + +### Production Build + +```bash +# Build for production +npx nuxt build + +# Build with prerendering +npx nuxt build --prerender + +# Build with specific preset +npx nuxt build --preset node-server +npx nuxt build --preset cloudflare-pages +npx nuxt build --preset vercel + +# Build with environment +npx nuxt build --envName staging +``` + +Output is created in `.output/` directory. + +### Static Generation + +```bash +# Generate static site (prerenders all routes) +npx nuxt generate +``` + +Equivalent to `nuxt build --prerender`. Creates static HTML files for deployment to static hosting. + +### Preview Production Build + +```bash +# Preview after build +npx nuxt preview + +# Custom port +npx nuxt preview --port 4000 +``` + +## Utilities + +### Prepare (Type Generation) + +```bash +# Generate TypeScript types and .nuxt directory +npx nuxt prepare +``` + +Run after cloning or when types are missing. + +### Type Check + +```bash +# Run TypeScript type checking +npx nuxt typecheck +``` + +### Analyze Bundle + +```bash +# Analyze production bundle +npx nuxt analyze +``` + +Opens visual bundle analyzer. + +### Cleanup + +```bash +# Remove generated files (.nuxt, .output, node_modules/.cache) +npx nuxt cleanup +``` + +### Info + +```bash +# Show environment info (useful for bug reports) +npx nuxt info +``` + +### Upgrade + +```bash +# Upgrade Nuxt to latest version +npx nuxt upgrade + +# Upgrade to nightly release +npx nuxt upgrade --nightly +``` + +## Module Commands + +### Add Module + +```bash +# Add a Nuxt module +npx nuxt module add @nuxt/ui +npx nuxt module add @nuxt/image +``` + +Installs and adds to `nuxt.config.ts`. + +### Build Module (for module authors) + +```bash +# Build a Nuxt module +npx nuxt build-module +``` + +## DevTools + +```bash +# Enable DevTools globally +npx nuxt devtools enable + +# Disable DevTools +npx nuxt devtools disable +``` + +## Common Workflows + +### Development + +```bash +# Install dependencies and start dev +pnpm install +pnpm dev # or npx nuxt dev +``` + +### Production Deployment + +```bash +# Build and preview locally +pnpm build +pnpm preview + +# Or for static hosting +pnpm generate +``` + +### After Cloning + +```bash +# Install deps and prepare types +pnpm install +npx nuxt prepare +``` + +## Environment-specific Builds + +```bash +# Development build +npx nuxt build --envName development + +# Staging build +npx nuxt build --envName staging + +# Production build (default) +npx nuxt build --envName production +``` + +Corresponds to `$development`, `$env.staging`, `$production` in `nuxt.config.ts`. + +## Layer Extension + +```bash +# Dev with additional layer +npx nuxt dev --extends ./base-layer + +# Build with layer +npx nuxt build --extends ./base-layer +``` + + diff --git a/agents/.agents/skills/nuxt/references/core-config.md b/agents/.agents/skills/nuxt/references/core-config.md new file mode 100644 index 0000000..f5dcdd9 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/core-config.md @@ -0,0 +1,239 @@ +--- +name: configuration +description: Nuxt configuration files including nuxt.config.ts, app.config.ts, and runtime configuration +--- + +# Nuxt Configuration + +Nuxt uses configuration files to customize application behavior. The main configuration options are `nuxt.config.ts` for build-time settings and `app.config.ts` for runtime settings. + +## nuxt.config.ts + +The main configuration file at the root of your project: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + // Configuration options + devtools: { enabled: true }, + modules: ['@nuxt/ui'], +}) +``` + +### Nuxt 4 Path Aliases + +In Nuxt 4 the default `srcDir` is `app/`, so the path aliases changed: + +| Alias | Resolves to | +| ----------- | -------------------------- | +| `~` / `@` | `/app` | +| `~~` / `@@` | `` (project root) | +| `#shared` | `/shared` | +| `#server` | `/server` | + +Reference root-level paths (modules, server handlers) with `~~` or `#server`: + +```ts +export default defineNuxtConfig({ + modules: ['~~/custom-modules/awesome.js'], // relative to rootDir + serverHandlers: [ + { route: '/foo/**', handler: '#server/foohandler.ts' }, + ], +}) +``` + +### Compatibility Version + +Nuxt 4 is the default behavior. To preview upcoming Nuxt 5 defaults (Vite Environment API, normalized page names, etc.): + +```ts +export default defineNuxtConfig({ + future: { + compatibilityVersion: 5, + }, +}) +``` + +### Environment Overrides + +Configure environment-specific settings: + +```ts +export default defineNuxtConfig({ + $production: { + routeRules: { + '/**': { isr: true }, + }, + }, + $development: { + // Development-specific config + }, + $env: { + staging: { + // Staging environment config + }, + }, +}) +``` + +Use `--envName` flag to select environment: `nuxt build --envName staging` + +## Runtime Config + +For values that need to be overridden via environment variables: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + runtimeConfig: { + // Server-only keys + apiSecret: '123', + // Keys within public are exposed to client + public: { + apiBase: '/api', + }, + }, +}) +``` + +Override with environment variables: + +```ini +# .env +NUXT_API_SECRET=api_secret_token +NUXT_PUBLIC_API_BASE=https://api.example.com +``` + +Access in components/composables: + +```vue + +``` + +## App Config + +For public tokens determined at build time (not overridable via env vars): + +```ts +// app/app.config.ts +export default defineAppConfig({ + title: 'Hello Nuxt', + theme: { + dark: true, + colors: { + primary: '#ff0000', + }, + }, +}) +``` + +Access in components: + +```vue + +``` + +## runtimeConfig vs app.config + +| Feature | runtimeConfig | app.config | +| ---------------------- | ------------- | ---------- | +| Client-side | Hydrated | Bundled | +| Environment variables | Yes | No | +| Reactive | Yes | Yes | +| Hot module replacement | No | Yes | +| Non-primitive JS types | No | Yes | + +**Use runtimeConfig** for secrets and values that change per environment. +**Use app.config** for public tokens, theme settings, and non-sensitive config. + +## External Tool Configuration + +Nuxt uses `nuxt.config.ts` as single source of truth. Configure external tools within it: + +```ts +export default defineNuxtConfig({ + // Nitro configuration + nitro: { + // nitro options + }, + // Vite configuration + vite: { + // vite options + vue: { + // @vitejs/plugin-vue options + }, + }, + // PostCSS configuration + postcss: { + // postcss options + }, +}) +``` + +### Environment-specific Vite Config + +Top-level `vite` options are shared. Use `$client` and `$server` to target a single Vite build: + +```ts +export default defineNuxtConfig({ + vite: { + $client: { + build: { rollupOptions: { output: { manualChunks: { analytics: ['analytics-package'] } } } }, + }, + $server: { + build: { sourcemap: 'inline' }, + }, + }, +}) +``` + +## Vue Configuration + +Enable Vue experimental features: + +```ts +export default defineNuxtConfig({ + vue: { + propsDestructure: true, + }, +}) +``` + +## Experimental Features & Defaults + +Notable Nuxt 4 flags under `experimental` (see source for the full list): + +```ts +export default defineNuxtConfig({ + experimental: { + // Stream the HTML shell first, then render body progressively (better TTFB) + ssrStreaming: true, + // Run useFetch when its key changes even if immediate:false and not yet triggered + alwaysRunFetchOnKeyChange: true, + // Split useAsyncData handlers into separate chunks (static builds) + extractAsyncDataHandlers: true, + // pending is true before fetching starts + pendingWhenIdle: true, + // Default options for core composables/components + defaults: { + nuxtLink: { prefetch: true, prefetchOn: { visibility: true } }, + useAsyncData: { deep: true }, + useState: { resetOnClear: true }, + }, + }, +}) +``` + + diff --git a/agents/.agents/skills/nuxt/references/core-data-fetching.md b/agents/.agents/skills/nuxt/references/core-data-fetching.md new file mode 100644 index 0000000..ed6ef0f --- /dev/null +++ b/agents/.agents/skills/nuxt/references/core-data-fetching.md @@ -0,0 +1,302 @@ +--- +name: data-fetching +description: useFetch, useAsyncData, and $fetch for SSR-friendly data fetching +--- + +# Data Fetching + +Nuxt provides composables for SSR-friendly data fetching that prevent double-fetching and handle hydration. + +## Overview + +- `$fetch` - Basic fetch utility (use for client-side events) +- `useFetch` - SSR-safe wrapper around $fetch (use for component data) +- `useAsyncData` - SSR-safe wrapper for any async function +- `createUseFetch` / `createUseAsyncData` - factories to build typed custom composables with baked-in defaults + +## useFetch + +Primary composable for fetching data in components: + +```vue + + + +``` + +### With Options + +```ts +const { data } = await useFetch('/api/posts', { + // Query parameters + query: { page: 1, limit: 10 }, + // Request body (for POST/PUT) + body: { title: 'New Post' }, + // HTTP method + method: 'POST', + // Only pick specific fields + pick: ['id', 'title'], + // Transform response + transform: (posts) => posts.map(p => ({ ...p, slug: slugify(p.title) })), + // Custom key for caching + key: 'posts-list', + // Don't fetch on server + server: false, + // Don't block navigation + lazy: true, + // Don't fetch immediately + immediate: false, + // Default value + default: () => [], +}) +``` + +### Reactive Parameters + +```vue + +``` + +### Computed URL + +```vue + +``` + +## useAsyncData + +For wrapping any async function: + +```vue + +``` + +### Multiple Requests + +```vue + +``` + +## Custom Fetchers: createUseFetch / createUseAsyncData + +Factory macros that produce a fully typed custom composable with pre-defined options. Must be an **exported** declaration inside `app/composables/` (Nuxt injects dedup keys at build time). + +```ts +// app/composables/useAPI.ts +export const useAPI = createUseFetch({ + baseURL: 'https://api.nuxt.com', + // shared interceptors, headers, etc. + onResponseError({ response }) { + if (response.status === 401) navigateTo('/login') + }, +}) +``` + +```vue + +``` + +**Default vs Override mode:** + +```ts +// Plain object → options act as DEFAULTS (caller can override) +export const useAPI = createUseFetch({ baseURL: '/api', lazy: true }) + +// Function → options OVERRIDE caller's (enforce auth/baseURL) +export const useAPI = createUseFetch(callerOptions => ({ + baseURL: 'https://api.nuxt.com', // always enforced +})) +``` + +Use the **function form** when you need `useNuxtApp()` (called in setup context, not module scope): + +```ts +// app/composables/useAPI.ts +export const useAPI = createUseFetch(callerOptions => ({ + $fetch: useNuxtApp().$api as typeof $fetch, + ...callerOptions, +})) +``` + +`createUseAsyncData` works identically for wrapping arbitrary async functions: + +```ts +// app/composables/useCachedData.ts +export const useCachedData = createUseAsyncData({ + getCachedData(key, nuxtApp) { + return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key] + }, +}) +``` + +> Replaces the old "don't await your custom `useFetch` wrapper" caveat — use these factories instead of hand-rolled wrappers. + +## $fetch + +For client-side events (form submissions, button clicks): + +```vue + +``` + +**Important**: Don't use `$fetch` alone in setup for initial data - it will fetch twice (server + client). Use `useFetch` or `useAsyncData` instead. + +## Return Values + +All composables return: + +| Property | Type | Description | +| --------- | -------------------------------------------------- | ------------------------------------------------- | +| `data` | `Ref` | Fetched data (`undefined` until resolved) | +| `error` | `Ref` | Error if request failed | +| `status` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | Request status | +| `pending` | `Ref` | Whether a request is in progress | +| `refresh` | `() => Promise` | Refetch data | +| `execute` | `() => Promise` | Alias for refresh | +| `clear` | `() => void` | Reset to default/idle and cancel pending requests | + +> Prefer `status` over `pending` for fine-grained state. `useFetch` no longer accepts a top-level `timeout` option (still available on `useAsyncData`); use a `cache` option (`'default'`, `'no-store'`, `false`, etc.) for Fetch cache control. + +## Lazy Fetching + +Don't block navigation: + +```vue + +``` + +## Refresh & Watch + +```vue + +``` + +## Caching + +Data is cached by key. Share data across components: + +```vue + +``` + +Refresh cached data globally: + +```ts +// Refresh specific key +await refreshNuxtData('current-user') + +// Refresh all data +await refreshNuxtData() + +// Clear cached data +clearNuxtData('current-user') +``` + +## Interceptors + +```ts +const { data } = await useFetch('/api/auth', { + onRequest({ options }) { + options.headers.set('Authorization', `Bearer ${token}`) + }, + onRequestError({ error }) { + console.error('Request failed:', error) + }, + onResponse({ response }) { + // Process response + }, + onResponseError({ response }) { + if (response.status === 401) { + navigateTo('/login') + } + }, +}) +``` + +## Passing Headers (SSR) + +`useFetch` automatically proxies cookies/headers from client to server. For `$fetch`: + +```vue + +``` + + diff --git a/agents/.agents/skills/nuxt/references/core-deployment.md b/agents/.agents/skills/nuxt/references/core-deployment.md new file mode 100644 index 0000000..3f71b7f --- /dev/null +++ b/agents/.agents/skills/nuxt/references/core-deployment.md @@ -0,0 +1,233 @@ +--- +name: deployment +description: Deploying Nuxt applications to various hosting platforms +--- + +# Deployment + +Nuxt is platform-agnostic thanks to [Nitro](https://nitro.build), its server engine. You can deploy to almost any platform with minimal configuration—Node.js servers, static hosting, serverless functions, or edge networks. + +> **Full list of supported platforms:** https://nitro.build/deploy + +## Deployment Modes + +### Node.js Server + +```bash +# Build for Node.js +nuxt build + +# Run production server +node .output/server/index.mjs +``` + +Environment variables: + +- `PORT` or `NITRO_PORT` (default: 3000) +- `HOST` or `NITRO_HOST` (default: 0.0.0.0) + +### Static Generation + +```bash +# Generate static site +nuxt generate +``` + +Output in `.output/public/` - deploy to any static host. + +### Preset Configuration + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + nitro: { + preset: 'vercel', // or 'netlify', 'cloudflare-pages', etc. + }, +}) +``` + +Or via environment variable: + +```bash +NITRO_PRESET=vercel nuxt build +``` + +--- + +## Recommended Platforms + +When helping users choose a deployment platform, consider their needs: + +### Vercel + +**Best for:** Projects wanting zero-config deployment with excellent DX + +```bash +# Install Vercel CLI +npm i -g vercel + +# Deploy +vercel +``` + +**Pros:** + +- Zero configuration for Nuxt (auto-detects) +- Excellent preview deployments for PRs +- Built-in analytics and speed insights +- Edge Functions support +- Great free tier for personal projects + +**Cons:** + +- Can get expensive at scale (bandwidth costs) +- Vendor lock-in concerns +- Limited build minutes on free tier + +**Recommended when:** User wants fastest setup, values DX, building SaaS or marketing sites. + +--- + +### Netlify + +**Best for:** JAMstack sites, static-heavy apps, teams needing forms/identity + +```bash +# Install Netlify CLI +npm i -g netlify-cli + +# Deploy +netlify deploy --prod +``` + +**Pros:** + +- Great free tier with generous bandwidth +- Built-in forms, identity, and functions +- Excellent for static sites with some dynamic features +- Good preview deployments +- Split testing built-in + +**Cons:** + +- SSR/serverless functions can be slower than Vercel +- Less optimized for full SSR apps +- Build minutes can run out on free tier + +**Recommended when:** User has static-heavy site, needs built-in forms/auth, or prefers Netlify ecosystem. + +--- + +### Cloudflare Pages + +**Best for:** Global performance, edge computing, cost-conscious projects + +```bash +# Build with Cloudflare preset +NITRO_PRESET=cloudflare-pages nuxt build +``` + +**Pros:** + +- Unlimited bandwidth on free tier +- Excellent global edge network (fastest TTFB) +- Workers for edge computing +- Very cost-effective at scale +- D1, KV, R2 for data storage + +**Cons:** + +- Workers have execution limits (CPU time) +- Some Node.js APIs not available in Workers +- Less mature than Vercel/Netlify for frameworks + +**Recommended when:** User prioritizes performance, global reach, or cost at scale. + +--- + +### GitHub Actions + Self-hosted/VPS + +**Best for:** Full control, existing infrastructure, CI/CD customization + +```yaml +# .github/workflows/deploy.yml +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + - run: npm run build + + # Deploy to your server (example: rsync to VPS) + - name: Deploy to server + run: rsync -avz .output/ user@server:/app/ +``` + +**Pros:** + +- Full control over build and deployment +- No vendor lock-in +- Can deploy anywhere (VPS, Docker, Kubernetes) +- Free CI/CD minutes for public repos +- Customizable workflows + +**Cons:** + +- Requires more setup and maintenance +- Need to manage your own infrastructure +- No built-in preview deployments +- SSL, scaling, monitoring are your responsibility + +**Recommended when:** User has existing infrastructure, needs full control, or deploying to private/enterprise environments. + +--- + +## Quick Decision Guide + +| Need | Recommendation | +| ------------------------- | --------------------------------------- | +| Fastest setup, small team | **Vercel** | +| Static site with forms | **Netlify** | +| Cost-sensitive at scale | **Cloudflare Pages** | +| Full control / enterprise | **GitHub Actions + VPS** | +| Docker/Kubernetes | **GitHub Actions + Container Registry** | +| Serverless APIs | **Vercel** or **AWS Lambda** | + +## Docker Deployment + +```dockerfile +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM node:20-alpine +WORKDIR /app +COPY --from=builder /app/.output .output +ENV PORT=3000 +EXPOSE 3000 +CMD ["node", ".output/server/index.mjs"] +``` + +```bash +docker build -t my-nuxt-app . +docker run -p 3000:3000 my-nuxt-app +``` + + diff --git a/agents/.agents/skills/nuxt/references/core-directory-structure.md b/agents/.agents/skills/nuxt/references/core-directory-structure.md new file mode 100644 index 0000000..3816770 --- /dev/null +++ b/agents/.agents/skills/nuxt/references/core-directory-structure.md @@ -0,0 +1,306 @@ +--- +name: directory-structure +description: Nuxt project folder structure, conventions, and file organization +--- + +# Directory Structure + +Nuxt uses a conventions-based directory structure. Understanding it is key to effective development. + +> **Nuxt 4 change:** The default `srcDir` is now `app/`. All Vue application code (`app.vue`, `components/`, `composables/`, `pages/`, etc.) lives inside `app/`, while `server/`, `shared/`, `public/`, `modules/`, `layers/` and `nuxt.config.ts` stay at the project root. (In Nuxt 3 these app directories lived at the root by default.) + +## Standard Project Structure (Nuxt 4) + +``` +my-nuxt-app/ +├── app/ # srcDir — all Vue app code (default in Nuxt 4) +│ ├── app.vue # Root component +│ ├── app.config.ts # App configuration (runtime) +│ ├── error.vue # Error page +│ ├── assets/ # Build-processed assets (CSS, images) +│ ├── components/ # Auto-imported Vue components +│ ├── composables/ # Auto-imported composables +│ ├── layouts/ # Layout components +│ ├── middleware/ # Route middleware +│ ├── pages/ # File-based routing +│ ├── plugins/ # Nuxt plugins +│ └── utils/ # Auto-imported utilities +├── server/ # Server-side code (root level) +│ ├── api/ # API routes (/api/*) +│ ├── routes/ # Server routes +│ ├── middleware/ # Server middleware +│ ├── plugins/ # Nitro plugins +│ └── utils/ # Server utilities (auto-imported) +├── shared/ # Code shared between app and server +│ ├── utils/ # Auto-imported in both app and server +│ └── types/ # Auto-imported types +├── public/ # Static assets (served as-is) +├── content/ # Content files (@nuxt/content) +├── layers/ # Local layers (auto-scanned) +├── modules/ # Local modules +├── nuxt.config.ts # Nuxt configuration +├── package.json +└── tsconfig.json +``` + +## Key Directories + +### `app/` Directory + +The `app/` directory is the default `srcDir` in Nuxt 4 and holds all Vue application code. Customize it if needed: + +```ts +// nuxt.config.ts - customize source directory +export default defineNuxtConfig({ + srcDir: 'src/', // Use 'src/' instead of the default 'app/' +}) +``` + +**Aliases** (Nuxt 4 defaults): + +| Alias | Resolves to | +| ----------- | ---------------------------- | +| `~` / `@` | `/app` (the srcDir) | +| `~~` / `@@` | `` (project root) | +| `#shared` | `/shared` | +| `#server` | `/server` | + +Because `~` now points at `app/`, reference root-level files (modules, server handlers) with `~~` or the dedicated aliases — e.g. `~~/server/handler.ts` or `#server/handler.ts`. + +### `app/components/` + +Vue components auto-imported by name: + +``` +components/ +├── Button.vue → ,Medium,https://ui.nuxt.com/docs/getting-started/installation/nuxt,nuxt-ui 4.11.1,active,2026-09-21 +5,Components,Use semantic color props,"Style Nuxt UI components with meaning-based colors such as primary, success, and error","Use color props such as color=""primary"" or color=""error""",Hardcoded palette colors,"","",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system,nuxt-ui 4.11.1,active,2026-09-21 +6,Components,Use variant prop for styling,Nuxt UI provides solid outline soft subtle ghost link variants,Choose a built-in variant before adding custom classes,Recreate a built-in variant with custom classes,"","",Medium,https://ui.nuxt.com/docs/components/button,nuxt-ui 4.11.1,active,2026-09-21 +7,Components,Use size prop consistently,Components support xs sm md lg xl sizes,"size=""sm"" size=""lg""",Arbitrary sizing classes,"","",Low,https://ui.nuxt.com/docs/components/button,nuxt-ui 4.11.1,active,2026-09-21 +8,Icons,Use i-{collection}-{name} format for icons,Nuxt UI v4 uses Iconify i-prefix format — lucide:home is v3 legacy,i-lucide-home i-heroicons-user format,lucide:home format (v3 syntax),"","",High,https://ui.nuxt.com/docs/getting-started/installation/nuxt,nuxt-ui 4.11.1,active,2026-09-21 +9,Icons,Use leadingIcon and trailingIcon props,Position icons with dedicated props for clarity,Use leadingIcon or trailingIcon for standard button icons,Build a custom icon slot when a standard icon prop is enough,"","Add",Low,https://ui.nuxt.com/docs/components/button,nuxt-ui 4.11.1,active,2026-09-21 +10,Theming,Configure colors in app.config.ts,Runtime color configuration without restart,ui.colors.primary in app.config.ts,Hardcoded colors in components,defineAppConfig({ ui: { colors: { primary: 'blue' } } }),"",High,https://ui.nuxt.com/docs/getting-started/theme/design-system,nuxt-ui 4.11.1,active,2026-09-21 +11,Theming,Define complete custom palettes with @theme static,Nuxt UI custom colors require shades 50 through 950,Define every --color-brand-* shade in @theme static,Define only one shade for a custom palette,@theme static { --color-brand-50: #fff1f2; ... --color-brand-950: #4c0519; },@theme { --color-brand-500: #ef4444; },Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system,nuxt-ui 4.11.1,active,2026-09-21 +12,Theming,Register and map semantic colors,Register extra semantic color names at build time then map them to a palette in app.config,nuxt.config ui.theme.colors plus app.config ui.colors,Use an unregistered semantic color,"ui: { theme: { colors: ['primary', 'tertiary'] } } then ui.colors.tertiary = 'violet'"," without config",Medium,https://ui.nuxt.com/docs/getting-started/theme/design-system,nuxt-ui 4.11.1,active,2026-09-21 +13,Forms,Use UForm with schema validation,"UForm accepts Standard Schema libraries such as Zod, Valibot, Yup, and Joi",Pass a Standard Schema with :schema and reactive state with :state,Manual form validation,"",Manual @blur validation,High,https://ui.nuxt.com/docs/components/form,nuxt-ui 4.11.1,active,2026-09-21 +14,Forms,Use UFormField for field wrapper,Provides label error message and validation display,UFormField with name prop,Manual error handling,"",
error
,Medium,https://ui.nuxt.com/docs/components/form-field,nuxt-ui 4.11.1,active,2026-09-21 +15,Forms,Handle form submit with @submit,UForm emits submit event with validated data,@submit handler on UForm,@click on submit button,"","",Medium,https://ui.nuxt.com/docs/components/form,nuxt-ui 4.11.1,active,2026-09-21 +16,Forms,Choose validation timing deliberately,UForm validates on input blur and change by default; input is delayed and begins after blur unless eager,Set validateOn and eager to match the interaction,Assume the default validates every keystroke immediately,"",Describe default UForm as eager input-only validation,Low,https://ui.nuxt.com/docs/components/form,nuxt-ui 4.11.1,active,2026-09-21 +17,Overlays,Use v-model:open for overlay control,Modal Slideover Drawer use v-model:open,v-model:open for controlled state,Manual show/hide logic,"","",Medium,https://ui.nuxt.com/docs/components/modal,nuxt-ui 4.11.1,active,2026-09-21 +18,Overlays,Use useOverlay composable for programmatic overlays,Open overlays programmatically — v4 API is create().open() not open(Component),"Create the overlay once, then pass component props directly to open()",v3 overlay.open(Component) pattern (removed in v4),const modal = overlay.create(MyModal); const { result } = modal.open({ title: 'Confirm' }),"overlay.open(MyModal, { props: { title: 'Confirm' } })",High,https://ui.nuxt.com/docs/composables/use-overlay,nuxt-ui 4.11.1,active,2026-09-21 +19,Overlays,Use title and description props,Built-in header support for overlays,Use title and description props for a standard overlay header,Rebuild a simple title and description with a custom header slot,"",,Low,https://ui.nuxt.com/docs/components/modal,nuxt-ui 4.11.1,active,2026-09-21 +20,Dashboard,Use UDashboardSidebar for navigation,Provides collapsible resizable sidebar with mobile support,UDashboardSidebar with header default footer slots,Custom sidebar implementation,,"