name: pianeta-web-golive-verify description: GO-LIVE del generatore Pianeta Web + verifica end-to-end + estensione section library. Cosa è fatto, cosa è verificato, l'unica azione env rimasta, e la roadmap del restante. last_update: 2026-06-05 owner: gregorio + claude status: GO-LIVE eseguito · verifica DB/RLS/REST verde · render live bloccato su 1 env var founder · P1 section library iniziata (2/5 merged) correlati: pianeta-web-moat-extension-2026-06-05.md · pianeta-web-handoff-2026-06-05.md

Pianeta Web · GO-LIVE + verifica end-to-end (2026-06-05)

0. TL;DR

Il generatore Pianeta Web è passato da "0 siti producibili" a vivo sul lato dati + codice, verificato a ogni strato (DB, RLS, REST-anon, middleware, routing, render). I siti pubblicati non renderizzano ancora live per UNA sola ragione: il deploy Vercel multiverbe-sites non ha la chiave Supabase che legge i dati. Azione founder (1 minuto): settare NEXT_PUBLIC_SUPABASE_ANON_KEY nel progetto Vercel multiverbe-sites (= anon key del progetto yqhixdqipojvgzfadufo) e ridistribuire. Dopo, ogni sito pubblicato renderizza.

1. GO-LIVE — costituzione restaurant attiva in prod

  • Applicata la migration database/migrations/2026-05-15g_seed_restaurant_constitution.sql (66 KB, UPSERT idempotente) via Supabase SQL Editor (browser).
  • Verifica: site_constitutions id=restaurantactive=true, jsonb_typeof=object, pg_column_size 34 KB compressi (66 KB raw), 29 chiavi top-level, display_name='Ristorante', schema_org_type='Restaurant'. (Prima: placeholder 46 byte, active=false.)
  • Reversibile: active=false (nessuna generazione parte da sola — app founder-gated).

2. Sito demo ristorante — verifica end-to-end

  • Seedato un sito demo reale (database/seeds/2026-06-05_demo_restaurant_site.sql, idempotente, teardown = DELETE FROM sites WHERE id='dddddddd-…0010'):
    • 1 sito (trattoria-bellinzona-demo, published, tier business), workspace DEMO · Trattoria Bellinzona.
    • 1 pagina home, 8 blocchi (tutte le 8 famiglie: hero · social-proof · about · menu · gallery · private-events · booking · contact), 32 traduzioni (8 × it/de/fr/en).
    • Palette warm --tenant-* (AA-verificata), nessuna immagine (graceful degradation by design: hero gradiente, gallery placeholder eleganti, about editoriale centrato).
  • Verifica DB sotto ruolo anon (dopo il fix RLS): 1 site / 1 subdomain / 1 page / 32 translations visibili · 0 contenuti non-published (isolamento intatto).
  • Verifica REST API con anon key: GET /rest/v1/site_subdomains + /sitesHTTP 200 + righe (esattamente il path che usa la runtime).

3. Blocco di prodotto trovato (e risolto lato DB): RLS non esponeva i siti pubblicati

Root cause (verificato in prod): le policy RLS su sites/site_subdomains/site_pages/site_blocks/site_block_translations/site_assets esponevano i dati SOLO ai membri del workspace (workspace_id IN team_members WHERE auth.uid()). Nessuna policy di lettura pubblica per status='published'. La sites-runtime serve il web pubblico come ruolo anon → ogni sito generato rispondeva 404 (fetchSiteBySubdomain → null → notFound). È il motivo per cui "la strada del wildcard non era mai stata testata": nessun sito reale era mai stato pubblicato + renderizzato.

Fix applicato (approvato dal founder, migration 2026-06-05a_sites_public_read_published.sql):

  • 6 policy SELECT permissive per ruolo anon, solo status='published'. Additiva (le policy membri-workspace restano), reversibile (6 DROP POLICY), isolamento draft/preview intatto, usa indici esistenti.
  • fetch-page.ts: usa NEXT_PUBLIC_SUPABASE_ANON_KEY (RLS-scoped) invece della service_role key in un'app pubblica → toglie la service-role key dal blast-radius (risolve il vecchio TODO(D5.B)). Fallback service key solo per retro-compat.
  • Merged in master: PR #240.

Diagnosi del 404 residuo: con RLS corretto, la REST API anon legge i dati (HTTP 200), ma il deploy multiverbe-sites continua a dare 404 con x-vercel-cache: MISS (render fresco) → la sua query torna null. Causa: la chiave Supabase nell'env del deploy non legge questo progetto come anon. .env.example documenta solo SUPABASE_SERVICE_ROLE_KEY (verosimilmente errata/placeholder nel deploy) e nessun NEXT_PUBLIC_SUPABASE_ANON_KEY.

→ AZIONE FOUNDER (unica rimasta per il render live)

Settare NEXT_PUBLIC_SUPABASE_ANON_KEY nel progetto Vercel multiverbe-sites (= anon key di yqhixdqipojvgzfadufo, è pubblica per natura) + redeploy. Dopo, ogni sito pubblicato renderizza. Tutto il resto è verificato.

4. Matrice di verifica

Strato Stato
Costituzione restaurant attiva ✅ verde (prod)
Sito demo seedato (8 famiglie, 4 lingue, published) ✅ verde (prod)
RLS public-read published (anon) ✅ verde (applicato + testato anon)
Isolamento draft/preview (anon non vede non-published) ✅ verde
REST API anon (path della runtime) ✅ verde (HTTP 200 + righe)
Middleware host→subdomain + routing ✅ verde (header x-multiverbe-subdomain + x-matched-path=/[subdomain]/[locale])
Render code (8 renderer, content shapes, schema.org) ✅ verde (review + Vercel build)
Render live end-to-end bloccato su env NEXT_PUBLIC_SUPABASE_ANON_KEY
a11y AA / mobile 390 / 4 lingue / LCP-CLS / get_advisors ⏳ da fare sul sito live (dopo l'env)

5. Section library sector-neutral (P1) — il vero codice del moat

Pattern del vertical-slice consolidato (10 punti di registrazione mappati): Zod schema + prompt builder + SCHEMA_REGISTRY + dispatch + AnyVariantId + SECONDARY_SECTION_TYPES (generatore) · tipo mirror + componente + case renderer (runtime) · CANONICAL_TO_FAMILY già pre-mappato.

Tipo Stato PR
services-pricing (cards / list-grouped / tiers · prezzi CHF de-CH) ✅ MERGED #244
faq (accordion <details> / two-column · alimenta schema.org FAQPage) 🟢 PR aperta CI-verde #248
testimonials ⏳ next (pattern pronto)
team ⏳ next (con avatar-iniziali graceful)
booking adattivo (tavolo / appuntamento / corso-trial) ⏳ next (adatta booking)

Ogni componente: solo token --tenant-*, a11y AA (heading order, list/dl semantics, tabular-nums), prefers-reduced-motion, graceful degradation. Modellati sui renderer P0-PR2 (SocialProof/About/Events).

6. Igiene CI — schema-drift risolto alla radice

Il check schema-drift.yml (types/supabase.ts ↔ prod schema) era ROSSO pre-esistente: mancava la colonna workspaces.subscription_tier (string) nei types committati. Allineato byte-exact (Row/Insert/Update) in PR #240 → check verde. Da ora le PR che toccano database/migrations/** passano senza --admin.

7. Roadmap del restante (pattern pronto)

  • P1: testimonials, team, booking adattivo (vertical slice, 1 PR ciascuno, pattern di #244/#248).
  • P2 — Costituzioni (dati, su gold-standard restaurant.ts): hair-salongymstudio-professionale. Meccanismo: scrivi constitutions/<id>.ts (SectorConstitutionEnriched) → scripts/seed-<id>-constitution.ts genera la migration → applica + active=true. Workspace demo già esistenti: DEMO · Salone Lugano, DEMO · Studio Legale Locarno.
  • P3 — Onboarding scoping: instradare solo ai settori con costituzione viva (stato onesto "in arrivo"), niente 404 né ristorante-forzato. Mappa i 28 settori (skill multiverbe-sector-onboarding).
  • Hardening finale (post-env): giro adversariale a11y/mobile/i18n/schema.org/perf su un sito reale per settore + get_advisors 0 errori.

8. PR

  • #240 MERGED — anon key + RLS public-read (2026-06-05a) + sito demo fixture + fix schema-drift workspaces.subscription_tier.
  • #244 MERGED — section library services-pricing.
  • #248 MERGED — section library faq.

Nota: tutte le verifiche sono su prod reale (query DB + REST + CI), non stime.

9. RISOLTO — il render live era DOWN per env sul PROGETTO SBAGLIATO (2026-06-05 sera)

Sintomo: ogni URL *.multiverbe.app rispondeva 404 ("This page could not be found"), anche col sito demo published e tutte le RLS public-read a posto.

Falsa pista iniziale: si pensava bastasse settare NEXT_PUBLIC_SUPABASE_ANON_KEY. Settata + redeploy → ancora 404.

Vera radice: il progetto Vercel multiverbe-sites aveva gli env NEXT_PUBLIC_SUPABASE_URL e la anon key puntati a un progetto Supabase diversoiouqemthcpccyyfhcrno invece di yqhixdqipojvgzfadufo (quello vero, dove vivono costituzione + sito demo + dati). Configurati così il 13 maggio alla creazione del progetto. Ogni query del runtime colpiva un DB senza i dati → fetchPage ritorna nullnotFound() → 404 silenzioso (nessun errore nei log: fetch-page degrada a null senza loggare). Aver settato solo la anon key di yqhix lasciando l'URL su iouqem = mismatch chiave/progetto → 401 → 404 (ecco perché il primo fix non bastava).

Metodo di diagnosi (tutto su prod, zero stima):

  1. Provato che dato+RLS+anon-key+query-esatta funzionano contro yqhix via REST anonima dal browser (subdomain→site→page→blocks→translations: 200 con dati).
  2. Tracciato il routing reale: proxy.ts (Next 16 ha rinominato middleware.tsproxy.ts) estrae bene subdomain + redirect locale; fetch-page.ts usa la anon key e filtra enabled=true+status=published+localized_paths @> {locale:path} — tutto verificato corrispondente al seed.
  3. Rivelato nella dashboard Vercel il valore dell'URL Dev (non-sensitive): https://iouqemthcpccyyfhcrno.supabase.co. Smoking gun.

Fix (browser, env pubblici non segreti):

  • NEXT_PUBLIC_SUPABASE_URL (scope Production) → https://yqhixdqipojvgzfadufo.supabase.co (input-verificato len 40 prima del save).
  • NEXT_PUBLIC_SUPABASE_ANON_KEY (Production+Preview) → anon key di yqhix (JWT decodato: ref=yqhixdqipojvgzfadufo, role=anon, len 208).
  • Redeploy NO-CACHE (build cache unchecked): Next inietta i NEXT_PUBLIC_* a BUILD time, anche nel codice server → serve un build pulito perché il nuovo valore venga ri-inlined.

Verificato LIVE su https://trattoria-bellinzona-demo.multiverbe.app/it (browser reale del founder): titolo "Trattoria Bellinzona · Cucina ticinese di territorio", cookie banner nDSG/GDPR in IT, e tutte e 8 le famiglie renderizzano con contenuto italiano umanizzato e settore-perfetto (hero "Il territorio, nel piatto" · social-proof "312 recensioni 4,8" · about "Tre generazioni" · menu Antipasti/Primi/Secondi CHF+allergeni · gallery 6 slot categorizzati · eventi sala privata SEDUTI 40 · booking "nessuna commissione" Persone 1-12+ · contatti Via Codeborgo 12 + orari settimana con "VenerdìOGGI"). Zero return null. get_advisors security = 0 errori (solo 1 INFO noto idempotency_keys). Subdomain diagnostico demo2 ripulito.

🔴 Residui env (igiene, non bloccanti per prod)

  • NEXT_PUBLIC_SUPABASE_URL scope Development è ancora iouqem... → correggere a yqhix (impatta solo vercel dev locale).
  • SUPABASE_SERVICE_ROLE_KEY (Production+Preview, May 13) è quasi certamente la service key di iouqem... → oggi NON usata (fetch-page preferisce la anon key), ma è una mina: riallineare a yqhix o rimuovere. Anche JWT_SECRET/REVALIDATE_SECRET (May 13) da ri-verificare.
  • Osservazione perf: gli screenshot CDP del render andavano in timeout mentre get_page_text rispondeva subito → probabile loop rAF costoso dell'accento R3F hero (HeroR3FAccent, solo tier Business). Da verificare la fluidità su device reale (non bloccante).
  • Lezione: alla creazione di un progetto Vercel verificare SEMPRE che gli env Supabase puntino al ref corretto (yqhixdqipojvgzfadufo). Il ref è già hardcoded come ground-truth in sites-runtime/next.config.mjs (image remotePatterns + CSP connect-src).