diff --git a/Application/ModernSetupApp/ModernSetupAppActions.c b/Application/ModernSetupApp/ModernSetupAppActions.c index c57d765..4b0df61 100644 --- a/Application/ModernSetupApp/ModernSetupAppActions.c +++ b/Application/ModernSetupApp/ModernSetupAppActions.c @@ -129,6 +129,99 @@ ModernSetupGetDashboardQuickGrid ( return TRUE; } +/** + Calculate the shared page-list row layout for setup pages. + + DashboardDensity reuses the app-owned density preference so secondary setup + pages follow the same compact/comfortable visual rhythm as the Dashboard. + Compact uses shorter rows and less padding; Comfortable preserves the + original list spacing as closely as possible. The helper also owns the + Devices preview split so drawing and navigation share one row-cap decision. + + @param[in] Ui Initialized render context. Must not be NULL. + @param[in] DashboardDensity Dashboard density preference. Compact selects + the compact list metrics; all other values use + comfortable metrics. + @param[in] HardRowCap Maximum rows the caller may show. Zero means no + selectable rows are requested. + @param[in] AllowPreviewPane TRUE when the page can use a right preview pane. + @param[out] Layout Receives the panel and list metrics. Must not be + NULL. Zeroed on entry; left zeroed on FALSE. + + @retval TRUE The content rect can host at least one requested row. + @retval FALSE Ui or Layout is NULL, HardRowCap is zero, or rows do not fit. +**/ +BOOLEAN +ModernSetupGetPageListLayout ( + IN MODERN_UI_RENDER_CONTEXT *Ui, + IN UINT8 DashboardDensity, + IN UINTN HardRowCap, + IN BOOLEAN AllowPreviewPane, + OUT MODERN_SETUP_PAGE_LIST_LAYOUT *Layout + ) +{ + BOOLEAN Compact; + UINTN TitleArea; + UINTN AvailableHeight; + UINTN AvailableRows; + UINTN PreviewGap; + UINTN ListWidth; + + if (Layout != NULL) { + ZeroMem (Layout, sizeof (*Layout)); + } + + if ((Ui == NULL) || (Layout == NULL) || (HardRowCap == 0)) { + return FALSE; + } + + Compact = (BOOLEAN)(DashboardDensity == ModernUiDashboardDensityCompact); + Layout->Panel = ModernSetupContentRect (Ui); + Layout->RowHeight = Compact ? 44 : 50; + Layout->RowStride = Compact ? 50 : 58; + Layout->HorizontalPad = Compact ? 14 : 20; + TitleArea = Compact ? 54 : 62; + + if (Layout->Panel.Width < 600) { + Layout->HorizontalPad = MAX (6, Layout->HorizontalPad / 2); + } + + Layout->RowX = Layout->Panel.X + Layout->HorizontalPad; + Layout->RowWidth = (Layout->Panel.Width > (Layout->HorizontalPad * 2)) ? + (Layout->Panel.Width - (Layout->HorizontalPad * 2)) : + MAX (1, Layout->Panel.Width); + Layout->FirstRowY = Layout->Panel.Y + TitleArea; + AvailableHeight = (Layout->Panel.Height > TitleArea) ? (Layout->Panel.Height - TitleArea) : 0; + AvailableRows = (AvailableHeight >= Layout->RowHeight) ? + ((AvailableHeight + Layout->RowStride - Layout->RowHeight) / Layout->RowStride) : + 0; + if ((AvailableRows == 0) && (AvailableHeight > 0)) { + AvailableRows = 1; + } + + Layout->MaxVisibleRows = MIN (HardRowCap, AvailableRows); + if (Layout->MaxVisibleRows == 0) { + return FALSE; + } + + if (AllowPreviewPane && (Layout->Panel.Width >= 720)) { + PreviewGap = 16; + ListWidth = (Layout->Panel.Width - (Layout->HorizontalPad * 2) - PreviewGap) / 2; + if (ListWidth >= 240) { + Layout->HasPreviewPane = TRUE; + Layout->RowWidth = ListWidth; + Layout->PreviewPanel = (MODERN_UI_RECT){ + Layout->RowX + ListWidth + PreviewGap, + Layout->FirstRowY - 8, + Layout->Panel.Width - (Layout->HorizontalPad * 2) - ListWidth - PreviewGap, + (Layout->Panel.Height > (TitleArea + 12)) ? (Layout->Panel.Height - TitleArea - 12) : Layout->RowHeight + }; + } + } + + return TRUE; +} + /** Resolve a Dashboard setup category card to its destination page and focus. @@ -410,15 +503,18 @@ ModernSetupGetPageSelectableCount ( } case PageBoot: { - MODERN_UI_RECT Panel; - UINTN MaxRows; + MODERN_SETUP_PAGE_LIST_LAYOUT Layout; - Panel = ModernSetupContentRect (Ui); - MaxRows = (Panel.Height > 96) ? ((Panel.Height - 92) / 58) : 0; - return MIN (ModernSetupGetBootCount (), MIN (MaxRows, MAX_BOOT_ROWS)); + return ModernSetupGetPageListLayout (Ui, mModernSetupPreferences.DashboardDensity, MAX_BOOT_ROWS, FALSE, &Layout) ? + MIN (ModernSetupGetBootCount (), Layout.MaxVisibleRows) : 0; } case PageDevices: - return ModernSetupGetVisibleDeviceCount (); + { + MODERN_SETUP_PAGE_LIST_LAYOUT Layout; + + return ModernSetupGetPageListLayout (Ui, mModernSetupPreferences.DashboardDensity, MAX_DEVICE_ROWS, TRUE, &Layout) ? + MIN (ModernSetupGetVisibleDeviceCount (), Layout.MaxVisibleRows) : 0; + } case PagePreferences: return MODERN_SETUP_PREFERENCE_ROW_COUNT; case PageExit: diff --git a/Application/ModernSetupApp/ModernSetupAppInternal.h b/Application/ModernSetupApp/ModernSetupAppInternal.h index 2e7cfba..69e2314 100644 --- a/Application/ModernSetupApp/ModernSetupAppInternal.h +++ b/Application/ModernSetupApp/ModernSetupAppInternal.h @@ -130,6 +130,19 @@ typedef struct { UINTN CardWidth; } MODERN_SETUP_DASHBOARD_QUICK_GRID; +typedef struct { + MODERN_UI_RECT Panel; + UINTN RowX; + UINTN RowWidth; + UINTN RowHeight; + UINTN RowStride; + UINTN FirstRowY; + UINTN MaxVisibleRows; + UINTN HorizontalPad; + BOOLEAN HasPreviewPane; + MODERN_UI_RECT PreviewPanel; +} MODERN_SETUP_PAGE_LIST_LAYOUT; + typedef struct { SETUP_PAGE Page; SETUP_FOCUS Focus; @@ -280,6 +293,15 @@ ModernSetupGetDashboardQuickGrid ( OUT MODERN_SETUP_DASHBOARD_QUICK_GRID *Grid ); +BOOLEAN +ModernSetupGetPageListLayout ( + IN MODERN_UI_RENDER_CONTEXT *Ui, + IN UINT8 DashboardDensity, + IN UINTN HardRowCap, + IN BOOLEAN AllowPreviewPane, + OUT MODERN_SETUP_PAGE_LIST_LAYOUT *Layout + ); + BOOLEAN ModernSetupGetDashboardCategoryRoute ( IN UINTN Selection, diff --git a/Application/ModernSetupApp/ModernSetupAppPages.c b/Application/ModernSetupApp/ModernSetupAppPages.c index 0569684..475592b 100644 --- a/Application/ModernSetupApp/ModernSetupAppPages.c +++ b/Application/ModernSetupApp/ModernSetupAppPages.c @@ -190,46 +190,51 @@ DrawProviderSummaryPage ( IN UINTN RowCount ) { - MODERN_UI_RECT Content; - MODERN_UI_RECT Panel; - UINTN Index; - UINTN RowY; - UINTN RowStep; - UINTN HeaderStep; - - Content = ModernSetupContentRect (Ui); - Panel = (MODERN_UI_RECT){ Content.X, Content.Y, Content.Width, MIN (Content.Height, 430) }; - RowY = Panel.Y + 58; - RowStep = 28; - HeaderStep = 20; - - DrawProviderSummarySection (Ui, Theme, Panel, Section, TRUE); - ModernUiDrawFocusFrame (Ui, Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); + MODERN_SETUP_PAGE_LIST_LAYOUT Layout; + UINTN Index; + UINTN RowY; + UINTN VisibleRows; + UINTN HeaderStep; + + if (!ModernSetupGetPageListLayout (Ui, mModernSetupPreferences.DashboardDensity, RowCount, FALSE, &Layout)) { + Layout.Panel = ModernSetupContentRect (Ui); + DrawProviderSummarySection (Ui, Theme, Layout.Panel, Section, TRUE); + ModernUiDrawFocusFrame (Ui, Layout.Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); + return; + } + + RowY = Layout.FirstRowY; + VisibleRows = 0; + HeaderStep = 20; + + DrawProviderSummarySection (Ui, Theme, Layout.Panel, Section, TRUE); + ModernUiDrawFocusFrame (Ui, Layout.Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); - for (Index = 0; Index < RowCount; Index++) { + for (Index = 0; (Index < RowCount) && (VisibleRows < Layout.MaxVisibleRows); Index++) { if ((Groups != NULL) && (Groups[Index] != NULL)) { - if ((RowY + 24) > (Panel.Y + Panel.Height)) { + if ((RowY + HeaderStep) > (Layout.Panel.Y + Layout.Panel.Height)) { break; } - DrawProviderSubsectionHeader (Ui, Theme, Panel.X + 22, RowY, Panel.Width - 44, Groups[Index]); + DrawProviderSubsectionHeader (Ui, Theme, Layout.RowX, RowY, Layout.RowWidth, Groups[Index]); RowY += HeaderStep; } - if ((RowY + 24) > (Panel.Y + Panel.Height)) { + if ((RowY + Layout.RowHeight) > (Layout.Panel.Y + Layout.Panel.Height)) { break; } DrawProviderSummaryInfoRow ( Ui, Theme, - Panel.X + 22, + Layout.RowX, RowY, - Panel.Width - 44, + Layout.RowWidth, Labels[Index], Values[Index] ); - RowY += RowStep; + RowY += Layout.RowStride; + VisibleRows++; } } @@ -260,30 +265,40 @@ DrawBoot ( CHAR16 Value[96]; CONST CHAR16 *State; BOOLEAN IsSelected; - MODERN_UI_RECT Panel; - UINTN RowX; - UINTN RowWidth; - UINTN MaxRows; + MODERN_SETUP_PAGE_LIST_LAYOUT Layout; MODERN_UI_ROW_MODEL RowModel; - Panel = ModernSetupContentRect (Ui); - RowX = Panel.X + 20; - RowWidth = Panel.Width - 40; - ModernUiDrawPanel (Ui, Panel, Theme); - ModernUiDrawFocusFrame (Ui, Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); - ModernUiDrawText (Ui, Panel.X + 20, Panel.Y + 20, ModernUiGetString (ModernUiStringBootInstruction), Theme->MutedText, Theme->Surface); + if (!ModernSetupGetPageListLayout (Ui, mModernSetupPreferences.DashboardDensity, MAX_BOOT_ROWS, FALSE, &Layout)) { + Layout.Panel = ModernSetupContentRect (Ui); + } + + ModernUiDrawPanel (Ui, Layout.Panel, Theme); + ModernUiDrawFocusFrame (Ui, Layout.Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); + ModernUiDrawText ( + Ui, + Layout.RowX, + Layout.Panel.Y + 20, + ModernUiGetString (ModernUiStringBootInstruction), + Theme->MutedText, + Theme->Surface + ); BootOptions = NULL; Status = ModernSetupGetCachedBootOptions (&BootOptions, &BootOptionCount); if (EFI_ERROR (Status) || (BootOptions == NULL)) { - ModernUiDrawText (Ui, Panel.X + 20, Panel.Y + 66, ModernUiGetString (ModernUiStringNoBootOptions), Theme->Warning, Theme->Surface); + ModernUiDrawText ( + Ui, + Layout.RowX, + Layout.FirstRowY, + ModernUiGetString (ModernUiStringNoBootOptions), + Theme->Warning, + Theme->Surface + ); return; } - MaxRows = (Panel.Height > 96) ? ((Panel.Height - 92) / 58) : 0; - MaxRows = MIN (MaxRows, MAX_BOOT_ROWS); - for (Index = 0; (Index < BootOptionCount) && (Index < MaxRows); Index++) { - Y = Panel.Y + 62 + Index * 58; + for (Index = 0; (Index < BootOptionCount) && (Index < Layout.MaxVisibleRows); Index++) { + Y = Layout.FirstRowY + (Index * Layout.RowStride); State = BootOptions[Index].Active ? ModernUiGetString (ModernUiStringActive) : ModernUiGetString (ModernUiStringInactive); IsSelected = (BOOLEAN)((Focus == SetupFocusContent) && (Index == Selected)); UnicodeSPrint ( @@ -302,7 +317,7 @@ DrawBoot ( BootOptions[Index].Hidden ? L" / Hidden / " : L" / ", BootOptions[Index].Category ); - RowModel.Rect = (MODERN_UI_RECT){ RowX, Y - 8, RowWidth, 42 }; + RowModel.Rect = (MODERN_UI_RECT){ Layout.RowX, Y - 8, Layout.RowWidth, Layout.RowHeight - 8 }; RowModel.Prompt = Line; RowModel.Value = Value; RowModel.Role = IsSelected ? ModernUiRowSelected : ModernUiRowNormal; @@ -310,9 +325,9 @@ DrawBoot ( ModernUiEngineDrawRows (Ui, &RowModel, 1, Theme); ModernUiDrawTextFit ( Ui, - RowX + 20, + Layout.RowX + Layout.HorizontalPad, Y + 18, - RowWidth - 40, + (Layout.RowWidth > (Layout.HorizontalPad * 2)) ? (Layout.RowWidth - (Layout.HorizontalPad * 2)) : Layout.RowWidth, BootOptions[Index].FilePathSummary, Theme->MutedText, IsSelected ? Theme->SelectedBand : Theme->Surface @@ -320,7 +335,14 @@ DrawBoot ( } if (BootOptionCount == 0) { - ModernUiDrawText (Ui, Panel.X + 20, Panel.Y + 66, ModernUiGetString (ModernUiStringNoBootOptions), Theme->Warning, Theme->Surface); + ModernUiDrawText ( + Ui, + Layout.RowX, + Layout.FirstRowY, + ModernUiGetString (ModernUiStringNoBootOptions), + Theme->Warning, + Theme->Surface + ); } } @@ -720,32 +742,29 @@ DrawDevices ( IN UINTN Selected ) { - EFI_STATUS Status; - MODERN_UI_DEVICE_ENTRY *Entries; - UINTN EntryCount; - UINTN Index; - UINTN HiiCount; - UINTN VisibleHiiCount; - UINTN VisibleDeviceCount; - UINTN VisibleRows; - UINTN RowY; - CHAR16 Line[168]; - CHAR16 Summary[96]; - BOOLEAN IsSelected; - MODERN_UI_RECT Panel; - UINTN RowX; - UINTN RowWidth; - MODERN_UI_ROW_MODEL RowModel; - MODERN_UI_DEVICE_ENTRY *SelectedEntry; - MODERN_UI_RECT PreviewPanel; - UINTN ListWidth; - BOOLEAN ShowPreview; + EFI_STATUS Status; + MODERN_UI_DEVICE_ENTRY *Entries; + UINTN EntryCount; + UINTN Index; + UINTN HiiCount; + UINTN VisibleHiiCount; + UINTN VisibleDeviceCount; + UINTN VisibleRows; + UINTN RowY; + CHAR16 Line[168]; + CHAR16 Summary[96]; + BOOLEAN IsSelected; + MODERN_SETUP_PAGE_LIST_LAYOUT Layout; + MODERN_UI_ROW_MODEL RowModel; + MODERN_UI_DEVICE_ENTRY *SelectedEntry; + BOOLEAN ShowPreview; - Panel = ModernSetupContentRect (Ui); - RowX = Panel.X + 20; - RowWidth = Panel.Width - 40; - ModernUiDrawPanel (Ui, Panel, Theme); - ModernUiDrawFocusFrame (Ui, Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); + if (!ModernSetupGetPageListLayout (Ui, mModernSetupPreferences.DashboardDensity, MAX_DEVICE_ROWS, TRUE, &Layout)) { + Layout.Panel = ModernSetupContentRect (Ui); + } + + ModernUiDrawPanel (Ui, Layout.Panel, Theme); + ModernUiDrawFocusFrame (Ui, Layout.Panel, (BOOLEAN)(Focus == SetupFocusContent), Theme); Entries = NULL; Status = ModernUiDeviceDataGetEntries (&Entries, &EntryCount); @@ -758,21 +777,14 @@ DrawDevices ( VisibleHiiCount = 0; VisibleDeviceCount = 0; SelectedEntry = (Selected < EntryCount) ? &Entries[Selected] : NULL; - ShowPreview = (BOOLEAN)((SelectedEntry != NULL) && SelectedEntry->HasForm && (Panel.Width >= 720)); - if (ShowPreview) { - ListWidth = (Panel.Width - 56) / 2; - RowWidth = ListWidth; - PreviewPanel = (MODERN_UI_RECT){ RowX + ListWidth + 16, Panel.Y + 54, Panel.Width - ListWidth - 56, Panel.Height - 74 }; - } else { - PreviewPanel = (MODERN_UI_RECT){ 0, 0, 0, 0 }; - } + ShowPreview = (BOOLEAN)((SelectedEntry != NULL) && SelectedEntry->HasForm && Layout.HasPreviewPane); for (Index = 0; Index < EntryCount; Index++) { if (Entries[Index].HasForm) { HiiCount++; } - if (Index < MAX_DEVICE_ROWS) { + if (Index < Layout.MaxVisibleRows) { if (Entries[Index].HasForm) { VisibleHiiCount++; } else { @@ -782,62 +794,62 @@ DrawDevices ( } UnicodeSPrint (Summary, sizeof (Summary), L"%u entries (%u HII, %u device)", EntryCount, HiiCount, EntryCount - HiiCount); - ModernUiDrawText (Ui, Panel.X + 20, Panel.Y + 20, Summary, Theme->MutedText, Theme->Surface); + ModernUiDrawText (Ui, Layout.RowX, Layout.Panel.Y + 20, Summary, Theme->MutedText, Theme->Surface); - RowY = Panel.Y + 54; + RowY = Layout.FirstRowY; VisibleRows = 0; if (VisibleHiiCount > 0) { - DrawProviderSubsectionHeader (Ui, Theme, RowX, RowY, RowWidth, L"HII formsets"); + DrawProviderSubsectionHeader (Ui, Theme, Layout.RowX, RowY, Layout.RowWidth, L"HII formsets"); RowY += 26; } - for (Index = 0; (Index < EntryCount) && (Index < MAX_DEVICE_ROWS) && (VisibleRows < MAX_DEVICE_ROWS); Index++) { + for (Index = 0; (Index < EntryCount) && (Index < Layout.MaxVisibleRows) && (VisibleRows < Layout.MaxVisibleRows); Index++) { if (!Entries[Index].HasForm) { continue; } UnicodeSPrint (Line, sizeof (Line), L"%02u %s", Index + 1, Entries[Index].Title); IsSelected = (BOOLEAN)((Focus == SetupFocusContent) && (Index == Selected)); - RowModel.Rect = (MODERN_UI_RECT){ RowX, RowY, RowWidth, 30 }; + RowModel.Rect = (MODERN_UI_RECT){ Layout.RowX, RowY, Layout.RowWidth, Layout.RowHeight }; RowModel.Prompt = Line; RowModel.Value = L"HII >"; RowModel.Role = IsSelected ? ModernUiRowSelected : ModernUiRowNormal; RowModel.ValueType = ModernUiValueAction; ModernUiEngineDrawRows (Ui, &RowModel, 1, Theme); - RowY += 36; + RowY += Layout.RowStride; VisibleRows++; } - if ((VisibleDeviceCount > 0) && (VisibleRows < MAX_DEVICE_ROWS)) { + if ((VisibleDeviceCount > 0) && (VisibleRows < Layout.MaxVisibleRows)) { RowY += (VisibleHiiCount > 0) ? 6 : 0; - DrawProviderSubsectionHeader (Ui, Theme, RowX, RowY, RowWidth, L"Device inventory"); + DrawProviderSubsectionHeader (Ui, Theme, Layout.RowX, RowY, Layout.RowWidth, L"Device inventory"); RowY += 26; } - for (Index = 0; (Index < EntryCount) && (Index < MAX_DEVICE_ROWS) && (VisibleRows < MAX_DEVICE_ROWS); Index++) { + for (Index = 0; (Index < EntryCount) && (Index < Layout.MaxVisibleRows) && (VisibleRows < Layout.MaxVisibleRows); Index++) { if (Entries[Index].HasForm) { continue; } UnicodeSPrint (Line, sizeof (Line), L"%02u %s", Index + 1, Entries[Index].Title); IsSelected = (BOOLEAN)((Focus == SetupFocusContent) && (Index == Selected)); - RowModel.Rect = (MODERN_UI_RECT){ RowX, RowY, RowWidth, 30 }; + RowModel.Rect = (MODERN_UI_RECT){ Layout.RowX, RowY, Layout.RowWidth, Layout.RowHeight }; RowModel.Prompt = Line; RowModel.Value = L"Device"; RowModel.Role = IsSelected ? ModernUiRowSelected : ModernUiRowNormal; RowModel.ValueType = ModernUiValueText; ModernUiEngineDrawRows (Ui, &RowModel, 1, Theme); - RowY += 36; + RowY += Layout.RowStride; VisibleRows++; } if (EntryCount == 0) { - ModernUiDrawText (Ui, Panel.X + 20, Panel.Y + 66, L"No HII formsets found.", Theme->Warning, Theme->Surface); + ModernUiDrawText (Ui, Layout.RowX, Layout.FirstRowY, L"No HII formsets found.", Theme->Warning, Theme->Surface); } if (ShowPreview) { - DrawHiiReadOnlyPreview (Ui, Theme, PreviewPanel, SelectedEntry); + DrawHiiReadOnlyPreview (Ui, Theme, Layout.PreviewPanel, SelectedEntry); } ModernUiDeviceDataFreeEntries (Entries, EntryCount); diff --git a/CHANGELOG.md b/CHANGELOG.md index a35c81a..babad90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ this file as both a release log and a lightweight development progress record. ### Added +- phase32(app): apply DashboardDensity to Boot/Devices/Provider pages. - Phase30 XArch/productization validation docs and smoke gate coverage through `Docs/ProductizationValidationMatrix.md`, its zh-CN counterpart, doc-index links, and host-side checks for evidence wording, concrete ARCH values, diff --git a/Tests/Smoke/smoke_validate.py b/Tests/Smoke/smoke_validate.py index 71e7002..61087a5 100755 --- a/Tests/Smoke/smoke_validate.py +++ b/Tests/Smoke/smoke_validate.py @@ -982,6 +982,27 @@ def c_function_definition_count(text: str, function_name: str) -> int: return len(pattern.findall(text)) +def extract_c_function_body(text: str, function_name: str) -> str: + pattern = re.compile( + rf"(^|\n)\s*(?:STATIC\s+)?[A-Z_][A-Z0-9_\s\*]+\s+{re.escape(function_name)}\s*\([^;]*?\)\s*\{{", + re.DOTALL, + ) + match = pattern.search(text) + if match is None: + raise SmokeFailure(f"could not isolate C function body: {function_name}") + + depth = 0 + for index in range(match.end() - 1, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + return text[match.start() : index + 1] + + raise SmokeFailure(f"unterminated C function body: {function_name}") + + def check_modern_setup_app_module_boundaries(root: Path) -> list[str]: app_dir = root / MODERN_SETUP_APP_DIR inf_sources = parse_inf_sources(root / MODERN_SETUP_APP_INF) @@ -1110,6 +1131,23 @@ def check_modern_setup_app_module_boundaries(root: Path) -> list[str]: raise SmokeFailure("Dashboard group labels must sit clearly below the section title before cards begin") if "ModernSetupGetDashboardQuickGrid" not in actions_body or "MODERN_SETUP_DASHBOARD_QUICK_GRID" not in internal_body: raise SmokeFailure("Dashboard quick-card layout must use a shared grid helper contract") + if "MODERN_SETUP_PAGE_LIST_LAYOUT" not in internal_body: + raise SmokeFailure("ModernSetupAppInternal.h missing shared page-list layout contract") + for helper in ("DrawBoot", "DrawDevices", "DrawProviderSummaryPage"): + helper_body = extract_c_function_body(pages_body, helper) + if "ModernSetupGetPageListLayout" not in helper_body: + raise SmokeFailure(f"{helper} must use the shared page-list layout helper") + for helper, forbidden_tokens in { + "DrawBoot": ("* 58", "+ 62", "Panel.Width - 40", "Panel.X + 20"), + "DrawDevices": ("Panel.Width - 40", "Panel.X + 20", ">= 720"), + }.items(): + helper_body = extract_c_function_body(pages_body, helper) + for token in forbidden_tokens: + if token in helper_body: + raise SmokeFailure(f"{helper} still contains hardcoded page-list geometry token: {token}") + page_layout_body = extract_c_function_body(actions_body, "ModernSetupGetPageListLayout") + if page_layout_body.count("Compact ?") < 2: + raise SmokeFailure("ModernSetupGetPageListLayout must expose compact and comfortable density branches") if "ModernSetupGetDashboardCategoryRoute" not in actions_body or "MODERN_SETUP_DASHBOARD_ROUTE" not in internal_body: raise SmokeFailure("Dashboard category landing routes must use the shared helper contract") if "ModernSetupGetDashboardCategoryRoute (DashboardSelection" not in app_body: