Skip to content

Commit c86a0ae

Browse files
committed
test fixes
1 parent 68aea43 commit c86a0ae

48 files changed

Lines changed: 1075 additions & 295 deletions

Some content is hidden

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

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ Rule format:
9696
- The onboarding flow must include a dedicated TPS explainer step or page, separate from the generic editor step, that tells users what TPS is, why it exists, and how PrompterOne uses it.
9797
- The dedicated TPS onboarding step must explain TPS in concrete beginner terms: what the format is, why it exists, how PrompterOne uses it across Editor/Learn/Teleprompter/Go Live, and where users can continue with the official TPS site or glossary.
9898
- Script discovery and authoring surfaces must support real search by script name and script content; Library/script pages and editor flows must not force manual browsing when the user needs to find files or text inside files.
99+
- Editor find/search interactions must keep keyboard focus inside the find UI while the user types; query input should only update highlighted matches, and only explicit `Next`/`Previous` search navigation may move focus/selection into the Monaco editor surface.
100+
- Editor status chrome must stay compact and omit the footer version pill unless a task explicitly asks to surface version metadata there.
101+
- Editor footer status metrics must keep a stable one-row layout while cursor or count values change; line, column, word, and duration updates must not make neighboring status chips jump or reflow.
102+
- Editor footer status chrome should read like a quiet IDE status strip: one compact row, low emphasis, and no oversized chip or outlined-block treatment for static metrics.
103+
- 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.
99104
- Public web hosting is split by role: the standalone PrompterOne app in this repo must publish on `app.prompter.one`, while the marketing landing site for `prompter.one` lives in the separate `PrompterOne-LandingPage` repository.
100105
- For deploy-only, domain, CI, or static-site hosting tasks, do not spend time on unrelated app/browser test suites unless the user explicitly asks or the runtime behavior itself changes; prefer workflow, build, and publish-config validation only.
101106
- Repo-wide .NET SDK and test-runner selection belong in the root `global.json`; do not split `global.json` test-runner opt-ins per project or subfolder once the user asks for a global test-platform policy.
@@ -466,6 +471,7 @@ Ask first:
466471
### Dislikes
467472

468473
- fallback code paths, compatibility shims, or alternate behavior branches added "just in case"; implement the direct fix and only add a fallback when the user explicitly asks for one
474+
- any "safety-net" or "just in case" workaround added to mask incorrect behavior; fix the root cause cleanly instead of layering retries, forced refocus, defensive rerenders, or compensating logic
469475
- backend creep in the standalone runtime
470476
- `git worktree`, temporary worktrees, throwaway repo copies, or off-branch isolation for normal repo tasks when the active workspace branch is available; do the work in the current repo and current branch unless the user explicitly asks for isolation
471477
- OBS-coupled runtime architecture or UI; `PrompterOne` must be the streaming system itself, not an OBS companion or Browser Source wrapper

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public static IServiceCollection AddPrompterOneShared(
5656
services.AddScoped<EditorOutlineBuilder>();
5757
services.AddScoped<EditorInterop>();
5858
services.AddScoped<EditorMonacoInterop>();
59+
services.AddScoped<EditorToolbarInterop>();
5960
services.AddScoped<AppShellFilePickerInterop>();
6061
services.AddScoped<EditorDocumentSaveCoordinator>();
6162
services.AddScoped<EditorLocalRevisionStore>();

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public static class Editor
4343
public const string FindNext = "editor-find-next";
4444
public const string FindPrevious = "editor-find-previous";
4545
public const string FindResult = "editor-find-result";
46-
public const string FindToggle = "editor-find-toggle";
4746
public const string FloatingAi = "editor-floating-ai";
4847
public const string FloatingBar = "editor-floating-bar";
4948
public const string FloatingDeliverySarcasm = "editor-float-delivery-sarcasm";
@@ -116,6 +115,9 @@ public static class Editor
116115
public const string SourceScrollHost = "editor-source-scroll-host";
117116
public const string SourceStage = "editor-source-stage";
118117
public const string Toolbar = "editor-toolbar";
118+
public const string ToolbarScrollNext = "editor-toolbar-scroll-next";
119+
public const string ToolbarScrollPrevious = "editor-toolbar-scroll-previous";
120+
public const string ToolbarTools = "editor-toolbar-tools";
119121
public const string SpeedFast = "editor-speed-fast";
120122
public const string SpeedCustomWpm = "editor-speed-custom-wpm";
121123
public const string SpeedSlow = "editor-speed-slow";
Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
@namespace PrompterOne.Shared.Components.Editor
22

3-
<div class="ed-find-bar" data-test="@UiTestIds.Editor.FindBar">
3+
<div class="@RootCssClass" data-test="@UiTestIds.Editor.FindBar">
44
<label class="ed-find-input-shell" data-test="@UiTestIds.Editor.FindInputShell">
55
<UiIcon Kind="UiIconKind.Search" Size="14" />
66
<input class="ed-find-input"
77
type="text"
88
value="@Query"
99
placeholder="@Placeholder"
1010
aria-label="@Placeholder"
11-
@ref="_inputRef"
1211
@oninput="HandleInputAsync"
1312
data-test="@UiTestIds.Editor.FindInput" />
1413
</label>
@@ -18,27 +17,55 @@
1817
<span class="ed-find-result" data-test="@UiTestIds.Editor.FindResult">@ResultLabel</span>
1918
}
2019

21-
<div class="ed-find-actions">
22-
<button type="button"
23-
class="ed-find-nav"
24-
@onclick="HandlePreviousAsync"
25-
disabled="@(!CanNavigate)"
26-
data-test="@UiTestIds.Editor.FindPrevious">@PreviousLabel</button>
27-
<button type="button"
28-
class="ed-find-nav"
29-
@onclick="HandleNextAsync"
30-
disabled="@(!CanNavigate)"
31-
data-test="@UiTestIds.Editor.FindNext">@NextLabel</button>
32-
<button type="button"
33-
class="ed-find-close"
34-
@onclick="HandleCloseAsync"
35-
data-test="@UiTestIds.Editor.FindClose">@CloseLabel</button>
36-
</div>
20+
@if (ShouldRenderActions)
21+
{
22+
<div class="ed-find-actions">
23+
<button type="button"
24+
class="@GetNavigationButtonCssClass()"
25+
@onclick="HandlePreviousAsync"
26+
aria-label="@PreviousLabel"
27+
disabled="@(!CanNavigate)"
28+
data-test="@UiTestIds.Editor.FindPrevious">
29+
@if (Compact)
30+
{
31+
<UiIcon Kind="UiIconKind.ChevronLeft" Size="14" StrokeWidth="2.5m" />
32+
}
33+
else
34+
{
35+
@PreviousLabel
36+
}
37+
</button>
38+
<button type="button"
39+
class="@GetNavigationButtonCssClass()"
40+
@onclick="HandleNextAsync"
41+
aria-label="@NextLabel"
42+
disabled="@(!CanNavigate)"
43+
data-test="@UiTestIds.Editor.FindNext">
44+
@if (Compact)
45+
{
46+
<UiIcon Kind="UiIconKind.ChevronRight" Size="14" StrokeWidth="2.5m" />
47+
}
48+
else
49+
{
50+
@NextLabel
51+
}
52+
</button>
53+
@if (ShowCloseAction)
54+
{
55+
<button type="button"
56+
class="ed-find-close"
57+
@onclick="HandleCloseAsync"
58+
data-test="@UiTestIds.Editor.FindClose">@CloseLabel</button>
59+
}
60+
</div>
61+
}
3762
</div>
3863

3964
@code {
4065
[Parameter] public bool CanNavigate { get; set; }
66+
[Parameter] public bool Compact { get; set; }
4167
[Parameter] public bool ShowResult { get; set; }
68+
[Parameter] public bool ShowCloseAction { get; set; } = true;
4269
[Parameter, EditorRequired] public string CloseLabel { get; set; } = string.Empty;
4370
[Parameter, EditorRequired] public string NextLabel { get; set; } = string.Empty;
4471
[Parameter] public EventCallback<ChangeEventArgs> OnInput { get; set; }
@@ -49,9 +76,8 @@
4976
[Parameter, EditorRequired] public string PreviousLabel { get; set; } = string.Empty;
5077
[Parameter, EditorRequired] public string Query { get; set; } = string.Empty;
5178
[Parameter, EditorRequired] public string ResultLabel { get; set; } = string.Empty;
52-
private ElementReference _inputRef;
53-
54-
public ValueTask FocusInputAsync() => _inputRef.FocusAsync();
79+
private string RootCssClass => Compact ? "ed-find-bar ed-find-bar--compact" : "ed-find-bar";
80+
private bool ShouldRenderActions => ShowResult || ShowCloseAction;
5581

5682
private Task HandleCloseAsync() => OnClose.InvokeAsync();
5783

@@ -60,4 +86,7 @@
6086
private Task HandleNextAsync() => OnNext.InvokeAsync();
6187

6288
private Task HandlePreviousAsync() => OnPrevious.InvokeAsync();
89+
90+
private string GetNavigationButtonCssClass() =>
91+
Compact ? "ed-find-nav ed-find-nav--compact" : "ed-find-nav";
6392
}

src/PrompterOne.Shared/Editor/Components/EditorFindBar.razor.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
.ed-find-nav,.ed-find-close{font:inherit;cursor:pointer;min-height:36px;padding:0 12px;border:1px solid rgba(223,187,124,.14);border-radius:10px;background:rgba(255,255,255,.03);color:var(--text-2);}
88
.ed-find-nav:hover,.ed-find-nav:focus-visible,.ed-find-close:hover,.ed-find-close:focus-visible{background:rgba(212,176,112,.08);color:var(--text-1);}
99
.ed-find-nav:disabled{cursor:default;opacity:.42;}
10+
.ed-find-bar--compact{gap:8px;padding:0;border-bottom:none;background:transparent;min-width:0;max-width:100%;}
11+
.ed-find-bar--compact .ed-find-input-shell{min-width:0;flex:0 1 clamp(148px, 18vw, 220px);padding:0 10px;min-height:32px;border-radius:10px;}
12+
.ed-find-bar--compact .ed-find-result{min-width:44px;font-size:11px;line-height:1;flex:0 0 auto;}
13+
.ed-find-bar--compact .ed-find-actions{gap:4px;margin-left:0;flex:0 0 auto;}
14+
.ed-find-nav--compact{display:inline-flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;padding:0;border-radius:9px;}
15+
.ed-find-nav--compact svg{flex-shrink:0;}
1016

1117
html[data-theme="light"] .ed-find-bar,
1218
html.theme-light .ed-find-bar,
@@ -34,3 +40,8 @@ html.theme-light .ed-find-input,
3440
body.theme-light .ed-find-input {
3541
color: #2C2418;
3642
}
43+
44+
@media (max-width:932px){
45+
.ed-find-bar--compact{gap:6px;}
46+
.ed-find-bar--compact .ed-find-input-shell{flex-basis:clamp(136px, 24vw, 188px);}
47+
}

src/PrompterOne.Shared/Editor/Components/EditorSourcePanel.Localization.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ public partial class EditorSourcePanel
4747

4848
private string StatusDurationValue => string.Concat(ApproximateValuePrefix, Status.Duration);
4949

50-
private string StatusVersionLabel => L(UiTextKey.CommonVersion);
51-
52-
private string StatusVersionValue => Status.Version;
53-
5450
private IReadOnlyList<EditorToolbarSectionDescriptor> ToolbarSections => _toolbarSections;
5551

5652
private string L(UiTextKey key) => Localizer[key.ToString()];

src/PrompterOne.Shared/Editor/Components/EditorSourcePanel.Search.cs

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Globalization;
22
using Microsoft.AspNetCore.Components;
33
using PrompterOne.Shared.Localization;
4+
using PrompterOne.Shared.Services.Editor;
45

56
namespace PrompterOne.Shared.Components.Editor;
67

@@ -9,7 +10,6 @@ public partial class EditorSourcePanel
910
private string _findQuery = string.Empty;
1011
private int _findMatchIndex = -1;
1112
private IReadOnlyList<EditorFindMatch> _findMatches = [];
12-
private bool _showFindBar;
1313

1414
private bool CanNavigateFindMatches => _findMatches.Count > 0;
1515

@@ -19,36 +19,19 @@ public partial class EditorSourcePanel
1919

2020
private bool ShouldRenderFindResult => !string.IsNullOrWhiteSpace(_findQuery);
2121

22-
private Task ToggleFindBarAsync()
22+
private Task ClearFindQueryAsync()
2323
{
24-
_showFindBar = !_showFindBar;
25-
CloseToolbarPanels();
26-
27-
if (_showFindBar)
28-
{
29-
RefreshFindMatchesForCurrentText();
30-
}
31-
else
32-
{
33-
ClearFindState();
34-
}
35-
36-
return InvokeAsync(StateHasChanged);
37-
}
38-
39-
private Task CloseFindBarAsync()
40-
{
41-
_showFindBar = false;
4224
ClearFindState();
25+
_syncFindHighlightsAfterRender = true;
4326
return InvokeAsync(StateHasChanged);
4427
}
4528

46-
private async Task OnFindQueryInputAsync(ChangeEventArgs args)
29+
private Task OnFindQueryInputAsync(ChangeEventArgs args)
4730
{
4831
_findQuery = args.Value?.ToString() ?? string.Empty;
4932
RefreshFindMatchesForCurrentText();
50-
await FocusActiveFindMatchAsync(focusEditor: false);
51-
await InvokeAsync(StateHasChanged);
33+
_syncFindHighlightsAfterRender = true;
34+
return InvokeAsync(StateHasChanged);
5235
}
5336

5437
private Task NavigateToPreviousFindMatchAsync() =>
@@ -59,7 +42,7 @@ private Task NavigateToNextFindMatchAsync() =>
5942

6043
private void RefreshFindMatchesForCurrentText()
6144
{
62-
if (!_showFindBar || string.IsNullOrWhiteSpace(_findQuery))
45+
if (string.IsNullOrWhiteSpace(_findQuery))
6346
{
6447
_findMatches = [];
6548
_findMatchIndex = -1;
@@ -98,7 +81,8 @@ private async Task NavigateFindMatchAsync(int direction)
9881

9982
var nextIndex = (_findMatchIndex + direction + _findMatches.Count) % _findMatches.Count;
10083
_findMatchIndex = nextIndex;
101-
await FocusActiveFindMatchAsync(focusEditor: false);
84+
_syncFindHighlightsAfterRender = true;
85+
await FocusActiveFindMatchAsync(focusEditor: true);
10286
await InvokeAsync(StateHasChanged);
10387
}
10488

@@ -110,7 +94,11 @@ private async Task FocusActiveFindMatchAsync(bool focusEditor)
11094
}
11195

11296
var activeMatch = _findMatches[_findMatchIndex];
113-
await FocusRangeAsync(activeMatch.Start, activeMatch.End, focusEditor: focusEditor);
97+
await FocusRangeAsync(
98+
activeMatch.Start,
99+
activeMatch.End,
100+
focusEditor: focusEditor,
101+
syncSelectionState: false);
114102
}
115103

116104
private void ClearFindState()
@@ -120,6 +108,23 @@ private void ClearFindState()
120108
_findMatchIndex = -1;
121109
}
122110

111+
private IReadOnlyList<EditorFindMatchInteropRange> BuildFindHighlightRanges()
112+
{
113+
if (_findMatches.Count == 0)
114+
{
115+
return [];
116+
}
117+
118+
var ranges = new EditorFindMatchInteropRange[_findMatches.Count];
119+
for (var index = 0; index < _findMatches.Count; index++)
120+
{
121+
var match = _findMatches[index];
122+
ranges[index] = new EditorFindMatchInteropRange(match.Start, match.End);
123+
}
124+
125+
return ranges;
126+
}
127+
123128
private readonly record struct EditorFindMatch(int Start, int Length)
124129
{
125130
public int End => Start + Length;

src/PrompterOne.Shared/Editor/Components/EditorSourcePanel.ToolbarState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private static string GetFloatingMenuCss(EditorFloatingMenuDescriptor menu) =>
116116

117117
private bool HasOpenToolbarMenu => !string.IsNullOrWhiteSpace(_openMenuId);
118118

119-
private bool ShouldRenderFloatingBar => CanRenderFloatingToolbar && _floatingBarAnchor.HasSelection && !HasOpenToolbarMenu && !_showFindBar;
119+
private bool ShouldRenderFloatingBar => CanRenderFloatingToolbar && _floatingBarAnchor.HasSelection && !HasOpenToolbarMenu;
120120

121121
private bool GetActionDisabled(EditorToolbarActionDescriptor action) =>
122122
action.ActionType switch

src/PrompterOne.Shared/Editor/Components/EditorSourcePanel.razor

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
@inject EditorMonacoInterop MonacoInterop
99

1010
<div class="ed-main" data-test="@UiTestIds.Editor.MainPanel">
11-
<div class="ed-toolbar" data-test="@UiTestIds.Editor.Toolbar">
11+
<div class="ed-toolbar"
12+
@ref="_toolbarRef"
13+
data-test="@UiTestIds.Editor.Toolbar">
1214
@for (var sectionIndex = 0; sectionIndex < ToolbarSections.Count; sectionIndex++)
1315
{
1416
var toolbarSection = ToolbarSections[sectionIndex];
@@ -57,34 +59,42 @@
5759
}
5860
}
5961

60-
<div class="ed-toolbar-tools">
62+
<div class="ed-toolbar-tail-spacer" aria-hidden="true"></div>
63+
64+
<div class="ed-toolbar-tools" data-test="@UiTestIds.Editor.ToolbarTools">
65+
<button type="button"
66+
class="ed-toolbar-scroll"
67+
aria-label="@L(UiTextKey.EditorToolbarScrollPrevious)"
68+
@onclick="() => ScrollToolbarBackwardAsync()"
69+
data-test="@UiTestIds.Editor.ToolbarScrollPrevious">
70+
<UiIcon Kind="UiIconKind.ChevronLeft" Size="14" StrokeWidth="2.5m" />
71+
</button>
72+
6173
<button type="button"
62-
class="ed-find-toggle"
63-
@onclick="ToggleFindBarAsync"
64-
aria-expanded="@(_showFindBar ? "true" : "false")"
65-
data-test="@UiTestIds.Editor.FindToggle">
66-
<UiIcon Kind="UiIconKind.Search" Size="14" />
67-
<span>@L(UiTextKey.EditorFindOpen)</span>
74+
class="ed-toolbar-scroll"
75+
aria-label="@L(UiTextKey.EditorToolbarScrollNext)"
76+
@onclick="() => ScrollToolbarForwardAsync()"
77+
data-test="@UiTestIds.Editor.ToolbarScrollNext">
78+
<UiIcon Kind="UiIconKind.ChevronRight" Size="14" StrokeWidth="2.5m" />
6879
</button>
80+
81+
<EditorFindBar CanNavigate="@CanNavigateFindMatches"
82+
CloseLabel="@L(UiTextKey.EditorFindClose)"
83+
Compact="true"
84+
NextLabel="@L(UiTextKey.EditorFindNext)"
85+
OnClose="ClearFindQueryAsync"
86+
OnInput="OnFindQueryInputAsync"
87+
OnNext="NavigateToNextFindMatchAsync"
88+
OnPrevious="NavigateToPreviousFindMatchAsync"
89+
Placeholder="@L(UiTextKey.EditorFindPlaceholder)"
90+
PreviousLabel="@L(UiTextKey.EditorFindPrevious)"
91+
Query="@_findQuery"
92+
ResultLabel="@FindResultLabel"
93+
ShowCloseAction="false"
94+
ShowResult="@ShouldRenderFindResult" />
6995
</div>
7096
</div>
7197

72-
@if (_showFindBar)
73-
{
74-
<EditorFindBar CanNavigate="@CanNavigateFindMatches"
75-
CloseLabel="@L(UiTextKey.EditorFindClose)"
76-
NextLabel="@L(UiTextKey.EditorFindNext)"
77-
OnClose="CloseFindBarAsync"
78-
OnInput="OnFindQueryInputAsync"
79-
OnNext="NavigateToNextFindMatchAsync"
80-
OnPrevious="NavigateToPreviousFindMatchAsync"
81-
Placeholder="@L(UiTextKey.EditorFindPlaceholder)"
82-
PreviousLabel="@L(UiTextKey.EditorFindPrevious)"
83-
Query="@_findQuery"
84-
ResultLabel="@FindResultLabel"
85-
ShowResult="@ShouldRenderFindResult" />
86-
}
87-
8898
<div class="ed-content ed-source-shell"
8999
data-test="@UiTestIds.Editor.SourceScrollHost">
90100
<div class="ed-source-stage">
@@ -185,8 +195,6 @@
185195
ProfileValue="@Status.Profile"
186196
SegmentsLabel="@StatusSegmentsLabel"
187197
SegmentsValue="@StatusSegmentsValue"
188-
VersionLabel="@StatusVersionLabel"
189-
VersionValue="@StatusVersionValue"
190198
WordCountLabel="@StatusWordCountLabel"
191199
WordCountValue="@StatusWordCountValue" />
192200
</div>

0 commit comments

Comments
 (0)