# Inherited Settings Display, Overrides, and Turns — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make inherited config values visible in-place with a source-aware marker (`inherited · List` / `inherited · Global`), let users override them, and add **Max turns** as a list- and task-level overridable setting.

**Architecture:** Add a nullable `MaxTurns` override to `ListConfigEntity` and `TaskEntity` resolved task → list → global in `TaskRunner`. Thread it through repositories, SignalR DTOs, the SignalR hub, and the MCP config tools. In the UI, a pure resolution helper computes `(value, source)`; a small reusable badge control plus per-field reset buttons render the marker in both the List settings modal and the task overrides flyout.

**Tech Stack:** .NET 8, EF Core (SQLite), ASP.NET Core SignalR, Avalonia 12 + CommunityToolkit.Mvvm, xUnit. ModelContextProtocol MCP server.

**Build/test reminders (see CLAUDE.md):** build individual csproj with `-c Release` (a running Worker locks Debug). Subagents use the `sonnet` model and stage files explicitly by path — never `git add -A`. `locales/en.json` and `locales/de.json` keys must stay in parity. Changing `IWorkerClient` / DTOs breaks hand-rolled fakes — update `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`.

---

## File Structure

**Created:**
- `src/ClaudeDo.Data/Migrations/<timestamp>_InheritableMaxTurns.cs` (+ `.Designer.cs`, generated by `dotnet ef`)
- `src/ClaudeDo.Ui/Services/InheritanceResolver.cs` — pure helper: `(value, source)` resolution + `InheritSource` enum
- `src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml` (+ `.axaml.cs`) — label + source badge control
- `tests/ClaudeDo.Ui.Tests/InheritanceResolverTests.cs`
- `tests/ClaudeDo.Worker.Tests/Runner/MaxTurnsResolutionTests.cs` (or extend an existing runner-config test file)
- `tests/ClaudeDo.Data.Tests/MaxTurnsRoundTripTests.cs`

**Modified:**
- `src/ClaudeDo.Data/Models/ListConfigEntity.cs`, `TaskEntity.cs`
- `src/ClaudeDo.Data/Configuration/ListConfigEntityConfiguration.cs`, `TaskEntityConfiguration.cs`
- `src/ClaudeDo.Data/Repositories/ListRepository.cs`, `TaskRepository.cs`
- `src/ClaudeDo.Worker/Runner/TaskRunner.cs`
- `src/ClaudeDo.Worker/Hub/WorkerHub.cs`
- `src/ClaudeDo.Worker/External/ConfigMcpTools.cs`
- `src/ClaudeDo.Ui/Services/WorkerClient.cs`, `Services/Interfaces/IWorkerClient.cs`
- `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`
- `src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs`, `Views/Modals/ListSettingsModalView.axaml`
- `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs`, `Views/Islands/DetailsIslandView.axaml`
- `locales/en.json`, `locales/de.json`

---

## Task 1: Add `MaxTurns` to entities, EF config, and migration

**Files:**
- Modify: `src/ClaudeDo.Data/Models/ListConfigEntity.cs`
- Modify: `src/ClaudeDo.Data/Models/TaskEntity.cs`
- Modify: `src/ClaudeDo.Data/Configuration/ListConfigEntityConfiguration.cs`
- Modify: `src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs:83-85`
- Create: migration (generated)

- [ ] **Step 1: Add the property to `ListConfigEntity`**

In `src/ClaudeDo.Data/Models/ListConfigEntity.cs`, after the `AgentPath` line:

```csharp
public sealed class ListConfigEntity
{
    public required string ListId { get; init; }
    public string? Model { get; set; }
    public string? SystemPrompt { get; set; }
    public string? AgentPath { get; set; }
    public int? MaxTurns { get; set; }

    // Navigation property
    public ListEntity List { get; set; } = null!;
}
```

- [ ] **Step 2: Add the property to `TaskEntity`**

In `src/ClaudeDo.Data/Models/TaskEntity.cs`, beside the existing `Model` / `SystemPrompt` / `AgentPath` override properties, add:

```csharp
    public int? MaxTurns { get; set; }
```

- [ ] **Step 3: Map the columns in both entity configurations**

In `ListConfigEntityConfiguration.cs`, after the `AgentPath` mapping:

```csharp
        builder.Property(c => c.MaxTurns).HasColumnName("max_turns");
```

In `TaskEntityConfiguration.cs`, after line 85 (`AgentPath` mapping):

```csharp
        builder.Property(t => t.MaxTurns).HasColumnName("max_turns");
```

- [ ] **Step 4: Generate the migration**

Run:
```bash
dotnet ef migrations add InheritableMaxTurns --project src/ClaudeDo.Data --startup-project src/ClaudeDo.Worker
```
Expected: creates `src/ClaudeDo.Data/Migrations/<timestamp>_InheritableMaxTurns.cs` (+ Designer) and updates `ClaudeDoDbContextModelSnapshot.cs`. The `Up` method should `AddColumn<int>("max_turns", "list_config", nullable: true)` and `AddColumn<int>("max_turns", "tasks", nullable: true)`.

If the `dotnet ef` tool is unavailable, hand-author the migration mirroring `Migrations/20260603141020_DailyPrepMaxTasks.cs` but with `nullable: true` and no `defaultValue`, adding both columns, and add matching `Property<int?>("MaxTurns").HasColumnName("max_turns")` entries to the snapshot for the `ListConfigEntity` and `TaskEntity` builders.

- [ ] **Step 5: Verify the migration is sane**

Open the generated `_InheritableMaxTurns.cs` and confirm both `AddColumn` calls use `nullable: true` and there is **no** `defaultValue` (null = inherit). Confirm `Down` drops both columns.

- [ ] **Step 6: Build Data**

Run: `dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj -c Release`
Expected: build succeeds.

- [ ] **Step 7: Commit**

```bash
git add src/ClaudeDo.Data/Models/ListConfigEntity.cs src/ClaudeDo.Data/Models/TaskEntity.cs src/ClaudeDo.Data/Configuration/ListConfigEntityConfiguration.cs src/ClaudeDo.Data/Configuration/TaskEntityConfiguration.cs src/ClaudeDo.Data/Migrations/
git commit -m "feat(data): add nullable max_turns override to list_config and tasks"
```

---

## Task 2: Repositories persist `MaxTurns`

**Files:**
- Modify: `src/ClaudeDo.Data/Repositories/ListRepository.cs:56-70`
- Modify: `src/ClaudeDo.Data/Repositories/TaskRepository.cs:157-170`
- Test: `tests/ClaudeDo.Data.Tests/MaxTurnsRoundTripTests.cs`

- [ ] **Step 1: Write the failing test**

Create `tests/ClaudeDo.Data.Tests/MaxTurnsRoundTripTests.cs`. Use the same in-memory/temp-SQLite context setup as a neighboring Data.Tests file (open one, e.g. `ListRepositoryTests.cs`, and copy its context-creation helper exactly — it builds a `ClaudeDoDbContext` against a temp SQLite file and calls `Database.Migrate()` or `EnsureCreated()`).

```csharp
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using Xunit;

namespace ClaudeDo.Data.Tests;

public class MaxTurnsRoundTripTests
{
    [Fact]
    public async Task ListConfig_persists_max_turns()
    {
        await using var ctx = TestDb.NewContext(); // use the helper pattern from existing Data.Tests
        var lists = new ListRepository(ctx);
        await ctx.Lists.AddAsync(new ListEntity { Id = "L1", Name = "L1" });
        await ctx.SaveChangesAsync();

        await lists.SetConfigAsync(new ListConfigEntity { ListId = "L1", MaxTurns = 42 });

        var cfg = await lists.GetConfigAsync("L1");
        Assert.Equal(42, cfg!.MaxTurns);
    }

    [Fact]
    public async Task Task_agent_settings_persist_and_clear_max_turns()
    {
        await using var ctx = TestDb.NewContext();
        var tasks = new TaskRepository(ctx);
        await ctx.Tasks.AddAsync(new TaskEntity { Id = "T1", ListId = "L1", Title = "t" });
        await ctx.SaveChangesAsync();

        await tasks.UpdateAgentSettingsAsync("T1", model: null, systemPrompt: null, agentPath: null, maxTurns: 7);
        var t = await tasks.GetByIdAsync("T1");
        Assert.Equal(7, t!.MaxTurns);

        await tasks.UpdateAgentSettingsAsync("T1", null, null, null, maxTurns: null);
        var cleared = await tasks.GetByIdAsync("T1");
        Assert.Null(cleared!.MaxTurns);
    }
}
```

> NOTE: replace `TestDb.NewContext()` with whatever the existing Data.Tests use (they may have a fixture or a static helper). Match the existing pattern exactly; do not invent a new harness. `ListEntity` requires only `Id` + `Name` (other fields have defaults) — verify against the existing tests.

- [ ] **Step 2: Run the test to verify it fails**

Run: `dotnet test tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj -c Release --filter MaxTurnsRoundTripTests`
Expected: FAIL — `UpdateAgentSettingsAsync` has no `maxTurns` parameter (compile error), and `SetConfigAsync` does not copy `MaxTurns` onto existing rows.

- [ ] **Step 3: Update `ListRepository.SetConfigAsync`**

In `src/ClaudeDo.Data/Repositories/ListRepository.cs`, in the `else` branch that updates an existing row (after `existing.AgentPath = config.AgentPath;`):

```csharp
            existing.MaxTurns = config.MaxTurns;
```

(The `Add` branch already stores the full entity, so no change there.)

- [ ] **Step 4: Update `TaskRepository.UpdateAgentSettingsAsync`**

Replace the method body in `src/ClaudeDo.Data/Repositories/TaskRepository.cs:157-170` with:

```csharp
    public async Task UpdateAgentSettingsAsync(
        string taskId,
        string? model,
        string? systemPrompt,
        string? agentPath,
        int? maxTurns = null,
        CancellationToken ct = default)
    {
        await _context.Tasks
            .Where(t => t.Id == taskId)
            .ExecuteUpdateAsync(s => s
                .SetProperty(t => t.Model, model)
                .SetProperty(t => t.SystemPrompt, systemPrompt)
                .SetProperty(t => t.AgentPath, agentPath)
                .SetProperty(t => t.MaxTurns, maxTurns), ct);
    }
```

> `maxTurns` is placed before `ct` with a default so existing 4-arg callers still compile; we update those callers explicitly in Task 4.

- [ ] **Step 5: Run the test to verify it passes**

Run: `dotnet test tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj -c Release --filter MaxTurnsRoundTripTests`
Expected: PASS (2 tests).

- [ ] **Step 6: Commit**

```bash
git add src/ClaudeDo.Data/Repositories/ListRepository.cs src/ClaudeDo.Data/Repositories/TaskRepository.cs tests/ClaudeDo.Data.Tests/MaxTurnsRoundTripTests.cs
git commit -m "feat(data): persist max_turns in list and task repositories"
```

---

## Task 3: Runner resolves `MaxTurns` task → list → global

**Files:**
- Modify: `src/ClaudeDo.Worker/Runner/TaskRunner.cs:388-394`
- Test: `tests/ClaudeDo.Worker.Tests/Runner/MaxTurnsResolutionTests.cs`

> Context: `BuildRunConfig` currently returns `MaxTurns: global.DefaultMaxTurns`. `ClaudeRunConfig.MaxTurns` is `int?` and `ClaudeArgsBuilder` already emits `--max-turns` when `> 0`. Only the resolution line changes.

- [ ] **Step 1: Inspect the method signature**

Open `src/ClaudeDo.Worker/Runner/TaskRunner.cs` around line 360-395 and confirm the names of the in-scope variables: the task entity (`task`), the list config (`listConfig`), and the app settings (`global`). If the config-building logic is not directly unit-testable (private method, lots of dependencies), extract a tiny pure static helper for just the MaxTurns precedence and test that:

```csharp
    internal static int? ResolveMaxTurns(int? taskTurns, int? listTurns, int globalDefault)
        => taskTurns ?? listTurns ?? globalDefault;
```

Place it in `TaskRunner` as an `internal static` method.

- [ ] **Step 2: Write the failing test**

Create `tests/ClaudeDo.Worker.Tests/Runner/MaxTurnsResolutionTests.cs`:

```csharp
using ClaudeDo.Worker.Runner;
using Xunit;

namespace ClaudeDo.Worker.Tests.Runner;

public class MaxTurnsResolutionTests
{
    [Fact]
    public void Task_override_wins()
        => Assert.Equal(5, TaskRunner.ResolveMaxTurns(taskTurns: 5, listTurns: 20, globalDefault: 100));

    [Fact]
    public void List_override_used_when_no_task_override()
        => Assert.Equal(20, TaskRunner.ResolveMaxTurns(taskTurns: null, listTurns: 20, globalDefault: 100));

    [Fact]
    public void Falls_back_to_global_default()
        => Assert.Equal(100, TaskRunner.ResolveMaxTurns(taskTurns: null, listTurns: null, globalDefault: 100));
}
```

- [ ] **Step 3: Run the test to verify it fails**

Run: `dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release --filter MaxTurnsResolutionTests`
Expected: FAIL — `ResolveMaxTurns` not defined.

- [ ] **Step 4: Add the helper and use it in `BuildRunConfig`**

Add the `ResolveMaxTurns` helper from Step 1 to `TaskRunner`. Then change the `MaxTurns:` argument in the `ClaudeRunConfig` construction (currently `MaxTurns: global.DefaultMaxTurns,`) to:

```csharp
            MaxTurns: ResolveMaxTurns(task.MaxTurns, listConfig?.MaxTurns, global.DefaultMaxTurns),
```

- [ ] **Step 5: Run the test to verify it passes**

Run: `dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release --filter MaxTurnsResolutionTests`
Expected: PASS (3 tests).

- [ ] **Step 6: Build Worker**

Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release`
Expected: build succeeds.

- [ ] **Step 7: Commit**

```bash
git add src/ClaudeDo.Worker/Runner/TaskRunner.cs tests/ClaudeDo.Worker.Tests/Runner/MaxTurnsResolutionTests.cs
git commit -m "feat(worker): resolve max-turns from task then list then global default"
```

---

## Task 4: Thread `MaxTurns` through Worker transport (DTOs, Hub, MCP)

**Files:**
- Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs:58-60, 334-368, 406-417`
- Modify: `src/ClaudeDo.Worker/External/ConfigMcpTools.cs`

- [ ] **Step 1: Extend the Worker-side DTO records**

In `src/ClaudeDo.Worker/Hub/WorkerHub.cs` (lines 58-60), add a trailing nullable `int?` to each record (defaulted so existing positional usages elsewhere keep compiling):

```csharp
public record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
public record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
public record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
```

- [ ] **Step 2: Persist `MaxTurns` in `UpdateListConfig`**

In `WorkerHub.UpdateListConfig` (lines 334-359): include MaxTurns in the "is everything empty?" delete check and in the `SetConfigAsync` call.

```csharp
    public async Task UpdateListConfig(UpdateListConfigDto dto)
    {
        using var ctx = _dbFactory.CreateDbContext();
        var repo = new ListRepository(ctx);

        var model = dto.Model.NullIfBlank();
        var systemPrompt = dto.SystemPrompt.NullIfBlank();
        var agentPath = dto.AgentPath.NullIfBlank();

        if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null)
        {
            await repo.DeleteConfigAsync(dto.ListId);
        }
        else
        {
            await repo.SetConfigAsync(new ListConfigEntity
            {
                ListId = dto.ListId,
                Model = model,
                SystemPrompt = systemPrompt,
                AgentPath = agentPath,
                MaxTurns = dto.MaxTurns,
            });
        }

        await _broadcaster.ListUpdated(dto.ListId);
    }
```

- [ ] **Step 3: Return `MaxTurns` from `GetListConfig`**

In `WorkerHub.GetListConfig` (line 367), change the return to include MaxTurns:

```csharp
        return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns);
```

- [ ] **Step 4: Persist `MaxTurns` in `UpdateTaskAgentSettings`**

In `WorkerHub.UpdateTaskAgentSettings` (lines 406-417), pass `dto.MaxTurns` to the repository (note the new `maxTurns` parameter sits before `ct`):

```csharp
        await repo.UpdateAgentSettingsAsync(
            dto.TaskId,
            dto.Model.NullIfBlank(),
            dto.SystemPrompt.NullIfBlank(),
            dto.AgentPath.NullIfBlank(),
            dto.MaxTurns);
```

- [ ] **Step 5: Extend the MCP config tools**

In `src/ClaudeDo.Worker/External/ConfigMcpTools.cs`:

Update the DTO (line 9):
```csharp
public sealed record TaskConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns);
```

Update `GetListConfig` (line 29):
```csharp
        return cfg is null ? null : new TaskConfigDto(cfg.Model, cfg.SystemPrompt, cfg.AgentPath, cfg.MaxTurns);
```

Update `SetListConfig` signature + body — add an `int? maxTurns` parameter (before `CancellationToken`), include it in the clear-check and the upsert:
```csharp
    [McpServerTool, Description("Set a list's default model/system prompt/agent path/max turns. Passing model, systemPrompt, agentPath, and maxTurns all null clears the list config.")]
    public async Task SetListConfig(
        string listId, string? model, string? systemPrompt, string? agentPath, int? maxTurns, CancellationToken cancellationToken)
    {
        _ = await _lists.GetByIdAsync(listId, cancellationToken)
            ?? throw new InvalidOperationException($"List {listId} not found.");

        var m = model.NullIfBlank();
        var sp = systemPrompt.NullIfBlank();
        var ap = agentPath.NullIfBlank();

        if (m is null && sp is null && ap is null && maxTurns is null)
            await _lists.DeleteConfigAsync(listId, cancellationToken);
        else
            await _lists.SetConfigAsync(new ListConfigEntity
            {
                ListId = listId, Model = m, SystemPrompt = sp, AgentPath = ap, MaxTurns = maxTurns,
            }, cancellationToken);

        await _broadcaster.ListUpdated(listId);
    }
```

Update `SetTaskConfig` signature + body — add `int? maxTurns` (before `CancellationToken`) and pass it through:
```csharp
    [McpServerTool, Description("Set per-task config overrides (model/system prompt/agent path/max turns). Pass null for any field to clear that override.")]
    public async Task SetTaskConfig(
        string taskId, string? model, string? systemPrompt, string? agentPath, int? maxTurns, CancellationToken cancellationToken)
    {
        _ = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");

        await _tasks.UpdateAgentSettingsAsync(taskId, model.NullIfBlank(), systemPrompt.NullIfBlank(), agentPath.NullIfBlank(), maxTurns, cancellationToken);
        await _broadcaster.TaskUpdated(taskId);
    }
```

- [ ] **Step 6: Build Worker**

Run: `dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release`
Expected: build succeeds. (If any other caller constructed these DTOs positionally and now mismatches, fix it — the new params are defaulted, so this should be clean.)

- [ ] **Step 7: Commit**

```bash
git add src/ClaudeDo.Worker/Hub/WorkerHub.cs src/ClaudeDo.Worker/External/ConfigMcpTools.cs
git commit -m "feat(worker): expose max-turns override over signalr and mcp config tools"
```

---

## Task 5: Mirror DTO changes in the UI transport layer

**Files:**
- Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs:524-526`
- Modify: `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs` (only if a signature/record change breaks it)

> The UI keeps its own copies of `UpdateListConfigDto`, `UpdateTaskAgentSettingsDto`, `ListConfigDto`. They must mirror the Worker exactly or SignalR (de)serialization drops the field. `IWorkerClient` method signatures don't change (they take whole DTOs), so the interface and stub need no new members — but verify the stub still compiles.

- [ ] **Step 1: Extend the UI DTO records**

In `src/ClaudeDo.Ui/Services/WorkerClient.cs` (lines 524-526):

```csharp
public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
public sealed record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
```

- [ ] **Step 2: Build the UI project**

Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
Expected: build succeeds (this pulls in Ui + Data).

- [ ] **Step 3: Build the UI test project**

Run: `dotnet build tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release`
Expected: build succeeds. `StubWorkerClient` returns `null` for `GetListConfigAsync` and ignores `UpdateTaskAgentSettingsAsync(dto)`, so the defaulted record params keep it compiling. If it breaks, fix only the affected line.

- [ ] **Step 4: Commit**

```bash
git add src/ClaudeDo.Ui/Services/WorkerClient.cs
git commit -m "feat(ui): mirror max-turns field on signalr config dtos"
```

---

## Task 6: Localization keys (en + de parity)

**Files:**
- Modify: `locales/en.json`
- Modify: `locales/de.json`

> Localization.Tests enforces key parity. Find the right nesting by searching for the existing sibling keys (`modals.listSettings.model`, `details.modelLabel`, `vm.details.effectiveIfInherited`).

- [ ] **Step 1: Add the new keys to `locales/en.json`**

Add (place each next to its siblings; values shown):
- under `modals.listSettings`: `"maxTurns": "Max turns"`
- under `details`: `"maxTurnsLabel": "Max turns"`, `"systemPromptPrepended": "Prepended automatically:"`
- a new/extended `settings.inherit` group (or wherever shared UI strings live — match existing convention):
  - `"inheritedFromList": "inherited · List"`
  - `"inheritedFromGlobal": "inherited · Global"`
  - `"overrideBadge": "override"`
  - `"resetToInherited": "Reset to inherited"`

- [ ] **Step 2: Add the same keys to `locales/de.json`**

Same key paths, German values:
- `modals.listSettings.maxTurns`: `"Max. Turns"`
- `details.maxTurnsLabel`: `"Max. Turns"`
- `details.systemPromptPrepended`: `"Wird automatisch vorangestellt:"`
- `settings.inherit.inheritedFromList`: `"geerbt · Liste"`
- `settings.inherit.inheritedFromGlobal`: `"geerbt · Global"`
- `settings.inherit.overrideBadge`: `"überschrieben"`
- `settings.inherit.resetToInherited`: `"Auf geerbt zurücksetzen"`

- [ ] **Step 3: Remove the now-unused `effectiveIfInherited` keys**

Remove `vm.details.effectiveIfInherited` from BOTH `en.json` and `de.json` (it is replaced by the badge approach in Tasks 8-9; this plan deletes its only usages there). Removing from both keeps parity.

- [ ] **Step 4: Run the localization tests**

Run: `dotnet test tests/ClaudeDo.Localization.Tests/ClaudeDo.Localization.Tests.csproj -c Release`
Expected: PASS (en/de key parity holds).

- [ ] **Step 5: Commit**

```bash
git add locales/en.json locales/de.json
git commit -m "feat(i18n): add inherited-marker, turns, and prepended-prompt strings"
```

---

## Task 7: `InheritanceResolver` helper (pure, unit-tested)

**Files:**
- Create: `src/ClaudeDo.Ui/Services/InheritanceResolver.cs`
- Test: `tests/ClaudeDo.Ui.Tests/InheritanceResolverTests.cs`

- [ ] **Step 1: Write the failing test**

Create `tests/ClaudeDo.Ui.Tests/InheritanceResolverTests.cs`:

```csharp
using ClaudeDo.Ui.Services;
using Xunit;

namespace ClaudeDo.Ui.Tests;

public class InheritanceResolverTests
{
    [Fact]
    public void Task_value_is_an_override()
    {
        var (value, source) = InheritanceResolver.Resolve("opus", "sonnet", "haiku");
        Assert.Equal("opus", value);
        Assert.Equal(InheritSource.Override, source);
    }

    [Fact]
    public void Falls_through_to_list()
    {
        var (value, source) = InheritanceResolver.Resolve(null, "sonnet", "haiku");
        Assert.Equal("sonnet", value);
        Assert.Equal(InheritSource.List, source);
    }

    [Fact]
    public void Falls_through_to_global()
    {
        var (value, source) = InheritanceResolver.Resolve("  ", null, "haiku");
        Assert.Equal("haiku", value);
        Assert.Equal(InheritSource.Global, source);
    }

    [Fact]
    public void List_scope_treats_list_value_as_override()
    {
        var (value, source) = InheritanceResolver.ResolveList("sonnet", "haiku");
        Assert.Equal("sonnet", value);
        Assert.Equal(InheritSource.Override, source);

        var (value2, source2) = InheritanceResolver.ResolveList(null, "haiku");
        Assert.Equal("haiku", value2);
        Assert.Equal(InheritSource.Global, source2);
    }
}
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter InheritanceResolverTests`
Expected: FAIL — type does not exist.

- [ ] **Step 3: Implement the helper**

Create `src/ClaudeDo.Ui/Services/InheritanceResolver.cs`:

```csharp
namespace ClaudeDo.Ui.Services;

public enum InheritSource { Override, List, Global }

public static class InheritanceResolver
{
    // Task-scope fields: task -> list -> global.
    public static (string Value, InheritSource Source) Resolve(
        string? taskValue, string? listValue, string? globalValue)
    {
        if (!string.IsNullOrWhiteSpace(taskValue)) return (taskValue!, InheritSource.Override);
        if (!string.IsNullOrWhiteSpace(listValue)) return (listValue!, InheritSource.List);
        return (globalValue ?? "", InheritSource.Global);
    }

    // List-scope fields: list -> global (lists have no tier above them).
    public static (string Value, InheritSource Source) ResolveList(
        string? listValue, string? globalValue)
    {
        if (!string.IsNullOrWhiteSpace(listValue)) return (listValue!, InheritSource.Override);
        return (globalValue ?? "", InheritSource.Global);
    }
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter InheritanceResolverTests`
Expected: PASS (4 tests).

- [ ] **Step 5: Commit**

```bash
git add src/ClaudeDo.Ui/Services/InheritanceResolver.cs tests/ClaudeDo.Ui.Tests/InheritanceResolverTests.cs
git commit -m "feat(ui): add inheritance resolver returning value and source"
```

---

## Task 8: `InheritedBadge` reusable control

**Files:**
- Create: `src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml`
- Create: `src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml.cs`

> A tiny `UserControl` rendering a single badge whose text comes from a bound string. The consuming VM supplies the already-localized badge text (e.g. "inherited · List" / "override") and a bool for muted styling. Keeping localization in the VM avoids `Loc` lookups inside the control. No `ICommand` lives in the control — reset buttons are placed by each consumer (Tasks 9-10).

- [ ] **Step 1: Create the control XAML**

`src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml`:

```xml
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="ClaudeDo.Ui.Views.Controls.InheritedBadge"
             x:Name="Root">
  <Border Background="{DynamicResource SubtleFillBrush}"
          CornerRadius="4" Padding="6,1"
          IsVisible="{Binding #Root.BadgeText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
          VerticalAlignment="Center" HorizontalAlignment="Left">
    <TextBlock Classes="meta" FontSize="11" Opacity="0.75"
               Text="{Binding #Root.BadgeText}"/>
  </Border>
</UserControl>
```

> If `SubtleFillBrush` is not a defined token, use an existing subtle background brush from `Design/Tokens.axaml` (open it and pick the closest — e.g. a hover/overlay brush). The user will do the final visual pass.

- [ ] **Step 2: Create the code-behind with a `BadgeText` StyledProperty**

`src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml.cs`:

```csharp
using Avalonia;
using Avalonia.Controls;

namespace ClaudeDo.Ui.Views.Controls;

public partial class InheritedBadge : UserControl
{
    public static readonly StyledProperty<string?> BadgeTextProperty =
        AvaloniaProperty.Register<InheritedBadge, string?>(nameof(BadgeText));

    public string? BadgeText
    {
        get => GetValue(BadgeTextProperty);
        set => SetValue(BadgeTextProperty, value);
    }

    public InheritedBadge() => InitializeComponent();
}
```

- [ ] **Step 3: Build the UI project**

Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
Expected: build succeeds (control compiles, even though nothing consumes it yet).

- [ ] **Step 4: Commit**

```bash
git add src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml src/ClaudeDo.Ui/Views/Controls/InheritedBadge.axaml.cs
git commit -m "feat(ui): add reusable inherited-source badge control"
```

---

## Task 9: List settings modal — Turns field + inherited markers

**Files:**
- Modify: `src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs`
- Modify: `src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml`

> Lists inherit only from Global. The ViewModel loads the global defaults (via `GetAppSettingsAsync`) to render the muted resolved value + `inherited · Global` badge. For Model we drop the `(default)` sentinel row and use `SelectedItem == null` to mean "inherited", with `PlaceholderText` showing the resolved value. Turns uses a `NumericUpDown` whose `Value` is `decimal?` (null = inherit) with a `Watermark` showing the global default.

- [ ] **Step 1: Add fields + load logic to the ViewModel**

In `ListSettingsModalViewModel.cs`:

1. Add a `using ClaudeDo.Ui.Services;` if not present (for `InheritanceResolver` / `InheritSource`).
2. Change the Model collection to **not** include the sentinel:
```csharp
    public ObservableCollection<string> ModelOptions { get; } = new(ModelRegistry.Aliases);
```
3. Change `SelectedModel` to nullable (null = inherit) and add the badge + Turns properties:
```csharp
    [ObservableProperty] private string? _selectedModel;          // null = inherit from global
    [ObservableProperty] private decimal? _maxTurns;              // null = inherit from global
    [ObservableProperty] private string _modelInheritedHint = ""; // muted resolved value, e.g. "sonnet"
    [ObservableProperty] private string _modelBadge = "";         // localized badge text
    [ObservableProperty] private string _turnsInheritedHint = ""; // muted resolved value, e.g. "100"
    [ObservableProperty] private string _turnsBadge = "";
    [ObservableProperty] private string _agentBadge = "";
    [ObservableProperty] private string _agentInheritedHint = "";
```
4. Add badge recompute helpers and wire them to property changes:
```csharp
    partial void OnSelectedModelChanged(string? value) => RecomputeModelBadge();
    partial void OnMaxTurnsChanged(decimal? value) => RecomputeTurnsBadge();
    partial void OnSelectedAgentChanged(AgentInfo? value) => RecomputeAgentBadge();

    private string _globalModel = ModelRegistry.DefaultAlias;
    private int _globalMaxTurns = 100;

    private void RecomputeModelBadge()
    {
        var overridden = !string.IsNullOrWhiteSpace(SelectedModel);
        ModelInheritedHint = _globalModel;
        ModelBadge = overridden ? Loc.T("settings.inherit.overrideBadge") : Loc.T("settings.inherit.inheritedFromGlobal");
    }

    private void RecomputeTurnsBadge()
    {
        var overridden = MaxTurns is not null;
        TurnsInheritedHint = _globalMaxTurns.ToString();
        TurnsBadge = overridden ? Loc.T("settings.inherit.overrideBadge") : Loc.T("settings.inherit.inheritedFromGlobal");
    }

    private void RecomputeAgentBadge()
    {
        var overridden = SelectedAgent is not null && !string.IsNullOrWhiteSpace(SelectedAgent.Path);
        // Agent has no global default; inherited means "(none)".
        AgentInheritedHint = "(none)";
        AgentBadge = overridden ? Loc.T("settings.inherit.overrideBadge") : Loc.T("settings.inherit.inheritedFromGlobal");
    }
```
5. In `LoadAsync`, load globals and seed the new fields. After the `GetListConfigAsync` block, replace the Model/Agent seeding with the null-sentinel approach and add MaxTurns + globals:
```csharp
        var app = await _worker.GetAppSettingsAsync();
        _globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
        _globalMaxTurns = app?.DefaultMaxTurns ?? 100;

        SelectedModel = string.IsNullOrWhiteSpace(config?.Model) ? null : config!.Model!;
        MaxTurns = config?.MaxTurns is int mt ? mt : (decimal?)null;
        SystemPrompt = config?.SystemPrompt ?? "";
        SelectedAgent = string.IsNullOrWhiteSpace(config?.AgentPath)
            ? Agents[0]
            : (Agents.FirstOrDefault(a => a.Path == config!.AgentPath) ?? Agents[0]);

        RecomputeModelBadge();
        RecomputeTurnsBadge();
        RecomputeAgentBadge();
```
   (Remove the old line that set `SelectedModel = ... ListDefaultSentinel ...`.)

6. In `SaveAsync`, compute the override values and include MaxTurns:
```csharp
        var model = string.IsNullOrWhiteSpace(SelectedModel) ? null : SelectedModel;
        var sp = string.IsNullOrWhiteSpace(SystemPrompt) ? null : SystemPrompt;
        var ap = SelectedAgent is null || string.IsNullOrWhiteSpace(SelectedAgent.Path) ? null : SelectedAgent.Path;
        var turns = MaxTurns is decimal d ? (int?)d : null;

        await _worker.UpdateListAsync(new UpdateListDto(
            ListId,
            string.IsNullOrWhiteSpace(Name) ? Loc.T("vm.listSettings.untitled") : Name,
            string.IsNullOrWhiteSpace(WorkingDir) ? null : WorkingDir,
            DefaultCommitType));

        await _worker.UpdateListConfigAsync(new UpdateListConfigDto(
            ListId, model, sp, ap, turns));
```

7. Add reset commands and update `ResetAgentSettings`:
```csharp
    [RelayCommand] private void ResetModel() => SelectedModel = null;
    [RelayCommand] private void ResetTurns() => MaxTurns = null;
    [RelayCommand] private void ResetAgent() => SelectedAgent = Agents.Count > 0 ? Agents[0] : null;
```
   And in the existing `ResetAgentSettings`, replace `SelectedModel = ModelRegistry.ListDefaultSentinel;` with `SelectedModel = null;` and add `MaxTurns = null;`.

- [ ] **Step 2: Update the modal XAML — Model field**

In `ListSettingsModalView.axaml`, add `xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"` to the root if not already present (it is). Replace the Model `StackPanel` (lines ~76-81) with a label-row that carries the badge + reset, and a ComboBox using null-selection + placeholder:

```xml
              <StackPanel Spacing="4">
                <Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="6">
                  <TextBlock Grid.Column="0" Classes="field-label" Text="{loc:Tr modals.listSettings.model}" VerticalAlignment="Center"/>
                  <ctl:InheritedBadge Grid.Column="1" BadgeText="{Binding ModelBadge}"/>
                  <Button Grid.Column="3" Classes="btn" Content="↺" ToolTip.Tip="{loc:Tr settings.inherit.resetToInherited}"
                          Command="{Binding ResetModelCommand}" Padding="6,1"/>
                </Grid>
                <ComboBox ItemsSource="{Binding ModelOptions}"
                          SelectedItem="{Binding SelectedModel, Mode=TwoWay}"
                          PlaceholderText="{Binding ModelInheritedHint}"
                          HorizontalAlignment="Left" MinWidth="160" />
              </StackPanel>
```

- [ ] **Step 3: Update the modal XAML — add the Turns field**

Add a new `StackPanel` inside the AGENT section's inner `StackPanel` (after the Model field, before System prompt):

```xml
              <StackPanel Spacing="4">
                <Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="6">
                  <TextBlock Grid.Column="0" Classes="field-label" Text="{loc:Tr modals.listSettings.maxTurns}" VerticalAlignment="Center"/>
                  <ctl:InheritedBadge Grid.Column="1" BadgeText="{Binding TurnsBadge}"/>
                  <Button Grid.Column="3" Classes="btn" Content="↺" ToolTip.Tip="{loc:Tr settings.inherit.resetToInherited}"
                          Command="{Binding ResetTurnsCommand}" Padding="6,1"/>
                </Grid>
                <NumericUpDown Value="{Binding MaxTurns, Mode=TwoWay}"
                               Watermark="{Binding TurnsInheritedHint}"
                               Minimum="1" Maximum="200" Increment="1" FormatString="0"
                               HorizontalAlignment="Left" Width="160"/>
              </StackPanel>
```

- [ ] **Step 4: Update the modal XAML — Agent field badge + reset**

Wrap the existing Agent `field-label` (line ~91) in the same label-row pattern so it shows `AgentBadge` and a reset button bound to `ResetAgentCommand`. Keep the existing ComboBox + browse button + path TextBlock unchanged below it.

- [ ] **Step 5: Build the UI project**

Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
Expected: build succeeds. Fix any binding/compile errors (compiled bindings require the VM properties to exist — they were added in Step 1).

- [ ] **Step 6: Commit**

```bash
git add src/ClaudeDo.Ui/ViewModels/Modals/ListSettingsModalViewModel.cs src/ClaudeDo.Ui/Views/Modals/ListSettingsModalView.axaml
git commit -m "feat(ui): show inherited markers and max-turns override in list settings"
```

---

## Task 10: Task overrides flyout — Turns field + inherited markers

**Files:**
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs`
- Modify: `src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml:90-122`

> Task scope falls through task → list → global. Model resolves to global default; Agent has no global tier (inherited = list value or "(none)"); Turns resolves to list value or global default. Reuse `InheritanceResolver.Resolve`. Drop the `(inherit)` sentinel from the Model dropdown and the Agent dropdown's first row; null selection = inherited.

- [ ] **Step 1: Replace the effective-hint fields with badge fields in the ViewModel**

In `DetailsIslandViewModel.cs`:

1. Remove `EffectiveModelLabel` / `EffectiveAgentLabel` computed props and the `OnEffective*HintChanged` partials (lines ~155-159), and the `OnPropertyChanged(nameof(EffectiveModelLabel))` calls at lines ~141-142 and ~291-292.
2. Change the Model options to drop the sentinel:
```csharp
    public System.Collections.ObjectModel.ObservableCollection<string> TaskModelOptions { get; } = new(ModelRegistry.Aliases);
```
3. Change `TaskModelSelection` to nullable and add new observable fields:
```csharp
    [ObservableProperty] private string? _taskModelSelection;     // null = inherit
    [ObservableProperty] private decimal? _taskMaxTurns;          // null = inherit
    [ObservableProperty] private string _modelBadge = "";
    [ObservableProperty] private string _modelInheritedHint = "";
    [ObservableProperty] private string _turnsBadge = "";
    [ObservableProperty] private string _turnsInheritedHint = "";
    [ObservableProperty] private string _agentBadge = "";
    // keep existing _taskSystemPrompt; EffectiveSystemPromptHint stays as the prepended-prompt hint
```
   Keep `EffectiveSystemPromptHint` (used as the read-only "prepended automatically" hint). Remove `EffectiveModelHint` and `EffectiveAgentHint` if nothing else references them after this task.
4. Track globals and recompute badges:
```csharp
    private string _globalModel = ModelRegistry.DefaultAlias;
    private int _globalMaxTurns = 100;

    partial void OnTaskModelSelectionChanged(string? value) => RecomputeModelBadge();
    partial void OnTaskMaxTurnsChanged(decimal? value) => RecomputeTurnsBadge();
    partial void OnTaskSelectedAgentChanged(AgentInfo? value) => RecomputeAgentBadge();

    private string? _listModel, _listAgentName;
    private int? _listMaxTurns;

    private void RecomputeModelBadge()
    {
        var (value, source) = InheritanceResolver.Resolve(TaskModelSelection, _listModel, _globalModel);
        ModelInheritedHint = value;
        ModelBadge = BadgeFor(source, taskSet: !string.IsNullOrWhiteSpace(TaskModelSelection));
    }

    private void RecomputeTurnsBadge()
    {
        var (value, source) = InheritanceResolver.Resolve(
            TaskMaxTurns?.ToString(), _listMaxTurns?.ToString(), _globalMaxTurns.ToString());
        TurnsInheritedHint = value;
        TurnsBadge = BadgeFor(source, taskSet: TaskMaxTurns is not null);
    }

    private void RecomputeAgentBadge()
    {
        var taskSet = TaskSelectedAgent is not null && !string.IsNullOrWhiteSpace(TaskSelectedAgent.Path);
        var (_, source) = InheritanceResolver.Resolve(
            taskSet ? TaskSelectedAgent!.Path : null, _listAgentName, null);
        AgentBadge = BadgeFor(source, taskSet);
    }

    private static string BadgeFor(InheritSource source, bool taskSet) => taskSet
        ? Loc.T("settings.inherit.overrideBadge")
        : source == InheritSource.List
            ? Loc.T("settings.inherit.inheritedFromList")
            : Loc.T("settings.inherit.inheritedFromGlobal");
```

- [ ] **Step 2: Update `LoadAgentSettingsAsync` to seed the new fields**

Replace the seeding block (lines ~504-514) with:

```csharp
            TaskModelSelection = string.IsNullOrWhiteSpace(entity.Model) ? null : entity.Model!;
            TaskMaxTurns = entity.MaxTurns is int tmt ? tmt : (decimal?)null;
            TaskSystemPrompt = entity.SystemPrompt ?? "";
            TaskSelectedAgent = string.IsNullOrWhiteSpace(entity.AgentPath)
                ? TaskAgentOptions[0]
                : (TaskAgentOptions.FirstOrDefault(a => a.Path == entity.AgentPath) ?? TaskAgentOptions[0]);

            var listCfg = await _worker.GetListConfigAsync(entity.ListId);
            var app = await _worker.GetAppSettingsAsync();
            _globalModel = app?.DefaultModel ?? ModelRegistry.DefaultAlias;
            _globalMaxTurns = app?.DefaultMaxTurns ?? 100;
            _listModel = listCfg?.Model;
            _listMaxTurns = listCfg?.MaxTurns;
            _listAgentName = string.IsNullOrWhiteSpace(listCfg?.AgentPath)
                ? null : System.IO.Path.GetFileName(listCfg!.AgentPath!);

            EffectiveSystemPromptHint = string.IsNullOrWhiteSpace(listCfg?.SystemPrompt) ? "" : listCfg!.SystemPrompt!;

            RecomputeModelBadge();
            RecomputeTurnsBadge();
            RecomputeAgentBadge();
```

   Also drop the `(inherit)` sentinel row added to `TaskAgentOptions` (line ~500): replace
   `TaskAgentOptions.Add(new AgentInfo(ModelRegistry.TaskInheritSentinel, "", ""));`
   with a "(none)"/inherit placeholder row (path empty) so `TaskAgentOptions[0]` still means "inherited":
   `TaskAgentOptions.Add(new AgentInfo("(inherited)", "", ""));`

- [ ] **Step 3: Update the save + clear paths**

In the agent-save method (line ~481), include MaxTurns:
```csharp
            var model = string.IsNullOrWhiteSpace(TaskModelSelection) ? null : TaskModelSelection;
            var sp = string.IsNullOrWhiteSpace(TaskSystemPrompt) ? null : TaskSystemPrompt;
            var ap = TaskSelectedAgent is null || string.IsNullOrWhiteSpace(TaskSelectedAgent.Path)
                ? null : TaskSelectedAgent.Path;
            var turns = TaskMaxTurns is decimal d ? (int?)d : null;

            await _worker.UpdateTaskAgentSettingsAsync(
                new ClaudeDo.Ui.Services.UpdateTaskAgentSettingsDto(Task.Id, model, sp, ap, turns));
```

In the `Bind(null)` reset block (lines ~583-596), set the new fields:
```csharp
                TaskModelSelection = null;
                TaskMaxTurns = null;
                TaskSystemPrompt = "";
                TaskSelectedAgent = null;
```
   and remove the now-deleted `EffectiveModelHint`/`EffectiveAgentHint` assignments; keep `EffectiveSystemPromptHint = "";`.

- [ ] **Step 4: Add reset commands**

```csharp
    [RelayCommand] private void ResetTaskModel() => TaskModelSelection = null;
    [RelayCommand] private void ResetTaskTurns() => TaskMaxTurns = null;
    [RelayCommand] private void ResetTaskAgent() => TaskSelectedAgent = TaskAgentOptions.Count > 0 ? TaskAgentOptions[0] : null;
```

- [ ] **Step 5: Update the flyout XAML**

In `DetailsIslandView.axaml`, add `xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"` to the root if absent. Replace the Model block (lines ~90-98) with the badge+reset label row + null-select ComboBox:

```xml
                <StackPanel Spacing="2">
                  <Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="6">
                    <TextBlock Grid.Column="0" Classes="field-label" Text="{loc:Tr details.modelLabel}" VerticalAlignment="Center"/>
                    <ctl:InheritedBadge Grid.Column="1" BadgeText="{Binding ModelBadge}"/>
                    <Button Grid.Column="3" Classes="icon-btn" Content="↺" ToolTip.Tip="{loc:Tr settings.inherit.resetToInherited}"
                            Command="{Binding ResetTaskModelCommand}"/>
                  </Grid>
                  <ComboBox ItemsSource="{Binding TaskModelOptions}"
                            SelectedItem="{Binding TaskModelSelection, Mode=TwoWay}"
                            PlaceholderText="{Binding ModelInheritedHint}"
                            HorizontalAlignment="Stretch"/>
                </StackPanel>

                <StackPanel Spacing="2">
                  <Grid ColumnDefinitions="Auto,Auto,*,Auto" ColumnSpacing="6">
                    <TextBlock Grid.Column="0" Classes="field-label" Text="{loc:Tr details.maxTurnsLabel}" VerticalAlignment="Center"/>
                    <ctl:InheritedBadge Grid.Column="1" BadgeText="{Binding TurnsBadge}"/>
                    <Button Grid.Column="3" Classes="icon-btn" Content="↺" ToolTip.Tip="{loc:Tr settings.inherit.resetToInherited}"
                            Command="{Binding ResetTaskTurnsCommand}"/>
                  </Grid>
                  <NumericUpDown Value="{Binding TaskMaxTurns, Mode=TwoWay}"
                                 Watermark="{Binding TurnsInheritedHint}"
                                 Minimum="1" Maximum="200" Increment="1" FormatString="0"
                                 HorizontalAlignment="Stretch"/>
                </StackPanel>
```

   For the System prompt block (lines ~100-105), keep the TextBox but change the placeholder hint label to use the new "prepended automatically" key by adding a small caption above/below it:
```xml
                <StackPanel Spacing="2">
                  <TextBlock Classes="field-label" Text="{loc:Tr details.systemPromptLabel}"/>
                  <TextBox Text="{Binding TaskSystemPrompt, Mode=TwoWay}"
                           AcceptsReturn="True" TextWrapping="Wrap" MinHeight="70"/>
                  <TextBlock Classes="meta" Opacity="0.6"
                             Text="{loc:Tr details.systemPromptPrepended}"
                             IsVisible="{Binding EffectiveSystemPromptHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
                  <TextBlock Classes="meta" Opacity="0.6" TextWrapping="Wrap"
                             Text="{Binding EffectiveSystemPromptHint}"
                             IsVisible="{Binding EffectiveSystemPromptHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
                </StackPanel>
```

   For the Agent block (lines ~107-121), wrap the `field-label` in the badge+reset row (badge bound to `AgentBadge`, reset to `ResetTaskAgentCommand`); remove the old `EffectiveAgentLabel` `<TextBlock>` at lines ~118-120; keep the ComboBox.

- [ ] **Step 6: Build the UI project**

Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
Expected: build succeeds. Resolve any leftover references to the removed `Effective*Label`/`Effective*Hint` members.

- [ ] **Step 7: Run the full UI test suite**

Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release`
Expected: PASS. If `StubWorkerClient.GetAppSettingsAsync` returning null causes a VM test NRE, the VM already null-coalesces (`app?.DefaultModel ?? ...`) — but verify any DetailsIsland VM test still passes; adjust the stub to return a minimal `AppSettingsDto` if a test needs non-null globals.

- [ ] **Step 8: Commit**

```bash
git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
git commit -m "feat(ui): show inherited markers and max-turns override in task flyout"
```

---

## Task 11: Full build + test sweep, docs, visual-verification flag

**Files:**
- Modify: `src/ClaudeDo.Ui/CLAUDE.md` (one-line note on the new flyout/modal fields), `src/ClaudeDo.Data/CLAUDE.md` (note `max_turns` on ListConfig/TaskEntity)

- [ ] **Step 1: Build everything that matters**

Run, in order:
```bash
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release
dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release
```
Expected: both succeed.

- [ ] **Step 2: Run the test suites**

Run:
```bash
dotnet test tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj -c Release
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release
dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release
dotnet test tests/ClaudeDo.Localization.Tests/ClaudeDo.Localization.Tests.csproj -c Release
```
Expected: all PASS.

- [ ] **Step 3: Update the two CLAUDE.md notes**

In `src/ClaudeDo.Data/CLAUDE.md`, update the `ListConfigEntity` and `TaskEntity` lines to mention `MaxTurns` (nullable override). In `src/ClaudeDo.Ui/CLAUDE.md`, update the `ListSettingsModalView` and `DetailsIslandView` descriptions to mention the Max-turns field and source-aware inherited markers.

- [ ] **Step 4: Commit docs**

```bash
git add src/ClaudeDo.Data/CLAUDE.md src/ClaudeDo.Ui/CLAUDE.md
git commit -m "docs: note max-turns override and inherited markers in module docs"
```

- [ ] **Step 5: Flag the visual-verification gap (do NOT claim the UI works)**

The badge styling, muted placeholder appearance, NumericUpDown watermark, and reset-button layout in both the List settings modal and the task overrides flyout require a human visual pass — they cannot be asserted programmatically. Report to the user: build + automated tests pass; the visual rendering of the inherited markers, placeholders, and reset buttons needs manual confirmation in the running app (open a list's settings and a task's ⚙ flyout, toggle override vs inherited for Model / Turns / Agent).

---

## Self-Review notes

- **Spec coverage:** marker shows resolved value muted (placeholder/watermark) + source-aware badge (Tasks 9-10 via Task 7 resolver) ✓; override semantics with reset (reset commands, Tasks 9-10) ✓; Turns at list + task levels (Tasks 1-5, 9-10) ✓; SystemPrompt stays plain + "prepended automatically" hint (Task 10 Step 5) ✓; MCP parity (Task 4) ✓; localization parity + retire old key (Task 6) ✓; test-fake sync (Task 5) ✓.
- **Naming consistency:** badge VM props named `ModelBadge` / `TurnsBadge` / `AgentBadge`; hints `*InheritedHint`; reset commands `Reset*Command`; resolver `Resolve` / `ResolveList` returning `(string Value, InheritSource Source)`; control property `BadgeText`. These names are used identically across tasks.
- **Known risk:** `AppSettingsDto` already carries `DefaultModel` and `DefaultMaxTurns` (confirmed) — UI globals load works. `NumericUpDown.Value` is `decimal?`, hence the `decimal?` VM properties with `int?` conversion at save.
