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

@ -30,6 +30,7 @@ interface NavBarProps {
activeProduct: { id: string; name: string } | null
products: { id: string; name: string }[]
hasActiveSprint: boolean
minQuotaPct: number
}
export function NavBar({
@ -41,6 +42,7 @@ export function NavBar({
activeProduct,
products,
hasActiveSprint,
minQuotaPct,
}: NavBarProps) {
const pathname = usePathname()
const router = useRouter()
@ -188,7 +190,7 @@ export function NavBar({
{/* Rechts: solo-status + notifications + account-menu */}
<div className="flex items-center gap-2 flex-1 justify-end">
<SoloNavStatusIndicators hasActiveProduct={!!activeProduct} />
<SoloNavStatusIndicators hasActiveProduct={!!activeProduct} minQuotaPct={minQuotaPct} />
<NotificationsBell currentUserId={userId} isDemo={isDemo} />
<UserMenu userId={userId} username={username} email={email} roles={roles} />
</div>

View file

@ -40,13 +40,28 @@ function RealtimeIndicator({
)
}
export function SoloNavStatusIndicators({ hasActiveProduct }: { hasActiveProduct: boolean }) {
export function SoloNavStatusIndicators({
hasActiveProduct,
minQuotaPct,
}: {
hasActiveProduct: boolean
minQuotaPct: number
}) {
const realtimeStatus = useSoloStore((s) => s.realtimeStatus)
const showConnectingIndicator = useSoloStore((s) => s.showConnectingIndicator)
const connectedWorkers = useSoloStore((s) => s.connectedWorkers)
const workerQuotaPct = useSoloStore((s) => s.workerQuotaPct)
if (!hasActiveProduct) return null
// M13: stand-by als alle workers low quota hebben (workerQuotaPct geldt
// voor de laatste-rapporterende worker; bij N>1 workers is dit een
// benadering — server-side aggregate is een v2-verbetering).
const isStandby =
connectedWorkers > 0 &&
workerQuotaPct !== null &&
workerQuotaPct < minQuotaPct
return (
<div className="flex items-center gap-3 px-2">
<RealtimeIndicator
@ -56,9 +71,28 @@ export function SoloNavStatusIndicators({ hasActiveProduct }: { hasActiveProduct
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<span className={cn(
'size-2 rounded-full',
connectedWorkers > 0 ? 'bg-status-done' : 'bg-muted-foreground/40'
isStandby
? 'bg-warning'
: connectedWorkers > 0
? 'bg-status-done'
: 'bg-muted-foreground/40'
)} />
{connectedWorkers > 0 ? 'Agent verbonden' : 'Geen agent'}
{isStandby ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger
render={<span>Stand-by ({workerQuotaPct}%)</span>}
/>
<TooltipContent>
Worker wacht tot Anthropic-quota stijgt boven {minQuotaPct}%
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : connectedWorkers > 0 ? (
'Agent verbonden'
) : (
'Geen agent'
)}
</div>
</div>
)