Skip to content

feat(eta): weekly-cap ETA projection for Pro/Max users [GET-21]#58

Merged
DevanshuNEU merged 9 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/lco-21-weekly-cap-eta
Apr 29, 2026
Merged

feat(eta): weekly-cap ETA projection for Pro/Max users [GET-21]#58
DevanshuNEU merged 9 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/lco-21-weekly-cap-eta

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Projects when a Pro/Max user will exhaust their 7-day rolling usage window, displayed as a one-liner under the weekly bar in both the overlay and the side panel. Uses a least-squares linear fit over a rolling snapshot buffer with R²-based confidence (high/medium/low) and flat/declining/post-reset guards. Closes GET-21.

Type of Change

  • feat — New feature

What Was Changed

  • lib/weekly-cap-eta.ts (new) — Pure ETA agent: computeWeeklyEta (least-squares fit, R², confidence classification) + formatEtaLabel (Intl.DateTimeFormat). No DOM, no chrome refs.
  • lib/conversation-store.ts — Added appendUsageBudgetSnapshot / getUsageBudgetSnapshots / clearUsageBudgetSnapshots ring-buffer (cap 200). Exported usageBudgetSnapshotsKey so the content script reads the key from one source of truth.
  • entrypoints/background.ts — After each STORE_USAGE_LIMITS (session tier), appends a snapshot. Detects weekly-reset (new weeklyPct < last - 5) and clears stale snapshots before appending.
  • lib/overlay-state.ts — Added weeklyEta: WeeklyEta | null to OverlayState + applyWeeklyEta state transition.
  • ui/overlay.ts — Renders .lco-weekly-eta div below the weekly bar. Hidden for credit tier and null ETA.
  • entrypoints/claude-ai.content.tscomputeEtaForOrg reads snapshots via browser.storage.local (WXT polyfill) using the exported key builder. Called on ORGANIZATION_DETECTED and post-STREAM_COMPLETE.
  • entrypoints/sidepanel/hooks/useDashboardData.tsloadWeeklyEta callback; wired into init, storage-change listener, tab-switch, tab-update, tab-close handlers.
  • entrypoints/sidepanel/App.tsx — Passes weeklyEta to UsageBudgetCard.
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx — ETA line under weekly bar with three confidence variants (high: "At this pace…", medium: "Estimated cap…", low: "Estimating…"). Credit tier and null eta both hidden.

How to Test

  1. Load the extension on a Pro/Max claude.ai account.
  2. Send 10+ messages across different conversations (the buffer needs ≥5 snapshots; snapshots append on every usage fetch, not just on message send).
  3. Open the side panel — under the Weekly bar you should see "At this pace, you'll hit your weekly cap by Wed 3:00 PM." (or medium/low copy depending on R²).
  4. Check the in-page overlay — it shows "~Wed 3:00 PM at this pace" below the weekly bar.
  5. The ETA is absent on Enterprise (credit) accounts — confirm the weekly bar itself is hidden and no ETA line appears.
  6. Wait for or simulate a weekly reset (usage drops >5pp between two reads) — ETA should disappear until new snapshots accumulate.

Checklist

  • Tests pass locally (bun run test) — 1759/1759
  • 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-21

Notes for Reviewer

  • Snapshot accumulation is background-driven (fires on every STORE_USAGE_LIMITS), so the ETA will not appear immediately on a fresh install — it needs ≥5 data points (typically 5 page loads or post-stream fetches). This is by design to avoid noisy early projections.
  • The content script reads snapshots directly from chrome.storage.local (now via browser.*) rather than through conversation-store's setStorage abstraction, because the content script does not call setStorage. The key is now sourced from the exported usageBudgetSnapshotsKey builder to prevent drift.
  • ETA timestamps are always in the user's browser locale via Intl.DateTimeFormat.

Summary by CodeRabbit

  • New Features

    • Session-tier users now see a projected ETA for when weekly usage will hit 100% shown in the overlay and Usage Budget card, with localized time labels and confidence levels (high/medium/low).
    • ETA is computed from recent usage snapshots, updates with tab/org changes, and is hidden for credit-tier accounts or when data is insufficient.
  • Tests

    • Added unit tests covering ETA computation, formatting, storage, and UI rendering.

DevanshuNEU and others added 7 commits April 28, 2026 19:47
Pure function computeWeeklyEta: linear fit over usage snapshots, R²-based
confidence, flat/declining/boundary guards. 21 unit tests, all passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
appendUsageBudgetSnapshot / getUsageBudgetSnapshots / clearUsageBudgetSnapshots
with 200-entry cap per org. 8 new tests covering append, ordering, pruning,
isolation, and reset-clear path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TS [GET-21]

After each usage-limits store, appends a snapshot to the org's ring buffer.
Detects weekly reset (drop >5pp) and clears stale snapshots automatically.
Session-tier only; credit variant is skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
applyWeeklyEta on OverlayState; overlay renders .lco-weekly-eta div with
"~{day} at this pace" copy, hidden for credit tier and when eta is null.
6 new overlay tests covering visibility, text, and tier-gating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…etch [GET-21]

computeEtaForOrg reads snapshots from chrome.storage.local and feeds
computeWeeklyEta; result applied to overlay via applyWeeklyEta on both
ORGANIZATION_DETECTED and post-STREAM_COMPLETE paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useDashboardData computes WeeklyEta from stored snapshots on every load and
storage change; App threads weeklyEta to UsageBudgetCard; SessionBudget
renders confidence-aware copy (high/medium/low). 7 new card tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tests [GET-21]

Export usageBudgetSnapshotsKey from conversation-store so content script uses
the same key builder instead of a hardcoded string. Switch chrome.storage.local
to browser.storage.local (WXT polyfill). Add 5 new tests: reset-detection
invariant (>5pp clear, exact-5 no-clear, rising growth), key-format contract,
and storage-read parity for computeEtaForOrg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 29, 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 Apr 29, 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 52 minutes and 7 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: 2e8e1dda-18c9-41d3-9ce7-39919beae4cd

📥 Commits

Reviewing files that changed from the base of the PR and between e9ac16a and a9ddaf0.

📒 Files selected for processing (2)
  • entrypoints/sidepanel/hooks/useDashboardData.ts
  • tests/unit/weekly-cap-eta.test.ts
📝 Walkthrough

Walkthrough

Adds a weekly-cap ETA feature: persist timestamped usage snapshots, compute ETA via linear regression with confidence, surface ETA in overlay and sidepanel, and wire snapshot capture into background usage-limit handling and dashboard loading.

Changes

Cohort / File(s) Summary
ETA Computation
lib/weekly-cap-eta.ts
New pure utility: computeWeeklyEta() and formatEtaLabel(); exports UsageBudgetSnapshot, WeeklyEta, and MIN_SNAPSHOTS_FOR_ETA.
Snapshot Storage API
lib/conversation-store.ts
Adds usageBudgetSnapshotsKey(), appendUsageBudgetSnapshot(), getUsageBudgetSnapshots(), clearUsageBudgetSnapshots(), and MAX_USAGE_BUDGET_SNAPSHOTS.
Background Capture
entrypoints/background.ts
STORE_USAGE_LIMITS handler now appends session-tier snapshots, detects significant seven-day utilization drops to clear stale snapshots, and logs append/reset errors without failing the handler.
Content Script Integration
entrypoints/claude-ai.content.ts
Adds computeEtaForOrg(); on ORGANIZATION_DETECTED and STREAM_COMPLETE the script awaits ETA for session budgets and applies it to overlay state.
Overlay State & UI
lib/overlay-state.ts, ui/overlay.ts
Overlay state gains weeklyEta + applyWeeklyEta(); overlay creates .lco-weekly-eta element and conditionally renders it for session-tier budgets.
Dashboard Hook & Sidepanel
entrypoints/sidepanel/hooks/useDashboardData.ts, entrypoints/sidepanel/App.tsx, entrypoints/sidepanel/components/UsageBudgetCard.tsx
Adds weeklyEta to dashboard state and load flows; App passes weeklyEta to UsageBudgetCard; UsageBudgetCard accepts weeklyEta prop and renders confidence-dependent ETA line for session variant.
Tests
tests/unit/weekly-cap-eta.test.ts, tests/unit/conversation-store.test.ts, tests/unit/overlay-weekly-cap.test.ts, tests/unit/usage-budget-card.test.tsx
Extensive unit tests for ETA computation, snapshot CRUD/pruning/reset behavior, and UI rendering (session vs credit, confidence variants).

Sequence Diagram(s)

sequenceDiagram
    participant BG as Background Service
    participant Store as Browser Storage
    participant CS as Content Script
    participant Dashboard as Dashboard Hook
    participant UI as Sidepanel UI

    BG->>BG: STORE_USAGE_LIMITS received (session tier)
    BG->>Store: appendUsageBudgetSnapshot(accountId, {timestamp, weeklyPct, sessionPct})
    Store-->>BG: Snapshot appended (overflow pruned)
    BG->>Store: detect sevenDayUtilization drop
    alt Reset Detected
        BG->>Store: clearUsageBudgetSnapshots(accountId)
    end

    CS->>Store: computeEtaForOrg() reads snapshots on usage fetch
    Store-->>CS: snapshots[]
    CS->>CS: computeWeeklyEta(snapshots, now) -> WeeklyEta|null
    CS->>CS: applyWeeklyEta(state, eta)

    Dashboard->>Store: loadWeeklyEta() on init/tab-change
    Store-->>Dashboard: snapshots[]
    Dashboard->>Dashboard: computeWeeklyEta() -> weeklyEta

    UI->>Dashboard: reads weeklyEta from useDashboardData()
    UI->>UI: if weeklyEta exists and budget is session -> render ETA line
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I hop through snapshots, one by one,

Timestamps stacked beneath the sun.
Trends whisper when the cap will fall,
Confidence hums — I note it all.
ETA found, I twirl and run.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% 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 and specifically describes the main change—weekly-cap ETA projection for Pro/Max users—and is concise and directly related to the changeset.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all required sections completed: Summary (2-3 sentences), Type of Change (feat selected), What Was Changed (detailed file-by-file breakdown), How to Test (6 numbered steps), Checklist (all items checked), Related Issues (GET-21), and Notes for Reviewer explaining snapshot accumulation and storage access patterns.
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 52 minutes and 7 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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
entrypoints/claude-ai.content.ts (1)

713-725: ⚠️ Potential issue | 🔴 Critical

Bring the token-count bridge under the same 5-layer validation.

This listener accepts any same-window LCO_TOKEN_REQ, so a page script can trigger COUNT_TOKENS without the origin, namespace, token, or schema checks used everywhere else in this file.

Suggested guard
 window.addEventListener('message', (event) => {
-        if (event.source !== window || !event.data || event.data.type !== 'LCO_TOKEN_REQ') return;
+        if (event.origin !== window.location.origin) return;
+        if (event.source !== window) return;
+        if (!event.data || event.data.namespace !== LCO_NAMESPACE) return;
+        if (event.data.token !== sessionToken) return;
+        if (event.data.type !== 'LCO_TOKEN_REQ') return;
         const { id, text } = event.data;

As per coding guidelines: Validate all incoming postMessages with 5 layers: origin check, source check, namespace LCO_V1, session token (UUID v4 per load), schema validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@entrypoints/claude-ai.content.ts` around lines 713 - 725, The message
listener registered with window.addEventListener('message') for LCO_TOKEN_REQ
currently forwards requests to browser.runtime.sendMessage(COUNT_TOKENS) without
the five-layer validation; update the handler to: verify event.origin equals
window.location.origin, keep the existing event.source === window check, require
event.data.namespace === 'LCO_V1', require a session token field in event.data
that matches the in-memory session UUID used across this file (e.g. the file's
session token variable), and run the existing schema validator for token
requests (or add a small validateTokenRequestSchema helper) to ensure id/text
shape before calling browser.runtime.sendMessage({ type: 'COUNT_TOKENS', text
}). If any check fails, do not call COUNT_TOKENS, log a warning/error, and
respond with LCO_TOKEN_RES containing count: 0 (or no response) as per current
failure behavior.
🤖 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/background.ts`:
- Around line 423-453: The get -> maybe clear -> append sequence inside the
async message handler (the block that reads message.kind === 'session' and calls
getUsageBudgetSnapshots, clearUsageBudgetSnapshots, and
appendUsageBudgetSnapshot) must be serialized per organization and funneled
through the existing 200ms batching mechanism rather than executed directly;
change the handler to enqueue the org-scoped work (or acquire a per-org mutex)
and then perform getUsageBudgetSnapshots -> conditional
clearUsageBudgetSnapshots -> appendUsageBudgetSnapshot from the batcher/queue so
concurrent STORE_USAGE_LIMITS messages cannot interleave and all storage writes
respect the 200ms postMessage/storage batching guideline.

In `@entrypoints/claude-ai.content.ts`:
- Around line 366-376: The code is vulnerable to using a stale currentOrgId when
async work completes; capture the org id into a local variable immediately
before calling fetchAndStoreUsageLimits/computeEtaForOrg (e.g., const orgId =
currentOrgId) and, after any await (especially around computeEtaForOrg), verify
orgId === currentOrgId and abort mutating state or calling
applyWeeklyEta/overlay.render if it no longer matches; apply the same pre- and
post-await stale-org guard inside the STREAM_COMPLETE callback as well so you
never apply an ETA or update state for a different org.

In `@entrypoints/sidepanel/hooks/useDashboardData.ts`:
- Around line 275-288: Clear weeklyEta whenever the active org changes: add a
small effect that watches the authoritative org-id state (not orgIdRef.current —
use the component/state value used elsewhere for the current org) and calls
setWeeklyEta(null) whenever that id becomes falsy or changes; also, if you call
loadWeeklyEta from loadActiveConversation, add loadBudget and loadWeeklyEta to
loadActiveConversation's dependency list so the loader is re-run on org switches
(references: loadWeeklyEta, setWeeklyEta, loadBudget, loadActiveConversation,
orgIdRef).

In `@lib/conversation-store.ts`:
- Around line 830-847: appendUsageBudgetSnapshot performs an unsafe
read-modify-write on the array returned by store().get which can be clobbered by
concurrent append calls; fix it by serializing writes per account (e.g.,
introduce a per-account mutex or queue keyed by
usageBudgetSnapshotsKey(accountId)) so only one operation reads, mutates and
calls store().set at a time, update appendUsageBudgetSnapshot to acquire/release
that lock around the existing get/modify/set logic and still enforce
MAX_USAGE_BUDGET_SNAPSHOTS trimming.

In `@tests/unit/weekly-cap-eta.test.ts`:
- Around line 226-237: The weekday assertion is brittle because it expects
English abbreviations while formatEtaLabel uses
Intl.DateTimeFormat(undefined,...), so update the test in
tests/unit/weekly-cap-eta.test.ts to derive the expected weekday from the
runtime formatter instead of hard-coding English; compute expectedWeekday using
new Intl.DateTimeFormat(undefined, { weekday: 'short' }).format(new Date(BASE +
6 * HOUR_MS)) and assert that formatEtaLabel(BASE + 6 * HOUR_MS) contains that
expectedWeekday (referencing formatEtaLabel, BASE and HOUR_MS).

---

Outside diff comments:
In `@entrypoints/claude-ai.content.ts`:
- Around line 713-725: The message listener registered with
window.addEventListener('message') for LCO_TOKEN_REQ currently forwards requests
to browser.runtime.sendMessage(COUNT_TOKENS) without the five-layer validation;
update the handler to: verify event.origin equals window.location.origin, keep
the existing event.source === window check, require event.data.namespace ===
'LCO_V1', require a session token field in event.data that matches the in-memory
session UUID used across this file (e.g. the file's session token variable), and
run the existing schema validator for token requests (or add a small
validateTokenRequestSchema helper) to ensure id/text shape before calling
browser.runtime.sendMessage({ type: 'COUNT_TOKENS', text }). If any check fails,
do not call COUNT_TOKENS, log a warning/error, and respond with LCO_TOKEN_RES
containing count: 0 (or no response) as per current failure behavior.
🪄 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: a3fc5c06-9aed-4f3d-920b-1186d982e932

📥 Commits

Reviewing files that changed from the base of the PR and between c5873fa and f0f7e9e.

📒 Files selected for processing (13)
  • entrypoints/background.ts
  • entrypoints/claude-ai.content.ts
  • entrypoints/sidepanel/App.tsx
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx
  • entrypoints/sidepanel/hooks/useDashboardData.ts
  • lib/conversation-store.ts
  • lib/overlay-state.ts
  • lib/weekly-cap-eta.ts
  • tests/unit/conversation-store.test.ts
  • tests/unit/overlay-weekly-cap.test.ts
  • tests/unit/usage-budget-card.test.tsx
  • tests/unit/weekly-cap-eta.test.ts
  • ui/overlay.ts

Comment thread entrypoints/background.ts
Comment thread entrypoints/claude-ai.content.ts
Comment thread entrypoints/sidepanel/hooks/useDashboardData.ts Outdated
Comment thread lib/conversation-store.ts
Comment thread tests/unit/weekly-cap-eta.test.ts
- claude-ai.content.ts: capture orgId before async computeEtaForOrg call
  in ORGANIZATION_DETECTED handler; add stale-org guard after await so a
  rapid org switch cannot apply ETA from the previous account
- useDashboardData.ts: clear weeklyEta on logout, trigger loadBudget +
  loadWeeklyEta when org changes inside loadActiveConversation; move
  loadBudget/loadWeeklyEta declarations above loadActiveConversation so
  they are in scope for the dependency array
- weekly-cap-eta.test.ts: replace hardcoded English weekday regex with
  runtime Intl.DateTimeFormat so the test passes on any locale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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: 2

🧹 Nitpick comments (1)
tests/unit/weekly-cap-eta.test.ts (1)

131-144: ⚡ Quick win

Make the short-span flatness test assert a single expected outcome.

The current assertion accepts both null and non-null, so it will still pass if the wrong guard path is taken.

Proposed test tightening
 it('does not return null for a flat span shorter than 24h', () => {
-    // 10 snapshots over 10 hours, flat (< 24h so flatness guard does not apply)
-    const snaps = Array.from({ length: 10 }, (_, i) =>
-        snap(BASE + i * HOUR_MS, 50 + (i % 2)), // only 1pp range, but <24h
-    );
+    // 10 snapshots over 10 hours, small positive slope and <10pp range.
+    // This should bypass the 24h flatness guard and still produce an ETA.
+    const snaps = Array.from({ length: 10 }, (_, i) =>
+        snap(BASE + i * HOUR_MS, 50 + i * 0.8), // range 7.2pp, span 9h
+    );
     const now = nowAfter(10);
-    // Slope may be near zero, so null is acceptable — the key assertion is
-    // that the flatness guard (24h + <10pp) does NOT fire here.
-    // We just confirm the guard itself does not reject short spans.
-    // (A near-zero slope will still return null via the slope <= 0 guard.)
     const result = computeWeeklyEta(snaps, now);
-    // Either null (slope guard) or non-null (tiny positive slope): both are valid.
-    expect(result === null || result.hoursRemaining > 0).toBe(true);
+    expect(result).not.toBeNull();
+    expect(result!.hoursRemaining).toBeGreaterThan(0);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/weekly-cap-eta.test.ts` around lines 131 - 144, The test currently
allows both null and non-null outcomes which masks whether the flatness guard
was correctly bypassed; change the synthetic snapshots created in the test (the
snaps array built with snap and BASE/HOUR_MS) to produce a clear positive slope
(e.g., steadily increasing values by a few percentage points across the 10-hour
span) so the slope <= 0 guard cannot return null, then assert a single expected
outcome: result is non-null and result.hoursRemaining > 0 by calling
computeWeeklyEta(snaps, nowAfter(10)). Ensure you only modify the values used to
construct snaps (not computeWeeklyEta) so the test deterministically verifies
the flatness guard is not triggered for short spans.
🤖 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/hooks/useDashboardData.ts`:
- Around line 211-224: The loadWeeklyEta async handler can race with
navigation/tab switches and repopulate weeklyEta after it was intentionally
cleared; fix by adding a request-scoped guard: introduce a weeklyEtaRequestIdRef
(incremented whenever you start or explicitly clear weeklyEta), capture its
value at the top of loadWeeklyEta, and before calling setWeeklyEta verify both
orgIdRef.current === orgId and weeklyEtaRequestIdRef.current === capturedId;
also increment weeklyEtaRequestIdRef when you clear weeklyEta elsewhere so
in-flight resolutions are ignored. Ensure you reference loadWeeklyEta,
setWeeklyEta, weeklyEtaRequestIdRef and orgIdRef in the change so stale
responses do not overwrite the cleared state.

In `@tests/unit/weekly-cap-eta.test.ts`:
- Around line 5-10: Replace all em dash characters ('—') in comments and
test/describe titles with colons or a rephrased sentence; specifically update
the header AC lines and any describe/test title strings that contain an em dash
so they use ":" or plain wording instead, ensuring the TypeScript text rule is
satisfied across the file (update every occurrence in comments and string
literals).

---

Nitpick comments:
In `@tests/unit/weekly-cap-eta.test.ts`:
- Around line 131-144: The test currently allows both null and non-null outcomes
which masks whether the flatness guard was correctly bypassed; change the
synthetic snapshots created in the test (the snaps array built with snap and
BASE/HOUR_MS) to produce a clear positive slope (e.g., steadily increasing
values by a few percentage points across the 10-hour span) so the slope <= 0
guard cannot return null, then assert a single expected outcome: result is
non-null and result.hoursRemaining > 0 by calling computeWeeklyEta(snaps,
nowAfter(10)). Ensure you only modify the values used to construct snaps (not
computeWeeklyEta) so the test deterministically verifies the flatness guard is
not triggered for short spans.
🪄 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: f4329da7-b61f-48bc-99af-9d6423244b53

📥 Commits

Reviewing files that changed from the base of the PR and between f0f7e9e and e9ac16a.

📒 Files selected for processing (3)
  • entrypoints/claude-ai.content.ts
  • entrypoints/sidepanel/hooks/useDashboardData.ts
  • tests/unit/weekly-cap-eta.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • entrypoints/claude-ai.content.ts

Comment thread entrypoints/sidepanel/hooks/useDashboardData.ts
Comment thread tests/unit/weekly-cap-eta.test.ts Outdated
useDashboardData.ts: add weeklyEtaRequestIdRef (monotonic counter) to
guard loadWeeklyEta against same-org double-fire races that the existing
orgId stale-check cannot catch; increment on explicit clears (logout) so
any in-flight resolution is invalidated before setWeeklyEta runs

weekly-cap-eta.test.ts: replace all em-dash characters in describe
strings and AC comment block with colons; rewrite the flat-span test from
a tautological assertion to a deterministic positive-slope check that
actually exercises the flatness guard bypass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 29, 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 Apr 29, 2026 11:31pm

@DevanshuNEU DevanshuNEU merged commit 22755ea into OpenCodeIntel:main Apr 29, 2026
6 of 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