Skip to content
Open
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
311 changes: 288 additions & 23 deletions PolyPilot.Tests/AddExistingRepoTests.cs

Large diffs are not rendered by default.

402 changes: 361 additions & 41 deletions PolyPilot.Tests/RepoManagerTests.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions PolyPilot.Tests/WorktreeStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public FakeRepoManager(List<RepositoryInfo> repos)
}

public override Task<WorktreeInfo> CreateWorktreeAsync(string repoId, string branchName,
string? baseBranch = null, bool skipFetch = false, string? localPath = null, CancellationToken ct = default)
string? baseBranch = null, bool skipFetch = false, CancellationToken ct = default)
{
CreateCalls.Add((repoId, branchName, skipFetch));
var id = $"wt-{Interlocked.Increment(ref _worktreeCounter)}";
Expand Down Expand Up @@ -560,7 +560,7 @@ public FailingRepoManager(List<RepositoryInfo> repos)
}

public override Task<WorktreeInfo> CreateWorktreeAsync(string repoId, string branchName,
string? baseBranch = null, bool skipFetch = false, string? localPath = null, CancellationToken ct = default)
string? baseBranch = null, bool skipFetch = false, CancellationToken ct = default)
{
throw new InvalidOperationException("Simulated git failure");
}
Expand Down
26 changes: 10 additions & 16 deletions PolyPilot/Components/Layout/SessionSidebar.razor
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ else
<div class="sidebar-create-section">
<span class="sidebar-create-section-label">Folder</span>
<div class="add-repo-folder-row">
<input type="text" @bind="addRepoFolderPath"
<input type="text" @bind="addRepoFolderPath" @bind:event="oninput"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: @bind:event="oninput" violates documented project guideline

The project's copilot-instructions.md explicitly states:

Avoid @bind:event="oninput" — causes round-trip lag per keystroke.

This PR adds @bind:event="oninput" on 3 inputs (lines 799, 820, 1101). While the UX goal (enabling the Add button immediately on keystroke) is valid, the implementation causes a Blazor → JS → C# → re-render round-trip on every keystroke, which is noticeable on mobile.

Suggested alternative: Use a JS oninput listener that only updates a flag variable to enable/disable the button, without re-binding the full input value per keystroke.

placeholder="Path to a cloned repository…"
class="repo-url-input sidebar-create-input" />
<button class="btn-repo-browse sidebar-create-button sidebar-create-button-secondary sidebar-create-browse" @onclick="BrowseExistingRepo"
Expand All @@ -817,7 +817,7 @@ else
{
<div class="sidebar-create-section">
<span class="sidebar-create-section-label">Repository URL</span>
<input type="text" @bind="newRepoUrl" placeholder="owner/repo or full URL" class="repo-url-input sidebar-create-input" />
<input type="text" @bind="newRepoUrl" @bind:event="oninput" placeholder="owner/repo or full URL" class="repo-url-input sidebar-create-input" />
</div>
@if (confirmRepoReplace)
{
Expand Down Expand Up @@ -1004,13 +1004,12 @@ else
</button>
@if (!string.IsNullOrEmpty(group.RepoId))
{
@* 📁 group backed by a bare clone — offer full branch/worktree features *@
@* 📁 group backed by a repo — offer full branch/worktree features *@
var lfRepoId = group.RepoId!;
var lfLocalPath = group.LocalPath!;
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; QuickCreateSessionForRepo(lfRepoId, gId, lfLocalPath); }">
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; QuickCreateSessionForRepo(lfRepoId, gId); }">
⚡ Quick Branch + Session
</button>
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; StartQuickBranch(lfRepoId, gId, lfLocalPath); }">
<button class="group-menu-item" @onclick="() => { openGroupMenuId = null; StartQuickBranch(lfRepoId, gId); }">
⑂ Named Branch + Session
</button>
}
Expand Down Expand Up @@ -1957,7 +1956,6 @@ else
// Quick-create inline branch input
private string? quickBranchRepoId = null;
private string? quickBranchGroupId = null;
private string? quickBranchLocalPath = null;
private string quickBranchInput = "";
private bool quickBranchIsCreating = false;
private string? quickBranchError = null;
Expand Down Expand Up @@ -2568,7 +2566,7 @@ else
}
}

private async Task QuickCreateSessionForRepo(string repoId, string? targetGroupId = null, string? localPath = null)
private async Task QuickCreateSessionForRepo(string repoId, string? targetGroupId = null)
{
if (isCreating) return;
isCreating = true;
Expand All @@ -2580,8 +2578,7 @@ else
var sessionInfo = await CopilotService.CreateSessionWithWorktreeAsync(
repoId: repoId,
model: selectedModel,
targetGroupId: targetGroupId,
localPath: localPath);
targetGroupId: targetGroupId);
CopilotService.SaveUiState(currentPage, selectedModel: selectedModel);
await OnSessionSelected.InvokeAsync();
}
Expand All @@ -2598,19 +2595,18 @@ else
}
}

private void StartQuickBranch(string repoId, string? targetGroupId = null, string? localPath = null)
private void StartQuickBranch(string repoId, string? targetGroupId = null)
{
quickBranchRepoId = repoId;
quickBranchGroupId = targetGroupId;
quickBranchLocalPath = localPath;
quickBranchInput = "";
quickBranchError = null;
}

private async Task HandleQuickBranchKeyDown(KeyboardEventArgs e, string repoId)
{
if (e.Key == "Enter") await CommitQuickBranch(repoId);
else if (e.Key == "Escape") { quickBranchRepoId = null; quickBranchLocalPath = null; }
else if (e.Key == "Escape") { quickBranchRepoId = null; }
}

private async Task CommitQuickBranch(string repoId)
Expand Down Expand Up @@ -2648,12 +2644,10 @@ else
branchName: branchName,
prNumber: prNumber,
model: selectedModel,
targetGroupId: quickBranchGroupId,
localPath: quickBranchLocalPath);
targetGroupId: quickBranchGroupId);

quickBranchRepoId = null;
quickBranchGroupId = null;
quickBranchLocalPath = null;
quickBranchInput = "";
CopilotService.SaveUiState(currentPage, selectedModel: selectedModel);
await OnSessionSelected.InvokeAsync();
Expand Down
25 changes: 25 additions & 0 deletions PolyPilot/Services/CopilotService.Organization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,31 @@ internal void ReconcileOrganization(bool allowPruning = true)
if (GetOrCreateRepoGroup(repo.Id, repo.Name) != null)
changed = true;
}

// Migration: update group names that were derived from id.Split('-').Last() (issue #570).
// E.g., groups named "maui" for repo "nicknisi-vscode-maui" should become "vscode-maui".
// Only migrate names that still match the old broken derivation — if the user
// renamed the group (e.g., "maui - PP"), preserve their customization.
foreach (var g in Organization.Groups.Where(g => g.RepoId == repo.Id && !g.IsMultiAgent && !g.IsLocalFolder))
{
var correctName = repo.Name;
if (string.IsNullOrEmpty(correctName) || g.Name == correctName)
continue;
if (Organization.Groups.Any(other => other != g && other.RepoId == repo.Id && other.Name == correctName && !other.IsMultiAgent && !other.IsLocalFolder))
continue;

// The old code derived names via id.Split('-').Last(). Only overwrite if
// the current group name matches that old pattern — otherwise user renamed it.
var oldDerivedName = repo.Id.Contains('-')
? repo.Id.Split('-').Last()
: repo.Id;
if (string.Equals(g.Name, oldDerivedName, StringComparison.Ordinal))
{
Debug($"ReconcileOrganization: migrating group name '{g.Name}' → '{correctName}' (repoId: {repo.Id})");
g.Name = correctName;
changed = true;
}
}
}

// Migration: back-fill LocalPath/RepoId on groups that were created by an older version
Expand Down
3 changes: 1 addition & 2 deletions PolyPilot/Services/CopilotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3074,7 +3074,6 @@ public async Task<AgentSessionInfo> CreateSessionWithWorktreeAsync(
string? model = null,
string? initialPrompt = null,
string? targetGroupId = null,
string? localPath = null,
CancellationToken ct = default)
{
// Remote mode: send the entire operation to the server as a single atomic command.
Expand Down Expand Up @@ -3150,7 +3149,7 @@ await _bridgeClient.CreateSessionWithWorktreeAsync(new CreateSessionWithWorktree
else
{
var branch = branchName ?? $"session-{DateTime.Now:yyyyMMdd-HHmmss}";
wt = await _repoManager.CreateWorktreeAsync(repoId, branch, null, localPath: localPath, ct: ct);
wt = await _repoManager.CreateWorktreeAsync(repoId, branch, null, ct: ct);
}

// Derive a friendly display name: prefer explicit sessionName, then branch name,
Expand Down
Loading