---
name: pipeline-pubblicazione
description: "Come si pubblica su @bg_perspective, dai file generati a Buffer, e dove stanno gli strumenti del progetto"
metadata: 
  node_type: memory
  type: project
  originSessionId: d9cb587f-e008-4bf3-b0fb-b6182f56fea4
  modified: 2026-09-21T18:24:47.695Z
---

Catena di pubblicazione di [[progetto-instagram-bg-perspective]], montata il 2026-08-31.

## Dove stanno le cose

Tutto sotto **`/Users/paolo/Server/Siti Web/_private/BG-Perspective/_instagram/`**, mai piu' nello scratchpad: `/tmp` si svuota a ogni riavvio del Mac e ci abbiamo perso il lavoro due volte.

**Riordinato il 2026-09-02**, su richiesta di Paolo: prima codice, dati, foto e scatole stavano tutti in `generatore/` e `mockup/`. La mappa completa e' in **`_instagram/README.md`**, che va letto per primo. In breve:

- `codice/` solo i .py e il venv
- `dati/` bgp.sqlite, l'export BG Stats, i json del portale e `cache/api/` + `cache/trascrizioni/`
- `schede/` una .md per gioco piu' `come_gira_paolo.md` coi vocali
- `foto/` le foto gia' preparate per le slide, `scatole/grezze/` e `scatole/pronte/`
- `out/post/`, `out/storie/`, `out/html/` (passaggio intermedio), `out/prove/`
- `bgp-immagini/` il progetto Vercel, `font/` i font in locale

Le cartelle si cambiano da cinque costanti in cima a `slide.py` e `storia.py`: `FOTO`, `SCATOLE`, `OUT`, `HTML`, `FONT`.

Chrome headless: `--headless --disable-gpu --hide-scrollbars --force-device-scale-factor=1 --window-size=1080,1440 --virtual-time-budget=4000 --screenshot`. **Mai `--user-data-dir`**, blocca tutto.

**La scatola si prende da BGG**, dal campo `image` che il portale gia' restituisce. E' la copertina piatta, gia' ritagliata, senza fondo da togliere. `portale.scatola_bgg(bgg_id, nome)` la scarica in `scatole/grezze/`. Deciso con Paolo il 2026-09-02: *"su BGG le scatole sono perfette, non in prospettiva, rettangoli gia' ritagliati, al 99% dei casi"*.

Prima si prendeva da DungeonDice, che pero' pubblica **render della scatola in prospettiva su fondo bianco**. Vanno scontornati, e lo scontorno fallisce quando il fronte della scatola e' chiaro: su Faraway il riempimento e' entrato dal bordo e ha bucato la copertina, lasciando sul nero solo l'illustrazione che galleggiava.

**L'imprevisto da controllare a occhio ogni volta e' la lingua.** BGG mostra l'edizione principale, non quella italiana:

- **Terraforming Mars**: la copertina di BGG e' inglese, con il claim "Coming to Mars was a big step" e i loghi Stronghold Games e FryxGames. La scatola Ghenos ha titolo e claim tradotti. Li' resta quella di DungeonDice, che per fortuna e' piatta.
- **Draftosaurus**: BGG dava la scatola francese Ankama mentre la slide diceva "Edizione italiana, Ghenos Games". Stesso problema.
- **Faraway**: BGG da' l'edizione Catch Up Games invece di GateOnGames, ma cambia **solo il logo in basso**, alto una quindicina di pixel sulla slide. Va bene lo stesso.

La regola: se cambia il titolo o l'illustrazione si va su DungeonDice, se cambia solo il logo dell'editore si tiene BGG.

Quando serve davvero scontornare, `scontorna.py` allaga il bianco dai bordi e poi **stringe la selezione di un pixel**. L'erosione non e' un dettaglio: il riempimento da solo lascia i pixel di antialiasing del bordo, mezzi bianchi, che sul nero della slide si vedono come un alone. Sull'immagine scontornata l'ombra va messa con `filter: drop-shadow`, non `box-shadow`, altrimenti segue il rettangolo.

## Buffer

Account gratuito di Paolo, canale **bg_perspective collegato come Professional** (l'account Instagram era gia' business): la modalita' Personal manda solo una notifica e va finita a mano, quindi non serve a niente. Permessi concessi: profilo, commenti, pubblicazione, insight. Tre canali inclusi nel piano, uno usato.

**Calendario: lunedi, mercoledi e venerdi alle 19:00, fuso Roma.** Gli orari che Buffer propone da solo sono medie di settore e vanno cancellati tutti. L'orario e' un'ipotesi di partenza, da correggere con gli Insights dopo qualche settimana.

**MCP di Buffer, autenticato con una chiave personale dal 2026-09-07.** Prima era
collegato in OAuth e il token scadeva ogni giorno o due: Paolo doveva rifare `/mcp` di
continuo, e ogni volta nasceva una connessione nuova invece di rinnovare la vecchia. Nella
pagina `publish.buffer.com/settings/api`, sotto "Active Integrations", se ne erano accumulate
sette in cinque giorni.

La chiave si genera in quella stessa pagina, riquadro **Create a Personal Key**, bottone
"New Key". Poi si configura cosi', **lanciandolo Paolo dalla sua shell** perche' la chiave non
deve passare dalla chat:

```
claude mcp remove buffer -s user && claude mcp add --transport http buffer https://mcp.buffer.com/mcp --header "Authorization: Bearer LA_CHIAVE"
```

Due cose da ricordare. **Senza `-s user` finisce nella configurazione locale del progetto**, cioe'
dentro `~/.claude.json` sotto la chiave del progetto (dal 29/9 "/Users/paolo/Server/Siti Web/_private/BG-Perspective"): Buffer funziona solo
lavorando da quella cartella. E **la configurazione la rilegge solo all'avvio**: dopo aver
cambiato l'header, un `/clear` non basta, va chiuso e riaperto Claude Code, se no continua a
rispondere "requires re-authorization".

La chiave vale come la password di Buffer. Sta in `~/.claude.json`, che e' fuori dal repository
e non viene mai committato. Se qualcosa va storto si revoca da quella pagina.

**I limiti sono condivisi fra chiavi e integrazioni**: cento chiamate ogni quindici minuti,
250 al giorno, 3.000 al mese. Uno slittamento di massa della coda, che sono una ventina di
`edit_post` piu' le verifiche, ne consuma qualche decina: si sta larghi, ma non e' infinito.

## Le immagini devono stare online

**Buffer non ha nessuna mutation per caricare file**: nello schema GraphQL ogni asset si passa solo come `url`, e i loro server se lo scaricano. Quindi le slide vanno pubblicate da qualche parte prima di poterle programmare via API.

**Soluzione vecchia, abbandonata il 17/9**: progetto Vercel statico `bgp-immagini` (`_instagram/bgp-immagini/`),
aggiornato copiandoci dentro i PNG di `out/` e lanciando `vercel deploy --prod --yes --scope
paoloalbys-projects` da quella cartella, con indirizzi tipo `https://bgp-immagini.vercel.app/post1_1.png`.

**Il 17/9 questo ha riempito il tetto gratuito di Vercel** (10 GB di Deployment Storage, per team non per
progetto): ogni deploy di bgp-immagini finiva in un cestino recuperabile per 30 giorni, e quello spazio
continuava a contare. Con tre uscite a settimana il cestino e' arrivato a 9,43 GB e ha bloccato anche i
deploy di bg-perspective, che pesa pochissimo di suo. Non esiste modo di svuotare il cestino prima del
tempo (ne' da dashboard ne' da `vercel remove`, che sui deploy gia' cancellati risponde "not found").
Paolo e' passato a Vercel Pro la sera stessa per sbloccarsi subito, sapendo che avrebbe voluto tornare
su Hobby dopo un mese, e ha chiesto un lavoro fatto bene per non ritrovarsi lo stesso problema.

**Soluzione buona, dal 17/9 sera: Vercel Blob**, un object storage separato dai deployment. `vercel blob
put` carica un file alla volta senza creare un deploy, quindi niente cestino e niente limite di
Deployment Storage: la quota di Blob e' un'altra cosa, molto piu' alta e non condivisa con i deploy.
Store creato (`bgp-immagini`, pubblico, `store_Hndm54GXGLhs3g3v`), collegato al progetto `bg-perspective`.
Codice in `_instagram/codice/blob.py`: `blob.carica(locale, pathname)` per un file,
`blob.carica_slide_post(chiave)` per le cinque slide di un post (`out/post/{chiave}_N.png` ->
`post/{chiave}_N.png`). `sito.prepara_immagini` usa `blob.carica` al posto del vecchio `vercel deploy`.
Le 102 immagini gia' pubblicate su bgp-immagini sono state migrate una volta sola con
`codice/migra_immagini_blob.py` (archiviato il 28/9 in `_archivio/2026-09-28/`), e `sito/src/lib/immagini.js` punta ora a
`https://hndm54gxglhs3g3v.public.blob.vercel-storage.com/sito`. Il vecchio progetto `bgp-immagini` resta
acceso ma congelato (niente piu' deploy sopra): i post gia' in coda su Buffer (Scythe, Happy Pigs) restano
sui loro vecchi indirizzi `bgp-immagini.vercel.app/postN_M.png`, che continuano a funzionare da soli.

**Il token (`BLOB_READ_WRITE_TOKEN` in `sito/.env`) non si legge mai da codice o da terminale**: Vercel lo
mostra una volta sola, nel tab ".env.local" della pagina dello store, dietro "Show secret" o "Copy
Snippet". Va copiato e incollato da Paolo, mai passato da me. E **mai `subprocess.run(cmd_col_token,
check=True)`**: l'eccezione include gli argomenti del comando, token compreso, vedi
[[subprocess-check-true-espone-i-segreti]] (successo una volta, il token e' stato ruotato).
`vercel blob put --rw-token` non funziona da riga di comando nonostante l'help lo dichiari: il token va
passato come variabile d'ambiente `BLOB_READ_WRITE_TOKEN`, non come flag.

Le immagini vanno lasciate online almeno finche' Buffer non ha pubblicato.

## Il tag dell'editore sulla foto

**Non basta la menzione in didascalia: l'editore va taggato anche sulla foto**, che e' il tag che compare quando uno tiene premuto sull'immagine e quello che finisce nella sezione "Foto di te" del profilo taggato. E' la differenza fra farsi leggere e farsi archiviare.

Si fa via API: nella `create_post` di Buffer, dentro `assets[0].image.metadata.userTags`, con `{handle, x, y}` e coordinate normalizzate da 0 a 1. **Solo sulla prima slide**, non su tutte, se no diventa spam.

**Le coordinate dipendono da quanti tag ci sono**, se no si sovrappongono: uno solo a `x 0.5 y 0.3`; due a `x 0.35` e `x 0.65`, tutti e due a `y 0.30`; tre, il terzo a `x 0.5 y 0.45`. Da quando si taggano anche gli autori il caso normale sono due, non uno.

Attenzione: `get_post` del server MCP non restituisce `userTags`, quindi sembra che il tag non sia stato salvato. Per verificarlo davvero serve una query diretta:

```graphql
query($input: PostInput!) { post(input: $input) {
  assets { ... on ImageAsset { source image { userTags { handle x y } } } } } }
```

Vale solo per Instagram: su Threads i tag sulle foto non esistono.

**L'eccezione: sui post critici l'editore non si tagga.** Deciso da Paolo il
2026-09-04 parlando di SETI, dove la tesi del post è che il gioco non ripaga le
tre ore e mezza che chiede. Taggare Cranio Creations su quel post significa
mandargli una notifica per dirgli che il suo gioco non vale il tempo, e *"sarebbe
controproducente"*. L'editore si nomina lo stesso, in chiaro e senza chiocciola,
quindi non si nasconde niente a chi legge.

**Il 2026-09-04 è venuto fuori che il tag non c'era su nessuno dei dieci post in
coda.** Ce l'aveva solo post1, Draftosaurus, che era già uscito. Tutti gli altri
avevano `userTags: null`: il campo era stato messo la prima volta e poi mai più,
e Dixit stava per uscire quella sera senza. Rimessi tutti e dieci con
`edit_post`, uno per uno.

**How to apply:** il tag non si vede da nessuna parte nell'interfaccia di Buffer
e `get_post` non lo restituisce, quindi sparisce in silenzio. Dopo ogni
`create_post` o `edit_post` su Instagram si rilancia la query diretta qui sopra e
si guarda che la **prima** immagine abbia il suo `userTags`. Attenzione: un
`edit_post` rivalida il post intero e **non fa merge**, quindi vanno riportati
avanti testo, tutte le immagini con il loro `altText` e il `metadata`, altrimenti
quello che non riscrivi lo perdi.

**La descrizione dello strumento su questo mente.** Dice che omettendo `assets` le
immagini restano: e' falso, il post viene rifiutato con *"Instagram posts require at
least one image"*. Il rifiuto pero' e' pulito, il post resta com'era: verificato il
2026-09-06 durante lo slittamento per Wingspan Pocket. E i `userTags` non tornano
indietro da soli: `get_post` non li mostra, quindi vanno riletti con la query diretta
**prima** di modificare e riscritti dentro `assets`, se no la modifica li cancella.

## Il tetto dei dieci slot, e verificare che il post ci sia davvero

Il piano gratuito ammette **10 post programmati per canale** (`limits.scheduledPosts`), e Instagram e Threads contano separatamente. Uno slot si libera solo quando un post viene pubblicato: con tre uscite a settimana la coda copre poco piu' di tre settimane, e chi programma in anticipo la riempie.

Contarli si fa con una query diretta, perche' `list_posts` con 18 post restituisce piu' di 58.000 caratteri e non entra in una risposta:

```graphql
query($in: PostsInput!) { posts(input: $in, first: 100) {
  edges { node { id dueAt status channelService } } } }
```
con `{"in": {"organizationId": "..."}}`. `status` non e' un campo di `PostsInput`, si filtra dopo.

**Il 2026-09-01 e' venuto fuori che Azul: Le vetrate di Sintra non era mai stato messo in coda**: slide generate, immagini online, ma nessuna `create_post`. Dopo una correzione di Paolo sulla CTA la lavorazione era ripartita dalle slide e il passaggio su Buffer era saltato, senza che nessuno se ne accorgesse per due giochi.

**How to apply:** un gioco non e' chiuso quando le slide piacciono, e' chiuso quando la `create_post` ha risposto con un `id` per **tutti e due** i canali. Prima di passare al gioco successivo si rileggono le date in coda e si controlla che ci sia quella appena programmata.

**Dal 18/9, un controllo in piu' prima del `create_post`**: editore, autori e illustratori
devono avere tutti un esito in `handle_autori.csv` e `handle_artisti.csv` (un handle, o un "non
trovato" con la prova del tentativo). Se manca qualcuno la ricerca si fa li', non dopo che il
post e' gia' uscito. Vedi [[foglio-editori-e-autori]].

## Threads

Non ha registrazione autonoma: un profilo Threads nasce sempre da un account Instagram e ne eredita il nome utente. Va creato entrando con **bg_perspective**, non col profilo personale di Paolo.

**How to apply:** i file veri si generano con `python3 slide.py`, si controllano a occhio uno per uno, e solo dopo si portano su Buffer. Niente va pubblicato senza un si' esplicito di Paolo.

## La scatola è quella in vendita adesso, non quella di Paolo

Regola cambiata da Paolo il 2026-09-02 su Sagrada: *"non c'entra cosa ho io.
Mettiamo comunque l'ultima versione. Se la scatola è nuova, mettiamo la scatola
nuova, sennò chi lo vuole comprare non trova quel disegno negli scaffali."*

Prima seguivamo "si fotografa la scatola che Paolo possiede". **Sostituita**: la
slide 4 serve a far riconoscere il gioco in negozio, quindi va l'edizione
**attualmente in commercio**. Su Sagrada significa la seconda edizione 2026,
non la prima Cranio del 2017 che è quella in casa.

Resta valido tutto il resto: prima si prova la copertina **piatta** di BGG,
perché DungeonDice mette render 3D su bianco. Su Sagrada seconda edizione
DungeonDice ha solo il 3D e lo `scontorna()` **ha bucato il cielo chiaro sopra
il titolo**, lasciando sbavature nere sull'illustrazione: stessa identica
rottura di Faraway. Quindi è rimasta quella di BGG, che è la stessa
illustrazione con il claim in inglese invece che in italiano. Sul riconoscimento
a scaffale non cambia niente, perché il disegno è lo stesso.

## Le schede sono il vero archivio, non queste memorie

In `_instagram/schede/` c'è una .md per gioco. Al 2026-09-03 ce ne sono **16**:
Barrage, Brass Birmingham, Century, Dixit, Draftosaurus, Esdra e Neemia, Grand
Austria Hotel, Happy Pigs, Kingdomino, Noctiluca, Quadropolis, Ratti di Wistar,
Sagrada, Scythe, Skara Brae, Wingspan. Più `come_gira_paolo.md` con i vocali e
`_come-si-usa.md` con le istruzioni.

**Non tutti i post ne hanno una.** Mancano le schede di Cryptid, Ora et Labora,
Between Two Cities, Lords of Xidit, Pozioni Esplosive, Terramara e Azul Sintra
(fatti prima che la convenzione fosse regolare) e di Harmonies, Terraforming Mars
e Faraway.

**Dentro ogni scheda c'è la didascalia già scritta**, nella sezione "Pubblicato",
pronta da incollare su Buffer. Insieme a: dati verificati con la fonte accanto,
come funziona il gioco ricavato dal tutorial, il "come gira" di Paolo, quali foto
sono state usate e **quali scartate e perché**, la chicca con le fonti, e le
trappole trovate strada facendo.

Quindi, quando si riprende: **prima la scheda del gioco, poi tutto il resto**.
Queste memorie servono per le regole generali, le schede per il singolo post.

## Aggiunte al codice, 2026-09-02 e 03

- `chiusura()` accetta **`stile_foto`**, come `storia()`, per spostare il ritaglio
  con `object-position`. Serve quando il soggetto sta in alto o in basso e
  `object-fit:cover` lo taglia: è servito su Wingspan, Sagrada e Kingdomino.
- `durata_da_portale()` accetta **`min_scatola` e `max_scatola`** per scavalcare
  la durata del portale quando è sbagliata. Vedi [[slide-durata-vera]].
- `portale.scatola_bgg()` scarica la copertina piatta da BGG.
- **`img.dungeondice.it` si scarica con curl** passando uno User-Agent da browser
  e il Referer della pagina prodotto: è solo `www.dungeondice.it` a stare dietro
  Cloudflare. Gli URL delle immagini si tirano fuori dalla pagina aperta nel
  browser con una riga di javascript sui tag `img`.

## Le storie su Drive: cartella sincronizzata, non API

`storia.py` scrive dentro
`/Users/paolo/Library/CloudStorage/GoogleDrive-paoloalby@gmail.com/Il mio Drive/Instagram`,
che è **Google Drive sincronizzato sul Mac**. Quindi per aggiornare una storia si
scrive il file **in locale** e Drive lo aggiorna da solo.

Non usare l'API Drive per questi file: `create_file` non sostituisce, crea un
gemello chiamato `nome (1).txt`, e per togliere il vecchio servirebbe cestinarlo.
Sbagliato due volte il 2026-09-04, poi rifatto in locale in un colpo solo.

Ogni storia ha due file: `mm-gg Nome.png` e `mm-gg Nome.txt`. Dal 2026-09-04 il
`.txt` contiene le tre opzioni del quiz, una per riga, poi una riga vuota e poi
**`TAG: @editore @autore`**, solo chiocciole e niente hashtag, così dal telefono
Paolo ha sotto mano chi taggare senza aprire il foglio. I tag stanno **dentro
`storia.py`**, campo `tag` accanto a `opzioni`, e li scrive `su_drive()`: se si
aggiunge un handle nuovo si aggiorna lì e si rigenera, non si tocca il file a
mano. L'elenco degli handle sta in [[foglio-editori-e-autori]].

## Fuori catalogo non è un motivo per scartare un gioco

Deciso da Paolo il 2026-09-05, davanti alla scoperta che dei dodici giochi nuovi
solo due si compravano quel giorno: *"i giochi belli valgono lo stesso... se uno
legge di un gioco, anche se ora non è disponibile, se lo preordina, e arriverà"*.

**Why:** nei giochi da tavolo il fuori catalogo non vuol dire morto. C'è il
ricircolo, e **una ristampa in corso è il segnale opposto**, cioè che il gioco è
ancora richiesto. Paolo stesso ha in preordine l'espansione di Through the Ages
con arrivo dichiarato al 31 dicembre 2026. Il criterio resta **famoso e
attuale**, non **disponibile oggi**.

**How to apply:** la disponibilità su DungeonDice si legge e si annota nella
scheda, perché serve a scegliere le parole (un "in ristampa" si può dire), ma non
entra nella scelta della scaletta. Non riaprire la questione a ogni giro. Resta
valida la regola separata sulla **scatola**, che è quella dell'edizione in
commercio adesso, vedi più sopra.

## Quando un workflow cade per limite di sessione

Successo due volte il 5 settembre: gli agenti dentro un workflow si fermano con
*"You've hit your session limit · resets 9:20pm"*, il workflow **termina lo
stesso** portando a casa i risultati buoni, e i caduti restano caduti.

**Why:** `autoContinueAtUsageLimit: true` nelle impostazioni di Paolo copre un
altro caso, cioè il turno principale interrotto. Qui non si interrompe niente:
per il sistema il lavoro in background è finito regolarmente. E io non riparto da
solo, perché **agisco solo quando qualcosa mi sveglia**. Lasciare il terminale
aperto non basta: la sessione aperta è la condizione necessaria, non quella
sufficiente.

**How to apply:** l'errore contiene sempre l'orario del reset. Nello stesso turno
in cui lo leggo, **senza chiedere** (approvato da Paolo il 2026-09-05):

1. `CronCreate` con `recurring: false` e il cron puntato all'ora del reset più
   due minuti, minuto e ora e giorno e mese fissati;
2. nel prompt della sveglia ci vanno il `scriptPath` e il `resumeFromRunId`
   esatti, più l'istruzione di riprogrammarsi ancora se ricade.

Il riavvio costa poco: gli agenti già finiti tornano dalla cache e ripartono solo
quelli caduti. La seconda volta ne ha rifatti dodici su ventiquattro senza
ripetere niente di buono.

**Due limiti da conoscere.** I job di `CronCreate` vivono **solo dentro la
sessione**, non sono scritti su disco e muoiono se Claude si chiude. E scattano
**solo mentre il REPL è fermo**, non durante una risposta.

## Si taggano tutti e due gli editori, l'italiano e l'originale

Regola data da Paolo il 2026-09-06, foto della scatola alla mano: sul Wingspan
italiano ci sono **tre marchi stampati**, Stonemaier Games, Ghenos Games e Automa
Factory. *"Molte scatole, anche se in Italia vengon poi portati da altri editori
italiani, mantengono il logo originale. Allora in questi casi van taggati
entrambi."*

**How to apply:** sulla prima slide vanno **due tag**, l'editore italiano e
l'editore originale, a coordinate diverse per non sovrapporsi (x 0.35 e x 0.65,
stessa y 0.3). Vale anche quando la scatola fotografata e' quella straniera, come
su Wingspan Pocket: l'italiano ci va lo stesso perche' e' chi lo portera' qui.

**Stonemaier Games e' un caso da conoscere.** `@stonemaiergames` esiste e si
verifica (9.706 follower, bio "Editore di giochi", link al sito ufficiale) ma **e'
parcheggiato**: 25 post, e la bio dice *"all posts are made on
@jameystegmaier"*. Anche il sito ufficiale linka Jamey e non l'account aziendale.
Per farsi vedere davvero si tagga **@jameystegmaier** (58.300 follower, gia' nel
foglio autori, verificato il 4 settembre).

**Da rivedere sui post gia' in coda**, dove finora avevamo messo un tag solo:
Brass Birmingham, Wingspan, Scythe e in generale ogni scatola che tiene il marchio
originale accanto a quello italiano.

## Le storie slittano insieme ai post, e si rinominano a cascata

Le storie si chiamano `mm-gg Nome` con la data in cui **esce la storia**, cioè il
giorno prima del post. Quando un post nuovo si infila in mezzo alla coda, tutte
quelle dopo slittano di uno slot e **ognuna prende il nome di quella successiva**.

Fatto il 2026-09-07 per l'inserimento di Wingspan Pocket il 9 settembre: 25 storie
spostate, `09-08 Cryptid` diventa `09-10`, e a cascata fino a Revive, che da
`11-03` finisce a `11-05`. Cinquanta file su Drive (png e txt) piu' i locali in
`out/storie/`.

**Si rinomina dall'ultima alla prima**, se no due file finiscono con lo stesso nome
a meta' strada. Lo spostamento sta in `dati/slittamento_storie_2026-09-07.csv`.

E vanno aggiornati anche **i commenti dentro `codice/slide.py`**, che portano la
data di uscita di ogni post: sono commenti, ma sono l'unico posto dove la scaletta
e' scritta accanto alle slide.

## La scatola piatta prima di tutto, anche per l'edizione italiana (Paolo, 28/9/2026)

Paolo *"predilige le immagini piatte invece di quelle in 3D prese da DungeonDice e poi scontornate"*: DungeonDice
**solo come ultima spiaggia**. Quando l'edizione italiana ha una copertina diversa, la piatta si cerca fra le
**versioni** del gioco su BGG (`/boardgame/<id>/<slug>/versions?language=2193` per l'italiano, poi la pagina della
versione): l'immagine della versione è la copertina piatta. L'originale si prende da
`https://api.geekdo.com/api/images/<picid>` (campo `images.original.url`), con curl e User-Agent da browser. Fatto su
Food Chain Magnate (versione 550712, pic5979062): il render 3D di DungeonDice scontornato era venuto male.

## Le scatole che sono un rendering in prospettiva

Trovato il 2026-09-07 su Autobahn. L'immagine dell'edizione italiana su BGG non e'
la copertina piatta, e' **la scatola renderizzata in prospettiva su fondo bianco**,
con sotto una sfumatura grigia che fa da ombra. Scontornata col solo criterio del
bianco, quell'ombra resta e su fondo nero si vede come un alone: l'ha notato Paolo
guardando la slide.

`scontorna()` adesso ha il parametro **`ombra`**, che e' la quota di altezza sotto
la quale conta come fondo anche il pixel chiaro e senza colore. Su Autobahn:
`scontorna(grezza, pronta, ombra=0.78)`.

**La fascia non e' prudenza, serve.** Applicando il criterio del grigio a tutta
l'immagine il riempimento si e' mangiato il cielo dentro la copertina, che e'
azzurro pallido e quindi chiaro e desaturato quanto l'ombra. L'ombra pero' sta
sempre sotto la scatola e il cielo sta sempre in alto.

**Ombra più scura, dal 24/9 (Masterland):** il render mandato dall'editore aveva l'ombra a 137-155 di
luminosità, sotto la soglia fissa di 150, e restava un cuneo grigio. `scontorna()` ha ora anche il
parametro `luce` (default 150): su Masterland `ombra=0.78, luce=120`. E vale la regola della scatola:
se il fianco dice una durata diversa da BGG (15-30 contro 15-40), vince la scatola.

**How to apply:** prima si guarda la grezza. Se e' una copertina piatta, si usa
com'e'. Se e' un rendering, `ombra=0.75`-`0.80` e poi **si guarda il risultato
composto su nero**, non solo la trasparenza, perche' l'alone si vede solo li'.

## Gli identificativi di Buffer

Non si tirano a mente e non si cercano ogni volta: sono questi.

| cosa | id |
|---|---|
| organizzazione | `6a958f0c1fe3eb83b2a8c4bf` |
| canale Instagram | `6a95918b065799be465e1f0d` |
| canale Threads | `6a95974a065799be465e4480` |
| canale Facebook (pagina BoardGame Perspective) | `6aa96a33ea19ca0bde4ae2ec` |

**Facebook dal 2026-09-15.** La pagina "BoardGame Perspective" (19 follower, ferma dall'ottobre 2024,
link a bg-perspective.it già in bio) è collegata a Buffer come terzo canale: tre su tre del piano
gratuito, non se ne aggiungono altri. Motivo: nei dati di Valentina i reel su Facebook portano
200–480 visualizzazioni gratis. Da qui in poi **ogni post in coda va su tutti e tre i canali**: su
Facebook stesse foto, stessa data, didascalia senza hashtag e senza le @menzioni di Instagram (su
Facebook non risolvono: si scrive il nome dell'editore, "In italiano lo porta Ghenos Games"). Le storie
continuano ad andarci da sole dall'app di Instagram. Anche su Facebook vale il **tetto dei dieci post in
coda** del piano gratuito. Il 15/9 sono stati messi in pari: i nove in coda (16/9 → 5/10) creati con le
stesse date, e i sei già usciti pubblicati subito con `shareNow`. I post Facebook si creano con
`create_post` e `metadata: {"facebook": {"type": "post"}}`, fino a sei immagini. **`sito.py pubblica`
prende ancora solo `--ig` e `--th`**: l'id Facebook non serve al sito, non c'è da passarlo.

Per contare la coda di un canale solo, il filtro sta **dentro `filter`**, non alla radice
di `PostsInput`:

```graphql
query($in: PostsInput!) { posts(input: $in, first: 50) {
  edges { node { id dueAt text } } } }
```
con `{"in": {"organizationId": "6a958f0c1fe3eb83b2a8c4bf",
"filter": {"channelIds": ["<canale>"], "status": ["scheduled"]}}}`.

## Dal 19/9, un passo in più dopo `sito.py pubblica`

Il sito parla anche inglese, francese, tedesco e spagnolo. Si traduce e si carica con
`sito.py traduci <slug> <lingua> <file.json>`, quattro volte (una per lingua). Vedi
[[sito-lingue]] per il dettaglio: dove vivono le traduzioni, il glossario condiviso per
categorie/meccaniche/peso, e la regola per cui gli URL dei tag non cambiano con la lingua.

## Il backup si fa prima, non dopo

Prima di ogni modifica di massa sulla coda si salva lo stato completo dei post — testi,
immagini, alt e tag — in `dati/backup/buffer_coda_<data>.json`. Fatto il 6 settembre prima
dello slittamento per Wingspan Pocket (diciotto modifiche) e l'8 settembre prima di
aggiungere gli autori.

**Why:** `edit_post` rivalida il post intero e non fa merge, quindi ogni modifica e'
un'occasione per perdere qualcosa che non hai riscritto. Con il backup accanto si vede
subito cosa c'era prima.

**Dal 2026-09-14 c'è un passo in più dopo la messa in coda su Buffer:** `sito.py pubblica` con i due id, che porta la scheda sul sito con la stessa data. Vedi [[sito-bg-perspective]].

## Un `edit_post` su un post già in coda: cosa serve per canale, scoperto il 20/9

Per riscrivere l'apertura di dieci post già programmati (vedi [[testi-e-fonti-bg-perspective]])
è servito capire cosa `edit_post` accetta senza rompere il post, canale per canale.

- **Instagram**: va sempre riscritto tutto, `assets` (tutte le immagini, ognuna col suo
  `altText` e `userTags` solo sulla prima) e `metadata.instagram`. Omettendoli il post viene
  rifiutato, coerente con [[pipeline-pubblicazione]] più sopra sui tag foto.
- **Threads**: omettendo `assets` e `metadata` il post accetta il nuovo testo e **mantiene da
  solo** le immagini originali. Tetto di **500 caratteri** per il testo: sopra, l'edit viene
  rifiutato (scoperto il 21/9, sei testi accorciati).
- **Facebook**: le immagini si conservano omettendo `assets`, ma **`metadata` va rimandato**,
  `{"facebook": {"type": "post"}}`: omesso, si perde (corretto il 21/9 sera dopo 27 edit fatti
  così; la versione precedente di questa nota diceva che Facebook si comportava come Threads, ed
  era sbagliata).

**Why:** la descrizione dello strumento dice che omettere `assets`/`metadata` preserva i
valori esistenti; per Instagram è falso (vedi sopra), per Threads/Facebook è vero. Non si può
generalizzare da un canale all'altro.

## Un post già pubblicato (`status: sent`) non si modifica da Buffer, su nessun canale

Verificato il 20/9 su sei post: quando un post è `sent`, `allowedActions` non contiene mai
`updatePost`, né su Instagram né su Threads né su Facebook. `edit_post` su un post sent non è
un'opzione, va cercata l'interfaccia nativa della piattaforma.

- **Instagram**: si modifica. "..." (Altre opzioni) → **Modifica** → si apre "Modifica
  informazioni" con la didascalia in un textarea. Funziona bene con `triple_click` su un
  paragrafo per selezionarlo (attenzione alla riga vuota che lo separa dal successivo: il
  `triple_click` spesso se la mangia, un `Return` la ripristina), poi si scrive sopra e si
  salva con **Fine**. **Si riscreenshotta sempre appena il modal si apre**: le coordinate della
  pagina normale non valgono dentro il modal, e cliccare sul punto sbagliato chiude il modal sul
  backdrop senza salvare, in silenzio. Dopo il salvataggio si ricarica la pagina e si legge il
  testo con `get_page_text`: la scritta "Elemento modificato" conferma, ma **va sempre
  riverificato leggendo il testo vero**, non solo il click su "Fine" — è successo che un salvataggio
  sembrasse andato a buon fine e invece il testo restasse quello vecchio (Cryptid, 20/9).
- **Threads**: **non si può modificare**, punto. Il menu "..." su un post pubblicato ha Insight,
  Salva, Fissa, Archivia, Nascondi numero, Opzioni risposta, Elimina, Copia link, Incorpora: nessuna
  voce "Modifica". È un limite della piattaforma, non di Buffer.
- **Facebook**: si modifica, dalla pagina o dal Dashboard per professionisti → Contenuti →
  Libreria di contenuti → "..." sul post → **Modifica post**. Stesso meccanismo di selezione del
  testo. **Attenzione al pulsante "Salva"**: la schermata "Impostazioni del post" che segue ha
  un secondo toggle, **"Metti in evidenza il post"**, che un click leggermente disallineato può
  accendere per sbaglio — significa mandare il post a pagamento. Si controlla che sia spento
  prima di premere "Salva" per davvero.

## La Libreria di contenuti della pagina Facebook mostra anche i post del gruppo

Scoperto il 20/9: **Ratti di Wistar** e **Wingspan Pocket** comparivano nella Libreria di
contenuti con un'etichetta **"Giochi da Tavolo"** invece che "Pubblicato da Buffer", testo
diverso da quello di Buffer e migliaia di visualizzazioni (6.457 e 6.553, contro le circa 10
dei gemelli via Buffer sulla pagina). **Non sono duplicati né errori**: sono post nativi nel
**gruppo Facebook "Giochi da Tavolo"** (77.548 membri, vedi [[reti-esterne-oltre-instagram]]),
non sulla pagina BoardGame Perspective, e per questo Buffer non li vede né li gestisce. La
Libreria li elenca comunque perché la pagina ha pubblicato lì.

**How to apply:** un'etichetta diversa da "Pubblicato" nella Libreria di contenuti significa
"non è passato da Buffer", non "è un duplicato da controllare". Il rendimento più alto si
spiega da solo: il gruppo ha un pubblico vero, la pagina ne ha 20.


## Scaricare un vocale da una chat Instagram, 22/9/2026

I messaggi vocali dei DM non stanno nel DOM finché non si premono. Il modo che ha funzionato con i
due vocali di Phil (Masterland): dalla tab della chat, `read_network_requests` con `clear: true` per
avviare il tracciamento, poi click su ogni "Riproduci" (e di nuovo per fermare), poi
`read_network_requests`: compaiono gli URL `cdn.fbsbx.com/v/t59.3654-21/.../audioclip-...mp4` firmati,
che si scaricano con `curl` senza cookie. Poi `ffmpeg -ar 16000 -ac 1` e
`whisper-cli -m ~/.whisper-models/ggml-small.bin -l it -nt`. I file vanno in
`_instagram/vocali/<editore>/`. La trascrizione storpia i nomi propri: si usa per il contenuto, mai
per copiare un nome senza verificarlo.


## Copertina dei reel di unboxing, dal 22/9/2026

Quando arriva un gioco da un editore, Paolo fa un video unico (pacco del corriere più apertura della
scatola) e lo pubblica come reel. La griglia mostra una copertina fissa, non il primo fotogramma:
la fa `codice/unboxing.py` (`python3 codice/unboxing.py <nome> "<TITOLO>" foto.jpg "dall'editore"
"<seconda pillola>"`), 1080×1920 con tutto il contenuto nella fascia 3:4 centrale. Layout scelto da
Paolo fra due proposte: foto della scatola sulla tovaglia verde sfumata sopra e sotto, occhiello
"UNBOXING · APPENA ARRIVATO", titolo Bebas, due pillole, la riga "A breve le nostre prime
impressioni, dal tavolo." e @bg_perspective piccolo in grigio (serve quando l'editore ricondivide).
La foto: orizzontale o quadrata, scatola chiusa un po' inclinata, aria in alto. Va in
`archivio/<Gioco>/<Gioco> - 00.jpg` così serve anche al post. Didascalia corta, tag editore e autore,
nessun annuncio delle storie ("le partite nelle storie" non si dice), e la storia della nascita del
gioco si tiene per il post. Primo uso: Masterland, `out/unboxing/masterland_copertina.png`.

**Geometria rifatta il 26/9/2026, su richiesta di Paolo** (le due griglie tagliano diverso: quella principale
mostra la fascia 3:4 centrale, y 240-1680; quella dei reel tutto il 9:16). Due cose: (1) il titolo deve cadere
alla stessa altezza dei titoli dei caroselli nella griglia principale: ora è Bebas 136px, line-height .86, con il
fondo a y 1354, cioè 1114 nella fascia, come `.cop .tit` di `slide.py`; foto fino a 1380 e banda nera di 300px
come la copertina dei caroselli; (2) niente sfumatura dentro la fascia: la foto arriva intera al bordo alto
della griglia, e la sfumatura sta solo sopra i 240, così nella griglia dei reel il nero in alto è poco.
`--alto N` fa partire la foto più in basso quando non c'è aria sopra la scatola: Masterland (foto quadrata e
stretta) va con `--alto 180`, se no in griglia la scatola si taglia in cima. Con una foto che ha aria, 0.

## Template della storia di festa, dal 26/9/2026

`codice/festa.py` (`python3 codice/festa.py <nome> <foto> "<occhiello>" "<titolo>"`): panno verde, foto intera in
una card centrata, titolo centrato sopra, meeple colorati sui bordi, fascia libera sotto per lo sticker Domanda.
Nato per i 500 follower ([[numeri-del-canale]]); Paolo l'ha tenuto come template "per ora, vedremo se
riutilizzarlo" (prossimi traguardi, 1.000). La sagoma del meeple è la costante `MEEPLE` nello script.

**Dal 29/9/2026 il materiale dei reel sta in `unboxing/`** (cartella di Paolo nella radice del progetto, fuori da git):
video `.MOV` e foto `.HEIC` della scatola chiusa, fotografata dall'alto sulla tovaglia verde, in verticale 3:4, nome
`AAAA-MM-GG hhmmss - Gioco`. Per la copertina l'HEIC si converte in JPEG girato (`sips` + `ImageOps.exif_transpose`,
l'originale è 6048×8064) e va a `unboxing.copertina(...)`: la foto verticale riempie la fascia quasi senza tagli e il
titolo cade sulla parte bassa della scatola. Esempi: `out/unboxing/moon_colony_copertina.png` e le altre del 1/10.


**Dal 30/9/2026 copertine e didascalie dei reel vanno su Drive, in `Instagram/reel`** (Paolo: *"mi ci metti sia la
copertina sia la didascalia in .txt [...] così me li gestisco io, di giorno in giorno"*). In locale la cartella è
`~/Library/CloudStorage/GoogleDrive-paoloalby@gmail.com/Il mio Drive/Instagram/reel/`: si copia lì, senza passare dal
connettore (le immagini in base64 non ci stanno). Nomi: `<Gioco o editore> - copertina.png` e `<Gioco o editore> -
didascalia.txt`. L'ordine di uscita lo decide Paolo, alternando gli editori. Primo giro: i sette di Mana Project Studio.
**In fondo a ogni didascalia, dopo una riga `-----`, il blocco "TAG NEL VIDEO (non copiare da qui in giù)"** (Paolo, 1/10):
handle verificati col ruolo (editore italiano e originale, autori, illustratori), gli stessi da invitare come
collaboratori, e i nomi di chi non è su Instagram.
**I loghi degli editori per la sovrimpressione stanno su Drive in `Instagram/loghi editori/`** (Paolo, 1/10): uno per
editore, nella versione chiara che si legge sulla tovaglia verde; fonti e originali in `_instagram/loghi/` (`LEGGIMI.md`).
A ogni editore nuovo che manda un gioco si aggiunge il suo, prima del reel. **Formato che Instagram accetta con la trasparenza (verificato 3/10 con
Doomlings):** circa 2000 px di larghezza, margine trasparente del 6% e ombra morbida sotto; il PNG piccolo tagliato a
filo perde la trasparenza nel reel. Dettagli in `_instagram/loghi/LEGGIMI.md`.
Le foto HEIC di Paolo hanno spesso l'orientamento solo nei metadati: prima di `unboxing.copertina()` si converte con
`sips` e si passa da `ImageOps.exif_transpose`, se no la scatola esce girata.
