plugins/lt-dev/skills/developing-lt-frontend/SKILL.md
Handles ALL Nuxt 4 and Vue frontend development tasks including composables, forms (Valibot), API integration (types.gen.ts, sdk.gen.ts), authentication (Better Auth), SSR, and Playwright E2E testing. Supports monorepos (projects/app/, packages/app/). Activates when working with .vue files, nuxt.config.ts, Nuxt UI, TailwindCSS, composables, server components, forms, or files in app/components/, app/composables/, app/pages/, app/interfaces/, app/layouts/. Also activates on phrases like "generate types", "sdk.gen.ts regenerate", "Valibot form", "useOverlay modal", "Chrome DevTools debug", "Playwright E2E". NOT for NestJS backend (use generating-nest-servers). NOT for framework-agnostic security theory (use general-frontend-security).
npx skillsauth add lennetech/claude-code developing-lt-frontendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
nuxt-base-starter runs vitest+oxlint+oxfmt, not eslint+prettier — Projects originally generated from older starters still ship eslint + prettier + Playwright-only. When aligning a project with the current starter, expect to migrate the entire toolchain: install oxlint + oxfmt + vitest + @vitejs/plugin-vue + happy-dom, drop eslint + prettier + jsdom, add tests/unit/setup.ts + tests/unit/mocks/nuxt-imports.ts, and update package.json scripts to test:unit, test:e2e, lint, format, format:check, plus the check / check:fix aggregate. Full recipe lives in the modernizing-toolchain skill (Phase 4).process.env.PORT as a string and feed it directly into net.Server#listen, which crashes with ERR_SOCKET_BAD_PORT options.port should be >= 0 and < 65536. Received type string. Always prefer NITRO_PORT=<num> for the production build (node .output/server/index.mjs) — NITRO_PORT is the documented Nitro-specific knob, goes through Nitro's own env loader, and is coerced to number reliably. The Nuxt dev server (nuxt dev) is unaffected — nuxt.config.ts devServer.port works as expected.nuxt-base-starter baseline, sync every dep version (both dependencies and devDependencies) to what the starter ships and read the CHANGELOG of any package whose major moved. The recurring trap that a blanket version-sync does not fix is missing direct deps after a peer-restructure: when Rollup failed to resolve import "X" (or an equivalent module-not-found at install time) appears, the wrapper package no longer pulls "X" transitively — declare X as a direct dependency in package.json, even if no app code imports it directly.pnpm run generate-types needs a RUNNING API — The generator fetches the OpenAPI/GraphQL schema from the API. Under lt dev up the URL is https://api.<slug>.localhost (see the "Active lt-dev project" context block); without lt dev it falls back to http://localhost:3000. If the API is not running, the command completes successfully but produces an empty or stale types.gen.ts / sdk.gen.ts — no error is raised. Always verify the API is up before regenerating (curl -k https://api.<slug>.localhost/health for lt-dev mode, curl http://localhost:3000/health otherwise).du vs Sie addressing matches the existing tone of the screen.useOverlay() for modals — NOT conditional rendering — The default instinct is <MyModal v-if="showModal" />. This bypasses Nuxt UI's modal stack, breaks focus trapping, and causes z-index issues with nested dialogs. The correct pattern is useOverlay().create(ModalComponent) from composables. See reference/modals.md.types.gen.ts and sdk.gen.ts are GENERATED — never hand-edit — Manual changes are overwritten on next generate-types run. If a type is missing, the fix is on the API side (add @ApiProperty, @Field, etc.) not in the generated file. .gitignore does NOT ignore these files — they ARE committed, but only via the regeneration command.lt dev up is used, these env vars are set automatically to the project's stable HTTPS URLs (https://api.<slug>.localhost, https://<slug>.localhost) and auth works regardless of internal port. The legacy "3000/3001 only" rule applies ONLY to non-migrated projects with hardcoded URLs. Run lt dev init once to migrate. See managing-dev-servers skill for the full URL rules.Developers typically work in a Lerna fullstack monorepo created via lt fullstack init:
project/
├── projects/
│ ├── api/ ← nest-server-starter (depends on @lenne.tech/nest-server)
│ └── app/ ← nuxt-base-starter (depends on @lenne.tech/nuxt-extensions)
├── lerna.json
└── package.json (workspaces: ["projects/*"])
Package relationships:
@lenne.tech/nest-serverprojects/app/ and any code using nuxt-base-starter or nuxt-extensionsapp/components/, app/composables/, app/pages/, app/interfaces/types.gen.ts, sdk.gen.ts)projects/app/ or packages/app/ structureNOT for: NestJS backend development (use generating-nest-servers skill instead)
ALWAYS read actual source code from node_modules/@lenne.tech/nuxt-extensions/ before guessing framework behavior. The framework ships documentation with the npm package.
| File (in node_modules/@lenne.tech/nuxt-extensions/) | When to Read |
|-------------------------------------------------------|-------------|
| CLAUDE.md | Start of any frontend task — composables, components, config |
| dist/runtime/composables/ | Available composables (useLtAuth, useLtAuthClient, useLtTusUpload, useLtFile, useLtShare, useLtErrorTranslation, and from 1.7.0 the useLtAi* family) |
| dist/runtime/components/ | Available components |
| dist/runtime/utils/ | Available utilities |
| dist/runtime/types/ | TypeScript type definitions |
Also read the nuxt-base-starter documentation:
README.md — Project overview, tech stack, auth setupAUTH.md — Better Auth integration detailsNever use placeholder data, TODO comments, or manual interfaces!
sdk.gen.ts from the starttypes.gen.ts (never manual interfaces for DTOs)pnpm run generate-types with API running before starting frontend workBefore starting: Ensure services are running. See reference/service-health-check.md
| User Intent | Correct Skill | |------------|---------------| | "Build a Vue component" | THIS SKILL | | "Create a Nuxt page" | THIS SKILL | | "Style with TailwindCSS" | THIS SKILL | | "Create a NestJS module" | generating-nest-servers | | "Security audit of frontend" | general-frontend-security | | "Implement with TDD" | building-stories-with-tdd |
Works closely with:
generating-nest-servers - For NestJS backend development (projects/api/)using-lt-cli - For Git operations and Fullstack initializationbuilding-stories-with-tdd - For complete TDD workflow (Backend + Frontend)contributing-to-lt-framework - When modifying @lenne.tech/nuxt-extensions itself and testing via pnpm link/lt-dev:frontend:env-migrate - Migrate env variables to NUXT_ prefix conventionWhen starting the App for manual testing, Chrome DevTools MCP debugging, or E2E tests: prefer lt dev up over nuxt dev directly. It serves the App under a stable HTTPS URL (https://<slug>.localhost) via Caddy, sets NUXT_API_URL/NUXT_PUBLIC_SITE_URL/NUXT_PUBLIC_STORAGE_PREFIX/NUXT_PUBLIC_API_PROXY=false automatically, and detaches into <root>/.lt-dev/app.log. Stop with lt dev down. For non-lt-projects (or when explicitly requested): use run_in_background: true and pkill -f "nuxt dev" afterwards. Leaving dev servers orphaned blocks the Claude Code session ("Unfurling..."). Full rules: managing-dev-servers skill.
In monorepo projects:
projects/app/ or packages/app/ → This skillprojects/api/ or packages/api/ → generating-nest-servers skillapp/ # Application code (srcDir)
├── components/ # Auto-imported components
├── composables/ # Auto-imported composables
├── interfaces/ # TypeScript interfaces
├── lib/ # Utility libraries (auth-client, etc.)
├── pages/ # File-based routing
├── layouts/ # Layout components
├── utils/ # Auto-imported utilities
└── api-client/ # Generated types & SDK
server/ # Nitro server routes
public/ # Static assets
nuxt.config.ts
| Priority | Source | Use For |
|----------|--------|---------|
| 1. | ~/api-client/types.gen.ts | All backend DTOs (REQUIRED) |
| 2. | ~/api-client/sdk.gen.ts | All API calls (REQUIRED) |
| 3. | Nuxt UI types | Component props (auto-imported) |
| 4. | app/interfaces/*.interface.ts | Frontend-only types (UI state, forms) |
| Rule | Value |
|------|-------|
| UI Labels | German (Speichern, Abbrechen) |
| Code/Comments | English |
| Styling | TailwindCSS only, no <style> |
| Colors | Semantic only (primary, error, success) |
| Types | Explicit, no implicit any |
| Backend Types | Generated only (types.gen.ts) |
| Composables | app/composables/use*.ts |
| Shared State | useState() for SSR-safe state |
| Local State | ref() / reactive() |
| Forms | Valibot (not Zod) |
| Modals | useOverlay() |
1. Backend API must be complete (API tests pass)
2. Write E2E tests BEFORE implementing frontend
3. Implement components/pages until E2E tests pass
4. Debug with Chrome DevTools MCP
Complete E2E testing guide: reference/e2e-testing.md
useLtErrorTranslationThe backend returns structured errors in the format #LTNS_XXXX: Developer message (core) or #PROJ_XXXX: ... (project-specific). The @lenne.tech/nuxt-extensions package ships useLtErrorTranslation() which parses the #CODE: marker, loads locale-specific translations from GET /i18n/errors/:locale, and returns end-user messages.
NEVER assert or display raw English backend messages in the UI. Always pipe errors through translateError() / showErrorToast() so users see localized text.
<script setup lang="ts">
const { translateError, showErrorToast, parseError } = useLtErrorTranslation();
const toast = useToast();
async function onSubmit() {
try {
await $fetch('/api/users', { method: 'POST', body: form.value });
} catch (error) {
// Preferred — direct toast from translated message
showErrorToast(error, 'Speichern fehlgeschlagen');
// Or manual, if you need more control
toast.add({
color: 'error',
title: 'Speichern fehlgeschlagen',
description: translateError(error), // '#LTNS_0400: Resource not found' → 'Ressource nicht gefunden.'
});
// Or parse for custom handling (e.g. redirect on specific code)
const parsed = parseError(error);
if (parsed.code === 'LTNS_0023') {
await navigateTo('/auth/verify-email');
}
}
}
</script>
Rules:
useLtErrorTranslation() — no raw error.message in Toast descriptions, form errors, or page-level error UIloadTranslations(locale) is called once at app start or on locale change (the composable caches per locale via useState)if (parsed.code === 'LTNS_XXXX')) for flow-control decisions (verification-required redirects, retry prompts) — never branch on message-string contents'Anmeldung fehlgeschlagen'); descriptions come from translateErrorerror.message) — see the test-reviewer rules in this pluginFull consumer reference: reference/error-translation.md
| Topic | File | |-------|------| | Core Patterns | reference/patterns.md | | Service Health Check | reference/service-health-check.md | | Browser Testing | reference/browser-testing.md | | TypeScript | reference/typescript.md | | Components | reference/components.md | | Composables | reference/composables.md | | Forms | reference/forms.md | | Modals | reference/modals.md | | API | reference/api.md | | Colors | reference/colors.md | | Nuxt Patterns | reference/nuxt.md | | Authentication | reference/authentication.md | | E2E Testing | reference/e2e-testing.md | | Troubleshooting | reference/troubleshooting.md | | Security | reference/security.md | | Error Translation (consume backend ErrorCodes) | reference/error-translation.md | | Informed Trade-offs (Composition API, readonly, SSR guards, v-html, useFetch) | reference/informed-trade-off-pattern.md |
sdk.gen.ts, all types from types.gen.tsuseOverlay, forms use ValibotanyuseLtAuth(), protected routes use middleware: 'auth'useLtAiChat().stop() for clean abort (NEVER raw AbortController.abort() on a useLtAi* stream — the composable does cleanup and treats AbortError as a clean stop)LtAiPromptInput (CRUD for useLtAiPrompts) vs LtAiPromptRunInput (execution payload for useLtAi.prompt() / .promptStream()) — never conflate; pre-1.7.0 they collided as one name and TypeScript silently merged themv-html with user content, tokens stored securelyuseLtErrorTranslation() — no raw backend messages in Toasts / UI/lt-dev:review for general scan)development
Single source of truth for the lenne.tech fullstack production-readiness checklist. Defines the eight pillars (configuration & secrets, observability & logging, health & lifecycle, security hardening, data durability, resilience under load, deployment hygiene, runbook & rollback) with concrete file/line evidence requirements per pillar, severity classification (Critical / Major / Minor), and a canonical machine-parseable report block. Activates whenever an agent or command needs to gate a release on production-readiness — currently used by /lt-dev:production-ready, lt-dev:production-readiness-orchestrator, and the devops-reviewer (read-only). NOT for OWASP-style code-level security review (use security-reviewer). NOT for npm dependency audits (use maintaining-npm-packages).
development
Single source of truth for executing GitLab CI/CD pipelines locally with the same image, env vars, and service containers as the real runner — so pipeline failures are caught before push. Defines pipeline discovery (.gitlab-ci.yml + includes), per-job execution via gitlab-runner exec, service-container orchestration (Mongo, Redis, MailHog), env injection without secrets, cache/artifact handling, and a job-by-job verdict report. Also describes the GitHub Actions equivalent via act for projects that mirror to GitHub. Activates whenever an agent or command needs to validate that the CI pipeline will pass — currently used by /lt-dev:production-ready and lt-dev:production-readiness-orchestrator. NOT for running the local check script (use running-check-script). NOT for writing or refactoring CI configs (use the devops agent).
development
Single source of truth for designing, running, and interpreting k6 load tests against lenne.tech fullstack APIs. Defines installation paths (brew, docker, npm), the three canonical scenarios (smoke / load / soak), endpoint discovery from the generated SDK, realistic Better-Auth login flows, threshold defaults for ~10 concurrent users (p95 < 500ms, error rate < 1%, http_req_failed < 1%), result interpretation, and the optimisation ladder when the system fails (DB indices, query rewrites, caching, connection pool sizing, rate-limit relaxation, payload trimming). Activates whenever an agent or command needs to validate that the API is stable for ~10 concurrent users performing many actions in short time, or to detect performance regressions via k6. Currently used by /lt-dev:production-ready, lt-dev:production-readiness-orchestrator, and lt-dev:performance-reviewer. NOT for Lighthouse frontend performance (use a11y-reviewer). NOT for unit performance assertions (use the test runner directly).
tools
Migrates lenne.tech projects from the legacy jest+eslint+prettier toolchain to the current vitest+oxlint+oxfmt baseline used by nest-server-starter and nuxt-base-starter. Covers swc decoratorMetadata config, the @Prop union-type fix for SWC, supertest default-import correction, ESM/CJS interop, the Nitro PORT-vs-NITRO_PORT bug, ANSI escape stripping in workspace runners (lerna/nx), free-port logic for check-server-start.sh, the offers-pattern config.env.ts (NSC__-only + fail-fast + auto-derived appUrl), and the multi-phase check-envs.sh smoke test. Activates whenever someone is migrating an existing project to the new toolchain, debugging "Cannot determine a type for the X field" Mongoose errors, ERR_SOCKET_BAD_PORT crashes from check-server-start, or wants to align an existing project with the current starter conventions.