feat(M13 PBI-31 T-519b/T-520b): NavBar stand-by badge + quota-check runbook (#119)

* feat(M13 T-519b): SSE worker_heartbeat + NavBar stand-by badge

Aanvulling op scrum4me-mcp PR #25 (worker_heartbeat MCP-tool).

- app/api/realtime/solo/route.ts: WorkerHeartbeatPayload type +
  isWorkerHeartbeatPayload guard + shouldEmit-routing op user_id.
- stores/solo-store.ts: workerQuotaPct + workerQuotaCheckAt state +
  setWorkerQuota action. Reset bij decrementWorkers naar 0.
- lib/realtime/use-solo-realtime.ts: handle worker_heartbeat-event,
  roep setWorkerQuota.
- components/solo/nav-status-indicators.tsx: stand-by badge wanneer
  workerQuotaPct < minQuotaPct + tooltip met drempel.
- components/shared/nav-bar.tsx + app/(app)/layout.tsx: minQuotaPct
  prop plumbing van User.min_quota_pct naar NavStatusIndicators.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(M13 T-520b): pre-flight quota-check sectie in mcp-integration

Documenteert de batch-loop-uitbreiding:
1. get_worker_settings → min_quota_pct
2. bin/worker-quota-probe.sh → pct + reset
3. worker_heartbeat naar server (NavBar stand-by-badge)
4. Sleep tot reset bij low quota; anders wait_for_job

Verwijst naar bin/worker-quota-probe.sh in scrum4me-docker (zie
PR daar).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-06 04:34:48 +02:00 committed by GitHub
parent 555ed8fe89
commit 31dc429b61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 126 additions and 8 deletions

View file

@ -64,7 +64,17 @@ type WorkerPayload = {
product_id?: string
}
type NotifyPayload = EntityPayload | JobPayload | WorkerPayload
// M13: per-iteration quota-rapport van de worker. Geen product-scope —
// elke heartbeat geldt voor alle producten waar deze user toegang toe heeft.
type WorkerHeartbeatPayload = {
type: 'worker_heartbeat'
user_id: string
token_id: string
last_quota_pct: number
last_quota_check_at: string
}
type NotifyPayload = EntityPayload | JobPayload | WorkerPayload | WorkerHeartbeatPayload
function isJobPayload(p: NotifyPayload): p is JobPayload {
return 'type' in p && (p.type === 'claude_job_enqueued' || p.type === 'claude_job_status')
@ -74,6 +84,10 @@ function isWorkerPayload(p: NotifyPayload): p is WorkerPayload {
return 'type' in p && (p.type === 'worker_connected' || p.type === 'worker_disconnected')
}
function isWorkerHeartbeatPayload(p: NotifyPayload): p is WorkerHeartbeatPayload {
return 'type' in p && p.type === 'worker_heartbeat'
}
function shouldEmit(
payload: NotifyPayload,
productId: string,
@ -90,6 +104,10 @@ function shouldEmit(
return payload.user_id === userId
}
if (isWorkerHeartbeatPayload(payload)) {
return payload.user_id === userId
}
// M11 (ST-1104): question-events horen op /api/realtime/notifications, niet hier.
if (payload.entity === 'question') return false