diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md
index 61b7d79..741c9ad 100644
--- a/PROJECT_STATE.md
+++ b/PROJECT_STATE.md
@@ -46,7 +46,7 @@ cancellazioni precedenti a M14 sono state bonificate una tantum (M15/M16).
`npx supabase migration list`)
- Migration `m17_notifiche_utente` (15/09/2026, DD-030): nuova tabella `notifiche_utente`
per il centro notifiche in-app — **in produzione** dal 15/09/2026 (è additiva: non tocca
- schema o comportamento esistenti, vedi DD-030). Il codice che la usa (icona notifiche,
+ schema o comportamento esistenti, vedi DD-030). Il codice che la usa (centro notifiche,
route aggiornate) non è ancora deployato: la tabella si riempie già in background
- Migration `m18_nascita_pubblica_giocatori_squadra` (15/09/2026, DD-031): nuova colonna
`giocatori_squadra.nascita`, sincronizzata da `profili_giocatore.data_nascita` — corregge
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 930f283..6a50f69 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -12,6 +12,12 @@ all'indietro.
## [Non rilasciato]
+### Modificato
+
+- **Notifiche in-app** — tolta la campanella in alto a destra: il numero delle notifiche è ora
+ un pallino rosso sull'avatar del profilo. Toccando il pallino si apre l'elenco (leggi ed
+ elimina), toccando l'avatar si va al profilo come prima.
+
## [0.9.2] - 2026-09-10
### Aggiunto
diff --git a/docs/DATABASE.md b/docs/DATABASE.md
index dba9d64..1564373 100644
--- a/docs/DATABASE.md
+++ b/docs/DATABASE.md
@@ -75,7 +75,7 @@ La tabella è verificata da `test/integration/permessi.test.ts` contro il databa
| `turni_palloni` | Gestione dei turni palloni. | Solo turni **confermati**. Gli allenamenti non ricevono proposta automatica (vedi [palloni.md](modules/palloni.md)); M10 azzera i turni salvati su allenamenti da oggi in poi. |
| `push_subscriptions` | Dispositivi registrati per le notifiche Push. | |
| `promemoria_push` | Non più usata. | Serviva da coda del testo quando la push partiva vuota; dal payload cifrato non la scrive né la legge nessuno. Tabella ancora presente, da eliminare con una migrazione. |
-| `notifiche_utente` | Centro notifiche in-app, con stato letto/non letto, accanto al profilo. | Introdotta dalla migration `m17_notifiche_utente`. Quattro sorgenti, tutte in parallelo alla push esistente: messaggio libero dell'admin (`notifica-personalizzata.ts`), un promemoria automatico prima di un evento — 24h e 3h prima, due `cron.schedule` (estensione `pg_cron`, abilitata il 31/07/2026 ma usata per la prima volta qui) — il turno palloni (`promemoria-palloni.ts`) e il sollecito presenze (`sollecita-presenze.ts`), questi ultimi due avviati a mano da un admin. Volutamente non c'è una notifica sulla sola creazione di un evento: conta l'avvicinarsi della data, non il momento in cui è stato messo in calendario. I destinatari di un evento seguono la stessa convenzione di `convocatiEvento()`/`evento_permette_voto()` (M13): `convocati` vuoto = tutta la rosa attiva. Letta/segnata come letta dal client con `useNotificheMie()`/`useSegnaLette()` (`src/lib/notifiche-utente.ts`), RLS-only, nessuna route API dedicata per queste due operazioni. |
+| `notifiche_utente` | Centro notifiche in-app, con stato letto/non letto, sull'avatar del profilo. | Introdotta dalla migration `m17_notifiche_utente`. Quattro sorgenti, tutte in parallelo alla push esistente: messaggio libero dell'admin (`notifica-personalizzata.ts`), un promemoria automatico prima di un evento — 24h e 3h prima, due `cron.schedule` (estensione `pg_cron`, abilitata il 31/07/2026 ma usata per la prima volta qui) — il turno palloni (`promemoria-palloni.ts`) e il sollecito presenze (`sollecita-presenze.ts`), questi ultimi due avviati a mano da un admin. Volutamente non c'è una notifica sulla sola creazione di un evento: conta l'avvicinarsi della data, non il momento in cui è stato messo in calendario. I destinatari di un evento seguono la stessa convenzione di `convocatiEvento()`/`evento_permette_voto()` (M13): `convocati` vuoto = tutta la rosa attiva. Letta/segnata come letta dal client con `useNotificheMie()`/`useSegnaLette()` (`src/lib/notifiche-utente.ts`), RLS-only, nessuna route API dedicata per queste due operazioni. |
## Funzioni speciali
diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md
index 91dd055..912cad8 100644
--- a/docs/DESIGN_DECISIONS.md
+++ b/docs/DESIGN_DECISIONS.md
@@ -1184,8 +1184,10 @@ RLS (`useNotificheMie()`/`useSegnaLette()`/`useEliminaNotifica()` in
`useSalvaEvento()` in `eventi.ts`. Nessuna policy INSERT per `authenticated`: le righe nascono
solo da funzioni `SECURITY DEFINER` o dalla service role, mai da un client.
-Icona a campana con badge (`IconaNotifiche`, `src/components/crapp/ui-bits.tsx`), pannello a
-comparsa costruito a mano (nel progetto non c'è una libreria dropdown). Ogni riga si elimina
+Pallino con il conteggio sull'avatar del profilo (`PallinoNotifiche`,
+`src/components/crapp/ui-bits.tsx`; in origine una campanella separata, tolta per liberare
+l'header: il tap sull'avatar resta l'accesso al profilo, quello sul pallino apre il
+pannello), pannello a comparsa costruito a mano (nel progetto non c'è una libreria dropdown). Ogni riga si elimina
con uno swipe o una ×: non c'è pulizia automatica delle notifiche vecchie, quindi è l'unico
modo per un giocatore di toglierle di torno per sempre.
@@ -1211,7 +1213,7 @@ modo per un giocatore di toglierle di torno per sempre.
**Conseguenze**
-- Il badge sull'icona riflette solo ciò che sta in `notifiche_utente`: un dispositivo senza
+- Il pallino sull'avatar riflette solo ciò che sta in `notifiche_utente`: un dispositivo senza
push attive vede comunque tutto, perché il canale è del tutto indipendente.
- `eventi_app.ora` è testo libero e non validato dal form (`` si può
svuotare): la funzione `genera_promemoria_eventi()` usa `ora_evento_a_time()`, che assorbe
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 0c87f57..bb70edf 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -21,8 +21,8 @@ Tutto quello che è in `main`, rilasciato in versione 0.9.0 (vedi `CHANGELOG.md`
- [x] Turno palloni
- [x] Infortuni — conteggio eventi saltati, in forma minima
- [x] Notifiche Push (promemoria intelligenti)
-- [x] Centro notifiche in-app — icona a campana accanto al profilo, con badge delle non
- lette, indipendente dalla push (messaggi admin, promemoria evento, turno palloni,
+- [x] Centro notifiche in-app — pallino con il numero delle non lette sull'avatar del
+ profilo (il tap sul pallino apre l'elenco), indipendente dalla push (messaggi admin, promemoria evento, turno palloni,
sollecito presenze)
- [x] Dashboard amministratore
- [x] Download CSV dati
diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md
index b9329bb..d404825 100644
--- a/docs/modules/notifiche.md
+++ b/docs/modules/notifiche.md
@@ -1,11 +1,11 @@
# Modulo — Notifiche
**Stato:** implementato — un unico opt-in dispositivo abilita tutto il canale push, più un
-centro notifiche in-app indipendente accanto al profilo
+centro notifiche in-app indipendente, con un pallino sull'avatar del profilo
**File principali:** `src/lib/notifiche-smart.ts`, `src/lib/notifiche-utente.ts`,
`src/lib/push-client.ts`, `src/lib/webpush.server.ts`, `src/routes/api/public/push-config.ts`,
`src/routes/api/public/push-subscribe.ts`, `public/push-sw.js`,
-`src/components/crapp/ui-bits.tsx` (`IconaNotifiche`)
+`src/components/crapp/ui-bits.tsx` (`LinkProfilo`, `PallinoNotifiche`)
---
@@ -83,10 +83,10 @@ route. Tutte e tre partono da un gesto di un amministratore dentro l'app, quindi
è uno solo (`richiediAdmin` in `src/lib/auth-route.server.ts`) e non serve configurare nessuna
variabile d'ambiente.
-| Route | Controllo | Chi la chiama |
-| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------- |
+| Route | Controllo | Chi la chiama |
+| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `apri-sondaggio`, `sollecita-presenze`, `promemoria-palloni`, `notifiche-attive`, `notifica-personalizzata` | `richiediAdmin` — token della sessione Supabase, poi ruolo `admin` in `user_roles` | l'app, da un pulsante o una vista riservati agli admin |
-| `csi`, `push-config`, `push-subscribe` | nessuno | il browser prima del login, che una sessione non ce l'ha ancora |
+| `csi`, `push-config`, `push-subscribe` | nessuno | il browser prima del login, che una sessione non ce l'ha ancora |
`notifiche-attive` è a sola lettura: non manda push, restituisce gli id giocatore con almeno
un dispositivo iscritto in `push_subscriptions` (deduplicati). Alimenta la tab "Notifiche"
@@ -116,8 +116,12 @@ ripetersi — deduplica puramente locale al dispositivo, non sincronizzata.
## Centro notifiche in-app (M17)
-Icona a campana accanto al link profilo (`IconaNotifiche` in `src/components/crapp/ui-bits.tsx`,
-dentro `PageHeader`), con badge del numero di non lette. Indipendente dal canale push sopra:
+Pallino sull'angolo dell'avatar del profilo in alto a destra (`PallinoNotifiche` dentro
+`LinkProfilo`, `src/components/crapp/ui-bits.tsx`): rosso con il numero delle non lette; se
+sono tutte lette resta neutro con il totale, così le lette restano raggiungibili per
+eliminarle; senza notifiche non compare. Il tap sull'avatar porta sempre a `/profilo`, solo il
+tap sul pallino apre il pannello (logica in `pallinoNotifiche()`). Fino a 0.9.2 era una
+campanella separata accanto all'avatar. Indipendente dal canale push sopra:
non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico persistente in
`notifiche_utente` (vedi [DATABASE.md](../DATABASE.md)) con stato letto/non letto, e non è la
stessa cosa delle notifiche smart (quelle restano locali, non salvate a database).
diff --git a/src/components/crapp/ui-bits.tsx b/src/components/crapp/ui-bits.tsx
index f0b371d..817af68 100644
--- a/src/components/crapp/ui-bits.tsx
+++ b/src/components/crapp/ui-bits.tsx
@@ -8,7 +8,7 @@ import {
} from "react";
import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "motion/react";
-import { Bell, ChevronDown, X } from "lucide-react";
+import { ChevronDown, X } from "lucide-react";
import { cn } from "@/lib/utils";
import { inizialiDa, statoMeta, type Stato } from "@/lib/crapp-data";
import { nomeCompleto } from "@/lib/giocatori-squadra";
@@ -16,7 +16,7 @@ import { useGiocatoreBase } from "@/lib/user-store";
import { useMotoRidotto } from "@/lib/motion";
import { molla, proietta } from "@/lib/molla";
import {
- contatoreBadge,
+ pallinoNotifiche,
useEliminaNotifica,
useNotificheMie,
useSegnaLette,
@@ -65,30 +65,6 @@ export function Card({
return
Nessuna notifica
- ) : ( -{sottotitolo}
) : null}