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:

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.

barra; users/config/sessioni/log con nav=false (fuori dalla barra,

raggiunte dal cruscotto). config RI-AGGANCIATA (era sganciata dal 03/07).

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.

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.

.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).

'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.

(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:

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.

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):

intatto = backup). Diff confermato identico prima di lavorarci.

extensions/modes), registrata in env.json services -> compare e si

edita da ?p=config. Politica d'accesso nella targa: lan:true, admin:true.

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'.

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.

+ 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.

rinominato vrw_/VRW_, percorsi dalla targa (VRW_STAGING_DEFAULT ecc.) e

VRW_DEST_BASE_DEFAULT = VRP_MEDIA_ROOT (leNostre dalla piattaforma).

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...").

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:

spaziatura. Tolto il newline nel loop.

ri-aggiorna ogni 3s e scende in fondo (follow=1 in URL, piccolo JS a fondo

pagina); spento -> si ferma (per ragionarci sopra).

testo bianco risalta sullo sfondo; header sticky piu' opaco.

(declutter); Righe/Applica/Reimposta restano visibili sotto.

Sessioni/Log).

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).

non un servizio) ma VittRos Share; corretto VRP_acronimi.md.

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:

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>.

Database aperte, Percorsi chiusa perche' sola lettura). Le sezioni collassate

restano nel form e continuano a inviare i loro campi al POST.

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).

log): il ritorno resta la voce in barra. La testata piu' corta fa entrare 8

utenti senza scrollbar.

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.

?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.

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.

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.

-> 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:

dai riquadri. Ora Utenti/Config/Sessioni/Log si aprono nella STESSA scheda; si

torna dalla voce "Amministrazione" in barra. Fine proliferazione.

.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.

(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).

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:

next=<rotta>`;

admin/auth:required ora vanno al gateway con next=REQUEST_URI (il ramo

lan resta 403 secco).

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.

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:

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').

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.)

body` (riusa la macchina wide). Come admin/config/utenti/sessioni/log.

o dell'unica scheda che collassava tutto:

Config e' PIATTAFORMA: spostata dal cruscotto admin ALLA barra (nav=true),

tolta dai riquadri di admin.php.

scheda condivisa (target="vr-servizi").

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.

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:

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.

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).

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:

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.

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).

(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).

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.)

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:

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).

(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).

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:

testata (vitti: "poco visibile ma una volta imparato ok" - lasciato, si vedra' con

Elena).

nav + pannello pecore) -> resta solo "↑ Torna al Wizard".

Gestore foto). "Import da staging" e "Converti HEIC (manuale)" spostati in

"Vecchi strumenti".

overflow) + checkbox "seleziona tutti" (JS). Non allunga piu' la pagina.

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):

repeat(auto-fill,minmax(180px,1fr))), box piu' alto (poi 460px).

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:

Sorgente/Destinazione.

anteprima si toglie ((!$didImport && $dryRun)), esclusioni preservate (JS

NC_EXCL_INIT seed).

l'handler per-foto via dispatchEvent).

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:

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().

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.

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.

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.

(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).

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.

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.

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

Fatto:

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).

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).

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.

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

centinaia di foto gia' archiviate. Verificati 18/18, byte identici.

Il codice (3 file + la targa)

FileCosa
platform/lib/vrw.phpVRW_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.phpbottone 🧹 sulla riga del giorno, pannello del ricontrollo, conferma, registro azioni
platform/config/vrw.jsonnextcloud.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)

creato -> cancellato -> nel cestino -> ripristinato -> ripulito.

degli altri (sez. 4b del progetto: risposta definitiva, servono due chiavi).

successo** (404 trattato come ok: qualcuno l'ha gia' tolto).

manca, il bottone non compare proprio.

I 34 filmati veri nel cestino: intatti.

⚠️ 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

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?»

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.

(legge un giorno in mappa nome|dimensione), e vrw_nc_esame_giorno() con il

parametro $registro in coda (default [] -> chiamate vecchie invariate).

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)

casoesito
mai importato, non in archivioresta (giusto)
nel registrosi pulisce
dopo rinomina/spostamento/cestino (indice archivio VUOTO = caso peggiore)si pulisce ancora
non nel registroresta 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.

filenel registro?esito
a.jpgsi'si pulisce
b.heicsi'si pulisce ← prima restava fuori
c.heicnoresta (entra convertita in jpg)
d.mov + d.jpg gemellasi'resta (vince il punto 1)
e.txtnoresta (non e' foto ne' filmato)
f.jpgnoresta (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

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:

Due versi: con lat/lon dichiara, con togli:true scorda.

nascoste_load/save.

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.

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):

#provaesito
1parcheggio nuovo per 3 fotovoce creata, nome preso dalla cartella
24a foto sulle STESSE coordinatefusa, nessun doppione
3foto di Colonnata spostata sul nuovoColonnata 4->3, nuovo 4->5
4lat:999 e lat:0rifiutate tutt'e due
5🅿️✖ su una foto solatolta, la passeggiata resta
6🅿️✖ su tutte le foto di ColonnataColonnata 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:

lat_mano, lon_mano DECIMAL(10,7) NULL + lat_eff, lon_eff

GENERATED ALWAYS AS (COALESCE(lat_mano, lat)) VIRTUAL. Solo aggiunte.

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).

(⚠️ PRIMA di ln_localita_fill, se no i nomi rinascono sull'EXIF), e il

bottone 📍 di maps/convalida_foto.php.

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).

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).

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.

suoi nomi legherebbe due servizi che oggi non si conoscono.

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:

non basta;

salire di specificita';

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;

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.

bg_picker.php): provider di 30 righe, la cartella e' piatta

Scelte:

si legge l'elenco dall'alto in basso e quello si salva;

avrebbe fatto sparire tutti gli Aizpute, che sono .m4a;

visibili marcati "non trovato" invece di svanire in silenzio;

update_sfondo.php che ha i controlli commentati;

cambierebbe la musica dei post che usano quel nome senza che nessuno se ne

accorga.

Corretti dopo le prove di vitti:

play(), e fra il clic e la marcatura il pannello non sapeva di star

suonando. Ora si scrive PRIMA di chiamare play();

piena"): ora nella tendina c'e' solo la scaletta;

(decorazione, non si preme) messo OLTRE al pulsante vero. Tolto;

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.

Una cartolina-scorciatoia senza codice suo e' prevista dal caricatore.

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:

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').

?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.

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.

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.

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.

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.

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):

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.

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.

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:

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.

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.

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:

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.

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.

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.

ultime 12 battute stanno nella sessione: e' un discorso, non un documento.

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):

Configurazione): stacca dalla foto e si legge. Dentro ci e' entrata anche la

riga di spiegazione, che prima era sulla foto.

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.

«il 07/09 alle...» e solo oltre la settimana la data piena (vrf_quando()).

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.

vrf_quando) stanno nella pagina, non in lib/vrf.php: li' c'e' il

mestiere (percorsi, gettoni, WOPI), qui come si scrive a video.

(.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):

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.

volte lo schermo. Se non e' arrivata nessuna parola, non parte niente.

lingua d'arrivo. Al campeggio vale piu' del testo - il vicino la SENTE.

("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.

scegli la lingua di partenza»): non c'e' lingua da dare all'orecchio.

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:

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.

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.

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

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):

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'.

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).

«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.

la sera e' un servizio, e vale la regola dell'08/09 - cartoline = servizi,

barra = pagine di piattaforma. Sale come cartolina in home.

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»):

«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».

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.

finche' il bottone e' acceso si riparte, se no ci si ferma a meta' racconto.

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

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):

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.

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.

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):

finiva sotto .vv-scrollpins, i segnaposto fissi a destra: sul tablet in

orizzontale erano proprio sopra. In tutta altezza i pin ora spariscono.

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.

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.

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.

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.