Skip to content

Fix safe area CSS in Blazor Hybrid templates for Android - #34463

Merged
kubaflo merged 3 commits into
inflight/currentfrom
copilot/fix-safe-area-handling
Jun 21, 2026
Merged

Fix safe area CSS in Blazor Hybrid templates for Android#34463
kubaflo merged 3 commits into
inflight/currentfrom
copilot/fix-safe-area-handling

Conversation

Copilot AI commented Mar 12, 2026

Copy link
Copy Markdown
Contributor
  • Analyze the issue and identify affected files
  • Fix src/Templates/src/templates/maui-blazor/wwwroot/app.css — Remove @supports wrapper, update background color, add env() fallbacks
  • Fix src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css — Same changes for the solution template variant
  • Verify the template project builds successfully
  • Run code review and security checks
  • Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

Original prompt

This section details on the original issue you should resolve

<issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge.

Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The BlazorWebView content extends behind the system status bar, causing the top navbar/sidebar to be obscured.

The template's app.css wraps all safe area CSS rules inside @supports (-webkit-touch-callout: none), which is a WebKit-only feature query. This means the safe area rules only apply on iOS (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView).

Current CSS (broken on Android):

.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}

Fix: Remove the @supports wrapper and make the safe area rules universal. The env(safe-area-inset-*) values resolve to 0px on platforms that don't need them, so there is no downside to applying them universally:

.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}

Note: The background color rgb(3, 23, 62) is the effective composited color of the sidebar gradient start (rgb(5, 39, 103) from MainLayout.razor.css) combined with the .top-row overlay (rgba(0,0,0,0.4) from NavMenu.razor.css). The original #f7f7f7 did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically.

Problem 2: Android — Status bar icon color is unreadable in light mode

After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain dark/black against the dark background in light mode, making them unreadable.

Attempts to fix this via:

  • styles.xml (windowLightStatusBar = false)
  • WindowCompat.GetInsetsController() with AppearanceLightStatusBars = false in OnCreate/OnPostCreate/OnResume
  • MauiProgram.cs lifecycle events
  • Setting UserAppTheme = AppTheme.Light in App.xaml

All failed because the MAUI framework overrides AppearanceLightStatusBars after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme.

Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), env(safe-area-inset-left) and env(safe-area-inset-right) both report 62px in landscape mode, regardless of which side the Dynamic Island is on. This means:

  • When the Dynamic Island is on the left (landscape-right, angle 90°): padding-left 62px is correct ✅
  • When the Dynamic Island is on the right (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌

Measured values in landscape on iPhone 17 (iOS 26.2):

  • env(safe-area-inset-top): 0px
  • env(safe-area-inset-right): 62px
  • env(safe-area-inset-bottom): 20px
  • env(safe-area-inset-left): 62px

Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using screen.orientation.angle (90° vs 270°) could conditionally apply the padding, but this adds complexity.

Steps to Reproduce

  1. Create a new app from the MauiBlazorWebIdentity template (or use dotnet/blazor-samples 10.0 sample)
  2. Run the MAUI app on an Android 15+ emulator (API 35+)
  3. Observe the navbar is behind the status bar in portrait
  4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
  5. Observe 62px padding on both sides regardless of island position

Expected Behavior

  • Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform
  • Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is

Environment

  • .NET 10 Preview
  • MAUI BlazorWebView
  • Android API 35+ (edge-to-edge enforced)
  • iOS 26.2 / iPhone 17 simulator
  • Tested on both physical characteristics via simulator rotation</issue_description>

Comments on the Issue (you are @copilot in this secti...


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Remove @supports (-webkit-touch-callout: none) wrapper that prevented
safe area CSS rules from applying on Android (Chromium WebView).
The env(safe-area-inset-*) values resolve to 0px on platforms that
don't need them, so there is no downside to applying them universally.

Also update background-color from #f7f7f7 to rgb(3, 23, 62) to match
the sidebar theme, and add fallback values to env() functions.

Fixes #34462

Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix safe area handling for Android and iOS in Blazor Hybrid templates Fix safe area CSS in Blazor Hybrid templates for Android Mar 12, 2026
Copilot AI requested a review from mattleibow March 12, 2026 19:56
@mattleibow
mattleibow marked this pull request as ready for review March 13, 2026 00:46
Copilot AI review requested due to automatic review settings March 13, 2026 00:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Blazor Hybrid MAUI templates’ safe-area CSS so it applies on Android (Chromium WebView) instead of being gated by a WebKit-only @supports query, addressing content rendering behind the status bar on edge-to-edge Android.

Changes:

  • Removes @supports (-webkit-touch-callout: none) wrapper so safe-area rules are not iOS-only.
  • Makes .status-bar-safe-area always present with env(safe-area-inset-top, 0px) and updates its background color.
  • Adds env(..., 0px) fallback usage for left inset padding.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Templates/src/templates/maui-blazor/wwwroot/app.css Makes safe-area spacer and left inset padding apply universally (not WebKit-gated).
src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css Mirrors the same safe-area CSS adjustments in the solution template.

Comment thread src/Templates/src/templates/maui-blazor/wwwroot/app.css
@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Mar 23, 2026
@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@kubaflo
kubaflo changed the base branch from main to inflight/current May 25, 2026 20:16
@dotnet dotnet deleted a comment from MauiBot May 25, 2026
@kubaflo

kubaflo commented May 25, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml -p android

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 2 findings

See inline comments for details.

position: sticky;
top: 0;
height: env(safe-area-inset-top, 0px);
background-color: rgb(3, 23, 62);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Template safe-area behavior — This hard-codes the sample app's dark navigation color into the universal host CSS. app.css is included even when SampleContent is disabled, and index.html always emits .status-bar-safe-area, so empty Blazor Hybrid templates can show an unrelated dark strip behind the Android status bar whenever env(safe-area-inset-top) is non-zero. It also risks poor contrast with Android light status-bar icons. Please keep the safe-area sizing universal, but scope the visual color to sample content, e.g. via a CSS variable with a neutral/transparent fallback.

position: sticky;
top: 0;
height: env(safe-area-inset-top, 0px);
background-color: rgb(3, 23, 62);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Template safe-area behavior — This host CSS is loaded for generated apps regardless of whether sample content is included, but the dark rgb(3, 23, 62) color only matches the sample navigation chrome. With sample content disabled, Android edge-to-edge devices can still get a dark status-bar spacer over an otherwise neutral/empty app, and dark status-bar icons may be unreadable. Please make the universal rule use a neutral fallback and let sample-content CSS opt into the dark color.

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 25, 2026
Keep the sample status bar spacer color scoped to sample content and use a transparent fallback for empty template output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo

kubaflo commented May 26, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml -p android

@MauiBot MauiBot added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-win AI found a better alternative fix than the PR labels May 26, 2026
@dotnet dotnet deleted a comment from MauiBot May 30, 2026
@kubaflo

kubaflo commented May 30, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml -p android

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 31, 2026
@MauiBot

MauiBot commented May 31, 2026

Copy link
Copy Markdown
Collaborator

🤖 AI Summary

👋 @copilot — new AI review results are available. Please review the latest session below.

📊 Review Sessionf08ce9a · Fix Blazor template safe area color · 2026-05-31 00:30 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

Recommendation: Add tests to verify the fix using the write-tests-agent.


🔍 Pre-Flight — Context & Validation

Context

  • PR: Fix safe area CSS in Blazor Hybrid templates for Android #34463
  • Branch inspected: pr-review-34463
  • Base used: local merge-base with origin/main
  • Platform for testing: android
  • Gate result: SKIPPED — no tests detected in this PR. Gate was not rerun.
  • Remote metadata: unavailable because gh is not authenticated in this environment.

Changed Areas

  • Copilot PR-review orchestration: .github/scripts/Review-PR.ps1
  • Azure pipeline: eng/pipelines/ci-copilot.yml
  • Posting/comment cleanup scripts under .github/scripts/
  • Gate verification script under .github/skills/verify-tests-fail-without-fix/
  • Template CSS adjustments under src/Templates/

PR Fix Summary

The current PR fix splits review work into token-scoped phases, copies trusted review scripts before merging PR code, avoids persistent GitHub checkout credentials, strips GitHub tokens before PR-controlled test/build subprocesses, and moves posting/labeling into a separate Post phase.

Code Review Summary

See pre-flight/code-review.md for the full review. Key candidate-driving concerns:

  • Android Gate does not receive DEVICE_UDID, while verify-tests-fail.ps1 attempts to use $DeviceUdid.
  • Stale generated-marker cleanup should require the maui-bot author before deletion.
  • Captured PR-controlled output should be sanitized before it reaches Azure log parsing.

Try-Fix Inputs

  • Test command: Gate verification was already skipped. For candidates, use targeted static validation of changed PowerShell scripts and Android pipeline YAML diff review.
  • Target files: eng/pipelines/ci-copilot.yml, .github/scripts/Review-PR.ps1, .github/scripts/shared/Remove-StaleMauiBotComments.ps1, .github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1.

🔬 Code Review — Deep Analysis

Code Review — PR #34463

Independent Assessment

What this changes: The local PR branch restructures the Copilot PR-review CI pipeline into separate Setup, Gate, CopilotReview, and Post phases; copies trusted scripts before PR merge; narrows token exposure by task; strips GitHub tokens before PR-controlled build/test subprocesses; moves posting/labeling into a token-scoped Post phase; and adds comment cleanup/TRX aggregation helpers.

Inferred motivation: Reduce blast radius when untrusted PR code is merged into the CI worktree, especially preventing PR-controlled MSBuild/test code from reading GitHub/Copilot tokens or using modified review scripts to post misleading comments.

Reconciliation with PR Narrative

Author claims: GitHub CLI was unavailable in this environment, so remote PR title/body/comments could not be fetched. The checked-out branch is pr-review-34463; local diff against origin/main was used as the source of truth.

Agreement/disagreement: The implementation largely matches a security-hardening intent. Remaining risks are around output boundaries, Android device reuse, and cleanup/posting paths that still consume untrusted content.

Findings

⚠️ Warning — Android Gate does not receive the prepared device UDID

eng/pipelines/ci-copilot.yml passes DEVICE_UDID to the CopilotReview task but not the Gate task. verify-tests-fail.ps1 already tries to use $DeviceUdid when deciding whether to boot a device, but the script has no parameter/default binding for it. On Android, that can make Gate boot a second emulator or run against a different device than the one prepared by the pipeline.

⚠️ Warning — Generated-marker cleanup can target non-bot comments

Remove-StaleMauiBotComments.ps1 matches <!-- AI Summary --> / <!-- AI Gate --> marker comments without first verifying the comment author is maui-bot. A user comment containing a marker could be deleted by the bot, whereas merge-conflict and try-fix cleanup already require the bot author.

⚠️ Warning — Some PR-controlled output is still written to Azure logs raw

The new instructions require stripping ##vso[...] from PR-controlled stdout. Review-PR.ps1 still writes captured gate verification output directly with Write-Host, so a malicious test/build output line could attempt Azure logging-command injection if it reaches that path.

Devil's Advocate

The PR's core architecture is a substantial improvement: token scoping and trusted script copies address the largest credential-exfiltration path. The warnings above are narrower hardening gaps; none prove that the PR is worse than the baseline, but each is a plausible follow-up candidate.

Verdict: NEEDS_DISCUSSION

Confidence: medium
Summary: The security direction is sound, but the Android Gate device binding and untrusted-output boundaries deserve consideration before calling this complete. No GitHub-hosted PR narrative or CI checks could be inspected because gh is unauthenticated in this environment.


🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 expert-review loop Android Gate device reuse, first attempt ❌ FAIL 2 files Static parse passed, but YAML edit targeted Setup env instead of Gate env. Fed into candidate 4.
2 expert-review loop Require maui-bot author before deleting generated-marker comments ✅ PASS 1 file Good posting-boundary hardening; not Android-specific.
3 expert-review loop Strip Azure ##vso[...] commands from captured Gate output ⚠️ BLOCKED 1 file Parse passed; deeper dry-run blocked by unauthenticated gh. Needs broader stdout coverage before selection.
4 expert-review loop Correct Android Gate DEVICE_UDID propagation and DeviceUdid binding ✅ PASS 2 files Corrected candidate 1; directly addresses Android test platform behavior.
PR PR #34463 Token-scoped phased review, trusted scripts, token stripping, Post phase ⚠️ Gate skipped 13 files Original PR fix; no tests detected by prior gate.

Cross-Pollination

Model/Reviewer Round New Ideas? Details
maui-expert-reviewer 1 Yes Suggested trusted-runner boundary for all stages, Azure logging-command firewall, safe comment/artifact manifest, and Android device-state hardening.
self-review after try-fix-1 2 Yes Detected that candidate 1 patched the wrong YAML env block; generated corrected candidate 4.
self-review after try-fix-3 2 No selected patch Logging firewall remains valuable but validation was blocked and the candidate needs broader coverage before being considered better than the PR fix.

Exhausted: Yes for this environment. Additional meaningful directions exist (trusted deep-UI runner boundary and manifest-rendered posting), but they require larger pipeline changes and CI validation not available without authenticated GitHub/AzDO context.

Selected Fix: Candidate #4 — it is the best tested Android-specific alternative. It fixes an existing $DeviceUdid binding gap in verify-tests-fail.ps1 and passes DEVICE_UDID to the Gate phase where Android tests actually run. Candidate #2 is also a low-risk hardening improvement, but Candidate #4 is more directly tied to the requested Android test platform.

try-fix-1 — Android device reuse (incorrect first attempt)

Approach: Bind DeviceUdid in verify-tests-fail.ps1 and pass DEVICE_UDID from the pipeline so Android Gate can reuse the emulator prepared by the pipeline instead of booting or selecting another device.

How it differs from the PR fix: The PR focuses on token/task separation and trusted scripts. This candidate addresses Android platform reliability by making device state explicit.

Test results: ❌ FAIL. PowerShell parsing passed, but review of the diff showed the pipeline variable was added to the Setup task environment, not the Gate task environment, so it did not satisfy the intended Android Gate reuse behavior.

Failure analysis: The patch matched the first GH_TOKEN env block in ci-copilot.yml. The next candidate must anchor the YAML edit to Task 2: Gate / RunGate rather than using a generic env-block match.

Diff:

diff --git a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
index e54d900a1c..84894ddb36 100644
--- a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
+++ b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
@@ -49,6 +49,9 @@
     (i.e., if no fix files are detected). Without this flag, the script will
     automatically run in verify failure only mode when no fix files are found.
 
+.PARAMETER DeviceUdid
+    Device/simulator UDID to reuse for platform tests. Defaults to DEVICE_UDID.
+
 .EXAMPLE
     # Auto-detect everything (test type, filter, platform)
     ./verify-tests-fail.ps1 -Platform android
@@ -93,7 +96,10 @@ param(
 
     [Parameter(Mandatory = $false)]
     [ValidateSet("UITest", "UnitTest", "XamlUnitTest", "DeviceTest")]
-    [string]$TestType
+    [string]$TestType,
+
+    [Parameter(Mandatory = $false)]
+    [string]$DeviceUdid = $env:DEVICE_UDID
 )
 
 $ErrorActionPreference = "Stop"
diff --git a/eng/pipelines/ci-copilot.yml b/eng/pipelines/ci-copilot.yml
index ebce013b91..f1346074f4 100644
--- a/eng/pipelines/ci-copilot.yml
+++ b/eng/pipelines/ci-copilot.yml
@@ -642,6 +642,7 @@ stages:
             displayName: 'Task 1: Setup (branch + merge)'
             env:
               GH_TOKEN: $(GH_COMMENT_TOKEN)
+              DEVICE_UDID: $(DEVICE_UDID)
               PARAM_PR_NUMBER: ${{ parameters.PRNumber }}
 
           # ─────────────────────────────────────────────────────────

try-fix-2 — Require bot author for generated-marker cleanup

Approach: Change stale comment cleanup so <!-- AI Summary --> and <!-- AI Gate --> marker matches only delete comments authored by maui-bot, mirroring the existing merge-conflict and try-fix cleanup behavior.

How it differs from the PR fix: The PR moves posting into a safer token-scoped phase. This candidate hardens the comment deletion boundary so user-authored comments cannot be removed just because they contain an internal marker string.

Test results: ✅ PASS. PowerShell parser validation passed for Remove-StaleMauiBotComments.ps1.

Failure analysis: None from validation. This is a small, robust hardening patch, but it does not address the Android-specific testing platform issue.

Diff:

diff --git a/.github/scripts/shared/Remove-StaleMauiBotComments.ps1 b/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
index 8508014836..95c14ef1ed 100644
--- a/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
+++ b/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
@@ -89,12 +89,16 @@ function Remove-StaleMauiBotIssueComments {
             continue
         }
 
+        $isMauiBotComment = Test-IsMauiBotCommentAuthor $comment
+
         $matchesGeneratedMarker =
-            ($IncludeAISummary -and $body.Contains($script:AiSummaryCommentMarker)) -or
-            ($IncludeLegacyGate -and $body.Contains($script:AiGateCommentMarker))
+            $isMauiBotComment -and (
+                ($IncludeAISummary -and $body.Contains($script:AiSummaryCommentMarker)) -or
+                ($IncludeLegacyGate -and $body.Contains($script:AiGateCommentMarker))
+            )
 
         $matchesBotOnlyContent =
-            (Test-IsMauiBotCommentAuthor $comment) -and (
+            $isMauiBotComment -and (
                 ($IncludeMergeConflict -and (Test-IsMergeConflictCommentBody $body)) -or
                 ($IncludeTryFix -and (Test-IsTryFixCommentBody $body))
             )

try-fix-3 — Azure logging-command firewall for captured Gate output

Approach: Add a helper to strip CR characters and ##vso[...] logging commands from captured Gate verification output before writing it to Azure logs.

How it differs from the PR fix: The PR strips GitHub/Copilot tokens before PR-controlled subprocesses. This candidate addresses a different boundary: preventing PR-controlled stdout from affecting Azure Pipelines state through logging commands.

Test results: ⚠️ BLOCKED. PowerShell parsing passed, but executing Review-PR.ps1 -Phase Setup -DryRun requires authenticated gh to look up PR #34463 in this environment.

Failure analysis: The implementation is structurally plausible, but validation could not proceed beyond parse-level checks without GitHub authentication. A fuller version should apply the sanitizer to every PR-controlled stdout path and include focused Pester coverage for CRLF and ##vso[...] payloads.

Diff:

diff --git a/.github/scripts/Review-PR.ps1 b/.github/scripts/Review-PR.ps1
index c966ce7d92..bbad43eab3 100644
--- a/.github/scripts/Review-PR.ps1
+++ b/.github/scripts/Review-PR.ps1
@@ -143,6 +143,17 @@ function Invoke-WithoutGhTokens {
     }
 }
 
+function ConvertTo-SafeAzDoLogLine {
+    param([AllowNull()][object]$InputObject)
+
+    if ($null -eq $InputObject) {
+        return ''
+    }
+
+    # PR-controlled output must not be able to emit Azure Pipelines logging commands.
+    return ($InputObject.ToString() -replace "`r", '' -replace '##vso\[[^\]]*\]', '[azdo-command-removed]')
+}
+
 # ─── Banner ───────────────────────────────────────────────────────────────────
 Write-Host ""
 Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
@@ -1071,7 +1082,7 @@ for ($gateAttempt = 1; $gateAttempt -le $maxGateAttempts; $gateAttempt++) {
     # subprocess invocations internally to strip the token before PR code runs.
     $gateOutput = & pwsh -NoProfile -File "$verifyScript" -Platform $gatePlatform -PRNumber $PRNumber 2>&1
     $gateExitCode = $LASTEXITCODE
-    $gateOutput | ForEach-Object { Write-Host "    $_" }
+    $gateOutput | ForEach-Object { Write-Host "    $(ConvertTo-SafeAzDoLogLine $_)" }
 
     # Check if this was an ENV ERROR (emulator timeout, ADB failure, etc.)
     $isEnvError = $false

try-fix-4 — Android Gate device reuse (corrected)

Approach: Bind DeviceUdid in verify-tests-fail.ps1 from the DEVICE_UDID environment variable and pass DEVICE_UDID to the Gate task specifically. This lets Android Gate reuse the emulator/device prepared by the pipeline and fixes the script's existing $DeviceUdid reference.

How it differs from the PR fix: The PR's current fix is primarily token-scoping/trusted-script architecture. This candidate is an Android-specific reliability fix for the Gate test runner path.

Test results: ✅ PASS. PowerShell parser validation passed for verify-tests-fail.ps1; YAML diff review confirmed DEVICE_UDID is added under Task 2: Gate / RunGate, not Setup.

Failure analysis: This corrected try-fix incorporates the try-fix-1 lesson by anchoring the YAML change to the Gate env block.

Diff:

diff --git a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
index e54d900a1c..84894ddb36 100644
--- a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
+++ b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
@@ -49,6 +49,9 @@
     (i.e., if no fix files are detected). Without this flag, the script will
     automatically run in verify failure only mode when no fix files are found.
 
+.PARAMETER DeviceUdid
+    Device/simulator UDID to reuse for platform tests. Defaults to DEVICE_UDID.
+
 .EXAMPLE
     # Auto-detect everything (test type, filter, platform)
     ./verify-tests-fail.ps1 -Platform android
@@ -93,7 +96,10 @@ param(
 
     [Parameter(Mandatory = $false)]
     [ValidateSet("UITest", "UnitTest", "XamlUnitTest", "DeviceTest")]
-    [string]$TestType
+    [string]$TestType,
+
+    [Parameter(Mandatory = $false)]
+    [string]$DeviceUdid = $env:DEVICE_UDID
 )
 
 $ErrorActionPreference = "Stop"
diff --git a/eng/pipelines/ci-copilot.yml b/eng/pipelines/ci-copilot.yml
index ebce013b91..da8bd5e7d1 100644
--- a/eng/pipelines/ci-copilot.yml
+++ b/eng/pipelines/ci-copilot.yml
@@ -672,6 +672,7 @@ stages:
             displayName: 'Task 2: Gate (test verification)'
             env:
               GH_TOKEN: $(GH_COMMENT_TOKEN)
+              DEVICE_UDID: $(DEVICE_UDID)
               PARAM_PR_NUMBER: ${{ parameters.PRNumber }}
 
           # ─────────────────────────────────────────────────────────

📋 Report — Final Recommendation

Comparative Report

Platform considered: android
Gate result: skipped because no tests were detected in this PR; gate verification was not rerun.

Candidate ranking

Rank Candidate Result status Assessment
1 pr-plus-reviewer Sandbox parse validation passed; gate skipped Best overall. Preserves the PR's broader security architecture and incorporates the expert reviewer's three actionable fixes: Android Gate device reuse, safer stale-comment cleanup, and Azure logging-command sanitization.
2 pr Gate skipped; expert review found major actionable gaps Strong baseline because it addresses the primary token/task separation problem, trusted script boundary, token stripping, and Post phase isolation, but it leaves Android device binding, comment cleanup safety, and raw Azure log output unresolved.
3 try-fix-4 PASS Best standalone try-fix for the requested android platform. It correctly binds DeviceUdid and passes DEVICE_UDID to Task 2 Gate, but it only addresses one expert finding and not the PR's broader security refactor.
4 try-fix-2 PASS Low-risk hardening improvement for stale comment deletion. It passed validation, but it is not Android-specific and does not address the core PR workflow/token boundary.
5 try-fix-3 BLOCKED Plausible Azure logging-command hardening, and it became part of pr-plus-reviewer, but as a standalone STEP 5a candidate it was only parse-validated and deeper dry-run validation was blocked by missing GitHub authentication.
6 try-fix-1 FAIL Ranked below all non-failing candidates. It attempted Android device reuse but patched the Setup env block instead of the Gate env block, so it did not satisfy the intended Android Gate behavior.

Winner

Winner: pr-plus-reviewer

pr-plus-reviewer is the single best candidate because it keeps the raw PR's comprehensive fix for token scoping and trusted posting while addressing all three expert-review gaps. Passed try-fix candidates are valuable but narrower; the failed try-fix-1 is ranked last as required, and the blocked try-fix-3 is not selected as a standalone candidate.

The PR should also get dedicated tests or pipeline validation coverage, because the gate explicitly detected no tests in this PR.


@PureWeen
PureWeen force-pushed the inflight/current branch from 1f8dd5f to 84345dc Compare June 2, 2026 20:09
@kubaflo
kubaflo merged commit 666602e into inflight/current Jun 21, 2026
33 of 36 checks passed
@kubaflo
kubaflo deleted the copilot/fix-safe-area-handling branch June 21, 2026 19:01
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 21, 2026
PureWeen pushed a commit that referenced this pull request Jun 22, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jun 25, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
- [x] Analyze the issue and identify affected files
- [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` —
Remove `@supports` wrapper, update background color, add env() fallbacks
- [x] Fix
`src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css`
— Same changes for the solution template variant
- [x] Verify the template project builds successfully
- [x] Run code review and security checks
- [x] Update PR description with related issue references

Fixes #34462
Fixes #33103
Fixes #14894

Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>


----

*This section details on the original issue you should resolve*

<issue_title>Safe area handling broken on Android and incomplete on iOS
in Blazor Hybrid templates</issue_title>
<issue_description>## Description

The Blazor Hybrid template has safe area issues on both Android and iOS
when running edge-to-edge.

### Problem 1: Android — Content renders behind status bar (portrait)

On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
`BlazorWebView` content extends behind the system status bar, causing
the top navbar/sidebar to be obscured.

The template's `app.css` wraps all safe area CSS rules inside `@supports
(-webkit-touch-callout: none)`, which is a WebKit-only feature query.
This means the safe area rules **only apply on iOS** (Safari/WebKit
WebView) and are completely ignored on Android (Chromium WebView).

**Current CSS (broken on Android):**
```css
.status-bar-safe-area {
    display: none;
}

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f7f7f7;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}
```

**Fix:** Remove the `@supports` wrapper and make the safe area rules
universal. The `env(safe-area-inset-*)` values resolve to `0px` on
platforms that don't need them, so there is no downside to applying them
universally:

```css
.status-bar-safe-area {
    display: flex;
    position: sticky;
    top: 0;
    height: env(safe-area-inset-top);
    background-color: rgb(3, 23, 62);
    width: 100%;
    z-index: 1;
}

.flex-column, .navbar-brand {
    padding-left: env(safe-area-inset-left, 0px);
}
```

> **Note:** The background color `rgb(3, 23, 62)` is the effective
composited color of the sidebar gradient start (`rgb(5, 39, 103)` from
`MainLayout.razor.css`) combined with the `.top-row` overlay
(`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did
not match the dark sidebar theme. Ideally the template should use a CSS
variable for this so it stays in sync automatically.

### Problem 2: Android — Status bar icon color is unreadable in light
mode

After fixing the safe area background to the dark navy color, the status
bar icons (clock, battery, signal) remain **dark/black** against the
dark background in light mode, making them unreadable.

Attempts to fix this via:
- `styles.xml` (`windowLightStatusBar = false`)
- `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars =
false` in `OnCreate`/`OnPostCreate`/`OnResume`
- `MauiProgram.cs` lifecycle events
- Setting `UserAppTheme = AppTheme.Light` in `App.xaml`

All failed because the **MAUI framework overrides
`AppearanceLightStatusBars`** after all lifecycle events on API 35+.
There does not appear to be a supported way to control status bar icon
color when the app has a dark-colored area behind the status bar but is
otherwise in light theme.

### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent
correct Dynamic Island handling

On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and
`env(safe-area-inset-right)` both report **62px** in landscape mode,
regardless of which side the Dynamic Island is on. This means:

- When the Dynamic Island is on the **left** (landscape-right, angle
90°): padding-left 62px is correct ✅
- When the Dynamic Island is on the **right** (landscape-left, angle
270°): padding-left 62px creates unnecessary space on the left side of
the sidebar ❌

**Measured values in landscape on iPhone 17 (iOS 26.2):**
- `env(safe-area-inset-top)`: 0px
- `env(safe-area-inset-right)`: 62px
- `env(safe-area-inset-bottom)`: 20px
- `env(safe-area-inset-left)`: 62px

Since both left and right are always 62px, CSS alone cannot distinguish
which side the island is on. A JavaScript-based approach using
`screen.orientation.angle` (90° vs 270°) could conditionally apply the
padding, but this adds complexity.

## Steps to Reproduce

1. Create a new app from the `MauiBlazorWebIdentity` template (or use
`dotnet/blazor-samples` 10.0 sample)
2. Run the MAUI app on an Android 15+ emulator (API 35+)
3. Observe the navbar is behind the status bar in portrait
4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape
5. Observe 62px padding on both sides regardless of island position

## Expected Behavior

- Portrait: Status bar area should have a background color matching the
sidebar and content should not render behind system bars on any platform
- Landscape: Safe area padding should only apply on the side where the
hardware cutout (Dynamic Island/camera) actually is

## Environment

- .NET 10 Preview
- MAUI BlazorWebView
- Android API 35+ (edge-to-edge enforced)
- iOS 26.2 / iPhone 17 simulator
- Tested on both physical characteristics via simulator
rotation</issue_description>

## Comments on the Issue (you are @copilot in this secti...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #34462

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

6 participants