feat(ui): base branch field in list settings, real branch names in dialogs
This commit is contained in:
@@ -471,6 +471,9 @@
|
|||||||
"folderColorHint": "Färbt den Ordnernamen und seine Führungslinie in der Seitenleiste.",
|
"folderColorHint": "Färbt den Ordnernamen und seine Führungslinie in der Seitenleiste.",
|
||||||
"workingDirectory": "Arbeitsverzeichnis",
|
"workingDirectory": "Arbeitsverzeichnis",
|
||||||
"workingDirectoryPlaceholder": "(keines)",
|
"workingDirectoryPlaceholder": "(keines)",
|
||||||
|
"baseBranch": "Basis-Branch",
|
||||||
|
"baseBranchPlaceholder": "(ausgecheckter Branch)",
|
||||||
|
"baseBranchHint": "Neue Task-Branches starten hier, und freigegebene Arbeit wird hierhin gemergt, z.B. dev. Leer = der gerade ausgecheckte Branch.",
|
||||||
"browse": "Durchsuchen...",
|
"browse": "Durchsuchen...",
|
||||||
"defaultCommitType": "Standard-Commit-Typ",
|
"defaultCommitType": "Standard-Commit-Typ",
|
||||||
"manualList": "Manuelle Liste (Erinnerungen)",
|
"manualList": "Manuelle Liste (Erinnerungen)",
|
||||||
|
|||||||
@@ -471,6 +471,9 @@
|
|||||||
"folderColorHint": "Tints the folder name and its guide line in the sidebar.",
|
"folderColorHint": "Tints the folder name and its guide line in the sidebar.",
|
||||||
"workingDirectory": "Working directory",
|
"workingDirectory": "Working directory",
|
||||||
"workingDirectoryPlaceholder": "(none)",
|
"workingDirectoryPlaceholder": "(none)",
|
||||||
|
"baseBranch": "Base branch",
|
||||||
|
"baseBranchPlaceholder": "(checked-out branch)",
|
||||||
|
"baseBranchHint": "New task branches start here and approved work merges back here, e.g. dev. Leave empty to use whatever branch is checked out.",
|
||||||
"browse": "Browse...",
|
"browse": "Browse...",
|
||||||
"defaultCommitType": "Default commit type",
|
"defaultCommitType": "Default commit type",
|
||||||
"manualList": "Manual list (reminders)",
|
"manualList": "Manual list (reminders)",
|
||||||
|
|||||||
@@ -208,13 +208,13 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyCommand, ticketProjectId and powerUseAllowed are List-only fields owned by
|
// verifyCommand, ticketProjectId, powerUseAllowed and baseBranch are List-only fields owned by
|
||||||
// ListSettingsModalViewModel (not this editor, which is also reused for Task scope); the caller
|
// ListSettingsModalViewModel (not this editor, which is also reused for Task scope); the caller
|
||||||
// passes them through so the single UpdateListConfig call carries the full desired row instead
|
// passes them through so the single UpdateListConfig call carries the full desired row instead
|
||||||
// of clobbering it. powerUseAllowed stays null-by-default for the same reason ticketProjectId
|
// of clobbering it. powerUseAllowed stays null-by-default for the same reason ticketProjectId
|
||||||
// does: a caller that doesn't own it must not revoke the list's opt-in by omission.
|
// does: a caller that doesn't own it must not revoke the list's opt-in by omission.
|
||||||
public async System.Threading.Tasks.Task SaveAsync(
|
public async System.Threading.Tasks.Task SaveAsync(
|
||||||
string? verifyCommand = null, int? ticketProjectId = null, bool? powerUseAllowed = null)
|
string? verifyCommand = null, int? ticketProjectId = null, bool? powerUseAllowed = null, string? baseBranch = null)
|
||||||
{
|
{
|
||||||
if (TargetId is null) return;
|
if (TargetId is null) return;
|
||||||
var model = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
var model = string.IsNullOrWhiteSpace(Model) ? null : Model;
|
||||||
@@ -227,7 +227,7 @@ public sealed partial class AgentConfigEditorViewModel : ViewModelBase, IDisposa
|
|||||||
if (_scope == AgentConfigScope.Task)
|
if (_scope == AgentConfigScope.Task)
|
||||||
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills, permission));
|
await _worker.UpdateTaskAgentSettingsAsync(new UpdateTaskAgentSettingsDto(TargetId, model, sp, ap, turns, skills, permission));
|
||||||
else
|
else
|
||||||
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, permission, ticketProjectId, powerUseAllowed));
|
await _worker.UpdateListConfigAsync(new UpdateListConfigDto(TargetId, model, sp, ap, turns, skills, verifyCommand, permission, ticketProjectId, powerUseAllowed, baseBranch));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string>? SelectedSessionSkillNames()
|
private List<string>? SelectedSessionSkillNames()
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
WorktreeBaseCommit = entity.Worktree?.BaseCommit ?? entity.HandlerBaseCommit;
|
WorktreeBaseCommit = entity.Worktree?.BaseCommit ?? entity.HandlerBaseCommit;
|
||||||
WorktreeHeadCommit = entity.Worktree?.HeadCommit ?? entity.HandlerHeadCommit;
|
WorktreeHeadCommit = entity.Worktree?.HeadCommit ?? entity.HandlerHeadCommit;
|
||||||
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
||||||
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} ← main" : null;
|
BranchLine = entity.Worktree?.BranchName;
|
||||||
var (add, del) = ParseDiffStat(entity.Worktree?.DiffStat);
|
var (add, del) = ParseDiffStat(entity.Worktree?.DiffStat);
|
||||||
DiffAdditions = add;
|
DiffAdditions = add;
|
||||||
DiffDeletions = del;
|
DiffDeletions = del;
|
||||||
@@ -930,7 +930,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
WorktreeBaseCommit = entity.Worktree?.BaseCommit ?? entity.HandlerBaseCommit;
|
WorktreeBaseCommit = entity.Worktree?.BaseCommit ?? entity.HandlerBaseCommit;
|
||||||
WorktreeHeadCommit = entity.Worktree?.HeadCommit ?? entity.HandlerHeadCommit;
|
WorktreeHeadCommit = entity.Worktree?.HeadCommit ?? entity.HandlerHeadCommit;
|
||||||
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
WorktreeStateLabel = entity.Worktree?.State.ToString();
|
||||||
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} ← main" : null;
|
BranchLine = entity.Worktree?.BranchName;
|
||||||
if (Task is { } row && entity.Worktree?.DiffStat is { } stat)
|
if (Task is { } row && entity.Worktree?.DiffStat is { } stat)
|
||||||
row.DiffStat = stat;
|
row.DiffStat = stat;
|
||||||
var (add, del) = ParseDiffStat(entity.Worktree?.DiffStat);
|
var (add, del) = ParseDiffStat(entity.Worktree?.DiffStat);
|
||||||
@@ -1220,7 +1220,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
if (Task == null) return;
|
if (Task == null) return;
|
||||||
if (Dialogs == null) return;
|
if (Dialogs == null) return;
|
||||||
|
|
||||||
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
var branchName = BranchLine ?? "the task branch";
|
||||||
var ok = await Dialogs.ConfirmAsync(
|
var ok = await Dialogs.ConfirmAsync(
|
||||||
$"Reset and retry?\nThis discards branch {branchName} (and uncommitted changes), then queues the task to run from the beginning.");
|
$"Reset and retry?\nThis discards branch {branchName} (and uncommitted changes), then queues the task to run from the beginning.");
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -1382,7 +1382,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
|||||||
private async System.Threading.Tasks.Task ResetReviewAsync()
|
private async System.Threading.Tasks.Task ResetReviewAsync()
|
||||||
{
|
{
|
||||||
if (Task is null || !_worker.IsConnected || Dialogs is null) return;
|
if (Task is null || !_worker.IsConnected || Dialogs is null) return;
|
||||||
var branchName = $"claudedo/{Task.Id.Replace("-", "")}";
|
var branchName = BranchLine ?? "the task branch";
|
||||||
var ok = await Dialogs.ConfirmAsync(
|
var ok = await Dialogs.ConfirmAsync(
|
||||||
$"Reset working tree?\nThis discards branch {branchName} (and all changes) and returns the task to Idle.");
|
$"Reset working tree?\nThis discards branch {branchName} (and all changes) and returns the task to Idle.");
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
|||||||
// harvests new work, longer handoff chain) when this AND the global switch in Settings →
|
// harvests new work, longer handoff chain) when this AND the global switch in Settings →
|
||||||
// Execution are both on. Off by default — the global switch alone must never change a list's behaviour.
|
// Execution are both on. Off by default — the global switch alone must never change a list's behaviour.
|
||||||
[ObservableProperty] private bool _powerUseAllowed;
|
[ObservableProperty] private bool _powerUseAllowed;
|
||||||
|
// Basis neuer Worktrees und Default-Merge-Ziel; leer = ausgecheckter Branch.
|
||||||
|
[ObservableProperty] private string _baseBranch = "";
|
||||||
// Dropdown hidden entirely when no ticket-system base URL is configured (Settings → Files tab).
|
// Dropdown hidden entirely when no ticket-system base URL is configured (Settings → Files tab).
|
||||||
// Icon key into the UI icon library (IconKeyConverter -> "Icon.<key>" StreamGeometry).
|
// Icon key into the UI icon library (IconKeyConverter -> "Icon.<key>" StreamGeometry).
|
||||||
[ObservableProperty] private string _icon = DefaultIcon;
|
[ObservableProperty] private string _icon = DefaultIcon;
|
||||||
@@ -275,6 +277,7 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
|||||||
cfg = Agent.LastLoadedListConfig;
|
cfg = Agent.LastLoadedListConfig;
|
||||||
VerifyCommand = cfg?.VerifyCommand ?? "";
|
VerifyCommand = cfg?.VerifyCommand ?? "";
|
||||||
PowerUseAllowed = cfg?.PowerUseAllowed ?? false;
|
PowerUseAllowed = cfg?.PowerUseAllowed ?? false;
|
||||||
|
BaseBranch = cfg?.BaseBranch ?? "";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -377,7 +380,9 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
|||||||
await Agent.SaveAsync(
|
await Agent.SaveAsync(
|
||||||
string.IsNullOrWhiteSpace(VerifyCommand) ? null : VerifyCommand,
|
string.IsNullOrWhiteSpace(VerifyCommand) ? null : VerifyCommand,
|
||||||
ticketProjectId,
|
ticketProjectId,
|
||||||
PowerUseAllowed);
|
PowerUseAllowed,
|
||||||
|
// Always explicit too: "" clears the stored base branch.
|
||||||
|
BaseBranch.Trim());
|
||||||
|
|
||||||
Saved = true;
|
Saved = true;
|
||||||
CloseAction?.Invoke();
|
CloseAction?.Invoke();
|
||||||
|
|||||||
@@ -124,6 +124,14 @@
|
|||||||
Foreground="{DynamicResource StatusErrorBrush}" FontSize="12" TextWrapping="Wrap"/>
|
Foreground="{DynamicResource StatusErrorBrush}" FontSize="12" TextWrapping="Wrap"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="4" IsVisible="{Binding !IsFolder}">
|
||||||
|
<TextBlock Classes="field-label" Text="{loc:Tr modals.listSettings.baseBranch}"/>
|
||||||
|
<TextBox Text="{Binding BaseBranch, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
PlaceholderText="{loc:Tr modals.listSettings.baseBranchPlaceholder}"/>
|
||||||
|
<TextBlock Text="{loc:Tr modals.listSettings.baseBranchHint}"
|
||||||
|
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="4" IsVisible="{Binding !IsFolder}">
|
<StackPanel Spacing="4" IsVisible="{Binding !IsFolder}">
|
||||||
<TextBlock Classes="field-label" Text="{loc:Tr modals.listSettings.defaultCommitType}"/>
|
<TextBlock Classes="field-label" Text="{loc:Tr modals.listSettings.defaultCommitType}"/>
|
||||||
<ComboBox ItemsSource="{Binding CommitTypeOptions}"
|
<ComboBox ItemsSource="{Binding CommitTypeOptions}"
|
||||||
|
|||||||
@@ -165,6 +165,23 @@ public class ListSettingsModalViewModelTests
|
|||||||
Assert.Equal(7, worker.SavedListConfig!.TicketProjectId);
|
Assert.Equal(7, worker.SavedListConfig!.TicketProjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Save_sends_BaseBranch_explicitly()
|
||||||
|
{
|
||||||
|
var worker = new CountingWorker { Cfg = new ListConfigDto("opus", null, null, BaseBranch: "dev") };
|
||||||
|
var vm = new ListSettingsModalViewModel(worker, NewFactory());
|
||||||
|
|
||||||
|
await vm.LoadAsync("l1", "List One", "/repo", "feat");
|
||||||
|
Assert.Equal("dev", vm.BaseBranch);
|
||||||
|
await vm.SaveCommand.ExecuteAsync(null);
|
||||||
|
Assert.Equal("dev", worker.SavedListConfig!.BaseBranch);
|
||||||
|
|
||||||
|
// "" (not null) so clearing the field really clears the stored branch.
|
||||||
|
vm.BaseBranch = " ";
|
||||||
|
await vm.SaveCommand.ExecuteAsync(null);
|
||||||
|
Assert.Equal("", worker.SavedListConfig!.BaseBranch);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LoadAsync_CoreLoadThrows_BlocksSaveAndDoesNotOverwriteConfig()
|
public async Task LoadAsync_CoreLoadThrows_BlocksSaveAndDoesNotOverwriteConfig()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user