feat(spend): enterprise month-end trajectory + per-conversation attribution [GET-22]#59
Conversation
…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.
|
@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. |
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughNew 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 27 minutes and 52 seconds.Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (9)
entrypoints/sidepanel/App.tsxentrypoints/sidepanel/components/UsageBudgetCard.tsxentrypoints/sidepanel/dashboard.cssentrypoints/sidepanel/hooks/useDashboardData.tslib/format.tslib/spend-trajectory.tslib/usage-budget.tstests/unit/spend-trajectory.test.tstests/unit/usage-budget-card.test.tsx
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary
Credit-tier (Enterprise) Usage Budget card now projects month-end spend from a 7-day rolling burn rate added to the exact
currentUsedCentsfrom 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 featurefix— Bug fixrefactor— Code restructure, no behavior changetest— Tests onlychore— Build, CI, tooling, dependenciesdocs— Documentation onlyWhat 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— extractedformatCurrencyCents(cents, currency)with the cachedIntl.NumberFormatmap. Single source of truth for cent-to-currency formatting;lib/usage-budget.tsand 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.ts—loadSpendTrajectoryreads typed limits and deltas, gates onkind === '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 existingconversationsarray (no extra storage reads).entrypoints/sidepanel/App.tsx— threadsspendTrajectory,topSpendConversations,conversationsthrough to the card.entrypoints/sidepanel/dashboard.css— new.lco-dash-budget-trajectoryand.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 againstConversationRecord.turns[].cost.tests/unit/usage-budget-card.test.tsx— 8 new credit-variant render cases.How to Test
bun run compile && bun run test && bun run build(1795 tests pass, build clean).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.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.costacross that conversation's turns.Checklist
bun run test) — 1795 / 1795bun run compile)bun run build)Related Issues
Closes GET-22.
Notes for Reviewer
aggregateByConversationlives inlib/conversation-store.ts. I put it inlib/spend-trajectory.tsinstead, becauseconversation-storeis 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.ConversationItemrows but are intentionally non-interactive, matching the existingConversationList(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.usageDeltas:{orgId}(exists since the token-economics work) andusageLimits:{orgId}(exists since GET-20).Summary by CodeRabbit