Skip to content

Commit cef8cf9

Browse files
committed
Add reader speed dial and stabilize Learn surfaces (#24, #32, #34)
1 parent 4b3dccb commit cef8cf9

29 files changed

Lines changed: 457 additions & 115 deletions

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Rule format:
8282
- Public-facing screenshots and any screenshot-generating or screenshot-asserting tests must use English-visible content so README, docs, and release assets stay globally readable and consistent.
8383
- Public-facing screenshots that include camera or preview feeds must not ship mirrored or reversed readable text; choose or configure the capture so visible text reads correctly in the final asset.
8484
- Teleprompter reader text alignment must expose explicit left, center, and right modes, default to left alignment, and keep the left-aligned mode optically centered by offsetting the text mass away from a visibly left-heavy block.
85+
- Learn RSVP focus layout is a hard stability contract: the ORP focus letter, focus row, and next-phrase region must keep fixed screen positions and fixed reserved heights while words and phrase text change; next-phrase wrapping must clamp inside its reserved area instead of moving the focus word vertically.
8586
- Teleprompter `Read Width` must map honestly to the visible reading lane: at `100%` it must not keep extra internal container padding or shrink-to-content gutters that make the text block visibly narrower than the width guides.
8687
- The TPS editor migration to Monaco must be complete: syntax coloring, IntelliSense/autocomplete, hover or inline tooltip help, decorations, and TPS authoring feedback must be Monaco-native instead of split across legacy overlay or hidden-textarea behavior.
8788
- TPS authoring completeness must be checked against the upstream `managedcode/TPS` README, not only the currently shipped editor menus, so new editor support stays aligned with the full spec for emotions, delivery, pauses, speed, pronunciation, and related cues.
@@ -128,6 +129,7 @@ Rule format:
128129
- Editor top-toolbar search must remain visible in narrow layouts; when width runs out, the other toolbar groups should become explicitly horizontally scrollable/reachable before search is allowed to disappear.
129130
- Do not push branches or `main` without an explicit user command for that push; local commits are allowed, but network publish actions require clear approval in the current conversation.
130131
- Issue-driven implementation work must create or reference the GitHub issue first, mention the issue number in related commits, and leave issue closure to the user after their verification.
132+
- For feedback issue triage, treat issues assigned to `byskyphy` as implemented and waiting for user verification, and treat unassigned issues as not ready unless issue comments explicitly say otherwise; after the release containing an implemented issue ships, add a comment to that issue with the app version where it was added.
131133
- Feature or improvement issues must include UI acceptance coverage expectations, and implementation commits for those issues must add or update browser/UI tests unless the issue explicitly documents why UI coverage is not applicable.
132134
- Stakeholder feedback issues should be interpreted as additive feature or presentation requests by default; do not remove existing behavior from wording such as "replace", "remove", or "rename" unless the user explicitly confirms deletion.
133135
- When a CI test job fails, times out, or is cancelled, the workflow summary must still state which tests failed or that the run ended before per-test failure data was available; do not leave browser-suite failures represented only by generic job annotations.

src/PrompterOne.Shared/Contracts/UiDomIds.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public static class Learn
6969
public const string ContextLeft = "rsvp-ctx-l";
7070
public const string ContextRight = "rsvp-ctx-r";
7171
public const string NextPhrase = "rsvp-next-phrase";
72+
public const string NotesBody = "rsvp-notes-body";
7273
public const string PauseFill = "rsvp-pause-fill";
7374
public const string ProgressLabel = "rsvp-progress-label";
7475
public const string ProgressFill = "rsvp-progress-fill";
@@ -91,6 +92,8 @@ public static class Teleprompter
9192
public const string FocalGuide = "rd-guide-h";
9293
public const string HeaderSegment = "rd-header-segment";
9394
public const string ProgressFill = "rd-progress-fill";
95+
public const string SpeedDial = "rd-speed-dial";
96+
public const string SpeedDialValue = "rd-speed-dial-val";
9497
public const string SpeedValue = "rd-speed-val";
9598
public const string Stage = "rd-stage";
9699
public const string Time = "rd-time";

src/PrompterOne.Shared/Contracts/UiTestIds.Learn.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static class Learn
1818
public const string NotesSave = "learn-notes-save";
1919
public const string NotesSection = "learn-notes-section";
2020
public const string NotesTextarea = "learn-notes-textarea";
21+
public const string NotesToggle = "learn-notes-toggle";
2122
public const string OrpLine = "learn-orp-line";
2223
public const string Page = "learn-page";
2324
public const string PauseIcon = "learn-pause-icon";

src/PrompterOne.Shared/Contracts/UiTestIds.Teleprompter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public static class Teleprompter
5454
public const string SpeedCueDisplayMultiplier = "teleprompter-speed-cue-display-multiplier";
5555
public const string SpeedCueDisplayTooltipKey = "speed-cue-display";
5656
public const string SpeedCueDisplayWpm = "teleprompter-speed-cue-display-wpm";
57+
public const string SpeedDial = "teleprompter-speed-dial";
58+
public const string SpeedDialTooltipKey = "speed-dial";
59+
public const string SpeedDialValue = "teleprompter-speed-dial-value";
5760
public const string SpeedUp = "teleprompter-speed-up";
5861
public const string SpeedValue = "teleprompter-speed-value";
5962
public const string Sliders = "teleprompter-sliders";

src/PrompterOne.Shared/Learn/Pages/LearnPage.PrepNotes.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.AspNetCore.Components;
2+
using PrompterOne.Shared.Components;
23
using PrompterOne.Shared.Learn.Services;
34
using PrompterOne.Shared.Localization;
45

@@ -26,6 +27,16 @@ private sealed record LearnPrepNoteContext(
2627

2728
private bool CanAddPrepNote => !string.IsNullOrWhiteSpace(_prepNoteDraft);
2829

30+
private string PrepNotesPanelCssClass =>
31+
_arePrepNotesExpanded
32+
? "rsvp-notes"
33+
: "rsvp-notes is-collapsed";
34+
35+
private UiIconKind PrepNotesToggleIcon =>
36+
_arePrepNotesExpanded
37+
? UiIconKind.ChevronDown
38+
: UiIconKind.ChevronRight;
39+
2940
private async Task LoadPrepNotesAsync()
3041
{
3142
var scriptKey = ResolvePrepNotesScriptKey();
@@ -37,6 +48,9 @@ private async Task LoadPrepNotesAsync()
3748
private void HandlePrepNoteDraftChanged(ChangeEventArgs args) =>
3849
_prepNoteDraft = args.Value?.ToString() ?? string.Empty;
3950

51+
private void TogglePrepNotesExpanded() =>
52+
_arePrepNotesExpanded = !_arePrepNotesExpanded;
53+
4054
private async Task AddPrepNoteAsync()
4155
{
4256
if (!CanAddPrepNote)

src/PrompterOne.Shared/Learn/Pages/LearnPage.razor

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
@ref="_displayRoot"
2828
data-rsvp-layout-ready="@BuildLayoutReadyAttributeValue()"
2929
data-test="@UiTestIds.Learn.Display">
30-
<div style="@BuildPendingLayoutHiddenStyle()">
30+
<div class="rsvp-focus-stage" style="@BuildPendingLayoutHiddenStyle()">
3131
<div class="rsvp-crosshair">
3232
<UiCrosshairGuide TestId="@UiTestIds.Learn.Crosshair" />
3333
</div>
@@ -80,43 +80,57 @@
8080
<div id="@UiDomIds.Learn.PauseFill" class="rsvp-pause-fill"></div>
8181
</div>
8282

83-
<aside class="rsvp-notes"
83+
<aside class="@PrepNotesPanelCssClass"
8484
data-test="@UiTestIds.Learn.NotesPanel"
85+
data-expanded="@_arePrepNotesExpanded.ToString().ToLowerInvariant()"
8586
@onkeydown:stopPropagation="true">
8687
<div class="rsvp-notes-head">
87-
<span class="rsvp-notes-title">@Text(UiTextKey.LearnNotesTitle)</span>
88+
<button class="rsvp-notes-toggle"
89+
type="button"
90+
aria-label="@Text(UiTextKey.LearnNotesTitle)"
91+
aria-controls="@UiDomIds.Learn.NotesBody"
92+
aria-expanded="@_arePrepNotesExpanded.ToString().ToLowerInvariant()"
93+
@onclick="TogglePrepNotesExpanded"
94+
data-test="@UiTestIds.Learn.NotesToggle">
95+
<span class="rsvp-notes-title">@Text(UiTextKey.LearnNotesTitle)</span>
96+
<UiIcon Kind="@PrepNotesToggleIcon" Size="16" StrokeWidth="2.4m" />
97+
</button>
8898
<span class="rsvp-notes-section" data-test="@UiTestIds.Learn.NotesSection">@CurrentPrepNotesSectionLabel</span>
8999
</div>
90-
<textarea class="rsvp-notes-input"
91-
rows="3"
92-
value="@_prepNoteDraft"
93-
placeholder="@Text(UiTextKey.LearnNotesPlaceholder)"
94-
aria-label="@Text(UiTextKey.LearnNotesTitle)"
95-
@oninput="HandlePrepNoteDraftChanged"
96-
data-test="@UiTestIds.Learn.NotesTextarea"></textarea>
97-
<button class="rsvp-notes-save"
98-
type="button"
99-
disabled="@(!CanAddPrepNote)"
100-
@onclick="AddPrepNoteAsync"
101-
data-test="@UiTestIds.Learn.NotesSave">@Text(UiTextKey.LearnNotesSave)</button>
102-
<div class="rsvp-notes-list" data-test="@UiTestIds.Learn.NotesList">
103-
@{
104-
var currentNotes = CurrentSectionPrepNotes;
105-
}
106-
@if (currentNotes.Count == 0)
107-
{
108-
<p class="rsvp-notes-empty" data-test="@UiTestIds.Learn.NotesEmpty">@Text(UiTextKey.LearnNotesEmpty)</p>
109-
}
110-
else
111-
{
112-
@foreach (var note in currentNotes)
100+
<div class="rsvp-notes-body"
101+
id="@UiDomIds.Learn.NotesBody"
102+
hidden="@(!_arePrepNotesExpanded)">
103+
<textarea class="rsvp-notes-input"
104+
rows="3"
105+
value="@_prepNoteDraft"
106+
placeholder="@Text(UiTextKey.LearnNotesPlaceholder)"
107+
aria-label="@Text(UiTextKey.LearnNotesTitle)"
108+
@oninput="HandlePrepNoteDraftChanged"
109+
data-test="@UiTestIds.Learn.NotesTextarea"></textarea>
110+
<button class="rsvp-notes-save"
111+
type="button"
112+
disabled="@(!CanAddPrepNote)"
113+
@onclick="AddPrepNoteAsync"
114+
data-test="@UiTestIds.Learn.NotesSave">@Text(UiTextKey.LearnNotesSave)</button>
115+
<div class="rsvp-notes-list" data-test="@UiTestIds.Learn.NotesList">
116+
@{
117+
var currentNotes = CurrentSectionPrepNotes;
118+
}
119+
@if (currentNotes.Count == 0)
113120
{
114-
<article class="rsvp-note" data-test="@UiTestIds.Learn.NoteItem(note.Id)">
115-
<span class="rsvp-note-anchor">@note.AnchorWord</span>
116-
<p>@note.Text</p>
117-
</article>
121+
<p class="rsvp-notes-empty" data-test="@UiTestIds.Learn.NotesEmpty">@Text(UiTextKey.LearnNotesEmpty)</p>
118122
}
119-
}
123+
else
124+
{
125+
@foreach (var note in currentNotes)
126+
{
127+
<article class="rsvp-note" data-test="@UiTestIds.Learn.NoteItem(note.Id)">
128+
<span class="rsvp-note-anchor">@note.AnchorWord</span>
129+
<p>@note.Text</p>
130+
</article>
131+
}
132+
}
133+
</div>
120134
</div>
121135
</aside>
122136
</div>

src/PrompterOne.Shared/Learn/Pages/LearnPage.razor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public partial class LearnPage : IAsyncDisposable
5757
private bool _isPlaying;
5858
private bool _isLoopEnabled;
5959
private bool _loadState = true;
60+
private bool _arePrepNotesExpanded = true;
6061
private bool _focusScreenAfterRender = true;
6162
private bool _syncFocusLayoutAfterRender;
6263
private bool _startPlaybackAfterLayoutSync;

src/PrompterOne.Shared/Localization/SharedResource.de.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@
561561
<data name="TooltipReaderSpeedCueDisplayMultiplier" xml:space="preserve">
562562
<value>Geschwindigkeitsmarken als Multiplikatoren anzeigen</value>
563563
</data>
564+
<data name="TooltipReaderSpeedDial" xml:space="preserve">
565+
<value>Live-Geschwindigkeitsregler für den Reader anpassen</value>
566+
</data>
564567
<data name="TooltipSmallerText" xml:space="preserve">
565568
<value>Kleinerer Text</value>
566569
</data>

src/PrompterOne.Shared/Localization/SharedResource.es.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@
561561
<data name="TooltipReaderSpeedCueDisplayMultiplier" xml:space="preserve">
562562
<value>Mostrar las etiquetas de velocidad como multiplicadores</value>
563563
</data>
564+
<data name="TooltipReaderSpeedDial" xml:space="preserve">
565+
<value>Ajustar el dial de velocidad en vivo del lector</value>
566+
</data>
564567
<data name="TooltipSmallerText" xml:space="preserve">
565568
<value>Texto más pequeño</value>
566569
</data>

src/PrompterOne.Shared/Localization/SharedResource.fr.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@
561561
<data name="TooltipReaderSpeedCueDisplayMultiplier" xml:space="preserve">
562562
<value>Afficher les repères de vitesse en multiplicateurs</value>
563563
</data>
564+
<data name="TooltipReaderSpeedDial" xml:space="preserve">
565+
<value>Régler le cadran de vitesse en direct du lecteur</value>
566+
</data>
564567
<data name="TooltipSmallerText" xml:space="preserve">
565568
<value>Texte plus petit</value>
566569
</data>

0 commit comments

Comments
 (0)