Front-end stays in sync with all forge mutations (broadcast issue/PR events on /events) #493
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#493
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
As an operator, I want the dashboard to reflect new issues, label changes, assignee changes, and PR state transitions immediately, so that I do not have to wait for the 30 s polling backstop or hit refresh after every Forgejo action.
Context
Today
/events(SSE) broadcasts only task lifecycle events (task_started,task_finished,steer_queued, …). Forgejo webhook events that mutate the operator's view of the world —issues.opened,issues.assigned,issues.labeled,issues.closed,pull_request.opened/closed/labeled,pull_request_review.submitted— are processed server-side (dispatch / label routing) but never propagated to the SSE stream.Result: when a new issue is created (manually or via the breakdown skill), the board / pipeline / watchdog screens only refresh on the 30 s polling backstop. Same for label retags, assignee swaps, PR state changes. The UI feels stale.
Acceptance criteria
Server
apps/server/src/http/webhook.ts+domain/workflows/event-handlers.ts) emits an SSE event for every state-mutating forge event after normalisation. Categories:issue.opened,issue.closed,issue.reopenedissue.assigned,issue.unassignedissue.labeled,issue.unlabeledissue.milestoned,issue.demilestonedpr.opened,pr.closed,pr.merged,pr.reopened,pr.draft_changedpr.labeled,pr.unlabeledpr.review_submitted{ kind: "<category>", repo: "owner/name", issue_number?: number, pr_number?: number, ts: <unix_ms> }. Same shape every consumer in the web app already understands (matches/eventsschema).pr_dependenciesupdates), so a refetch triggered by the event sees the new state.Web app
/eventsconsumers (board, pipeline, watchdog, monitor) react to the new event categories by invalidating the matching React Query keys:issue.*,pr.*→ invalidate["board"],["pipeline", repo],["watchdog"].["pipeline", repo, issue_number]instead of the whole pipeline list).Observability
[sse-broadcast]log line per event withkind,repo, and consumer count. Drop-rate metric on SSE backpressure.issues.openedwebhook and asserts the corresponding SSE event lands within 100 ms.Out of scope
issue.labeledto the local React Query cache without refetch). Invalidate-and-refetch is enough for now; hand-merging would create drift.References
apps/server/src/main.ts:1617—/eventsroute handlerapps/server/src/domain/views/pipeline-sse.ts+ testsapps/server/src/http/webhook.ts— ingressapps/server/src/http/webhook-normalize.ts— already produces normalisedForgeEventshapes; this ticket consumes thoseapps/web/src/components/app-shell.tsx— global SSE subscriptionapps/web/src/routes/planner.index.tsx:140— current 30 s poll commentDependencies