Piantine staccate dai territori, editor a schermo intero, restyle
Le piantine non dipendono piu' da un territorio esistente: territorio_id e' nullable, la chiave esterna e' nullOnDelete invece di cascadeOnDelete, e `nome` fa da etichetta finche' il collegamento manca. Si disegna guardando le strade e si decide dopo di chi e' il disegno; se il territorio non c'e', lo si crea dall'editor con numero, zona, tipologia e confini — zona e tipologia comprese, create sul posto quando mancano. Il cambio di cascata chiude anche una perdita di dati silenziosa: ogni import XML svuota `territori` in blocco e finora si portava via tutte le piantine. Il disegno passa su /piantine/editor, a schermo intero con telaio proprio: su una mappa larga un terzo di schermo un confine si sbaglia, e finisce stampato. /piantine resta la gestione — area, generazione, collegamenti, storico. Cartolina S-12 reimpaginata: pagina 1 il modulo con la Localita' (che ora e' il nome della zona, non un campo duplicato) e i confini scritti nello spazio centrale, con il corpo del testo che si adatta alla lunghezza; pagina 2 la piantina sola al 98%. Aggiunta l'anteprima, che compone il PDF al volo senza toccare pdf_path ne' lo storico. Immagine: JPEG invece di PNG (0,9 MB contro 5 per territorio), composta alle tessere dello zoom piu' alto e ridotta con Lanczos, nelle proporzioni della pagina cosi' riempie il foglio invece di lasciare fasce bianche. Il tratto del confine e' espresso in pixel dell'immagine finale: si vede il confine e si legge la via che ci passa sotto. L'attribuzione OSM si dimensiona con l'immagine, come la ODbL richiede. Rimossa la rigenerazione massiva: job, stato in cache e interfaccia. Sistema visivo ripassato su tutta l'applicazione: le scale gray e indigo di Tailwind sono ridefinite nei token, cosi' le 37 viste si riallineano insieme invece di essere riscritte una per una. Grigi caldi, un accento solo con quattro significati, carattere servito dall'applicazione e non da Google Fonts, via il gradiente della testata. Aggiunto il link "salta al contenuto", che mancava. Corretto un guasto d'ambiente che bloccava TUTTI gli upload in sviluppo: dalla 1.26 il fastcgi_params di nginx passa HTTP_HOST come $host, che perde la porta, e ogni URL firmato smetteva di validare — l'endpoint di upload di Livewire rispondeva 401. 294 test verdi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
160
HANDOFF.md
160
HANDOFF.md
@@ -4,7 +4,7 @@ Stato del progetto per chi ci mette mano dopo. Per installazione, deploy e uso
|
||||
quotidiano vedi [README.md](README.md); qui c'è il contesto che il README non
|
||||
può dare: perché le cose sono come sono, e cosa resta aperto.
|
||||
|
||||
Ultimo aggiornamento: **31 luglio 2026**.
|
||||
Ultimo aggiornamento: **28 agosto 2026**.
|
||||
|
||||
---
|
||||
|
||||
@@ -22,7 +22,7 @@ redeploy, login, tutte le pagine, code e scheduler.
|
||||
| Sviluppo locale | ✅ stesso compose, profilo `dev` |
|
||||
| Backup / ripristino / azzeramento | ✅ verificati end-to-end in locale |
|
||||
| Ripristino da browser in linea | ⚠️ **limitato a 100 MB da Cloudflare** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
| Test automatici | ⚠️ **impalcatura pronta, 57 test** — copertura ancora parziale |
|
||||
| Test automatici | ⚠️ **impalcatura pronta, 294 test** — copertura ancora parziale |
|
||||
|
||||
**Punto in cui ci si è fermati (30/07/2026).** Su `develop` è pubblicato tutto:
|
||||
backup completo `.zip`, ripristino, azzeramento, cambio password forzato, barre
|
||||
@@ -39,6 +39,18 @@ architetturali](#le-piantine-sono-una-sotto-app-renderer-esterno-agganci-minimi)
|
||||
Al primo deploy il compose costruisce anche l'immagine del renderer; non
|
||||
servono variabili nuove in Dokploy.
|
||||
|
||||
**Il 28/08 la sotto-app è stata rifatta e l'interfaccia ripassata.** Le piantine
|
||||
si sono staccate dai territori (una piantina vive anche scollegata, il territorio
|
||||
si crea dall'editor), il disegno è passato su una pagina a schermo intero, la
|
||||
cartolina S-12 è stata reimpaginata (confini sulla prima pagina, piantina al 98%
|
||||
sulla seconda) e la rigenerazione massiva è stata rimossa. In più il sistema
|
||||
visivo è stato ripassato su tutta l'applicazione, non solo qui: vedi [Il sistema
|
||||
visivo sta nei token](#il-sistema-visivo-sta-nei-token-non-nelle-viste).
|
||||
|
||||
Nello stesso giro è emerso e corretto un guasto d'ambiente che bloccava **tutti**
|
||||
gli upload in sviluppo: vedi [`HTTP_HOST` a nginx deve conservare la
|
||||
porta](#http_host-a-nginx-deve-conservare-la-porta).
|
||||
|
||||
Il 30/07 sono state fatte due cose.
|
||||
|
||||
**La suite di test, che prima non esisteva**: 79 test eseguibili con un comando,
|
||||
@@ -166,13 +178,13 @@ password `secret`.
|
||||
|
||||
### Le piantine sono una sotto-app: renderer esterno, agganci minimi
|
||||
|
||||
La pagina **Piantine** (`/piantine`, solo amministratore) genera le mappe dei
|
||||
territori: si carica il KML dell'area della città, si disegnano i poligoni su
|
||||
OpenStreetMap (Leaflet), e da ogni poligono si ottiene un'immagine ad alta
|
||||
definizione e — a scelta — il PDF sul modulo ufficiale S-12
|
||||
(`resources/pdf/S-12_I.pdf`, importato con FPDI) che sostituisce quello del
|
||||
Le **Piantine** (solo amministratore) generano le mappe dei territori. Si carica
|
||||
il KML dell'area della città, si disegnano i confini su OpenStreetMap (Leaflet)
|
||||
nell'editor a schermo intero, e da ogni disegno si ottiene un'immagine ad alta
|
||||
definizione e — a scelta — la cartolina sul modulo ufficiale S-12
|
||||
(`resources/pdf/S-12_I.pdf`, importato con FPDI) che sostituisce il PDF del
|
||||
territorio. Il PDF precedente finisce nella tabella `territorio_pdf_versioni`
|
||||
ed è ripristinabile dalla stessa pagina.
|
||||
ed è ripristinabile dalla pagina di gestione.
|
||||
|
||||
Il rendering vero e proprio **non sta nell'applicazione**: lo fa il container
|
||||
`map-renderer` (`docker/renderer/`, Node + sharp), che scarica le tessere OSM
|
||||
@@ -192,9 +204,104 @@ Le geometrie vivono nella tabella separata `territorio_mappe`, l'area KML in
|
||||
`storage/app/piantine/`: rimuovere la sotto-app significa togliere questi
|
||||
pezzi, non disfare il gestionale.
|
||||
|
||||
La rigenerazione massiva (`Jobs/RigeneraPiantine`) usa lo stesso schema di
|
||||
avanzamento di backup e import PDF (`PiantineOperationState`, cache + poll
|
||||
della pagina, un'operazione alla volta).
|
||||
**Il disegno viene prima del numero.** Una piantina non ha bisogno di un
|
||||
territorio: `territorio_mappe.territorio_id` è nullable e il collegamento si fa
|
||||
(e si disfa) quando si vuole, con `nome` come etichetta finché manca. Si taglia
|
||||
l'area guardando le strade e solo dopo si decide di chi è — e se quel territorio
|
||||
non esiste, lo si crea dall'editor col solo numero. La chiave esterna è
|
||||
`nullOnDelete`, non più `cascadeOnDelete`: cancellare un territorio, o svuotare
|
||||
`territori` come fa ogni import XML, non porta più via i disegni.
|
||||
|
||||
**Due pagine, non una.** `/piantine` tiene area, generazione, collegamenti e
|
||||
storico; `/piantine/editor` è l'editor a schermo intero, con un telaio suo
|
||||
(`layouts/editor`) senza barra laterale. Tagliare un confine su una mappa larga
|
||||
un terzo di schermo vuol dire sbagliarlo, e quel disegno finisce stampato.
|
||||
|
||||
La rigenerazione massiva è stata tolta: job, stato in cache e interfaccia. Si
|
||||
rigenera un territorio alla volta dall'elenco.
|
||||
|
||||
**La cartolina e la piantina sono due pagine distinte.** Il riquadro dell'S-12 è
|
||||
largo sette centimetri: una mappa di quartiere lì dentro non si legge. Quindi
|
||||
pagina 1 è il modulo con Località, numero e i **confini scritti** nello spazio
|
||||
centrale (il corpo del testo si restringe da solo finché ci sta), pagina 2 è la
|
||||
piantina sola al 98% della pagina.
|
||||
|
||||
**La "Località" del modulo è il nome della zona.** Era un campo globale, poi un
|
||||
campo della piantina, e in entrambi i casi era un doppione: la zona esiste già,
|
||||
dice la stessa cosa («Palermo Montepellegrino») ed è quella che si aggiorna
|
||||
quando la congregazione riorganizza il lavoro. Due posti dove scrivere lo stesso
|
||||
nome divergono prima o poi, e sulla carta finisce quello sbagliato. L'elenco
|
||||
marca i territori collegati senza zona: senza, l'intestazione della cartolina
|
||||
esce vuota e ce se ne accorge in stampa.
|
||||
|
||||
**L'anteprima non tocca niente.** `piantine.anteprima` compone il PDF al volo e
|
||||
lo serve `inline`: non aggiorna `pdf_path`, non archivia versioni, non lascia
|
||||
file. «Genera PDF» manda il documento in vigore, e scoprire un confine sbagliato
|
||||
dopo significa trovarselo già nello storico.
|
||||
|
||||
**L'immagine è JPEG, sovracampionata e nella forma della pagina.** Tre scelte
|
||||
che vanno lette insieme (`config/piantine.php`, `docker/renderer/server.js`):
|
||||
|
||||
- Il renderer compone alle tessere dello zoom più alto che entri in
|
||||
`max_side × 2` e poi riduce con Lanczos. Le etichette OSM sono disegnate
|
||||
dentro la tessera a dimensione fissa: salire di uno zoom le rende più
|
||||
definite, e ridurre dopo porta quella nitidezza dentro l'immagine finale.
|
||||
- Chiede al renderer le **proporzioni della pagina** (`aspetto`): un territorio
|
||||
quasi quadrato su una cartolina orizzontale, tenendo le sue proporzioni,
|
||||
lascerebbe due fasce bianche larghe un quinto di foglio. Allargando il
|
||||
ritaglio si mostrano più dintorni invece di carta vuota.
|
||||
- JPEG a qualità 86 invece di PNG: 0,9 MB invece di 5 per territorio. Con 274
|
||||
territori è la differenza fra qualche centinaio di MB e diversi GB.
|
||||
|
||||
L'attribuzione OSM si dimensiona sull'immagine finale, non su una misura fissa:
|
||||
22 px su 3400 sarebbero meno di un millimetro stampato, cioè presenti nel file
|
||||
ma invisibili a chi tiene la cartolina — e la ODbL la vuole leggibile. Per lo
|
||||
stesso motivo il tratto del confine è espresso in pixel dell'immagine finale e
|
||||
riportato alla scala di composizione: deve dire dove passa il confine senza
|
||||
cancellare la via che ci corre sotto, che è proprio quella che serve leggere.
|
||||
|
||||
### Il sistema visivo sta nei token, non nelle viste
|
||||
|
||||
`resources/css/app.css` **ridefinisce le scale `gray` e `indigo` di Tailwind**
|
||||
dentro `@theme`. È la cosa che sorprende di più aprendo il file, ed è
|
||||
deliberata: le trentasette viste sono scritte con le utility (`text-gray-600`,
|
||||
`bg-indigo-50`), e cambiare la scala le riallinea tutte insieme. L'alternativa
|
||||
— riscrivere le classi vista per vista — a ogni file dimenticato lascia in giro
|
||||
il grigio di prima accanto a quello nuovo, e nessuno se ne accorge finché non
|
||||
apre proprio quella pagina.
|
||||
|
||||
Da qui tre conseguenze che vanno sapute prima di metterci mano:
|
||||
|
||||
- **I grigi sono caldi.** Quelli di serie di Tailwind tirano al blu e, sommati a
|
||||
un accento indaco, davano all'interfaccia una dominante fredda. Questi elenchi
|
||||
diventano moduli di carta che passano di mano: un fondo caldo li avvicina a
|
||||
quello che sono. Non mescolare grigi freddi qui dentro.
|
||||
- **L'accento è uno.** Indaco = azione, verde = fatto, ambra = in sospeso,
|
||||
rosso = distrugge dati. Se una cosa non rientra in queste quattro, è grigia.
|
||||
Prima le tinte erano cinque e decorative.
|
||||
- **Il carattere è servito dall'applicazione** (`@fontsource-variable/figtree`,
|
||||
dipendenza npm), non da Google Fonts. Un gestionale che tiene nomi e indirizzi
|
||||
di persone non deve chiamare un dominio di terze parti a ogni pagina caricata,
|
||||
e così funziona anche senza rete.
|
||||
|
||||
I componenti `tm-*` (pulsanti, console, card, campi, elenchi, editor) stanno
|
||||
tutti qui e **fuori da `@layer`**, quindi vincono sulle utility: un pulsante non
|
||||
si può ritoccare a metà con le classi. Chi aggiunge una schermata usa questi,
|
||||
non catene di utility nuove.
|
||||
|
||||
### `HTTP_HOST` a nginx deve conservare la porta
|
||||
|
||||
`docker/nginx/default.conf` rimette `fastcgi_param HTTP_HOST $http_host;` dopo
|
||||
l'`include fastcgi_params`. Non e' ridondanza: dalla 1.26 il `fastcgi_params` di
|
||||
sistema usa `$host`, che la porta la butta via. Con `APP_URL` su porta non
|
||||
standard — cioe' lo sviluppo locale su `:8090` — Laravel ricostruisce l'URL
|
||||
della richiesta senza porta mentre `URL::forceRootUrl()` genera i link con, e
|
||||
ogni URL firmato non valida piu': **tutti** gli upload di file rispondono 401,
|
||||
perche' l'endpoint di Livewire aborta sulla firma. In produzione `APP_URL` non
|
||||
ha porta e le due forme coincidono, quindi il guasto si vede solo in locale.
|
||||
Inoltrare l'header grezzo e' sicuro perche' `trustHosts()` in `bootstrap/app.php`
|
||||
rifiuta gia' gli Host che non siano quello di `APP_URL`, `localhost` o
|
||||
`127.0.0.1`.
|
||||
|
||||
### Modifica/cancellazione delle assegnazioni: solo amministratore
|
||||
|
||||
@@ -918,7 +1025,7 @@ blocchi che impedivano di scrivere il primo test sono stati rimossi: stage
|
||||
docker compose --profile test run --build --rm test
|
||||
```
|
||||
|
||||
**Coperto oggi — 233 test, tutti verdi:**
|
||||
**Coperto oggi — 294 test, tutti verdi:**
|
||||
|
||||
| File | Cosa protegge |
|
||||
|------|---------------|
|
||||
@@ -929,6 +1036,10 @@ docker compose --profile test run --build --rm test
|
||||
| `tests/Feature/PaginaBackupImportTest.php` | La pagina servita per intero: permessi, le cinque zone con la loro ancora **e nel loro ordine**, la console unica, l'assenza dei vecchi riquadri di log, che nessun tag `<x-btn` sia rimasto da compilare, che i pulsanti usino il sistema unico, e che la console sia definita prima del pacchetto che avvia Alpine |
|
||||
| `tests/Feature/PagineApplicativeTest.php` | **Ogni** pagina dell'applicazione si apre davvero (200) e non ha markup rimasto da compilare. È la rete che serve quando una modifica tocca tutte le viste in una volta: un errore di markup non rompe niente finché qualcuno non apre quella pagina |
|
||||
| `tests/Unit/StileUnificatoTest.php` | Che i vocabolari di stile paralleli non rientrino: `.btn-action*`, `.btn-primary-*`, l'hover simulato con `onmouseover`, gli sfondi scritti a mano sui pulsanti, le classi `tm-btn` messe in una vista invece che dal componente |
|
||||
| `tests/Feature/PiantineManagerTest.php` | La pagina di gestione: accesso riservato, KML accettato e rifiutato, collegamento e scollegamento di una piantina, rifiuto di un territorio che ne ha gia' una, cancellare il territorio **non** cancella il disegno (la regressione che la cascata causava a ogni import XML), niente PDF senza territorio, e l'anteprima che non tocca `pdf_path` ne' lo storico |
|
||||
| `tests/Feature/PiantineEditorTest.php` | L'editor: piantina salvata senza territorio, territorio creato dalla scheda con zona/tipologia/confini, aggiornamento di uno esistente, zona e tipologia create sul posto e lasciate selezionate, numero e zona duplicati respinti, ridisegno che sostituisce senza duplicare, geometria non valida ignorata |
|
||||
| `tests/Unit/PiantinaPdfServiceTest.php` | La cartolina: due pagine, la Localita' presa dalla zona e i confini davvero presenti nel PDF (estratti decomprimendo i flussi, non solo "non ha sollevato eccezioni"), il giro completo dello storico — sostituzione, ripristino reversibile, file sparito |
|
||||
| `tests/Unit/PiantinaRendererTest.php` | Il client del renderer: formato, qualita' e lato massimo davvero spediti, JPEG salvato con l'estensione giusta, il file di un formato precedente rimosso invece che lasciato orfano, errori e risposte non-immagine trasformati in messaggi leggibili, geometria corrotta bloccata prima della chiamata |
|
||||
| `tests/Feature/HomeMediaDurataTest.php` | La "media percorrenza congregazione": unità di misura, esclusione delle assegnazioni aperte, dei rientri nello stesso giorno, delle date impossibili ereditate dalla vecchia applicazione e degli altri anni di servizio |
|
||||
|
||||
Il primo giro ha già trovato tre difetti reali, tutti corretti: il warning di
|
||||
@@ -1007,9 +1118,12 @@ questi elementi si può mettere a fuoco — ma sono lo stesso genere di debito:
|
||||
stesso verde compare in quattro tonalità leggermente diverse, e cambiare la
|
||||
tinta dell'applicazione oggi significa cercare a mano tutte le occorrenze.
|
||||
|
||||
La via è la stessa già percorsa: dei token in `app.css` (o le variabili di tema
|
||||
di Tailwind 4) e le viste che li citano invece di ripetere i valori. Il test
|
||||
`StileUnificatoTest` è il posto dove aggiungere la guardia, quando si farà.
|
||||
**Dal 28/08 metà del lavoro è fatta**: i token esistono (`:root` e `@theme` in
|
||||
`app.css`, vedi *Il sistema visivo sta nei token*), e le scale `gray`/`indigo`
|
||||
di Tailwind sono ridefinite, quindi tutto ciò che usa le utility è già
|
||||
allineato. Resta da togliere gli `style=` con gli esadecimali scritti a mano e
|
||||
farli citare i token. Il test `StileUnificatoTest` è il posto dove aggiungere la
|
||||
guardia, quando si farà.
|
||||
|
||||
Fuori perimetro per scelta: `resources/views/pdf/*` le impagina dompdf, che di
|
||||
Tailwind e delle nostre classi non sa niente. `StileUnificatoTest` esclude anche
|
||||
@@ -1018,6 +1132,22 @@ pubblicate da Laravel sono state rimosse perche' i paginatori sono tutti dentro
|
||||
componenti Livewire, che risolvono la vista dal namespace `livewire::` e non da
|
||||
`pagination::`. L'esclusione resta a guardia di future viste di terze parti.
|
||||
|
||||
### 7. Il backup non contiene l'area KML delle piantine
|
||||
|
||||
`BackupArchive` impacchetta il solo disco `public` (`storage/app/public`), e
|
||||
`PiantineArea` scrive in `storage/app/piantine/` — una cartella **sorella**, non
|
||||
dentro. Verificato: dopo un ripristino su volumi vuoti l'area va ricaricata a
|
||||
mano dal KML originale, che quindi conviene conservare fuori di qui.
|
||||
|
||||
I disegni invece si salvano: stanno in `territorio_mappe`, e da quando la chiave
|
||||
esterna è `nullOnDelete` sopravvivono anche allo svuotamento di `territori` che
|
||||
fa ogni import XML — scollegati, ma recuperabili.
|
||||
|
||||
Sistemarlo vuol dire estendere `BackupArchive` a una seconda cartella, cosa che
|
||||
tocca anche il formato dell'archivio (`FORMAT_VERSION`) e il ripristino. Non è
|
||||
stato fatto perché il KML è un file che l'utente ha già sul suo computer: la
|
||||
perdita è un fastidio, non un dato irrecuperabile.
|
||||
|
||||
---
|
||||
|
||||
## Trappole da conoscere
|
||||
|
||||
Reference in New Issue
Block a user