diff --git a/.gitignore b/.gitignore index 22f51ff..044eda4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ coverage/ .worktrees/ test-results/ playwright-report/ +.playwright-mcp/ tsconfig.tsbuildinfo +.vercel/ diff --git a/AGENTS.md b/AGENTS.md index aae89f2..5b90d2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,80 +1,146 @@ -# AGENTS.md - EasyPIVA Coding Guidelines +# AGENTS.md - EasyPIVA -## Build, Lint, Test +Guida operativa per agenti AI e maintainer che lavorano su questo repository. Le regole qui descritte prevalgono sulle abitudini generiche: EasyPIVA è uno strumento fiscale usato da persone reali e un errore su una soglia o su un'aliquota ha conseguenze concrete. -```bash -npm run dev # Start Vite dev server on port 3000 -npm run build # Production build to dist/ -npm run preview # Preview production build -npm run typecheck # TypeScript check -npm run lint # ESLint check -npm run test # Vitest unit/integration tests -npm run test:e2e # Playwright end-to-end tests on port 4173 -npm run format # Prettier write -npm run format:check # Prettier check -npm run ci # Full local/CI verification pipeline -``` +## Panoramica e finalità + +EasyPIVA è una single-page application client-side che fornisce **simulazioni fiscali indicative** per la Partita IVA italiana (regime forfettario, contributi INPS, confronto con il regime ordinario, calcolo inverso del fatturato, pianificazione ricavi) e un generatore di preventivi con export PDF. -Install dependencies with `npm ci`. npm is the canonical package manager for this repository. +- Repository **pubblico**, licenza MIT, workflow `maintainers-only` (vedi `CONTRIBUTING.md`). +- Nessun backend applicativo, nessun account utente, nessuna telemetria: tutto viene eseguito nel browser. +- Distribuzione: build statica Vite pubblicata su Vercel (). +- Non è un pacchetto npm (`"private": true`) e non è una VS Code extension: nessun `npm publish`, nessun `.vsix`. -## Tech Stack +## Stack e runtime - React 19, React Router 8, TypeScript 6, Vite 6. -- Tailwind CSS v4 with `@tailwindcss/vite`. -- Base UI / shadcn-style primitives in `components/ui/`. -- Zustand for local client state. -- React Hook Form and Zod for validated forms. -- Recharts, motion, jsPDF, and html2canvas for charts, animation, and PDF export. -- Vitest for unit/UI tests and Playwright for E2E. +- Tailwind CSS v4 tramite `@tailwindcss/vite` (nessuna pipeline PostCSS: non aggiungere `postcss.config.*` né `autoprefixer`). +- Primitivi UI Base UI / shadcn in `components/ui/`; `src/index.css` importa `shadcn/tailwind.css`, quindi `shadcn` è una dipendenza di build reale e non solo una CLI. +- Zustand per lo stato client, React Hook Form + Zod per i form. +- Recharts, motion, jsPDF e html2canvas per grafici, animazioni ed export PDF. +- Vitest (jsdom) per unit/UI, Playwright (chromium) per gli E2E. +- **Runtime richiesto:** Node.js 24 LTS (`.nvmrc`, `engines.node >= 24`) e npm 11 (`packageManager`). +- **Package manager obbligatorio:** npm. Non introdurre pnpm, yarn o bun e non aggiungere un secondo lockfile. -## Project Structure +## Struttura del repository ```text src/ - pages/ # Route pages, lazy loaded in App.tsx - components/ # App components + App.tsx # Router e lazy loading delle pagine + pages/ # Route pubbliche + components/ # Layout, disclaimer, componenti del preventivo lib/ - calculations/ # Pure fiscal domain logic - quote/ # Quote builder model, pagination, export - fiscal-data.ts # Fiscal constants and ATECO categories - number-input.ts # Numeric input normalization helpers - public-copy.ts # Public warning/disclaimer copy - store/ # Zustand stores - test/ # Vitest setup and storage mocks -components/ui/ # Shared UI primitives -tests/e2e/ # Playwright tests -docs/ # Architecture, privacy, fiscal assumptions + calculations/ # Logica fiscale pura (forfettario, inps, comparison, targetNet, planning) + quote/ # Modello preventivo, paginazione, export PDF + fiscal-data.ts # Soglie, aliquote, coefficienti ATECO 2026 + number-input.ts # Normalizzazione input numerici non negativi + public-copy.ts # Copy centralizzata dei warning fiscali + browser-storage.ts # Unico accesso consentito a localStorage + theme.ts # Inizializzazione e persistenza del tema + store/ # Store Zustand (disclaimer, tema) + test/ # Setup Vitest e mock di storage +components/ui/ # Primitivi UI condivisi (alias @/components/) +tests/e2e/ # Playwright +docs/ # architecture.md, privacy-and-storage.md, ADRs/, repository-governance.md +.github/ # CI, dependency review, Dependabot, CODEOWNERS, template +``` + +## Comandi (verificati) + +```bash +npm ci # Install riproducibile (usa sempre questo, non npm install ad hoc) +npm run dev # Dev server su http://127.0.0.1:3000 +npm run dev:e2e # Dev server dedicato agli E2E su http://127.0.0.1:4173 +npm run build # Build di produzione in dist/ +npm run preview # Anteprima della build +npm run clean # Rimuove dist/ +npm run typecheck # tsc --noEmit +npm run lint # eslint . +npm run format # prettier . --write +npm run format:check # prettier . --check +npm run test # vitest run +npm run test:watch # vitest in watch +npm run test:e2e # playwright test (richiede chromium installato) +npm run ci # format:check + typecheck + lint + test + build + test:e2e ``` -## Coding Rules +Se Playwright segnala browser mancanti: `npx playwright install chromium`. +Non esiste uno script di deploy nel repository: il deployment è gestito da Vercel a partire da `main`. + +## Regole fiscali (area critica) + +- **Non modificare aliquote, coefficienti, soglie o formule senza una fonte primaria verificabile** (Agenzia delle Entrate, Circolari INPS, legge di bilancio). Cita la fonte nel commit e nell'ADR. +- Tutte le costanti fiscali vivono in `src/lib/fiscal-data.ts`. Non duplicarle nelle pagine né nei componenti. +- Ogni cambiamento fiscale richiede un aggiornamento **coordinato** di: costante in `fiscal-data.ts`, logica in `src/lib/calculations/`, test corrispondenti, `docs/ADRs/0001-fiscal-assumptions.md`, copy pubblica (`src/lib/public-copy.ts`, `src/pages/Sources.tsx`) e `CHANGELOG.md`. +- La logica in `src/lib/calculations/` deve restare **pura**: nessun accesso a `window`, storage, rete o data corrente. +- I warning verso l'utente passano dal pattern `DomainWarning` + `warningCopy`; non scrivere messaggi fiscali inline nelle pagine. +- Il disclaimer ("stime indicative, non consulenza fiscale") è parte del prodotto: è presente nel modale iniziale, in `/informativa` e nel README. **Non rimuoverlo né attenuarlo.** +- Se emergono incoerenze tra codice, test e documentazione fiscale, **segnalale** invece di correggerle a intuito. + +## Convenzioni di codice + +- La copy UI è in italiano; il codice, i nomi e i commenti tecnici in inglese o italiano tecnico, coerentemente con il file. +- Import: `@/` per `src/`, `@/components/` per i primitivi UI. Usa `import type` per i tipi. +- Usa `cn()` per il merge delle classi Tailwind. +- Normalizza gli input numerici con `parseNonNegativeNumber` invece di `Number(...)` ad hoc. +- Ogni accesso a `localStorage` passa da `src/lib/browser-storage.ts` (gestisce SSR, quota esaurita e storage disabilitato). +- Prettier è la fonte di verità sulla formattazione: non riformattare a mano, esegui `npm run format`. +- Commenti sparsi e utili; niente commenti che ripetono il codice. + +## Testing + +- Aggiungi o aggiorna test Vitest per **ogni** cambiamento di comportamento fiscale. +- Aggiungi test UI quando cambiano validazione dei form o copy visibile all'utente. +- Mantieni Playwright sulla porta dedicata 4173 (`npm run dev:e2e`); non riusare la 3000. +- `npm run ci` deve essere verde prima di considerare il lavoro concluso. + +## File generati e da non modificare a mano + +- `package-lock.json`: aggiornalo solo tramite npm. +- `dist/`, `test-results/`, `playwright-report/`, `tsconfig.tsbuildinfo`, `node_modules/`: generati, ignorati da Git. +- `docs/assets/easypiva-dashboard.png`: asset di prodotto. Non sostituirlo, ridimensionarlo o ricomprimerlo (provenienza in `docs/asset-provenance.md`). +- `components/ui/`: primitivi generati da shadcn. Preferisci l'estensione a monte invece della modifica invasiva. +- Il commento su `DISABLE_HMR` in `vite.config.ts` è intenzionale: non rimuoverlo. + +## Sicurezza e variabili d'ambiente + +- Il progetto **non usa variabili d'ambiente applicative** e non ha `.env.example`: non introdurne senza una necessità reale. Qualsiasi valore inserito in un bundle Vite (`VITE_*`) è pubblico. +- `.env*` è già in `.gitignore`. Non committare mai token, chiavi o dati di clienti. +- Le GitHub Actions sono fissate a commit SHA completi con il tag in commento: mantieni questa convenzione. +- Gli header HTTP in `vercel.json` sono parte della superficie di sicurezza pubblica e sono coperti da `src/test/deployment-security.test.ts`: non indebolirli senza una motivazione verificata. +- Le animazioni devono rispettare `prefers-reduced-motion`; conserva sia `MotionConfig reducedMotion="user"` sia il fallback CSS globale. +- `allowScripts` in `package.json` è l'allowlist npm 11 degli install script: aggiungi voci solo dopo aver revisionato lo script (`npm approve-scripts --allow-scripts-pending` elenca i pendenti). +- Dependabot apre solo aggiornamenti di sicurezza (`open-pull-requests-limit: 0` sui version update): non riattivare i version update senza una decisione esplicita del maintainer. +- Le vulnerabilità si segnalano privatamente secondo `SECURITY.md`, mai via issue pubbliche. + +## Anti-breaking-change e compatibilità + +- Non cambiare le chiavi di `localStorage` (`easypiva-disclaimer-storage`, `easypiva-theme-mode`, `easypiva.quote-draft`) senza una migrazione: gli utenti perderebbero bozze di preventivo reali. +- Non cambiare i path delle route (`/calcolatore`, `/confronto`, `/contributi`, `/quanto-fatturare`, `/pianificazione`, `/preventivo`, `/informativa`): sono link pubblici. +- Preserva l'architettura local-first: niente backend, niente analytics, niente richieste di rete a runtime. +- Non modificare la firma dei risultati esportati da `src/lib/calculations/` senza aggiornare tutti i consumatori e i test. -- Keep fiscal calculations pure and covered by tests. -- Centralize tax thresholds and rates in `src/lib/fiscal-data.ts`. -- When changing fiscal assumptions, update code, `docs/ADRs/0001-fiscal-assumptions.md`, and public copy together. -- Normalize numeric form inputs through shared helpers instead of ad hoc `Number(...)` parsing. -- Use the `DomainWarning` pattern plus `warningCopy` for user-facing fiscal warnings. -- Keep browser storage access behind `src/lib/browser-storage.ts`. -- Preserve the local-first/no-backend architecture unless explicitly changing product scope. +## Versioning, release e pubblicazione -## Testing Rules +- SemVer. La versione è replicata in `package.json`, `package-lock.json`, `README.md` e `CITATION.cff`: **vanno aggiornati insieme**. +- Il branch `main` è protetto: pull request obbligatoria, 1 approvazione, conversazioni risolte, branch aggiornato, storia lineare, check `build` e `dependency-review` obbligatori. Niente push diretti, niente force push, niente riscrittura della storia. +- Flusso di release: branch dedicato → `npm run ci` verde → PR con template compilato → merge squash → tag `vX.Y.Z` → GitHub Release. Il dettaglio è in `docs/repository-governance.md`. +- Non esiste pubblicazione su registry: il pacchetto è `private` e la distribuzione avviene solo tramite il deploy Vercel di `main`. -- Add or update Vitest tests for every fiscal behavior change. -- Add UI tests when form validation or user-visible copy changes. -- Keep Playwright on its dedicated port via `npm run dev:e2e`; do not reuse port 3000 for E2E. -- Run `npm run ci` before considering work complete. +## Criteri di validazione obbligatori -## GitHub Repository Hygiene +Prima di dichiarare completato un lavoro: -- Keep `.github/workflows/ci.yml`, `.github/dependabot.yml`, and `.github/workflows/dependency-review.yml` aligned with `package.json` scripts. -- Keep Dependabot version updates disabled with `open-pull-requests-limit: 0`; security updates remain enabled and require maintainer review. -- Update `docs/repository-governance.md` when repository settings, branch protection recommendations, or supply-chain policy change. -- Use the PR template checklist for maintainer reviews. -- Do not route security reports through public issues; follow `SECURITY.md`. +1. `npm ci` (o `npm install` se hai cambiato dipendenze, così il lockfile resta coerente); +2. `npm run ci` completamente verde; +3. `npm audit` senza vulnerabilità nuove; +4. documentazione, changelog e versione allineati alle modifiche reali; +5. nessun segreto, artefatto o file locale nel diff. -## Style +## Istruzioni per agenti AI -- UI copy is Italian. -- Prefer `@/` imports for `src/` and `@/components/` for UI primitives. -- Use `type` imports for type-only imports. -- Use `cn()` for class merging. -- Keep comments sparse and useful; avoid restating obvious code. +- Trattandosi di repository pubblico, presumi che ogni riga di codice, commit e documento sia leggibile da chiunque. +- Non inventare fonti normative, badge, statistiche o risultati di comandi: riporta l'output reale. +- Non disabilitare test, lint o controlli per farli passare. +- Preferisci diff piccoli e motivati; evita refactoring estetici che non portano beneficio. +- Se un'informazione fiscale non è verificabile con una fonte primaria, segnalala nel riepilogo invece di modificarla. diff --git a/CHANGELOG.md b/CHANGELOG.md index bd98a44..8d7f730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Tutte le modifiche rilevanti a EasyPIVA vengono tracciate in questo file. ## [Unreleased] +Nessuna modifica in attesa di rilascio. + +## [1.1.0] - 2026-08-02 + +- rimosse le devDependencies inutilizzate `@types/express` (nessun uso di Express nel progetto) e `autoprefixer` (il progetto non ha una pipeline PostCSS: Tailwind CSS v4 passa da `@tailwindcss/vite`), per un totale di 14 pacchetti in meno nell'albero di installazione; +- aggiunti i metadata `bugs` e `keywords` in `package.json`, allineati ai topic GitHub del repository; +- aggiunti `repository-code` e `date-released` a `CITATION.cff` e allineata la versione citabile; +- ignorati `.playwright-mcp/` e `.vercel/` per evitare che artefatti locali di tooling finiscano nel branch principale; +- README esteso con link alla web app pubblicata, sezione di deployment su Vercel e struttura del repository; +- `AGENTS.md` riscritto come guida operativa completa per agenti e maintainer (architettura, comandi verificati, file generati, aree fiscali delicate, versioning, release e deployment); +- aggiunto `CITATION.cff` alla checklist di release in `docs/repository-governance.md`; - aggiornato il runtime di sviluppo e CI da Node.js 20 EOL a Node.js 24 LTS, con `.nvmrc`, vincoli `engines`, npm dichiarato e tipi Node allineati; - migrato il routing dichiarativo da `react-router-dom` 7 a `react-router` 8, eliminando l'alert di sicurezza relativo ai percorsi RSC non usati dall'app; - corretto il calcolo inverso del fatturato affinché rispetti il massimale INPS anche per obiettivi elevati; @@ -20,7 +31,10 @@ Tutte le modifiche rilevanti a EasyPIVA vengono tracciate in questo file. - reso deterministico lo smoke test E2E restringendo i link alla navigazione, rimuovendo l'ambiguità con le card della home; - allineate documentazione (ADR assunzioni fiscali) e pagina informativa ai dati 2026; - uniformata la formattazione del codice sorgente con Prettier; -- risolte 12 vulnerabilità `npm audit` locali aggiornando le dipendenze nei range consentiti. +- risolte 12 vulnerabilità `npm audit` locali aggiornando le dipendenze nei range consentiti; +- aggiunti header HTTP di sicurezza al deployment Vercel (CSP, anti-framing, anti-MIME-sniffing, referrer e permissions policy), con test di regressione sulla configurazione; +- migliorata l'accessibilità della navigazione e dell'anteprima preventivo con nome accessibile del toggle tema mobile, `aria-current` sulle route attive e scope espliciti sulle intestazioni della tabella; +- rispettata la preferenza `prefers-reduced-motion` nelle animazioni Motion, nella transizione del tema e nel fallback CSS. ## [1.0.0] - 2026-05-03 diff --git a/CITATION.cff b/CITATION.cff index 9eede45..0dd9096 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -5,5 +5,7 @@ authors: - family-names: Gasperini given-names: Michael url: 'https://github.com/TheStreamCode/easypiva' -version: '1.0.0' +repository-code: 'https://github.com/TheStreamCode/easypiva' +version: '1.1.0' +date-released: '2026-08-02' license: MIT diff --git a/README.md b/README.md index 6dca79f..6fd4f79 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # EasyPIVA 2026 [![CI](https://github.com/TheStreamCode/easypiva/actions/workflows/ci.yml/badge.svg)](https://github.com/TheStreamCode/easypiva/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) -Versione corrente del repository: `1.0.0`. +Versione corrente del repository: `1.1.0`. EasyPIVA è una web app client-side per simulazioni fiscali indicative dedicate alla Partita IVA italiana. Copre regime forfettario, contributi INPS, confronto tra regimi, pianificazione dei ricavi e generazione di preventivi con export PDF. Tutti i calcoli vengono eseguiti localmente nel browser. Il progetto non richiede account e non usa un backend applicativo. +**App online:** [easypiva.vercel.app](https://easypiva.vercel.app) + ![Screenshot della dashboard EasyPIVA con strumenti per simulazioni fiscali Partita IVA 2026](docs/assets/easypiva-dashboard.png) ## Branding e packaging @@ -17,12 +20,15 @@ Tutti i calcoli vengono eseguiti localmente nel browser. Il progetto non richied - Packaging supportato: applicazione web statica buildata con Vite. - Questo repository non è una VS Code extension: non usa `vsce`, non genera `.vsix` e non richiede icone separate per Activity Bar, sidebar o Marketplace. -## Release 1.0.0 +## Release 1.1.0 + +- Allinea i contributi INPS 2026 (minimale, contributi fissi, aliquota aggiuntiva +1% e massimale) alle Circolari INPS 8/2026 e 14/2026. +- Migliora il confronto con il regime ordinario introducendo la detrazione per redditi di lavoro autonomo. +- Aggiorna runtime e toolchain: Node.js 24 LTS, TypeScript 6, ESLint 10, `react-router` 8. +- Elimina la richiesta runtime a Google Fonts self-hostando i font nel bundle, in coerenza con l'architettura local-first. +- Ripulisce le dipendenze di sviluppo non utilizzate e allinea metadata, citazione e documentazione. -- Promuove EasyPIVA a baseline stabile del repository. -- Include correzioni fiscali 2026, validazione coerente degli input numerici e copertura E2E Playwright. -- Consolida governance GitHub, Dependabot, Dependency Review, template issue/PR e documentazione di manutenzione. -- Conferma che il branch `main` è il ramo di riferimento per release, CI e automazioni GitHub. +Il dettaglio completo è nel [changelog](CHANGELOG.md). ## Funzionalità principali @@ -68,6 +74,30 @@ L'app viene servita in sviluppo su `http://127.0.0.1:3000`. - `npm run build` genera la build di produzione in `dist/`. - `npm run ci` esegue il flusso completo usato dalla CI: format check, typecheck, lint, Vitest, build e Playwright. +Per gli end-to-end in locale può servire una sola volta `npx playwright install chromium`. + +## Struttura del repository + +```text +src/ + pages/ # Route pubbliche, lazy loaded da App.tsx + components/ # Layout, disclaimer e componenti del preventivo + lib/ + calculations/ # Logica fiscale pura e testata + quote/ # Modello preventivo, paginazione ed export PDF + fiscal-data.ts # Costanti fiscali 2026 (soglie, aliquote, coefficienti) + store/ # Store Zustand (disclaimer, tema) +components/ui/ # Primitivi UI condivisi +tests/e2e/ # Test end-to-end Playwright +docs/ # Architettura, privacy, ADR fiscale, governance +``` + +## Deployment + +- La build statica prodotta da `npm run build` in `dist/` viene pubblicata su Vercel: . +- `vercel.json` applica la rewrite `/(.*) → /index.html` necessaria al routing client-side e gli header HTTP di sicurezza (CSP, anti-framing, MIME sniffing, referrer e permissions policy). +- Non esiste un backend applicativo né una configurazione di deploy alternativa nel repository. + ## Documentazione - [Architettura](docs/architecture.md) diff --git a/docs/architecture.md b/docs/architecture.md index 92cd774..d5a7b2b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -11,7 +11,8 @@ L'architettura separa in modo netto UI, logica fiscale pura, stato client-side e - Il routing dichiarativo è gestito con `react-router` in `src/App.tsx`. - Le pagine principali sono lazy-loaded per ridurre il carico iniziale. - La build di produzione viene generata in `dist/` tramite `npm run build`. -- `vercel.json` applica la rewrite verso `index.html` per supportare il routing client-side su hosting statico. +- `vercel.json` applica la rewrite verso `index.html` per supportare il routing client-side su hosting statico e una policy di header HTTP verificata dai test (`Content-Security-Policy`, anti-framing, anti-MIME-sniffing, referrer e permissions policy). +- `MotionConfig` e il fallback CSS rispettano `prefers-reduced-motion`; la transizione animata del tema viene saltata quando l'utente richiede movimento ridotto. - La CI GitHub usa Node.js 24 LTS, definito in `.nvmrc`, ed esegue `npm run ci` da `package.json`. ## Mappa delle route diff --git a/docs/repository-governance.md b/docs/repository-governance.md index 986a950..e7920b3 100644 --- a/docs/repository-governance.md +++ b/docs/repository-governance.md @@ -38,7 +38,7 @@ Le impostazioni non versionate vanno verificate periodicamente dalla UI GitHub o ## Processo di release -1. Aggiornare `package.json`, `package-lock.json`, README e changelog con la nuova versione. +1. Aggiornare `package.json`, `package-lock.json`, `CITATION.cff`, README e changelog con la nuova versione. 2. Eseguire `npm run ci` in locale. 3. Pubblicare su `main` solo dopo verifica completa. 4. Attendere il workflow `CI / build` verde su GitHub. diff --git a/package-lock.json b/package-lock.json index 67c8dff..2b9741a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "easypiva", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "easypiva", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@base-ui/react": "^1.6.0", @@ -38,11 +38,9 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@types/express": "^4.17.21", "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "autoprefixer": "^10.4.21", "eslint": "^10.6.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.5", @@ -2809,17 +2807,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2831,16 +2818,6 @@ "assertion-error": "^2.0.1" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -2924,39 +2901,6 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", - "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2964,13 +2908,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -2987,13 +2924,6 @@ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", "license": "MIT" }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/raf": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", @@ -3001,13 +2931,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", @@ -3028,39 +2951,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3905,43 +3795,6 @@ "node": ">=10.12.0" } }, - "node_modules/autoprefixer": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", - "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.2", - "caniuse-lite": "^1.0.30001787", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -5819,20 +5672,6 @@ "node": ">= 0.6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, "node_modules/framer-motion": { "version": "12.42.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", @@ -7920,13 +7759,6 @@ "node": ">=4" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", diff --git a/package.json b/package.json index f9af698..a92421f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "easypiva", "private": true, - "version": "1.0.0", + "version": "1.1.0", "type": "module", "packageManager": "npm@11.16.0", "engines": { @@ -17,6 +17,20 @@ "type": "git", "url": "https://github.com/TheStreamCode/easypiva.git" }, + "bugs": { + "url": "https://github.com/TheStreamCode/easypiva/issues" + }, + "keywords": [ + "partita-iva", + "regime-forfettario", + "tax-calculator", + "inps", + "irpef", + "italy", + "react", + "vite", + "typescript" + ], "license": "MIT", "scripts": { "dev": "vite --port=3000 --host=0.0.0.0", @@ -63,11 +77,9 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@types/express": "^4.17.21", "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "autoprefixer": "^10.4.21", "eslint": "^10.6.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.5", diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 3258310..e6392f7 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -51,8 +51,10 @@ export default function Layout() { const isDark = mode === 'dark'; const toggleTheme = (event: MouseEvent) => { - // Fallback for browsers that don't support View Transitions - if (!document.startViewTransition) { + const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + + // Fallback for browsers that don't support View Transitions and for users who reduce motion. + if (!document.startViewTransition || prefersReducedMotion) { toggleThemeMode(); return; } @@ -116,6 +118,7 @@ export default function Layout() { setSidebarOpen(false)} className={`flex min-h-11 items-center gap-3 px-4 py-3 text-sm rounded-md transition-colors ${ isActive @@ -146,6 +149,7 @@ export default function Layout() { - diff --git a/src/components/__tests__/Layout.test.tsx b/src/components/__tests__/Layout.test.tsx index 9d3952b..e1dcfad 100644 --- a/src/components/__tests__/Layout.test.tsx +++ b/src/components/__tests__/Layout.test.tsx @@ -5,6 +5,25 @@ import { describe, expect, test } from 'vitest'; import Layout from '../Layout'; describe('Layout', () => { + test('identifies the current page and labels the mobile theme control', () => { + render( + + + }> + } /> + + + , + ); + + expect( + screen + .getAllByRole('link', { name: 'Dashboard' }) + .every((link) => link.getAttribute('aria-current') === 'page'), + ).toBe(true); + expect(screen.getByRole('button', { name: 'Attiva modalità scura' })).toBeInTheDocument(); + }); + test('links the footer GitHub icon to the repository', () => { render( diff --git a/src/components/quote/QuoteItemsTable.tsx b/src/components/quote/QuoteItemsTable.tsx index 0245744..ebb33ee 100644 --- a/src/components/quote/QuoteItemsTable.tsx +++ b/src/components/quote/QuoteItemsTable.tsx @@ -41,11 +41,21 @@ export function QuoteItemsTable({ {showHeader ? ( - Descrizione - Qta - Unita - Prezzo - Totale + + Descrizione + + + Qta + + + Unita + + + Prezzo + + + Totale + ) : null} diff --git a/src/index.css b/src/index.css index 4e53858..9ede0df 100644 --- a/src/index.css +++ b/src/index.css @@ -126,6 +126,20 @@ } } +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto !important; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + @layer base { * { @apply border-border outline-ring/50; diff --git a/src/main.tsx b/src/main.tsx index dcf08c3..d1e808e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { MotionConfig } from 'motion/react'; import App from './App.tsx'; import './index.css'; createRoot(document.getElementById('root')!).render( - + + + , ); diff --git a/src/test/deployment-security.test.ts b/src/test/deployment-security.test.ts new file mode 100644 index 0000000..1f91c31 --- /dev/null +++ b/src/test/deployment-security.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; + +import configJson from '../../vercel.json'; + +type VercelHeader = { + key: string; + value: string; +}; + +type VercelConfig = { + headers?: Array<{ + source: string; + headers: VercelHeader[]; + }>; + rewrites?: Array<{ + source: string; + destination: string; + }>; +}; + +const config = configJson as VercelConfig; + +describe('Vercel deployment policy', () => { + it('keeps the SPA fallback rewrite', () => { + expect(config.rewrites).toContainEqual({ source: '/(.*)', destination: '/index.html' }); + }); + + it('applies the required security headers to every route', () => { + const routePolicy = config.headers?.find(({ source }) => source === '/(.*)'); + const headers = new Map(routePolicy?.headers.map(({ key, value }) => [key, value])); + + expect(headers.get('Content-Security-Policy')).toContain("default-src 'self'"); + expect(headers.get('Content-Security-Policy')).toContain("frame-ancestors 'none'"); + expect(headers.get('Permissions-Policy')).toBe('camera=(), microphone=(), geolocation=()'); + expect(headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin'); + expect(headers.get('X-Content-Type-Options')).toBe('nosniff'); + expect(headers.get('X-Frame-Options')).toBe('DENY'); + }); +}); diff --git a/vercel.json b/vercel.json index 1323cda..e36e4fd 100644 --- a/vercel.json +++ b/vercel.json @@ -1,4 +1,31 @@ { + "headers": [ + { + "source": "/(.*)", + "headers": [ + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" + }, + { + "key": "Permissions-Policy", + "value": "camera=(), microphone=(), geolocation=()" + }, + { + "key": "Referrer-Policy", + "value": "strict-origin-when-cross-origin" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-Frame-Options", + "value": "DENY" + } + ] + } + ], "rewrites": [ { "source": "/(.*)",