Skip to content

Collection folder rails: reuse Home's actual row widget - #282

Merged
ghbarker merged 1 commit into
feature/post-refactorfrom
fix/collection-rows-match-home
Sep 8, 2026
Merged

ghbarker merged 1 commit into
feature/post-refactorfrom
fix/collection-rows-match-home

Conversation

@ghbarker

@ghbarker ghbarker commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Why

The user (testing on the Shield) reported the collection folder menu still doesn't match Home's row design after PR #277. That PR made rails use homeRailPosterWidth() for poster WIDTH, but the rails themselves still rendered through DiscoverShelfScope/SeeAllPosterGrid — the Discover feature's own card widget (CatalogItemTile). Matching width alone left the card corner radius, shadow, focus ring, hover-grow scale/timing and row gutter all different from an actual Home row, which is painted by a completely different widget family: BoardCell (in lib/screens/search/board_cell.dart), wrapping CardFocusRise for the focus/hover chrome, inlined directly into search_screen.dart's _buildRow.

What

Full widget reuse — not a matched-properties fallback. _buildRow in search_screen.dart is a private method on the giant SearchScreen state (tightly coupled to _rowNodes, _hero, _boardRuntime, etc.), so it can't be called directly from another screen. But the actual per-poster widget it composes, BoardCell, has zero such coupling — it already takes plain callbacks and is reused as-is by the Canvas/Promenade board stages (stage_shelf_content.dart, canvas_board_stage.dart). It was the reusable unit all along.

Added lib/screens/search/board_shelf_row.dart — BoardShelfRow, a small standalone widget that lays out one horizontal row of BoardCells with Home's exact row gutter (13px outer / 11px per card) and pagination threshold (900px), owning its own focus-node pool (mirroring SeeAllPosterGridState's bookkeeping) and exposing focusFirst() for a host's DPAD ladder. It lives next to BoardCell and ContinueWatchingRow (Home's other extractable row-shell widget) rather than under lib/widgets/, since BoardCell is itself under lib/screens/search/ and the repo's layering check forbids widgets/ importing from screens/ (tool/check_layering.dart) — this keeps the violation count unchanged (still 42, at the ceiling).

CollectionFolderScreen._buildRail now renders each rail through BoardShelfRow instead of DiscoverShelfScope + SeeAllPosterGrid, preserving every rail-specific behavior: the rail-to-rail DPAD ladder (_focusRailAbove/_focusRailBelow), the "All" row, per-rail pagination (_loadMoreRail), folder switching, and PR #277's "no See-all link, whole header opens the rail" behavior. The merged "All" grid and the Tabs layout's single-list view are intentionally left on SeeAllPosterGrid — they're full poster walls (multi-column, titled), not rows, and were never meant to mimic Home.

Zero changes to search_screen.dart, continue_watching_row.dart, board_cell.dart, or card_focus_rise.dart — Home's own rendering code is untouched, so there's no risk of Home drifting from itself.

How verified

  • flutter test --no-pub test/board_shelf_row_test.dart — new tests: renders one BoardCell/CardFocusRise per item (proves the actual class, not a lookalike), SELECT opens the focused item, RIGHT walks cards and LEFT at column 0 is safely swallowed (this screen has no sidebar to hand off to, unlike the Home board), UP/DOWN call the host's exit callbacks from any card, the trailing pagination spinner appears, focusFirst() on an empty row no-ops. All 6 pass.
  • flutter test --no-pub test/collection_folder_screen_test.dart — all 6 existing tests (from Collection folder: plain rows in Rows layout, no See all on collection rails #277) pass unchanged: layout chrome, DPAD ladder to the Retry button, Tabs' filter-bar ladder.
  • flutter test --no-pub on Home's regression suite (home_board_controller_test.dart, home_row_id_grammar_test.dart, home_row_order_test.dart, home_row_registry_test.dart, search_board_cell_origin_test.dart, search_board_runtime_origin_test.dart, spotlight_board_compact_test.dart, spotlight_board_test.dart) — all 103 pass, confirming zero regression to Home's own rows (expected, since none of those files changed).
  • dart analyze on both changed/added lib files and the new test file — 0 issues.
  • dart run tool/check_layering.dart — 42 violations, ceiling 42, unchanged (exit 0).
  • python tool/analyze_baseline.py — my files produce no new diagnostics; the script's pre-existing failure (UNAWAITED_RETURN_IN_TRY_BLOCK in unrelated services files) is present on the base branch too and untouched by this diff.
  • No golden tests cover the collection folder screen or Home's board rows (checked grep -rl matchesGoldenFile test), so none needed regenerating.

Risks

  • A rail whose first page returns few enough items to not overflow the row's width (no scroll ever fires) won't auto-paginate on non-TV/mouse — SeeAllPosterGrid had a _maybeAutoFill safeguard for this that BoardShelfRow doesn't replicate. This matches Home's own _buildRow, which has the same gap, so it's parity rather than a regression, but flagging it as a known edge case (very small catalogs only).
  • Collection rails stay portrait/2:3 only; they don't follow the "Home Cards: Landscape" preference some users have. That preference was never wired into homeRailPosterWidth's collection-rail usage before this PR either — out of scope here, not a regression.

Rails now render through a new BoardShelfRow, wrapping the same BoardCell
(and its CardFocusRise scale/shadow/focus-ring/hover-grow chrome) Home's
own board rows paint, instead of DiscoverShelfScope/SeeAllPosterGrid's
Discover-stage card. Matching poster width alone (PR #277) left the card
corner radius, shadow, focus ring, hover-grow and row gutter different
from Home's — this makes a folder's rails an actual Home row rather than
an approximation of one.

BoardShelfRow owns its own focus-node pool and exposes focusFirst() so
the folder screen's existing rail-to-rail DPAD ladder, All row and
per-rail pagination are unchanged. The merged "All" grid and Tabs
single-list view stay on SeeAllPosterGrid — they're poster walls, not
rows, and were never meant to read as Home.
@ghbarker
ghbarker merged commit 7d7c35a into feature/post-refactor Sep 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant