Chore — Router cleanup, orfano rimosso, vendor dashboard collegato

Commit: chore(router): register vendor dashboard, remove orphan create_campaign screen · Branch: edit/mobile-audit-ux-polish · Audit: 2026-04-20 UX polish (finding C4)

Cosa non andava

L’audit ha identificato tre tipi di “codice sospetto” nel layer UI:

  1. Duplicato di flusso accessibile: CreateCampaignScreen era uno screen pienamente implementato, ma il flusso “creare campagne” è già coperto da CreateEventScreen (registrato su /home/create-event, raggiungibile dal CreationBottomSheet). Zero route nel router, zero riferimenti reali, genuine orphan.
  2. Screen polished non registrato: VendorDashboardScreen — una B2B dashboard per venue/organizer (biglietti venduti, entrate, views profilo, feedback) completa di UI, tokens brand allineati, ma senza route nel router. Un asset compiuto che giaceva inutilizzato.
  3. Falsi orfani da non toccare (corretti in fase di verifica):
    • CrewMatchmakingWizard → raggiungibile via showModalBottomSheet da friends_screen.dart:122 e chats_screen.dart:246.
    • CreateSquadScreen → il nome file dice _screen.dart ma la classe esportata è CreateSquadSheet (bottom sheet). È invocato dal feed come onCreateTap callback del SquadBentoSection. Questa asimmetria file↔class è ciò che ha fatto credere all’audit iniziale che fosse orfano.

Decisioni

Orfano candidatoReale statusDecisione
CreateCampaignScreenDuplicato venue + zero referenceRimosso (+ cartella features/campaigns/ rimossa, era single-file)
VendorDashboardScreenPolished, self-referencedRegistrato a /profile/vendor-dashboard
CreateSquadScreen (file → CreateSquadSheet class)Bottom sheet USATA dal feedInvariato (lint: rinominare file → create_squad_sheet.dart in pass futuro)
CrewMatchmakingWizardUsato via showModalBottomSheetInvariato (deep-link non adatto — rompe drag gesture)

Diff riassuntivo

lib/core/router/app_router.dart

 import 'package:flow_app/features/social/screens/create_crew_screen.dart';
 import 'package:flow_app/features/reviews/screens/create_review_screen.dart';
 import 'package:flow_app/features/gamification/screens/leaderboard_screen.dart';
+import 'package:flow_app/features/dashboard/screens/vendor_dashboard_screen.dart';
                   GoRoute(
                     path: 'leaderboard',
                     parentNavigatorKey: rootNavigatorKey,
                     builder: (context, state) => const LeaderboardScreen(),
                   ),
+                  GoRoute(
+                    path: 'vendor-dashboard',
+                    parentNavigatorKey: rootNavigatorKey,
+                    builder: (context, state) => const VendorDashboardScreen(),
+                  ),
                 ],

Rimozione

  • lib/features/campaigns/screens/create_campaign_screen.dart (deleted)
  • lib/features/campaigns/ (cartella vuota rimossa)

Perché il vendor dashboard sotto /profile e non un branch a sé

La navigation shell ha 4 branch (home / search / social / profile). Creare un 5° branch “Organizer” lo trasformerebbe in un’icona permanente nella bottom nav, ma solo una frazione degli utenti ha un profilo venue o PR. Un branch per una minoranza = UI debt per la maggioranza.

Soluzione minima: il ramo /profile ospita già pr-dashboard (PR panel), leaderboard, my-crews, verification-request, ticket-wallet. Vendor dashboard è un concept parallelo → stessa casa semantica. In un pass futuro il ProfileScreen avrà una sezione “Business tools” condizionale (visibile solo a utenti con role == venue || role == pr).

Decisioni deferite

  • UI entry-point nel ProfileScreen: al momento /profile/vendor-dashboard è raggiungibile solo via deep-link (context.push('/profile/vendor-dashboard')). Aggiungere un tile discoverable richiede prima (a) conferma che il dato user.role è in schema Supabase (b) una skeleton UI per i dati reali (i dati attuali sono mock hardcoded). Da fare in un task dedicato “venue profile surface”.
  • Import inutilizzato di go_router in vendor_dashboard_screen.dart: lasciato — non in scope di questo chore. Sarà preso dal prossimo pass di lint cleanup.
  • Rename create_squad_screen.dartcreate_squad_sheet.dart: fatto in commit 666575c (round 2 polish). Import in feed_screen.dart aggiornato.
  • Wizard matchmaking come deep-link: non aggiungere /social/matchmaking. Una wizard full-route avrebbe semantica diversa (history stack + back gesture) che non corrisponde all’esperienza pensata (sheet che si può chiudere con drag).

Verifica

flutter analyze lib/core/router/app_router.dart \
  lib/features/feed/screens/feed_screen.dart \
  lib/features/dashboard/screens/vendor_dashboard_screen.dart \
  lib/features/social/screens/create_squad_screen.dart
# → No issues found!

Check manuale:

  • context.push('/profile/vendor-dashboard') da qualsiasi entry-point raggiunge la dashboard
  • Il feed ancora si carica, mostra le bento squad, e tap su “Crea squad” apre la bottom sheet
  • Nessun errore “undefined name ‘CreateCampaignScreen’” in flutter analyze

Lessons

  • “Orfano” va verificato, non dedotto. L’audit iniziale aveva marcato 3 candidati orfani. La verifica ha confermato solo uno. Gli altri due erano falsi positivi per motivi diversi:
    • CrewMatchmakingWizard → invocato via showModalBottomSheet (non via GoRoute). Grep solo su GoRoute l’avrebbe mancato.
    • CreateSquadScreennome file diverso dal nome classe. Grep su CreateSquadScreen non trova nulla; grep su CreateSquadSheet trova 3 call site. La lezione operativa: grep sul nome del simbolo, non del file.
  • Nome file ↔ nome classe: mantenere coerenza. Un file create_squad_screen.dart che esporta CreateSquadSheet è un’ambiguità che costa tempo di indagine ogni volta che qualcuno ci inciampa. Convenzione flow: *_screen.dart → class *Screen; *_sheet.dart → class *Sheet. Deviazioni = debt.
  • Una route registrata è una promessa. Registrare VendorDashboardScreen significa che da qui in avanti il layer UI garantisce l’esistenza di una destinazione /profile/vendor-dashboard. Code che dipende da questa route (future deep-link notifications, share urls) ora può farsi avanti senza paura di URL 404. È il meccanismo con cui i routers “contrattualizzano” l’app.
  • Delete-then-restore è OK se il safety net esiste. Questo chore ha avuto una falsa partenza: ho rimosso create_squad_screen.dart prima di verificare che la classe al suo interno fosse CreateSquadSheet. La restore è stata immediata perché esisteva una copia nei .worktrees/*/lib/... — backups parallel branch. La lezione: un ambiente con worktree multipli sopravvive agli errori di chi prova cose. Un solo working tree + zero remote = un’unica copia di verità = zero margine di errore.