PR state was written by two flows and the second one could not persist: the feedback fetch returned live PR data without saving it, and the browser patched its own store copy instead. Opening a PR's detail panel turned that tab purple while the kanban card and boot payload still read state=open, and a reload snapped it back.
The backend is now the only writer — the feedback read reconciles through SyncTaskPR and publishes github.task_pr.updated, so every surface converges and the state survives a reload. SyncTaskPR also refuses to move a row out of merged, since GitHub cannot un-merge a PR.
* fix(github): stop PR polling for archived tasks
The PR watcher kept making GitHub API calls for tasks the user had
archived, wasting rate limit and surfacing stale checks. Hide archived
and orphaned watches from the poller via an INNER JOIN on tasks, and
proactively prune watches when task.updated (archived_at set) or
task.deleted events fire. Closes#989.
* fix(github): address review comments on PR watch pruning
- Detect archived_at from a non-empty string value instead of key
presence, so a future null/zero archived_at in the task event payload
doesn't silently prune watches on non-archive updates.
- Track task-event subscriptions on the Service and release them in the
provider cleanup so handlers don't accumulate if the service is torn
down and re-created while the event bus persists.
- Propagate RowsAffected errors from DeletePRWatchesByTaskID instead of
silently discarding them.