fix(desktop-windows): wire chat quota gate into main-window send path (#10240)

Merged
FO
2mo ago
+75-83

Summary

Closes #10240.

The main-window send path had no pre-send quota check — it fired the request, received the server's canned "You've reached your limit" reply as a chat message, and then UsageLimitTriggerHost polled the quota endpoint post-hoc to show the upgrade popup. This is now replaced with a proper pre-send gate matching Mac's AgentBridge.quotaExceeded parity and the bar's existing FloatingBarUsageLimiter parity:

  • useChat.send() calls mainChatQuotaGate.check() before the request fires. If blocked: showUsageLimit('chat') is raised immediately and the send returns — no request sent, no history entry, no canned server reply.
  • UsageLimitTriggerHost now calls chatQuotaGate.sync() on the busy→idle edge to refresh the snapshot for the next send's check, instead of polling maybeTriggerChatQuotaPopup.
  • maybeTriggerChatQuotaPopup removed from usageLimit.ts (no remaining callers).

Test plan

  • node_modules/.bin/vitest run src/renderer/src/lib/usageLimit.test.ts src/renderer/src/lib/chatQuotaGate.test.ts src/renderer/src/hooks/useChat.test.tsx — 68/68 pass
  • New describe('useChat — chat quota gate') covers: blocked send (popup shown, no fetch, no history entry) and allowed send (proceeds, recordQuery called)

Product invariants affected

  • INV-CHAT-1 (one shared transcript across surfaces): this change adds a pre-send quota gate to the main-window send path, maintaining the same chat flow invariant as Mac and the bar.

Failure-Class: none

🤖 Generated with Claude Code