diff --git a/src/ClaudeDo.Worker/Online/OnlineSyncService.cs b/src/ClaudeDo.Worker/Online/OnlineSyncService.cs index 3afb1e58..99e5c665 100644 --- a/src/ClaudeDo.Worker/Online/OnlineSyncService.cs +++ b/src/ClaudeDo.Worker/Online/OnlineSyncService.cs @@ -1,6 +1,7 @@ using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Online.Interfaces; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; @@ -15,19 +16,22 @@ public sealed class OnlineSyncService : BackgroundService private readonly IOnlineAuthProvider _auth; private readonly OnlineInboxConfig _config; private readonly ILogger _logger; + private readonly HubBroadcaster _broadcaster; public OnlineSyncService( IDbContextFactory dbFactory, IOnlineInboxApi api, IOnlineAuthProvider auth, OnlineInboxConfig config, - ILogger logger) + ILogger logger, + HubBroadcaster broadcaster) { _dbFactory = dbFactory; _api = api; _auth = auth; _config = config; _logger = logger; + _broadcaster = broadcaster; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -129,6 +133,8 @@ public sealed class OnlineSyncService : BackgroundService CommitType = CommitTypeRegistry.DefaultType, }; await tasks.AddAsync(entity, ct); + // Without this the imported task only shows up after a manual reload. + await _broadcaster.TaskUpdated(entity.Id); await _api.MarkImportedAsync(remote.Id, ct); _logger.LogInformation("OnlineSyncService: imported task {Id} ('{Title}')", remote.Id, remote.Title); diff --git a/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs b/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs index 1edbe5c0..ca2bce98 100644 --- a/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs @@ -1,6 +1,7 @@ using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Online; using ClaudeDo.Worker.Tests.Infrastructure; using Microsoft.Extensions.Logging.Abstractions; @@ -55,7 +56,8 @@ public sealed class OnlineSyncServiceTests : IDisposable } } - private OnlineSyncService BuildService(FakeApi api, string? token = "test-token", bool enabled = true) + private OnlineSyncService BuildService( + FakeApi api, string? token = "test-token", bool enabled = true, HubBroadcaster? broadcaster = null) { var config = new OnlineInboxConfig { Enabled = enabled, PollIntervalSeconds = 60 }; var auth = new StaticTokenAuthProvider(token); @@ -64,7 +66,8 @@ public sealed class OnlineSyncServiceTests : IDisposable api, auth, config, - NullLogger.Instance); + NullLogger.Instance, + broadcaster ?? new HubBroadcaster(new CapturingHubContext())); } private async Task<(string ListId, ClaudeDoDbContext Ctx, TaskRepository Tasks, ListRepository Lists)> SeedAsync() @@ -103,6 +106,26 @@ public sealed class OnlineSyncServiceTests : IDisposable Assert.Contains(remoteId, api.MarkedImported); } + [Fact] + public async Task Tick_Imports_RemoteTask_BroadcastsTaskUpdated() + { + var (listId, ctx, _, _) = await SeedAsync(); + using var _ = ctx; + + var remoteId = Guid.NewGuid().ToString(); + var api = new FakeApi + { + UnimportedTasks = [new RemoteTask(remoteId, listId, "From Web", "desc", DateTimeOffset.UtcNow)], + }; + var hubContext = new CapturingHubContext(); + var svc = BuildService(api, broadcaster: new HubBroadcaster(hubContext)); + + await svc.TickAsync(CancellationToken.None); + + Assert.Contains(hubContext.Proxy.Calls, + c => c.Method == "TaskUpdated" && (string)c.Args[0]! == remoteId); + } + [Fact] public async Task Tick_UnknownList_Skips_And_DoesNotMark() {