Skip to content

feat: refresh credit usage after chat completion - #374

Merged
AnthonyRonning merged 2 commits into
masterfrom
claude/issue-373-20260109-0045
Jan 9, 2026
Merged

feat: refresh credit usage after chat completion#374
AnthonyRonning merged 2 commits into
masterfrom
claude/issue-373-20260109-0045

Conversation

@AnthonyRonning

@AnthonyRonningAnthonyRonning commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

Fixes#373

Invalidates billing status query 3 seconds after chat streaming completes to allow backend billing events to process. This ensures the sidepanel credit usage indicator stays up-to-date during long chat sessions.

Changes

  • Added TanStack Query invalidation in UnifiedChat.tsx after streaming completes
  • Uses a 3-second delay to allow backend processing

Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved billing-status refresh after chat interactions by delaying the refresh briefly to ensure accurate display.
    • Ensures any pending refresh is canceled if the chat view is closed to avoid stale or duplicate updates.

✏️ Tip: You can customize this high-level summary in your review settings.

Invalidate billing status query 3 seconds after chat streaming completes
to allow backend billing events to process. This ensures the sidepanel
credit usage indicator stays up-to-date during long chat sessions.
Fixes#373
Co-authored-by: Anthony <AnthonyRonning@users.noreply.github.com>
@coderabbitai

coderabbitaiBot commented Jan 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Schedules a 3-second delayed cache invalidation of the ["billingStatus"] query after a user message streaming cycle completes, and ensures the timer is cleared on component unmount.

Changes

Cohort / File(s)Summary
Post-response billing refresh
frontend/src/components/UnifiedChat.tsx
Adds a billingRefreshTimeoutRef to schedule ["billingStatus"] query invalidation 3000ms after an assistant streaming response finishes; clears the timeout on unmount and after firing.

Sequence Diagram(s)

sequenceDiagram
participant User
participant UnifiedChat
participant ReactQueryCache
participant BillingBackend
User->>UnifiedChat: Sends message / receives streaming response
UnifiedChat->>UnifiedChat: Detects stream complete\nstarts 3000ms timer
Note right of UnifiedChat: If unmount -> clear timer
UnifiedChat->>ReactQueryCache: Invalidate ["billingStatus"] (after 3s)
ReactQueryCache->>BillingBackend: (on refetch) Request billing status
BillingBackend-->>ReactQueryCache: Return updated billing data
ReactQueryCache-->>UnifiedChat: Provide refreshed billingStatus
Loading

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 I timed a hop, then paused to wait,
Three ticks later, billing's up to date.
A gentle ping, the cache revived,
Credits hop back—oh what a jive!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat: refresh credit usage after chat completion' directly and clearly summarizes the main change - adding functionality to refresh billing/credit usage after chat completion.
Linked Issues check✅ PassedThe code changes implement the requirement from #373: invalidates billing status query after chat streaming completes with a 3-second delay to allow backend billing events to process, ensuring credit usage indicator updates during long chat sessions.
Out of Scope Changes check✅ PassedAll changes are scoped to the UnifiedChat component and directly address the linked issue requirement. The addition of billing refresh timer logic with proper cleanup is entirely on-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35f4cc5 and 4f2da2b.

📒 Files selected for processing (1)
  • frontend/src/components/UnifiedChat.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/UnifiedChat.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-linux
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-ios
  • GitHub Check: build-android

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/components/UnifiedChat.tsx (1)

2368-2372: Consider adding cleanup for the setTimeout to prevent potential memory leaks.

The timeout will fire even if the component unmounts during the 3-second delay. While invalidateQueries is generally safe to call, it's better practice to store the timeout ID and clean it up on unmount.

♻️ Suggested cleanup pattern

Store timeout IDs in a ref and clean them up:

 const abortControllerRef = useRef<AbortController | null>(null);
+ const billingRefreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);

Then update the invalidation code:

 } finally {
// Re-enable polling after streaming completes
assistantStreamingRef.current = false;
setCurrentResponseId(undefined);
// Invalidate billing status after a delay to allow backend processing
- setTimeout(() => {- queryClient.invalidateQueries({ queryKey: ["billingStatus"] });- }, 3000);+ billingRefreshTimeoutRef.current = setTimeout(() => {+ queryClient.invalidateQueries({ queryKey: ["billingStatus"] });+ billingRefreshTimeoutRef.current = null;+ }, 3000);
}

Add cleanup in a useEffect:

// Cleanup billing refresh timeout on unmountuseEffect(()=>{return()=>{if(billingRefreshTimeoutRef.current){clearTimeout(billingRefreshTimeoutRef.current);}};},[]);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf2b890 and 35f4cc5.

📒 Files selected for processing (1)
  • frontend/src/components/UnifiedChat.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use path aliases (@/* maps to ./src/*) for imports in TypeScript/React files
Use 2-space indentation, double quotes, and enforce 100-character line limit in TypeScript/React code
Maintain strict TypeScript and avoid using any type
Use PascalCase for component names and camelCase for variables and function names
Use functional components with React hooks instead of class components
Use React context for global state management and TanStack Query for server state management
Run just format, just lint, and just build after making TypeScript/React changes to ensure code quality and compilation

Files:

  • frontend/src/components/UnifiedChat.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build-ios
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-android
  • GitHub Check: build-linux
  • GitHub Check: Cloudflare Pages

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Adds TanStack Query invalidation for billingStatus after chat streaming completes, with a 3-second delay to allow backend billing events to process.

  • Added setTimeout with 3-second delay in the inner finally block of handleSendMessage
  • Invalidation occurs after streaming completes successfully, fails, or is aborted (appropriate since credits are consumed in all cases)
  • Does not invalidate if conversation/stream creation fails before streaming starts (correct behavior - no credits consumed)

The implementation is focused and achieves the stated goal. One minor observation: the timeout lacks cleanup handling if the component unmounts, though the practical impact is negligible since React Query's invalidateQueries is stable and idempotent.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk
  • The change is focused, well-placed, and achieves its stated goal. The implementation correctly invalidates billing status after streaming completes (whether successful, failed, or aborted), and appropriately skips invalidation when no credits are consumed. One minor consideration is the lack of timeout cleanup on unmount, though this has negligible practical impact.
  • No files require special attention

Important Files Changed

File Analysis

FilenameScoreOverview
frontend/src/components/UnifiedChat.tsx4/5Added 3-second delayed billing status invalidation after streaming completes. Minor concern: timeout lacks cleanup on unmount, but impact is minimal given React Query's design.

Sequence Diagram

sequenceDiagram
participant User
participant UnifiedChat
participant OpenAI API
participant Backend
participant QueryClient
participant BillingStatus
User->>UnifiedChat: Send message
UnifiedChat->>OpenAI API: Create streaming response
OpenAI API-->>UnifiedChat: Stream starts
Note over UnifiedChat: assistantStreamingRef = true
UnifiedChat->>UnifiedChat: processStreamingResponse()
OpenAI API-->>UnifiedChat: Stream chunks...
OpenAI API-->>UnifiedChat: Stream complete
Note over UnifiedChat: finally block executes
Note over UnifiedChat: assistantStreamingRef = false
UnifiedChat->>Backend: (Processing billing event)
Note over UnifiedChat: setTimeout(3000ms)
UnifiedChat->>UnifiedChat: Wait 3 seconds
UnifiedChat->>QueryClient: invalidateQueries(["billingStatus"])
QueryClient->>Backend: Fetch updated billing status
Backend-->>QueryClient: Return billing data
QueryClient-->>BillingStatus: Update UI with fresh data
Loading

Comment threadfrontend/src/components/UnifiedChat.tsx Outdated
@AnthonyRonning

Copy link
Copy Markdown
ContributorAuthor

@claude can you handle the review comment about unmount cleanup?

@claude

claudeBot commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @AnthonyRonning's task in 3m 28s —— View job


Handling unmount cleanup for setTimeout

I've addressed the review comment about cleaning up the setTimeout to prevent memory leaks.

Tasks

  • Read current implementation in UnifiedChat.tsx
  • Add ref to store timeout ID
  • Implement cleanup logic in useEffect
  • Run formatting and linting checks
  • Commit and push changes

Changes Made

Added proper cleanup for the billing refresh timeout to prevent memory leaks:

  1. Added billingRefreshTimeoutRef at frontend/src/components/UnifiedChat.tsx:966 to store the timeout ID
  2. Updated setTimeout logic at frontend/src/components/UnifiedChat.tsx:2371-2374 to store the timeout ID in the ref and clear it after firing
  3. Added cleanup useEffect at frontend/src/components/UnifiedChat.tsx:996-1003 to clear the timeout on component unmount

The implementation now properly cleans up the timeout if the component unmounts during the 3-second delay, preventing the queryClient.invalidateQueries() call from executing on an unmounted component.

All formatting, linting, and build checks passed successfully.

--- | Branch: claude/issue-373-20260109-0045

- Add billingRefreshTimeoutRef to store timeout ID
- Clear timeout in useEffect cleanup to prevent memory leaks
- Set ref to null after timeout fires
Co-authored-by: Anthony <AnthonyRonning@users.noreply.github.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying maple with Cloudflare Pages Cloudflare Pages

Latest commit:4f2da2b
Status: ✅ Deploy successful!
Preview URL:https://3754cd83.maple-ca8.pages.dev
Branch Preview URL:https://claude-issue-373-20260109-00.maple-ca8.pages.dev

View logs

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Adds automatic refresh of billing status after chat streaming completes to keep credit usage indicators up-to-date. The implementation uses a 3-second delay before invalidating the TanStack Query cache to allow backend billing events to process. Includes cleanup logic to prevent memory leaks when the component unmounts.

Confidence Score: 2/5

  • Moderate risk—billing refresh missing in retry and error completion paths
  • The implementation correctly adds billing refresh for the main completion path, but omits it in the retry success path (line 2543) and the outer finally block (line 2621). This means users won't see updated credit usage when messages succeed on retry or when errors occur after consuming credits. Additionally, rapid successive messages could trigger redundant query invalidations without clearing previous timeouts.
  • frontend/src/components/UnifiedChat.tsx requires billing refresh in all streaming completion paths

Important Files Changed

File Analysis

FilenameScoreOverview
frontend/src/components/UnifiedChat.tsx2/5Adds billing status refresh after chat completion; missing refresh in retry and error paths

Sequence Diagram

sequenceDiagram
participant User
participant UnifiedChat
participant OpenAI API
participant Backend
participant QueryClient
participant BillingStatus UI
User->>UnifiedChat: Sends chat message
UnifiedChat->>OpenAI API: Stream chat completion
OpenAI API-->>UnifiedChat: Streaming response
UnifiedChat->>Backend: Backend logs usage event
Note over UnifiedChat: Stream completes
UnifiedChat->>UnifiedChat: Wait 3 seconds
UnifiedChat->>QueryClient: Invalidate billingStatus query
QueryClient->>Backend: Fetch updated billing status
Backend-->>QueryClient: Return updated credits
QueryClient->>BillingStatus UI: Trigger re-render
BillingStatus UI->>User: Show updated credit count
Loading

Comment threadfrontend/src/components/UnifiedChat.tsx
@greptile-apps

Copy link
Copy Markdown
Contributor
Additional Comments (2)

frontend/src/components/UnifiedChat.tsx
The retry stream path also completes a chat interaction and should refresh billing status. After successful retry completion at line 2540, the billing query invalidation is missing—users won't see updated credit usage if their message succeeds on retry.

Add the same billing refresh logic here:

 await processStreamingResponse(retryStream);
console.log("Retry completed successfully");
} finally {
// Re-enable polling after streaming completes
assistantStreamingRef.current = false;
// Invalidate billing status after a delay to allow backend processing
billingRefreshTimeoutRef.current = setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["billingStatus"] });
billingRefreshTimeoutRef.current = null;
}, 3000);

frontend/src/components/UnifiedChat.tsx
The outer finally block at line 2618 also completes streaming (when errors occur or when the try block exits). Credits are consumed even if there's an error, so billing status should refresh here too.

Add billing refresh in this finally block:

 } finally {
setIsGenerating(false);
setCurrentResponseId(undefined);
abortControllerRef.current = null;
assistantStreamingRef.current = false;
// Invalidate billing status after a delay to allow backend processing
billingRefreshTimeoutRef.current = setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["billingStatus"] });
billingRefreshTimeoutRef.current = null;
}, 3000);

Should billing refresh on errors, or only on successful completions?

@AnthonyRonning
AnthonyRonning merged commit 6749ab0 into masterJan 9, 2026
8 checks passed
@AnthonyRonning
AnthonyRonning deleted the claude/issue-373-20260109-0045 branch January 9, 2026 04:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh credit usage after chats complete

1 participant

@AnthonyRonning