Skip to content

feat(landing): polish sprint - feedback widget, waitlist modal, theme fixes#232

Merged
DevanshuNEU merged 19 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/polish-sprint-launch
Jan 30, 2026
Merged

feat(landing): polish sprint - feedback widget, waitlist modal, theme fixes#232
DevanshuNEU merged 19 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/polish-sprint-launch

Conversation

@DevanshuNEU

@DevanshuNEU DevanshuNEU commented Jan 29, 2026

Copy link
Copy Markdown
Collaborator

Polish Sprint - Pre-Launch Prep

Final polish before LinkedIn soft launch. Adds user feedback capture, waitlist signups, and landing page improvements.

Features

Feedback Widget

  • Floating button (bottom-right) opens modal
  • Emoji mood selector with optional message and email capture
  • Posts to Discord webhook with rich embeds

Waitlist Modal

  • Pro plan "Join Waitlist" posts to Discord
  • Enterprise "Contact Sales" posts to Discord
  • Early bird discount messaging for Pro
  • Success animations on submit

Landing Page Updates

  • Hero: Updated messaging to showcase GitHub import
  • Badge: "Now in beta - One-click GitHub import"
  • CTA: "Import from GitHub"
  • Footer: "Connect GitHub in seconds - Free for 3 repos - Self-host available"

Bug Fixes

  • Search bar now theme-aware (white background in light mode, dark in dark mode)
  • Disabled search button during auto-typing animation to prevent partial query searches

Cleanup

  • Footer: Removed dead links (Twitter, Changelog, About, Blog, Careers, Privacy, Terms)
  • Rebranding: CodeIntel to OpenCodeIntel across 13 files
  • Docker: Added Discord webhook env var to build args

Files Changed

New Components

  • FeedbackWidget.tsx - Feedback capture modal
  • WaitlistModal.tsx - Pro/Enterprise waitlist signup

Modified

  • Hero.tsx - Updated copy, pass autoTyping prop
  • HeroSearch.tsx - Theme-aware styling, disable during auto-type
  • Footer.tsx - Cleaned dead links, rebranded
  • Pricing.tsx - Integrated waitlist modal
  • DashboardStats.tsx - Added loading skeleton
  • DashboardHome.tsx - Show skeleton during load
  • DashboardLayout.tsx - Added FeedbackWidget

Config

  • docker-compose.yml - Added webhook build arg
  • frontend/Dockerfile - Added webhook ARG/ENV
  • .env.example - Documented webhook variable

Testing

  • Feedback widget posts to Discord
  • Waitlist modal posts to Discord (Pro and Enterprise)
  • Search bar renders correctly in both themes
  • Search disabled during auto-typing animation
  • All footer links functional
  • TypeScript compiles clean

Summary by CodeRabbit

  • New Features

    • Feedback widget and modal to submit mood, message, and email
    • Waitlist modal with an enterprise contact flow
    • Backend endpoints to receive feedback and waitlist signups (posted to configured webhook)
  • Improvements

    • Dashboard stats display a loading skeleton
    • Full product rebrand from CodeIntel to OpenCodeIntel across the UI
    • Footer simplified to GitHub-first resources; Hero CTAs updated for GitHub import
    • Pricing CTAs open the waitlist modal for select plans

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

- Add FeedbackWidget with Discord webhook integration
- Floating button in bottom-right, opens modal
- Emoji mood selector + text message + email capture
- Rich Discord embeds with color coding by mood
- Add loading skeleton for DashboardStats
- Update .env.example with VITE_DISCORD_FEEDBACK_WEBHOOK
- WaitlistModal with email capture
- Posts to same Discord webhook with different embed style
- 30% early bird discount messaging
- Success animation on submit
- Pro plan button now opens modal instead of dead /waitlist link
- Badge: 'One-click GitHub import' instead of 'Free for open source'
- Subtext: 'Import any GitHub repo in one click'
- Primary CTA: 'Import from GitHub' with GitHub icon
- Footer: 'Connect GitHub in seconds • Free for 3 repos'
REMOVED:
- Twitter link (no account yet)
- Changelog, About, Blog, Careers (pages don't exist)
- Privacy, Terms (legal pages don't exist)

KEPT:
- Features, Pricing (anchor links that work)
- Docs (route exists)
- GitHub (works)
- MCP Setup (route exists)

Also linked 'Devanshu' to devanshu.dev portfolio
Updated branding in:
- Landing: Navbar, Footer, Features, FAQ, Pricing
- Dashboard: TopNav, CommandPalette
- Auth: LoginForm
- Components: FeedbackWidget, WaitlistModal
- Pages: DocsHomePage, MCPSetupPage, Playground

Also updated logo from 'CI' to 'OCI' everywhere
- Force white text with inline style (browser was overriding)
- Lighten animated placeholder for better contrast
- Add caret-white for cursor visibility
- Add VITE_DISCORD_FEEDBACK_WEBHOOK to docker-compose build args
- Add ARG/ENV to frontend Dockerfile
- Webhook now baked in during Docker build
- Enterprise button opens modal instead of mailto link
- Purple themed modal for Enterprise vs blue for Pro
- Different Discord embed: '🏢 Enterprise Inquiry' (purple)
- Custom messaging for enterprise inquiries
- All leads now go to Discord for easy tracking
Light mode: white background, dark text
Dark mode: dark background, light text

- Background: bg-white / dark:bg-zinc-900
- Text: text-foreground (theme variable)
- Border: border-zinc-200 / dark:border-zinc-800
- Placeholder: zinc-400 / dark:zinc-500
- Disabled button: zinc-100 / dark:zinc-800
Bug: Clicking Search mid-animation searched partial text like 'authen'
Fix: Added autoTyping prop to HeroSearch, button disabled during animation

- User can only search after manually typing
- Pressing Enter during animation does nothing
- Once user interacts, isTyping=false and search works normally
@vercel

vercel Bot commented Jan 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 Jan 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a feedback collection flow (frontend widget + waitlist modal), backend endpoints that post feedback and waitlist signups to a Discord webhook, an in-project rate limiter service, dashboard loading skeletons, and widespread frontend branding updates renaming "CodeIntel" to "OpenCodeIntel".

Changes

Cohort / File(s) Summary
Compose & Env
docker-compose.yml, backend/.env.example
Added DISCORD_FEEDBACK_WEBHOOK env var entry and FORWARDED_ALLOW_IPS in compose; included example webhook in env sample.
Backend Feedback API
backend/routes/feedback.py, backend/main.py
New /feedback router with POST /feedback and POST /feedback/waitlist, Pydantic models, Discord embed construction, async webhook posting, and error handling. Mounted in main.py.
Rate Limiting Service
backend/services/rate_limiter.py
Added in-memory rate_limit decorator, RateLimit/APIKey dataclasses, RateLimiter and APIKeyManager classes and public APIs for rate checking and key management.
Backend Docker CMD
backend/Dockerfile
Updated uvicorn CMD to include --proxy-headers and added comments about proxy headers and FORWARDED_ALLOW_IPS.
Frontend Feedback Widget
frontend/src/components/FeedbackWidget.tsx, frontend/src/components/dashboard/DashboardLayout.tsx
New FeedbackWidget component (floating button + modal) and integration into dashboard layout.
Waitlist Modal & Pricing
frontend/src/components/landing/WaitlistModal.tsx, frontend/src/components/landing/Pricing.tsx, frontend/src/components/landing/index.ts
Added WaitlistModal component, integrated waitlist flow into Pricing, and exported the modal.
Dashboard Loading UX
frontend/src/components/dashboard/DashboardHome.tsx, frontend/src/components/dashboard/DashboardStats.tsx
DashboardStats gains optional loading?: boolean prop and a StatsGridSkeleton; DashboardHome passes reposLoading so stats show while loading.
Hero & Search
frontend/src/components/landing/Hero.tsx, frontend/src/components/landing/HeroSearch.tsx
Hero CTA updated for GitHub import and now passes autoTyping prop; HeroSearch adds autoTyping?: boolean, updates canSearch logic, and dark-mode styling.
Branding & Copy
frontend/src/components/..., frontend/src/pages/...
Wide rebranding: string replacements and logo badge changes from "CodeIntel"/"CI" to "OpenCodeIntel"/"OCI" across many components (LoginForm, CommandPalette, TopNav, Navbar, FAQ, Features, Docs, MCPSetup, Playground, Footer, etc.).
Footer Restructure
frontend/src/components/landing/Footer.tsx
Footer simplified to 3-column layout, removed Twitter, added GitHub CTA, updated LINKS/resources, and brand attribution/links.
Frontend Additions (new files)
frontend/src/components/FeedbackWidget.tsx, frontend/src/components/landing/WaitlistModal.tsx
New interactive UI components for feedback and waitlist flows (animations, form state, API submission).

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as FeedbackWidget (Frontend)
    participant API as Backend (/api/v1/feedback)
    participant Discord as Discord Webhook (External)

    User->>Frontend: open modal, select mood, enter message/email
    User->>Frontend: submit feedback
    Frontend->>API: POST /feedback {mood,message,email}
    API->>API: validate payload, apply rate_limit
    API->>API: build Discord embed
    API->>Discord: POST embed to webhook
    alt Discord success
        Discord-->>API: 200 OK
        API-->>Frontend: 200 OK
        Frontend->>User: show success state
    else Discord failure or missing webhook
        Discord--xAPI: error
        API--xFrontend: 5xx/4xx
        Frontend->>User: show error message
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped a tiny widget into view,
Collected moods and messages too.
Webhooks carry whispers through the night,
Waitlists bloom and dashboards glow bright.
Carrots up — feedback takes flight! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: feedback widget, waitlist modal, and theme fixes for a landing page polish sprint.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@DevanshuNEU DevanshuNEU changed the title feat: Polish sprint - feedback widget, waitlist modal, landing page updates feat(landing): polish sprint - feedback widget, waitlist modal, theme fixes Jan 29, 2026

@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

🤖 Fix all issues with AI agents
In `@frontend/Dockerfile`:
- Around line 10-16: Remove the client-side exposure of the
VITE_DISCORD_FEEDBACK_WEBHOOK variable: delete the ARG/ENV lines that inject
VITE_DISCORD_FEEDBACK_WEBHOOK into the frontend build and stop reading
VITE_DISCORD_FEEDBACK_WEBHOOK from the client. Instead, store the webhook URL as
a server-side secret and implement a backend endpoint (e.g., POST /api/feedback
or a server function sendFeedback) that validates/rate-limits/authenticates
requests and forwards feedback to the Discord webhook; update the frontend to
call that endpoint rather than using VITE_DISCORD_FEEDBACK_WEBHOOK.

In `@frontend/src/components/landing/HeroSearch.tsx`:
- Around line 92-97: The submit handler (submit) currently allows Enter to
trigger onSubmit even when a search is in-flight; update the guard to match the
button disabled logic by including the searching check used in canSearch.
Concretely, change submit to check the same composite condition (value.trim(),
!autoTyping, and !searching) before calling onSubmit (or simply reuse canSearch)
so Enter cannot fire duplicate searches while searching is true.

In `@frontend/src/components/landing/WaitlistModal.tsx`:
- Around line 86-91: The early return "if (!isOpen) return null" prevents
AnimatePresence from seeing unmounts; remove that early return and always render
AnimatePresence, then render the modal markup conditionally inside it (e.g.,
{isOpen && <...>}), so the child components inside AnimatePresence (the modal
container/motion elements used in WaitlistModal and the Icon variable/Building2
or Rocket usage) can mount and unmount and trigger their exit animations; ensure
the motion elements keep their existing exit props and a stable key if needed.

In `@frontend/src/pages/DocsHomePage.tsx`:
- Line 149: Fix the typo in the GitHub URLs in the DocsHomePage component by
replacing the duplicated "OpenOpenCodeIntel" organization with the correct
"OpenCodeIntel" in the href attributes (change occurrences of
"https://github.com/OpenOpenCodeIntel/opencodeintel" and
"https://github.com/OpenOpenCodeIntel/opencodeintel/issues" to
"https://github.com/OpenCodeIntel/opencodeintel" and
".../opencodeintel/issues"); locate the JSX anchor elements in DocsHomePage.tsx
(the href attributes in the relevant <a> tags) and update both links to match
the organization used in Footer.tsx.

In `@frontend/src/pages/MCPSetupPage.tsx`:
- Line 408: The GitHub issues link in the JSX anchor inside MCPSetupPage (the
string "https://github.com/OpenOpenCodeIntel/opencodeintel/issues" used in the
<a> element) contains a duplicated organization name; update the href to the
correct organization URL (e.g.,
"https://github.com/OpenCodeIntel/opencodeintel/issues") so the link points to
the real repo issues page and ensure target="_blank" and rel remain unchanged.
- Around line 133-134: Update the incorrect GitHub organization name
"OpenOpenCodeIntel" to "OpenCodeIntel" in MCPSetupPage.tsx: fix the git clone
command inside the CodeBlock that currently shows `git clone
https://github.com/OpenOpenCodeIntel/opencodeintel.git` and also update the
GitHub issues URL near the later link (around the section referencing
issues/PRs) so both use `OpenCodeIntel/opencodeintel`; search for those exact
strings and replace them accordingly.
🧹 Nitpick comments (5)
frontend/.env.example (1)

14-15: Add a note that Vite env vars are public.
This helps prevent treating the webhook URL as a secret.

📝 Suggested clarification
 # Feedback Discord Webhook (optional)
+# Note: VITE_* vars are embedded in the client bundle. Do not put secrets here.
 VITE_DISCORD_FEEDBACK_WEBHOOK=
docker-compose.yml (1)

5-5: Consider: Container names still use "codeintel-" prefix.

The container names (codeintel-redis, codeintel-backend, codeintel-frontend) and network name (codeintel-network) haven't been updated to match the "OpenCodeIntel" rebranding applied elsewhere. This is a minor inconsistency you may want to address for consistency.

frontend/src/components/FeedbackWidget.tsx (1)

29-72: Consider adding user-facing error feedback on submission failure.

When the Discord webhook call fails, the error is logged to the console but the user receives no indication that their feedback wasn't sent. The modal closes normally after the timeout regardless of success/failure.

💡 Suggested improvement for error handling
   const handleSubmit = async () => {
     if (!mood && !message.trim()) return
     
     setSending(true)
     
     try {
       const moodInfo = moods.find(m => m.value === mood)
       
       const embed = {
         // ... embed config
       }
 
       if (DISCORD_WEBHOOK) {
-        await fetch(DISCORD_WEBHOOK, {
+        const response = await fetch(DISCORD_WEBHOOK, {
           method: 'POST',
           headers: { 'Content-Type': 'application/json' },
           body: JSON.stringify({ embeds: [embed] }),
         })
+        if (!response.ok) {
+          throw new Error('Failed to send')
+        }
       } else {
         console.log('Feedback (no webhook configured):', { mood, message, email: email || userEmail })
       }
       
       setSent(true)
       setTimeout(() => {
         setIsOpen(false)
         setSent(false)
         setMood(null)
         setMessage('')
         setEmail('')
       }, 2000)
     } catch (error) {
       console.error('Failed to send feedback:', error)
+      // Optionally show error state to user
+      // For now, still close modal but consider adding error UI
     } finally {
       setSending(false)
     }
   }
frontend/src/components/dashboard/DashboardLayout.tsx (1)

15-15: Consider updating the storage key to match the rebrand.

The storage key still uses codeintel-sidebar-collapsed while the app has been rebranded to OpenCodeIntel. This is a minor inconsistency that could be addressed for consistency, though changing it would reset existing users' sidebar preferences.

frontend/src/components/landing/Pricing.tsx (1)

133-159: Consider extracting the duplicated className logic.

The CTA styling (lines 138-142 and 151-155) is duplicated between the button and anchor elements. This could be extracted to a variable for DRY compliance.

♻️ Optional refactor
+          const ctaClassName = `block w-full py-3 px-4 rounded-lg text-center text-sm font-medium transition-all ${
+            plan.popular
+              ? 'bg-accent text-white hover:bg-accent/90 shadow-lg shadow-accent/20'
+              : 'border border-border text-foreground hover:bg-muted/50'
+          }`
+
               {(plan.ctaHref === '/waitlist' || plan.ctaHref === '/contact') ? (
                 <motion.button
                   onClick={() => handleWaitlistClick(plan.name, plan.price === 'Custom' ? 'Custom pricing' : `${plan.price}${plan.period}`)}
                   whileHover={{ scale: 1.02 }}
                   whileTap={{ scale: 0.98 }}
-                  className={`block w-full py-3 px-4 rounded-lg text-center text-sm font-medium transition-all ${
-                    plan.popular
-                      ? 'bg-accent text-white hover:bg-accent/90 shadow-lg shadow-accent/20'
-                      : 'border border-border text-foreground hover:bg-muted/50'
-                  }`}
+                  className={ctaClassName}
                 >

Comment thread frontend/Dockerfile Outdated
Comment thread frontend/src/components/landing/HeroSearch.tsx Outdated
Comment thread frontend/src/components/landing/WaitlistModal.tsx Outdated
Comment thread frontend/src/pages/DocsHomePage.tsx Outdated
Comment thread frontend/src/pages/MCPSetupPage.tsx Outdated
Comment thread frontend/src/pages/MCPSetupPage.tsx Outdated
Submit handler now reuses canSearch which includes !searching check.
Previously Enter could fire onSubmit even while a search was in flight.
Fixed typo in:
- DocsHomePage.tsx: GitHub and Issues links
- MCPSetupPage.tsx: git clone command and issues link

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

🤖 Fix all issues with AI agents
In `@frontend/src/components/FeedbackWidget.tsx`:
- Around line 57-61: The catch block in FeedbackWidget currently only logs
errors and leaves the modal without any user-visible feedback; add an error
state (e.g., const [error, setError] = useState<string | null>(null)) in the
FeedbackWidget component, set that state inside the catch
(setError(error?.message || 'Failed to send feedback')), and update the JSX to
render a visible error message area (similar to WaitlistModal) that displays the
error and optionally a retry hint; keep the existing setSending(false) in the
finally block so the spinner state is cleared.
🧹 Nitpick comments (4)
frontend/src/components/landing/WaitlistModal.tsx (1)

6-6: Consider importing API_URL from the shared config.

This duplicates the API URL definition that exists in frontend/src/config/api.ts. Using the shared config would ensure consistency if the URL construction logic changes.

♻️ Proposed refactor
-const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
+import { API_URL } from '@/config/api'

Note: Verify the export name matches your config file's API URL export (it may be BASE_URL based on the relevant snippet showing API_URL is constructed with API_PREFIX).

frontend/src/components/FeedbackWidget.tsx (1)

7-7: Same API_URL duplication as in WaitlistModal.

Consider extracting this to a shared config to maintain consistency.

backend/routes/feedback.py (2)

49-50: Consider logging the exception before returning False.

Swallowing exceptions silently makes debugging difficult when Discord posts fail. At minimum, log the error for observability.

🔍 Proposed fix
+import logging
+
+logger = logging.getLogger(__name__)

 async def post_to_discord(embed: dict) -> bool:
     """Post an embed to Discord webhook."""
     if not DISCORD_WEBHOOK_URL:
         return False
     
     try:
         async with httpx.AsyncClient() as client:
             response = await client.post(
                 DISCORD_WEBHOOK_URL,
                 json={"embeds": [embed]},
                 timeout=10.0
             )
             return response.status_code == 204
-    except Exception:
+    except Exception as e:
+        logger.error(f"Failed to post to Discord: {e}")
         return False

69-69: Use datetime.now(timezone.utc) instead of deprecated datetime.utcnow().

datetime.utcnow() is deprecated in Python 3.12+ because it returns a naive datetime. For forward compatibility, use timezone-aware UTC:

♻️ Proposed fix
-from datetime import datetime
+from datetime import datetime, timezone

# ... in embed construction:
-        "timestamp": datetime.utcnow().isoformat(),
+        "timestamp": datetime.now(timezone.utc).isoformat(),

Comment thread frontend/src/components/FeedbackWidget.tsx Outdated

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

🤖 Fix all issues with AI agents
In `@frontend/src/components/landing/WaitlistModal.tsx`:
- Around line 1-2: Remove the duplicate React import in WaitlistModal.tsx:
there's two identical lines importing useState; keep a single import statement
(import { useState } from 'react') and delete the redundant one so the symbol is
declared only once and the build error is resolved.
🧹 Nitpick comments (1)
frontend/src/components/landing/WaitlistModal.tsx (1)

44-64: Clear the auto-close timer on close/unmount.

The success setTimeout can fire after the modal is closed/unmounted, causing stale state updates. Consider storing the timer id and clearing it in handleClose and on unmount.

♻️ Proposed refactor
 export function WaitlistModal({ isOpen, onClose, planName = 'Pro', planPrice = '$19/mo' }: WaitlistModalProps) {
   const [email, setEmail] = useState('')
   const [sending, setSending] = useState(false)
   const [sent, setSent] = useState(false)
   const [error, setError] = useState('')
+  const closeTimerRef = useRef<number | null>(null)
+
+  useEffect(() => {
+    return () => {
+      if (closeTimerRef.current) {
+        clearTimeout(closeTimerRef.current)
+        closeTimerRef.current = null
+      }
+    }
+  }, [])
@@
       setSent(true)
-      setTimeout(() => {
+      if (closeTimerRef.current) clearTimeout(closeTimerRef.current)
+      closeTimerRef.current = window.setTimeout(() => {
         onClose()
         setSent(false)
         setEmail('')
       }, 2500)
@@
   const handleClose = () => {
     if (!sending) {
+      if (closeTimerRef.current) {
+        clearTimeout(closeTimerRef.current)
+        closeTimerRef.current = null
+      }
       onClose()
       setEmail('')
       setError('')
       setSent(false)
     }
   }

Add useEffect/useRef to the React import (and consolidate the duplicate import per the prior comment).

Comment thread frontend/src/components/landing/WaitlistModal.tsx Outdated
Tests were failing because feedback.py imports rate_limit decorator
which didn't exist. Added in-memory rate limiting decorator.
- Added error state with user-visible error message
- Clear error on modal open and after successful submit
- Match pattern used in WaitlistModal

@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

🤖 Fix all issues with AI agents
In `@backend/services/rate_limiter.py`:
- Around line 15-58: The decorator's in-memory store (_rate_limit_store) never
removes inactive client keys, causing unbounded growth; update the rate_limit
wrapper to evict stale keys by: after filtering timestamps for the current key
(using window_start), delete _rate_limit_store[key] if its list becomes empty,
and add a light global sweep mechanism (module-level variables like
_last_cleanup and _cleanup_interval) that runs occasionally (e.g., if now -
_last_cleanup > _cleanup_interval) to iterate _rate_limit_store and remove any
entries whose newest/last timestamp is older than window_start; reference
symbols: _rate_limit_store, rate_limit, wrapper, key, requests_per_minute.
- Around line 27-41: The wrapper coroutine currently falls back to a shared
"unknown" bucket and uses request.client.host which can be the proxy IP; update
wrapper to require a FastAPI/Starlette Request instance (raise a clear error if
missing) and derive client_id exclusively from request.client.host (relying on
server proxy header configuration) instead of defaulting to "unknown"; document
that the deployment must run Uvicorn/FastAPI with trusted proxy headers (e.g.,
--proxy-headers and --forwarded-allow-ips) so request.client.host reflects the
real client IP and avoid collapsing all callers into one rate-limiter bucket.

Comment thread backend/services/rate_limiter.py
Comment thread backend/services/rate_limiter.py
…owth

Every 60s, evict keys whose newest timestamp is stale (>60s old).
Prevents unbounded memory growth from inactive client IPs.
- rate_limit decorator now requires Request parameter (raises 500 if missing)
- Routes updated: request: Request, body: PydanticModel pattern
- Added proxy-headers flag to Dockerfile for X-Forwarded-For support
- Added FORWARDED_ALLOW_IPS env var to docker-compose
- Documented proxy header configuration in decorator docstring

Behind reverse proxy, this ensures rate limiting uses real client IP
instead of proxy IP (which would bucket all users together).
@vercel

vercel Bot commented Jan 29, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
opencodeintel Ignored Ignored Preview Jan 29, 2026 11:55pm

@DevanshuNEU

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

🤖 Fix all issues with AI agents
In `@backend/Dockerfile`:
- Around line 32-34: The Dockerfile enables proxy header processing via the
uvicorn command (CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port",
"8000", "--proxy-headers"]) but the docker-compose.yml currently sets
FORWARDED_ALLOW_IPS="*", which trusts all X-Forwarded-For headers; update
docker-compose.yml to set FORWARDED_ALLOW_IPS to the specific proxy IP or CIDR
(e.g., your reverse proxy's address like 10.0.0.0/8 or the load balancer IP)
instead of "*", so the uvicorn --proxy-headers behavior only trusts headers from
your known proxy and prevents client IP spoofing and rate-limit bypass.

In `@backend/routes/feedback.py`:
- Around line 72-73: The feedback route currently appends body.email directly
into Discord embeds (embed["fields"].append({"name": "User", "value":
body.email, ...})) which leaks PII; update backend/routes/feedback.py so you do
not send raw emails to Discord unless an explicit opt-in/config flag (e.g.,
ENABLE_SHARE_EMAILS) is enabled and the user has recorded consent—otherwise omit
the field or send a masked/placeholder value and log consent server-side; apply
the same change to the other occurrences that append the "User" field with
body.email (the other embed["fields"].append(...) calls referenced in the
review).
🧹 Nitpick comments (5)
frontend/src/components/landing/WaitlistModal.tsx (1)

6-6: Consider importing API_URL from shared config.

A shared API_URL is exported from frontend/src/config/api.ts. Using the shared constant ensures consistency across the codebase and centralizes environment variable handling.

♻️ Suggested change
-const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
+import { API_URL } from '@/config/api'

Note: Verify that API_URL from the config doesn't include the /api/v1 prefix already, and adjust the fetch URL accordingly.

backend/services/rate_limiter.py (2)

15-18: In-memory store lacks thread safety for multi-worker deployments.

The module-level _rate_limit_store and _last_cleanup are not thread-safe. With uvicorn running multiple workers (or threads via --workers flag), concurrent modifications could cause race conditions or lost updates.

The docstring mentions "single-instance deployments," which is appropriate. Consider adding a note that this also assumes single-worker mode, or use a threading.Lock if multi-worker support is needed.


51-52: Fallback to "unknown" still allows shared bucket abuse.

When request.client is None, the client ID becomes "unknown", creating a shared rate-limit bucket. While rare, this could happen in certain proxy misconfigurations. Consider logging a warning when this occurs to aid debugging.

🔍 Suggested enhancement
             # Get client IP (relies on Uvicorn proxy-headers config for real IP)
-            client_id = request.client.host if request.client else "unknown"
+            if request.client:
+                client_id = request.client.host
+            else:
+                client_id = "unknown"
+                # Consider logging: logger.warning("Rate limiter: request.client is None, using shared bucket")
backend/routes/feedback.py (2)

49-50: Avoid bare except clause; catch specific exceptions.

The bare except catches all exceptions including KeyboardInterrupt and SystemExit. Catch specific exceptions like httpx.RequestError or httpx.TimeoutException for cleaner error handling.

♻️ Suggested fix
-    except Exception:
+    except (httpx.RequestError, httpx.TimeoutException) as e:
+        # Optionally log the error for debugging
         return False

48-48: Discord webhooks may return 200 or other 2xx status codes.

While 204 is common for Discord webhooks, checking for any successful 2xx response is more robust.

♻️ Suggested fix
-            return response.status_code == 204
+            return response.is_success  # or: 200 <= response.status_code < 300

Comment thread backend/Dockerfile
Comment thread backend/routes/feedback.py
@DevanshuNEU DevanshuNEU merged commit 7a4e5bf into OpenCodeIntel:main Jan 30, 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