Chore — Rename loading_widgets.dartloading_skeletons.dart

Commit: chore(loading): rename loading_widgets.dart to loading_skeletons.dart and document split · Branch: edit/mobile-audit-ux-polish · Audit: 2026-04-20 UX polish (finding L3, riveduto)

Cosa non andava (L3, riveduto)

L’audit iniziale aveva marcato L3 come “doppio file loading_widget.dart. La verifica ha mostrato una situazione più sottile:

Due file esistevano nel repo, distinti per una singola lettera:

  • lib/shared/widgets/loading_widget.dart (singolare)
  • lib/shared/widgets/loading_widgets.dart (plurale)

Non erano duplicati — contenevano classi distinte:

FileClassi esportateRegistro
loading_widget.dartLoadingWidget, LoadingOverlay, ShimmerLoading, ListLoadingWidget, CardLoadingWidget, PullToRefreshLoading, InfiniteScrollLoadingGenerico, theme-aware (usa Theme.of(context).colorScheme)
loading_widgets.dartLoadingScreen, LoadingIndicator, EventCardSkeleton, EventListSkeleton, ProfileHeaderSkeleton, ShimmerBox, InlineLoader, LoadMoreIndicator, RefreshLoadingIndicatorBrand-locked (usa AppTheme.electricCoral), skeleton domain-specific (event card, profile)

Il vero bug era la trap cognitiva del naming:

  • Due file che differiscono per un carattere → impossibile distinguerli in autocomplete.
  • Contenuti sovrapposti in concetto (LoadingWidget vs LoadingIndicator, InfiniteScrollLoading vs LoadMoreIndicator) → confusion su quale importare.
  • Solo feed_screen.dart consumava il plurale, e solo per EventListSkeleton → singleton-consumer, zero impact per rename.

Decisione

Rinominare il plurale in qualcosa di semanticamente distinto. Scelta: loading_skeletons.dart — riflette il contenuto prevalente (skeleton loader per card, profile, event).

Lasciare il singolare con il nome attuale loading_widget.dart — è il file con il consumer count più alto (5 screens) e un rename propagherebbe più diff.

Aggiunto header docstring (/// ... + library; directive) a entrambi i file per esplicitare il contratto:

// loading_widget.dart
/// Generic, theme-aware loading primitives.
/// Rule of thumb:
///   - Need a neutral spinner/overlay/shimmer primitive? → this file.
///   - Need an event-card or profile-header skeleton? → loading_skeletons.dart.
library;
// loading_skeletons.dart
/// Brand-locked loading skeletons and branded indicators.
/// Rule of thumb:
///   - Need a shimmer that looks like an event card? → this file.
///   - Need a neutral spinner on a secondary surface? → loading_widget.dart.
library;

La library; directive serve a Dart per associare il docstring al file, non alla prima dichiarazione sottostante. Senza library;, flutter analyze emette warning “Dangling library doc comment”.

Diff riassuntivo

Rinominato

lib/shared/widgets/loading_widgets.dart → lib/shared/widgets/loading_skeletons.dart

Git mv preserva la storia del file — git log --follow lib/shared/widgets/loading_skeletons.dart mostra ancora tutti i commit precedenti.

lib/features/feed/screens/feed_screen.dart

-import 'package:flow_app/shared/widgets/loading_widgets.dart';
+import 'package:flow_app/shared/widgets/loading_skeletons.dart';

Header aggiunto a entrambi i file (estratto)

Vedi sopra — /// ... + library; directive.

Perché non “merge tutto in un file solo”

Opzione scartata: consolidare i due file in un unico lib/shared/widgets/loading.dart. Motivazioni:

  • Separation of concerns semantica: “primitive neutre” e “skeleton brandati” sono due layer diversi. Primitive → base, skeleton → usa le primitive + brand. Tenerle separate documenta la gerarchia.
  • Tree-shaking Dart non è Rollup: un file con 15 classi viene compilato tutto insieme — non c’è il beneficio “import solo ciò che serve” che darebbe un merge in ESM/Rollup.
  • Rollback-safety: un rename è atomico. Un merge richiede decidere chi tiene i nomi, risolvere conflitti API, aggiornare 6+ import. Un task da ~1h → rimandato a pass dedicato.

Decisioni deferite

  • Merge delle classi analoghe: LoadingWidget (singolare) e LoadingIndicator (plurale, brand) hanno lo stesso API. Idealmente ne resta una sola — quella brand, perché il singolare non è usato mai con il suo colore custom. Task futuro.
  • InfiniteScrollLoading vs LoadMoreIndicator: entrambi fanno lo stesso lavoro. Eliminare uno richiede scegliere quale API è migliore (message-aware vs zero-config). Task futuro.
  • ShimmerLoading (custom) vs Shimmer.fromColors (package shimmer): il singolare ha una re-implementazione del pattern shimmer; il plurale usa il package. Due implementazioni → un po’ di waste. La re-implementazione fu probabilmente creata prima che il package venisse aggiunto. Candidato per sostituzione + rimozione di un file. Task futuro.

Verifica

flutter analyze lib/shared/widgets/loading_skeletons.dart \
  lib/shared/widgets/loading_widget.dart \
  lib/features/feed/screens/feed_screen.dart
# → No issues found!

Lessons

  • “Doppio file” ≠ “duplicato”. L’audit aveva intuito un problema (due nomi quasi identici) ma diagnosticato male (“doppio file”). Un file di audit che descrive solo i sintomi senza verificare le classi è una guida utile ma non un piano esecutivo. Sempre aprire i file prima di agire.
  • Naming è UX per i dev. Un autocomplete che suggerisce loading_widget.dart vs loading_widgets.dart è una trap quotidiana: ci si prende il primo suggerito, si importa il wrong file, si scopre solo in build. Differenziare il nome ha zero costo e ripaga per sempre.
  • library; directive è Dart-specifico e non opzionale per docstring top-of-file. In Dart, un /// comment prima di un import/class si associa a quella dichiarazione. Per avere un doc del file intero (visibile in dartdoc, IDE hover sul file) serve library directive anonima dopo il comment. Senza, il comment è dangling.
  • Un consumer solo rende safe il rename. Grep prima dell’azione ha mostrato che solo feed_screen.dart importa il plurale. Un rename con 1 import è un minuto di lavoro. Con 20 import sarebbe un task da 10x e coordinamento multi-PR. Regola flow: rename files only when consumer count is low; else wrap + deprecate first.