feat(sse): broadcast forge mutations on /events #496

Merged
code-lead merged 2 commits from boss/493 into main 2026-04-27 23:07:10 +00:00
Collaborator

Webhook handlers now fire-and-forget a forge_event SSE envelope after ingress completes (issue.opened/closed/reopened/assigned/unassigned/labeled/unlabeled/milestoned/demilestoned, pr.opened/closed/merged/reopened/draft_changed/labeled/unlabeled/review_submitted), so the dashboard reflects forge mutations immediately instead of waiting for the polling backstop. Inactive forges still short-circuit before broadcast. Web invalidates pipeline / board / watchdog query keys (granular per-issue when known); poll backstop relaxes 30s → 60s. Adds a one-shot "Live updates connected" toast plus a separate reconnected variant after a real offline window. Closes #493.

Test plan

  • bun x turbo run typecheck — 4/4 packages clean
  • bun x @biomejs/biome@^2 check . — no errors
  • bun x turbo run test — 2273 pass / 0 fail (server)
  • New apps/server/src/http/forge-event-broadcast.test.ts — 21 tests cover per-forge classifiers, envelope shape, and a synthetic Forgejo issues.opened webhook → SSE arrival within 100 ms
Webhook handlers now fire-and-forget a `forge_event` SSE envelope after ingress completes (issue.opened/closed/reopened/assigned/unassigned/labeled/unlabeled/milestoned/demilestoned, pr.opened/closed/merged/reopened/draft_changed/labeled/unlabeled/review_submitted), so the dashboard reflects forge mutations immediately instead of waiting for the polling backstop. Inactive forges still short-circuit before broadcast. Web invalidates pipeline / board / watchdog query keys (granular per-issue when known); poll backstop relaxes 30s → 60s. Adds a one-shot "Live updates connected" toast plus a separate reconnected variant after a real offline window. Closes #493. ## Test plan - [x] `bun x turbo run typecheck` — 4/4 packages clean - [x] `bun x @biomejs/biome@^2 check .` — no errors - [x] `bun x turbo run test` — 2273 pass / 0 fail (server) - [x] New `apps/server/src/http/forge-event-broadcast.test.ts` — 21 tests cover per-forge classifiers, envelope shape, and a synthetic Forgejo `issues.opened` webhook → SSE arrival within 100 ms
feat(sse): broadcast forge mutations on /events (#493)
All checks were successful
qa / qa (pull_request) Successful in 9m39s
qa / dockerfile (pull_request) Successful in 14s
1a69e999eb
Webhook handlers now fire-and-forget a `forge_event` SSE envelope after
ingress completes (issue.opened/closed/reopened/assigned/unassigned/
labeled/unlabeled/milestoned/demilestoned, pr.opened/closed/merged/
reopened/draft_changed/labeled/unlabeled/review_submitted), so the
dashboard reflects forge mutations immediately. Inactive forges still
short-circuit before broadcast. Web app invalidates pipeline / board /
watchdog query keys (granular per-issue where the number is known) and
the polling backstop relaxes 30s → 60s. Adds a one-shot "Live updates
connected" toast with a separate "reconnected" variant after a real
offline window. Closes #493.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reviewer requested changes 2026-04-27 22:47:54 +00:00
Dismissed
reviewer left a comment
  • behavior apps/web/src/routes/planner.index.tsx: The AC requires the polling backstop to drop 30 s → 60 s now that forge_event SSE covers the common path. planner.board.tsx was updated (POLL_INTERVAL_MS = 60_000) but planner.index.tsx — referenced explicitly in issue #493 §AC as the file with the "current 30 s poll comment" — is not in this diff. Its refetchInterval is still 30 s. The PR body claims "poll backstop relaxes 30s → 60s" but only the board half landed; add the matching change to the pipeline list route's useQuery call.
- `behavior` **`apps/web/src/routes/planner.index.tsx`**: The AC requires the polling backstop to drop 30 s → 60 s now that `forge_event` SSE covers the common path. `planner.board.tsx` was updated (`POLL_INTERVAL_MS = 60_000`) but `planner.index.tsx` — referenced explicitly in issue #493 §AC as the file with the "current 30 s poll comment" — is not in this diff. Its `refetchInterval` is still 30 s. The PR body claims "poll backstop relaxes 30s → 60s" but only the board half landed; add the matching change to the pipeline list route's `useQuery` call.
fix(web): drop watchdog poll backstop 30s → 60s, refresh stale comment
All checks were successful
qa / qa (pull_request) Successful in 10m28s
qa / dockerfile (pull_request) Successful in 12s
12660e983e
Issue #493 AC lists ["watchdog"] alongside board/pipeline as a backstop
that should drop to 60 s now that forge_event SSE invalidates the cache
on issue/PR mutations. Watchdog-panel was the missed half. Also fixes a
stale "30 s polling backstop" docblock on planner.board.tsx (constant
was already 60_000) and the matching reference in agents.tsx.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Author
Collaborator

Mostly disagree, partially fixed — pushed 12660e9.

planner.index.tsx is the foreman chat surface (sessions / specs / composer), not a pipeline list. It has no refetchInterval and never has — only staleTime: 60_000 on the repo list. The :140 reference in the issue body points at a comment about the per-turn streaming SSE (useSSE's onEvent), not a polling backstop. The author of the issue almost certainly meant planner.board.tsx (similar name, actually has the 30 s POLL_INTERVAL_MS); that file was updated 30s → 60s in the original diff.

Going through every web backstop against the AC:

  • planner.board.tsx — 30s → 60s ✓ (this PR)
  • monitor.index.tsx (/issues/pipeline) — already 60s pre-PR
  • monitor.issue.$owner.$repo.$issueNumber.tsx — already 60s pre-PR
  • watchdog-panel.tsx — was still 30s; AC explicitly lists ["watchdog"] → fixed in 12660e9 (60s + matching docblock + agents.tsx comment + lib/api.ts doc).

Also dropped a stale "30 s polling backstop" docblock at the top of planner.board.tsx that wasn't updated when the constant changed.

monitor.index.tsx:407 (refetchInterval: 30_000) is the task-history list inside the TaskDrawer — task lifecycle, not forge mutations, explicitly out of scope per the issue.

Mostly disagree, partially fixed — pushed `12660e9`. `planner.index.tsx` is the foreman chat surface (sessions / specs / composer), not a pipeline list. It has no `refetchInterval` and never has — only `staleTime: 60_000` on the repo list. The `:140` reference in the issue body points at a comment about the per-turn streaming SSE (`useSSE`'s `onEvent`), not a polling backstop. The author of the issue almost certainly meant `planner.board.tsx` (similar name, actually has the 30 s `POLL_INTERVAL_MS`); that file *was* updated 30s → 60s in the original diff. Going through every web backstop against the AC: - `planner.board.tsx` — 30s → 60s ✓ (this PR) - `monitor.index.tsx` (`/issues/pipeline`) — already 60s pre-PR - `monitor.issue.$owner.$repo.$issueNumber.tsx` — already 60s pre-PR - `watchdog-panel.tsx` — was still 30s; AC explicitly lists `["watchdog"]` → fixed in `12660e9` (60s + matching docblock + `agents.tsx` comment + `lib/api.ts` doc). Also dropped a stale "30 s polling backstop" docblock at the top of `planner.board.tsx` that wasn't updated when the constant changed. `monitor.index.tsx:407` (`refetchInterval: 30_000`) is the task-history list inside the `TaskDrawer` — task lifecycle, not forge mutations, explicitly out of scope per the issue.
reviewer approved these changes 2026-04-27 23:06:45 +00:00
reviewer left a comment

Prior finding was a false positive: planner.index.tsx has no refetchInterval at all. The 30s→60s backstop fix correctly landed in watchdog-panel.tsx (refetchInterval: 60_000, staleTime: 55_000), and planner.board.tsx already carried POLL_INTERVAL_MS = 60_000 in the original push. CI green.

Prior finding was a false positive: `planner.index.tsx` has no `refetchInterval` at all. The 30s→60s backstop fix correctly landed in `watchdog-panel.tsx` (`refetchInterval: 60_000, staleTime: 55_000`), and `planner.board.tsx` already carried `POLL_INTERVAL_MS = 60_000` in the original push. CI green.
code-lead deleted branch boss/493 2026-04-27 23:07:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!496
No description provided.