Skip to content

Commit a03bfa4

Browse files
committed
fixe
1 parent 65e862a commit a03bfa4

Some content is hidden

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

43 files changed

+930
-399
lines changed

AGENTS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,14 @@ Repo-specific design rules:
304304
- Repo-owned manifests, scripts, workflows, and project files that track third-party runtime JavaScript SDKs MUST point to concrete GitHub release versions and asset URLs, never floating references.
305305
- Any vendored runtime JavaScript SDK that tracks an upstream GitHub repo MUST have an automated watcher job that checks new GitHub releases and opens a repo issue describing the required update when a newer release appears.
306306
- Teleprompter TPS speed modifiers MUST affect both playback timing and subtle word- or phrase-level letter spacing, so slower spans open up slightly and faster spans tighten slightly without hurting readability.
307+
- Teleprompter default reader width MUST start at the maximum readable width from the design unless the user explicitly narrows it; shipping a visibly narrower default is a regression.
308+
- Teleprompter speed styling MUST produce a visible but tasteful letter-spacing or kerning change: slower text opens up slightly and faster text tightens slightly, not a no-op.
307309
- Teleprompter reader word styling MUST mirror TPS/editor inline semantics: explicit inline TPS tags control per-word emphasis and color, while section or block emotion sets card context and must not recolor every reader word.
310+
- Teleprompter underline or highlight treatments that span a phrase or block MUST render as one continuous block-level treatment; separate per-word underlines inside the same phrase are forbidden.
311+
- Teleprompter reader text MUST appear on the focal guide immediately when a word or block becomes active; visible post-appearance drift or settling onto the guide is forbidden.
308312
- Teleprompter block transitions MUST stay visually consistent: outgoing cards move upward and incoming cards rise from below in the same direction every time; alternating up/down travel is forbidden, and extra settling, bounce, or intermediate card states are forbidden.
313+
- Reader and Learn tokenization MUST treat punctuation-only tokens such as commas, periods, and dashes as punctuation attached to nearby words or pauses, never as standalone counted words.
314+
- 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.
309315
- 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.
310316
- 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.
311317
- Build quality gates must stay green under `-warnaserror`.
@@ -368,8 +374,14 @@ Ask first:
368374
- made-up About/team content or stale attribution; About must point to real Managed Code ownership and official links
369375
- any visible typing latency in the editor; plain input must feel immediate with no observable delay
370376
- teleprompter controls that fade so much they become hard to see during real reading
377+
- teleprompter starting with a narrowed text width instead of the design-max default
371378
- teleprompter paragraph repositioning, line hopping, or per-word vertical transform updates that make the text jump; `design/teleprompter.html` motion is the required reference, with steady bottom-to-top movement and no extra animation layers beyond the reference
379+
- teleprompter words or blocks appearing away from the focus line and only then drifting onto it; activation must look immediate
380+
- teleprompter section changes that introduce odd transition motion instead of the straight reference direction
372381
- any green teleprompter shell or background treatment; Teleprompter must stay on its dark reader palette and use emotion only for accents, not green screen-wide fills
382+
- fragmented per-word underline styling where the intended emphasis should read as one continuous block
383+
- punctuation showing up or being counted as standalone words in Learn or Teleprompter flows
384+
- app logo clicks landing on a feature route instead of the main home/library screen
373385
- Learn and Teleprompter style boundaries bleeding through a shared feature stylesheet; their visuals must stay isolated by page-owned style manifests
374386
- Learn RSVP compositions that shift when shorter or longer words render; changing word length must not move the overall RSVP component or its anchored centerline
375387
- teleprompter camera starting enabled by default; default reader startup should keep the camera off until the user explicitly enables it

src/PrompterOne.Core/Tps/Services/ScriptCompiler.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,12 @@ private static void AddWord(List<CompiledWord> words, string token, FormattingSt
669669
return;
670670
}
671671

672+
if (TpsTokenTextRules.IsStandalonePunctuationToken(clean))
673+
{
674+
AttachStandalonePunctuation(words, clean);
675+
return;
676+
}
677+
672678
var metadata = new WordMetadata
673679
{
674680
IsEmphasis = state.IsEmphasis,
@@ -708,6 +714,20 @@ private static void AddWord(List<CompiledWord> words, string token, FormattingSt
708714
});
709715
}
710716

717+
private static void AttachStandalonePunctuation(List<CompiledWord> words, string punctuationToken)
718+
{
719+
var previousWord = words.LastOrDefault(word =>
720+
word.Metadata?.IsPause != true &&
721+
!string.IsNullOrWhiteSpace(word.CleanText));
722+
if (previousWord is null)
723+
{
724+
return;
725+
}
726+
727+
previousWord.CleanText += TpsTokenTextRules.BuildStandalonePunctuationSuffix(punctuationToken);
728+
previousWord.CharacterCount = previousWord.CleanText.Length;
729+
}
730+
711731
private static string NormalizeContent(string text)
712732
{
713733
if (string.IsNullOrEmpty(text))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace PrompterOne.Core.Services;
2+
3+
internal static class TpsTokenTextRules
4+
{
5+
private const string StandaloneDashCharacters = "-—–";
6+
private const string StandalonePunctuationCharacters = ",.;:!?-—–…";
7+
8+
public static bool IsStandalonePunctuationToken(string? token)
9+
{
10+
if (string.IsNullOrWhiteSpace(token))
11+
{
12+
return false;
13+
}
14+
15+
foreach (var character in token.Trim())
16+
{
17+
if (!StandalonePunctuationCharacters.Contains(character))
18+
{
19+
return false;
20+
}
21+
}
22+
23+
return true;
24+
}
25+
26+
public static string BuildStandalonePunctuationSuffix(string token)
27+
{
28+
var trimmed = token.Trim();
29+
return UsesLeadingSeparator(trimmed)
30+
? string.Concat(" ", trimmed)
31+
: trimmed;
32+
}
33+
34+
private static bool UsesLeadingSeparator(string token)
35+
{
36+
foreach (var character in token)
37+
{
38+
if (!StandaloneDashCharacters.Contains(character))
39+
{
40+
return false;
41+
}
42+
}
43+
44+
return token.Length > 0;
45+
}
46+
}

src/PrompterOne.Core/Workspace/Models/ReaderSettingsDefaults.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public static class ReaderSettingsDefaults
44
{
55
public const int CountdownSeconds = 3;
66
public const double FontScale = 1.0;
7-
public const double TextWidth = 0.72;
7+
public const double TextWidth = 1.0;
88
public const double ScrollSpeed = 1.0;
99
public const bool MirrorText = false;
1010
public const bool ShowFocusLine = true;

src/PrompterOne.Shared/Contracts/UiTestIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ public static class GoLive
358358
public const string ModeStudio = "go-live-mode-studio";
359359
public const string NdiToggle = "go-live-ndi-toggle";
360360
public const string ObsToggle = "go-live-obs-toggle";
361+
public const string OpenHome = "go-live-open-home";
361362
public const string OpenLearn = "go-live-open-learn";
362363
public const string OpenRead = "go-live-open-read";
363364
public const string OpenSettings = "go-live-open-settings";

src/PrompterOne.Shared/GoLive/Components/GoLiveSourcesCard.razor

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
@namespace PrompterOne.Shared.Components.GoLive
2-
@using PrompterOne.Core.Models.Workspace
32

43
<section class="gl-sources-panel">
54
<div class="gl-sources-header">
@@ -52,37 +51,6 @@
5251
<span class="gl-cam-name">@source.Label</span>
5352
<span class="gl-cam-hw">@GetSourceStatus(source)</span>
5453
</div>
55-
56-
<div class="gl-cam-row2">
57-
<div class="gl-cam-reader">
58-
<div class="@GetSourceAvatarCssClass(source)">@GetSourceAvatarInitial(source)</div>
59-
<span class="gl-cam-reader-name">@GetSourceReaderLabel(source)</span>
60-
</div>
61-
62-
<div class="gl-crop-btns">
63-
@foreach (var crop in CropOptions)
64-
{
65-
var cropIsActive = GetCropSelection(source.SourceId) == crop;
66-
<button type="button"
67-
class="gl-crop-btn @(cropIsActive ? "active" : null)"
68-
@onclick="() => SetCropSelection(source.SourceId, crop)">@GetCropLabel(crop)</button>
69-
}
70-
</div>
71-
</div>
72-
73-
<div class="gl-cam-script-tag @GetSourceScriptTagCssClass(source)">
74-
<svg width="10"
75-
height="10"
76-
viewBox="0 0 24 24"
77-
fill="none"
78-
stroke="currentColor"
79-
stroke-width="2"
80-
aria-hidden="true">
81-
<path d="M4 6h16M4 12h12M4 18h14" />
82-
</svg>
83-
<span>@CurrentScriptTitle</span>
84-
<span class="gl-script-progress">@CurrentScriptProgressLabel</span>
85-
</div>
8654
</div>
8755
</button>
8856

@@ -128,25 +96,14 @@
12896
private const string PrompterUtilitySourceId = "prompter-display";
12997
private const string RemoveLabel = "Remove";
13098
private const string ScreenShareUtilitySourceId = "screen-share";
131-
private const string ScriptProgressDefaultLabel = "No script loaded";
13299
private const string SourceIncludedLabel = "Armed for output";
133100
private const string SourceLiveLabel = "Ready for canvas";
134101
private const string SourceOnAirLabel = "On air";
135102
private const string SourceSceneOnlyLabel = "Scene only";
136-
private static readonly IReadOnlyList<GoLiveCropPreset> CropOptions =
137-
[
138-
GoLiveCropPreset.Full,
139-
GoLiveCropPreset.HeadAndShoulders,
140-
GoLiveCropPreset.Face
141-
];
142-
143-
private readonly Dictionary<string, GoLiveCropPreset> _cropSelections = new(StringComparer.Ordinal);
144103

145104
[Parameter] public EventCallback AddCamera { get; set; }
146105
[Parameter] public string? ActiveSourceId { get; set; }
147106
[Parameter] public bool CanAddCamera { get; set; }
148-
[Parameter] public string CurrentScriptProgressLabel { get; set; } = ScriptProgressDefaultLabel;
149-
[Parameter] public string CurrentScriptTitle { get; set; } = ScriptWorkspaceState.UntitledScriptTitle;
150107
[Parameter] public bool HasPrimaryMicrophone { get; set; }
151108
[Parameter] public string MicrophoneName { get; set; } = string.Empty;
152109
[Parameter] public string MicrophoneRouteLabel { get; set; } = string.Empty;
@@ -175,49 +132,6 @@
175132
: SourceSceneOnlyLabel;
176133
}
177134

178-
private static string GetCropLabel(GoLiveCropPreset crop) =>
179-
crop switch
180-
{
181-
GoLiveCropPreset.HeadAndShoulders => "H&S",
182-
GoLiveCropPreset.Face => "Face",
183-
_ => "Full"
184-
};
185-
186-
private static string GetSourceAvatarInitial(SceneCameraSource source)
187-
{
188-
return string.IsNullOrWhiteSpace(source.Label)
189-
? "C"
190-
: source.Label[..1].ToUpperInvariant();
191-
}
192-
193-
private string GetSourceAvatarCssClass(SceneCameraSource source)
194-
{
195-
var classes = new List<string> { "gl-cam-avatar" };
196-
classes.Add(IsActive(source)
197-
? "gl-cam-avatar-live"
198-
: IsSelected(source)
199-
? "gl-cam-avatar-preview"
200-
: "gl-cam-avatar-idle");
201-
return string.Join(' ', classes);
202-
}
203-
204-
private string GetSourceReaderLabel(SceneCameraSource source)
205-
{
206-
if (IsActive(source))
207-
{
208-
return SourceOnAirLabel;
209-
}
210-
211-
if (IsSelected(source))
212-
{
213-
return SourceLiveLabel;
214-
}
215-
216-
return source.Transform.IncludeInOutput
217-
? SourceIncludedLabel
218-
: SourceSceneOnlyLabel;
219-
}
220-
221135
private string GetSourceBadge(SceneCameraSource source)
222136
{
223137
if (IsActive(source))
@@ -232,23 +146,6 @@
232146
: SourceSceneOnlyLabel;
233147
}
234148

235-
private string GetSourceScriptTagCssClass(SceneCameraSource source)
236-
{
237-
return IsActive(source) || IsSelected(source)
238-
? "gl-script-active"
239-
: string.Empty;
240-
}
241-
242-
private GoLiveCropPreset GetCropSelection(string sourceId)
243-
{
244-
return _cropSelections.TryGetValue(sourceId, out var crop) ? crop : GoLiveCropPreset.Full;
245-
}
246-
247-
private void SetCropSelection(string sourceId, GoLiveCropPreset crop)
248-
{
249-
_cropSelections[sourceId] = crop;
250-
}
251-
252149
private string GetSourceCardCssClass(SceneCameraSource source)
253150
{
254151
var classes = new List<string> { "gl-cam-card" };

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
display: flex;
4343
flex-direction: column;
4444
gap: 8px;
45+
flex: 1 1 0;
4546
min-height: 0;
4647
overflow-y: auto;
4748
}

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

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ public partial class GoLivePage
4040
private const string RecordingReadyDetailLabel = "Ready";
4141
private const string RecordingReadyMetricValue = "Armed";
4242
private const string RelayPlatformLabel = "Relay preset";
43-
private const string RemoteTalentSourceId = "prompter-display";
44-
private const string RemoteTalentTitle = "Prompter Display";
4543
private const string RoomCodeFallback = "local-studio";
4644
private const string RuntimeEngineIdleValue = "Idle";
4745
private const string RuntimeEngineLabel = "Runtime";
@@ -51,32 +49,18 @@ public partial class GoLivePage
5149
private const string RuntimeEngineRecorderValue = "Recorder";
5250
private const string SceneSlidesId = "scene-slides";
5351
private const string SceneSlidesLabel = "Slides";
54-
private const string ScreenShareSourceId = "screen-share";
55-
private const string ScreenShareTitle = "Share Screen";
5652
private const string SecondarySceneId = "scene-secondary";
5753
private const string SettingsPlatformLabel = "Settings preset";
5854
private const string StatusBitrateLabel = "Bitrate";
5955
private const string StatusOutputLabel = "Output";
56+
private const string StudioSourcesTitle = "Sources";
6057
private const string SessionMetricLabel = "Session";
61-
private const string SlidesSourceId = "slides";
62-
private const string SlidesSourceTitle = "Slides";
63-
private const string UtilitySourceClickLabel = "Click to share";
64-
private const string UtilitySourcePrompterBadge = "Prompter";
65-
private const string UtilitySourceShareBadge = "Add";
66-
private const string UtilitySourceSlidesBadge = "Slides";
67-
private const string UtilitySourceSlidesLabel = "Keynote";
68-
private const string UtilitySourceTalentFacingLabel = "Talent-facing only";
58+
private const string DirectorSourcesTitle = "Cameras";
6959
private const string GoLiveContentBaseClass = "gl-content";
7060
private const string GoLiveFullProgramClass = "gl-layout-fullpgm";
7161
private const string GoLiveHideLeftClass = "gl-hide-left";
7262
private const string GoLiveHideRightClass = "gl-hide-right";
7363

74-
private static readonly IReadOnlyList<GoLiveUtilitySourceViewModel> StudioUtilitySources =
75-
[
76-
new(RemoteTalentSourceId, RemoteTalentTitle, UtilitySourceTalentFacingLabel, UtilitySourcePrompterBadge),
77-
new(SlidesSourceId, SlidesSourceTitle, UtilitySourceSlidesLabel, UtilitySourceSlidesBadge),
78-
new(ScreenShareSourceId, ScreenShareTitle, UtilitySourceClickLabel, UtilitySourceShareBadge)
79-
];
8064

8165
private string _activeSceneId = PrimarySceneId;
8266
private GoLiveSceneLayout _activeSceneLayout = GoLiveSceneLayout.Full;
@@ -113,7 +97,17 @@ public partial class GoLivePage
11397

11498
private IReadOnlyList<GoLiveMetricViewModel> StatusMetrics => BuildStatusMetrics();
11599

116-
private static IReadOnlyList<GoLiveUtilitySourceViewModel> UtilitySources => StudioUtilitySources;
100+
private static IReadOnlyList<GoLiveUtilitySourceViewModel> UtilitySources => [];
101+
102+
private IReadOnlyList<SceneCameraSource> VisibleSceneCameras =>
103+
_activeStudioMode == GoLiveStudioMode.Studio && SceneCameras.Count > 0
104+
? [SceneCameras[0]]
105+
: SceneCameras;
106+
107+
private string SourcesHeaderTitle =>
108+
_activeStudioMode == GoLiveStudioMode.Director
109+
? DirectorSourcesTitle
110+
: StudioSourcesTitle;
117111

118112
private string GoLiveContentClass
119113
{

src/PrompterOne.Shared/GoLive/Pages/GoLivePage.razor

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
data-testid="@UiTestIds.GoLive.SessionBar">
1212
<div class="gl-topbar-left">
1313
<a class="gl-back-btn"
14-
href="@ReadRoute"
15-
data-testid="@UiTestIds.GoLive.OpenRead">
14+
href="@HomeRoute"
15+
data-testid="@UiTestIds.GoLive.OpenHome">
1616
<svg width="16"
1717
height="16"
1818
viewBox="0 0 24 24"
@@ -168,11 +168,10 @@
168168
{
169169
<aside class="gl-sources"
170170
data-testid="@UiTestIds.GoLive.SourceRail">
171-
<GoLiveSourcesCard Sources="@SceneCameras"
171+
<GoLiveSourcesCard Sources="@VisibleSceneCameras"
172172
ActiveSourceId="@ActiveCamera?.SourceId"
173173
SelectedSourceId="@SelectedCamera?.SourceId"
174-
CurrentScriptTitle="@_screenTitle"
175-
CurrentScriptProgressLabel="@CurrentScriptProgressLabel"
174+
Title="@SourcesHeaderTitle"
176175
UtilitySources="@UtilitySources"
177176
HasPrimaryMicrophone="@HasPrimaryMicrophone"
178177
MicrophoneName="@PrimaryMicrophoneLabel"

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public partial class GoLivePage : ComponentBase
1919
private const string GoLiveSceneOperation = "Go Live save scene";
2020
private const string GoLiveStudioMessage = "Unable to save live routing settings.";
2121
private const string GoLiveStudioOperation = "Go Live save studio";
22+
private const string HomeRoute = AppRoutes.Library;
2223
private const string NoMicrophoneLabel = "No microphone";
2324
private const string StreamingSubtitle = "Program routing";
2425

@@ -75,10 +76,6 @@ private string PrimaryMicrophoneRoute
7576
}
7677
}
7778

78-
private string ReadRoute => HasScriptContext
79-
? AppRoutes.TeleprompterWithId(SessionService.State.ScriptId)
80-
: AppRoutes.Teleprompter;
81-
8279
private IReadOnlyList<SceneCameraSource> SceneCameras => MediaSceneService.State.Cameras;
8380

8481
protected override Task OnParametersSetAsync()

0 commit comments

Comments
 (0)