Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Capacitor.App/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@ public ISessionWorkspace? CurrentWorkspace {
public bool HistoryImportVisible => HistoryImport is not null;
public bool HistoryImportRunning => HistoryImport is { Running: true };
public string HistoryImportStatus => HistoryImport is not { } run ? ""
: run.Running ? $"Importing history · {run.Imported:N0} sessions uploaded"
: run.Running ? $"Importing history · {run.Processed:N0} sessions processed"
: run.State is ImportRunState.Failed or ImportRunState.Cancelled
? $"History import didn't finish · {run.Imported:N0} sessions uploaded. Reopen setup to retry."
: $"History imported · {run.Imported:N0} sessions uploaded";
? $"History import didn't finish · {run.Processed:N0} sessions processed. Reopen setup to retry."
: $"History imported · {run.Processed:N0} sessions processed";

ObservableAsPropertyHelper<bool>? _signInVisible;
/// True while the rail reads Signed out and a sign-in action exists — the help flyout
Expand Down Expand Up @@ -420,7 +420,7 @@ public MainWindowViewModel(

this.WhenActivated(disposables => {
if (historyImport is not null)
historyImport.WhenAnyValue(x => x.State, x => x.Imported, (state, imported) => (state, imported))
historyImport.WhenAnyValue(x => x.State, x => x.Processed, (state, processed) => (state, processed))
.ObserveOn(RxSchedulers.MainThreadScheduler)
.Subscribe(_ => {
this.RaisePropertyChanged(nameof(HistoryImportStatus));
Expand Down
4 changes: 2 additions & 2 deletions src/Capacitor.App/ViewModels/Onboarding/DoneStepViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public DoneStepViewModel(Func<DoneFacts> facts, IUrlOpener? opener = null, Func<
: Import is null ? $"kcap keeps what your coding agents do on {_current.MachineName}, and gives them tools to search it back — so the next time you hit something, what you already worked out is there to find."
: Import.Running ? "What has landed is already searchable."
: Import.Imported > 0 ? "Your agents can search all of it — ask one what you have already tried, and it looks here first."
: "No sessions were uploaded. Your history is still on this machine.";
: "No sessions were imported. Your history is still on this machine.";

public bool ShowsFigures => Import is not null;

Expand Down Expand Up @@ -100,7 +100,7 @@ public DoneStepViewModel(Func<DoneFacts> facts, IUrlOpener? opener = null, Func<
public bool FailedVisible => Import is { State: ImportRunState.Failed or ImportRunState.Cancelled } or { Running: false, Failed: > 0 };

public string FailedTitle => Import is { Failed: 0 } ? "History import did not finish"
: Import is { Failed: 1 } ? "1 session failed to upload" : $"{Import?.Failed ?? 0} sessions failed to upload";
: Import is { Failed: 1 } ? "1 session failed to import" : $"{Import?.Failed ?? 0} sessions failed to import";

public string FailedBody => _current.Local
? $"Your history is still on {_current.MachineName}. Go back to History to retry."
Expand Down
22 changes: 14 additions & 8 deletions src/Capacitor.App/ViewModels/Onboarding/HistoryImportRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public sealed partial class HistoryImportRun : ReactiveObject {
int _processed;
int _passImported;
int _passFailed;
int _passProcessed;

internal HistoryImportRun(int expected, int repositories, string windowCaption, int passes, Action<Action> post) {
Expected = expected;
Expand Down Expand Up @@ -90,8 +91,9 @@ async Task RunAsync(IKcapCli cli, IReadOnlyList<ImportRequest> passes) {
anyFailed |= result.ExitCode != 0 || result.TimedOut;
_post(() => {
// A pass that died before its totals line keeps its live counts; the next pass's totals must not net them out.
_passImported = 0;
_passFailed = 0;
_passImported = 0;
_passFailed = 0;
_passProcessed = 0;
PassesDone++;
});
}
Expand All @@ -112,12 +114,15 @@ void OnLine(StreamedLine line) => _post(() => {

var text = line.Text.Trim();
if (Totals().Match(text) is { Success: true } m &&
int.TryParse(m.Groups[1].Value, out var imported) && int.TryParse(m.Groups[3].Value, out var failed)) {
int.TryParse(m.Groups[1].Value, out var imported) && int.TryParse(m.Groups[2].Value, out var skipped) &&
int.TryParse(m.Groups[3].Value, out var failed)) {
// The closing line is authoritative: it also counts what never printed a session line.
Imported = Add(Imported, imported - _passImported);
Failed = Add(Failed, failed - _passFailed);
_passImported = 0;
_passFailed = 0;
Imported = Add(Imported, imported - _passImported);
Failed = Add(Failed, failed - _passFailed);
Processed = Add(Processed, (long)imported + skipped + failed - _passProcessed);
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
_passImported = 0;
_passFailed = 0;
_passProcessed = 0;
return;
}

Expand All @@ -131,10 +136,11 @@ void OnLine(StreamedLine line) => _post(() => {
return;
}

_passProcessed++;
Processed = Add(Processed, 1);
});

static int Add(int total, int delta) => (int)Math.Clamp((long)total + delta, 0, int.MaxValue);
static int Add(int total, long delta) => (int)Math.Clamp(total + delta, 0, int.MaxValue);

// Each pass closes with this line.
[GeneratedRegex(@"^(\d+) imported · (\d+) skipped · (\d+) failed$")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ public HistoryStepViewModel(
public bool Applicable => true;
public string SkipLabel => "Not now";

public string Lede =>
"kcap uploads the transcripts your harnesses have already written, so your past work is searchable " +
"from day one. Nothing leaves this machine until you choose.";
public string Lede => _onLocalPath()
? "kcap imports the transcripts your harnesses have already written, so your past work is searchable " +
"from day one. Everything stays on this machine."
: "kcap uploads the transcripts your harnesses have already written, so your past work is searchable " +
"from day one. Nothing leaves this machine until you choose.";

public string MachineName { get; }

Expand Down
4 changes: 2 additions & 2 deletions test/Capacitor.App.Tests.Unit/DoneStepViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task The_restart_line_shows_only_when_new_hooks_need_one() {

[Test]
[NotInParallel("AvaloniaSession")]
public async Task Failed_uploads_are_named_with_where_they_still_are() {
public async Task Failed_imports_are_named_with_where_they_still_are() {
var (visible, title, body, landed, unit) = await AvaloniaSession.DispatchAsync(async () => {
var vm = new DoneStepViewModel(() => Facts(import: null));
var run = await FinishedRun(30, 3);
Expand All @@ -79,7 +79,7 @@ public async Task Failed_uploads_are_named_with_where_they_still_are() {
});

await Assert.That(visible).IsTrue();
await Assert.That(title).IsEqualTo("3 sessions failed to upload");
await Assert.That(title).IsEqualTo("3 sessions failed to import");
await Assert.That(body).Contains("test-mac");
await Assert.That(body).Contains("retry");
await Assert.That(landed).IsEqualTo("30");
Expand Down
28 changes: 28 additions & 0 deletions test/Capacitor.App.Tests.Unit/HistoryImportRunTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Capacitor.App.Services;
using Capacitor.App.ViewModels.Onboarding;
using Capacitor.Cli.Core;

namespace Capacitor.App.Tests.Unit;

public class HistoryImportRunTests {
/// The totals line also counts sessions that never printed a line of their own, so it settles the
/// processed count without counting the printed ones twice.
[Test]
public async Task The_totals_line_settles_the_processed_count() {
var cli = new FakeKcapCli {
ImportBehavior = (_, onLine, _) => {
onLine(new StreamedLine(ProcessStreamKind.Stdout, " Loading s1 (claude)"));
onLine(new StreamedLine(ProcessStreamKind.Stdout, " Loading s2 (claude)"));
onLine(new StreamedLine(ProcessStreamKind.Stdout, " Skipping k1 (already current)"));
onLine(new StreamedLine(ProcessStreamKind.Stdout, " 5 imported · 2 skipped · 1 failed"));
return Task.FromResult(new StreamingResult(0, false, []));
},
};
var run = new HistoryImportRun(0, 1, "from the last 90 days", 1, action => action());
run.Start(cli, [new ImportRequest(ImportScopeChoice.Repo, null, [], ["a/b"])]);
await run.Completion;

await Assert.That(run.Processed).IsEqualTo(8);
await Assert.That(run.Imported).IsEqualTo(5);
}
}
2 changes: 1 addition & 1 deletion test/Capacitor.App.Tests.Unit/HistoryStepViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public async Task The_run_counts_sessions_as_they_land() {
await Assert.That(midway).IsEqualTo((2, 2, 6));
await Assert.That(h.Vm.Run!.Imported).IsEqualTo(6);
await Assert.That(h.Vm.Run!.Failed).IsEqualTo(6);
await Assert.That(h.Vm.Run!.Processed).IsEqualTo(12);
await Assert.That(h.Vm.Run!.Processed).IsEqualTo(16);
}

/// A pass that dies before its totals line keeps what it counted, and the next pass's totals add to it.
Expand Down
Loading