Conversation
There was a problem hiding this comment.
Pull request overview
This PR makes the Settings experience reflect actual persisted browser state (instead of “fake” desktop defaults), splits the Settings page into reusable sections, and updates test coverage to validate the new runtime/persistence behavior.
Changes:
- Reworked Settings UI into dedicated components/partials for Files, Cameras, Microphones, and AI provider drafts.
- Introduced persisted stores/models for AI provider drafts and browser file-storage preferences; included them in cloud snapshot import/export.
- Updated unit/UI tests and CSS to align with the new “honest runtime state” behavior and styling.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/PrompterOne.App.UITests/Teleprompter/TeleprompterSettingsFlowTests.cs | Updates UI expectations for AI provider cards (open state + save button). |
| tests/PrompterOne.App.UITests/Support/BrowserTestConstants.cs | Adds new UI-test constants and regex helpers used by updated flows. |
| tests/PrompterOne.App.UITests/Settings/SettingsCloudStorageFlowTests.cs | Tightens cloud settings flow to start from persisted toggle state. |
| tests/PrompterOne.App.Tests/Support/TestSupport.cs | Registers newly introduced settings stores for test DI. |
| tests/PrompterOne.App.Tests/Settings/SettingsInteractionTests.cs | Adds/updates tests validating browser-local labels and AI draft persistence. |
| src/PrompterOne.Shared/wwwroot/design/modules/settings/20-reference.css | Styling updates for “idle/local” statuses and inactive device cards. |
| src/PrompterOne.Shared/Storage/PrompterStorageDefaults.cs | Introduces browser-container display prefix and adds recordings/exports paths. |
| src/PrompterOne.Shared/Storage/Cloud/CloudStorageTransferService.cs | Includes AI + file-storage settings in settings bundle export/import. |
| src/PrompterOne.Shared/Storage/Cloud/CloudStorageModels.cs | Makes cloud prefs booleans “honest” (no forced defaults) and expands settings bundle. |
| src/PrompterOne.Shared/Settings/Services/BrowserFileStorageStore.cs | New store for file-storage settings + browser-local view state. |
| src/PrompterOne.Shared/Settings/Services/AiProviderSettingsStore.cs | New store for persisted AI provider draft settings. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.razor | Replaces monolithic sections with extracted settings components. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.Preferences.cs | Removes legacy fake file/AI prefs and centralizes toggle CSS helper. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.Navigation.cs | Minor nav constant refactor. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.Microphones.cs | New partial: microphone behaviors extracted from the monolith. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.MediaState.cs | New partial: media/device load/normalization logic extracted. |
| src/PrompterOne.Shared/Settings/Pages/SettingsPage.Cameras.cs | New partial: camera behaviors extracted from the monolith. |
| src/PrompterOne.Shared/Settings/Models/SettingsPagePreferences.cs | Removes fake desktop defaults and AI selection from settings-page prefs. |
| src/PrompterOne.Shared/Settings/Models/BrowserFileStorageSettings.cs | New persisted model + view-state records for file-storage section. |
| src/PrompterOne.Shared/Settings/Models/AiProviderSettings.cs | New persisted model for AI provider drafts (Claude/OpenAI/Ollama). |
| src/PrompterOne.Shared/Settings/Components/SettingsMicrophonesSection.razor | New extracted microphones section UI component. |
| src/PrompterOne.Shared/Settings/Components/SettingsMicrophoneLevelCard.razor | Makes mic meter/test IDs configurable and respects enabled/active states. |
| src/PrompterOne.Shared/Settings/Components/SettingsFilesSection.razor.cs | New code-behind for loading/saving browser file-storage settings/view state. |
| src/PrompterOne.Shared/Settings/Components/SettingsFilesSection.razor | Updates Files section to show browser-real storage labels and persisted toggles. |
| src/PrompterOne.Shared/Settings/Components/SettingsCloudSection.razor.cs | Adds explicit “idle” status class when disconnected. |
| src/PrompterOne.Shared/Settings/Components/SettingsCloudSection.razor | Clarifies copy to reflect optional snapshot targets + local credential storage. |
| src/PrompterOne.Shared/Settings/Components/SettingsCamerasSection.razor | New extracted cameras section UI component. |
| src/PrompterOne.Shared/Settings/Components/SettingsAiSection.razor.cs | New AI provider persistence logic (save/clear + local-only messaging). |
| src/PrompterOne.Shared/Settings/Components/SettingsAiSection.razor | Replaces “test connection” UI with local draft save/clear flow. |
| src/PrompterOne.Shared/Settings/Components/SettingsAiProviderCard.razor | New reusable card wrapper for AI provider UI actions and messaging. |
| src/PrompterOne.Shared/Contracts/UiTestIds.cs | Adds test IDs for AI save/clear/message and GO LIVE metrics. |
| src/PrompterOne.Shared/Contracts/UiDomIds.cs | Adds per-device microphone monitor DOM IDs for multiple meters. |
| src/PrompterOne.Shared/AppShell/Services/PrompterOneServiceCollectionExtensions.cs | Registers new settings stores in the shared DI setup. |
| src/PrompterOne.Core/Workspace/Models/StudioSettings.cs | Changes default camera mirror setting to false. |
| src/PrompterOne.Core/Media/Models/MediaSceneState.cs | Changes default scene transform horizontal mirror to false. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private static async Task<bool> HasOnClassAsync(ILocator locator) | ||
| { | ||
| var classes = await locator.GetAttributeAsync("class"); | ||
| return (classes ?? string.Empty).Contains("on", StringComparison.Ordinal); | ||
| } |
There was a problem hiding this comment.
HasOnClassAsync uses string.Contains("on") which can produce false positives if any other CSS class contains the substring on (e.g., "button", "connection"). Since this helper controls toggle state assertions, it should parse the class list (split on spaces like before) or use a word-boundary regex (e.g., \bon\b) to reliably detect the on class.
| await _aiProviderSettingsStore.SaveAsync(bundle.AiProviderSettings, cancellationToken); | ||
| await _browserFileStorageStore.SaveSettingsAsync(bundle.FileStorageSettings, cancellationToken); |
There was a problem hiding this comment.
RestoreSettingsAsync saves bundle.AiProviderSettings / bundle.FileStorageSettings without null-coalescing, but both AiProviderSettingsStore.SaveAsync and BrowserFileStorageStore.SaveSettingsAsync throw on null. If an imported snapshot contains explicit null values for these fields, import will fail. Coalesce/normalize before saving (e.g., default + .Normalize() for AI settings; BrowserFileStorageSettings.Default for file storage).
| await _aiProviderSettingsStore.SaveAsync(bundle.AiProviderSettings, cancellationToken); | |
| await _browserFileStorageStore.SaveSettingsAsync(bundle.FileStorageSettings, cancellationToken); | |
| // Ensure AI provider settings are never null when saved | |
| var aiSettings = (bundle.AiProviderSettings ?? new AiProviderSettings()).Normalize(); | |
| // Ensure file storage settings are never null when saved | |
| var fileStorageSettings = bundle.FileStorageSettings ?? BrowserFileStorageSettings.Default; | |
| await _aiProviderSettingsStore.SaveAsync(aiSettings, cancellationToken); | |
| await _browserFileStorageStore.SaveSettingsAsync(fileStorageSettings, cancellationToken); |
Summary
Validation
dotnet format /Users/ksemenenko/Developer/PrompterOne/PrompterOne.slnxdotnet build /Users/ksemenenko/Developer/PrompterOne/PrompterOne.slnx -warnaserrordotnet test /Users/ksemenenko/Developer/PrompterOne/tests/PrompterOne.App.Tests/PrompterOne.App.Tests.csproj --filter Settingsdotnet test /Users/ksemenenko/Developer/PrompterOne/tests/PrompterOne.App.UITests/PrompterOne.App.UITests.csproj --filter "FullyQualifiedName~Settings|FullyQualifiedName~TeleprompterSettingsFlow|FullyQualifiedName~MediaRuntimeIntegrationTests"dotnet test /Users/ksemenenko/Developer/PrompterOne/PrompterOne.slnxdotnet test /Users/ksemenenko/Developer/PrompterOne/PrompterOne.slnx --collect:"XPlat Code Coverage"