Skip to content

feat(spend): enterprise month-end trajectory + per-conversation attribution [GET-22]#59

Merged
DevanshuNEU merged 2 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/get-22-spend-trajectory
May 1, 2026
Merged

feat(spend): enterprise month-end trajectory + per-conversation attribution [GET-22]#59
DevanshuNEU merged 2 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/get-22-spend-trajectory

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Credit-tier (Enterprise) Usage Budget card now projects month-end spend from a 7-day rolling burn rate added to the exact currentUsedCents from Anthropic's endpoint, and surfaces the top 3 most expensive conversations of the current month. Closes the Cycle 1 gap that GET-20 left: the credit card was a static photograph of "$X of $Y this month"; it is now a moving picture with a forecast and per-conversation attribution. Session and unsupported variants are unchanged.

Type of Change

  • feat — New feature
  • fix — Bug fix
  • refactor — Code restructure, no behavior change
  • test — Tests only
  • chore — Build, CI, tooling, dependencies
  • docs — Documentation only

What Was Changed

  • lib/spend-trajectory.ts (new) — pure agent. projectMonthEnd(deltas, capturedAt, monthlyLimitCents, currentUsedCents) returns { projectedSpentCents, projectedUtilizationPct, daysRemaining, confidence } or null. Distinct-day floor of 7 (drives the "need more data" UI). Confidence: high ≥ 14 distinct days AND CV < 0.3, medium ≥ 10, low ≥ 7. aggregateByConversation(deltas, sinceTimestamp) groups by conversationId and ranks descending by total cost in cents.
  • lib/format.ts — extracted formatCurrencyCents(cents, currency) with the cached Intl.NumberFormat map. Single source of truth for cent-to-currency formatting; lib/usage-budget.ts and the side-panel card both route through it (was duplicated before this PR).
  • lib/usage-budget.ts — switched to the shared formatter; private cache deleted.
  • entrypoints/sidepanel/hooks/useDashboardData.tsloadSpendTrajectory reads typed limits and deltas, gates on kind === 'credit', runs the agent, joins to top-N spenders. Wired into init, tab activation, tab URL change, org switch, logout, budget-write, and delta-write paths. Mirrors the request-id stale-check pattern from the weekly-cap ETA loader.
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx — credit variant gains a trajectory line under the status (with confidence-tiered copy, falling back to a "need more data" placeholder) and an expandable <details> block listing top spenders. Subjects looked up from the existing conversations array (no extra storage reads).
  • entrypoints/sidepanel/App.tsx — threads spendTrajectory, topSpendConversations, conversations through to the card.
  • entrypoints/sidepanel/dashboard.css — new .lco-dash-budget-trajectory and .lco-dash-budget-spenders[-summary|-list|-spender] classes plus their compact-density variants.
  • tests/unit/spend-trajectory.test.ts (new) — 28 cases including stable / variable burn confidence tiers, < 7 distinct days → null, post-reset deltas excluded, clock-skew safety (future timestamps), sub-cent precision preservation, and a reconciliation invariant against ConversationRecord.turns[].cost.
  • tests/unit/usage-budget-card.test.tsx — 8 new credit-variant render cases.

How to Test

  1. bun run compile && bun run test && bun run build (1795 tests pass, build clean).
  2. Manual: load the unpacked extension on a Pro account; the side panel's Usage Budget card should look identical to before (no trajectory line, no top-spenders block on the session variant).
  3. Manual: load on Devanshu's Northeastern Enterprise account.
    • Within the first 6 days of a calendar month, the trajectory line should read Need 7+ days of usage before we can project month-end. and the top-spenders block should render only after at least one delta record exists.
    • After 7+ distinct cost-bearing days, the trajectory line should read On track for $X of $Y by Apr 30. (or the medium/low variant). Cents in the projection should round consistently with the spend status line above.
    • The expandable "Top conversations this month" should list 3 rows; each subject should match the History list, each cost should equal the sum of cost across that conversation's turns.
  4. Edge: switch tabs to a non-Claude tab — both trajectory and top-spenders should clear, just like the existing live data does.

Checklist

  • Tests pass locally (bun run test) — 1795 / 1795
  • TypeScript compiles clean (bun run compile)
  • Extension builds without errors (bun run build)
  • No secrets, hardcoded URLs, or sensitive tokens exposed
  • Comments are professional and clear — no emojis, no AI-generated filler
  • Commit messages follow conventional commits

Related Issues

Closes GET-22.

Notes for Reviewer

  • One spec deviation worth flagging: the issue description suggests aggregateByConversation lives in lib/conversation-store.ts. I put it in lib/spend-trajectory.ts instead, because conversation-store is the storage CRUD layer and pure aggregations live in agent files (token-economics, weekly-cap-eta both follow this pattern). Happy to move it if the convention should change.
  • "Top conversations this month" rows mirror the visual styling of the History ConversationItem rows but are intentionally non-interactive, matching the existing ConversationList (rows are not anchor links). If we want them to open the conversation in claude.ai on click, that should be a separate follow-up so the affordance ships uniformly across both surfaces.
  • No new chrome.* permissions, no new storage keys, no new network surface. Reuses usageDeltas:{orgId} (exists since the token-economics work) and usageLimits:{orgId} (exists since GET-20).

Summary by CodeRabbit

  • New Features
    • Added spending trajectory projection to the Usage Budget card, displaying projected month-end spending based on recent usage patterns
    • Added expandable "Top conversations" section to the Usage Budget card, ranking conversations by total spending with formatted cost display

…bution [GET-22]

Credit-tier (Enterprise) card now projects month-end spend and surfaces the
top 3 most expensive conversations of the current month. Session and
unsupported variants are unchanged.

Pure agent in lib/spend-trajectory.ts: projectMonthEnd adds a 7-day rolling
burn rate to the exact currentUsedCents from the Anthropic endpoint, with
a distinct-day floor of 7 and three confidence tiers (high/medium/low)
based on distinct cost-bearing days and CV of daily totals.
aggregateByConversation groups deltas by conversationId and ranks
descending by total cost.

Shared formatter formatCurrencyCents extracted into lib/format.ts so the
agent and the side-panel card share one cached Intl.NumberFormat per
currency. lib/usage-budget.ts now routes through the shared helper.

Side-panel hook loadSpendTrajectory wired into init, tab activation, tab
URL change, org switch, logout, budget-write, and delta-write paths with
the same request-id stale-check pattern as the weekly-cap ETA loader.

UsageBudgetCard credit variant adds a trajectory line under the spend
status with confidence-tiered copy and a "need more data" placeholder, and
an expandable <details> block listing top spenders with subjects joined
from the History list (no extra storage reads).

Tests: 28 cases for the agent (boundaries, post-reset, clock-skew safety,
reconciliation invariant against ConversationRecord.turns, sub-cent
precision, future-timestamp filter), 8 cases for the credit-tier card
render path. 1759 → 1795 tests on main.
@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown

@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@DevanshuNEU has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 52 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d07fa7cc-374d-4ab1-af9c-5cd3d7daebb6

📥 Commits

Reviewing files that changed from the base of the PR and between a9f4b78 and e01e668.

📒 Files selected for processing (7)
  • entrypoints/sidepanel/App.tsx
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx
  • entrypoints/sidepanel/dashboard.css
  • entrypoints/sidepanel/hooks/useDashboardData.ts
  • lib/spend-trajectory.ts
  • tests/unit/spend-trajectory.test.ts
  • tests/unit/usage-budget-card.test.tsx
📝 Walkthrough

Walkthrough

New spend-trajectory analytics module computes month-end spend projections and top conversation rankings. Enhanced dashboard hook loads and manages this data on Claude-tab activation. Updated UsageBudgetCard renders trajectory confidence messaging and expandable top-spender list. New currency formatting utility and CSS styling support the visualization.

Changes

Cohort / File(s) Summary
Spend Trajectory Analytics
lib/spend-trajectory.ts, tests/unit/spend-trajectory.test.ts
New module exports spend projection logic (projectMonthEnd), conversation cost aggregation (aggregateByConversation), and month-boundary helpers. Includes comprehensive test coverage validating projection accuracy, confidence tiers, filtering edge cases, and aggregation correctness.
Dashboard State Management
entrypoints/sidepanel/hooks/useDashboardData.ts, entrypoints/sidepanel/App.tsx
Hook introduces spendTrajectory and topSpendConversations state with dedicated loadSpendTrajectory loader fetching limits/deltas and computing projections. Wired into initial load, org changes, and storage updates while on Claude tab; explicitly cleared on tab/org transitions. App.tsx extracts and forwards new data to UsageBudgetCard.
Usage Budget UI
entrypoints/sidepanel/components/UsageBudgetCard.tsx, tests/unit/usage-budget-card.test.tsx
Component signature expanded to accept spendTrajectory, topSpendConversations, conversations props for credit tier only. Renders confidence-dependent trajectory paragraph and expandable "Top conversations" section via new SpenderRow component. Tests validate conditional rendering, subject matching, cost formatting, and credit-only gating.
Formatting & Styling
lib/format.ts, lib/usage-budget.ts, entrypoints/sidepanel/dashboard.css
New formatCurrencyCents utility provides cached Intl.NumberFormat with fallback for unsupported currencies. usage-budget.ts refactored to delegate formatting to shared helper. CSS adds trajectory text styling, expandable spender list with custom chevron, 3-column spender-row grid layout, and compact-density adjustments.

Sequence Diagram

sequenceDiagram
    participant App as App.tsx
    participant Hook as useDashboardData
    participant Storage as Browser Storage API
    participant Card as UsageBudgetCard
    
    App->>Hook: On Claude tab activation
    Hook->>Storage: Fetch getUsageLimits
    Storage-->>Hook: Return limits
    
    alt limits.kind === 'credit'
        Hook->>Storage: Fetch getUsageDeltas
        Storage-->>Hook: Return deltas
        Hook->>Hook: projectMonthEnd(deltas, ...)
        Hook->>Hook: aggregateByConversation(deltas, ...)
        Hook->>Hook: Compute spendTrajectory & topSpenders
    else non-credit tier
        Hook->>Hook: Clear trajectory & topSpenders
    end
    
    Hook-->>App: Return updated DashboardData
    App->>Card: Pass spendTrajectory,<br/>topSpendConversations,<br/>conversations props
    Card->>Card: Render trajectory paragraph<br/>(confidence-dependent)
    Card->>Card: Render expandable<br/>top spenders list
    Card-->>App: Rendered component
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through coins so bright,
Projecting spend with pure delight,
Top spenders ranked and trajectories gleam,
Currency formatted, a budgeter's dream,
Seven days of data make confidence bloom!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: month-end spend trajectory projection and per-conversation cost attribution for Enterprise tier, with issue reference.
Description check ✅ Passed The description follows the template comprehensively, covering all required sections including summary, type of change, detailed file changes, testing steps, completed checklist, related issues, and reviewer notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 27 minutes and 52 seconds.

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

@coderabbitai coderabbitai Bot 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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@entrypoints/sidepanel/components/UsageBudgetCard.tsx`:
- Around line 54-59: UsageBudgetCard currently resolves conversation subjects by
joining topSpendConversations against the truncated conversations prop (passed
from useDashboardData(), capped at 20), which can yield "Untitled conversation"
for true top spenders; update the logic so the hook or component fetches or
resolves subjects for the exact IDs in topSpendConversations instead of relying
on conversations: either extend useDashboardData() to accept an array of
conversation IDs and return their full ConversationRecord(s), or add a targeted
fetch inside UsageBudgetCard (lookup by topSpendConversations IDs) and replace
the join logic that uses the conversations?: ConversationRecord[] prop; ensure
you reference/modify aggregateByConversation(), topSpendConversations handling,
and the conversations prop mapping (also update the same pattern around the code
indicated at lines ~268-269).

In `@entrypoints/sidepanel/dashboard.css`:
- Around line 757-779: The disclosure summary
(.lco-dash-budget-spenders-summary) needs a visible keyboard focus state: add a
:focus-visible selector for .lco-dash-budget-spenders-summary to apply a clear
focus indicator (for example an outline or box-shadow and/or background color
change) and ensure it doesn't conflict with the hidden native marker
(::-webkit-details-marker) or the existing ::before chevron; also add a matching
:focus-visible rule for the ::before if you want the chevron to be highlighted
when focused so keyboard users can see focus clearly.

In `@entrypoints/sidepanel/hooks/useDashboardData.ts`:
- Around line 502-513: The non-Claude branches clear state but do not invalidate
in-flight spend trajectory requests; before calling
setBudget/setWeeklyEta/setSpendTrajectory/setTopSpendConversations in the
off-Claude branches (and the similar blocks handled around loadSpendTrajectory
calls), increment/bump spendTrajectoryRequestIdRef (the same mechanism used in
the logout path) so any pending loadSpendTrajectory() responses will be ignored,
then proceed to clear state; update every corresponding off-Claude/clear block
that mirrors the lines around loadSpendTrajectory to perform this bump-first
invalidation.

In `@lib/spend-trajectory.ts`:
- Around line 220-233: The helper aggregateByConversation currently only
enforces a lower bound (sinceTimestamp) and must also ignore future-dated
deltas; change the function signature of aggregateByConversation to accept an
upper bound (e.g., untilTimestamp or capturedAt) and inside the loop add a guard
to skip deltas where delta.timestamp > upperBound (and keep the existing
delta.timestamp < sinceTimestamp check and null-cost check), then update any
callers (such as projectMonthEnd) to pass the same capturedAt value so
future/skewed deltas are excluded from the monthly aggregation.
- Around line 2-3: Several comments/docstrings in spend-trajectory.ts use em
dashes (—), which the repo style forbids; locate the comment header "Spend
Trajectory Agent — projects month-end spend..." and the other comment blocks
(including the ones around the function/class that describe ranking expensive
conversations) and replace each em dash with an approved punctuation or rewrite
(e.g., use a colon, semicolon, or rephrase to "projects month-end spend for
credit-tier (Enterprise) accounts" and "and ranks the most expensive
conversations of the current month."). Ensure all occurrences in this file (the
top header and the other comment/docstring blocks) remove the em dash characters
and follow the repository guideline for TS/TSX/JS/MD files.

In `@tests/unit/spend-trajectory.test.ts`:
- Around line 130-136: The test title is misleading: it says "returns null on
the last second of the month" but actually asserts projectMonthEnd(...) is not
null; update the it(...) description to reflect the actual expectation (e.g.,
"returns non-null on the last second of the month (1 day remaining)") so test
names match behavior; locate the test using symbols dailyDeltas,
daysUntilNextMonth, and projectMonthEnd to rename the string passed to it(...)
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2991f422-5ec4-4706-bcb8-31b4ec0ed84a

📥 Commits

Reviewing files that changed from the base of the PR and between 22755ea and a9f4b78.

📒 Files selected for processing (9)
  • entrypoints/sidepanel/App.tsx
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx
  • entrypoints/sidepanel/dashboard.css
  • entrypoints/sidepanel/hooks/useDashboardData.ts
  • lib/format.ts
  • lib/spend-trajectory.ts
  • lib/usage-budget.ts
  • tests/unit/spend-trajectory.test.ts
  • tests/unit/usage-budget-card.test.tsx

Comment thread entrypoints/sidepanel/components/UsageBudgetCard.tsx Outdated
Comment thread entrypoints/sidepanel/dashboard.css
Comment thread entrypoints/sidepanel/hooks/useDashboardData.ts
Comment thread lib/spend-trajectory.ts Outdated
Comment thread lib/spend-trajectory.ts
Comment thread tests/unit/spend-trajectory.test.ts Outdated
Six fixes from the CodeRabbit pass on PR OpenCodeIntel#59:

1. Resolve top-spender subjects per ID via getConversation rather than
   joining against the History list. The History list is capped at 20, so
   an older top spender was rendering as "Untitled conversation" even when
   a record existed in storage. New TopSpender type carries the resolved
   subject so the card never has to do a lookup.
2. aggregateByConversation now accepts an upper-bound timestamp; the hook
   passes Date.now() so future-timestamped deltas (clock skew) cannot
   distort the ranking. Mirrors the projectMonthEnd safety guard.
3. Off-Claude clear paths in useDashboardData now bump
   weeklyEtaRequestIdRef and spendTrajectoryRequestIdRef before clearing
   state so any in-flight loaders cannot repopulate the card after the
   user leaves claude.ai.
4. Disclosure summary on the top-spenders details element gains a
   :focus-visible outline. The native marker is hidden, so without this
   keyboard users had no focus indicator.
5. Em dashes purged from comments and docstrings in lib/spend-trajectory.ts.
6. Test name corrected: "returns null on the last second of the month"
   actually asserts non-null behavior; renamed to "still projects on the
   last second of the month (1 day remaining)".

New tests: aggregateByConversation upper-bound clock-skew guard.
Test count: 1795 → 1796.
@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
getsaar Ready Ready Preview, Comment May 1, 2026 2:26pm

@DevanshuNEU DevanshuNEU merged commit 9c1d6af into OpenCodeIntel:main May 1, 2026
7 checks passed
Sign up for free to 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.

1 participant