From 48568fd4123695d1ead5d8e1de101e3d73e03eaf Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 2 Jul 2026 02:47:38 +0200 Subject: [PATCH] perf(tv): exclude inactive hub rows from semantics Their cards can't take focus until the row activates (locked-hub model), and the per-frame semantics pass scales with node count. --- lib/widgets/tv_browse_rail.dart | 28 ++++++++++++++++-------- lib/widgets/tv_spotlight_background.dart | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/widgets/tv_browse_rail.dart b/lib/widgets/tv_browse_rail.dart index e0f2fa00..835aeb42 100644 --- a/lib/widgets/tv_browse_rail.dart +++ b/lib/widgets/tv_browse_rail.dart @@ -1315,15 +1315,25 @@ class TvBrowseRailState extends State { child: Stack( clipBehavior: Clip.none, children: [ - _buildHubRailList( - hub: hub, - hubIndex: hubIndex, - episodePosterMode: episodePosterMode, - metrics: metrics, - scale: scale, - fullCardLayout: fullCardLayout, - scrollController: scrollController, - totalCount: totalCount, + // Inactive rows drop out of the semantics tree: their cards + // can't take focus until the hub is activated (locked-hub + // model), and the per-frame semantics pass on a11y-active TV + // boxes scales with node count — the active row's nodes are + // the only ones a screen reader can act on anyway. + ListenableSelector( + listenable: _focusModel, + selector: () => _focusModel.hubIndex == hubIndex, + builder: (context, isActive, child) => ExcludeSemantics(excluding: !isActive, child: child), + child: _buildHubRailList( + hub: hub, + hubIndex: hubIndex, + episodePosterMode: episodePosterMode, + metrics: metrics, + scale: scale, + fullCardLayout: fullCardLayout, + scrollController: scrollController, + totalCount: totalCount, + ), ), Positioned.fill( left: -leftOverflow, diff --git a/lib/widgets/tv_spotlight_background.dart b/lib/widgets/tv_spotlight_background.dart index 88db7c15..4eb50117 100644 --- a/lib/widgets/tv_spotlight_background.dart +++ b/lib/widgets/tv_spotlight_background.dart @@ -102,7 +102,7 @@ class TvSpotlightBackground extends StatelessWidget { // Expand instead of the default loose centered Stack so the // info keeps filling the region and bottom-left aligning. layoutBuilder: (currentChild, previousChildren) => - Stack(fit: StackFit.expand, children: [...previousChildren, if (currentChild != null) currentChild]), + Stack(fit: StackFit.expand, children: [...previousChildren, ?currentChild]), child: KeyedSubtree( key: ValueKey(media.globalKey), child: LayoutBuilder(