Skip to content

Commit 9dc72c7

Browse files
committed
refine(config): keep search save flow in context
Return Esc from the saved state to the Search backend list instead of exiting the editor. Clarify the provider markers so active and configured backends are visually distinct.
1 parent ed83b95 commit 9dc72c7

3 files changed

Lines changed: 115 additions & 5 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="SearchConfigEditorPageTests.cs" company="Petabridge, LLC">
3+
// Copyright (C) 2026 - 2026 Petabridge, LLC <https://petabridge.com>
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Netclaw.Cli.Tui.Config;
8+
using Netclaw.Configuration;
9+
using Netclaw.Tests.Utilities;
10+
using Termina;
11+
using Termina.Hosting;
12+
using Termina.Input;
13+
using Termina.Terminal;
14+
using Xunit;
15+
16+
namespace Netclaw.Cli.Tests.Tui.Config;
17+
18+
public sealed class SearchConfigEditorPageTests : IDisposable
19+
{
20+
private readonly DisposableTempDir _dir = new();
21+
private readonly NetclawPaths _paths;
22+
23+
public SearchConfigEditorPageTests()
24+
{
25+
_paths = new NetclawPaths(_dir.Path);
26+
_paths.EnsureDirectoriesExist();
27+
File.WriteAllText(_paths.NetclawConfigPath, """
28+
{
29+
"configVersion": 1,
30+
"Search": {
31+
"Backend": "duckduckgo"
32+
}
33+
}
34+
""");
35+
}
36+
37+
public void Dispose() => _dir.Dispose();
38+
39+
[Fact]
40+
public async Task ProviderSelection_RendersActiveAndConfiguredLegend()
41+
{
42+
var (terminal, app, _) = CreateHeadlessApp(out var input);
43+
input.EnqueueKey(ConsoleKey.Q, false, false, true);
44+
45+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
46+
await app.RunAsync(cts.Token);
47+
48+
Assert.True(terminal.Contains("(*) active backend"),
49+
$"Expected active-backend legend in terminal output. Screen:\n{terminal}");
50+
Assert.True(terminal.Contains("backend has saved setup"),
51+
$"Expected configured-backend legend in terminal output. Screen:\n{terminal}");
52+
}
53+
54+
[Fact]
55+
public async Task SavedScreen_EscapeReturnsToProviderSelection()
56+
{
57+
var (terminal, app, vm) = CreateHeadlessApp(out var input);
58+
59+
vm.SaveWithoutProbeOverride();
60+
61+
input.EnqueueKey(ConsoleKey.Escape);
62+
input.EnqueueKey(ConsoleKey.Q, false, false, true);
63+
64+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
65+
await app.RunAsync(cts.Token);
66+
67+
Assert.Equal(SearchConfigEditorScreen.ProviderSelection, vm.CurrentScreen.Value);
68+
Assert.True(terminal.Contains("Choose the backend Netclaw uses for web search."),
69+
$"Expected provider selection screen after Esc from saved state. Screen:\n{terminal}");
70+
}
71+
72+
private (VirtualTerminal Terminal, TerminaApplication App, SearchConfigEditorViewModel Vm)
73+
CreateHeadlessApp(out VirtualInputSource input)
74+
{
75+
var terminal = new VirtualTerminal(120, 40);
76+
var virtualInput = new VirtualInputSource();
77+
input = virtualInput;
78+
79+
SearchConfigEditorViewModel? capturedVm = null;
80+
81+
var services = new ServiceCollection();
82+
services.AddSingleton<IAnsiTerminal>(terminal);
83+
services.AddTerminaVirtualInput(virtualInput);
84+
services.AddTermina("/search", builder =>
85+
{
86+
builder.RegisterRoute<SearchConfigEditorPage, SearchConfigEditorViewModel>(
87+
"/search",
88+
_ => new SearchConfigEditorPage(),
89+
_ =>
90+
{
91+
capturedVm = new SearchConfigEditorViewModel(_paths);
92+
return capturedVm;
93+
});
94+
});
95+
96+
var sp = services.BuildServiceProvider();
97+
var app = sp.GetRequiredService<TerminaApplication>();
98+
99+
return (terminal, app, capturedVm!);
100+
}
101+
}

src/Netclaw.Cli/Tui/Config/SearchConfigEditorPage.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private ILayoutNode BuildProviderSelectionScreen()
9494
.WithSpacing(1)
9595
.WithChild(new TextNode(" Choose the backend Netclaw uses for web search.").WithForeground(Color.White))
9696
.WithChild(BuildProviderList())
97+
.WithChild(new TextNode(" (*) active backend ✓ backend has saved setup").WithForeground(Color.Gray))
9798
.WithChild(new TextNode($" {GetProviderDescription(ViewModel.BackendOptions[_providerIndex].Value)}").WithForeground(Color.Gray));
9899
}
99100

@@ -136,7 +137,7 @@ private ILayoutNode BuildSavedScreen()
136137
=> Layouts.Vertical()
137138
.WithSpacing(1)
138139
.WithChild(new TextNode($" \u2714 {ViewModel.CurrentBackendLabel} validated and saved.").WithForeground(Color.Green))
139-
.WithChild(new TextNode(" Press Esc to return to Settings Areas or Up/Down to review providers.")
140+
.WithChild(new TextNode(" Press Esc to return to Search backends or Up/Down to review providers.")
140141
.WithForeground(Color.Gray));
141142

142143
private ILayoutNode BuildProviderList()
@@ -238,9 +239,6 @@ private LayoutNode BuildKeyBindings()
238239

239240
public override bool HandlePageInput(ConsoleKeyInfo keyInfo)
240241
{
241-
if (base.HandlePageInput(keyInfo))
242-
return true;
243-
244242
if (keyInfo.Key == ConsoleKey.Q && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control))
245243
{
246244
ViewModel.RequestQuit();
@@ -262,10 +260,19 @@ public override bool HandlePageInput(ConsoleKeyInfo keyInfo)
262260
return true;
263261
}
264262

263+
if (ViewModel.CurrentScreen.Value == SearchConfigEditorScreen.Saved)
264+
{
265+
BeginProviderSelection();
266+
return true;
267+
}
268+
265269
ViewModel.NavigateBack();
266270
return true;
267271
}
268272

273+
if (base.HandlePageInput(keyInfo))
274+
return true;
275+
269276
if (ViewModel.ActiveDialog.Value == SearchConfigEditorDialog.ProbeWarning)
270277
{
271278
_dialogList?.HandleInput(keyInfo);

tests/smoke/tapes/config-search.tape

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ Down 2
5252
Enter
5353
Wait+Screen@10s /validated and saved/
5454
Escape
55-
Wait+Screen@10s /Settings Areas/
55+
Wait+Screen@10s /Choose the backend Netclaw uses for web search/
5656

5757
# ─── Back out to shell ────────────────────────────────────────────────────
58+
Escape
59+
Wait+Screen@10s /Settings Areas/
5860
Ctrl+Q
5961
Wait+Screen@10s /TAPE\$/
6062

0 commit comments

Comments
 (0)