# Fase 1 — Fondamenta · Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Avere uno scheletro SvelteKit funzionante e deployabile su Vercel, con DB Neon (Drizzle), autenticazione email/password (Better Auth con verifica email e reset), email via Resend, design system di base (tema pergamena) e una home + dashboard protetta.

**Architecture:** App unica SvelteKit (`adapter-vercel`). Accesso dati server-only via Drizzle su Neon Postgres (`drizzle-orm/neon-http`). Auth con Better Auth (adapter Drizzle/pg) montato in `hooks.server.ts`. Email transazionali con Resend (fallback console in dev). Stile con Tailwind v4 + token CSS del tema approvato. Engine eventi NON in questa fase.

**Tech Stack:** SvelteKit · TypeScript · pnpm · Tailwind v4 (`@tailwindcss/vite`) · Drizzle ORM + `@neondatabase/serverless` · Neon Postgres · Better Auth · Resend · Vitest · `@sveltejs/adapter-vercel`.

**Riferimenti:** spec [`../specs/2026-06-07-tta-piattaforma-design.md`](../specs/2026-06-07-tta-piattaforma-design.md) · ADR stack [`../../decisioni/2026-06-07-stack-architettura.md`](../../decisioni/2026-06-07-stack-architettura.md) · palette in `.superpowers/brainstorm/*/design-proposal.html`.

> ⚠️ **Verifica librerie a runtime:** Better Auth e Drizzle evolvono. Le API qui sotto sono allineate alla doc 2026-06; se un comando cambia, confermare con context7 (`/better-auth/better-auth`, `/websites/orm_drizzle_team`) prima di adattare. Non reinventare: seguire la doc ufficiale.

---

## Task 0 — Prerequisiti forniti da Paolo (no codice, sblocca l'esecuzione)

**Files:** `.env` (NON committato), `.env.example` (committato).

- [ ] **Step 1: Neon** — creare un DB Postgres (Vercel → Storage/Marketplace → Neon, oppure neon.tech) e copiare la **connection string pooled** (`postgresql://...?sslmode=require`).
- [ ] **Step 2: Resend** — creare account, generare una **API key** (`re_...`). In dev senza key, le email verranno loggate in console (vedi Task 6).
- [ ] **Step 3: Secret auth** — generare un secret: `openssl rand -base64 32`.
- [ ] **Step 4:** creare `.env` nella root del progetto con:

```bash
DATABASE_URL="postgresql://...pooled...?sslmode=require"
BETTER_AUTH_SECRET="<output di openssl rand -base64 32>"
BETTER_AUTH_URL="http://localhost:5173"
RESEND_API_KEY="re_xxx"   # opzionale in dev
EMAIL_FROM="TtA Goblin <noreply@tuo-dominio>"  # in dev può restare onboarding@resend.dev
```

- [ ] **Step 5:** Vercel login (serve solo al Task 8): l'utente esegue `! vercel login`.

---

## Task 1 — Scaffold SvelteKit nel repo esistente

La cartella contiene già `docs/ data/ tasks/ .git`. Si scaffolda in una dir temporanea e si copiano i file dell'app nella root.

**Files:**
- Create: `package.json`, `svelte.config.js`, `vite.config.ts`, `tsconfig.json`, `src/`, `static/`, `.npmrc`
- Modify: `.gitignore` (già presente, verificare copra `node_modules/ .svelte-kit/ build/ .vercel`)

- [ ] **Step 1: Scaffold in temp**

```bash
pnpm dlx sv create /tmp/tta-app --template minimal --types ts --no-add-ons
```

- [ ] **Step 2: Copiare i file dell'app nella root del progetto** (senza sovrascrivere docs/data/tasks/.git/README/CLAUDE)

```bash
cd "/Users/paolo/Server/Siti Web/Lavoro/TtA"
rsync -a --exclude='.git' --exclude='README.md' /tmp/tta-app/ ./
```

Se `rsync` non c'è: `cp -R /tmp/tta-app/{package.json,svelte.config.js,vite.config.ts,tsconfig.json,src,static,.npmrc} ./`

- [ ] **Step 3: Installare adapter Vercel e dipendenze**

```bash
pnpm add -D @sveltejs/adapter-vercel
pnpm install
```

- [ ] **Step 4: Impostare adapter-vercel** in `svelte.config.js`

```js
import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
  preprocess: vitePreprocess(),
  kit: { adapter: adapter() }
};
export default config;
```

- [ ] **Step 5: Verificare che parta**

Run: `pnpm dev`
Expected: server su `http://localhost:5173`, pagina di default visibile. Poi `Ctrl-C`.

- [ ] **Step 6: Commit**

```bash
git add -A && git commit -m "feat(fase1): scaffold SvelteKit + adapter-vercel

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 2 — Tailwind v4 + token del tema + layout base

**Files:**
- Create: `src/app.css`, `src/routes/+layout.svelte`, `src/lib/components/Header.svelte`, `src/lib/components/Footer.svelte`
- Modify: `vite.config.ts`

- [ ] **Step 1: Installare Tailwind v4**

```bash
pnpm add -D tailwindcss @tailwindcss/vite
```

- [ ] **Step 2: Aggiungere il plugin in `vite.config.ts`**

```ts
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [tailwindcss(), sveltekit()]
});
```

- [ ] **Step 3: Creare `src/app.css` con i token del tema approvato (pergamena + civiltà; 4 colori giocatore funzionali)**

```css
@import "tailwindcss";

@theme {
  /* Base / marchio */
  --color-pergamena: #F3EAD6;
  --color-carta: #FBF6EA;
  --color-inchiostro: #241D14;
  --color-sepia: #7A6A4F;
  --color-bronzo: #9C6B3F;
  --color-oro: #B5852F;
  --color-verderame: #2C7A74;
  --color-oxblood: #7C2D12;
  /* 4 colori giocatore (uso funzionale) */
  --color-giocatore-giallo: #E0A500;
  --color-giocatore-blu: #2C5FA0;
  --color-giocatore-rosso: #B23A33;
  --color-giocatore-verde: #3E8E5A;
  --font-display: Georgia, "Iowan Old Style", serif;
}

html { background: var(--color-pergamena); color: var(--color-inchiostro); }
body { font-family: system-ui, sans-serif; }
h1, h2, h3 { font-family: var(--font-display); }
```

- [ ] **Step 4: Header e Footer**

`src/lib/components/Header.svelte`:
```svelte
<header class="border-b" style="border-color:var(--color-sepia); background:var(--color-inchiostro)">
  <nav class="max-w-5xl mx-auto flex items-center gap-6 px-4 py-3" style="color:var(--color-pergamena)">
    <a href="/" class="text-xl font-bold" style="font-family:var(--font-display)">TtA&nbsp;Goblin</a>
    <a href="/eventi" class="opacity-90 hover:opacity-100">Eventi</a>
    <a href="/classifiche" class="opacity-90 hover:opacity-100">Classifiche</a>
    <a href="/albo" class="opacity-90 hover:opacity-100">Albo d'Oro</a>
    <a href="/regolamento" class="opacity-90 hover:opacity-100">Regolamento</a>
    <a href="/login" class="ml-auto" style="color:var(--color-oro)">Accedi</a>
  </nav>
</header>
```

`src/lib/components/Footer.svelte`:
```svelte
<footer class="max-w-5xl mx-auto px-4 py-8 text-sm" style="color:var(--color-sepia)">
  TtA Goblin — tornei Through the Ages della Tana dei Goblin.
</footer>
```

- [ ] **Step 5: Layout root** `src/routes/+layout.svelte`

```svelte
<script>
  import '../app.css';
  import Header from '$lib/components/Header.svelte';
  import Footer from '$lib/components/Footer.svelte';
  let { children } = $props();
</script>

<Header />
<main class="max-w-5xl mx-auto px-4 py-8 min-h-[70vh]">{@render children()}</main>
<Footer />
```

- [ ] **Step 6: Verifica visiva**

Run: `pnpm dev` → la home mostra header su sfondo inchiostro e pagina pergamena. `Ctrl-C`.

- [ ] **Step 7: Commit**

```bash
git add -A && git commit -m "feat(fase1): tailwind v4 + token tema pergamena + layout base

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 3 — Drizzle + Neon: client, schema base, prima tabella

**Files:**
- Create: `drizzle.config.ts`, `src/lib/server/db/index.ts`, `src/lib/server/db/schema.ts`
- Test: `src/lib/server/db/db.smoke.test.ts`

- [ ] **Step 1: Installare**

```bash
pnpm add drizzle-orm @neondatabase/serverless
pnpm add -D drizzle-kit
```

- [ ] **Step 2: `drizzle.config.ts`**

```ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
  schema: './src/lib/server/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL! }
});
```

- [ ] **Step 3: Client** `src/lib/server/db/index.ts`

```ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import { env } from '$env/dynamic/private';
import * as schema from './schema';

const sql = neon(env.DATABASE_URL);
export const db = drizzle({ client: sql, schema });
```

- [ ] **Step 4: Schema iniziale** `src/lib/server/db/schema.ts` (la tabella `player`; le tabelle auth le genera Better Auth nel Task 4)

```ts
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core';

export const player = pgTable('player', {
  id: serial('id').primaryKey(),
  userId: text('user_id'),            // FK verso user (Better Auth) — collegata nel Task 4
  nickTdg: text('nick_tdg').unique(),
  nomeAppTta: text('nome_app_tta').notNull().unique(),
  attivo: boolean('attivo').notNull().default(true),
  note: text('note'),
  createdAt: timestamp('created_at').notNull().defaultNow()
});
```

- [ ] **Step 5: Creare le tabelle su Neon**

Run: `pnpm drizzle-kit push`
Expected: crea la tabella `player` su Neon (output "Changes applied"). Verificare su Neon SQL editor: `SELECT * FROM player;` → 0 righe, nessun errore.

- [ ] **Step 6: Smoke test connessione** `src/lib/server/db/db.smoke.test.ts`

```ts
import { describe, it, expect } from 'vitest';
import { db } from './index';
import { player } from './schema';

describe('db connectivity', () => {
  it('esegue una select su player senza errori', async () => {
    const rows = await db.select().from(player).limit(1);
    expect(Array.isArray(rows)).toBe(true);
  });
});
```

(Il runner Vitest si installa nel Task 7; questo test si esegue lì o subito dopo aver fatto il Task 7.)

- [ ] **Step 7: Commit**

```bash
git add -A && git commit -m "feat(fase1): drizzle + neon, tabella player, smoke test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 4 — Better Auth (email/password + verifica + reset) con Drizzle

**Files:**
- Create: `src/lib/server/auth.ts`, `src/lib/auth-client.ts`, `src/hooks.server.ts`, `src/routes/api/auth/[...all]/+server.ts`
- Modify: `src/lib/server/db/schema.ts` (aggiunta tabelle auth generate)

- [ ] **Step 1: Installare**

```bash
pnpm add better-auth
```

- [ ] **Step 2: Config auth** `src/lib/server/auth.ts` (gli sender email reali arrivano nel Task 6: per ora console.log)

```ts
import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { env } from '$env/dynamic/private';
import { db } from './db';

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: 'pg' }),
  secret: env.BETTER_AUTH_SECRET,
  baseURL: env.BETTER_AUTH_URL,
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    minPasswordLength: 8,
    sendResetPassword: async ({ user, url }) => {
      console.log(`[reset password] ${user.email} -> ${url}`);
    }
  },
  emailVerification: {
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
      console.log(`[verifica email] ${user.email} -> ${url}`);
    }
  }
});
```

- [ ] **Step 3: Generare le tabelle auth nello schema Drizzle**

Run: `pnpm dlx @better-auth/cli generate --output ./src/lib/server/db/auth-schema.ts`
Poi re-export in `schema.ts` aggiungendo in fondo:
```ts
export * from './auth-schema';
```
Expected: file `auth-schema.ts` con tabelle `user`, `session`, `account`, `verification`.

- [ ] **Step 4: Applicare a Neon**

Run: `pnpm drizzle-kit push`
Expected: crea `user/session/account/verification`.

- [ ] **Step 5: Handler API** `src/routes/api/auth/[...all]/+server.ts`

```ts
import { auth } from '$lib/server/auth';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = ({ request }) => auth.handler(request);
export const POST: RequestHandler = ({ request }) => auth.handler(request);
```

- [ ] **Step 6: Hook sessione** `src/hooks.server.ts`

```ts
import { auth } from '$lib/server/auth';
import { svelteKitHandler } from 'better-auth/svelte-kit';
import { building } from '$app/environment';

export async function handle({ event, resolve }) {
  return svelteKitHandler({ event, resolve, auth, building });
}
```

- [ ] **Step 7: Client** `src/lib/auth-client.ts`

```ts
import { createAuthClient } from 'better-auth/svelte';
export const authClient = createAuthClient();
```

- [ ] **Step 8: Verifica build**

Run: `pnpm build`
Expected: build OK senza errori di tipo.

- [ ] **Step 9: Commit**

```bash
git add -A && git commit -m "feat(fase1): better-auth email/password con drizzle adapter + schema auth

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 5 — Pagine auth (registrazione/login/logout) + dashboard protetta

**Files:**
- Create: `src/routes/register/+page.svelte`, `src/routes/login/+page.svelte`, `src/routes/dashboard/+page.svelte`, `src/routes/dashboard/+page.server.ts`

- [ ] **Step 1: Registrazione** `src/routes/register/+page.svelte`

```svelte
<script>
  import { authClient } from '$lib/auth-client';
  let email = $state(''); let password = $state(''); let nick = $state(''); let msg = $state('');
  async function submit(e) {
    e.preventDefault();
    const { error } = await authClient.signUp.email({ email, password, name: nick });
    msg = error ? error.message : 'Registrato! Controlla la mail per verificare (in dev: vedi console).';
  }
</script>
<h1 class="text-2xl mb-4">Registrazione</h1>
<form onsubmit={submit} class="flex flex-col gap-3 max-w-sm">
  <input class="border p-2 rounded" placeholder="Nickname TtA" bind:value={nick} required />
  <input class="border p-2 rounded" type="email" placeholder="Email" bind:value={email} required />
  <input class="border p-2 rounded" type="password" placeholder="Password (min 8)" bind:value={password} required />
  <button class="p-2 rounded text-white" style="background:var(--color-bronzo)">Crea account</button>
  {#if msg}<p class="text-sm">{msg}</p>{/if}
</form>
```

- [ ] **Step 2: Login** `src/routes/login/+page.svelte`

```svelte
<script>
  import { authClient } from '$lib/auth-client';
  import { goto } from '$app/navigation';
  let email = $state(''); let password = $state(''); let msg = $state('');
  async function submit(e) {
    e.preventDefault();
    const { error } = await authClient.signIn.email({ email, password });
    if (error) msg = error.message; else goto('/dashboard');
  }
</script>
<h1 class="text-2xl mb-4">Accedi</h1>
<form onsubmit={submit} class="flex flex-col gap-3 max-w-sm">
  <input class="border p-2 rounded" type="email" placeholder="Email" bind:value={email} required />
  <input class="border p-2 rounded" type="password" placeholder="Password" bind:value={password} required />
  <button class="p-2 rounded text-white" style="background:var(--color-bronzo)">Entra</button>
  {#if msg}<p class="text-sm" style="color:var(--color-oxblood)">{msg}</p>{/if}
</form>
```

- [ ] **Step 3: Guardia server dashboard** `src/routes/dashboard/+page.server.ts`

```ts
import { auth } from '$lib/server/auth';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ request }) => {
  const session = await auth.api.getSession({ headers: request.headers });
  if (!session) throw redirect(302, '/login');
  return { user: session.user };
};
```

- [ ] **Step 4: Dashboard** `src/routes/dashboard/+page.svelte`

```svelte
<script>
  import { authClient } from '$lib/auth-client';
  import { goto } from '$app/navigation';
  let { data } = $props();
  async function logout() { await authClient.signOut(); goto('/'); }
</script>
<h1 class="text-2xl mb-4">Ciao, {data.user.name}</h1>
<p class="mb-4">Email: {data.user.email}</p>
<button class="p-2 rounded text-white" style="background:var(--color-verderame)" onclick={logout}>Esci</button>
```

- [ ] **Step 5: Verifica manuale end-to-end** (con `.env` reale e `pnpm dev`)

1. `/register` → crea account → in console appare il link di verifica → aprilo.
2. `/login` → entra → redirect a `/dashboard` che mostra nome+email.
3. Logout → torni alla home. Verifica su Neon: `SELECT email, email_verified FROM "user";`.

- [ ] **Step 6: Commit**

```bash
git add -A && git commit -m "feat(fase1): pagine registrazione/login + dashboard protetta

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 6 — Email reali con Resend (fallback console in dev)

**Files:**
- Create: `src/lib/server/email.ts`
- Modify: `src/lib/server/auth.ts` (usare il sender al posto di console.log)

- [ ] **Step 1: Installare**

```bash
pnpm add resend
```

- [ ] **Step 2: Sender** `src/lib/server/email.ts`

```ts
import { Resend } from 'resend';
import { env } from '$env/dynamic/private';

const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null;

export async function sendEmail(to: string, subject: string, text: string) {
  if (!resend) { console.log(`[email:dev] to=${to} subject=${subject}\n${text}`); return; }
  await resend.emails.send({ from: env.EMAIL_FROM ?? 'onboarding@resend.dev', to, subject, text });
}
```

- [ ] **Step 3: Collegare in `auth.ts`** — sostituire i due `console.log`:

```ts
import { sendEmail } from './email';
// ...
sendResetPassword: async ({ user, url }) => {
  await sendEmail(user.email, 'Reimposta la password', `Apri il link per reimpostare la password: ${url}`);
},
// ...
sendVerificationEmail: async ({ user, url }) => {
  await sendEmail(user.email, 'Verifica la tua email', `Conferma il tuo account: ${url}`);
}
```

- [ ] **Step 4: Verifica** — con `RESEND_API_KEY` impostata, registrazione invia email reale; senza, log in console.

Run: `pnpm build` → OK.

- [ ] **Step 5: Commit**

```bash
git add -A && git commit -m "feat(fase1): invio email con Resend e fallback console

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 7 — Vitest + primo test TDD (utility nickname) + smoke DB

**Files:**
- Create: `vitest.config.ts`, `src/lib/validation/nickname.ts`, `src/lib/validation/nickname.test.ts`
- Modify: `package.json` (script `test`)

- [ ] **Step 1: Installare**

```bash
pnpm add -D vitest
```

- [ ] **Step 2: `vitest.config.ts`**

```ts
import { defineConfig } from 'vitest/config';
export default defineConfig({ test: { environment: 'node', include: ['src/**/*.{test,spec}.ts'] } });
```

Aggiungere a `package.json` scripts: `"test": "vitest run"`.

- [ ] **Step 3 (TDD): Scrivere il test che fallisce** `src/lib/validation/nickname.test.ts`

```ts
import { describe, it, expect } from 'vitest';
import { normalizeNickname, isValidNickname } from './nickname';

describe('nickname', () => {
  it('normalizza trim + collassa spazi, conserva il case', () => {
    expect(normalizeNickname('  Rocko  Coccodrillo ')).toBe('Rocko Coccodrillo');
  });
  it('rifiuta vuoto o troppo corto', () => {
    expect(isValidNickname('')).toBe(false);
    expect(isValidNickname('a')).toBe(false);
  });
  it('accetta un nick valido', () => {
    expect(isValidNickname('Sellux')).toBe(true);
  });
});
```

- [ ] **Step 4: Eseguire e vedere il fallimento**

Run: `pnpm test`
Expected: FAIL (`normalizeNickname is not a function`).

- [ ] **Step 5: Implementazione minima** `src/lib/validation/nickname.ts`

```ts
export function normalizeNickname(input: string): string {
  return input.trim().replace(/\s+/g, ' ');
}
export function isValidNickname(input: string): boolean {
  const n = normalizeNickname(input);
  return n.length >= 2 && n.length <= 40;
}
```

- [ ] **Step 6: Eseguire e vedere il verde** (incluso lo smoke test DB del Task 3)

Run: `pnpm test`
Expected: PASS (test nickname + `db.smoke.test.ts` se `DATABASE_URL` è presente).

- [ ] **Step 7: Commit**

```bash
git add -A && git commit -m "test(fase1): vitest + util validazione nickname (TDD)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 8 — Deploy su Vercel + variabili + connessione Git

**Prerequisito:** `! vercel login` (Paolo). **Files:** nessuno (config su Vercel).

- [ ] **Step 1: Link progetto**

```bash
vercel link
```
(scegliere lo scope di Paolo, creare/collegare il progetto `tta-goblin`)

- [ ] **Step 2: Caricare le variabili d'ambiente su Vercel** (Production + Preview)

```bash
vercel env add DATABASE_URL
vercel env add BETTER_AUTH_SECRET
vercel env add BETTER_AUTH_URL          # in prod: https://<dominio o url vercel>
vercel env add RESEND_API_KEY
vercel env add EMAIL_FROM
```

- [ ] **Step 3: Collegare la repo GitHub** (deploy automatico ad ogni push)

```bash
vercel git connect
```

- [ ] **Step 4: Primo deploy di preview**

```bash
vercel
```
Expected: URL di preview raggiungibile; la home carica; `/register` e `/login` funzionano contro Neon. (In prod aggiornare `BETTER_AUTH_URL` all'URL reale e rifare deploy.)

- [ ] **Step 5: Commit** (eventuale `.vercel/` è già in `.gitignore`)

```bash
git add -A && git commit -m "chore(fase1): collegamento Vercel + env (preview online)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>" || echo "niente da committare"
```

---

## Task 9 — README sviluppo + chiusura fase

**Files:** Modify: `README.md`

- [ ] **Step 1: Aggiungere sezione "Sviluppo"** al `README.md`

```markdown
## Sviluppo
- Stack: SvelteKit · Drizzle/Neon · Better Auth · Tailwind v4 · Vitest · deploy Vercel.
- Setup: copia `.env.example` in `.env` e compila (DATABASE_URL Neon, BETTER_AUTH_SECRET, RESEND_API_KEY).
- Comandi: `pnpm install` · `pnpm dev` · `pnpm test` · `pnpm build` · `pnpm drizzle-kit push`.
```

- [ ] **Step 2: Creare `.env.example`** (senza valori reali) con le stesse chiavi del Task 0.

- [ ] **Step 3: Commit**

```bash
git add -A && git commit -m "docs(fase1): istruzioni di sviluppo + .env.example

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
git push
```

---

## Definition of Done (Fase 1)
- [ ] `pnpm dev` parte; home col tema pergamena.
- [ ] Registrazione → email di verifica (console o Resend) → login → `/dashboard` mostra l'utente; logout.
- [ ] `pnpm test` verde (nickname + smoke DB).
- [ ] `pnpm build` OK con `adapter-vercel`.
- [ ] Preview Vercel online e repo GitHub collegata (deploy on push).
- [ ] `README` con istruzioni; `.env.example` presente; segreti mai committati.

## Note di self-review
- Coverage spec: §3 stack ✓, §4-5 auth/ruoli (ruolo base via Better Auth; i ruoli admin/super-admin/developer si aggiungono in fase successiva sul campo `user.role`) ✓ parziale (atteso), §14 tema ✓, §3 deploy ✓. Le tabelle del motore eventi (§6-7) e la migrazione (§12) sono di fasi successive — corretto.
- Fuori scope Fase 1 (come da spec §15): engine eventi, gironi/partite/classifiche, vetrina dati live, migrazione storico, notifiche schedulate, PWA.
