Skip to content

Fix SDK detection treating ~/.dotnet cache as valid installation - #37

Merged
Redth merged 2 commits into
mainfrom
copilot/fix-sdk-detection-macos
Feb 10, 2026
Merged

Redth merged 2 commits into
mainfrom
copilot/fix-sdk-detection-macos

Conversation

Copilot AI commented Feb 10, 2026 •

Copy link
Copy Markdown
Contributor

~/.dotnet typically contains cache/settings, not actual SDKs. Current detection checks it before system paths and only validates that a sdk/ subdirectory exists, causing incorrect path detection and missing SDK versions on macOS.

Changes

Path priority reordering

  • System paths (/usr/local/share/dotnet, /usr/share/dotnet, /opt/homebrew/opt/dotnet/libexec) now checked before user directories
  • Added /usr/share/dotnet and /usr/local/lib/dotnet to macOS paths
  • ~/.dotnet moved to lowest priority (fallback only)

SDK validation

// Before: weak validation
if (Directory.Exists(path) && Directory.Exists(Path.Combine(path, "sdk")))
    return path;

// After: validates actual SDK presence
private bool IsValidSdkPath(string path)
{
    // Checks: path exists, sdk/ exists, contains version dirs, dotnet.dll present
    if (SdkVersion.TryParse(versionDir, out _))
        return File.Exists(Path.Combine(dir, "dotnet.dll"));
}

Supporting changes

  • Added SdkVersion.TryParse() for safe version parsing (rejects "tools", "corefx", etc.)
  • Debug logging throughout detection path (logs checked paths, rejection reasons, found SDKs)
Original prompt

The issue with MAUI.Sherpa involves incorrect detection of installed .NET SDK versions on macOS. Sherpa currently misinterprets the ~/.dotnet directory as an SDK location, which often doesn't point to valid SDK installations. This results in the tool showing incorrect or missing SDK versions, as reported in issue #36.

To address this:

  1. Update SDK detection logic to exclude ~/.dotnet as a presumed SDK location unless specific subdirectories (like ~/.dotnet/sdk) explicitly contain valid SDK installations.
  2. Ensure that detecting SDK versions considers paths commonly used for installations, such as /usr/local/share/dotnet, /usr/local/lib/dotnet, or other relevant paths for macOS.
  3. Add robust validation checks to confirm SDK integrity from detected paths.
  4. Include additional debugging logs for better insights into how and where SDKs are identified.

Implement these changes in the codebase, test extensively on macOS environments similar to those mentioned in the GitHub issue, and verify that the issue is resolved.

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix incorrect SDK detection logic for macOS in MAUI.Sherpa Fix SDK detection treating ~/.dotnet cache as valid installation Feb 10, 2026
Copilot AI requested a review from Redth February 10, 2026 22:35
Copilot AI added 2 commits February 10, 2026 18:38
…ocation support

Rewrite GetPossibleDotNetPaths() to follow the official dotnet/designs
install-locations specification:

1. DOTNET_ROOT_<ARCH> / DOTNET_ROOT env vars (highest priority)
2. /etc/dotnet/install_location[_<arch>] files (macOS/Linux)
3. Windows registry InstallLocation key
4. Default global paths (/usr/local/share/dotnet on macOS, etc.)
5. Additional well-known paths (x64-on-arm, Homebrew, VS private)
6. Project-local .dotnet
7. ~/.dotnet (lowest priority — typically just cache/settings)

Key changes vs original PR:
- Add /etc/dotnet/install_location file reading (the official mechanism
  for registered install locations on macOS/Linux)
- Add per-architecture install_location_<arch> support (.NET 6+)
- Add Windows registry lookup for custom global installs
- Add /usr/local/share/dotnet/x64 (x64-on-arm64 macOS via Rosetta)
- Remove incorrect paths (/usr/share/dotnet on macOS, /usr/local/lib/dotnet)
- Keep IsValidSdkPath() validation and SdkVersion.TryParse()
- Keep debug logging throughout detection

Fixes #36
@Redth
Redth force-pushed the copilot/fix-sdk-detection-macos branch from 0cad058 to 8997a09 Compare February 10, 2026 23:40
@Redth

Redth commented Feb 10, 2026

Copy link
Copy Markdown
Owner

I've rebased this on main and reworked the path resolution to follow the official dotnet/designs install-locations spec.

What changed from the original PR

Added: /etc/dotnet/install_location file support (macOS/Linux)
This is the official registered install location mechanism. The .pkg installer writes these files:

  • /etc/dotnet/install_location_arm64 → e.g. /usr/local/share/dotnet
  • /etc/dotnet/install_location → e.g. /usr/local/share/dotnet/x64

These are more reliable than hardcoding paths — they tell us exactly where the installer put things.

Added: Per-architecture install_location_<arch> files (.NET 6+)
Supports x64/arm64 side-by-side on Apple Silicon Macs.

Added: Windows registry lookup
HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\<arch>\InstallLocation — the official mechanism for custom global installs on Windows.

Added: /usr/local/share/dotnet/x64
The x64-on-arm64 macOS path (Rosetta). Confirmed present on real systems.

Removed: incorrect paths

  • /usr/share/dotnet on macOS — this is a Linux path, not macOS
  • /usr/local/lib/dotnet — not a standard path on any platform

Kept from original PR:

  • IsValidSdkPath() with dotnet.dll validation ✅
  • SdkVersion.TryParse() ✅
  • Debug logging throughout ✅
  • ~/.dotnet as lowest priority ✅
  • SdkVersion.TryParse tests ✅

Full resolution order now

  1. DOTNET_ROOT_<ARCH> / DOTNET_ROOT env vars
  2. /etc/dotnet/install_location_<arch> (macOS/Linux)
  3. /etc/dotnet/install_location (macOS/Linux)
  4. Windows registry InstallLocation key
  5. Default global paths (/usr/local/share/dotnet, %ProgramFiles%\dotnet, etc.)
  6. Additional well-known paths (x64-on-arm, Homebrew, VS private install)
  7. Project-local .dotnet
  8. ~/.dotnet (lowest priority — usually just cache/settings)

All 135 tests pass (88 Core + 47 Workloads). Mac Catalyst build succeeds.

@Redth Redth mentioned this pull request Feb 10, 2026
@Redth
Redth marked this pull request as ready for review February 10, 2026 23:42
@Redth
Redth merged commit 7ae5bb4 into main Feb 10, 2026
5 checks passed
@Redth
Redth deleted the copilot/fix-sdk-detection-macos branch February 10, 2026 23:42
Redth added a commit that referenced this pull request Feb 24, 2026
- Update all three packages from beta.10 to beta.11
- Replace _objc_msgSend_bool setHidden: workaround for menus/search with
  MacOSToolbarItem.SetIsVisible() API (upstream PR #36)
- Store menu/search items as fields for direct IsVisible manipulation
- Re-add TitlebarDragView overlay on WebView container for window dragging
  (upstream overlay on themeFrame doesn't work with split view layout)
- Scroll pocket fix (PR #37) and Release trimming fix (PR #38) automatic

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redth added a commit that referenced this pull request Feb 24, 2026
* feat: add macOS AppKit head with native sidebar and toolbar support

Add new MauiSherpa.MacOS project targeting net10.0-macos using the
shinyorg/mauiplatforms backend (Platform.Maui.MacOS). Key features:

- FlyoutPage with NativeSidebarFlyoutPageHandler for native NSOutlineView
  sidebar with grouped items (General, Android, Apple, Secrets) and
  SF Symbol icons
- Single MacOSBlazorWebView as detail content area with JS interop
  navigation from native sidebar selections
- IToolbarService in Core for Blazor pages to register native toolbar
  actions (Refresh, Create, etc.) that render as NSToolbar items
- IPlatformService.IsMacOS property for platform detection
- MainLayout hides Blazor sidebar on macOS (native sidebar replaces it)
- Shared Blazor components/pages via symlinks from MauiSherpa head
- macOS-specific entry point (Main.cs, MauiMacOSApp.cs, MacOSMauiProgram.cs)

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

* Fix macOS AppKit blank window crash and add MauiDevFlow

The app was crashing during startup because MainThread.BeginInvokeOnMainThread
was called before Essentials was fully initialized (during
NativeSidebarFlyoutPageHandler.ConnectHandler). Replaced with
Dispatcher.Dispatch which is available earlier in the lifecycle.

Also added MauiDevFlow agent and Blazor packages to the macOS project
for debugging support, and updated skill documentation for macOS AppKit.

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

* Add native toolbar items to macOS AppKit head

Register IToolbarService in all 11 Blazor pages that have toolbar
buttons (Refresh, Create, Import, etc.). On macOS, the Blazor .toolbar
div is hidden via CSS and native NSToolbar buttons appear in the
titlebar instead.

Pages updated: Emulators, Devices, AndroidSdk, Keystores, Certificates,
BundleIds, ProvisioningProfiles, AppleSimulators, AppleDevices, Secrets,
Doctor.

Also fix OnToolbarChanged to use Dispatcher.Dispatch instead of
MainThread.BeginInvokeOnMainThread (same Essentials timing issue).

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

* Use SF Symbol icons for native macOS toolbar items

Post-process NSToolbar buttons after the handler creates them to replace
text labels with SF Symbol icons (arrow.clockwise, plus, etc.). Icons
show as ImageOnly with the label text preserved as tooltip.

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

* Add titlebar content inset and scroll fade for macOS

Push Blazor content down 58px so it starts below the native titlebar.
Use CSS mask-image gradient to smoothly fade content as it scrolls
under the titlebar area, keeping toolbar icons visible.

Works in both light and dark mode since the mask uses transparency.

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

* Remove titlebar title text on macOS

Set BlazorContentPage.Title to empty string so the native NSToolbar
doesn't render a centered title label. The sidebar and page headings
provide sufficient context.

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

* Replace hamburger with Copilot toolbar icon, hide FAB on macOS

Replace the sidebar toggle (☰) button with a sparkles SF Symbol that
triggers the Copilot overlay via JS interop. Hide the Copilot floating
action button on macOS since the native toolbar button replaces it.

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

* Tighten titlebar content inset and gradient overlay

Reduce padding-top from 58px to 44px and shorten the gradient mask
to match, pushing content closer to the top of the window.

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

* Apply Copilot toolbar icon on initial page appearance

Add OnAppearing override to run ApplySfSymbolIcons so the Copilot
sparkles icon replaces the hamburger immediately, not only after
a page with toolbar items loads.

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

* Sync native sidebar selection when Blazor navigates internally

Add RouteChanged event to IToolbarService so Blazor can notify the
native side when NavigationManager.LocationChanged fires. MainLayout
subscribes to LocationChanged and calls NotifyRouteChanged(route).

MacOSApp listens for RouteChanged, finds the matching MacOSSidebarItem
by tag, then programmatically selects it in the NSOutlineView via
reflection into NativeSidebarFlyoutPageHandler's private fields.
Suppresses the selection-changed callback during programmatic selection
to avoid re-navigation loops.

Filed shinyorg/mauiplatforms#9 requesting a public API for this.

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

* Fix toolbar button clicks not triggering Blazor UI updates

Add StateHasChanged() at the end of every OnToolbarItemClicked
InvokeAsync lambda. Without this, synchronous actions like
ShowCreateDialog() set component state but never trigger a re-render.
The dialog would only appear after a subsequent interaction (like
clicking Refresh) that happened to call StateHasChanged().

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

* Fix titlebar gradient overlaying modal backgrounds

Replace mask-image (which clips all descendants including modal
overlays) with a ::before pseudo-element at z-index 50. Modal
overlays at z-index 1000 now render fully above the gradient.

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

* Use SF Pro font and 13px base size on macOS

Switch to -apple-system/SF Pro font stack and reduce base font size
from 16px to 13px to match native macOS sidebar proportions. Scale
down heading sizes proportionally.

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

* Enable sidebar resize by dragging the split view divider

Add SidebarSplitViewDelegate with ConstrainSplitPosition (150-400px
bounds), min/max coordinate constraints, and ShouldAdjustSize to keep
the sidebar width stable during window resize. Lower the handler's
sidebar width constraint priority from DefaultHigh to
DragThatCanResizeWindow so user dragging can override it.

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

* Set sidebar default width to 170px and disable right-click context menu

Adjust the sidebar width constraint constant from 220px to 170px in
HandlerChanged to better match native macOS proportions. Add JS
contextmenu event listener to prevent the browser right-click menu
from appearing in the BlazorWebView.

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

* Add Settings and Doctor menu items to App menu

Insert Settings (⌘,) and Doctor items into the macOS App menu
after About, using MenuActionHandler with strong references to
prevent GC. Menu items navigate to the corresponding Blazor pages.

Note: Default Edit/Window menus not yet available in Platform.Maui.MacOS.

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

* Add IsMacOS to all platform guard checks

Update IsSupported properties in SimulatorService, SimulatorLogService,
PhysicalDeviceService, LocalCertificateService, and AppleRootCertService
to check IsMacOS in addition to IsMacCatalyst. Update Blazor pages
(AppleTools, AppleSimulators) and ProcessExecutionService likewise so
the macOS AppKit head is treated as a supported Mac platform.

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

* Update Platform.Maui.MacOS packages to 0.2.0-beta.3

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

* Add loading overlay to macOS BlazorContentPage

Show a native overlay matching the macOS window background color
while the Blazor WebView loads. Fades out when ISplashService
signals BlazorReady, with a 15s safety timeout.

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

* Skip update check dialog in DEBUG builds

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

* Fix device icon alignment on Registered Devices page

Add fixed width, height, and flex centering to .device-icon to match
the Simulators page pattern, ensuring consistent left alignment of
text content regardless of icon width.

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

* Add Logcat and Inspect buttons to running emulators

Show the same Logs (logcat) and Inspect (files) buttons that the
Devices page shows, using the running emulator's serial from the
runningEmulators dictionary to open DeviceInspectorService.

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

* Update Platform.Maui.MacOS packages to 0.2.0-beta.4

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

* Update Platform.Maui.MacOS packages to 0.2.0-beta.5

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

* Add macOS-native theme service with system appearance sync

MacOSThemeService detects light/dark mode via NSApplication
EffectiveAppearance, observes system theme changes via distributed
notification, and sets Application.Current.UserAppTheme when the
user toggles the theme in Settings.

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

* Replace all MAUI Essentials .Default statics with DI interfaces

Create macOS-native implementations:
- MacOSPreferences (NSUserDefaults)
- MacOSLauncher (NSWorkspace)
- MacOSClipboard (NSPasteboard)
- MacOSSecureStorage (Keychain Services)

Replace Preferences.Default, Launcher.Default, Clipboard.Default,
and SecureStorage.Default with injected IPreferences, ILauncher,
IClipboard, and ISecureStorage across all shared Blazor pages,
components, and services. Mac Catalyst continues to use MAUI's
built-in Essentials implementations via auto-registration.

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

* Replace floating inspector panels with native MAUI windows

- Create AndroidInspector.razor and AppleInspector.razor pages with
  tab bar and device selector, reusing existing tab content components
- Create InspectorPage ContentPage hosting BlazorWebView at inspector route
- Create InspectorLayout.razor (minimal layout without sidebar)
- macOS InspectorPage removes FullSizeContentView for proper titlebar
- DeviceInspectorService/SimInspectorService now open/close native windows
  with singleton pattern (one window per inspector type, focus if exists)
- Remove LogcatPanel.razor, SimLogPanel.razor floating panel components
- Remove InspectorCoordinator z-order service
- Remove panel rendering from MainLayout.razor

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

* Quit app when main window is closed

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

* Update Platform.Maui.MacOS packages to 0.2.0-beta.6

- Platform.Maui.MacOS, BlazorWebView, and Essentials all to beta.6
- Remove stopgap MacOS{Preferences,Launcher,Clipboard,SecureStorage}
  implementations now that Platform.Maui.MacOS.Essentials is published
- AddMacOSEssentials() registers all IPreferences/ILauncher/IClipboard/
  ISecureStorage implementations from the official package

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

* Fix portable stub crash in AlertService.ShowToastAsync

MainThread.BeginInvokeOnMainThread throws on macOS AppKit since
it's a portable stub. Removed the unnecessary MainThread wrapper.
Also re-register Essentials interfaces with factory delegates and
move AddMacOSEssentials() after AddMacOSBlazorWebView().

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

* Replace MainThread with Dispatcher in DialogService

MainThread static API is a portable stub on macOS AppKit. Use
Application.Current.Dispatcher instead, which works on all
platforms (Mac Catalyst, macOS AppKit, Windows).

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

* Update Platform.Maui.MacOS packages to 0.2.0-beta.7

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

* Use native inset sidebar, new window/toolbar APIs, fix titlebar drag

- MacOSFlyoutPage.SetUseNativeSidebar for NSSplitViewController inset sidebar
- MacOSWindow.SetFullSizeContentView replaces manual StyleMask hack
- SF Symbols via IconImageSource instead of manual ApplySfSymbolIcons
- MacOSFlyoutPage.SelectSidebarItem replaces reflection-based sidebar sync
- TitlebarDragView overlay fixes undraggable content titlebar area
- Remove SidebarSplitViewDelegate (handled natively now)

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

* Make WebView transparent to show native macOS window background

- Set WKWebView drawsBackground=false via KVC to disable opaque rendering
- Set html, body, .content backgrounds to transparent on macOS platform
- Remove content::before gradient overlay on macOS (no longer needed)
- Preserve body transparency on theme changes in SyncBodyThemeAsync

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

* Reduce content padding on macOS (30px → 15px left/right)

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

* Move Copilot button to sidebar trailing area via MacOSToolbarItem API

Replace sidebar toggle rewiring hack with proper MacOSToolbarItem.SetPlacement
using SidebarTrailing placement. Copilot button now appears on the right side
of the sidebar toolbar area. Always present regardless of page toolbar items.

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

* Set toolbar items before async data loading on all pages

Move ToolbarService.SetItems() and event subscription to the top of
OnInitializedAsync() before any await calls, so toolbar buttons appear
immediately when navigating to a page rather than waiting for data to load.

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

* Move search/filter UI to native macOS toolbar items

Extend IToolbarService with SetSearch() and SetFilters() for native toolbar
integration. On macOS, pages now register their search placeholder and filter
dropdowns which render as NSSearchToolbarItem and NSPopUpButton in the native
toolbar. The Blazor filter bars are hidden on macOS via PlatformInfo.IsMacOS.

Pages updated: AndroidSdk, AppleDevices, AppleSimulators, BundleIds,
Certificates, ProvisioningProfiles, Secrets

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

* Remove sidebar toggle button from toolbar

Poll-remove the MauiSidebarToggle toolbar item that the platform backend
adds automatically for FlyoutPage. Uses a short timer since the toolbar
manager re-adds it on rebuilds.

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

* Fix toolbar item race condition and clear on page navigation

Remove ClearItems() from Dispose (old page clears after new page sets).
Add ClearItems() in OnInitialized for pages without toolbar items
(Dashboard, Settings, FirebasePush, RootCertificates, PushTesting,
SecretsPublish) so toolbar clears when navigating to those pages.

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

* Consolidate filter dropdowns into single menu toolbar item

Replace separate popup dropdowns with a single MacOSMenuToolbarItem
using submenus per filter category with checkmarks. Uses content layout
to position: [action buttons] ← flex → [filter menu] [search].

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

* Fix Copilot button leaking into content toolbar area

Use explicit sidebar layout to position Copilot item so it only
appears in the sidebar trailing area, not as a ghost button in content.

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

* Revert hide workaround for sidebar toggle, file upstream issue #20

The MauiSidebarToggle is auto-added by the platform backend for
FlyoutPage with no API to suppress it. Filed shinyorg/mauiplatforms#20
to request explicit sidebar layout should suppress auto-injection.

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

* Update toolbar filters dynamically after data refresh

AppleSimulators: populate Family/Runtime filter options from loaded
simulator data after each refresh.
AndroidSdk: update Category filter options from loaded packages
after each refresh.

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

* Update filter menu checkmarks natively without toolbar rebuild

Subscribe to FilterChanged and update NSMenuItem.State directly on
the existing NSMenu, avoiding full toolbar rebuild flicker.

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

* Move Apple identity picker to native toolbar menu

Show Apple logo + identity name as a menu toolbar item on Apple pages
(Certificates, Profiles, Devices, Bundle IDs). Uses async identity
loading with caching to avoid UI thread deadlock. Hidden from Blazor
on macOS since it's in the native toolbar.

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

* Replace emoji sync badges with Font Awesome icons on Certificates page

Use fa-check-circle (green), fa-hdd (blue), fa-cloud (yellow),
fa-times-circle (grey) instead of 🟢🔵🟡⚫ emoji characters.

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

* Adopt platform backend beta.8: content insets, remove workarounds

- Update Platform.Maui.MacOS packages from beta.7 to beta.8
- Use native ContentInsets on MacOSBlazorWebView instead of CSS padding hack
- Remove TitlebarDragView overlay (now handled by platform backend)
- Remove sidebar toggle polling workaround (fixed in beta.8 via #20)
- Add ShowsTitle=true to Apple identity menu (enabled by #22)
- Switch to convenience mode toolbar layout (no explicit layouts)
- Remove ghost toolbar item by not mixing SetMenuItems with layouts
- Copilot button stays in ToolbarItems with SidebarTrailing placement

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

* Rearrange toolbar: identity+add left, filter+search center, refresh right

Use explicit content layout to position toolbar items:
[Identity] [Add/Create] ← FlexibleSpace → [Filter] [Search] [Refresh]

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

* Cache toolbar structure to avoid flash on same-structure navigation

Compute a toolbar signature from action IDs, search, filter, and identity
presence. When navigating between pages with identical structure, update
action commands, search placeholder, and filter submenus natively without
triggering a full NSToolbar rebuild. Uses MenuActionTarget NSObject to
bridge native NSMenuItem clicks for in-place filter menu updates.

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

* Add apple-simulators to AppleRoutes, remove debug logging

Fix toolbar flash between Bundle IDs and Simulators caused by
/apple-simulators missing from AppleRoutes — identity flag mismatch
triggered unnecessary full rebuilds.

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

* Create superset toolbar once, toggle visibility with setHidden:

Build all possible toolbar items (refresh, create, import, search,
filter, identity) once during initialization. On page navigation,
use NSToolbarItem.setHidden: to show/hide items natively instead of
rebuilding the entire NSToolbar. Updates commands, search placeholder,
and filter submenus in place. Eliminates toolbar flash on all
page transitions.

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

* Fix filter checkmarks not updating on pages with identity menu

OnFilterSelectionChanged iterated all NSMenuToolbarItems and hit the
identity menu (MauiMenu_0) first. Identity items have no submenus,
so the null submenu check caused an early return before reaching the
actual filter menu (MauiMenu_1). Now targets the filter menu directly
by its toolbar item identifier.

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

* Fix identity menu: native rebuild, auto-select, title display

- RebuildIdentityMenuNatively() populates NSMenu items on navigation
- Auto-select first identity when none explicitly selected
- Set NSMenuToolbarItem.Title (not just Label) for visible button text
- Find filter/identity menus by identifier (MauiMenu_0/MauiMenu_1)

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

* Add Google identity to native toolbar for Firebase Push page

Identity menu now adapts per route - shows Apple identities on Apple
routes (apple.logo icon) and Google identities on Firebase routes
(flame icon). Both auto-select the first identity when none selected.
Added GoogleRoutes HashSet for /firebase-push.

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

* Add Settings... item to identity menus with scroll-to-section

Both Apple and Google identity toolbar menus now show a separator
followed by 'Settings...' that navigates to /settings and smooth-
scrolls to the relevant identity section. Added id attributes to
the Apple and Google identity sections in Settings.razor.

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

* Hide GoogleIdentityPicker on macOS — native toolbar handles it

Same pattern as AppleIdentityPicker: check PlatformInfo.IsMacOS and
render nothing, since the identity selection is in the native toolbar.

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

* Add refresh toolbar button to Secrets Publish page

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

* Add search and clear filters on Secrets Publish page

Search filters the managed secrets list by key name. Calls
SetFilters() with no args to clear inherited filters so the
filter toolbar button hides on this page.

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

* Move Publish wizard to toolbar button on macOS

Added 'Publish to CI/CD' toolbar action (wand.and.stars icon) to the
superset. On macOS, the Apple CI Secrets card is hidden via
platform-hide-macos CSS class since the toolbar button replaces it.
Added platform-hide-macos utility class to MainLayout.

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

* Use MacOSMenuToolbarItem for Publish to CI/CD with icon+text

Replace plain ToolbarItem with MacOSMenuToolbarItem using ShowsTitle
to display wand icon alongside 'Publish to CI/CD' text. Updated menu
identifier mapping: identity=MauiMenu_0, publish=MauiMenu_1,
filter=MauiMenu_2.

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

* Publish toolbar menu with wizard and selection-aware publish

Publish toolbar button now shows 'Publish' with upload icon and two
submenu items:
- 'Publish to CI/CD' (wand icon) — opens the CI secrets wizard
- 'Publish Selected...' (upload icon) — disabled until secrets are
  selected, then enabled and triggers publish flow

Selection changes in the Blazor page toggle publish-selected in the
toolbar items, and UpdatePublishMenuState enables/disables the native
NSMenuItem accordingly.

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

* Replace emoji sync badges with Font Awesome icons on Keystores page

Match the Certificates page pattern: use fas fa-check-circle (synced),
fas fa-hdd (local), fas fa-cloud (cloud), fas fa-times-circle (none)
with colored text-sync-* CSS classes instead of emoji icons.

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

* Use custom Copilot icon in macOS toolbar sidebar

Replace SF Symbol 'sparkles' with a custom template PNG loaded from
the app bundle. The image is sized to 18pt for crisp Retina rendering
and set as a template image so macOS auto-tints it for light/dark mode.

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

* Create ghcp-icon.png

* Disable refresh toolbar button during refresh operations

Add SetItemEnabled/IsItemEnabled to IToolbarService for toggling
individual toolbar item enabled state. All 12 pages with refresh
buttons now disable the button on click and re-enable in a finally
block. The macOS native side reflects this via setEnabled: on the
NSToolbarItem.

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

* Upgrade to Platform.Maui.MacOS 0.2.0-beta.10

- Remove hardcoded ContentInsets (auto-calculated from toolbar height, #26/#28)
- Add HideScrollPocketOverlay for cleaner toolbar appearance (#27)
- Use MacOSToolbarItem.SetIsVisible() for action items instead of
  objc_msgSend setHidden: workaround (#23/#31)
- Keep objc_msgSend for menus/search (upstream #32 filed for IsVisible support)
- Ghost Title item fix from SetContentLayout(null) now handled upstream (#24/#30)

Filed upstream: #32 (IsVisible for menus/search), #33 (Release trimming strips types)

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

* Windows TitleBar toolbar, fix Essentials DI, fix adb path on Windows

- Add MAUI TitleBar to Windows app with dynamic toolbar content
- Create WindowsTitleBarManager that subscribes to IToolbarService and
  rebuilds TitleBar search/filter/action controls per page
- Add HasNativeToolbar to IPlatformService; update 6 Blazor pages to
  hide inline toolbar UI when native toolbar is available
- Register MAUI Essentials (ISecureStorage, IPreferences, ILauncher,
  IClipboard) in DI for Windows - fixes startup crash
- Fix adb path on Windows: add AppDataPath.GetAdbPath() helper that
  appends .exe on Windows, update 5 services
- Update MauiDevFlow CLI and packages to 0.10.0
- Wrap macOS right-click suppression in #if !DEBUG for DevTools access

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

* Improve Windows TitleBar: functional filter pickers, better buttons, logo icon

- Replace non-functional filter buttons with native Picker dropdowns
- Make action buttons larger with visible background, border, rounded corners
- Add separator between filters and action buttons
- Use sherpa logo icon instead of title text
- Extract shared color constants

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

* Add PerMonitorV2 DPI awareness manifest for crisp high-DPI rendering

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

* Polish TitleBar: compact filter dropdowns, icon-only buttons, fix centering

- Replace ugly Picker controls with compact filter buttons using
  MenuFlyout dropdowns that open on left-click via WinUI ShowAt
- All action buttons are now icon-only (32x36px) with tooltip labels
- Fix vertical centering on all controls with VerticalOptions.Center
- Filter buttons show muted text for 'All', white when filtered

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

* Unified filter menu with submenus like macOS toolbar

- Single 'Filter' button replaces per-category dropdowns
- MenuFlyout with MenuFlyoutSubItem for each filter category
- Checkmarks show selected option in each submenu
- Button turns accent purple when any filter is active

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

* Use Fluent icon font for TitleBar buttons with proper centering

- Replace unicode text icons with FluentIcons font glyphs (Filter, ChevronDown, ArrowClockwise, Add, etc.)
- Add AathifMahir.Maui.MauiIcons.Fluent NuGet package and wire up UseFluentMauiIcons()
- Add ApplyVerticalCentering() using WinUI platform interop for proper content alignment
- Add ApplyHoverEffect() for interactive hover states on all TitleBar buttons
- Filter button shows Filter20 + ChevronDown16 glyphs, action buttons use mapped Fluent icons

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

* Increase filter button horizontal padding for better spacing

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

* Make window caption buttons white on dark TitleBar background

Set ButtonForegroundColor, ButtonInactiveForegroundColor, ButtonHoverForegroundColor,
and ButtonPressedForegroundColor to white via AppWindow.TitleBar so min/max/close
buttons are always visible against the dark TitleBar.

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

* Move sherpa logo from TitleBar.Icon to LeadingContent for larger display

Use a 28x28 Image in LeadingContent instead of the tiny TitleBar.Icon property.

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

* Widen filter button for better glyph spacing

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

* Move filter button next to search bar, remove separator, widen filter button

Filter button now sits in Content area beside the search entry instead of
TrailingContent. Removed the vertical separator between filters and action
buttons. Increased filter button WidthRequest to 60 with more padding.

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

* Upgrade to Platform.Maui.MacOS 0.2.0-beta.11

- Update all three packages from beta.10 to beta.11
- Replace _objc_msgSend_bool setHidden: workaround for menus/search with
  MacOSToolbarItem.SetIsVisible() API (upstream PR #36)
- Store menu/search items as fields for direct IsVisible manipulation
- Re-add TitlebarDragView overlay on WebView container for window dragging
  (upstream overlay on themeFrame doesn't work with split view layout)
- Scroll pocket fix (PR #37) and Release trimming fix (PR #38) automatic

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

* Rearrange TitleBar: [icon][add] center:[search][filter] right:[refresh]

Split actions so add/create buttons appear in LeadingContent next to the logo,
search+filter are centered in Content, and refresh goes to TrailingContent.
Reduced filter button padding and width for tighter fit.

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

* Add spacer between sherpa icon and action buttons to align with sidebar width

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

* Increase icon-to-button spacer to 215px for better sidebar alignment

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

* Add macOS app CI build, signing, and notarization

- Add Entitlements.plist and Entitlements.Debug.plist for macOS project
  (sandbox off, network client; debug adds network server + get-task-allow)
- Add EnableHardenedRuntime and CodesignEntitlements to macOS csproj
- Add publish-macos job: build, sign with hardened runtime, create ZIP
- Add notarize-macos job: submit to Apple notarization, staple ticket
- Update create-release to include MAUI-Sherpa.macos.zip artifact
- Same signing certificate and identity as Mac Catalyst build

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

* Polish Copilot overlay: top-left anchor, hide debug FAB

- Wrap floating Copilot button in #if DEBUG (macOS uses native toolbar)
- Animate overlay from top-left instead of bottom-left
- Anchor dialog to top-left with smaller top/left margins (1rem)

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

* Add identity picker dropdown to TitleBar for Apple/Google pages

Shows Apple identity picker (with  icon) on certificate/profile/device/bundle/simulator
pages, and Google identity picker (with fire icon) on firebase-push page.
Dropdown shows available identities with checkmark on selected, plus a
Settings separator item. Placed in LeadingContent between icon spacer and
add buttons, matching macOS toolbar behavior.

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

* Use FormattedText with per-Span FontFamily for identity picker

Replace Button with Border+Label using FormattedString so icon glyph
(FluentIcons font), identity name (system font), and chevron glyph
(FluentIcons font) each render in their correct font family.
Shared CreateIdentityPickerView() handles layout, flyout, and hover.

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

* Fix identity picker: use AppStore icon, HorizontalStackLayout for alignment

Replace FormattedString with HorizontalStackLayout containing separate
Image (Fluent icon), Label (name), and Label (chevron) for proper
vertical alignment. Use AppStore24 icon for Apple identity picker.

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

* Remove sidebar top padding on Windows for flush menu alignment

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

* Use FontAwesome Brand icons for Apple/Google identity pickers

Add AathifMahir.Maui.MauiIcons.FontAwesome.Brand package for proper
Apple () and Google (G) brand icons in identity picker buttons.
Use Label with FontAwesomeBrandIcons font family for consistent
rendering alongside Fluent icon chevron.

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

* fix: enable document-level scrolling on macOS for scroll-under-toolbar effect

- Move scrolling from nested .content div to document level so WKWebView
  setObscuredContentInsets can scroll content behind the toolbar naturally
- Set html/body height to auto and overflow to visible via JS init
- Disable horizontal scroll bounce with overflow-x: hidden
- Remove broken #if DEBUG preprocessor directive from CopilotOverlay.razor
  (Razor doesn't process C# preprocessor directives in markup)
- Exclude WindowsTitleBarManager.cs from macOS build (FluentIcons not available)

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

* fix: remove SDK path bar from Android pages, add loading state to SDK Packages

Users can change the SDK path via the Settings page — no need to show it
on every Android page. Removes the SDK path bar (with copy/open/settings
buttons) from Devices, Emulators, and SDK Packages pages.

Adds a loading state card to SDK Packages page matching the Emulators
pattern (pulsing icon + title + description) for when data is loading.

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

* Respect native toolbar; add SDK path & toolbar updates

Replace macOS-specific UI checks with PlatformInfo.HasNativeToolbar and hide built-in identity pickers/toolbars when a native toolbar is present. Add an SDK path bar (with copy/reveal/settings actions) to Android SDK, Devices, Emulators pages. Add an "Update All" toolbar action and handler to the Android SDK page. Inject PlatformInfo where needed and wrap toolbar UI with native-toolbar checks across multiple pages. In Devices page add toolbar search integration and client-side filtering (FilteredDevices) and wire up search events. Update WindowsTitleBarManager to auto-select the first cached Apple/Google identity when none is selected and ensure toolbar updates only when identities exist.

* Remove Android SDK path bar from pages

The SDK path is available in Settings — no need to show it inline
on the Devices, Emulators, and SDK Packages pages.

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

* Add Copilot button to Windows TitleBar

- Add white copilot icon (ghcp_icon_white.png) inverted from docs icon
- Add copilot button between sherpa logo and sidebar spacer in TitleBar
- Button triggers ICopilotContextService.ToggleOverlay() to open/close overlay
- Hide Blazor FAB when HasNativeToolbar (not just IsMacOS)
- Reduce sherpa icon margin, add explicit spacer for sidebar width alignment
- Hover effect via PointerGestureRecognizer on Border

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

* Notarize apps on main branch pushes with commit status

- Both notarize jobs now run on main pushes and tag pushes (not just tags)
- Add commit status updates for notarization results
- Release creation and Homebrew tap remain tag-only

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redth added a commit that referenced this pull request Mar 11, 2026
…ng style

- Use HttpClient.GetAsync instead of GetStringAsync to read error response bodies
- Parse structured error JSON ({success: false, error: '...'}) from non-2xx responses
- Format permission errors as actionable messages ('Missing Android permission: ...')
- Show permission errors as amber warnings (shield icon) instead of red errors
- Add info-card-warning CSS with amber border and text color
- Filed MauiDevFlow issue #37 for structured error reason codes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redth added a commit that referenced this pull request Mar 11, 2026
* feat: profiling wizard UI, integrated --dsrouter, and pipeline model

- Refactor Profiling.razor from single scrolling form into 6-step wizard:
  Session Setup → Capture Kinds → Target → Review & Plan → Capture → Results
- Add horizontal step indicator, Next/Back navigation, step validation
- Move advanced session fields into collapsible section
- Auto-run prerequisites and plan generation on entering Review step
- Add placeholder steps for Capture Running and Results (pipeline runner TBD)

- Update ProfilingCaptureOrchestrationService to prefer integrated --dsrouter:
  dotnet-trace/dotnet-gcdump now use --dsrouter android|android-emu|ios|ios-sim
  instead of separate dotnet-dsrouter process step
- Add ProfilingStopTrigger enum and CanRunParallel/StopTrigger to step model
- Set parallel/stop metadata on trace, launch, memory, and log steps
- Keep CreateDsRouterStep as documented fallback

- Update orchestration tests for new --dsrouter integrated commands
- Verify no standalone dsrouter step in mobile plans
- Assert CanRunParallel and StopTrigger on generated steps

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

* Change profiling artifact output dirs from GUIDs to project-name/date-run paths

Replace opaque GUID-based output directories (artifacts/profiling/{guid}/)
with human-readable paths (artifacts/profiling/{ProjectName}/{date}-{run}/).

- Add BuildDefaultOutputDirectory method that derives the project name from
  the project path and auto-increments run numbers per date
- Simplify artifact filenames: trace.nettrace, memory.gcdump, logs.txt
- Update Profiling.razor placeholder text to reflect the new pattern
- Add tests for output directory naming, artifact filenames, and fallback

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

* feat: pipeline runner service, capture & results step UI

- Add IProfilingSessionRunner interface with RunAsync, StopCapture, Cancel
- Add ProfilingSessionRunnerService with dependency-ordered execution,
  parallel step support, long-running process management, SIGINT stop
- Add ProfilingPipelineModels (state enums, step status, pipeline result)
- Remove per-command 'Run in Sherpa' buttons from step 3 (Review & Plan)
- Implement step 4 (Capture): live pipeline status, per-step cards with
  expandable output logs, Stop Capture and Cancel buttons
- Implement step 5 (Results): artifact listing with file sizes, Reveal
  in Finder, Open in speedscope, Copy path, session summary, New Session
- Register IProcessExecutionService as Transient (per-step instances)
- Register IProfilingSessionRunner as Transient

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

* fix: register IProfilingSessionRunner in macOS DI, change IProcessExecutionService to Transient

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

* Fix dsrouter conflict: use standalone dsrouter when both trace+gcdump on mobile

When both dotnet-trace and dotnet-gcdump are requested on a mobile target,
only one can use --dsrouter at a time. This change:

- Detects when both trace and memory capture are requested on mobile
- Falls back to a standalone dotnet-dsrouter process in that case
- Both tools connect via --diagnostic-port using the IPC socket address
- Introduces isMobileTarget flag to separate mobile detection from dsrouter arg
- Fixes process discovery logic to not trigger for mobile targets
- Adds test for trace-only mobile capture (still uses inline --dsrouter)
- Updates existing mobile tests to expect standalone dsrouter behavior

All 270 tests pass (223 Core + 47 Workloads).

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

* Fix IPC socket path too long for macOS domain sockets

macOS limits Unix domain socket paths to 104 characters. The previous
naming pattern 'maui-sherpa-profile-{full-guid}.sock' was 108 chars
when combined with the temp directory path, causing dsrouter and
dotnet-trace to crash with ArgumentOutOfRangeException.

Shortened to 'ms-prof-{8-char-guid}.sock' (~75 chars total).

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

* Fix diagnostic-port to use connect mode for standalone dsrouter

When using --diagnostic-port with a standalone dsrouter, both dotnet-trace
and dotnet-gcdump must connect as clients (not create their own server).
Append ',connect' to the IPC address so tools use connect mode instead
of the default listen mode which tries to bind the same socket.

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

* Set ANDROID_SERIAL env var for dsrouter device targeting

When multiple Android devices/emulators are connected, adb commands
(used by dsrouter for port forwarding) fail without knowing which
device to target. Set ANDROID_SERIAL environment variable to the
selected device identifier on all dsrouter-related steps (standalone
dsrouter, dotnet-trace, dotnet-gcdump).

Extracted BuildAndroidEnvironment() helper to set both ANDROID_HOME
and ANDROID_SERIAL consistently across all Android profiling steps.

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

* Add auto-scroll to pipeline log output and fix app launch hang

Auto-scroll:
- Created logScrollInterop.js with track/scrollToBottom/untrack methods
- Log containers auto-scroll to bottom as output arrives
- Pauses auto-scroll when user scrolls up manually
- Re-engages when user scrolls back to bottom
- Tracks per-container via element ID

App launch fix:
- Changed SuspendAtStartup default from true to false
- Prevents profiled apps from hanging on splash screen waiting for
  diagnostic tool connection (the previous default suspended the runtime)

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

* Fix stop capture to cleanly shut down and show results

When user clicks Stop Capture, all running steps should be treated as
'stopped' (not failed), artifacts should be collected, and the pipeline
should transition to Completed with results.

Changes:
- Added _stopRequested flag to ProfilingSessionRunnerService
- StopCapture() now marks ALL running steps as Stopped (not just
  ManualStop ones) since killing dsrouter cascades to dependent steps
- LaunchStepAsync treats non-zero exits as 'stopped' when _stopRequested
- RunAsync catch block detects stop-during-shutdown and collects
  artifacts instead of reporting failure
- Pipeline transitions to Completed (with partial results) after stop

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

* Convert profiling sample to native MAUI with Blazor fallback

- Create NativeMainPage.cs with full native MAUI controls for all 4 scenarios
  (CPU stress, memory allocation/churn, rendering tile wall + feed, network bursts)
- Add RunNativeNetworkBurstAsync to ProfilingScenarioService (HttpClient-based,
  replaces JS fetch for native mode)
- Fix BlazorMainPage constructor name (was still MainPage after rename)
- Native page includes: metrics strip (6 process/GC labels), scenario cards with
  sliders/buttons/status labels, tile wall with FlexLayout, feed list, network
  results list
- App defaults to native page with toolbar button to switch to Blazor view
- Dark theme styling matching Blazor version

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

* Fix artifact discovery: remove speedscope format, fix path nesting

- Remove --format speedscope from dotnet-trace args — it caused the output
  file to be written as trace.nettrace.speedscope.json while we looked for
  trace.nettrace. Raw .nettrace is the most useful format anyway.
- Fix CollectArtifacts double path nesting — RelativePath already contains
  the full path from CWD (including OutputDirectory), but the old code
  prepended OutputDirectory again, making files unfindable.
- Use Path.GetFullPath for consistent absolute path resolution.

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

* Fix artifact paths and add per-step log files

- Make OutputDirectory absolute at plan creation time by resolving
  relative paths against the project's working directory. The macOS app
  runs from inside the .app bundle, so relative paths resolved to the
  wrong location when checking for artifacts.
- Write per-step stdout/stderr to {stepId}.log files in the output
  directory. Each log includes a header (command, timestamp) and footer
  (exit code, duration, errors). Logs stream in real-time via AutoFlush.
- Update test to expect absolute output directory path.

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

* Fix artifact flush: wait for process exit after SIGINT

dotnet-trace needs time to finalize the .nettrace file after receiving
SIGINT. Previously, Cancel() sent SIGINT then immediately cancelled the
linked CancellationTokenSource, which caused WaitForExitAsync to throw
and return before the process had flushed its output. Now Cancel() sends
SIGINT and waits up to 15s for the process to exit before cancelling the
token, giving tools time to write their output files.

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

* Fix process shutdown: let SIGINT'd processes exit naturally

The core issue: Cancel() sent SIGINT then immediately cancelled the
CancellationToken, which unblocked WaitForExitAsync before the process
had time to flush output files (dotnet-trace needs several seconds to
finalize .nettrace). Now Cancel() sends SIGINT but does NOT cancel the
token — WaitForExitAsync naturally completes when the process exits
after flushing. A 30s safety timeout force-cancels only if the process
hangs. Also made StopCapture async (StopCaptureAsync) on the interface.

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

* Fix Android profiling: use AndroidEnableProfiler and adb setprop

Replace incorrect MSBuild properties (DiagnosticAddress, DiagnosticPort,
DiagnosticListenMode, EnableDiagnostics) with the correct Android profiling
setup:

- Use AndroidEnableProfiler=true to include Mono diagnostic component
- Add adb shell setprop debug.mono.profile step to configure diagnostic port
- Add adb reverse step for physical Android devices
- Set ANDROID_SERIAL environment on launch step for multi-device targeting

The runtime diagnostic port on Android is configured via the
debug.mono.profile system property, not MSBuild properties. This was
confirmed by testing against the dotnet/android documentation and
verified with successful trace captures.

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

* Fix Stop Capture button visibility during active recording

The Stop Capture button was only shown during the 'Running' state (while
steps are launching) but hidden during 'WaitingForStop' (when capture
tools are actively recording and waiting for user to stop). This meant
the user could only see a Cancel button during recording.

Fix: Show Stop Capture button during WaitingForStop (the active recording
state) and show a launching spinner during Running. Also show elapsed
time during WaitingForStop and update state text to clarify what each
state means.

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

* Add profiling results visualization: speedscope viewer, GC dump table, auto-conversion

- Bundle speedscope v1.25.0 standalone build in wwwroot/speedscope/
- Add JS interop (speedscopeInterop.js) to load profile data into iframe
- Add inline speedscope viewer in Results step with auto file injection
- Create GcDumpReportParser (Core) to parse dotnet-gcdump report output
- Create GcDumpReportService to shell out to dotnet-gcdump and return data
- Create ProfilingArtifactConverterService for .nettrace to .speedscope.json
- Pipeline runner auto-converts .nettrace to speedscope after capture
- Results step shows 'View Trace' and 'View GC Dump' action buttons
- GC dump viewer: sortable/filterable table with type, count, size, % bar
- Add IGcDumpReportService and IProfilingArtifactConverterService interfaces
- Register new services in both MauiProgram.cs and MacOSMauiProgram.cs
- Add 6 unit tests for GcDumpReportParser (229 total, all passing)

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

* Add profiling session library: persistent sessions, session list UI, import/export

- Add ProfilingSessionManifest models for persistent session metadata
- Add ProfilingSessionStorageService for CRUD + import/export of session folders
- Transform Profiling page from one-shot wizard to session library with ViewMode toggle
- Session list shows cards with platform icon, date, duration, artifacts, status badges
- Expandable session cards with inline artifact viewers (speedscope, GC dump)
- Session persistence: captures auto-save session.json with full manifest after pipeline
- Output directory now uses managed storage (AppDataPath/profiling/{session-id}/)
- Toolbar: New Session (+), Import, Search, Refresh buttons
- Session actions: Export (.zip), Open Folder, Delete with confirmation
- Empty state with prominent 'New Profiling Session' call-to-action
- Wizard mode accessible via 'Back to Sessions' navigation

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

* Extract profiling wizard steps 0-4 into modal dialog

Move the inline wizard (steps 0-4: Session Setup, Capture Kinds, Target,
Review & Plan, Capture) from Profiling.razor into a new
ProfilingCaptureWizardModal.razor component that uses the established
WizardFormPage pattern for native footer buttons (Back/Next/Start Capture).

- Create ProfilingCaptureWizardModal.razor at /modal/profiling-wizard route
- Wire HybridFormBridge for native Back/Next/Submit button control
- Handle pipeline completion: save session manifest, set bridge.Result
- Hard link already exists for macOS project
- Simplify Profiling.razor to session list only (remove ViewMode enum)
- Add StartNewWizardAsync using IFormModalService.ShowAsync pattern
- Auto-expand new session in list after wizard completes

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

* Fix stray closing brace rendered on profiling session list page

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

* Fix 10 profiling UI polish issues

- Fix GC dump timing: add build-and-run dependency so gcdump waits for app
- Fix nettrace viewer: handle conversion failure with inline error + retry
- Fix artifact card layout: buttons wrap to their own row
- Reduce wizard modal padding for compact layout
- Add squircle border-radius (14px) on all cards/panels
- Fix artifact path overflow: show filename only, full path in tooltip
- Make plan step commands collapsible (collapsed by default)
- Disable Next button during prerequisites check and plan generation
- Simplify prerequisites: green banner when all pass, expandable details
- Fix suspend at startup: default to false, add descriptive help text

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

* Split artifacts into profiling and log file sections

Profiling artifacts (.nettrace, .gcdump, .speedscope.json) show in a
primary always-visible section. Log files (.log) are grouped into a
collapsible section that starts collapsed, keeping the view clean.

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

* Reorganize wizard: move Scenario & Suspend to Capture Kinds step

- Move Scenario dropdown from Step 0 to Step 1 (Capture Kinds) as preset
- Move Suspend at Startup from Step 0 Advanced to Step 1
- Auto-toggle suspend when Launch scenario selected
- Rename Step 0 from 'Session Setup' to 'Project'
- Reduce outer padding from 0.5rem to 0.25rem

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

* Remove inline New Profiling Session button and reduce wizard padding

Toolbar + button already provides the CTA. Also eliminates remaining
excess padding in the wizard step indicator and form-content wrapper.

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

* Remove scenario summary from Target step

Already shown on Capture Kinds step; no need to repeat on Target.

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

* Polish wizard: compact prerequisites, plan summary, and commands

- Auto-toggle suspend at startup when Startup capture kind is checked
- Remove platform notes info-banner from Target step
- Prerequisites: use FA icon instead of emoji, compact list with inline
  status icons instead of verbose grid cards
- Plan summary: inline key-value rows instead of 4 nested cards
- Commands: compact list with tooltip descriptions instead of card grid
- Move Expected Artifacts before Commands in capture plan
- Clean up unused CSS (prereq cards, summary cards, command cards)

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

* Simplify expected artifacts to flat inline list

Replace grid of nested cards with a compact row-per-artifact layout
showing name, filename, and kind inline.

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

* Show managed session storage path in plan preview

Plan preview now pre-computes the session storage directory path
instead of showing the relative artifacts/ path. The actual pipeline
still generates its own session ID at start time.

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

* Wrap output directory path on its own line in capture plan

Long paths no longer cause horizontal scrolling. The path is now in a
wrapping monospace code block below the label.

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

* Flatten nested panel cards across all wizard steps

Replace <section class='panel'> wrappers with flat step-content/section-header
pattern across Steps 0-4. Add CSS for .step-content, .section-header, and
.section-title classes. Remove extra closing div tag from capture plan step.

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

* Fix input/select background to contrast with dialog

Change field-group inputs and selects from --bg-primary to --bg-secondary
so they visually stand out from the dialog background.

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

* Fix section header background and remove unused panel CSS

Set explicit transparent background on section-header to prevent
subtle color artifacts on hover. Remove dead .panel/.panel-header/
.panel-body CSS no longer used after card flattening.

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

* Show section headers with inline loading on Review & Plan step

Replace single loading spinner with always-visible Prerequisites and
Capture plan section headers. Each shows inline status badges
(Checking.../Generating.../Waiting...) while loading, then expands
content when ready. Add .section-loading CSS.

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

* Hide section header badges while loading

Only show status badges (All OK, Valid, etc.) after loading completes.
The inline loading text below the header is sufficient during loading.

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

* Simplify prerequisites to chips + chevron toggle

Remove success banner card. Show status badges inline in section header
with a chevron button to expand/collapse details. Move loading indicators
to inline text in headers for both Prerequisites and Capture plan.

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

* Redesign capture plan: squircle card, artifact chips, unified command list

- Wrap TFM and output directory in a borderless squircle card
- Show expected artifacts as inline extension chips (.nettrace, .gcdump)
  next to output directory label instead of separate section
- Replace individual command cards with a single bordered list using
  border-bottom separators and chevron toggle on far right
- Remove unused expected-artifact and command-details CSS

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

* Stop duplicating prerequisite warnings in capture plan

Prerequisite warnings (like 'Update available') are already shown in the
Prerequisites section. Remove forwarding them into plan validation
warnings to avoid redundant display.

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

* Reduce input/select and Browse button sizing in wizard

Decrease field padding and font-size for a more compact, consistent
look. Make Browse button match input height with smaller padding.

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

* Move Stop Capture and Cancel to native footer buttons

On step 4, the native primary button becomes 'Stop Capture' when the
pipeline is waiting for stop. The native Cancel button cancels the
pipeline and returns to step 3 without closing the modal (via new
PreventClose bridge property). Remove redundant Blazor action buttons.

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

* Fix trace files going to wrong directory

Pass session storage path as OutputDirectory when generating the plan
so commands and artifact RelativePaths are built with the correct path.
Previously the plan was generated with a relative artifacts/ path and
only the top-level OutputDirectory was overridden at pipeline start,
leaving command arguments pointing to the old location.

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

* Compact artifact list: rows with inline actions

Replace large artifact cards with a compact bordered list. Each artifact
is a single row with icon, filename, size, and small action buttons on
the right. View buttons stay labeled; Reveal/Copy are icon-only with
tooltips. Error rows nest below the relevant artifact.

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

* Fix Stop Capture closing modal before session is saved

The native Submit button (renamed to 'Stop Capture') always closed the
modal after the handler returned. But HandleStopCapture just signals the
pipeline to stop — the actual completion, artifact collection, and
session saving happen asynchronously in StartPipelineAsync.

Add PreventSubmitClose to HybridFormBridge so the native Submit handler
does not pop the bridge and close the modal while the pipeline is still
active. After the pipeline completes and the session is saved, the
Blazor component calls bridge.RequestClose() to close the modal with
the manifest as the result.

Also adds CloseRequested event to HybridFormBridge for Blazor components
to programmatically close the modal with a result.

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

* Fix GC dump running before app is ready

The pipeline executor treated a long-running dependency as satisfied the
moment it started. This was fine for dotnet-trace (which itself waits for
the app to connect via the diagnostic port) but broke dotnet-gcdump,
which connects, collects, and exits immediately — before the app had
even finished building.

Changes:
- Add IsReady property to ProfilingStepStatus. Long-running steps signal
  readiness when their output matches a configurable ReadyOutputPattern.
- Add ReadyOutputPattern to ProfilingCommandStep. Set to 'Process' for
  dotnet-trace (emitted when the app connects) and 'Build succeeded' for
  the build-and-run step.
- Refine dependency logic: non-long-running steps (like gcdump) now wait
  for their long-running dependencies to signal IsReady, not just start.
  Long-running steps still proceed as soon as the dependency starts.
- Make capture-memory depend on capture-trace when both are requested, so
  gcdump waits for trace to establish its diagnostic port connection.
- Poll with 500ms delay when waiting for readiness instead of blocking on
  task completion only.

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

* Open artifact viewers in separate windows

Move speedscope and GC dump viewers from inline rendering on the
Profiling page to dedicated Blazor pages (SpeedscopeViewer.razor,
GcDumpViewer.razor) that open in their own macOS windows via
ProfilingViewerService. Each viewer type gets its own window; clicking
View again replaces the previous viewer window.

Removes ~200 lines of inline viewer HTML, CSS, and state management
from Profiling.razor. The viewer pages are self-contained with their
own loading, error, and content states.

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

* Fix GC dump deadlock: add ReadyOutputPattern to dsrouter step

The start-dsrouter step was long-running but had no ReadyOutputPattern,
so its IsReady flag was never set. Since capture-memory (GC dump) is a
non-long-running step that depends on dsrouter, IsDependencySatisfied
required IsReady == true — causing a permanent deadlock.

dsrouter outputs 'Starting IPC server' when ready; use that as the
readiness pattern.

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

* Make GC dump on-demand instead of automatic pipeline step

dotnet-gcdump is a point-in-time heap snapshot, not a continuous
collector. Running it automatically in the pipeline captured the heap
at an arbitrary moment (usually right after app launch, before anything
useful happens).

Now GC dump is triggered on-demand via a 'Collect GC Dump' button in
the capture step UI. Users can take multiple snapshots at specific
moments during their session. Each dump is numbered (memory-1.gcdump,
memory-2.gcdump, etc.) and tracked as a session artifact.

Changes:
- Remove capture-memory from automatic pipeline commands
- Add CollectGcDumpAsync() to IProfilingSessionRunner for on-demand use
- Add Collect GC Dump button in wizard capture step (visible when
  pipeline is in WaitingForStop state and memory capture is selected)
- Update tests to reflect GC dump is no longer an auto pipeline step

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

* Add macOS AppKit implementation for ShowInputDialogAsync

The DialogService.ShowInputDialogAsync was wrapped in #if MACCATALYST
and the #else branch returned null, so the keystore password prompt
never appeared on macOS AppKit builds. Add a MACOSAPP implementation
using NSAlert with an accessory NSTextField (NSSecureTextField for
password prompts).

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

* Fix cloud keystore deletion silently failing

DeleteKeystoreFromCloudAsync was ignoring the bool return values from
DeleteSecretAsync, so when Infisical delete calls failed, the UI showed
a success toast and optimistically removed the item. On refresh, the
keystore reappeared because it was never actually deleted.

Now checks return values and throws InvalidOperationException with
details on which secrets failed to delete. Also improved Infisical
error logging to include inner exception messages for debugging.

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

* Fix cloud keystore delete using wrong alias for names with underscores

ExtractAliasFromKey strips the last _segment assuming it's the suffix
(_JKS/_PWD/_META). But DeleteFromCloud passed 'KEYSTORE_{alias}' with
no suffix, so for aliases like TEST_SIGNING_KEY it extracted
TEST_SIGNING instead — deleting non-existent keys while the real ones
remained.

Fix: pass the alias directly to DeleteKeystoreFromCloudAsync instead
of wrapping it in KEYSTORE_ prefix and re-extracting. Also filter
Infisical ListSecrets to exclude imported secrets from other paths.

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

* Enable MauiDevFlow profiling in sample app

Configure EnableProfiler, EnableHighLevelUiHooks, and
EnableDetailedUiHooks in the profiling sample app. Update
MauiDevFlow packages from 0.18.0 to 0.20.0.

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

* Require MauiDevFlow 0.20.0 for profiling, show version message

Update all MauiDevFlow package references from 0.18.0 to 0.20.0.
Add minimum version check (0.20.0) to the DevFlow profiling tab —
shows a clear message when the connected agent is too old instead
of failing with a capabilities error.

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

* Fix dotnet-trace to capture useful CPU sampling data for speedscope

dotnet-trace collect was invoked without --profile or --providers,
producing nearly empty speedscope files (9 generic frames, no managed
stacks). Now maps ProfilingCaptureKind to dotnet-trace profiles:
- Cpu/Startup → cpu-sampling (~100Hz kernel sampling)
- Rendering/Network/Energy/SystemTrace → dotnet-common

Also adds --format Speedscope so dotnet-trace emits both .nettrace
and .speedscope.json during capture, eliminating the separate
post-capture conversion step. Converter kept as fallback for
manually imported .nettrace files.

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

* Fix dotnet-trace profile: use dotnet-sampled-thread-time (cross-platform)

cpu-sampling is Linux-only (collect-linux). Use dotnet-sampled-thread-time
which works on all platforms and samples managed stacks at ~100Hz.

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

* Remove --format Speedscope from dotnet-trace collect

The --format flag may cause issues with --dsrouter mode, causing
the trace to end prematurely. Keep using post-capture conversion
via dotnet-trace convert instead.

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

* Make trace on-demand with Start/Stop buttons alongside GC Dump

- Remove capture-trace from auto-planned pipeline steps
- Add StartTraceAsync/StopTraceAsync to IProfilingSessionRunner
- Add IsTraceActive property for mutual exclusion with gcdump
- Pipeline enters WaitingForStop for on-demand actions even without ManualStop steps
- Always use standalone dsrouter on mobile (needed for on-demand trace/gcdump)
- Add JIT/Loader provider flags (Microsoft-Windows-DotNETRuntime:0x10000018:5) for speedscope symbol resolution
- Unified capture action bar UI with Start Trace, Stop Trace, and GC Dump buttons
- Disable conflicting actions (no gcdump while tracing, no trace while gcdump)
- Stop active trace automatically when user clicks Stop Capture
- Update 4 orchestration tests for on-demand trace pattern

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

* Fix session artifacts, speedscope loading, and nettrace View button

- Scan output directory for on-demand gcdump/nettrace/speedscope files
  not in the expected artifact list (memory-1.gcdump, trace-1.nettrace, etc.)
- Add fallback directory scan in wizard manifest creation for all artifact types
- Fix speedscope iframe to use #localProfilePath=1 hash param so
  window.speedscope.loadFileFromBase64() API is available
- Add polling (up to 5s) for speedscope API initialization with drop fallback
- Hide .nettrace View button on macOS (redundant with .speedscope.json;
  only useful on Windows with PerfView)

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

* Fix speedscope and gcdump viewer content loading

Speedscope: Replace cross-frame contentWindow API injection (fails in
WKWebView) with postMessage. Add message listener script to bundled
speedscope/index.html that receives profile data and simulates file
drop within iframe's own JS context. Remove broken #localProfilePath=1
hash that injected failing <script src="file:///1">.

GcDump: Fix parser to handle modern dotnet-gcdump report output format
(Object Bytes/Count/Type header, comma-formatted numbers, no hex MT
prefix). Strip annotation suffixes like (Bytes > 1K) [Module(...)].
Support both modern and legacy formats.

Add test for modern gcdump output format. All 257 tests pass.

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

* Fix speedscope viewer: force hash before bundle init + localStorage fallback

BlazorWebView strips #hash from iframe src attributes, so speedscope
never saw #localProfilePath and rendered its landing page instead of
activating the loadFileFromBase64 API.

Fix: add inline script BEFORE speedscope bundle that forces
window.location.hash = 'localProfilePath=_'. Also add localStorage
polling as a second communication channel alongside postMessage.

Use OnAfterRenderAsync instead of @onload (doesn't fire on iframes
in Blazor WebView). Send postMessage multiple times at increasing
intervals to handle timing variations.

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

* Show platform-specific native memory labels from MauiDevFlow

Add NativeMemoryKind to DevFlowProfilerSample DTO to match MauiDevFlow
PR #31. Update profiling tab to display platform-aware labels:
- apple.phys-footprint → 'Phys Footprint'
- android.native-heap-allocated → 'Native Heap'
- windows.working-set → 'Working Set'
- process.working-set-minus-managed → 'Native Memory'

Labels appear on metric card, chart header, and capabilities detail.
Raw kind string shown as tooltip and in detail panel.

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

* Add Platform Features tab to MauiDevFlow inspector

- Upgrade MauiDevFlow NuGet packages and CLI to 0.22.0
- Add DTOs for platform info, storage, permissions, sensors, geolocation
- Add 22 new API methods to DevFlowAgentClient (platform info, preferences
  CRUD, secure storage CRUD, permissions, sensors with WebSocket streaming,
  geolocation)
- Add DeleteAsync helper method to DevFlowAgentClient
- Create DevFlowPlatformTab.razor with 5 sub-tabs:
  - Info: card grid showing app info, device, display, battery, connectivity,
    version tracking
  - Storage: preferences table with inline edit/add/delete + secure storage
    lookup with add/delete
  - Permissions: color-coded status grid for all known permissions
  - Sensors: start/stop toggles with live WebSocket data streaming
  - Location: GPS coordinates with accuracy/timeout controls
- Register platform tab in DevFlowInspector (ValidTabs, tab button, switch)

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

* Fix platform tab: permissions parsing, error display, battery N/A

- Fix permissions: API returns { permissions: [...] } wrapper, not bare
  array. Added DevFlowPermissionsResponse wrapper DTO.
- Fix info cards: show per-card error messages when API returns errors
  (e.g., UIKit thread errors for App Info/Display on iOS simulator)
- Fix battery: show 'N/A' instead of '-100%' when chargeLevel is -1
- Add .info-card-error CSS with red border for failed cards

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

* Upgrade MauiDevFlow NuGet packages to 0.23.0

Includes fix for UIKit thread errors in AppInfo and DeviceDisplay
platform handlers (MauiDevFlow PR #35).

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

* Fix sensor reading display: match actual property names from agent

Agent broadcasts PascalCase properties (X, Y, Z, PressureInHectopascals,
HeadingMagneticNorth) but UI was looking for lowercase (x, y, z, pressure,
heading). Added TryGetDataProperty helper that tries exact, PascalCase,
and camelCase. Also shows raw JSON as fallback when properties don't match.

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

* Improve platform tab error handling: user-friendly messages and warning style

- Use HttpClient.GetAsync instead of GetStringAsync to read error response bodies
- Parse structured error JSON ({success: false, error: '...'}) from non-2xx responses
- Format permission errors as actionable messages ('Missing Android permission: ...')
- Show permission errors as amber warnings (shield icon) instead of red errors
- Add info-card-warning CSS with amber border and text color
- Filed MauiDevFlow issue #37 for structured error reason codes

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

* Support multiple concurrent sensor streams and add Stop All button

- Refactor DevFlowAgentClient to use Dictionary of per-sensor WebSocket
  connections instead of a single shared WebSocket
- Add StopSensorStream(sensor), StopAllSensorStreams(), IsSensorStreaming(),
  StreamingSensorCount to client API
- Update UI to track readings per sensor in a Dictionary
- Add 'Stop All (N)' button in toolbar when streams are active
- Each sensor can independently start/stop streaming simultaneously

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

* Move sensor readings inline into card row

Place live values between sensor name and action buttons instead of a
separate panel below. Readings sit right-aligned in the card, keeping
the layout compact. Shortened stream button labels to icon-only.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants