Appearance preview: genuinely pinned header, unified and larger - #284
Merged
ghbarker merged 1 commit intoSep 8, 2026
Merged
Conversation
The live preview scrolled away with the rest of the Appearance pane (only its own top-of-pane position, not the top of the screen) and the compact dock shown while hovering a Screen-layouts row squeezed the stage into a 78px-tall box — both flagged on the Shield. Restructure each surface (SettingsSpotlightShell, SettingsTvLayout) so the preview is a fixed header above an Expanded scrolling body, instead of a Stack overlay riding on top of it. Retire AppearancePreviewDock: AppearancePreviewHost is now the one preview, pinned directly, and LayoutPreviewChannel is extended with pointLook/unpointLook/restLook so a Look hover and a Screen-layouts row hover share the same pointed/resting model instead of two parallel notifiers. The stage slot scales with available width up to a new named constant, well past the old dock's 78px and LayoutPreviewStage's own 320x180 design canvas. Pane-node accounting for the TV two-pane shell is unchanged: the preview still claims exactly node zero, whether or not buildSettingsCategoryChildren inlines the widget itself (includeAppearancePreview) or a shell builds it separately as the pinned header.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Shield feedback after PRs #263/#271/#275/#279: "The live preview also needs changed, it should stay pinned at the top of the screen and change based on what is hovered/last selected. The current way is way too small."
Two problems in the current system:
AppearancePreviewCardinsideAppearancePreviewHost) was only the first item in the scrolling category column — it scrolled away like anything else, not pinned to the screen at all.AppearancePreviewDock, appeared as aStack/Positioned/IgnorePointeroverlay while a Screen-layouts row was hovered/focused, but fit its stage into a fixed 78px-tall box — the "way too small" the Shield feedback names.What
AppearancePreviewDockentirely.AppearancePreviewHostis now the ONE preview. It shows the theme stage when a Look is hovered on the strip, or aLayoutPreviewStagewhen a Screen-layouts row elsewhere in the pane is hovered/focused, in the same slot — driven byLayoutPreviewChannel's pointed/resting model, extended withpointLook/unpointLook/restLookso a Look candidate and a layout candidate are one notifier instead of two parallel systems (a local_candidateState plus the channel).Stackoverlay:SettingsSpotlightShell(phone/tablet/desktop): a newpinnedHeaderBuildercallback letssettings_screen.dartsupply the preview as aColumnheader above anExpandedscrolling body — both the wide (_buildCategoryBody) and compact (_buildCompactDetail) layouts.SettingsTvLayout:_buildPane's Appearance branch is nowColumn([header, Expanded(scrollBody)])instead ofStack([paneScroller, Positioned(dock)]), with both header and body kept inside the sameFocus(onKeyEvent: _paneKey)/FocusTraversalGroupso DPAD bubbling (Left at the first chip → back to rail) is unchanged.buildSettingsCategoryChildrengainedincludeAppearancePreview(default true): it always reserves pane node zero for the preview so indices never shift, but a shell that pins the header separately passesfalseand buildsAppearancePreviewHost(focusNode: paneNodes[0])itself.kAppearancePreviewStageMaxWidth(390) /kAppearancePreviewStageAspectRatio(16:9) /kAppearancePreviewStageMaxHeight(≈219) inappearance_preview_card.dart, replacing the old dock's fixed 78px box (≈2.8x taller) — bothThemePreviewStageandLayoutPreviewStageare fitted into the same slot via the existing "fixed canvas, FittedBox" technique.ConstrainedBox+SingleChildScrollViewcapped at(viewport height × 0.75).clamp(240, 520)— inert at normal scale (natural height stays under the cap), the escape hatch only engages under extreme conditions.How verified
flutter test --no-pubon every touched test file — all pass except two pre-existing, unrelated golden failures (settings_spotlight_phone.png,settings_spotlight_tv.png— the Connections-category root dashboards, untouched by this change) that reproduce identically onorigin/feature/post-refactorbefore this branch's changes; this repo's goldens are Windows-rendered and allowlisted against the Linux goldens job.dart analyzeon every changed file: 0 errors, only 3 pre-existinguse_build_context_synchronouslyinfos unrelated to this diff.tool/check_layering.dart: 42/42 (unchanged ceiling, no new violations in touched files).tool/analyze_baseline.json: unchanged; zero new diagnostics in any file this PR touches (the baseline-check script does surface unrelatedUNAWAITED_RETURN_IN_TRY_BLOCKdrift across ~10 pre-existing files elsewhere in the repo — environmental SDK/analyzer drift, not introduced here).Regenerated goldens (Windows-rendered,
[golden]-tagged tests, all pre-existing golden tests — no new golden tests added so no new allowlist entries needed):test/goldens/settings_layout_options_phone.pngtest/goldens/settings_spotlight_desktop.pngtest/goldens/settings_spotlight_phone_appearance.pngtest/goldens/settings_spotlight_tablet.pngtest/goldens/settings_spotlight_tv_appearance.pngNew/updated tests:
test/appearance_preview_dock_test.dart— rewritten for the pinned-header architecture: pinned position unchanged across DPAD scrolling (TV @ 960×540 and 1920×1080) and pointer scrolling (desktop @ 1280); the sameAppearancePreviewHostState instance shows theme-stage content for a Look hover and layout-stage content for a Screen-layouts row hover with no duplicate preview; a flicker-regression test sweeping the pointer rapidly across the Look strip and a Screen-layouts row's chips (no exception,pumpAndSettleterminates, nothing under the pointer shifts); a size assertion against the new named constants.test/appearance_preview_tv_strip_test.dart— the old "focusing the strip reveals the whole card" scroll-reveal test replaced with "the header stays fully on screen no matter how far the pane below it is scrolled" (there is nothing left to reveal — the header was never inside the scrolled body).test/settings_spotlight_shell_test.dart— its Appearance fixture now supplies the preview throughpinnedHeaderBuilderinstead of embedding it as the first child ofcategoryBuilder's Column, matching production.appearance_preview_focus_node_release_test.dart,settings_appearance_preview_test.dart,settings_layout_options_test.dart,settings_appearance_groups_test.dart.Risks
0.75 × viewport height, clamped 240–520) is a defensive heuristic, not derived from measured content — a future caption/chip change that grows meaningfully taller could reintroduce clipping at 540p; the cap is in one place per shell if it needs raising.pinnedHeaderBuilderis a new, generic extension point onSettingsSpotlightShell— currently only Appearance uses it, but any category returning a non-null header now gets the same pinned treatment, so a future category with a tall header would need the same size discipline.settings_catalog.dartorsettings_page_registry.dart's category/group wiring beyond the minimalincludeAppearancePreviewparameter needed here, to avoid conflicting with the concurrentfeature/settings-menu-reorglane.