VRP - B300 - Diario di coding
Stato: APERTO 2026-07-05. Segue VRP_B200_architettura.
Registro cronologico degli interventi di CODIFICA: cosa si e' toccato, perche',
quali file, e come e' stato collaudato. Le decisioni di architettura restano nel
B200; qui il "cantiere" giorno per giorno. ASCII puro. Voci nuove in fondo.
2026-07-05 - Cruscotto Amministrazione + Sessioni/Log + timeout
Perche': dalla home ?p=home non c'era modo di raggiungere le pagine
riservate (utenti compariva in barra solo da LAN+admin; config era parcheggiata).
Serviva una porta unica per l'amministrazione, piu' due strumenti d'indagine.
Fatto:
- Cruscotto
platform/pages/admin.php, rotta?p=admin(LAN+admin, unica
voce in barra): atrio con 4 riquadri (Utenti, Configurazione, Sessioni, Log)
che aprono in nuova scheda (target=_blank + rel=noopener), cosi' la console
resta li'. Riusa .vrp-cards/.vrp-card. E' l'"Altezza 1" (admin di
PIATTAFORMA) della sez. 3b del B200, in piedi.
- Front controller
public/index.php: tabella rotte ampliata.adminin
barra; users/config/sessioni/log con nav=false (fuori dalla barra,
raggiunte dal cruscotto). config RI-AGGANCIATA (era sganciata dal 03/07).
- Sessioni
platform/pages/sessioni.php: legge la vistasessions_hdi
vrp_auth (storico del portinaio). Scelta colonne con caselle; whitelist viva
presa da SHOW COLUMNS (mai nomi arbitrari nella SQL). Sola lettura, ORDER BY
started_at DESC, LIMIT parametrico.
- Log
platform/pages/log.php: tail dal fondo (riciclato da
vv/vv_where_logs.php) su VRP_LOG_FILE + candidati di sistema in whitelist
(nginx / php-fpm / error_log). Filtro per livello (ERROR..DEBUG, riconosciuto
da ][LIVELLO][) e righe colorate.
- CSS
public/css/vrp-lab.css: nuove.vrp-cols-*(selettore colonne) e
.vrp-log-* (barra compatta + pannello). Pagine Log e Sessioni a tutta
finestra ~92% con scroll INTERNO del pannello e testata compatta: attivato SOLO
su queste due via body:has(.vrp-log-view) / body:has(.vrp-sess-table),
nessun flag nel FC. Intestazione tabella fissa (position:sticky).
- timeout != logout in
platform/lib/vrp_auth.php: `vrp_logout($action=
'logout') timbra l'azione richiesta; vrp_session_check passa 'timeout'`;
logout.php passa 'timeout' se arriva dal meta-refresh (?t=1). Nelle
Sessioni si legge timeout invece di logout. Scrittura idempotente
(WHERE ended_at IS NULL): se scattano due vie, si timbra una volta sola.
- session TTL rimesso a 900s in
boot/env.json(era 60 da una prova). - doc API rigenerata:
docs/genera_doc.php->VRP_API_lib.md
(6 file di platform/lib/, 31 funzioni documentate).
Collaudo: php -l su tutti i file toccati OK. Da localhost (LAN):
?p=home 200; ?p=admin|config|sessioni|log -> 302 alla login (guardia
LAN+admin che funziona, nessun 500). Query sessions_h provata via PDO (colonne
+ righe reali, incluso un accesso da IP pubblico). Utenti admin: vitti / seneca /
lella (Elena e' editor, non vede il bottone).
Aperto: la UX vera per editare i JSON per-servizio (config e' ancora una
textarea); :has() richiede un browser recente (ok su Manjaro rolling,
altrimenti le due pagine ripiegano sulla resa normale).
2026-07-05 (pomeriggio) - Profilo self-service + auto-prepend + integrazione VRW
Perche': (1) un utente non-admin doveva poter modificare il PROPRIO profilo;
(2) far entrare il primo servizio (wiz_foto) nella piattaforma col metodo di
vitti: "la piattaforma fa tutto, i servizi ereditano l'ambiente". Obiettivo
dichiarato: nel codice delle pagine NON un solo require; se manca una funzione
la si aggiunge alla libreria di piattaforma (niente 'not defined' ne' 'already
defined').
Fatto - profilo e ruolo:
- Profilo
platform/pages/profilo.php, rotta?p=profilo(serve solo essere
loggati, non LAN ne' admin): l'utente cambia SOLO i suoi dati (nome, email,
password vuota=invariata, foto). Username/ruoli restano all'admin. Riusa la
logica di users.php.
- Icona-ruolo nel badge:
vrp_role_top()(admin>editor>viewer>guest) +
vrp_role_icon() in vrp_auth.php; il badge in barra e' ora un link a
?p=profilo con l'icona del ruolo (admin=corona, il "Cesare imperatore").
Ricognizione Nextcloud (mang3): due account vitti e lella; i telefoni
caricano in Photos/AAAA/MM/GG/. Estensioni reali: solo .jpg (+ .png per
lella) e VIDEO (.mp4/.mov) - NIENTE .heic (anche l'iPhone carica jpg). Dato
in /srv/dati/nextcloud/data (bind mount). DECISO: importare anche i video
(Elena decide poi). Il 13T (HyperOS) ritardava l'auto-upload: batteria "nessuna
restrizione" + avvio automatico.
Fatto - integrazione VRW (il metodo, rodato qui per i prossimi servizi):
- Copia
/srv/http/wiz_foto->/srv/http/lab/public/wiz_foto(originale
intatto = backup). Diff confermato identico prima di lavorarci.
- Targa
platform/config/vrw.json(manifest +paths/nextcloud/
extensions/modes), registrata in env.json services -> compare e si
edita da ?p=config. Politica d'accesso nella targa: lan:true, admin:true.
- auto-prepend
public/.user.ini->auto_prepend_file=3_2_1_Go.php: la
piattaforma carica l'ambiente PRIMA di ogni pagina sotto /lab/public. Preso
al volo senza reload di php-fpm. Il loader usa require_once -> nessun
'already defined'.
- Guardia d'AREA
vrp_guard_request()invrp_auth.php, chiamata dal loader:
per i servizi ospitati nel lab applica lan/admin/auth dalla loro targa,
SENZA guardie nel codice del servizio. wiz_foto era esposto sotto public ->
ora 302 se non LAN+admin.
- Librerie dei servizi dal REGISTRO (il "puntatore"):
vrp_service_config()
+ vrp_load_service_libs() in vrp_services.php. Il loader NON nomina i
servizi: gira su env.json services, e ogni servizio che dichiara "lib"
nella targa viene caricato (config passata alla lib in $vrp_service).
Aggiungere un servizio = registrarlo + "lib", zero tocchi al loader.
platform/lib/vrw.php: fold diwiz_foto/_cfg.php+_pecore.php,
rinominato vrw_/VRW_, percorsi dalla targa (VRW_STAGING_DEFAULT ecc.) e
VRW_DEST_BASE_DEFAULT = VRP_MEDIA_ROOT (leNostre dalla piattaforma).
- sed sulle 6 pagine web (wizard_import, upload_to_staging, unzip_amazon,
heic_to_jpg, import_amazon, export_staging_csv): via TUTTI i require
(_cfg/_pecore), vv_->vrw_, VV_->VRW_, e header-commento in testa
("questa pagina e' gestita dalla piattaforma...").
- Front controller: tolto anche il suo
requiredel loader (lo da'
l'auto-prepend) + header-commento.
Collaudo: php -l su tutti i file OK. vrw.json valido. Da CLI il loader
definisce vrw_base_url/vrw_pecore_overlay e VRW_STAGING_DEFAULT/
VRW_DEST_BASE_DEFAULT -> lib caricata DAL REGISTRO. Rotte: home 200, admin 302,
wiz_foto 302 (guardia attiva, prima era esposto). Runtime vero:
wizard_import.php RENDE senza un solo require, ereditando l'ambiente. Nelle 6
pagine: zero require, zero residui vv_/VV_.
Aperto: (1) lenostre_index.php -> platform/lib/vrn_index.php (ultimo
require, in gestore_foto+popola); (2) orfani _cfg.php/_pecore.php nella
copia da cancellare; (3) rename CSS .vv- -> .vrw-; (4) nuovo Step "Scarica da
Nextcloud" (foto+video, un account per volta); (5) ruolo vrw a vitti/lella ->
compare la cartolina in home. Prossimo servizio previsto: VRS (`/srv/http/
share), policy "login + WAN" (targa: auth:required, senza lan`).
2026-07-05 (sera) - Rifiniture admin (leggibilita', tail -f) + fix identita' log
Perche': giro di feedback di vitti sulle paginette admin (leggibilita',
spazio, aggiornamento del log) e un baco scovato leggendo il log.
Fatto:
- Log - riga vuota: il
<pre>emetteva un\nOLTRE al blocco -> doppia
spaziatura. Tolto il newline nel loop.
- Log - toggle "Segui" (tail -f): checkbox in barra; se acceso la pagina si
ri-aggiorna ogni 3s e scende in fondo (follow=1 in URL, piccolo JS a fondo
pagina); spento -> si ferma (per ragionarci sopra).
- Log - riquadro da nero pieno a semi-trasparente (stessa cura di Sessioni).
- Sessioni - leggibilita': tabella con contenitore semi-opaco (rgba) cosi' il
testo bianco risalta sullo sfondo; header sticky piu' opaco.
- Sessioni - "Colonne" collassabile: il selettore colonne e' un
<details>
(declutter); Righe/Applica/Reimposta restano visibili sotto.
- Back-link "<- Amministrazione" su Utenti e Configurazione (coerenza con
Sessioni/Log).
- FIX identita' log:
vrp_log_identity()leggeva$_SESSION['vrp_user'](mai
valorizzato: il login salva vrp_uid) -> ogni riga usciva @ospite#0@guest.
Era un segnaposto pre-vrp_auth. Ora usa vrp_current_user() + vrp_role_top().
Verificato via CLI (sessione simulata): @vitti#1@admin, @lella#8@admin. Le
righe vecchie restano ospite (storia, non si riscrive).
- Acronimo:
vrsnon e' piu' "Sera" (che e' una CATEGORIA di post del VRV,
non un servizio) ma VittRos Share; corretto VRP_acronimi.md.
- doc API rigenerata:
VRP_API_lib.md(7 file, 42 funzioni).
Collaudo: php -l su tutti i file OK; rotte admin 302 (guardia). Identita'
log provata in CLI con $_SESSION['vrp_uid'] impostato (vitti/lella).
Aperto (coda UI): layout wide+fisso+scroll interno per Utenti e Config +
sezioni collassabili; tab riusati (login / amministrazione / un tab per
servizio); bottoni servizio in home (ruolo vrw + placeholder VRShare).
2026-07-05 (sera, 2) - Coda UI: Utenti/Config wide, Sessioni paginate/sortabili, VRW+VRS in home
Perche': chiudere la coda UI (punti 1 e 3) e un giro di rifiniture di vitti
provando le paginette in browser.
Fatto:
- Layout wide+fisso+scroll interno per Utenti e Config (come Log/Sessioni).
Introdotto un marcatore GENERICO e riusabile in vrp-lab.css: la testata sta
in .vrp-fixed-head (fissa), il corpo in .vrp-fixed-body (scorre). Le liste
di selettori :has() sono state ESTESE (non duplicate) per includere il nuovo
marcatore. Nuova classe .vrp-fold: una sezione collassabile (<details>) con
la testata resa come un vecchio <h2>.
- Config (
config.php): 4 sezioni ora<details vrp-fold>(Ambiente/Logging/
Database aperte, Percorsi chiusa perche' sola lettura). Le sezioni collassate
restano nel form e continuano a inviare i loro campi al POST.
- Utenti (
users.php): elenco e form sono una FISARMONICA ESCLUSIVA via
l'attributo HTML name="utenti-acc" sui due <details> (accordion nativo, zero
JS): apri uno -> l'altro si chiude. Tolto il bottone "Nuovo utente" ridondante.
Paginazione a 8 (?pg=N, prec/pagina X/Y/succ; pager nascosto se <=8).
$edit ora preso per id (vale anche se l'utente sta in un'altra pagina).
- Back-link "Amministrazione" TOLTO dalle 4 paginette (users/config/sessioni/
log): il ritorno resta la voce in barra. La testata piu' corta fa entrare 8
utenti senza scrollbar.
- Cruscotto admin (
vrp-lab.css): card a larghezza uniforme (flex:1 1 220px,
max 320, altezza allineata dalla riga); titolo .vrp-card b su una riga che
scorre (overflow-x:auto, scrollbar nascosta) invece di allargare la card.
- Sessioni (
sessioni.php): paginazione a 20 (campo "Righe/pag." 5-200,
?pg=N) e ordinamento cliccando l'intestazione (freccia su/giu' sulla
colonna attiva, ri-clic inverte). Colonna d'ordine sempre da whitelist
(SHOW COLUMNS). Chiusura $sess_url() (closure) costruisce i link preservando
lo stato (colonne/righe/sort) con override. Nuove classi CSS .vrp-pager* e
.vrp-sort.
- VRW + VRS in home (coda punto 3): creata la targa
config/vrs.json(VittRos
Share, url:/share/, auth:required, NIENTE lib: e' un primo link al servizio
standalone, integrazione vera = poi) e registrata in env.json services. Dato a
vitti e lella il ruolo admin su vrw e vrs (in user_services), cosi'
le due cartoline compaiono. Home ora: VRV(pub), VRN, VRW, VRS.
- Fix 403 wiz_foto: la targa vrw punta alla DIRECTORY
/lab/public/wiz_foto/,
che senza index.php dava 403 (autoindex off). Rinominato wizard_import.php
-> index.php e aggiornati gli 8 back-link "Torna al Wizard" nei passi. nginx
ha gia' index index.php ... -> la URL della cartella ora serve il wizard.
- Permessi:
boot/env_backupseradrwxr-sr-x(gruppo http senza scrittura)
-> messo g+w, cosi' il web server puo' scrivere i backup di env.json al salva.
Collaudo: php -l su tutti i file toccati OK. Render CLI (utente vitti id 1):
Config = 4 fold + fixed-head/body; Utenti = fisarmonica esclusiva (default elenco
aperto; ?new=1 form aperto/elenco chiuso), paginazione provata a soglia 2 (pag
2/3 con prec/succ giusti); Sessioni = 1/2 default, sort username asc -> 1/7, pag 2
prec=1/succ=3, stato preservato nei link; home mostra le 4 card per vitti; wiz_foto
index.php rende il wizard.
Aperto (coda UI): resta solo il punto 2 - tab riusati (login /
amministrazione / un tab per servizio). Idea di vitti: "tutto wide" anche il
cruscotto admin (rimandato). Proliferazione tab del browser (da risolvere
domani): i riquadri del cruscotto admin usano target="_blank" (admin.php) -> ogni
clic apre una scheda nuova e si moltiplicano; da ripensare insieme al punto 2
(tab/aree interne riusate invece di schede del browser). Minori: orfani _cfg.php/_pecore.php in wiz_foto da
cancellare; rename CSS .vv- -> .vrw-; lenostre_index.php -> vrn_index;
integrazione VERA di VRS share (login+WAN) e Step "Scarica da Nextcloud".
2026-07-06 - Tab riusati, cruscotto wide, wallpaper configurabile, gateway accesso, fix timeout
Perche': giro di feedback di vitti provando la piattaforma: (1) i riquadri
del cruscotto admin aprivano ogni volta una scheda nuova del browser
(proliferazione, "veramente fastidioso"); (2) il cruscotto admin andava reso
"wide" come le altre; (3) lo sfondo di piattaforma non era editabile dalla
pagina Config (c'era in env.json ma nessuna voce nel form); (4) aprire un'area
riservata da sloggati saltava MUTO alla login; (5) il timeout buttava fuori vitti
MENTRE lavorava sul VRW.
Fatto:
- Tab riusati (
pages/admin.php): toltotarget="_blank" rel="noopener"
dai riquadri. Ora Utenti/Config/Sessioni/Log si aprono nella STESSA scheda; si
torna dalla voce "Amministrazione" in barra. Fine proliferazione.
- Cruscotto admin WIDE (
pages/admin.php): avvolto in.vrp-fixed-head+
.vrp-fixed-body, cosi' scatta la macchina "wide+fisso+scroll interno" gia'
esistente (body:has(.vrp-fixed-body) in vrp-lab.css). Zero CSS nuovo.
- Wallpaper configurabile (
pages/config.php): nuova sezione Aspetto
(fold) con la voce "Sfondo (wallpaper)" che edita ui.wallpaper in env.json.
Salvataggio + validazione: se l'URL sta sotto la base (/lab/...) si mappa al
filesystem e si controlla che il file esista (ui.wallpaper (file non trovato)
fra gli errori). Messa in cartella la foto public/wallpapers/buddha.jpeg
(da /tmp, gruppo http, 664). NB: env.json NON toccato a mano su richiesta di
vitti: lo sfondo si sceglie dalla pagina (resta sfondo_spagna finche' non salva
buddha).
- Gateway di cortesia (
pages/accesso.php, rotta bareaccesso, nav=false):
quando serve l'accesso non si salta piu' muti alla login. Splash "Riservato -
<SERVIZIO> richiede l'accesso", meta-refresh a 5s verso la login, bottone "Vai
subito". Porta con se' svc e next. Agganciato in DUE punti:
- FC (
public/index.php) ramo admin-non-autorizzato -> `?p=accesso&svc=admin&
next=<rotta>`;
vrp_guard_request()(lib/vrp_auth.php) per i servizi ospitati: i rami
admin/auth:required ora vanno al gateway con next=REQUEST_URI (il ramo
lan resta 403 secco).
nextnella login (pages/login.php): dopo l'accesso torni dove volevi.
Solo percorsi same-origin sotto la base (strncmp base, niente // ne'
a-capo) -> NIENTE open-redirect; ostili -> home. Portato anche nel POST (campo
hidden) cosi' sopravvive all'invio del form.
- Fix timeout scheda-fantasma (
pages/logout.php+public/index.php): la
causa era il meta-refresh di una scheda di piattaforma lasciata ferma che dopo
il TTL andava dritta a logout&t=1 e sloggava, anche se sul VRW eri
attivissimo (ogni pagina rinfresca vrp_last via vrp_session_check al boot,
ma la scheda immobile non lo sa). Ora logout.php?t=1 NON slogga se
time()-vrp_last <= TTL (sessione viva altrove): torna a ?p=home. Inoltre il
meta-refresh del FC ora usa il tempo RESIDUO (TTL - inattivita' + 5) invece
del TTL fisso.
Collaudo: php -l su tutti i 7 file OK. Contro nginx vivo (127.0.0.1 = LAN):
?p=home 200; ?p=admin da anonimo -> 302 al gateway `?p=accesso&svc=admin&
next=...`; gateway rende meta-refresh 5s + "<SVC> richiede l'accesso" + bottone;
login ?next= porta il campo hidden col target; login con next=//evil.com
RIFIUTA (0 campi next -> home). Render CLI come utente id 1 (stesso harness del
05/07): admin = 2 marcatori fixed-head/body e 0 target="_blank"; config = voce
"Aspetto" presente col valore wallpaper attuale.
Aperto: resta il punto 2 vero e proprio (tab/aree interne riusate: per ora
la proliferazione e' spenta tornando alla navigazione in-scheda, ma un vero
"tab per servizio" e' rimandato); "tutto wide" anche altrove se vitti vuole;
integrazione VERA di VRS share (login+WAN) e Step "Scarica da Nextcloud"; orfani
_cfg.php/_pecore.php in wiz_foto; rename CSS .vv- -> .vrw-;
lenostre_index.php -> vrn_index. Da provare da loggato in browser: cruscotto
wide e scelta buddha dal form Aspetto.
2026-07-06 (2) - Sfondi per-contesto + regia dei 3 tab (named target)
Perche': feedback di vitti provando in browser. (1) Ogni CONTESTO deve avere
il suo sfondo, riconoscibile e cambiabile SENZA codice (es. "se il buddha non
piace a Elena lo cambio in un attimo"). (2) La home doveva essere wide anch'essa.
(3) Sui tab avevo esagerato (tutto in una scheda): vitti vuole 3 schede a nome
fisso, riusate.
Modello sfondi deciso: piattaforma (tutte le sue pagine, incl. Config) =
spagna; ogni SERVIZIO = il suo (VRW = maloia); pagina di CORTESIA = buddha. La
cortesia NON e' un servizio: fa parte della piattaforma, quindi nel JSON di
piattaforma ci sono DUE voci (normale + cortesia).
Fatto:
- Sfondo cortesia configurabile (
boot/vrp_defines.php,pages/accesso.php,
pages/config.php): nuova costante VRP_WALLPAPER_CORTESIA da
ui.wallpaper_cortesia (vuoto = eredita quello di piattaforma). accesso.php usa
lo sfondo cortesia con fallback. La sezione Aspetto in Config ora ha DUE
voci: "Sfondo piattaforma" (ui.wallpaper) e "Sfondo cortesia"
(ui.wallpaper_cortesia); validazione file-esiste su entrambe (loop). env.json
NON toccato a mano: vitti sceglie dalle voci (buddha da salvare li').
- Sfondo VRW = maloia (
config/vrw.json): messo `public/wallpapers/
maloia.jpeg (da /tmp, gruppo http 664) e valorizzato manifest.wallpaper`. La
cartolina VRW in home lo mostra gia' (.vrp-card-wp). Cambiabile dal riquadro
vrw in Config. (Dipingere anche le PAGINE interne del VRW resta da fare: e'
parte dell'integrazione VRW vera.)
- Home WIDE (
pages/home.php): avvolta in.vrp-fixed-head+ `.vrp-fixed-
body` (riusa la macchina wide). Come admin/config/utenti/sessioni/log.
- Regia dei 3 TAB (named target) invece del
target=_blankche moltiplicava
o dell'unica scheda che collassava tutto:
- Tab 1 (corrente,
_self): login, piattaforma (home), Configurazione. La
Config e' PIATTAFORMA: spostata dal cruscotto admin ALLA barra (nav=true),
tolta dai riquadri di admin.php.
- Tab 2 (
vr-servizi): le cartoline dei servizi in home aprono tutte in questa
scheda condivisa (target="vr-servizi").
- Tab 3 (
vr-admin): la voce "Amministrazione" in barra e i riquadri del
cruscotto (Utenti/Sessioni/Log) aprono in questa scheda condivisa
(target="vr-admin"). Aggiunto supporto target per-rotta nel FC
(public/index.php) e reso nella barra.
- Ctrl/mid-clic per aprire altrove resta sempre possibile (comportamento
nativo del browser).
Collaudo: php -l su tutti i file OK. Render CLI (utente id 1): home = 1
fixed-head + fixed-body, 4 cartoline target="vr-servizi", maloia.jpeg sulla
VRW; admin = 3 riquadri target="vr-admin", niente "Configurazione", wide;
config Aspetto = due voci ("Sfondo piattaforma"=spagna, "Sfondo cortesia"=vuoto).
FC intero: barra = Piattaforma | Amministrazione (target vr-admin) |
Configurazione (_self). Cortesia via HTTP: eredita spagna finche' non si salva
buddha.
Aperto: dipingere le pagine INTERNE dei servizi col loro sfondo (VRW=maloia)
= integrazione VRW vera; integrazione vera VRS (login+WAN) + Step Nextcloud;
orfani _cfg.php/_pecore.php; rename CSS .vv- -> .vrw-; `lenostre_index ->
vrn_index`; "tab per servizio" ancora piu' fine se i named target non bastano.
2026-07-06 (3) - Permessi scrittura config + sfondo che veste le pagine del servizio
Perche': (1) salvando la config: `file_put_contents(env.json): Permission
denied - php-fpm gira come http` ma i JSON erano a sola lettura per il gruppo.
(2) vitti vuole lo sfondo del servizio (VRW=maloia) NON solo sulla cartolina ma
sulle PAGINE del servizio, e modificabile "come piattaforma e cortesia".
Fatto:
- Permessi scrittura (una-tantum, non codice):
boot/env.jsone
config/*.json -> 664 (gruppo http in scrittura); cartella config/ ->
2775 (setgid) cosi' "Aggiungi servizio" puo' creare il nuovo file col gruppo
giusto. env_backups/ e config/backups/ erano gia' g+w. NB: era proprio il
buco che mancava dopo aver reso editabile la config dalla pagina.
- **Sfondo che VESTE le pagine del servizio, config-driven, zero codice nel
servizio** (lib/vrp_services.php + boot/3_2_1_Go.php): due funzioni nuove.
vrp_current_service() riconosce il servizio ospitato in cui cade la richiesta
(url targa vs SCRIPT_NAME, come la guardia). vrp_service_bg() (chiamata al
boot dopo la guardia) bufferizza l'output e, se c'e' un wallpaper nella targa,
inserisce un <style> PRIMA di </head> (o dopo il primo <link .css>) che
mette lo sfondo con un velo scuro (.58->.82) per la leggibilita'. Vince sul tema
perche' inserito per ultimo nella testata. Le pagine del wizard NON emettono
quasi mai <body>: per questo dipinge la PIATTAFORMA, non le pagine. CSV,
download e binari (senza </head> ne <link .css>) restano INTATTI (il
callback li lascia passare identici). Il velo forte + i pannelli opachi del tema
VRW tengono tutto leggibile; maloia incornicia ai lati del .vv-wrap (920px).
- Sfondo modificabile come piattaforma/cortesia (
pages/config.php): nel
riquadro di OGNI servizio, sopra il JSON grezzo, una voce amichevole Sfondo
(id f-wp-<servizio>, univoco). Fonde nel manifest.wallpaper al salvataggio
(il campo e' la fonte di verita'), con la stessa validazione file-esiste.
Impostato config/vrw.json -> maloia.
Collaudo: php -l OK sui file toccati. Motore d'iniezione provato su 3
campioni: pagina con </head> -> style prima di </head>; pagina col solo
<link .css> -> style dopo il link; testo CSV -> intatto. Render CLI config:
voce Sfondo per ogni servizio, VRW prefillato con maloia. Permessi verificati
(664 gruppo http, config/ 2775). DA VEDERE IN BROWSER (loggato): una pagina del
wizard vestita di maloia.
Aperto: provare in browser il wizard vestito (e valutare se il gestore_foto,
UI ricca, sta bene o va escluso); poi integrazione VRS vera + Step Nextcloud;
orfani _cfg/_pecore; rename CSS .vv- -> .vrw-; lenostre_index -> vrn_index.
2026-07-06 (4) - VRW: sfondo senza velo, tema wide/translucido, menu cruscotto, rename import
Perche': feedback di vitti provando il wizard in browser (dal fiume). (1) maloia
troppo scuro = via il velo, foto originale; (2) heic_to_jpg + import "wide e pannelli
trasparenti/opachi come le altre pagine"; (3) il menu a 6 bottoni -> cruscotto con le
voci vive, i vecchi passi Amazon (Upload/Unzip/CSV) da parte; (4) import_amazon.php
-> togliere "amazon" dal nome (non si usera' piu').
Fatto:
- Sfondo servizio SENZA velo (
lib/vrp_services.php,vrp_service_bg()): tolto il
linear-gradient scuro dall'iniezione; ora `body{background:url(<wp>) center/cover
fixed no-repeat}` = foto originale piena. La leggibilita' la garantiscono i pannelli
translucidi (sotto), non piu' un velo che scuriva la foto.
- Tema wide + pannelli translucidi (
wiz_foto/theme.css), vale per TUTTE le pagine
del wiz in un colpo: .vv-wrap max-width 920 -> 1100; .vv-card/.vv-banner/
details da colore pieno a rgba(.74) + backdrop-filter blur(4px) cosi' lo sfondo
del servizio traspare (stile pannelli di piattaforma). Aggiunte classi cruscotto
.vv-cards/.vv-tile (tessere translucide).
- Menu wizard -> CRUSCOTTO (
wiz_foto/index.phpriscritto): 4 tessere vive
(Scarica da Nextcloud, Converti HEIC->JPG, Import in leNostre, Gestore foto); i vecchi
passi Amazon (Upload/Unzip/Esporta CSV) spostati in un <details> "Vecchi strumenti"
(declutter ma reversibile, niente cancellazioni).
- Rename
import_amazon.php->import.php(mv) + aggiornati i 3 link che lo
puntavano: heic_to_jpg.php (x2), unzip_amazon.php (x1), e il menu. Header-commento
aggiornato. (Restano cosmetici interni: nome report import_amazon_v4_*.txt, _README.)
- Step 0
nextcloud_import.phpcreato come STUB tematizzato ("in costruzione" + il
piano): la tessera non e' un clic a vuoto. La macchina vera (leggere
/data/<acct>/files/Photos, copiare in staging, poi Import) = prossimo giro.
Collaudo: php -l OK su index/nextcloud_import/import/heic_to_jpg/unzip_amazon e
vrp_services. Render CLI del menu: 4 .vv-tile giuste + "Vecchi strumenti", zero
residui import_amazon nei link. Iniezione senza velo confermata nel sorgente. DA
VEDERE IN BROWSER (loggato): maloia originale + pannelli translucidi + cruscotto.
Aperto (prossimo giro): la MACCHINA dello step 0 Nextcloud (usare le 2 foto del
fiume in Photos/2026/07/06 come cavie); valutare gestore_foto (UI ricca) con lo
sfondo; poi integrazione VRS; orfani _cfg/_pecore; rename CSS .vv- -> .vrw-;
lenostre_index -> vrn_index; report/README residui con "amazon".
2026-07-06 (5) - Step "Scarica da Nextcloud": import DIRETTO in leNostre (FATTO)
Perche': costruire la macchina vera dello step 0 (finora stub). Vitti al fiume,
su 5G (221 Mbit -> banda non e' il problema): auto-upload e meta-refresh RIMANDATI,
focus sull'import da Nextcloud.
Fatto:
- Motore di import nella libreria (
platform/lib/vrw.php): verificato che le
funzioni-motore stavano SOLO in import.php (nessuna collisione). Aggiunte in vrw.php
con nomi vrw_* NUOVI (niente clash con le copie legacy di import.php, che resta
intatto): vrw_is_jpeg, vrw_exif_date, vrw_filename_date,
vrw_filename_year_hint, vrw_safe_target_path, vrw_is_duplicate_by_size, e
vrw_ingest() (importa UN file: data EXIF->nome->[mtime]->indecise;
dest AAAA/AAAA_da_archiviare/MMGG/AAAAMMGG-HHMMSS.jpg; niente sovrascritture;
doppioni per dimensione; ritorna un array esito, con flag dry). NB vrw_ingest
ha un nome diverso da import.php::vrw_import_file APPOSTA (entrambe caricate? no:
import.php e' una pagina; la lib gira ovunque -> nome diverso = zero redeclare).
DEBITO NOTO: migrare import.php a vrw_* e togliere le sue copie (TODO snello).
- Pagina vera
wiz_foto/nextcloud_import.php(ex stub): legge la targa vrw
(nextcloud: data_dir=/srv/dati/nextcloud/data, photos_subdir=Photos,
accounts=[vitti,lella]); selezione account (whitelist); scansiona
data_dir/<acct>/files/Photos/AAAA/MM/GG e mostra le cartelle-giorno con conteggio
jpeg (checkbox, piu' recenti in cima); opzioni "solo anteprima" (default ON) e
"fallback mtime"; import in copy (gli originali restano su Nextcloud) via
vrw_ingest -> leNostre. Whitelist dei giorni dal set scansionato (no traversal).
Solo JPEG per ora (video = TODO). Livrea vv-theme (maloia + pannelli translucidi).
- Le vecchie funzioni restano in import.php: intatto, nessun rischio.
Collaudo: php -l OK (vrw.php + nextcloud_import.php). DRY-RUN reale (harness
CLI, utente 1, POST acct=vitti days=2026/07/06 dry_run=1): Lette 2, Importate 2,
Indecise/Doppioni/Errori 0; datazione [NOME] corretta (IMG_20260706_105246 ->
06/07 10:52:46); zero scritture su leNostre confermato. Permessi: leNostre +
leNostre/2026 = drwxrwsr-x gruppo http -> php-fpm(http) scrive. Manca solo la prova
REALE dal browser (vitti loggato) -> deseleziona "solo anteprima" e importa.
Aperto: import REALE dal browser (le 2 foto fiume) + vederle in Gestore foto ->
poi mostrare a ELENA; VIDEO (vrw_ingest fa solo jpg, i .mp4/.mov vanno gestiti a
parte, magari in una cartella video di leNostre); barra "pecore" per lotti grossi
(ora e' POST sincrono); migrare import.php al motore vrw_*; auto-upload app +
meta-refresh servizio (rimandati).
2026-07-06 (6) - Rifiniture flusso wiz + auto-HEIC nell'import
Perche': feedback di vitti dopo il dry-run. (1) gestore_foto senza ritorno al
wiz; (2) heic_to_jpg con rimandi vecchi "Step 2/Step 4"; (3) import.php ormai
inutile (l'import Nextcloud va dritto in leNostre); (4) pagina Nextcloud troppo
lunga; (5) HEIC: metterlo tra i vecchi e convertirlo AL VOLO nell'import.
Fatto:
- gestore_foto.php: aggiunto
<a class="btn" href="index.php">↑ Wizard</a>in
testata (vitti: "poco visibile ma una volta imparato ok" - lasciato, si vedra' con
Elena).
- heic_to_jpg.php: tolti i rimandi "← Step 2: Unzip" e "Step 4: Importa" (blocco
nav + pannello pecore) -> resta solo "↑ Torna al Wizard".
- index.php (menu): cruscotto ridotto a 2 tessere vive (Scarica da Nextcloud,
Gestore foto). "Import da staging" e "Converti HEIC (manuale)" spostati in
"Vecchi strumenti".
- nextcloud_import.php: giorni in un riquadro SCORREVOLE (max-height 220,
overflow) + checkbox "seleziona tutti" (JS). Non allunga piu' la pagina.
- AUTO-HEIC nell'import: nuovo in
platform/lib/vrw.php->vrw_is_heic()+
vrw_heic_to_jpg() (heif-convert -> magick -> convert, il primo disponibile;
tool memoizzato; nomi NUOVI, niente clash con vrw_pick_converter/vrw_run_convert
di heic_to_jpg.php). In nextcloud_import: la scansione conta jpg+heic; nell'import
vero un HEIC viene convertito in un jpg temporaneo (sys_get_temp_dir, basename
conservato per la datazione dal nome), poi ingerito e il temp cancellato; in
anteprima e' solo segnalato. Tutti i tool presenti sul server (heif-convert,
magick, convert).
Collaudo: php -l OK. Dry-run 2026/07/06 ancora 2/2. Menu = 2 tessere.
Round-trip HEIC provato davvero: magick crea un .heic di prova ->
vrw_heic_to_jpg() lo riconverte in jpg (172 KB, rc 0). Manca solo l'import REALE
dal browser (vitti sta per farlo).
Aperto: import reale + demo Elena stasera; **svuota Nextcloud dopo import via
WebDAV** (destructive: serve app-password per non desincronizzare l'indice NC -
proposto, non ancora fatto); video; migrare import.php al motore vrw_*.
FIX 06/07 - banner "senza feedback": vitti fa l'import reale (funziona: 2 file
in leNostre/2026/2026_da_archiviare/0706 come 20260706-HHMMSS.jpg) ma "nessun
feedback". Causa: i .vv-banner.vv-ok/err/warn avevano sfondo a 14% (nati per
il fondo scuro pieno) -> su maloia sparivano. Reso opachi ~88% (dark tinto per
tipo, bordino colorato) in theme.css. Verificato riproducendo il POST reale
(harness): il banner c'era gia' nell'HTML (Lette 2 / Doppioni 2 al re-run = dedup
OK), era solo invisibile. Nota generale: i tint a bassa opacita' non reggono sopra
i wallpaper -> alzare l'alpha.
2026-07-07 - Motore img condiviso (vrp_img) + anteprime Nextcloud + Esci ovunque
Giornata nata dal collaudo dell'import con Elena (punch-list in memoria
project_vrw_demo_elena): rifiniture UX + il primo pezzo di piattaforma che serve
DAVVERO a piu' servizi (un motore immagini condiviso).
Rifiniture pagina import (nextcloud_import.php):
- Lista giorni da colonna singola a griglia responsiva (2-3 colonne,
repeat(auto-fill,minmax(180px,1fr))), box piu' alto (poi 460px).
- "Solo anteprima" default OFF (
$dryRunGET=false): il primo clic importa
davvero. Elena vuole FARE, non guardare a vuoto.
**MOTORE IMMAGINI CONDIVISO - platform/lib/vrp_img.php (estratto da maps/img.php,
che resta INTATTO): pattern motore puro + cappelli**. Due funzioni pure, stile
di casa (@desc/@param, fail-loud): vrp_img_resolve($src,$bases) (web-prefix ->
path reale + guardia anti-traversal) e vrp_img_serve($fsPath,$w,$opt) (resize +
cache disco + orientamento EXIF + ETag; EMETTE ed esce). Costanti
VRP_IMG_ALLOWED_W/DEFAULT_W/QUALITY. Nessun gate nel motore (scelta chiave): chi
puo' vedere cosa lo decide il CAPPELLO; il motore ha i pomelli cache_dir e
visibility:public|private per gli alberi sensibili. Collaudo CLI: lint, traversal
-> NULL, ridotta una foto leNostre (480x328) *e* una Nextcloud (480x853) = serve
entrambe le radici; cache creata.
ANW nel nuovo schema: il motore non puo' esporre ANW (non gestisce accessi). La
regola odierna (path sotto /srv/http/leNostre/ANW/ -> LAN-only + cache privata)
diventa un WRAPPER esplicito nel futuro cappello maps; STESSA ricetta del cappello
Nextcloud (albero sensibile = LAN + private). In collaudo non renderizzata alcuna
foto ANW di proposito.
Cappello wiz_foto/nc_thumb.php (thumbnail SORGENTE su Nextcloud per l'anteprima
prima dell'import): require vrp_img.php, gate vrp_is_lan(), data_dir dalla targa
vrw, vrp_img_resolve($src,['/ncsrc'=>$dataDir]), DOPPIA CINTURA (anti-traversal
+ il path reale deve contenere /files/Photos/ -> note/BANCHE NON sbirciabili),
serve cache_dir=null (on-the-fly v1) + visibility:private.
Anteprime per-giorno in nextcloud_import.php: la scansione raccoglie i basename
jpeg per giorno ($dayJpegs; heic contati ma non sbirciabili). Ogni giorno ha 👁;
click -> pannello con griglia <img loading=lazy src="nc_thumb.php?..."> on-demand.
Selezione per-foto (richiesta di Elena): ogni thumbnail ha una casella (default
spuntata=importa); deselezioni le schifezze -> l'import le SALTA (JS Set ncExcluded
-> hidden exclude[]; PHP: $excl=array_flip($_POST['exclude']), skip su
$rel.'/'.basename). Multi-cartella GIA' possibile (esclusioni globali).
Rifiniture 2a-3a prova browser:
- Spiegazione lunga -> tooltip (title=) sull'h1 + icona ⓘ; la card mostra solo
Sorgente/Destinazione.
- Dopo dry-run pronto per import: giorni restano
checked($postedDays), spunta
anteprima si toglie ((!$didImport && $dryRun)), esclusioni preservate (JS
NC_EXCL_INIT seed).
- Toggle seleziona/deseleziona tutte nel pannello (
#ncPrevToggle, riusa
l'handler per-foto via dispatchEvent).
- Il giorno segue le foto (
ncSyncDay): aprire 👁 auto-spunta il giorno; deselezioni
tutte -> si dispunta. Risolve "seleziono la foto ma il giorno resta vuoto".
Esci in OGNI pagina-servizio - vrp_service_esci() in lib/vrp_services.php,
chiamata nel boot 3_2_1_Go.php dopo vrp_service_bg(). Gemella dello sfondo:
bufferizza l'output e inietta un "Esci" fisso (in alto a dx) SOLO nelle pagine-servizio
loggate (che non hanno la barra di piattaforma). Stessa guardia salva-binari dello
sfondo (inietta dopo <link .css> o prima di </head>; CSV/immagini/binari INTATTI ->
i JPEG di nc_thumb non si corrompono). URL assoluto vrp_url('public/?p=logout') (le
pagine-servizio sono fuori dal front controller).
Collaudo: php -l su tutti i file OK; ASCII puro, zero curly-quotes; cintura Photos
provata (Photos AMMESSO / Notes BLOCCATO); iniezione Esci provata su 3 output (pagina
wiz INIETTATO, con-head INIETTATO, binario JPEG INTATTO). NON verificato dal vivo nel
browser i pezzi che richiedono la sessione (render anteprime, comparsa Esci): li prova
vitti loggato.
Doc: B200 aggiornato (sez.0 strato meta-utility search+img; sez.3b la piattaforma
inietta cornice - sfondo+Esci - nelle pagine servizio). API rigenerate con
genera_doc.php.
Aperto/debito: cache privata su disco per nc_thumb (scegliere dir scrivibile+negata
da nginx); cappello maps ANW-aware quando si rifara' maps/img.php (originale ora intatto);
versione leNostre delle anteprime = riusa vrn_foto_search+vrp_img; svuota-Nextcloud
post-import (WebDAV, destructive); video; migrare import.php a vrw_ingest.
VRS - Port dello Share nella piattaforma (08/07/2026) - CHIUSO
Cosa: portato il servizio Share (VittRos Share) da /srv/http/share (standalone, core
VRV) a /lab/public/share nella piattaforma. Primo servizio ospitato "pieno" dopo il wizard.
Struttura: lib platform/lib/vrs.php (funzioni vrs_*, convenzione VRP); pagine
public/share/ (index, download, upload, create_dir, thumb, delete) + anw/verboten.php.
I dati share_dirs/share_files restano in vittrosviaggi, registrato come DB vrv
(secret stabile secrets/db_vrv.php). Identita' di piattaforma (vrp_current_user).
Feature: thumbnail immagini via vrp_img (thumb.php); anteprime VIDEO via fotogramma
ffmpeg (cache .vthumb) con badge play + fallback onerror; upload multi con PRG
(redirect in cartella + banner + overlay d'attesa col filmato pecore .mp4); cancella HARD
(unlink fs + DELETE riga + rmdir) con guardia vrs_can_manage (proprietario o admin);
griglia a mattonelle.
Permessi: ruoli VRS = admin + registrato (registrato = qualsiasi loggato, nessun
ruolo da assegnare). Fix home.php: la cartolina dei servizi auth:required la vede ogni
loggato (visibilita' = stessa regola della guardia d'accesso, non piu' per-ruolo).
Verboten (gag 403): pagina mockup con foto naturista gatata (LAN+login), 2 versioni
(full / purga: cartello FRECH! con ImageMagick sul petto + taglio basso), in share/anw/,
nessun link visibile.
Sessioni admin: filtri username (LIKE) + action (dropdown da valori distinti) + pulisci
filtri; sort su username gia' presente. Preparazione al registro azioni.
Generatore doc: genera_doc.php ora aggiunge "Usato in" (grep dei punti di chiamata,
file:riga) per ogni funzione. API rigenerate (83 funzioni).
Collaudo: php -l su tutti i file; giri HTTP autenticati (login bot usa-e-getta,
ripristinato): render share, thumb immagine+video JPEG puliti, upload PRG, delete hard,
verboten full+purga, cartolina home per utente senza ruolo vrs. nginx client_max_body_size
50M->2g (sudo vitti) per gli upload grossi.
Aperto: registro azioni (logging vrp_action, scelta A/B); VittExplorer -> vrp_explorer
(estrarre da share + media_popup VRV); leNostre50 sul vecchio /maps/img.php -> vrp_img;
unificazione utenti in vrp_auth (owner dei file vecchi).
2026-08-02 - VRW: i FILMATI entrano in leNostre (import + Gestore)
Perche': richiesta di Elena (vedi RICHIESTE_ELENA). Il wiz
prendeva solo JPEG/HEIC, quindi dal 2025 i video non entravano PIU' in archivio: 2023
79 filmati, 2024 130 (di cui 125 messi a mano in ANNO/9999 Filmati), 2025 3, 2026 0.
In piu' il rischio vero: pulendo Nextcloud dopo l'import si cancellavano filmati mai
archiviati (successo il 02/08: 3 filmati veri recuperati a mano dal cestino Nextcloud).
Il TODO era gia' scritto qui il 06/07 ("VIDEO: vrw_ingest fa solo jpg").
Decisione presa con Elena: i filmati vanno nella **cartella-giorno insieme alle
foto**, NON in una cartella video separata. Cosi' quando sposta le foto di un evento,
il filmato la segue (ed e' come stanno i 407 filmati gia' in archivio).
Fatto:
platform/lib/vrw.php- costantiVRW_VIDEO_EXT(mp4/mov/m4v/3gp/avi/mpg/mpeg/
mts/mkv/webm) + VRW_FFPROBE/VRW_FFMPEG a path ASSOLUTO (php-fpm ha il PATH scarno,
regola B200 6a). Tre funzioni nuove: vrw_is_video(), vrw_video_date()
(i video non hanno EXIF: creation_time del contenitore via ffprobe, riportato al
fuso LOCALE o la cartella-giorno sbaglia di un'ora) e vrw_is_live_photo().
- Live Photo: i
.movda 0,4-6 MB dell'iPhone sono il pezzo-video di una foto,
non filmati. Si riconoscono dal gemello con lo stesso stem (X.mov + X.jpg) e si
SCARTANO: la foto entra per conto suo. Scelto il gemello e non una soglia di
dimensione - la soglia sbaglierebbe sui filmati brevi veri.
vrw_ingest()consapevole dell'estensione:$ext=jpgper le foto (gli HEIC
arrivano gia' convertiti), la sua per i filmati. Toccati i due punti che avevano
.jpg cablato (nome datato + sequenza indecise). Catena data: EXIF -> **ffprobe
(solo video)** -> nome -> mtime -> indecise.
vrw_filename_date()+ anno a DUE cifre (26-03-11 15-37-37 9907.mov): e' come
nomina il caricamento automatico di Nextcloud, e nessun pattern esistente lo prendeva
(per questo il .mp4 del 6/2, che non ha creation_time, sarebbe finito in indecise).
Messo per ULTIMO ed accettato solo se checkdate() + ora valida, cosi' un numero di
serie qualsiasi non passa per data.
wiz_foto/nextcloud_import.php- scansione e import dei video; contatorelive
(clip scartate) nel banner e nel log; badge 🎬 col numero di filmati accanto al
conteggio foto di ogni giorno; tooltip di testata aggiornato ("I video arriveranno"
-> ci sono).
wiz_foto/vrw_provider.php- il Gestore ELENCA i filmati (prima solo jpg/jpeg/
png, per questo i 407 video in archivio erano invisibili): mattonella col fotogramma,
bollino ▶, href al file. NIENTE full: il lightbox dell'explorer e' un <img>
e un video lo romperebbe; senza full il video resta fuori dall'array immagini e il
clic cade sul ramo it.href -> si apre nel lettore del browser.
wiz_foto/vrw_vthumb.php(nuovo) - cappello "filmati": estrae un fotogramma con
ffmpeg (a 2s, per non pescare il nero iniziale) e lo serve da vrp_img. Gemello di
nc_thumb.php e di share/thumb.php. Cache in VRP_SHARED_DIR/vrw_vthumb, FUORI da
leNostre (l'archivio va nel backup notturno: non deve ingrassare di roba ricavabile);
chiave = sha1(path + mtime), cosi' si invalida da sola.
- ZERO JavaScript:
vrp-explorer.jsaveva gia'hrefebadge. Nessun file JS toccato. - Indice
lenostre.fotonon toccato:lenostre_index.phpfiltrajpe?g|png|heic,
quindi i video restano fuori dall'indice foto. Voluto: l'indice serve a GPS e ricerca
FOTO. (Se un domani si vorranno i video cercabili, e' li' che si mette mano.)
Collaudo (CLI, sola lettura): php -l sui 4 file. Harness sui 3 filmati veri:
26-03-11 15-37-37 9907.mov ffprobe 14:37:37Z -> 15:37:37 locali = identico al
nome (fuso giusto); il .mp4 del 6/2 non ha creation_time -> ripiega sul nome ->
0206/20260206-112343.mp4; il .mov di set-2025 -> 0906/20250906-083955.mov.
Live Photo: coppia sintetica .mov+.jpg -> scartata, filmato senza gemella ->
importato. Non-regressione foto: jpg con EXIF -> 20260202-131901.jpg, identico a prima.
Date-dal-nome fasulle (99-99-99, mese 34) -> null. vthumb: risoluzione path OK,
traversal /leNostre/../../etc/passwd RESPINTO, fotogramma 640x1138 in 315 ms, immagine
vera (sala del concerto, non nera). Scansione sui dati veri: lella 2026/02/06 1 filmato
(prima invisibile), vitti 179 foto + 2 filmati.
Aperto: prova REALE dal browser da loggati (l'import e il Gestore rispondono 302 a
curl anonimo, come tutto il wiz) - da fare con Elena; poi la pulizia dell'account vitti
su Nextcloud (25 filmati nel cestino da guardare PRIMA di svuotare). Da valutare: mpg e
avi vecchi (ffprobe spesso non ha creation_time -> finiranno per nome o mtime).
2026-08-02 (2) - VRW filmati: i conteggi (collaudo di Elena)
Perche': Elena ha collaudato i filmati e ha trovato il difetto giusto: la cartella
0206 mostrava "0 foto" pur contenendo il filmato appena importato. La sua proposta
("scrivere 0 file") non bastava: il numero non era solo etichettato male, era sbagliato
- veniva dall'indice
lenostre.foto, che i video non li ha e non li deve avere.
Fatto:
vrw_count_videos()inplatform/lib/vrw.php: conta i filmati sotto una cartella,
ricorsivo, dal FILESYSTEM (RecursiveCallbackFilterIterator che pota .cestino,
thumbs e le cartelle nascoste). Misurato sul mang3: 0 ms per una cartella-giorno,
12 ms per un anno, 225 ms per l'intero archivio (54.800 file, 424 filmati) -
solo la radice paga, e paga una volta. Niente cache: costo noto e accettabile, meglio
che inventarsi un'invalidazione (stile "codice essenziale" del B200).
- Mattonelle-cartella (
vrw_provider.php):subora unisce le due fonti - le foto
dall'indice, i filmati dal filesystem: "12 foto · 2 🎬". Il conto video sta fuori
dal try del DB: se il database e' giu' si perdono i numeri delle foto, non anche
quelli dei filmati. "0 foto" resta quando non c'e' NIENTE (e' il segnale di
"cartella gia' lavorata" a cui Elena e' abituata).
- Conteggio in toolbar: aveva lo stesso difetto al contrario (contava i video ma li
chiamava "foto"). Scelta di Elena fra tre opzioni: "2 foto · 1 filmato". Realizzato
con l'unico ritocco JS di tutto il lavoro, e ADDITIVO: vrp-explorer.js usa
res.countLabel se il servizio lo manda, altrimenti conta come sempre con
itemNoun -> lo share non cambia di una virgola. L'etichetta la scrive il provider,
che e' l'unico a sapere cosa e' foto e cosa e' filmato.
- Lista dei giorni nell'import: stessa cura, "1 foto · 1 🎬" era fuorviante per un
giorno di soli filmati -> ora si nomina solo cio' che c'e' davvero.
Collaudo: php -l + node --check. vrw_count_videos: 0206 1, 2026 2,
2024 130, 2024/9999 Filmati 125 - coerenti col find. Provider chiamato da CLI:
0206 -> toolbar "1 filmato" e la mattonella sopra "1 🎬"; 0202 Parlasco -> "8 foto"
(nessuna regressione); 2026_da_archiviare -> "0 foto" + mattonella 0206 "1 🎬".
URL delle anteprime: vrw_base_url() e' corretto perche' l'AJAX dell'explorer passa
dalla STESSA pagina (gestore_foto.php fa vrp_explorer_dispatch), come confermato
dalla prova di Elena nel browser.
Aperto: ri-collaudo di Elena sui conteggi; poi la pulizia dell'account vitti su
Nextcloud (25 filmati nel cestino da guardare PRIMA di svuotare).
2026-08-05 - VRW: il bottone "Pulisci su Nextcloud" (richiesta di Elena)
Chiusura della richiesta del 03/08: dal Wizard, togliere da Nextcloud le foto
gia' archiviate, senza cambiare strumento. Progetto in VRW_pulizia_nextcloud.md
(scritto il 03/08, discusso e approvato oggi). Da collaudare: Elena, nel pomeriggio.
I due punti aperti sono decaduti misurandoli
Vitti: «penso che Elena non produrra' piu' Live Photo, e non penso che ce ne
siano adesso». Verificato: su Nextcloud zero .mov, zero heic - 188 file in
tutto, 183 jpg + 3 filmati veri (nessuno con foto gemella) + 2 note. Quindi
niente spunta «togli anche le clip», niente scelta sugli heic: la regola secca
(«si propone solo cio' che si ritrova in leNostre») tratta bene tutt'e due i casi
da sola, e li lascia dov'e' sono senza una riga di codice apposta.
Il vincolo dei filmati nel cestino, sciolto
34 filmati nei cestini, 33 non erano in archivio. I doppioni apparenti erano
il telefono che carica due volte (InstantUpload/Camera + Photos): distinti,
18 di vitti (3,13 GB) e 9 di lella (di cui 5 sono clip di Live Photo, 1920x1440
hevc ~3 s). I 18 rimessi su Nextcloud file per file nella loro cartella-giorno
- NON ripristinando la cartella-mese dal cestino, che avrebbe riportato su anche
centinaia di foto gia' archiviate. Verificati 18/18, byte identici.
Il codice (3 file + la targa)
| File | Cosa |
platform/lib/vrw.php | VRW_NC_CREDS (puntatore ai segreti); vrw_nc_creds(), vrw_nc_configurato(), vrw_nc_dav(), vrw_nc_trash(); vrw_archivio_indice(), vrw_nome_normalizzato(), vrw_gia_archiviato(), vrw_nc_esame_giorno(); vrw_data_scatto() estratta da vrw_ingest() |
wiz_foto/nextcloud_import.php | bottone 🧹 sulla riga del giorno, pannello del ricontrollo, conferma, registro azioni |
platform/config/vrw.json | nextcloud.creds = puntatore al file dei segreti |
secrets/nextcloud_app.php (nuovo) | le due password-applicazione, 640 gruppo http, fuori dall'albero web |
Backup .bak_20260805_131826_prepulizia per i tre file toccati.
Le tre scelte non ovvie
1. vrw_data_scatto() estratta apposta. Il ricontrollo deve calcolare lo
STESSO nome di destinazione che calcola l'import: se le due catene di date
(EXIF -> ffprobe -> nome -> mtime) divergessero, direbbe «non archiviato» per
file archiviatissimi, o peggio il contrario. Una sola sorgente di verita'.
2. Il ricontrollo cerca in TUTTO l'archivio, non dove l'import aveva messo.
Il metodo del documento (rilanciare l'import in anteprima e leggere gli
SKIPPED) sembra equivalente ma non lo e': Elena sposta le foto nelle
cartelle degli eventi, e li' l'anteprima non le ritrova piu'. Misurato: il
giorno 2026/07/14 da' 18 gia' in leNostre, ritrovate in `ANW/2026/0714
Secchiello Selvaggio - Compleanno Seneca/` - col vecchio metodo sarebbero
risultate tutte non archiviate, e il bottone non avrebbe pulito niente
proprio nei giorni gia' sistemati. Il metro resta stretto: nome di
destinazione (data al SECONDO) + dimensione IDENTICA al byte.
3. La mtime non vale per la pulizia (NC_PULIZIA_MTIME = false), nemmeno se
l'import l'aveva usata: e' la piu' fragile delle date e qui si decide se
buttare. Chi e' stato archiviato per mtime risulta «non ritrovato» e resta
dov'e': si sbaglia dalla parte giusta.
Prestazioni: una trappola presa e risolta
Prima versione: indice nome-file -> [size,path] e per ogni file una regex su
tutto l'indice -> 1,5 s per il giorno da 135 foto (135 x 32.000 regex).
Ora la chiave dell'indice e' il nome normalizzato (minuscolo, senza la coda
_001 dell'anti-collisione), quindi la ricerca e' un accesso diretto:
14 ms, stessi risultati. L'indice si costruisce in 0,21 s per 54.800 file.
Collaudo fatto (CLI)
- Prova a vuoto WebDAV: PROPFIND 207 su tutt'e due gli account; file finto
creato -> cancellato -> nel cestino -> ripristinato -> ripulito.
- La chiave di
vittisull'area dilellada' 404: l'admin non vede i file
degli altri (sez. 4b del progetto: risposta definitiva, servono due chiavi).
vrw_nc_trash(): cestina (204), e **ricancellare un file assente e' un
successo** (404 trattato come ok: qualcuno l'ha gia' tolto).
- Account senza chiave: risponde male senza esplodere; se il file dei segreti
manca, il bottone non compare proprio.
- I file finti delle prove tolti anche dal cestino: nessuna spazzatura lasciata.
I 34 filmati veri nel cestino: intatti.
php -lOK, zero virgolette tipografiche. Doc rigenerata: 229 funzioni.
⚠️ Tolta curl_close() dal codice nuovo: dal PHP 8.0 non fa nulla e dall'8.5 e'
deprecata - riempiva il log di avvisi (e su una pagina AJAX romperebbe il
JSON, vedi il baco noto dell'«errore 200»).
Resta
- Collaudo di Elena, un giorno solo dell'account
lella. - Importare dal Wizard gli 11 giorni dei filmati ripristinati; solo dopo si
possono svuotare i cestini.
2026-08-05 (2) - VRW: il REGISTRO degli import (la scopetta regge alla cernita)
Seguito della voce precedente, aperto da una domanda di Vitti a codice finito -
il tipo di domanda che vale piu' di una giornata di collaudo.
Il buco
«La scopetta controlla se le foto sono state importate, ma se Elena dopo fa una
cernita e ne cancella alcune, o le sposta nella cartella definitiva, non le
trovera' piu' e non permettera' la cancellazione. E' cosi'?»
Meta' si'. Lo spostamento era gia' coperto (la ricerca guarda in tutto
l'archivio). La cancellazione no. E Vitti ha aggiunto: **«Elena usa spesso il
rename»** - se rinomina un FILE, il confronto per nome-di-destinazione salta.
Poi l'osservazione che ha cambiato il disegno: **«se esiste il cestino del
Nextcloud + il cestino del VRW, mi sembra che stiamo facendo il caso piu' grosso
di quello che e'»**. Vero: la regola «deve stare in leNostre ADESSO» era stata
scritta immaginando la cancellazione come definitiva, ma ci sono due reti.
Si chiedeva alla scopetta una garanzia gia' data due volte.
La domanda giusta non e' «e' in leNostre adesso?» ma «e' mai stata importata?»
- un fatto che non cambia piu'. E la sa l'import.
Fatto
Tabella vrp_auth.vrw_import (DDL lanciata da Vitti: vrp_auth_app ha solo
SELECT/INSERT/UPDATE/DELETE). Disegno suo: la history resta il diario umano con
la riga-riassunto, i dettagli file-per-file stanno a parte e **li legge solo la
scopetta**; session_id (FK a sessions, ON DELETE SET NULL) lega le due cose
per poter indagare.
vrw.php:vrw_import_registra()(scrive, non-bloccante),vrw_import_registro()
(legge un giorno in mappa nome|dimensione), e vrw_nc_esame_giorno() con il
parametro $registro in coda (default [] -> chiamate vecchie invariate).
nextcloud_import.php: raccoglie$registratidurante l'import vero e li
annota dopo vrp_insert_action(), che gia' restituiva l'id della riga-riassunto.
Le tre scelte da non tradire
1. NON si annota la destinazione. Annotarla farebbe invecchiare il registro
al primo spostamento o rinomina: esattamente il male da cui deve salvare. Si
annota il FATTO.
2. src_size e' la dimensione dell'ORIGINALE su Nextcloud ($src, non
$ingestSrc): un heic convertito in jpg pesa un'altra cosa.
3. Due strade indipendenti, basta una: il registro (futuro, regge a
spostamenti/rinomine/cernite) e la ricerca in archivio (passato, cioe' tutto
quello importato prima che il registro esistesse). Resta su Nextcloud solo
cio' su cui nessuna delle due sa dire di si'.
Numeri, contro la paura della crescita
Vitti: «ho paura che si riempia all'infinito». sessions fa **1.560 righe in 3
settimane, 640 KB** = ~11 MB/anno. vrw_import cresce col materiale che passa:
in leNostre entrano ~2.700 file l'anno (2023: 2.589 / 2024: 3.034 / 2025: 2.483
/ 2026: 2.530) = ~350 KB/anno. Vent'anni in 7 MB. **Trenta volte piu' piano
della history che c'e' gia'.**
Verifica dell'altra paura ("non sono sicuro che il DB sia diverso")
Controllato invece di rassicurare: db_vrv.php -> vittrosviaggi (utente
vviaggi_app), db_vrp_auth.php -> vrp_auth (utente vrp_auth_app), piu'
lenostre e photo. E la prova: SHOW DATABASES **con le credenziali della
history risponde un database solo**, il suo. Da li' la roba di Elena non si
tocca nemmeno sbagliando a scrivere una query.
⚠️ Ma la correzione di Vitti restava giusta: vrp_auth **e' il DB da cui dipende
il VRW che Elena usa** (login/sessioni). Non "un DB che non c'entra": una
CREATE TABLE e' additiva e non tocca sessions, ma la prudenza era fondata.
Collaudo (CLI, transazione ANNULLATA: zero righe lasciate)
| caso | esito |
| mai importato, non in archivio | resta (giusto) |
| nel registro | si pulisce |
| dopo rinomina/spostamento/cestino (indice archivio VUOTO = caso peggiore) | si pulisce ancora |
| non nel registro | resta fuori (mano non allargata) |
| strada vecchia (ricerca in archivio) | intatta: 2026/07/14 da' sempre 18 |
php -l OK, zero virgolette tipografiche, pagine 302. Doc rigenerata: **231
funzioni**. sessions.id e' int unsigned (non bigint): la colonna di aggancio
e' stata fatta uguale, se no la chiave esterna non legava - trovato guardando lo
schema invece di andare a memoria.
2026-08-05 (3) - VRW: l'ordine dei controlli, e il registro che non veniva letto
Coda delle due voci precedenti. Il registro c'era, funzionava, era collaudato -
e per una categoria di file non veniva mai consultato. Trovato rileggendo
vrw_nc_esame_giorno() dall'alto, non da un sintomo.
Il difetto
I controlli sul TIPO del file (clip Live Photo, heic, non-media) stavano sopra
la consultazione del registro. Un heic usciva sempre dalla porta «entra convertita
in jpg, quindi l'originale non si ritrova uguale» e non arrivava mai alla prima
strada: per gli heic la strada (a) era codice morto. Un heic davvero importato
sarebbe rimasto su Nextcloud per sempre pur essendo archiviato - il contrario
esatto di cio' che il registro serve a garantire.
Nessun danno oggi: heic su Nextcloud zero, misurato ieri. E' il tipo di difetto
che si manifesta il giorno che qualcosa non si pulisce e nessuno capisce perche'.
La regola, in una riga
Il tipo del file dice solo se ha senso cercare un gemello identico in
archivio: riguarda la strada (b), non la (a). Il registro e' un fatto, e i
fatti si guardano per primi.
Ordine nuovo
1. clip Live Photo -> resta fuori prima di tutto, anche del registro.
In archivio non entra mai; se il registro dicesse il contrario sarebbe lui a
sbagliare, e qui si tiene il file, non lo si butta.
2. registro (strada a).
3. heic / non-media -> restano fuori (qui il tipo conta).
4. ricerca in archivio (strada b), invariata.
Il punto 1 e' la scelta non ovvia: e' l'unico posto dove il registro non ha
l'ultima parola. Tenerlo in cima fa si' che il falso positivo noto di
vrw_is_live_photo() (il 1224 Natale dal Bruno.MOV del 2011, MJPEG con la jpg
gemella - vedi il progetto sez. 8bis) continui a sbagliare dalla parte giusta.
Un file in piu' su Nextcloud costa quota; un file in meno costa un ricordo.
Collaudo (CLI, cartella finta: nessun file vero toccato, niente DB)
Il $registro passato a mano, cosi' si prova la funzione e non il giro intero.
| file | nel registro? | esito |
a.jpg | si' | si pulisce |
b.heic | si' | si pulisce ← prima restava fuori |
c.heic | no | resta (entra convertita in jpg) |
d.mov + d.jpg gemella | si' | resta (vince il punto 1) |
e.txt | no | resta (non e' foto ne' filmato) |
f.jpg | no | resta (non importata, non ritrovata) |
php -l OK. Backup vrw.php.bak_20260805_173425_preheic. Aggiornati il progetto
(VRW_pulizia_nextcloud.md sez. 8 punto 2, corretta, e nuova sez. 12) e questa
doc. Il registro vrw_import e' ancora vuoto: nessun import vero e' ancora
passato dal codice nuovo, si riempie al primo. AUTO_INCREMENT = 3 e' l'unica
traccia del collaudo di ieri, la cui transazione era stata annullata.
⏳ Resta il collaudo di Elena su un giorno solo dell'account lella.
2026-08-05 (4) - VRW: il collaudo di Elena, e i due buchi che ha fatto uscire
Collaudo fatto da Elena la sera stessa, in autonomia. Passato. E come sempre
il collaudo vero non conferma il codice: gli fa le domande che il codice non si
era fatto.
Il giro completo funziona
Il registro vrw_import e' passato da vuoto a 591 righe (206 imported +
382 skipped per vitti, 5 per lella) e la scopetta ha ripulito Nextcloud
giorno per giorno. Verificati a mano il primo giro (lella 2026/08/01, 2 foto) e
i filmati della Festa Alpini (vitti 2026/06/28, 6 file, 2,6 GB): pesi identici
al byte in leNostre, file nel cestino di Nextcloud e non svaniti, traccia in
sessions. La strada (a), il registro, ha funzionato al primo colpo su materiale
mai importato prima - che era esattamente il pezzo non ancora provato.
Il vincolo dei filmati e' sciolto per davvero: i 18 di vitti sono in
leNostre (piu' 2 arrivati dopo), quindi i cestini non sono piu' l'unica copia.
🐞 BUCO 1 - l'import ha lo STESSO punto cieco che aveva la scopetta
vrw_ingest() decide se una foto e' gia' entrata guardando un solo posto:
il path canonico ANNO_da_archiviare/MMGG/AAAAMMGG-HHMMSS.jpg (riga 347,
vrw_is_duplicate_by_size($firstCand, ...)). Appena Elena sistema il giorno
- cioe' sposta le foto nella cartella dell'evento - quel posto torna libero, e
al reimport successivo la foto viene ricopiata.
Misurato sul campo: l'import "tutti i giorni insieme" di Elena ha portato dentro
~200 file, di cui 172 lavoro vero (mai archiviati prima) e 26 doppioni
di foto che lei aveva gia' schedato in ANW/2026/0714 Secchiello Selvaggio,
2026/0711 Elena Malata, 2024/.../0914 Camping Kanegra. Confermati confrontando
il contenuto, non il peso.
E' lo stesso male della sez. 11 del progetto, dalla parte dell'import invece che
della scopetta. La cura esiste gia' ed e' li': il registro vrw_import sa
rispondere «questo file l'ho gia' portato dentro», ma vrw_ingest() non lo
consulta. Da fare con Vitti - non e' una riga, perche' vrw_ingest() e' condiviso
con gli altri import (zip Amazon, staging) che non hanno un registro.
🐞 BUCO 2 - i _NNN si moltiplicano a ogni giro
Corollario del primo, ma indipendente. Se il nome di destinazione collide con
una foto diversa scattata nello stesso secondo (i file IMG_..._1.jpg del
telefono), vrw_safe_target_path() accoda _001. Al giro dopo il confronto
guarda ancora il nome base, che ha un peso diverso -> non e' un doppione ->
_002. Poi _003. Senza limite.
Visto succedere in diretta: Elena ha lanciato lo stesso import tre volte
(18:15:56, 18:16:07, 18:17:24) e 3 foto hanno prodotto 6 copie in piu'.
Cestinate (soft) su sua autorizzazione. Cura proposta: il confronto deve
guardare anche le varianti _NNN - vrw_archivio_indice() normalizza gia' la
coda, la logica c'e', va portata dentro vrw_ingest().
🐞 BUCO 3 (UI) - occhio e scopetta si pestavano i piedi
Segnalato da Elena come primissima cosa: «l'occhio è finito tra la parentesi e
non funziona più». La scopetta era stata infilata nella riga del giorno senza
allargare la traccia della griglia (180px) e senza flex:none sui bottoncini: il
flex li stringeva sotto la loro larghezza, il simbolo usciva dal proprio riquadro
e il clic finiva sull'etichetta. Sistemato in nextcloud_import.php: traccia a
215px, flex:none su occhio e scopetta, overflow:hidden sull'etichetta.
Backup .bak_20260805_174624_preocchio.
🔧 E un permesso, che non era codice
Spostare un filmato da 2026/9999 Filmati dava errore. Non era il Gestore:
quella cartella e 2025/9999 Filmati - create a mano il 02/08 durante il
recupero dei filmati - erano drwxr-sr-x vitti:http, cioe' senza scrittura per
il gruppo. php-fpm gira come http: poteva leggere ma non spostare. Rimesse a
2775 come le sorelle 2023/2024. Sono le uniche due cosi' in tutto leNostre
(controllate tutte le cartelle).
Promemoria: quello che si crea a mano in leNostre va creato col permesso di
gruppo, se no il Wizard ci guarda dentro e basta.
Una cosa che NON e' un difetto
Scansione dei doppioni su tutto leNostre (37.140 file, confronto sul contenuto):
1.233 gruppi, 2.130 copie in piu', 2,98 GB. Sembra un disastro, non lo e':
di quelli nati da un reimport ne resta uno. Gli altri sono la stessa foto
messa in due posti apposta - 001 Cagnolini/Seneca/ piu' la cartella del
viaggio, ANW (453), web_wallpaper (294). Elena: «è vero, una foto può stare
in più posti». Detto a lei chiaramente di non trattare quell'elenco come una
lista di cose da cancellare: si smonterebbe un ordine costruito in vent'anni per
recuperare 3 GB su 108 liberi.
2026-08-26 (sera) - 🅿️ Il parcheggio della passeggiata prende il suo bottone
Dov'e': /srv/http/maps/convalida_foto.php (fuori dal lab: la mappa e' un
albero suo, non lo scandisce genera_doc.php).
Perche': dal 26/08 mattina il filo tratteggiato delle foto 🥾 parte dal
parcheggio DICHIARATO invece che dalla tappa piu' vicina - a Colonnata la tappa
piu' vicina (Campocecina) sta a 4,6 km dall'altra parte delle Apuane. Ma
passeggiate.json era il quinto degli elenchi-che-comandano e l'unico **senza
bottone**: si scriveva a mano con l'editor. Domanda di vitti la mattina stessa,
«riesco a metterlo a posto io?», e la risposta era no.
Fatto:
- Azione nuova
parcheggionello sportello POST, accanto apiedi/place.
Due versi: con lat/lon dichiara, con togli:true scorda.
- Due funzioni
passeggiate_load()/passeggiate_save(), gemelle di
nascoste_load/save.
- Due bottoni in barra:
🅿️ Il camper era quie🅿️✖ Non era li'.
Leggono lo stesso campo lat, lon del 📍: e' la stessa domanda fatta per due
cose diverse, e un secondo campo accanto al primo sarebbe stato solo un modo in
piu' di sbagliare casella. Vale la lezione del 21/08: il campo si svuota dopo
l'uso e se non c'e' una coppia di numeri il programma NON indovina.
- Bollino 🅿️ sulla miniatura (blu, sopra al 🥾, che spesso convivono) +
data-parch per accendere/spegnere i bottoni.
Tre regole scelte, e il perche':
1. Stesse coordinate = stessa passeggiata: si fondono invece di duplicare. E' il
caso di chi si accorge dopo di aver dimenticato due foto.
2. Una foto sta in UNA passeggiata sola: prima di riassegnarla la si toglie da
tutte. Se no spostare il parcheggio la lasciava rivendicata da tutt'e due e
vinceva la prima dell'elenco - cioe' di nuovo il caso, che e' il difetto da cui
e' nato tutto questo.
3. Una passeggiata che elencava foto e non ne ha piu' sparisce, ma le voci
senza la chiave foto non si toccano mai: sono la "comodita' a distanza" dei
viaggi vecchi, non hanno mai avuto foto da perdere.
Unica azione delle sei che NON riscrive map_points.js, ed esce dallo
sportello per conto suo prima del rebuild_derived: passeggiate.json lo rilegge
map_bootstrap.js a ogni apertura della mappa (fetch no-store), quindi basta
ricaricare - niente «Ricostruisci».
Collaudo (viaggio finto trips/prova_park, copia di monchio_2026, cancellato
dopo; il viaggio vero mai toccato, md5 di passeggiate.json invariato):
| # | prova | esito |
| 1 | parcheggio nuovo per 3 foto | voce creata, nome preso dalla cartella |
| 2 | 4a foto sulle STESSE coordinate | fusa, nessun doppione |
| 3 | foto di Colonnata spostata sul nuovo | Colonnata 4->3, nuovo 4->5 |
| 4 | lat:999 e lat:0 | rifiutate tutt'e due |
| 5 | 🅿️✖ su una foto sola | tolta, la passeggiata resta |
| 6 | 🅿️✖ su tutte le foto di Colonnata | Colonnata sparisce, "senza elenco" resta |
Piu': pagina vera del Monchio resa senza warning, 4 bollini 🅿️ sulle 4 foto
dichiarate, php -l e node --check (sul blocco <script> estratto dalla
pagina resa) puliti.
Backup: convalida_foto.php.bak_20260826_171500_parcheggio.
Aggiornati: MANUALE_diario_e_mappa.md (2.5bis: tabella a sei bottoni, "come
si dichiara" col bottone, "cinque elenchi" invece di "tre" - che erano gia'
quattro elencati, svista vecchia) e la testata di convalida_foto.php, che
documentava tre azioni POST su sei (mancavano place e piedi, aggiunte a
suo tempo senza toccare il commento).
Resta aperto: il gemello di questo bottone per la briciola sdoppiata
(via_points.json, deviazione andata-e-ritorno) - l'altra cosa che si fa solo a
mano.
2026-08-26 (sera, subito dopo) - Le due verita' della posizione di una foto
Disegno completo: MAPS_posizioni_a_mano.md.
Qui solo il cantiere.
Perche': guardando il 19/08 col bottone 🅿️ appena fatto, si e' visto che le
uniche foto SENZA nome del posto erano proprio quelle raddrizzate a mano col 📍.
Domanda di vitti: *«mi sembra di ricordare che avevamo in progetto di cambiare
l'indice delle foto, con i nomi e le coordinate manuali, cosi' se si rilegge
l'exif non si perde il lavoro fatto»*. Il progetto non era mai stato scritto (solo
una riga di promemoria); il timore era fondato e verificabile in due punti -
ln_upsert() fa ON DUPLICATE KEY UPDATE ... lat=VALUES(lat), e
popola_foto_index.php fa DELETE FROM foto.
E guardando meglio, era peggio del previsto: l'indice per quelle 17 foto non
era vuoto, aveva il nome SBAGLIATO - Ponte di Arcola, Castiglione Vara,
Bedizzano x8, Massa x6 - calcolati sulle coordinate che il GPS si era
inventato. Al primo «Ricostruisci» il cartellino avrebbe scritto «Bedizzano»
invece di restare vuoto.
La forma scelta - e NON e' "spostiamo la verita' nel DB":
la verita' resta trips/<v>/foto_posizioni.json; l'indice ne tiene una **copia
rifacibile** in lat_mano/lon_mano; tutti leggono lat_eff/lon_eff, che le
calcola il database. Cosi' un DELETE FROM foto non fa danno (si riversa dai
JSON) e nessun lettore puo' scordarsi di guardare la colonna giusta - che e'
l'errore in cui era caduto via_points.py la mattina stessa.
Fatto:
- ALTER su
lenostre.foto(lanciato da vitti da root, 35.408 righe, 0,9s):
lat_mano, lon_mano DECIMAL(10,7) NULL + lat_eff, lon_eff
GENERATED ALWAYS AS (COALESCE(lat_mano, lat)) VIRTUAL. Solo aggiunte.
ln_posizioni_fill(PDO, ?string $trip)inwiz_foto/lenostre_index.php:
legge i foto_posizioni.json, scrive lat_mano/lon_mano, e **dove il valore
cambia azzera localita** cosi' il nome si rifa' sulla coordinata nuova. Le
"tolte" si ripuliscono solo nella passata completa (leggendo un viaggio solo non
si puo' sapere se un altro la rivendica).
ln_localita_fillleggelat_eff/lon_effinvece dilat/lon.- Chiamata da tre posti:
geocodifica_foto.phpepopola_foto_index.php
(⚠️ PRIMA di ln_localita_fill, se no i nomi rinascono sull'EXIF), e il
bottone 📍 di maps/convalida_foto.php.
- Nel bottone 📍 la chiamata e' in try/catch, e non e' pigrizia: l'indice e'
una copia, il JSON che comanda e' gia' scritto - se il DB non risponde il 📍
deve riuscire lo stesso. E il ln_localita_fill e' limitato alle **cartelle
toccate**, se no il budget di rete se lo mangiano le 71 foto che un nome non
possono averlo (vedi sotto).
- I contatori di
geocodifica_foto.phpepopola_foto_index.phppassati a
lat_eff.
Collaudo: sei prove, tutte passate; le due che contano sono il
ln_reindex_target (rilegge l'EXIF di 23 foto: posizioni e nomi intatti) e la
simulazione del DELETE FROM foto (26 posizioni rimesse dai JSON, 26 nomi
rifatti con 0 domande a Nominatim). Dettagli e numeri in sez. 6 del disegno.
Backup: lenostre_index.php non ne aveva bisogno (solo aggiunte);
geocodifica_foto.php.bak_..._posmano, popola_foto_index.php.bak_..._posmano,
convalida_foto.php.bak_..._posmano.
Trovato per strada, NON un difetto: le 71 foto che restano senza nome non
sono un baco dell'attrezzo (l'avevo detto, sbagliando, prima di guardare). Sono
4 posti soli: 68 foto con lat=0, lon=0 (fotocamere che scrivono zeri quando
non agganciano) e 3 scattate dall'aereo sul Tirreno. Nominatim non ha un nome
per il Golfo di Guinea ne' per il mare aperto, e risponde giustamente niente.
🐞 APERTO, nato da li': 68 foto risultano "col GPS" avendo lat=0, lon=0. Se
una finisse in un viaggio, la mappa la piazzerebbe al largo dell'Africa. Lo zero
non e' una posizione, e' l'assenza di posizione: va trattato come tale in
ln_rows_from_json() (dove l'EXIF diventa riga) e nei filtri lat_eff IS NOT NULL.
2026-09-04 - Dark sulle pagine di lavoro, musica del post, «I diari di Elena»
Perche': vitti, al mattino: "i servizi della piattaforma hanno un feeling
diverso, il wizard foto e lo share sono dark, il VRV chiaro". Nel pomeriggio si
sono aggiunte la musica nell'editor e la richiesta di Elena di condividere TUTTI
i post di una categoria, non un articolo alla volta.
Il lungo, con i numeri e le strade scartate, sta in
docs/VRV_B100_dark_e_musica.md. Qui il cantiere.
1. Il dark: un cappotto, non un tema
Misurato prima di toccare: il CSS del VRV e' **36 file / 8.309 righe con ~880
colori scritti a mano e 24 presi da variabile**. vrv_base.css le variabili le
dichiara e non le usa quasi nessuno. Un tema vero vorrebbe prima variabilizzare
tutto: giorni, su ogni pagina del diario.
Deciso con vitti: solo le pagine di lavoro; il diario che legge Elena resta
chiaro, li' il chiaro e' voluto (carta sopra le foto).
- NUOVO
public/vv/css/vrv-dark.css- un file solo per tutte le pagine
vestite, a sezioni: paletta comune, poi una sezione per pagina, il nome della
sezione e' la classe del body. Si carica per ULTIMO e ridipinge quello che si
vede. Per tornare indietro si toglie la riga di enqueue.
- Colori copiati NEI VALORI da
wiz_foto/theme.css, non nei nomi: prendere i
suoi nomi legherebbe due servizi che oggi non si conoscono.
- Vestite:
admin.php(assaggio approvato) e il TELAIO di
modifica_post.php - barra, tendine, testata, cornice, modale, segnalibri.
NON il foglio: il contenuto lo disegna TinyMCE in un iframe suo, dove il
cappotto non arriva. Resta chiara anche la toolbar di TinyMCE (skin suo).
Il regalo: i pochi punti che le --vrv-* le usano davvero sono .vrv-panel e
.vrv-panel--strong (vrv_layout.css) e i testi (vrv_components.css) - cioe' la
testata del titolo e la card dell'editor. Ridefinite 9 variabili, quei pezzi si
sono vestiti da soli.
I bottoni della barra sono un CODICE COLORE (media azzurro, anteprima rosa,
cofanetto marrone, lingue verde, mappa turchese, versioni viola, salva/chiudi
arancio): la tinta non e' cambiata, e' cambiata solo la LUCE. Elena li ritrova
dove li lasciava.
Trappole, tutte trovate da vitti guardando:
- il bianco di
.vv-containersta dentro unargba()a mano: l'alpha da sola
non basta;
- il tema base tinge di verde
#219324ogni button/input/select; footer.phpcarica bootstrap dal CDN DOPO i fogli del tema: ogni regola deve
salire di specificita';
- vestire il testo di una classe senza vestirne il fondo:
.vrv-btnlo
portano anche i bottoni DENTRO i pannelli, che tinta loro non hanno - erano
crema su crema, illeggibili (i DD di Cofanetto e Lingue). Il fondo scuro va
messo sul BASE, i modificatori lo sovrascrivono;
- le opacita' basse si rovesciano sul fondo scuro: le lingue non tradotte
stanno a opacity: .28 (leggibile su chiaro, invisibile su scuro) -> .58.
2. Il wallpaper sparito dalle liste - non era il dark
vitti: "nelle liste ci doveva essere il wallpaper dei cappelli, non c'e' piu'".
Era cosi' su TUTTE le pagine del VRV, vestite o no.
La piattaforma lo sfondo lo metteva gia' lei e bene: vrp_service_bg()
bufferizza l'output del servizio e prima di </head> inietta
body{background:url(<manifest.wallpaper>)}. Ma theme-default.css cablava a
sua volta body.theme-default{background:url('/vittrosviaggi_shared/...')}, e
body pesa 0,0,1 mentre body.theme-default pesa 0,1,1: vinceva il tema, e
quell'indirizzo sulla 8443 e' un 404. La configurazione parlava e il CSS non la
faceva passare.
TOLTA l'immagine cablata dal tema, che ora dichiara solo il colore di ripiego.
Regola: nel VRV nessun tema deve scrivere una shorthand background: sul
body - schiaccia sempre l'iniezione della piattaforma, che ha specificita'
minima per costruzione. Un background-color va bene: sta sotto l'immagine.
3. Le tendine che non si chiudevano - un lavoro lasciato a meta'
vitti, provandole una per una: Cofanetto e Mappa si aprono e chiudono col clic
sul bottone; Lingue, Versioni e Salva/Chiudi si aprono ma per chiuderle bisogna
cliccare ovunque tranne che sul bottone.
Erano proprio quelle tre ad avere DUE gestori: le bindDropdown() in
initVrvDropdowns() di js/modifica_post.js piu' quelle di
vrv-modifica-post-barra.js. E tenevano lo stato in due posti diversi - classe
.is-open contro attributo aria-hidden - con
.vrv-dd.is-open > .vrv-dd__menu (0,4,1) che batte
.vrv-dd__menu[aria-hidden="true"] (0,3,1). Al clic su una tendina aperta il
primo gestore toglieva is-open, il secondo la RIAPRIVA.
Il commento in quel punto diceva gia' "NIENTE Cofanetto qui, due gestori si
pestano i piedi": il Cofanetto era stato staccato, le altre tre no.
Trappola dentro la trappola: initVrvDropdowns() e' dichiarata DUE VOLTE in
quel file, e fra due dichiarazioni omonime JavaScript tiene l'ULTIMA. Corretta
la prima non sarebbe cambiato niente. Sistemate entrambe, con un avviso sulla
copia morta. Non si e' perso niente: keepMenuInViewport(), l'unico extra che
bindDropdown dava a Versioni, e' una funzione vuota da tempo.
4. La musica del post nell'editor
Prima: post.musica si poteva scrivere SOLO da una casella di testo nuda in
admin_post.php (r.258) - si usciva dall'editor e si batteva a mano il nome
esatto del file. In modifica_post.php non c'era traccia della musica (il 🎵 in
alto a destra e' la Radio VV, un'altra cosa: suona shared/mp3).
Tendina SUA, non dentro il Cofanetto: **"la musica e' per post, non per gruppi
di post"** (vitti) - il cofanetto raggruppa piu' post, metterla li' insegnerebbe
una cosa falsa. Lo spazio non e' servito ritagliarlo: la barra aveva gia' ~200px
vuoti fra Mappa e Versioni.
- NUOVO
lib/musica_widget.php- elenco brani + HTML della scaletta - NUOVO
ajax/post_musica.php- l'unica porta che scrivepost.musica - NUOVO
musica_picker.php- il selettore, su vrp_explorer (stampo di
bg_picker.php): provider di 30 righe, la cartella e' piatta
- NUOVO
ajax/musica_upload.php- sportello CONDIVISO (lo usera' il JukeBox) - NUOVO
js/vrv-musica.js; TOCCATImodifica_post.php, i js/css della barra
Scelte:
- la scaletta E' l'ordine delle righe: niente numeretti da tenere allineati,
si legge l'elenco dall'alto in basso e quello si salva;
- l'elenco lo stampa il SERVER con la pagina (nessuna chiamata all'apertura);
- non riusata
vv_list_mp3()dilib/audio.php: filtra solo.mp3e
avrebbe fatto sparire tutti gli Aizpute, che sono .m4a;
- si salva solo un nome che ESISTE nella cartella; i brani spariti restano
visibili marcati "non trovato" invece di svanire in silenzio;
- permessi come
ajax/cofanetto_azione.php(loggato + admin/editor), NON come
update_sfondo.php che ha i controlli commentati;
- l'upload non sovrascrive un nome gia' preso: numera. Sovrascrivere
cambierebbe la musica dei post che usano quel nome senza che nessuno se ne
accorga.
Corretti dopo le prove di vitti:
- il ▶ suonava ma non fermava: lo stato si marcava DENTRO la promise di
play(), e fra il clic e la marcatura il pannello non sapeva di star
suonando. Ora si scrive PRIMA di chiamare play();
- la prima versione stampava tutti i brani dentro la tendina ("e' troppo
piena"): ora nella tendina c'e' solo la scaletta;
- due triangoli sulla mattonella, uno finto: era il
badge: '▶'del contratto
(decorazione, non si preme) messo OLTRE al pulsante vero. Tolto;
- "non si puo' deselezionare": il clic nel selettore era solo aggiunta. Ora
e' un INTERRUTTORE (un clic mette, un altro toglie) e le mattonelle gia'
prese portano un ✓. Chi sceglie deve poter disfare dove sta scegliendo.
Resta il campo a mano di admin_post.php come via di fuga.
5. «I diari di Elena» - due voci di config, zero codice
Elena vuole mandare agli amici tutto il diario di viaggio. Non serviva scrivere
niente: diario_lista_guest.php filtra GIA' per categoria da URL
($_GET['categoria'], ripulita e ricordata in un cookie 90 giorni). Mancava la
porta.
- NUOVO
platform/config/diari.json; TOCCATOplatform/boot/env.json libe' OPZIONALE:vrp_load_service_libs()faif ($lib==='') continue.
Una cartolina-scorciatoia senza codice suo e' prevista dal caricatore.
- ⚠️ l'url tiene il
.phpapposta:vrp_current_service()confronta lo
SCRIPT con l'url della targa PIU' LO SLASH, quindi un url che finisce in
.php non puo' vincere quel confronto e la cartolina non ruba al VRV le sue
pagine. Verificato sull'HTML servito.
6. La nota della Radio VV
Il bottone lo tinge di verde button{background:#219324} e l'emoji 🎵 e' verde
chiaro: verde su verde. Essendo un'emoji A COLORI non obbedisce a color, e un
filtro sul bottone avrebbe scurito anche il fondo -> la nota sta in uno span suo
e il filtro prende solo lei.
Collaudo: php -l su tutti i PHP toccati, node --check sui js, graffe dei
css bilanciate, nessuna variabile CSS usata senza dichiararla. Da HTTP:
musica_picker.php da non loggato 403, post_musica.php e musica_upload.php
"Devi essere loggato", home 200 con la cartolina e il link giusto, filtro ospite
8 post su viaggi (34 senza filtro), e su diario_lista_guest.php lo sfondo
iniettato resta quello del VRV. Il resto l'ha collaudato vitti dal vivo, pezzo
per pezzo.
Backup: admin.php.bak_..._darkassaggio,
modifica_post.php.bak_..._darktelaio, modifica_post.js.bak_..._ddoppio,
vrv-modifica-post-barra.js.bak_..._musica, theme-default.css.bak_..._wallpaperconfig,
header_badge.php.bak_..._nota, env.json.bak_..._diari.
Aperto: completare il dark sulle altre pagine di lavoro (admin_post,
admin_utenti, admin_config, diario_lista_admin, profilo); il JukeBox - una
pagina di piattaforma per suonare tutta la musica del VRV, da decidere se
assorbe la Radio VV (che suona shared/mp3, un'altra cartella); la toolbar di
TinyMCE lasciata chiara; il pruning del backup sacro che si ferma su una
directory non vuota.
2026-09-06 - Playing list del JukeBox, il MOTTO, e la soffitta degli avatar
Perche': tre richieste di vitti nella stessa mattina, che a guardarle bene
fanno una domanda sola - dove si mettono le cose di un utente che cambiano
nel tempo:
1. *"per il jukebox, le playing list, pensavo di aggiungere una tabella, legata
all'user_id; ogni loggato avrebbe le sue playing list, gli ospiti potrebbero
suonare la list di vitti, o di Elena, o farne una propria, ma non salvarla!"*
2. *"per l'avatar utenti... conservare gli avatar precedenti, oltre quello
corrente"*
3. *"aggiungere un motto per utente, anche quello con lo storico"* - il suo:
Vitti, Uto, Ubi, Elena, Seneca & Frida, nihil separatum est
Disegno e ragioni: VRP_B100_motto_avatar_playlist.
La forma, in una riga: il CORRENTE sta in users (avatar, motto), i
PRECEDENTI in user_storia. Cosi' **nessun lettore di oggi si e' dovuto
accorgere del cambiamento**, e lo storico e' un registro che si appende e non si
rilegge mai per sapere "chi sono adesso" - quindi non puo' nascere una seconda
verita'.
DDL (lanciata da vitti come root, docs/VRP_ddl_20260906_motto_avatar_playlist.sql):
users.motto VARCHAR(255), tabella user_storia (una sola per avatar e motto:
e' lo stesso mestiere), tabella jukebox_playlist.
📌 vrp_auth_app non ha CREATE/ALTER e il root di MariaDB non e' unix_socket:
vuole la sua password (cassaforte KeePass). Il sudo mysql che avevo suggerito
dava lo stesso errore - il sudo qui non c'entra niente.
Fatto:
platform/lib/vrp_auth.php:mottoaggiunto alla SELECT di
vrp_current_user() (una parola, ed e' in mano a tutti: badge, profilo,
utenti). Due funzioni nuove: vrp_user_ritira() (manda in soffitta il
valore di prima; non scrive niente se era vuoto o se non e' cambiato - lo
storico racconta i CAMBI, non i salvataggi) e vrp_user_storia() (i
precedenti, dal piu' recente; torna vuoto invece di cadere se la tabella non
c'e').
platform/pages/jukebox.php: le playing list. Due azioni sul canale
?vx=action gia' pronto del motore esploratore (niente file ajax nuovo),
la lista renduta nella pagina (un ospite non fa nemmeno una chiamata per
ascoltare la lista di Elena), e in console una tendina accanto alla coda.
I brani stanno in un TEXT separati da |, come gia' fa post.musica: una
playlist e' un elenco ordinato di brani, non merita ne' un formato nuovo ne'
una tabella di righe. Si salva LA CODA, non la selezione: la coda gia' e'
l'elenco ordinato che si sta ascoltando.
Un clic su una lista ACCODA, come un clic su una mattonella: stessa
grammatica, e due liste si attaccano una all'altra.
🔑 Le chiavi si filtrano contro i file che esistono davvero, in lettura e
in scrittura: un brano cancellato dalla cartella non rompe la coda di nessuno,
e un ../ che arriva dal browser non entra nel database.
platform/pages/profilo.php: campo Motto (max 160 - un motto che non sta
nel badge non e' un motto), i motti di prima in elenco, e **"Le foto di
prima"** con il bottone Riprendi.
⚠️ L'avatar non si sovrascrive piu': il file si chiama u<id>_<data>.<ext>
e quello vecchio RESTA sul disco. Era proprio il @unlink a rendere
impossibile conservare i precedenti. I vecchi u<id>.<ext> restano validi:
il nome sta scritto in users.avatar e nessuno lo ricalcola - **zero
migrazione**.
🔑 Riprendere una foto e' uno SCAMBIO, non una copia: un solo UPDATE, il
disco non si tocca. Il nome arriva dal form, quindi si ricontrolla contro la
soffitta VERA di quell'utente: senza quel controllo uno poteva prendersi
l'avatar di un altro scrivendone il nome.
platform/pages/users.php: motto nell'elenco e nel form, la soffitta in
sola lettura (le foto le riprende il padrone dal suo Profilo), e la
cancellazione di un utente ora porta via anche le sue foto di prima - i
nomi si raccolgono PRIMA della DELETE, perche' il cascade si porta via le
righe di user_storia e con loro l'unico posto dove c'era scritto come si
chiamavano.
- La pagina Utenti ha DUE FACCE (vitti: *"gli admin possono tutto,
modificare se stessi e gli altri; i loggati possono modificarsi, ma potrebbero
vedere gli altri"*): l'elenco ("Chi c'e' in casa": foto, nome, motto, ruoli)
lo vede chiunque sia loggato; creare/modificare/cancellare resta agli admin
dalla LAN. La guardia e' uscita dalla rotta (public/index.php: users
non e' piu' lan+admin) ed e' entrata nella pagina, dove sa a quale meta'
si applica. Il lucchetto sta sul POST, non sui bottoni: un form nascosto non
e' un form che non si puo' mandare.
- Il motto nell'hover (vitti: *"in hover sul badge, anche nella lista
utenti"*): vv/lib/header_badge.php (badge del VRV) e public/index.php
(barra di piattaforma). Se il motto non c'e', l'etichetta resta quella di
prima: un title vuoto sarebbe un passo indietro.
- CSS:
.jb-liste*invrp-jukebox.css,.vrp-sof-*e.vrp-usr-mottoin
vrp-lab.css. Le foto della soffitta sono quadrate con l'angolo tondo, non
cerchi: la forma dice da sola che non e' quella in carica.
Collaudo (tutto dal vivo su lab, con un utente usa-e-getta poi cancellato):
- JukeBox, 11 prove: brano finto e
../scartati; stesso nome = aggiorna e non
duplica; nome vuoto, coda vuota e CSRF storto respinti; **ospite: vede le
liste di tutti, non puo' salvare**; cancella solo il padrone (o un admin), e
chi non e' padrone si sente dire di no.
- Motto/avatar, 15 prove: primo motto -> 0 righe di storia (giusto: non c'era
niente da ritirare); secondo motto -> il primo in soffitta; due foto -> tutte
e due sul disco, la vecchia in soffitta; Riprendi -> scambio corretto;
Riprendi con un nome inventato (u1.jpg, la foto di vitti) -> negato;
admin che cambia il motto di un altro -> ritirato_da = l'admin; utente
cancellato -> file spariti (2 su 2) e nessuna riga orfana.
- Elenco utenti: da loggato-non-admin niente Cancella, niente form, niente
email, e un POST forzato risponde *"Da qui puoi solo guardare"* con vitti
(id 1) ancora al suo posto.
Aperto: il manuale (JukeBox e Cestino non ci sono ancora); e il **ruolo
esterno**, che vitti aveva messo in attesa e che tocca le stesse tabelle.
2026-09-06 (2) - IL JUKEBOX NON MOSTRAVA I BRANI, e la cartella sembrava vuota
Come e' uscito: vitti dall'amaca, sul tablet: *"non vedo lo scaffale!"*, con
uno screenshot. Sullo schermo: mang3:8443/?p=jukebox, loggato come
UbiOneKenobi, la barra e lo sfondo a posto, e in mezzo "0 brani" e
"Cartella vuota.", con l'albero a sinistra vuoto.
📌 Da riga di comando non si vedeva, e per un motivo preciso: io il curl lo
facevo su ?p=jukebox&vx=scan, cioe' mettendoci il p a mano. Il browser no.
La causa: vrp-explorer.js costruiva l'URL degli endpoint cosi':
base() + '?vx=' + verbo // base() = cfg.endpoint || location.pathname
Il JukeBox pero' non e' un file .php suo: e' una ROTTA del front controller
(?p=jukebox). Quindi location.pathname vale /, l'URL diventava
/?vx=scan&node= - il ?p=jukebox si perdeva per strada - e la chiamata
finiva sulla HOME, che rispondeva HTML invece di JSON.
readJsonSafe trasformava il pasticcio in {ok:false, err:...}, ma openDir
non guardava ok: senza items la griglia scriveva "Cartella vuota." e
l'errore spariva senza lasciare traccia.
🔑 Gli altri cappelli non ci erano incappati perche' sono tutti file a se'
(bg_picker.php, media_popup.php, share/index.php, gestore_foto.php):
per loro location.pathname e' gia' l'indirizzo giusto. Il JukeBox e' **il
primo esploratore che vive su una rotta a query string**.
Fatto:
public/js/vrp-explorer.js:vxUrl(verbo)al posto del'?vx='cablato -
se l'endpoint ha gia' un ?, attacca con &. La usano tutti e tre i canali
(api, action, upload). Il caso endpoint = '' resta identico a prima.
public/js/vrp-explorer.js:openDirora guardares.oke alza un toast
appiccicato con l'errore. *Una lettura fallita non deve somigliare a una
cartella vuota*: chi guarda non ha modo di distinguere "qui non c'e' niente"
da "non sono riuscito a chiedere", ed e' esattamente l'ora che ci si perde.
platform/pages/jukebox.php:
vrp_explorer_render($provider, ['endpoint' => '?p=jukebox']). Relativo e non
assoluto, cosi' vale sia su /?p=jukebox (porta 8443) sia su
/lab/public/?p=jukebox.
Collaudo: riprodotto il guasto (/?vx=scan -> 4.491 byte di HOME, non
JSON), poi Chrome headless su ?p=jukebox: le due mattonelle ci sono, con
"2 cartelle · 0 brani", "30 brani" e "23 brani", e l'albero pieno. Salvata una
playing list con l'URL nuovo (ok) e riprovata con quello vecchio (torna HTML,
come deve). Gli altri tre esploratori riprovati e intatti.
La lezione: *un URL costruito a pezzi non e' un URL finche' non lo si guarda
finito.* Il '?' cablato dava per scontato che l'endpoint fosse un percorso
nudo, e per otto pagine su nove era vero. E soprattutto: **avevo provato
l'endpoint, non il gesto**. Il curl passava perche' scrivevo io l'indirizzo che
il browser non avrebbe mai scritto.
2026-09-09 - INTERPRETE: parlare con il vicino di piazzola
Perche': vitti, dal campeggio di Kanegra: *"ho una richiesta da un vicino al
campeggio, posso usare il VRV per tradurre delle frasi dal tedesco
all'italiano?"*. Il motore c'era gia' (Gemini, shared/bin/translate_lib.php),
ma era tarato sull'opposto del bisogno: POST interi, in HTML, a pezzi, con la
barra di avanzamento e il salvataggio come post figlio. Qui serve una frase,
subito, su un tablet che passa di mano.
Fatto:
platform/pages/interprete.php(nuova). Un form normale: si scrive, si
tocca Traduci, la pagina torna con la risposta. Niente ajax e niente
JavaScript - la traduzione di una frase sta sotto il secondo, e una pagina che
si ricarica funziona anche con una riga di campo. Livrea di piattaforma
(vrp-fixed-head / vrp-fixed-body), form dentro una vrp-cfg-section,
testo della traduzione a 22px: si legge al sole, spesso lo tiene in mano
l'altro.
- Le lingue non sono quelle del diario.
meta/translation/languages.json
sono le 7 in cui si PUBBLICA; qui ne servono 21, quelle che si incontrano in
un campeggio d'Europa (croato, sloveno, polacco, ceco...). Si consegnano al
motore col NOME INGLESE: lang_name() non riconosce il nome e lo ripassa
tale e quale al prompt. Passare un codice sconosciuto (hr) sarebbe stato
l'errore: il commento di lang_name() (01/09) dice che allora il modello
RICOPIA invece di tradurre.
- Riconoscimento automatico come prima voce di "Dalla lingua": da una frase
scritta da un altro non sempre si sa nemmeno che lingua sia. E' una riga di
prompt (the language of the text (detect it)), non una libreria.
- Il verso si gira con un tasto (submit, non JavaScript). Lingue scelte e
ultime 12 battute stanno nella sessione: e' un discorso, non un documento.
public/index.php: rottainterpretee un flag nuovoauthnel filtro
della barra (if (!empty($r['auth']) && !$vrp_cu) continue;) - in barra solo
per chi e' entrato. Il lucchetto vero resta dentro la pagina, come in
fogli.php: ogni frase e' una chiamata all'API che si paga.
LA TRAPPOLA DELLA GIORNATA - una frase nuda non e' HTML. Al banco, la
stessa frase tedesca tornava indietro IN TEDESCO. Non e' un caso: il prompt di
translate_html() finisce con HTML:, e sotto ci finiva una frase nuda; il
modello non trovava niente da fare e ricopiava. Misurato, non supposto - stessa
frase, quattro tentativi per parte:
"Guten Tag! Duerfen wir den Hund an den Strand mitnehmen?"
nuda -> 4 copie su 4
dentro <p>...</p> -> 0 copie su 4
Quindi le righe si impacchettano in paragrafi all'andata e si spacchettano al
ritorno. E il guardiano di serie non aiutava: quota_copiata() sotto le dodici
parole si arrende e torna 0.0 - e qui le frasi corte sono la regola, non
l'eccezione. Aggiunta vri_e_una_copia() (confronto alla buona: via
punteggiatura e spazi, tutto minuscolo) e fino a tre tentativi.
Collaudo: banco di prova che rende ?p=interprete come lo renderebbe il
front controller, poi Chrome headless a 1280x900 e a 800x1000 (tablet).
Provati DE->IT, IT->DE su due righe, e croato con riconoscimento automatico:
*"Dobar dan, imate li slobodno mjesto za sutra?"* -> *"Buongiorno, avete un
posto libero per domani?"*. La voce Interprete c'e' in barra ed e' accesa.
⚠️ Non provato dal vivo il cancello: le sessioni di FPM stanno in un /tmp
privato (PrivateTmp=yes), quindi al banco la sessione e' finta. Il giro
login -> pagina lo fa vitti.
2026-09-09 (2) - FOGLI piu' leggibile, e il bottone che non esisteva
Perche': vitti, 08/09 sera: *"la pagina FOGLI, renderla piu' leggibile - e'
quella di CODE per Elena"*. Guardata a video (Chrome headless) prima di toccare
una riga, e il guaio si e' visto subito: l'elenco era una **tabella nuda
appoggiata sulla fotografia della landing**. I nomi dei file finivano sopra il
mare e le facce del Vulcano; e le tre colonne davano lo stesso peso al nome e
ai kB, che a chi cerca un documento non servono quasi mai.
Fatto (platform/pages/fogli.php):
- L'elenco sta in un pannello (
vrp-cfg-section, lo stesso di
Configurazione): stacca dalla foto e si legge. Dentro ci e' entrata anche la
riga di spiegazione, che prima era sulla foto.
- Una riga per documento, alta 12px di padding - si centra col dito -, con
icona per tipo, il nome in 18px e sotto, in piccolo, quando e' stato
toccato / che tipo e' / quanto pesa. Tutta la riga e' il collegamento.
- La data come la direbbe una persona: «oggi alle 21:17», «ieri alle...»,
«il 07/09 alle...» e solo oltre la settimana la data piena (vrf_quando()).
- L'estensione resta a vista, ma in sordina (
.vrf-est): in casa c'e' un
kanegra.rtf e un kanegra.xlsx, e togliendo il codino sembravano lo stesso
documento. Provato: sono due righe che si distinguono a colpo d'occhio.
- Le quattro funzioncine di vetrina (
vrf_icona,vrf_tipo,vrf_titolo,
vrf_quando) stanno nella pagina, non in lib/vrf.php: li' c'e' il
mestiere (percorsi, gettoni, WOPI), qui come si scrive a video.
- La barretta del documento aperto e' diventata un nastro suo
(.vrf-barretta), col nome del documento in grande: prima erano tre pezzi di
testo appoggiati sulla foto.
LA SCOPERTA: .vrp-btn NON ESISTEVA. Cinque punti la usavano (quattro in
fogli.php, uno in users.php) e in vrp-lab.css non c'era: quei bottoni
cadevano sul disegno di serie del browser. Il «Torna all'elenco» era un **link
blu sottolineato in mezzo alla fotografia**, e i due «Nuovo...» erano i
bottoncini grigi di sistema. Nessuno se n'era accorto leggendo il PHP - la
classe c'era scritta, sembrava a posto. Definita ora in vrp-lab.css
(.vrp-btn + .vrp-btn--ghost, stesso vestito di .vrp-cfg-btn): si aggiusta
anche users.php senza toccarlo.
Tolto anche il glifo ⛶ dal tasto «Tutto schermo»: meta' dei caratteri di
sistema non ce l'hanno e diventava un quadratino. Parole e basta.
Collaudo: Chrome headless a 1280x900, 820x760 e sulla vista a documento
aperto. La cornice dell'editor al banco resta grigia (il container non serve
una pagina aperta da file:// con una sessione finta): quello che si e'
verificato qui e' l'IMPAGINAZIONE, il giro con Collabora vivo lo fa vitti.
2026-09-09 (3) - Interprete: il MICROFONO e la VOCE
Perche': vitti, subito dopo aver provato l'Interprete dal tablet: *"puo'
usare il microfono del tablet o del pc per il riconoscimento del linguaggio
parlato?"*. Si': lo fa il browser, non noi. SpeechRecognition per ascoltare,
speechSynthesis per parlare - in casa in Chrome e in Safari da anni. Zero
librerie, zero chiamate in piu' all'API.
Fatto (platform/pages/interprete.php):
- 🎤 Parla: si detta la frase invece di scriverla. Quel che il
browser capisce finisce subito nella casella, anche prima di essere
definitivo: chi parla vede che il tablet lo sta seguendo, invece di guardare
un bottone rosso e sperare. Il bottone diventa rosso e batte mentre ascolta.
- Finito di parlare, traduce da solo. Sul materassino non si tocca due
volte lo schermo. Se non e' arrivata nessuna parola, non parte niente.
- 🔊 Ascolta su ogni battuta: legge la traduzione ad alta voce nella
lingua d'arrivo. Al campeggio vale piu' del testo - il vicino la SENTE.
vri_bcp47(): il motore di traduzione lavora coi nomi inglesi
("German"), il microfono e la voce vogliono de-DE. Senza la nazione, il
riconoscimento ascolta in italiano anche mentre parla un tedesco. Le opzioni
della tendina portano il codice in un data-bcp, cosi' cambiando lingua
cambia orecchio senza ricaricare.
- Con «Riconosci da sola» il microfono si spegne e lo dice («Per dettare,
scegli la lingua di partenza»): non c'e' lingua da dare all'orecchio.
- Se il browser non ha queste cose, i bottoni non compaiono e la pagina
resta quella di prima: si scrive e si legge.
⚠️ IL MICROFONO VUOLE UN CERTIFICATO BUONO. Il browser lo concede solo a
una pagina davvero sicura: da casarosini.duckdns.org c'e' Let's Encrypt e va;
aprendo la stessa pagina come mang3 (autofirmato) resta muto. Il messaggio
d'errore lo dice con parole sue, invece di lasciare pensare a un guasto.
⚠️ submit() da codice non manda il nome del bottone premuto, quindi
azione non arriva: la dettatura funziona perche' il valore di ripiego lato PHP
e' 'traduci'. Chi cambiasse quel ripiego spegnerebbe la dettatura senza
accorgersene - c'e' scritto nel commento, sopra la riga.
Collaudo: il microfono vero non si prova da qui, ma il GESTO si'. Banco con
un SpeechRecognition finto e uno speechSynthesis finto (montato con
defineProperty, che su window l'assegnazione diretta non attacca), e il
giro completo verificato: bottone visibile, spento con «Riconosci da sola» e
col perche' nel title, rosso mentre ascolta, il provvisorio che compare nella
casella, l'invio automatico con lang=de-DE alla fine, il giro a vuoto che NON
spedisce, e l'Ascolta che parla de-DE scegliendo la voce tedesca. Piu' il
solito Chrome headless per l'impaginazione.
2026-09-09 (4) - FOGLI grigi dal tunnel: NON e' il tunnel, e' il CERTIFICATO
Il sintomo (vitti, dal campeggio, sul wg0 di mang8): *"non mi fa vedere il
contenuto dei file, ma mi sembra che lo avevamo gia' messo a posto!"*. La
cornice si apre, la barretta c'e', dentro resta grigio.
Cosa dicono i log (nessuna supposizione - access.log, 09/09 12:58:59,
sorgente 10.9.0.5, referer https://mang3:8443/...):
"GET /lab/public/?p=fogli&apri=Nuovo testo....docx" 200 9145
"POST /browser/cc5614c67e/cool.html?WOPISrc=..." 200 10749
"GET /wopi/files/Nuovo testo....docx?access_token=..." 200 339 (COOLWSD)
...e poi PIU' NIENTE.
Manca la riga GET /cool/ws?WOPISrc=... e manca /wopi/.../contents. Il 07/09,
quando funzionava, quelle due righe ci sono tutte e due - e il **filo di dati
partiva dal nome PUBBLICO**: la sorgente e' 93.55.232.194 (l'IP di casa, il
giro dal router), non un indirizzo di LAN.
La lettura. La pagina, la cornice, il gettone e la CheckFileInfo sono tutti
a posto: la piattaforma ha fatto il suo. Quello che non e' mai partito e' il
websocket dell'editor, che va allo stesso nome da cui si naviga - qui
wss://mang3:8443/cool/ws. Su mang3 il certificato e' quello fatto in casa:
la PAGINA si apre lo stesso (si da' l'ok una volta e il browser se lo ricorda),
ma per il filo di dati quel permesso non vale e la connessione muore senza
un rumore. Ecco il grigio.
Quindi: non c'entra WireGuard. La correzione dell'08/09 (vrp_is_lan() che
riconosce 10.9.0.) e' un'altra cosa e sta in piedi. Qui conta **quale NOME si
scrive nella barra**, non da dove si arriva: dal nome pubblico funziona anche
dentro il tunnel, da mang3 no - e non funzionerebbe nemmeno stando in casa.
Fatto (platform/pages/fogli.php): vrf_altra_porta(). Quando si apre un
documento navigando su un nome diverso da quello pubblico della config, sopra
la cornice compare una riga rossa che lo dice **e offre il collegamento allo
stesso documento sul nome pubblico**. Non aggiusta niente: fa in modo che chi
guarda capisca, e che la prova sia un clic.
✅ CONFERMATO dal vivo (vitti, 09/09): aperto dal nome pubblico, *"si
funziona, i fogli si vedono!"*. La diagnosi dai log reggeva.
⏳ La cura vera e' un'altra e va decisa: o si fa fidare le macchine di casa
del nostro certificato, o si trova il modo di avere un certificato buono anche
per i nomi locali. Fino ad allora, per i Fogli si usa il nome pubblico.
Anche: il tasto «Tutto schermo» ha di nuovo il suo glifo, come lo
voleva vitti - ma disegnato in SVG, non un carattere: i simboli U+26F6 e
U+2924 mancano da meta' dei caratteri di sistema e diventavano un quadratino
(visto stamattina). Quattro angoli che puntano in fuori per allargare, in
dentro per tornare: cambia da solo quando si entra e si esce.
2026-09-09 (5) - JukeBox: il FOGLIO, giu' dalla barra, e la scena fantasma
Perche': la diagnosi era gia' chiusa l'08/09 e diceva che non era il
player: tutte le pagine stanno in .vrp-wrap (760px) e il JukeBox restava li'
dentro - la barra andava a capo e le mattonelle uscivano minuscole.
Fatto:
- Il foglio.
platform/pages/jukebox.php: la testata in.vrp-fixed-head,
tutto il resto in .vrp-fixed-body. Il :has() di vrp-lab.css fa il
resto - 92% di larghezza, alto quanto lo schermo, testata ferma e corpo che
scorre. Come la lista del VRV, non come il gestore foto.
- Lo spazio va DATO. Il solo marcatore non bastava:
.vx-shelle' alto
quanto il suo contenuto, e in mezzo alla pagina restava un buco con la
fotografia sotto. Tre righe in vrp-jukebox.css su una classe NUOVA
(.jb-foglio), non su .vrp-fixed-body: quella e' di tutta la piattaforma,
e riscriverla da una pagina sola la romperebbe altrove - la lezione del
.vv-btn della barra dell'editor.
- Giu' dalla barra (
nav => falseinpublic/index.php). La cartolina in
home c'era gia' (config/jukebox.json): adesso la regola dell'08/09 e'
intera - cartoline = servizi, barra = pagine di piattaforma, tendina = il
resto.
🐞 LA SCENA FANTASMA (trovata guardando la pagina, non cercandola). Il
pannellone della scena - il ♫ grande con «tocca per tornare alla musica da
scegliere» - stava a video anche a musica ferma, sotto l'esploratore.
display: flex vince sull'attributo hidden, e la guardia
.jb-scena[hidden] { display: none } non c'era. Il bello e' che nello stesso
foglio la stessa trappola era gia' stata scritta due volte, per
.vx-shell e per .jb-scena__nota: mancava proprio sulla scena. Il JavaScript
era innocente - scenaAccesa() metteva e toglieva hidden come si deve, era
il CSS a non stare a sentire.
Collaudo: Chrome headless sulla pagina VERA (https://localhost:8443/?p=jukebox
- e' pubblica, non serve sessione) a 1280x860 e 820x900: l'esploratore riempie
la cornice, la console e' incollata in fondo, la scena non c'e' piu' a riposo,
la barra sta su una riga sola e il JukeBox non c'e' - la sua cartolina in home
si', terza dopo «VittRos Viaggi» e «I diari di Elena».
⏳ Resta da provare col suono acceso: che la scena si accenda dopo l'attesa e
riempia la cornice al posto dell'esploratore. Quello lo fa vitti.
2026-09-09 (6) - L'Interprete diventa un SERVIZIO A INVITO (per Lothar)
Perche': Lothar, il vicino di piazzola tedesco, ha accettato di fare da
collaudatore - e un collaudatore vuole il SUO accesso. vitti: *"pero' devo
creargli un account, altrimenti non vede le pagine dell'interprete"*. Vero, ma
un account normale apre tutta la casa: home, JukeBox, il diario da ospite e
l'elenco «Chi c'e' in casa» con i nomi di famiglia. A uno conosciuto stamattina
in campeggio si da' la chiave di una stanza, non del portone.
Fatto (deciso da vitti fra tre strade, ha scelto la chiave su misura):
platform/config/interprete.jsonconruolo: truee il suoroles.
Niente lib: il servizio non ha codice suo da condividere - il mestiere sta
in shared/bin/translate_lib.php, che e' del diario e resta li'.
boot/env.json: registrato fra iservices. Da qui in poi la pagina
Utenti mostra da sola la casella «Interprete» accanto a VRV, VRN, Fogli...
(users.php legge i ruoli dal roles di ogni config: non c'e' una riga da
aggiungere li' dentro).
pages/interprete.php: il cancello passa da «devi essere entrato» a
«devi avere un ruolo per QUESTO servizio», come fogli.php. Resta vera anche
la prima ragione del cancello: ogni frase e' una chiamata che si paga.
public/index.php:nav => false. La mattina l'Interprete era in barra;
la sera e' un servizio, e vale la regola dell'08/09 - cartoline = servizi,
barra = pagine di piattaforma. Sale come cartolina in home.
- Tolto il flag
authche avevo aggiunto stamattina al filtro della barra:
non lo usa piu' nessuna rotta. Un flag che non serve piu' e' un mistero per
chi legge il codice fra sei mesi.
⚠️ Da fare A MANO, e senza non si apre a nessuno: in ?p=users spuntare
«Interprete» per vitti, per Elena e per l'account di Lothar. Finche' la riga in
user_services non c'e', la cartolina non si vede e la pagina rimbalza su
?p=accesso - anche a vitti. Verificato: da ospite risponde 302 verso il
gateway di cortesia, come deve.
2026-09-09 (7) - LA DETTATURA NELL'EDITOR DEL DIARIO
Perche': vitti, dopo il microfono sull'Interprete: *"io parlavo per il
traduttore, ma anche per scrivere i testi non e' male!"*. Nei Fogli non si puo'
(Collabora e' una cornice di un altro dominio: dalla nostra pagina non ci si
scrive dentro), nell'editor del diario si': TinyMCE e' roba nostra.
Fatto (public/vv/js/vrv-carica-tinymce.js, un blocco solo in coda al
setup(editor), piu' vvDetta in barra dopo «undo redo»):
- La punteggiatura si detta. A voce non arriva mai da sola: senza «punto»,
«virgola», «a capo» un post dettato e' un muro di parole. Quindici voci, e
le lunghe si provano prima delle corte - se no *punto interrogativo* diventa
«. ogativo».
- Il provvisorio NON entra nel post. Il riconoscitore si corregge mentre
ascolta; scrivere ogni ripensamento sporcherebbe il testo di Elena. Va in una
strisciolina in fondo allo schermo; nel documento entra solo la frase FINITA,
al cursore, con editor.insertContent.
- Si riaccende da solo. Chrome si ferma dopo qualche secondo di silenzio:
finche' il bottone e' acceso si riparte, se no ci si ferma a meta' racconto.
- Le maiuscole dei nomi restano. Prima abbassavo tutto a minuscolo per
cercare le parole di comando, e «Kanegra», «Seneca», «Lothar» tornavano
minuscoli. Adesso si cerca senza badare al caso (la i nella regex) e il
testo resta com'e'; la maiuscola la si mette solo a inizio frase e dopo . ! ?
🐞 Due difetti trovati AL BANCO, non ragionandoci:
1. trim() mangiava l'a capo iniziale: «a capo, il mare e' bello» finiva
attaccato alla riga di prima. Ora si tolgono solo spazi e tabulazioni ai
bordi, gli a capo no.
2. E poi restava uno spazio davanti al <br>. Il segno e l'a capo non
vogliono niente davanti: sono due modi di sporcare il post.
Collaudo: il microfono vero da qui non si preme, ma il GESTO si'. Banco con
un riconoscitore finto dentro TinyMCE vero (pagina servita sulla 8443, dove
l'alias /libs/ fa trovare l'editor). Verificato: il bottone c'e' in barra
(terzo, dopo Undo e Redo, con la sua etichetta), la striscia si accende, il
provvisorio si vede e il post resta pulito, la frase finita entra al cursore
- «siamo arrivati a Kanegra virgola con Seneca e Frida punto / a capo il mare e'
bello punto» diventa *«Siamo arrivati a Kanegra, con Seneca e Frida.<br>Il mare
e' bello.»* - il riconoscitore riparte da solo dopo il silenzio, e spegnendo il
bottone la striscia sparisce. Schermata presa.
⚠️ Vale l'avviso di sempre: il microfono lo da' solo il nome pubblico. Da
mang3 il bottone c'e' ma il browser non lo concede - e l'errore not-allowed
lo dice in italiano, invece di lasciar pensare a un guasto.
🐞 SUBITO DOPO - «vedo il microfono... ma non mi sembra funzionare». Il
difetto era mio e stava in una SEQUENZA, non in una riga: quando il browser
nega il microfono manda prima onerror e subito dopo onend. L'errore
scriveva la sua spiegazione nella striscia, e mezzo millisecondo piu' tardi
onend la nascondeva (if (!acceso) spegniStriscia()). A video restava un
lampo, e chi guarda conclude giustamente che e' rotto. In piu' due catch
spegnevano tutto in silenzio, senza dire niente a nessuno.
Ora un avviso si prenota sette secondi di video (avvisoFinoA) e finche'
non scade nessuno lo puo' spegnere; i due catch muti parlano; e c'e' un caso in
piu' che in campeggio e' reale, network: il riconoscimento di Chrome gira sui
server di Google, quindi senza internet non c'e' dettatura, il server di
casa non basta.
Provato al banco riproducendo la sequenza vera (onerror + onend di fila): la
striscia resta, e dice *«Il browser non da' il microfono. Apri il diario da
casarosini.duckdns.org (non da mang3), e alla domanda rispondi Consenti»*.
Rifatto anche il giro buono: intatto.
La lezione: *un messaggio d'errore non e' scritto finche' non lo si e' visto
restare a video.* L'avevo provato accendendo e spegnendo, mai fallendo.
2026-09-09 (8) - «Pigio Parla e non compare nessuna riga» - due difetti miei
Il sintomo: vitti, sul tablet, dopo aver messo la spunta del ruolo: *"pigio
il bottone parla, quello con il microfono, ma non compare nessuna riga"*.
Prima cosa: capire QUALE bottone. Nei log (access.log, 14:03-14:06,
sorgente 10.9.0.5) si legge tutto il giro: ?p=users&edit=9, edit=15 - le
spunte del ruolo - e poi ?p=interprete, con referer https://mang3:8443/....
Non ha mai aperto l'editor del diario: il bottone e' quello dell'Interprete
(che infatti si chiama «Parla»; quello dell'editor e' un'icona senza scritta).
Lui l'ha confermato subito dopo. Cercare il difetto nel posto sbagliato sarebbe
costato mezz'ora.
Difetto 1 - IL BOTTONE SPENTO NON DICEVA NIENTE. Con «Riconosci da sola»
(che e' il valore di partenza!) il microfono lo disabilitavo, e la spiegazione
la mettevo nel title: cioe' in un suggerimento che si vede solo fermandoci
sopra il puntatore - e su un tablet non si vede mai. Uno preme, non succede
niente, e conclude giustamente che e' rotto. Adesso il bottone si preme sempre;
se manca la lingua, lo dice nella riga di avviso.
Difetto 2 - E COMUNQUE DA mang3 IL MICROFONO NON C'E'. E' la stessa storia
dei Fogli chiusa oggi pomeriggio: il browser lo concede solo a una pagina col
certificato buono. Lo sapevo e non l'avevo scritto **da nessuna parte nella
pagina**: l'avevo detto a voce in chat, che non e' un posto dove uno va a
cercare. Aggiunta vri_altra_porta() (gemella di vrf_altra_porta() dei
Fogli, e con la stessa chiave origine nel JSON del servizio): navigando su un
nome diverso da quello pubblico compare una riga rossa che lo dice, chiarisce
che scrivere e tradurre funzionano lo stesso, e offre il collegamento alla
porta giusta.
Collaudo: banco con riconoscitore finto - bottone visibile, premibile, e al
clic la riga *«Per dettare scegli prima la lingua di partenza...»*; piu' la
schermata della pagina resa da un nome non pubblico, con l'avviso al suo posto.
La lezione, che oggi si e' ripetuta tre volte: *un impedimento che il codice
conosce va scritto NELLA PAGINA.* Prima il filo di dati dei Fogli, poi l'errore
del microfono che durava un lampo, adesso il bottone spento con la spiegazione
in un tooltip. Ogni volta il codice sapeva perche' non funzionava, e chi
guardava lo schermo no.
🐞 E SUBITO DOPO: «ripete la stessa frase un mucchio di volte». Prima prova
a voce vera della dettatura (vitti, 09/09 sera): il riconoscimento e' buono, ma
ogni frase finiva nel post piu' e piu' volte.
Perche'. In modo continuo e.results **non e' quello che e' cambiato
adesso**: e' l'elenco di TUTTA la sessione, che si allunga a ogni battuta. E
e.resultIndex torna indietro ogni volta che il riconoscitore ci ripensa,
quindi un pezzo gia' definitivo ripassa dal ciclo a ogni evento successivo -
e ogni volta lo riscrivevo. Al banco non si era visto perche' gli avevo dato un
risultato per volta, pulito: proprio la cosa che il browser vero non fa mai.
Fatto: un contatore dei pezzi definitivi gia' scritti (visti), azzerato in
onstart cosi' il riavvio automatico riparte pulito. Un pezzo entra nel post
solo la prima volta.
Collaudo rifatto imitando Chrome sul serio - elenco che cresce, resultIndex
che torna indietro, provvisori in mezzo, e un riavvio automatico:
[f1] · [f1,pv] · [f1,f2] · [f1,f2] · [f1,f2,pv] · riavvio · [f3] produce
*«Inizio. Oggi siamo a Kanegra. Il mare e' calmo. Seconda ripresa.»* - ogni
frase UNA volta, il provvisorio mai.
La lezione: *un banco che dice sempre di si' non e' un collaudo.* Le finte
vanno fatte disordinate come la realta', se no si prova solo il caso che si
aveva in mente scrivendo il codice.
2026-09-13 - TUTTA ALTEZZA: il foglio si allunga, non si allarga
Perche': vitti: *"pensi che riusciamo a fare un tutto schermo, nell'editor
del VRV?"*. E subito dopo, la correzione che ha cambiato il lavoro: *"forse piu'
che tutto schermo, sarebbe meglio tutta altezza, altrimenti si perde la
proporzione della pagina"*.
Fatto (js/vrv-carica-tinymce.js, css/vrv_modifica_post.css):
- Acceso il plugin di serie
fullscreen(stava gia' in/libs/tinymce/plugins/,
come searchreplace stamattina: bastava nominarlo) e messo in coda alla barra
un bottone vvSchermo, che si chiama *Tutta altezza* e si accende quando e'
attivo. Si esce con lo stesso bottone o con Esc; di serie arriva anche la
scorciatoia Ctrl+Shift+F.
- La larghezza resta quella della pagina. Il fullscreen di TinyMCE prende
tutta la finestra, e su uno schermo largo il foglio diventava 1.920px: le
righe lunghissime non dicono piu' niente su come il post si leggera' nel
diario. In .tox-fullscreen il foglio e' fissato a --vrv-page-max (1200px)
e centrato, con un velo scuro ai lati.
resizeEditor()non tocca piu' l'altezza mentre si e' in tutta altezza:
il suo calcolo (fin dove arriva il footer) la riportava corta. Uscendo,
l'evento FullscreenStateChanged gli fa rifare il conto.
Il rosicchio degli !important: il blocco della cornice mette
width/max-width: 100% !important su .tox.tox-tinymce. Senza pari forza il
max-width: 1200px non veniva nemmeno guardato, e la prima schermata l'ha
mostrato subito: foglio largo quanto lo schermo e scivolato mezzo fuori a
sinistra.
Collaudo: banco _mastro_render/tuttaltezza_20260913/prova.html con i CSS
veri della pagina di modifica (rosso, cornice, barra, pie'), e Chrome headless
a 1440x900 prima e dopo: il foglio passa da ~500px a ~850px di altezza e
resta largo 1200, centrato, con testata e pie' coperti.
La lezione: *"tutto schermo" e "tutta altezza" non sono la stessa cosa.* Chi
scrive un post guarda la larghezza per capire dove andranno a capo le righe:
allargare il foglio gli toglie l'unica misura che stava usando.
🔁 Poche ore dopo, dal tablet: «non e' un vero tutto schermo». vitti, con
l'iPad in mano: *"in landscape il tasto e' coperto dai pin di navigazione, e in
portrait non e' un vero tutto schermo, perche' le barre ci sono ancora"*. E la
proposta giusta: *"magari il pulsante toglierebbe tutta la cornice, anche
quella del browser"*.
Fatto (stessi due file):
- Il tasto e' passato in testa alla barra, subito dopo la lente. In coda
finiva sotto .vv-scrollpins, i segnaposto fissi a destra: sul tablet in
orizzontale erano proprio sopra. In tutta altezza i pin ora spariscono.
- Il tutto schermo vero, quello che toglie anche la cornice del browser:
requestFullscreen(). Il tasto adesso fa le due cose in fila - prima il
fullscreen di TinyMCE (che mette la classe sul body, cosi' il
ridimensionamento che segue trova la guardia gia' alzata), poi quello del
browser.
- Chiesto per TUTTA LA PAGINA, non per il riquadro dell'editor - ed e' il
motivo per cui NON si e' usata l'opzione pronta fullscreen_native. A
schermo intero il browser disegna solo il sottoalbero dell'elemento scelto, e
le finestre dell'editor (Foto, Maserati, cerca, codice) vivono in
.tox-tinymce-aux, fuori da quel riquadro: sarebbero sparite.
- Esc rimette a posto tutt'e due. Il tasto Esc lo intercetta il browser, non
la pagina: senza un orecchio su fullscreenchange si restava su un foglio
lungo dentro una pagina normale, con la testata nascosta e nessun modo ovvio
di tornare indietro.
- Dove l'API non c'e' (Safari su iPhone) non succede niente: resta il
tutta-altezza di prima.
Collaudo, stavolta col GESTO vero: Chrome headless pilotato via CDP (Node 26
ha WebSocket di suo, niente puppeteer) - Runtime.evaluate con
userGesture: true perche' il fullscreen lo si puo' chiedere solo sotto un
clic. Premuto il tasto: classe sul body ✓, document.fullscreenElement =
HTML ✓, pin display:none ✓. Aperta la finestra Cerca **stando a schermo
intero**: 480x249 visibili ✓ (era il rischio vero). Poi
document.exitFullscreen(), che e' quello che fa Esc: l'editor esce dal suo
tutta-altezza da solo, i pin tornano, la testata torna al suo posto ✓.
La lezione: *a schermo intero esiste solo il sottoalbero che hai scelto.* Il
riquadro dell'editor sembrava la scelta ovvia ed era quella che nascondeva meta'
dei comandi; la pagina intera costa una riga in piu' e non perde niente.
🐞 E dal tablet vero: «la testata c'e' ancora». Redmi Pad Pro (Android 16,
Chrome), non l'iPad: la cornice del browser spariva ✓ e i pin pure ✓, ma **il
foglio restava incastrato sotto la testata della pagina**, e in verticale la
barra sbordava a destra.
Perche'. Il foglio in tutta altezza e' position: fixed, e un fixed si
ancora al viewport solo se nessun antenato crea un containing block.
L'editor sta dentro `<article class="vv-card vv-edit vrv-panel
vrv-editor-wrap">, e .vrv-panel (in vrv_layout.css`) ha
backdrop-filter: blur(4px). Tanto basta: il foglio restava appeso al
pannello - partiva sotto la testata e teneva la larghezza della pagina (1200)
anche su uno schermo da 840.
Al banco non si era visto per un motivo preciso: **avevo riprodotto la cornice,
non la catena degli antenati**. Messo il pannello vero attorno all'editor, il
guasto e' comparso al primo colpo, negli stessi numeri del tablet: foglio a
y=106, alto 804 su 1300 di schermo.
Fatto: mentre si sta in tutta altezza, su .vrv-panel/.vv-card/
.vrv-editor-wrap si spengono backdrop-filter, filter e transform. Dopo:
0, 0, 840x1300 in verticale e 40, 0, 1200x800 in orizzontale - pieno sopra
e sotto, largo quanto la pagina e centrato.
La lezione: *un banco che copia il pezzo ma non i suoi antenati prova meta'
del problema.* E: un backdrop-filter non e' una decorazione, e' un
containing block - la stessa famiglia di guai dei blur sugli sfondi.
E un terzo che stava fisso in alto a destra. Screenshot delle 22:45, il post
183 a tutto schermo con la vignetta del Maserati: bello, ma il cartellino
#global-user-badge (*"UbiOneKenobi (editor)"*, con la nota della musica e
l'avatar) copriva Grassetto, Corsivo e Sottolineato. Sta position: fixed
con z-index: 2147483647 - il massimo numero intero a 32 bit, cioe' sopra
qualunque cosa, foglio compreso. In tutta altezza sparisce anche lui, con i pin:
mentre si scrive non serve.
2026-09-14 - LE FOTO DEL POST PASSANO DAL RIDIMENSIONATORE
Perche': vitti, da Basovizza: *"i disegni del fumetto di Eleonora dove
finiscono? ... quello che volevo evitare e' di avere tante copie diverse delle
stesse foto/disegni. img.php funziona su quella cartella?"*.
La risposta era no. Le basi consentite di img.php erano quattro -
leNostre, maps/img, shared/post_wallpaper, shared/foto_ospiti - e la
cartella dove vivono le immagini dei post non c'era. Quelle immagini le serviva
nginx dirette, a piena risoluzione: aprire il post 183 voleva dire
scaricare 16 MB di originali, telefono compreso.
Da dire subito, perche' era la sua paura: copie doppie non ce n'erano. In
post_183 ci sono 25 file e nessuna variante _800/thumb. Il rischio era
l'opposto - il giorno che fosse servita una miniatura, senza img.php
l'unica strada sarebbe stata fare un doppione.
Da sapere anche: vv/foto non e' una cartella, e' un LINK a
/srv/http/vittrosviaggi_shared/foto. Quindi i disegni stavano gia' sul
ripiano giusto - fuori da leNostre e fuori dall'albero del codice - solo che
nessuno lo sapeva guardando l'indirizzo.
Fatto, in due passi.
1. La base nuova, in maps/img.php E nel gemello lab/public/media/img.php
(sono gia' segnalati l'uno nell'altro: si toccano insieme):
'/vittrosviaggi_shared/foto' => '/srv/http/vittrosviaggi_shared/foto'.
Cartella NOMINATA, non tutta shared: li' dentro stanno anche i segreti.
2. L'uploader restituisce l'indirizzo servito e non piu' quello diretto
(ajax/handle_upload_media.php): /maps/img.php?w=1920&src=..., la stessa
riga che media_popup.php usa gia' per le foto di leNostre. Il percorso si
scrive per esteso (e' il link, non BASE_URL) e se il link un giorno non ci
fosse, un realpath se ne accorge e si torna all'indirizzo diretto invece
di servire un 404. /maps/img.php e non /media/img.php perche' e' quello
che il VRV usa gia' dappertutto ed esiste anche fuori dalla piattaforma.
Collaudo: striscia1_FINALE.jpg, 1.826.632 byte di originale ->
399.719 a w=1280 e 76.092 a w=480: ventiquattro volte piu' leggero alla
misura del telefono. Il gemello di piattaforma risponde byte per byte uguale.
Provata anche la porta sbagliata: ?src=/vittrosviaggi_shared/secrets/...
risponde 404. Poi l'indirizzo ESATTO che l'uploader ora scrive, calcolato
con le sue stesse righe e chiesto al server: 200, image/jpeg.
⚠️ Il giro completo dall'editor (scegli file -> compare nel post) lo fa vitti:
lo sportello vuole la sessione.
Attenzione per il futuro: nessuno, nel VRV, legge l'HTML del post per
cercarci dentro le immagini (nessun preg_match su src=, nessun DOM). Il
giorno che qualcosa lo facesse - il PDF, una copertina - dovra' sapere che
l'indirizzo puo' essere un img.php?src=, non solo un percorso nudo.
La lezione: *una cartella raggiungibile non e' una cartella servita.* Il
link c'era, l'indirizzo funzionava, le immagini si vedevano: sembrava tutto a
posto, e intanto ogni lettura del post si portava via i megabyte interi.