Skip to content

Commit 27b9db0

Browse files
committed
Fix reader and Go Live validation regressions
1 parent 28d9d4d commit 27b9db0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+710
-188
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ Repo-specific design rules:
320320
- App-shell logo navigation MUST always lead to the main home/library screen; it must not deep-link into Go Live, Teleprompter, or another feature-specific route.
321321
- Learn rehearsal speed MUST default to about 250 WPM and stay user-adjustable upward from that baseline; shipping a 300 WPM startup default is too aggressive.
322322
- Go Live `ON AIR` badges and preview live dots MUST appear only while recording or streaming is actually active; idle selected or armed sources must stay visually non-live.
323+
- Go Live chrome MUST stay operational and generic; do not surface the loaded script title or script preview subtitle in the Go Live header/session bar just because a script is open.
324+
- Go Live back navigation MUST return to the actual previous in-app screen when known, and only fall back to library when there is no valid in-app return target; it must never hardcode teleprompter as the back target.
323325
- Learn and Teleprompter are separate screens with separate style ownership; do not bundle RSVP and teleprompter reader feature styles into one shared screen stylesheet or let one page inherit the other page's visual treatment.
324326
- User preferences persistence MUST sit behind a platform-agnostic user-settings abstraction, with browser storage implemented via local storage and room for other platform-specific implementations; theme, teleprompter layout preferences, camera/scene preferences, and similar saved settings belong there instead of ad-hoc feature stores.
325327
- Streaming destination/platform configuration MUST be user-defined and persisted in settings; Settings and Go Live must not ship hardcoded platform instances, seeded destination accounts, or fixed fake provider rows beyond real runtime capabilities.

docs/Architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ flowchart LR
173173
- `BrowserThemeService` is the first concrete remote consumer and keeps shell appearance aligned across tabs without reload.
174174
- `GoLiveSessionService` is the current publisher and consumer for active `Go Live` session snapshots, including startup catch-up requests.
175175
- `MainLayout` consumes `GoLiveSessionService` and renders the global shell `Go Live` status for every screen.
176+
- `AppShellService` owns the current in-app route and the last valid non-`Go Live` return target so the `Go Live` back control can return to the actual previous screen instead of a hardcoded reader route.
176177
- The contract must stay message-based; tabs do not share live .NET memory.
177178

178179
## Library Contracts

src/PrompterOne.Shared/AppShell/Layout/MainLayout.razor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
5151
private string HeaderSubtitle => ShellState.Screen switch
5252
{
5353
AppShellScreen.Teleprompter => ShellState.Subtitle,
54-
AppShellScreen.GoLive => ShellState.Title,
5554
AppShellScreen.Learn => ShellState.Subtitle,
5655
_ => string.Empty
5756
};
@@ -131,6 +130,7 @@ protected override void OnInitialized()
131130
Navigation.LocationChanged += HandleLocationChanged;
132131
Shell.StateChanged += HandleShellStateChanged;
133132
GoLiveSession.StateChanged += HandleGoLiveSessionChanged;
133+
Shell.TrackNavigation(Navigation.Uri);
134134
SyncShellStateWithCurrentRoute(Navigation.Uri);
135135
}
136136

@@ -154,6 +154,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
154154
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
155155
{
156156
Logger.LogInformation(RouteChangedLogTemplate, e.Location);
157+
Shell.TrackNavigation(e.Location);
157158
SyncShellStateWithCurrentRoute(e.Location);
158159
StateHasChanged();
159160
}
@@ -182,7 +183,7 @@ private void SyncShellStateWithCurrentRoute(string uri)
182183
Shell.ShowTeleprompter(ShellState.Title, ShellState.Subtitle, currentScriptId);
183184
break;
184185
case AppRoutes.GoLive:
185-
Shell.ShowGoLive(ShellState.Title, ShellState.Subtitle, currentScriptId);
186+
Shell.ShowGoLive(currentScriptId);
186187
break;
187188
case AppRoutes.Settings:
188189
Shell.ShowSettings();

src/PrompterOne.Shared/AppShell/Services/AppShellService.cs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
using PrompterOne.Shared.Contracts;
2+
using PrompterOne.Shared.GoLive.Models;
23

34
namespace PrompterOne.Shared.Services;
45

56
public sealed class AppShellService
67
{
8+
private const string EmptyRoute = "";
9+
private const string QuerySeparator = "?";
10+
11+
private string _currentRoute = AppRoutes.Library;
12+
private string _goLiveBackRoute = AppRoutes.Library;
13+
714
public event Action? StateChanged;
815
public event Action<string>? LibrarySearchChanged;
916

@@ -34,8 +41,13 @@ public void ShowLearn(string title, string subtitle, string wpmLabel, string? sc
3441
public void ShowTeleprompter(string title, string subtitle, string? scriptId) =>
3542
SetScriptScopedState(AppShellScreen.Teleprompter, title, subtitle, string.Empty, scriptId);
3643

37-
public void ShowGoLive(string title, string subtitle, string? scriptId) =>
38-
SetScriptScopedState(AppShellScreen.GoLive, title, subtitle, string.Empty, scriptId);
44+
public void ShowGoLive(string? scriptId) =>
45+
SetScriptScopedState(
46+
AppShellScreen.GoLive,
47+
GoLiveText.Chrome.ScreenTitle,
48+
GoLiveText.Chrome.StreamingSubtitle,
49+
string.Empty,
50+
scriptId);
3951

4052
public void ShowSettings() =>
4153
SetState(new AppShellState(
@@ -67,6 +79,26 @@ public void UpdateLibrarySearch(string searchText)
6779

6880
public string GetGoLiveRoute() => BuildScriptScopedRoute(AppShellScreen.GoLive);
6981

82+
public string GetGoLiveBackRoute() => IsValidGoLiveBackTarget(_goLiveBackRoute)
83+
? _goLiveBackRoute
84+
: AppRoutes.Library;
85+
86+
public void TrackNavigation(string uri)
87+
{
88+
var nextRoute = NormalizeAppRoute(uri);
89+
if (string.IsNullOrWhiteSpace(nextRoute) || string.Equals(_currentRoute, nextRoute, StringComparison.Ordinal))
90+
{
91+
return;
92+
}
93+
94+
if (IsGoLiveRoute(nextRoute) && IsValidGoLiveBackTarget(_currentRoute))
95+
{
96+
_goLiveBackRoute = _currentRoute;
97+
}
98+
99+
_currentRoute = nextRoute;
100+
}
101+
70102
private void SetScriptScopedState(
71103
AppShellScreen screen,
72104
string title,
@@ -108,4 +140,54 @@ private string BuildScriptScopedRoute(AppShellScreen screen)
108140
_ => AppRoutes.Library
109141
};
110142
}
143+
144+
private static bool IsGoLiveRoute(string route)
145+
{
146+
var querySeparatorIndex = route.IndexOf(QuerySeparator, StringComparison.Ordinal);
147+
var routeBase = querySeparatorIndex >= 0
148+
? route[..querySeparatorIndex]
149+
: route;
150+
151+
return string.Equals(routeBase, AppRoutes.GoLive, StringComparison.Ordinal);
152+
}
153+
154+
private static bool IsTrackedRoute(string path) => path switch
155+
{
156+
AppRoutes.Library => true,
157+
AppRoutes.Editor => true,
158+
AppRoutes.Learn => true,
159+
AppRoutes.Teleprompter => true,
160+
AppRoutes.GoLive => true,
161+
AppRoutes.Settings => true,
162+
_ => false
163+
};
164+
165+
private static bool IsValidGoLiveBackTarget(string route) =>
166+
!string.IsNullOrWhiteSpace(route) && !IsGoLiveRoute(route);
167+
168+
private static string NormalizeAppRoute(string uri)
169+
{
170+
if (!Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri))
171+
{
172+
return EmptyRoute;
173+
}
174+
175+
var normalizedPath = NormalizePath(parsedUri.AbsolutePath);
176+
if (!IsTrackedRoute(normalizedPath))
177+
{
178+
return EmptyRoute;
179+
}
180+
181+
return string.IsNullOrWhiteSpace(parsedUri.Query)
182+
? normalizedPath
183+
: string.Concat(normalizedPath, parsedUri.Query);
184+
}
185+
186+
private static string NormalizePath(string path)
187+
{
188+
var trimmedPath = path.TrimEnd('/');
189+
return string.IsNullOrWhiteSpace(trimmedPath)
190+
? AppRoutes.Library
191+
: trimmedPath;
192+
}
111193
}

src/PrompterOne.Shared/Contracts/UiTestIds.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ public static class Settings
331331

332332
public static string SceneMirror(string sourceId) => $"settings-scene-mirror-{sourceId}";
333333

334+
public static string SelectOption(string triggerTestId, string optionValue) =>
335+
$"{triggerTestId}-option-{optionValue}";
336+
334337
public static string StreamingProviderCard(string providerId) => $"settings-streaming-provider-{providerId}";
335338

336339
public static string StreamingProviderSourcePicker(string providerId) => $"settings-streaming-provider-sources-{providerId}";
@@ -377,6 +380,7 @@ public static class GoLive
377380
public const string ActiveSourceLabel = "go-live-active-source-label";
378381
public const string AddSource = "go-live-add-source";
379382
public const string AudioMixer = "go-live-audio-mixer";
383+
public const string Back = "go-live-back";
380384
public const string Bitrate = "go-live-bitrate";
381385
public const string CreateRoom = "go-live-create-room";
382386
public const string CustomRtmpKey = "go-live-custom-rtmp-key";
@@ -393,7 +397,7 @@ public static class GoLive
393397
public const string ModeStudio = "go-live-mode-studio";
394398
public const string NdiToggle = "go-live-ndi-toggle";
395399
public const string ObsToggle = "go-live-obs-toggle";
396-
public const string OpenHome = "go-live-open-home";
400+
public const string OpenHome = Back;
397401
public const string OpenLearn = "go-live-open-learn";
398402
public const string OpenRead = "go-live-open-read";
399403
public const string OpenSettings = "go-live-open-settings";
@@ -418,6 +422,7 @@ public static class GoLive
418422
public const string StreamTab = "go-live-stream-tab";
419423
public const string SceneBar = "go-live-scene-bar";
420424
public const string SceneControls = "go-live-scene-controls";
425+
public const string ScreenTitle = "go-live-screen-title";
421426
public const string SessionBar = "go-live-session-bar";
422427
public const string SessionTimer = "go-live-session-timer";
423428
public const string SourceRail = "go-live-source-rail";

src/PrompterOne.Shared/GoLive/Components/GoLiveCameraPreviewCard.razor.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@
6565
width: 7px;
6666
height: 7px;
6767
border-radius: 50%;
68-
background: rgba(239, 68, 68, .2);
69-
transition: background .3s;
68+
background: rgba(255, 245, 224, .18);
69+
border: 1px solid rgba(255, 245, 224, .16);
70+
transition: background .3s, border-color .3s, box-shadow .3s;
7071
}
7172

7273
.gl-air-dot.gl-air-dot-live {
7374
background: #ef4444;
75+
border-color: rgba(239, 68, 68, .65);
7476
box-shadow: 0 0 6px rgba(239, 68, 68, .7);
7577
}
7678

src/PrompterOne.Shared/GoLive/Components/GoLiveProgramFeedCard.razor.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
gap: 4px;
55
min-width: 0;
66
min-height: 0;
7-
flex: 1;
7+
flex: 0 0 auto;
88
}
99

1010
.gl-monitor-label-top {
@@ -35,7 +35,8 @@
3535
}
3636

3737
.gl-monitor-program {
38-
flex: 1;
38+
flex: 0 0 auto;
39+
aspect-ratio: 16 / 9;
3940
min-height: 0;
4041
border-color: var(--gold-14);
4142
box-shadow: 0 0 40px rgba(0, 0, 0, .4), inset 0 0 0 1px var(--gold-06);
@@ -44,6 +45,7 @@
4445
::deep .gl-monitor-feed {
4546
flex: 1;
4647
position: relative;
48+
aspect-ratio: 16 / 9;
4749
min-height: 0;
4850
overflow: hidden;
4951
background: #050508;

src/PrompterOne.Shared/GoLive/Models/GoLiveText.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public static class Chrome
77
public const string BackLabel = "Back";
88
public const string DirectorModeLabel = "Director";
99
public const string LivePreviewTitle = "Live";
10+
public const string ScreenTitle = "Go Live";
1011
public const string StreamingSubtitle = "Program routing";
1112
public const string StudioModeLabel = "Studio";
1213
}
@@ -84,7 +85,6 @@ public static class Surface
8485
public const string HostParticipantName = "Host";
8586
public const string InterviewSceneFallback = "Interview";
8687
public const string LocalRoomPrefix = "local-";
87-
public const string MainSceneFallback = "Camera 1";
8888
public const string MicrophoneMetricLabel = "Mic";
8989
public const string NoScriptProgressLabel = "No script loaded";
9090
public const string PictureInPictureSceneId = "scene-picture-in-picture";

src/PrompterOne.Shared/GoLive/Pages/GoLivePage.Bootstrap.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,13 @@ private async Task EnsureSessionLoadedAsync()
146146

147147
private void UpdateScreenMetadata()
148148
{
149-
_screenTitle = SessionService.State.Title;
150-
_screenSubtitle = SessionService.State.PreviewSegments.Count > 0
149+
_sessionTitle = SessionService.State.Title;
150+
_sessionSubtitle = SessionService.State.PreviewSegments.Count > 0
151151
? SessionService.State.PreviewSegments[0].Title
152152
: GoLiveText.Chrome.StreamingSubtitle;
153153
SyncGoLiveSessionState();
154154
EnsureStudioSurfaceState();
155-
Shell.ShowGoLive(_screenTitle, _screenSubtitle, SessionService.State.ScriptId);
155+
Shell.ShowGoLive(SessionService.State.ScriptId);
156156
}
157157

158158
private async Task PersistSceneAsync()

src/PrompterOne.Shared/GoLive/Pages/GoLivePage.Runtime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ private GoLiveOutputRuntimeRequest BuildRuntimeRequest(SceneCameraSource? camera
1111
MediaSceneService.State,
1212
_studioSettings.Streaming,
1313
_recordingPreferences,
14-
_screenTitle);
14+
_sessionTitle);
1515
}

0 commit comments

Comments
 (0)