chore: aggiungi CLAUDE.md e rimuovi skill UX locale
Aggiunta documentazione per Claude Code (comandi, architettura, testing, pipeline Android). Rimossa la skill UX da .claude/skills/ non più necessaria. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: ux
|
|
||||||
description: Senior frontend developer e UX designer per app mobile-first. Usa quando si lavora su componenti Vue, layout, CSS, accessibilità o si vuole una revisione UX.
|
|
||||||
argument-hint: "[componente o area da rivedere]"
|
|
||||||
disable-model-invocation: true
|
|
||||||
---
|
|
||||||
|
|
||||||
Agisci come senior frontend developer e UX designer specializzato in app mobile-first (Vue 3, CSS nativo, accessibilità WCAG 2.1).
|
|
||||||
|
|
||||||
## Regole
|
|
||||||
|
|
||||||
- Proponi solo modifiche minime e mirate — niente over-engineering, niente librerie UI esterne salvo richiesta
|
|
||||||
- Per ogni scelta CSS o di layout, motiva la decisione UX in una riga
|
|
||||||
- Se esistono trade-off, mostra l'alternativa scartata e spiega perché
|
|
||||||
- Commenta nel codice solo le decisioni non ovvie
|
|
||||||
|
|
||||||
## Priorità di design
|
|
||||||
|
|
||||||
1. **Chiarezza** — l'utente capisce cosa fare senza istruzioni
|
|
||||||
2. **Feedback immediato** — ogni azione ha risposta visiva entro 100ms
|
|
||||||
3. **Touch-friendly** — target minimo 44×44px, spaziature generose
|
|
||||||
4. **Coerenza** — variabili CSS centralizzate, stessa logica visiva ovunque
|
|
||||||
5. **Accessibilità** — contrasto AA (4.5:1), label semantici, focus visibile
|
|
||||||
|
|
||||||
## Output atteso
|
|
||||||
|
|
||||||
Restituisci sempre il file Vue completo aggiornato (non solo il diff).
|
|
||||||
|
|
||||||
## Risorse di riferimento
|
|
||||||
|
|
||||||
- Per la checklist di revisione UX completa, consulta [checklist.md](checklist.md)
|
|
||||||
- Per i pattern Vue 3 + CSS approvati per questo progetto (variabili, componenti tipo), consulta [patterns.md](patterns.md)
|
|
||||||
|
|
||||||
$ARGUMENTS
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UX Review Checklist
|
|
||||||
|
|
||||||
Usa questa checklist quando rivedi o scrivi un componente Vue mobile-first.
|
|
||||||
|
|
||||||
## Touch & interazione
|
|
||||||
|
|
||||||
- [ ] Ogni elemento interattivo ha `min-height: 44px` e `min-width: 44px`
|
|
||||||
- [ ] Spaziatura tra elementi adiacenti ≥ 8px (evita tap accidentali)
|
|
||||||
- [ ] Stati `:active` e `:focus-visible` definiti esplicitamente
|
|
||||||
- [ ] Nessun hover-only interaction (non funziona su touch)
|
|
||||||
|
|
||||||
## Feedback visivo
|
|
||||||
|
|
||||||
- [ ] Ogni azione utente ha risposta visiva immediata (cambio colore, spinner, messaggio)
|
|
||||||
- [ ] Stato vuoto gestito con messaggio utile, non schermata bianca
|
|
||||||
- [ ] Errori comunicati in linguaggio naturale, non codici tecnici
|
|
||||||
- [ ] Operazioni distruttive (elimina, svuota) richiedono conferma
|
|
||||||
|
|
||||||
## Accessibilità
|
|
||||||
|
|
||||||
- [ ] Rapporto di contrasto ≥ 4.5:1 per testo normale, ≥ 3:1 per testo grande
|
|
||||||
- [ ] `<input>` ha `<label>` associato (non solo placeholder)
|
|
||||||
- [ ] `<button>` ha testo leggibile o `aria-label`
|
|
||||||
- [ ] Ordine di navigazione con Tab è logico e visibile
|
|
||||||
|
|
||||||
## CSS & coerenza
|
|
||||||
|
|
||||||
- [ ] Colori e dimensioni usano variabili CSS (`:root { --color-* }`)
|
|
||||||
- [ ] Nessun valore hardcoded che duplica una variabile esistente
|
|
||||||
- [ ] Font-size base 16px, nessun testo sotto 14px
|
|
||||||
- [ ] `box-sizing: border-box` applicato globalmente
|
|
||||||
|
|
||||||
## Vue 3
|
|
||||||
|
|
||||||
- [ ] Props tipizzate con `defineProps`
|
|
||||||
- [ ] Eventi emessi con `defineEmits`
|
|
||||||
- [ ] Nessuna logica diretta nel template (estrai in computed o funzioni)
|
|
||||||
- [ ] `watch` con `{ deep: true }` solo dove necessario (ha costo)
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Pattern di riferimento
|
|
||||||
|
|
||||||
Pattern Vue 3 + CSS nativo approvati per questo progetto.
|
|
||||||
|
|
||||||
## Variabili CSS del progetto
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
--color-bg: #f5f5f5;
|
|
||||||
--color-surface: #ffffff;
|
|
||||||
--color-primary: #2d6a4f;
|
|
||||||
--color-primary-light: #52b788;
|
|
||||||
--color-text: #1a1a1a;
|
|
||||||
--color-muted: #6b7280;
|
|
||||||
--color-border: #e5e7eb;
|
|
||||||
--color-danger: #dc2626;
|
|
||||||
--radius: 8px;
|
|
||||||
--nav-height: 60px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usa sempre queste variabili. Non aggiungere colori hardcoded.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Input con label accessibile
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<label class="field">
|
|
||||||
<span class="field-label">Nome elemento</span>
|
|
||||||
<input v-model="value" type="text" placeholder="es. pollo 200g" />
|
|
||||||
</label>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.field { display: flex; flex-direction: column; gap: 4px; }
|
|
||||||
.field-label { font-size: 0.85rem; color: var(--color-muted); font-weight: 600; }
|
|
||||||
input { border: 1px solid var(--color-border); border-radius: var(--radius);
|
|
||||||
padding: 10px 12px; min-height: 44px; font-size: 1rem; }
|
|
||||||
input:focus { outline: 2px solid var(--color-primary); outline-offset: 1px; }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Riga con azione distruttiva
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<div class="item-row">
|
|
||||||
<span class="item-text">{{ item.name }}</span>
|
|
||||||
<button class="btn-remove" @click="$emit('remove')" aria-label="Rimuovi {{ item.name }}">✕</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.item-row { display: flex; align-items: center; justify-content: space-between;
|
|
||||||
background: var(--color-surface); border-radius: var(--radius);
|
|
||||||
padding: 12px 14px; border: 1px solid var(--color-border); }
|
|
||||||
.btn-remove { background: none; color: var(--color-muted); min-height: 44px;
|
|
||||||
min-width: 44px; font-size: 0.85rem; }
|
|
||||||
.btn-remove:active { color: var(--color-danger); }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bottone primario full-width (mobile)
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<button class="btn-primary" @click="action">Conferma</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.btn-primary { width: 100%; background: var(--color-primary); color: #fff;
|
|
||||||
border-radius: var(--radius); min-height: 48px; font-size: 1rem;
|
|
||||||
font-weight: 600; }
|
|
||||||
.btn-primary:active { background: var(--color-primary-light); }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Stato vuoto
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template v-if="items.length">
|
|
||||||
<!-- lista -->
|
|
||||||
</template>
|
|
||||||
<div v-else class="empty-state">
|
|
||||||
<p>Nessun elemento.</p>
|
|
||||||
<p class="empty-hint">Aggiungi qualcosa con il campo qui sopra.</p>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.empty-state { text-align: center; padding: 40px 16px; color: var(--color-muted); }
|
|
||||||
.empty-hint { font-size: 0.85rem; margin-top: 4px; }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Toggle a due stati
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<div class="toggle-group">
|
|
||||||
<button :class="['toggle-btn', { active: mode === 'a' }]" @click="mode = 'a'">Opzione A</button>
|
|
||||||
<button :class="['toggle-btn', { active: mode === 'b' }]" @click="mode = 'b'">Opzione B</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
```css
|
|
||||||
.toggle-group { display: flex; gap: 8px; }
|
|
||||||
.toggle-btn { flex: 1; background: var(--color-bg); color: var(--color-muted);
|
|
||||||
border: 1px solid var(--color-border); min-height: 44px; font-size: 0.9rem; }
|
|
||||||
.toggle-btn.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
BitePlan is a mobile-first Vue 3 PWA for meal planning, raw↔cooked weight conversion, and shopping list management. It targets Android via Capacitor and is designed for a 480px-max viewport. All UI strings and documentation are in **Italian**.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Dev server at http://localhost:5173
|
||||||
|
npm run build # Production build → dist/
|
||||||
|
npm run preview # Preview built app
|
||||||
|
|
||||||
|
npm test # Vitest unit + integration tests (watch mode)
|
||||||
|
npm run test:coverage # Coverage report (v8 provider)
|
||||||
|
npm run test:e2e # Playwright e2e (requires dev server running)
|
||||||
|
npm run test:e2e:ui # Playwright interactive UI mode
|
||||||
|
|
||||||
|
# Single test file
|
||||||
|
npx vitest run tests/unit/conversion.test.js
|
||||||
|
|
||||||
|
# Android APK
|
||||||
|
bash docker/build.sh # Debug APK
|
||||||
|
bash docker/build.sh --release # Signed release APK
|
||||||
|
```
|
||||||
|
|
||||||
|
E2E tests simulate iPhone 14 Pro viewport (393×852) with Italian locale.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**App.vue** is the root router — it conditionally renders three pages based on a `currentPage` ref (`meal`, `convert`, `shop`) and hosts the portrait-lock transform, `InfoPanel`, and `DocsPanel`.
|
||||||
|
|
||||||
|
**Three pages** in `src/pages/`:
|
||||||
|
- `MealPlanner.vue` — 7-day plan (Mon–Sun), 3 meal slots each (colazione/pranzo/cena), per-day accordion via `MealCard.vue`, QR share, and "generate shopping list" export
|
||||||
|
- `Converter.vue` — real-time food search → select food+method → bidirectional raw↔cooked calculation using yield coefficients from `src/data/conversions.json`
|
||||||
|
- `ShoppingList.vue` — checklist with add/remove/check, importable from meal planner
|
||||||
|
|
||||||
|
**State & persistence**: No Pinia/Vuex — pages use Vue composables + direct `localStorage` via the wrappers in `src/utils/storage.js` (`save(key, val)` / `load(key, default)`).
|
||||||
|
|
||||||
|
**Conversion logic** lives entirely in `src/utils/conversion.js`:
|
||||||
|
```js
|
||||||
|
rawToCooked(food, method, rawGrams, db) = rawGrams * db[food][method].yield
|
||||||
|
cookedToRaw(food, method, cookedGrams, db) = cookedGrams / db[food][method].yield
|
||||||
|
```
|
||||||
|
`yield = cooked_weight / raw_weight`. The database in `src/data/conversions.json` has 50+ foods × 1–4 cooking methods with coefficients sourced from CREA, SINU, USDA (see `docs/conversioni.md`).
|
||||||
|
|
||||||
|
**CSS**: Vanilla CSS only, no framework. Design tokens are CSS variables in `:root` in `src/style.css` (`--color-primary: #2d6a4f`, `--nav-height: 64px`, etc.). Buttons must be min 44px; layout is single-column.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- **Unit** (`tests/unit/`): Pure function tests for `conversion.js` and `storage.js`
|
||||||
|
- **Integration** (`tests/integration/`): Vue Test Utils component mounts with localStorage seeding
|
||||||
|
- **E2E** (`tests/e2e/`): Playwright against the running dev server
|
||||||
|
|
||||||
|
`tests/setup.js` clears localStorage before/after each test. Vitest uses `happy-dom` environment.
|
||||||
|
|
||||||
|
## Android Build
|
||||||
|
|
||||||
|
The Docker pipeline in `docker/` handles the full Android build:
|
||||||
|
1. `npm run build` → `dist/`
|
||||||
|
2. `cap sync` → copies dist into Android project
|
||||||
|
3. Gradle builds the APK; release APK is signed with `docker/biteplan.jks` (gitignored)
|
||||||
|
|
||||||
|
See `docker/README.md` for full APK build instructions.
|
||||||
Reference in New Issue
Block a user