From b3c689bd013d8028e373ca91b415066245ac2f21 Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 1 Aug 2026 23:21:31 -0300 Subject: [PATCH] fix(dashboard): stop the desktop 3-column layout from collapsing to zero height IntrinsicHeight wrapping a Row whose children were Expanded + SingleChildScrollView (itself nested inside the page's own SingleChildScrollView) gave every column unbounded/conflicting height constraints. On the web renderer this collapsed Jogadores/NPCs/Log to zero height, leaving only the sidebar and session card visible - no error surfaced in the UI since Flutter contains layout failures to the affected subtree. Removed IntrinsicHeight and the per-column SingleChildScrollView; the columns now size to their natural content height and scroll along with the rest of the page via the single outer SingleChildScrollView. Also added a takeException() check to the desktop layout test, since find.text() alone doesn't detect a RenderObject that built fine but failed to lay out. --- .../screens/session_dashboard_screen.dart | 37 ++++++++++--------- .../session_dashboard_screen_test.dart | 18 +++++++++ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/features/sessions/presentation/screens/session_dashboard_screen.dart b/lib/features/sessions/presentation/screens/session_dashboard_screen.dart index 31f198c..6b02abd 100644 --- a/lib/features/sessions/presentation/screens/session_dashboard_screen.dart +++ b/lib/features/sessions/presentation/screens/session_dashboard_screen.dart @@ -544,9 +544,14 @@ class _SessionDashboardScreenState required NpcInstancesState npcInstancesState, required SessionsState sessionsState, }) { + // Note: these columns intentionally do NOT get their own + // SingleChildScrollView - the whole page already scrolls via the + // SingleChildScrollView below, and nesting an unbounded-height + // scrollable inside another (previously also wrapped in + // IntrinsicHeight) collapsed every column to zero height on web. Widget column(String title, Widget body, {Widget? headingTrailing}) { return Expanded( - child: SingleChildScrollView( + child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -600,23 +605,21 @@ class _SessionDashboardScreenState children: [ _sessionInfoCard(), const SizedBox(height: 24), - IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - leftColumn, - column( - 'NPCs em Combate', - _npcsBody( - npcInstancesState, - isGm, - showNpcHp, - crossAxisCount: 2, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + leftColumn, + column( + 'NPCs em Combate', + _npcsBody( + npcInstancesState, + isGm, + showNpcHp, + crossAxisCount: 2, ), - column('Log de Combate', _logBody(isGm)), - ], - ), + ), + column('Log de Combate', _logBody(isGm)), + ], ), ], ), diff --git a/test/features/sessions/presentation/screens/session_dashboard_screen_test.dart b/test/features/sessions/presentation/screens/session_dashboard_screen_test.dart index a01f8b1..48214e7 100644 --- a/test/features/sessions/presentation/screens/session_dashboard_screen_test.dart +++ b/test/features/sessions/presentation/screens/session_dashboard_screen_test.dart @@ -344,10 +344,28 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); + // Regression guard: catches layout errors (e.g. IntrinsicHeight + + // nested SingleChildScrollView) that Flutter swallows into a + // per-subtree error widget instead of failing the pump outright - + // find.text() alone wouldn't detect those, since the Text widgets + // are still built even when their RenderObject fails to lay out. + expect(tester.takeException(), isNull); + expect(find.text('SESSÕES'), findsOneWidget); expect(find.text('Voltar à campanha'), findsOneWidget); // FAB is mobile-only. expect(find.byKey(const Key('session_add_npc_fab')), findsNothing); + + // Regression guard: the 3-column layout previously collapsed to zero + // height on web (IntrinsicHeight + nested SingleChildScrollView), + // hiding every column's content behind the sidebar/session card. + expect(find.text('Jogadores'), findsOneWidget); + expect(find.text('NPCs em Combate'), findsOneWidget); + expect(find.text('Log de Combate'), findsOneWidget); + expect( + find.text('Nenhum jogador com ficha ativa nesta campanha.'), + findsOneWidget, + ); }); }); }