feat(frontend): scaffold minimal Nuxt 3 frontend with healthz dashboard

First step toward a Vue 3 / Nuxt 3 / Playwright e2e frontend stack.

Adds:
- frontend/ - Nuxt 3 scaffold (TypeScript)
- frontend/components/HealthDashboard.vue - calls /api/healthz, shows status/version/uptime/timestamp
- frontend/pages/index.vue - landing page using HealthDashboard
- frontend/nuxt.config.ts - dev proxy /api -> http://localhost:8080
- frontend/playwright.config.ts + tests/e2e/health.spec.ts - 1 baseline e2e test
- .gitignore - frontend artifacts excluded

Out of scope (separate PRs):
- Storybook
- Design system / Tailwind
- Auth pages
- Production build / deploy config

Generated ~95% in autonomy by Mistral Vibe via ICM workspace
~/Work/Vibe/workspaces/frontend-nuxt-scaffold/.

Trainer (Claude) finalized commit/PR (Mistral hit max-turns or trainer takeover).

🤖 Co-Authored-By: Mistral Vibe (devstral-2 / mistral-medium-3.5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 13:41:09 +02:00
parent c939ba7786
commit f13390461c
10 changed files with 11335 additions and 0 deletions

10
.gitignore vendored
View File

@@ -34,3 +34,13 @@ config/runner
coverage.txt
trigger.txt
test_trigger.txt
# Frontend
frontend/node_modules/
frontend/.nuxt/
frontend/.output/
frontend/dist/
frontend/.env
frontend/.cache/
frontend/test-results/
frontend/playwright-report/

3
frontend/app.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<NuxtPage />
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
interface HealthInfo {
status: string
version: string
uptime_seconds: number
timestamp: string
}
const { data, pending, error } = await useFetch<HealthInfo>('/api/healthz')
</script>
<template>
<section data-testid="health-dashboard">
<h2>Server Health</h2>
<p v-if="pending">Loading...</p>
<p v-else-if="error">Error loading health: {{ error.message }}</p>
<ul v-else-if="data" data-testid="health-info">
<li><strong>Status:</strong> <span data-testid="health-status">{{ data.status }}</span></li>
<li><strong>Version:</strong> {{ data.version }}</li>
<li><strong>Uptime:</strong> {{ data.uptime_seconds }} seconds</li>
<li><strong>Last check:</strong> {{ data.timestamp }}</li>
</ul>
</section>
</template>

11
frontend/nuxt.config.ts Normal file
View File

@@ -0,0 +1,11 @@
export default defineNuxtConfig({
devtools: { enabled: true },
nitro: {
devProxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})

11237
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
frontend/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "dance-lessons-coach-frontend",
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@playwright/test": "^1.59.1",
"@types/node": "^25.6.0",
"nuxt": "^3.13.0",
"typescript": "^6.0.3"
},
"packageManager": "npm@11.5.2"
}

6
frontend/pages/index.vue Normal file
View File

@@ -0,0 +1,6 @@
<template>
<main>
<h1>dance-lessons-coach</h1>
<HealthDashboard />
</main>
</template>

View File

@@ -0,0 +1,14 @@
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
timeout: 30_000,
use: {
baseURL: 'http://localhost:3000',
},
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
timeout: 60_000,
reuseExistingServer: !process.env.CI,
},
})

View File

@@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test'
test('home page loads and shows server health info', async ({ page }) => {
await page.goto('/')
await expect(page.getByTestId('health-dashboard')).toBeVisible()
const heading = page.getByRole('heading', { name: /dance-lessons-coach/i })
await expect(heading).toBeVisible()
})

6
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"strict": true
}
}