POST /sync (SyncController → SyncService.syncServiceVectorStore) currently runs synchronously: the HTTP request blocks until every page of the Homebox catalog has been fetched, embedded, and written to pgvector. For now the manual "sync" button in the frontend relies on this (it just awaits the fetch and gates AI search on the promise resolving). This ticket tracks moving to the async version once we outgrow that.
Problem
Blocking the request thread for the full sync duration:
risks browser/proxy timeouts on larger inventories
ties up a request thread for the whole sync
gives the frontend no way to know sync progress if the user navigates away and comes back, or if we later add a periodic/scheduled sync that isn't triggered by a button at all
Goal
Make /sync non-blocking, and let the frontend know when an in-progress sync finishes so it can keep AI search disabled until indexing is actually done.
Scope
Backend
Run SyncService.syncServiceVectorStore asynchronously (@Async + dedicated TaskExecutor, not the request thread pool) so SyncController returns immediately.
Track sync status per connection: IDLE / SYNCING / COMPLETED / FAILED, plus lastSyncedAt. Start with an in-memory ConcurrentHashMap<connectionId, SyncStatus> — revisit persistence only if status needs to survive a backend restart.
SyncController returns 202 Accepted (not 204) and flips status to SYNCING before dispatching the async work.
Extend ConnectionStatusResponse (GET /connections/status) to include the new sync status fields — reuse this existing endpoint instead of adding a dedicated status endpoint, since the frontend already calls it.
On failure mid-sync (e.g. Homebox becomes unreachable), record FAILED instead of leaving status stuck at SYNCING.
Frontend
After triggering /sync, poll GET /connections/status at a short interval while status is SYNCING.
Keep AI search disabled until status is COMPLETED; surface FAILED with a retry action.
Out of scope
Periodic/scheduled sync (separate ticket — needs this status tracking as a dependency)
SSE/WebSocket push instead of polling
Acceptance criteria
Triggering sync from the UI returns immediately, no request blocking.
AI search stays disabled for a connection while its sync status is SYNCING.
/connections/status reflects live sync state on poll, without requiring a page refresh.
A failed sync surfaces as FAILED rather than hanging indefinitely.
### Context
`POST /sync` (`SyncController` → `SyncService.syncServiceVectorStore`) currently runs synchronously: the HTTP request blocks until every page of the Homebox catalog has been fetched, embedded, and written to pgvector. For now the manual "sync" button in the frontend relies on this (it just awaits the fetch and gates AI search on the promise resolving). This ticket tracks moving to the async version once we outgrow that.
### Problem
Blocking the request thread for the full sync duration:
- risks browser/proxy timeouts on larger inventories
- ties up a request thread for the whole sync
- gives the frontend no way to know sync progress if the user navigates away and comes back, or if we later add a periodic/scheduled sync that isn't triggered by a button at all
### Goal
Make `/sync` non-blocking, and let the frontend know when an in-progress sync finishes so it can keep AI search disabled until indexing is actually done.
### Scope
**Backend**
- Run `SyncService.syncServiceVectorStore` asynchronously (`@Async` + dedicated `TaskExecutor`, not the request thread pool) so `SyncController` returns immediately.
- Track sync status per connection: `IDLE` / `SYNCING` / `COMPLETED` / `FAILED`, plus `lastSyncedAt`. Start with an in-memory `ConcurrentHashMap<connectionId, SyncStatus>` — revisit persistence only if status needs to survive a backend restart.
- `SyncController` returns `202 Accepted` (not `204`) and flips status to `SYNCING` before dispatching the async work.
- Extend `ConnectionStatusResponse` (`GET /connections/status`) to include the new sync status fields — reuse this existing endpoint instead of adding a dedicated status endpoint, since the frontend already calls it.
- On failure mid-sync (e.g. Homebox becomes unreachable), record `FAILED` instead of leaving status stuck at `SYNCING`.
**Frontend**
- After triggering `/sync`, poll `GET /connections/status` at a short interval while status is `SYNCING`.
- Keep AI search disabled until status is `COMPLETED`; surface `FAILED` with a retry action.
### Out of scope
- Periodic/scheduled sync (separate ticket — needs this status tracking as a dependency)
- SSE/WebSocket push instead of polling
### Acceptance criteria
- Triggering sync from the UI returns immediately, no request blocking.
- AI search stays disabled for a connection while its sync status is `SYNCING`.
- `/connections/status` reflects live sync state on poll, without requiring a page refresh.
- A failed sync surfaces as `FAILED` rather than hanging indefinitely.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
POST /sync(SyncController→SyncService.syncServiceVectorStore) currently runs synchronously: the HTTP request blocks until every page of the Homebox catalog has been fetched, embedded, and written to pgvector. For now the manual "sync" button in the frontend relies on this (it just awaits the fetch and gates AI search on the promise resolving). This ticket tracks moving to the async version once we outgrow that.Problem
Blocking the request thread for the full sync duration:
Goal
Make
/syncnon-blocking, and let the frontend know when an in-progress sync finishes so it can keep AI search disabled until indexing is actually done.Scope
Backend
SyncService.syncServiceVectorStoreasynchronously (@Async+ dedicatedTaskExecutor, not the request thread pool) soSyncControllerreturns immediately.IDLE/SYNCING/COMPLETED/FAILED, pluslastSyncedAt. Start with an in-memoryConcurrentHashMap<connectionId, SyncStatus>— revisit persistence only if status needs to survive a backend restart.SyncControllerreturns202 Accepted(not204) and flips status toSYNCINGbefore dispatching the async work.ConnectionStatusResponse(GET /connections/status) to include the new sync status fields — reuse this existing endpoint instead of adding a dedicated status endpoint, since the frontend already calls it.FAILEDinstead of leaving status stuck atSYNCING.Frontend
/sync, pollGET /connections/statusat a short interval while status isSYNCING.COMPLETED; surfaceFAILEDwith a retry action.Out of scope
Acceptance criteria
SYNCING./connections/statusreflects live sync state on poll, without requiring a page refresh.FAILEDrather than hanging indefinitely.