fix/crew-event-link — la crew creata da un evento non era collegata

Data: 2 agosto 2026 Priorità: 1 — Bug

Il sintomo, in tre travestimenti

L’utente ha segnalato, in sequenza:

  1. “Se dall’evento ne creo una crew collegata non si crea!”
  2. “Ah no si crea ma non riesco a vederla. E se clicco sull’invito dà errore”
  3. “E non posso cercarla, ma se cerco un’altra cosa mi esce”

Sembravano tre bug. Erano una causa sola più due indipendenti.

Causa

CrewService.createCrew accetta eventId da sempre e lo inserisce in crews.event_id. Nessuno poteva passarglielo:

LivelloeventId
create_crew_screen._submitCrewnon lo passava
CrewNotifier.createCrewparametro assente
CrewRepository.createCrewparametro assente
CrewService.createCrewlo accetta e lo scrive — mai ricevuto

Tutto il resto della catena funzionava: la rotta legge ?eventId=, il wizard carica l’evento e precompila, lo stato lo conserva attraverso copyWith, e la UI mostra perfino una sequenza di passi diversa quando è valorizzato (_stepsLinked vs _stepsStandalone). La chiamata di invio lo buttava via.

Verificato in produzione: 0 delle 8 crew più recenti aveva un event_id. Il vibe "Market" su quattro di esse dimostra che il precaricamento partiva davvero — la categoria dell’evento veniva ereditata — e che quindi il collegamento si perdeva a valle, non a monte.

Perché sembrava che il bug fosse cambiato

Il vincolo crew_must_have_anchor sul database pretende che una crew sia ancorata a un evento, un locale o una zona.

  • Con event_id perso, una crew nata da un evento non aveva ancora → l’insert veniva rifiutato → “non si crea”.
  • Qualcuno ha risolto aggiungendo la zona corrente come ripiego (areaFallback, vedi il commento in crew_notifier.dart). L’insert è passato — con il collegamento all’evento sempre mancante“si crea ma non la vedo”, perché la pagina evento elenca le crew con where event_id = ….

Il sintomo si è spostato, la causa no. Il vincolo del database stava segnalando il bug vero e la correzione l’ha zittito.

Correzione

eventId (e venueId, finora ugualmente irraggiungibile) aggiunti a CrewRepository.createCrew e CrewNotifier.createCrew; create_crew_screen._submitCrew passa wizardState.linkedEventId.

Bug 2 — rotte inesistenti nelle card condivise

Introdotto il 31 luglio con feature-chat-sharing.

SharedContent.route puntava:

  • le crew a /crew/:idrotta che non esiste in app_router.dart;
  • i profili a /profile/:id — la rotta vera è /user/:userId; /profile è una shell branch con figli fissi (settings, my-crews…), quindi /profile/<uuid> non combacia con niente.

GoRouter lancia un’eccezione su una rotta sconosciuta, quindi la card dava errore sotto il dito. Non è un link morto: è un crash.

Le crew ora non restituiscono nessuna rotta, perché davvero non c’è dove mandarle: si aprono con showSquadDetailSheet, che vuole un oggetto Squad e non un id. Il pulsante “Unisciti” dell’invito continua a funzionare, che è l’azione che conta.

Quella modifica ne ha scoperta un’altra: _open trattava “nessuna rotta” come “allora è un posto” e apriva Google Maps cercando il titolo. Una crew non è un posto, e “Quelli di Grazzano” digitato su Maps non è una risposta utile. Ora solo SharedKind.place finisce sulle mappe.

Bug 3 — la ricerca crew cercava nel campo sbagliato

search_screen.dart cercava le crew con ilike su vibe_tag, mentre gli eventi cercano title e i locali name. Digitare il nome di una crew non trovava niente; digitare il suo genere l’avrebbe trovata.

Ora or(title.ilike, vibe_tag.ilike), con il termine ripulito da , ( ) — sono i caratteri con cui PostgREST delimita un filtro or, e lasciarli dentro trasforma una ricerca tipo “rock, techno” in un filtro malformato che fa fallire l’intera query.

Verificato in produzione: il filtro vecchio restituisce 0 righe per la crew creata quella sera, il nuovo 1.

Bug 4 — la chat della crew non si creava

Segnalato subito dopo: “ok però non mi si crea la chat”.

Causa: colonna inesistente

squad_detail_sheet._openSquadChat creava la chat di gruppo, poi salvava l’id con update crews set chat_id = …. crews.chat_id non esisteva. PostgREST rifiutava la scrittura, scattava il catch, l’utente vedeva “impossibile aprire la chat” e non entrava mai.

E siccome l’id non poteva essere salvato, Squad.chatId restava null per sempre: ogni tocco creava un’altra chat.

Prova schiacciante: la crew “Andiamo al Milk?” ha cinque chat di gruppo create fra le 06:02:07 e le 06:02:10 del 22 aprile. “PiselloPalliamo”, “Sborra”, “House Nation” e “cassino” ne hanno due ciascuna.

La colonna da sola non sarebbe bastata

Le policy su crews consentono l’UPDATE solo al creatore (Creators can update own crews). Per qualsiasi altro membro l’update non combacia con nessuna riga — e PostgREST considera un update a zero righe un successo, quindi non c’è nemmeno un errore da intercettare. Il collegamento non sarebbe mai stato scritto e il tocco successivo avrebbe creato un’altra chat, in silenzio.

Due meccanismi indipendenti (RLS + corsa fra tocchi) producevano lo stesso duplicato. Per questo la correzione è una funzione atomica, non tre chiamate dal client.

get_or_create_crew_chat(p_crew_id)

SECURITY DEFINER, search_path = ''. Fa tutto sotto un FOR UPDATE sulla riga della crew, così il secondo chiamante aspetta e poi trova la chat che ha appena scritto il primo.

Poiché SECURITY DEFINER scavalca RLS, i controlli dentro la funzione SONO l’autorizzazione: senza di essi la funzione regalerebbe a qualsiasi account loggato l’ingresso in qualsiasi conversazione di crew.

  1. autenticato;
  2. is_approved() — il gate della beta;
  3. membro della crew.

Aggiunge inoltre tutti i membri della crew come partecipanti. Il client vecchio chiamava createGroupChat(participantIds: []), quindi la chat conteneva una persona sola e il resto della crew non poteva vederla.

Collaudata in transazione annullata: estraneo respinto, due chiamate → una sola chat con lo stesso id, tutti i membri dentro, crews.chat_id collegato.

Ripristino

3 crew ricollegate alla chat che avevano già. Scelta per numero di messaggi prima, data poi: i duplicati non sono intercambiabili, la conversazione finiva sempre nell’ultima chat creata, quindi prendere la più vecchia avrebbe attaccato la crew a una stanza vuota orfanando il thread vero.

Pulizia (autorizzata dall’utente, 2 ago 2026)

Il primo ripristino pretendeva chats.created_by = crews.creator_id e aveva collegato solo 3 crew. Condizione sbagliata: i duplicati li generavano soprattutto i membri non creatori — cioè proprio il caso che RLS scartava in silenzio. “Andiamo al Milk?” aveva sette chat, tutte create da un account che non aveva creato la crew.

Ripristino allargato al solo titolo, con la condizione un solo crew per quel titolo verificata nella query invece che data per scontata.

Poi cancellazione, con tre condizioni di cui la terza è quella che conta: una chat con anche un solo messaggio non viene mai toccata. Non è stato cancellato niente di detto da nessuno; sono sparite stanze vuote che nessuno poteva più raggiungere.

Risultato verificato in produzione:

chat vuote rimosse12
crew con esattamente una chat collegata7 su 7
messaggi ancora presenti47
chat senza partecipanti0
righe partecipante orfane0

Le conversazioni con contenuto sono tutte sopravvissute: PiselloPalliamo 3 messaggi, Andiamo al Milk? 1, House Nation 1, Sborra 1.

Cosa non è stato fatto

Nessun test di regressione. CrewRepository costruisce CrewService() direttamente e CrewService legge Supabase.instance.client in un inizializzatore di campo: non esiste un punto dove iniettare un finto. Aggiungerlo vale la pena — questa classe di bug (un parametro che esiste in fondo allo stack e nessuno glielo passa) è invisibile a flutter analyze ed è già successa due volte.

Le crew già esistenti restano orfane. Non è possibile indovinare a quale evento andassero collegate. Vanno ricollegate a mano o ricreate.

fix-crew-creation-v2 — descrive la selezione dell’evento collegato come funzionante: lo era nella UI, non nel salvataggio feature-chat-sharing — origine delle rotte sbagliate feature-crew-chat — crew e chat