Skip to content

feat: Chrome MV3 extension with Clerk auth sync (#7)#93

Open
Maurya-H wants to merge 14 commits into
Venkat-Kolasani:mainfrom
Maurya-H:Extensions-feature
Open

feat: Chrome MV3 extension with Clerk auth sync (#7)#93
Maurya-H wants to merge 14 commits into
Venkat-Kolasani:mainfrom
Maurya-H:Extensions-feature

Conversation

@Maurya-H

@Maurya-H Maurya-H commented Jul 8, 2026

Copy link
Copy Markdown

Fixes #7

What this PR does

  • Adds extensions/ folder with Chrome MV3 extension
  • Auth via @clerk/chrome-extension with syncHost: https://futuretracker.online
  • Popup UI with dark theme — review/edit before save
  • Reads page metadata only (document.title, URL, og:title, og:description)
  • POST /api/opportunities with Clerk JWT
  • Backend CORS updated to allow extension origin via CORS_ORIGIN env var
  • extension/README.md with full setup instructions

Manual Test Plan

  1. Sign in at futuretracker.online
  2. Visit any job listing page
  3. Click the FutureTracker extension icon
  4. Review/edit pre-filled title and description
  5. Click Save Opportunity
  6. Confirm entry appears on Dashboard

Out of Scope (per issue #7)

  • Site-specific parsers (LinkedIn, AngelList)
  • Deadline auto-fill from page content
  • Firefox/Safari support

Known Issue

Dashboard crashes when opportunity has null deadline — pre-existing bug in src/utils/dateHelpers.js, not introduced by this PR. parseLocalDate needs a null check.

Checklist

  • New extensions/ folder — Chrome MV3 only
  • Auth via @clerk/chrome-extension with syncHost
  • Popup UI — dark theme, review/edit before save
  • Page metadata only — no site-specific scraping
  • POST /api/opportunities with Clerk JWT
  • Backend CORS updated
  • extension/README.md with setup instructions
  • Manual test plan included

Summary by CodeRabbit

  • New Features
    • Added a Manifest V3 Chrome extension with a popup that reads the active tab’s title/description/URL, lets you edit opportunity details, and saves with sign-in gating plus clear saving/saved/error states.
  • Configuration
    • Updated environment examples for Chrome extension usage and expanded CORS origin guidance.
    • Added extension build configuration and a Vite-based setup for local development.
  • Documentation
    • Added a dedicated extension README covering setup, loading, and manual testing.
  • Chores
    • Improved ignore rules for extension build artifacts and local environment files.

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

@Maurya-H is attempting to deploy a commit to the venkat-kolasani's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ 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

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Chrome MV3 extension to save opportunities with Clerk-authenticated API calls

✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Introduces a Chrome MV3 extension to capture page metadata and save opportunities.
• Uses Clerk Chrome Extension auth to mint JWTs and call backend /api/opportunities.
• Documents extension/backend setup and expands CORS configuration for extension origins.
Diagram

graph TD
  A[Chrome Extension Popup] --> B[Content Script] --> C[Web Page]
  A --> D[Backend API]
  A --> E{{Clerk (syncHost)}}
  F[Background SW] --> E
  subgraph "extensions/"
    A
    B
    F
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. On-demand script injection (no persistent content script)
  • ➕ Avoids running a content script on by default
  • ➕ Reduces perceived privacy risk and review surface
  • ➕ Can narrow required permissions/host matches
  • ➖ More moving parts (chrome.scripting.executeScript wiring and result passing)
  • ➖ Slightly more complex error handling across frames/pages
2. Backend-issued short-lived tokens (exchange Clerk token once)
  • ➕ Reduces dependency on Clerk token minting for every save
  • ➕ Allows backend to standardize auth/session semantics across web + extension
  • ➖ Introduces an additional backend endpoint and token storage/rotation logic
  • ➖ More security surface (token exchange + revocation) than direct Clerk JWT
3. Restrict permissions and matches to a minimal allowlist
  • ➕ Least-privilege posture (fewer host_permissions, narrower matches)
  • ➕ Easier to justify in Chrome Web Store review
  • ➖ Limits usefulness on arbitrary job boards unless continuously updated
  • ➖ Harder local/prod parity if allowlists diverge

Recommendation: Current approach (simple content script + Clerk JWT + direct POST) is a reasonable first iteration for speed and clarity. The main improvement worth considering is switching to on-demand script injection to avoid content script execution; it materially improves least-privilege without changing core product behavior.

Files changed (14) +312 / -0

Enhancement (7) +188 / -0
manifest.jsonDefine Chrome MV3 manifest with popup, SW, and content script +26/-0

Define Chrome MV3 manifest with popup, SW, and content script

• Adds the MV3 manifest wiring the popup UI, module service worker background script, and a content script that runs on all URLs. Declares required permissions and host permissions for FutureTracker, local dev, and Clerk domains.

extensions/manifest.json

background.jsInitialize Clerk client in MV3 service worker +12/-0

Initialize Clerk client in MV3 service worker

• Creates a Clerk background client configured with publishable key and syncHost. Loads Clerk on extension install for session sync readiness.

extensions/src/background.js

content.jsExpose page metadata via message listener +17/-0

Expose page metadata via message listener

• Implements GET_PAGE_METADATA handling to return title, description, and URL using og:* meta tags with fallbacks. Keeps extraction limited to basic metadata (no site-specific scraping).

extensions/src/content.js

api.jsAdd authenticated saveOpportunity API helper +14/-0

Add authenticated saveOpportunity API helper

• Implements a fetch wrapper that POSTs to /api/opportunities with JSON payload and Bearer token. Throws on non-2xx responses and returns parsed JSON on success.

extensions/src/lib/api.js

index.htmlAdd popup HTML shell with dark theme styling +23/-0

Add popup HTML shell with dark theme styling

• Defines the popup document, root mount point, and inline dark theme styles sized for a compact UI. Loads the React entrypoint as a module.

extensions/src/popup/index.html

main.jsxMount popup React app under ClerkProvider +13/-0

Mount popup React app under ClerkProvider

• Bootstraps React rendering and wraps the popup in ClerkProvider configured with publishable key and syncHost. Enables useAuth() inside the popup UI.

extensions/src/popup/main.jsx

popup.jsxImplement popup UI: metadata prefill, auth gating, and save flow +83/-0

Implement popup UI: metadata prefill, auth gating, and save flow

• Fetches active tab metadata via content script and allows the user to edit title/description before saving. Requires the user to be signed in via Clerk, then gets a token and POSTs a new opportunity with default category/status and deadline=null.

extensions/src/popup/popup.jsx

Documentation (1) +77 / -0
readme.mdDocument extension setup, Clerk prerequisites, and manual test plan +77/-0

Document extension setup, Clerk prerequisites, and manual test plan

• Provides end-to-end setup instructions (env vars, build, load unpacked), Clerk dashboard prerequisites, and backend CORS notes. Includes a manual test plan and folder structure overview.

extensions/readme.md

Other (6) +47 / -0
.env.exampleDocument extension ID and multi-origin CORS configuration +5/-0

Document extension ID and multi-origin CORS configuration

• Adds EXTENSION_ID guidance and an example CORS_ORIGIN value including chrome-extension://<id>. This documents how to allow the extension origin to call the backend API.

backend/.env.example

.env.exampleAdd extension env template for Clerk key and API base URL +5/-0

Add extension env template for Clerk key and API base URL

• Introduces Vite env variables for the Clerk publishable key and backend API base URL used by the extension build.

extensions/.env.example

package.jsonAdd extension build/tooling and runtime dependencies +23/-0

Add extension build/tooling and runtime dependencies

• Creates a standalone npm package for the extension using Vite + crxjs with React. Adds @clerk/chrome-extension plus React dependencies.

extensions/package.json

postcss.config.jsAdd minimal PostCSS config placeholder +1/-0

Add minimal PostCSS config placeholder

• Adds an empty PostCSS config export, likely to satisfy tooling expectations even though no PostCSS plugins are configured.

extensions/postcss.config.js

clerk.jsCentralize Clerk publishable key and syncHost configuration +2/-0

Centralize Clerk publishable key and syncHost configuration

• Defines CLERK_PUBLISHABLE_KEY from Vite env and SYNC_HOST with a localhost fallback. Used by both background and popup for consistent Clerk configuration.

extensions/src/lib/clerk.js

vite.config.jsConfigure Vite build with CRX plugin and React +11/-0

Configure Vite build with CRX plugin and React

• Sets up Vite to build a Chrome extension bundle using @crxjs/vite-plugin with the provided manifest, plus the React plugin.

extensions/vite.config.js

@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (7) 📜 Skill insights (0)

Context used

Grey Divider


Action required

1. URL field name mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
The extension sends the page URL as url, but the backend API expects link, so opportunities
created by the extension will store a null link (and lose the original page URL).
Code

extensions/src/popup/popup.jsx[R6-44]

+  const { isSignedIn, getToken } = useAuth();
+  const [data, setData] = useState({ title: '', description: '', url: '' });
+  const [status, setStatus] = useState('idle');
+
+  useEffect(() => {
+    chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
+      chrome.tabs.sendMessage(tab.id, { type: 'GET_PAGE_METADATA' }, (resp) => {
+        if (resp) setData(resp);
+      });
+    });
+  }, []);
+
+  if (!isSignedIn) {
+    return (
+      <div style={{ padding: '20px', textAlign: 'center' }}>
+        <h2 style={{ color: '#6366f1' }}>FutureTracker</h2>
+        <p>Please sign in first at</p>
+        <a 
+          href="https://futuretracker.online/sign-in" 
+          target="_blank" 
+          rel="noreferrer"
+          style={{ color: '#6366f1' }}
+        >
+          futuretracker.online
+        </a>
+      </div>
+    );
+  }
+
+  async function handleSave() {
+    setStatus('saving');
+    try {
+      const token = await getToken();
+      await saveOpportunity(token, {
+        ...data,
+        category: 'internship',
+        status: 'applied',
+        deadline: null,
+      });
Relevance

⭐⭐⭐ High

Backend create route persists link (not url) in opportunities API; mismatch would drop URL.

PR-#3
PR-#4

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The extension populates and sends url, while the backend route and validation schema only
recognize link, so the submitted URL is ignored for persistence.

extensions/src/content.js[8-16]
extensions/src/popup/popup.jsx[5-16]
extensions/src/popup/popup.jsx[35-45]
backend/src/routes/opportunities.js[110-130]
backend/src/validation/schemas.js[6-38]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The extension collects and POSTs the page URL under the `url` key, but the backend expects `link`. As a result, saved opportunities lose the link.

### Issue Context
- Content script returns `{ ..., url: window.location.href }`.
- Popup saves `{ ...data }` directly.
- Backend `POST /api/opportunities` reads `link` and stores `link: link || null`.

### Fix Focus Areas
- extensions/src/content.js[8-16]
- extensions/src/popup/popup.jsx[5-49]

### Suggested change
- Either rename `url` -> `link` in the content script response and popup state, or map it at save time:
 - `await saveOpportunity(token, { ...data, link: data.url, ... })` and ensure `url` is not sent (to avoid confusion).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. No parser unit test setup ✓ Resolved 📎 Requirement gap ☼ Reliability
Description
The extension package has no test script or unit test harness for parser logic, so required parser
pure-function testing is not present. This increases regression risk as parsers are added or
modified.
Code

extensions/package.json[R6-9]

+  "scripts": {
+    "dev": "vite build --watch",
+    "build": "vite build"
+  },
Relevance

⭐⭐ Medium

Repo values tests/CI (PR #29) but no evidence of enforcing tests for new subpackages like
extensions.

PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
extensions/package.json defines only dev and build scripts and no test script, indicating
unit test execution is not provided for parser pure functions.

Unit tests added for parser pure functions (no browser required)
extensions/package.json[6-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
No unit test script/harness is defined for parser logic.

## Issue Context
Compliance requires unit tests for parser pure functions (no browser runtime), covering at least Wellfound and the chosen ATS parser.

## Fix Focus Areas
- extensions/package.json[6-9]
- extensions/readme.md[63-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Wrong default syncHost ✓ Resolved 🐞 Bug ≡ Correctness
Description
SYNC_HOST defaults to http://localhost:3000, but the extension setup docs/.env.example don’t
define VITE_SYNC_HOST, so builds created following the README will attempt Clerk session sync
against localhost instead of the deployed site.
Code

extensions/src/lib/clerk.js[R1-2]

+export const CLERK_PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
+export const SYNC_HOST = import.meta.env.VITE_SYNC_HOST || 'http://localhost:3000';
Relevance

⭐⭐ Medium

Config/docs mismatches often fixed (PR #5), but no prior precedent around VITE_SYNC_HOST defaults.

PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code falls back to localhost when VITE_SYNC_HOST is unset, and neither the extension
.env.example nor README includes that variable, making the default path likely.

extensions/src/lib/clerk.js[1-2]
extensions/.env.example[1-5]
extensions/readme.md[20-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The extension’s Clerk `syncHost` defaults to `http://localhost:3000`, but the documented setup doesn’t instruct setting `VITE_SYNC_HOST`. This makes production builds misconfigured by default.

### Issue Context
The popup wraps the UI in `<ClerkProvider syncHost={SYNC_HOST} />` and background also uses the same `SYNC_HOST` value.

### Fix Focus Areas
- extensions/src/lib/clerk.js[1-2]
- extensions/.env.example[1-5]
- extensions/readme.md[20-47]

### Suggested change
- Prefer defaulting to the production host (e.g. `https://futuretracker.online`) and explicitly document how to override for local dev.
- Add `VITE_SYNC_HOST=...` to `.env.example` and README examples.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Missing LinkedIn URLs in README 📎 Requirement gap ⚙ Maintainability
Description
The extension README manual test plan does not include supported LinkedIn URL patterns or 2+ real
LinkedIn job URLs. This makes the LinkedIn feature unverifiable per the documentation/testing
requirements.
Code

extensions/readme.md[R49-56]

+## Manual Test Plan
+
+1. Sign in at futuretracker.online
+2. Visit any job listing page
+3. Click the FutureTracker extension icon
+4. Review and edit the pre-filled title and description
+5. Click **Save Opportunity**
+6. Go to Dashboard and confirm the entry appears
Relevance

⭐⭐ Medium

Docs/test-plan tweaks sometimes accepted (PR #5), but no precedent requiring real external URLs in
README.

PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The README manual test plan says "Visit any job listing page" and provides no LinkedIn URL patterns
or real LinkedIn job URLs as required.

LinkedIn implementation documentation and PR manual test plan provided
extensions/readme.md[49-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Documentation lacks supported LinkedIn URL patterns and real-world test URLs required for verification.

## Issue Context
Compliance requires README updates with LinkedIn URL patterns and a PR manual test plan containing 2+ real LinkedIn job URLs.

## Fix Focus Areas
- extensions/readme.md[49-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Unhandled sendMessage failures ✓ Resolved 🐞 Bug ☼ Reliability
Description
Popup metadata loading assumes an active tab with a reachable content script and does not check
tab?.id or chrome.runtime.lastError, so it can fail silently (or throw) on restricted pages or
when messaging fails.
Code

extensions/src/popup/popup.jsx[R10-15]

+  useEffect(() => {
+    chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
+      chrome.tabs.sendMessage(tab.id, { type: 'GET_PAGE_METADATA' }, (resp) => {
+        if (resp) setData(resp);
+      });
+    });
Relevance

⭐⭐ Medium

No historical review evidence for Chrome extension messaging error-handling; extension code appears
new to repo.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The popup directly dereferences tab.id and ignores message errors; there is no error-handling
branch for missing tabs or failed messaging.

extensions/src/popup/popup.jsx[10-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The popup calls `chrome.tabs.sendMessage(tab.id, ...)` without verifying `tab`/`tab.id` and without checking `chrome.runtime.lastError`. When content scripts aren’t available (or the active tab isn’t a normal page), the popup won’t populate fields and provides no actionable feedback.

### Issue Context
Content scripts are configured via the manifest, but they won’t execute on every possible active tab type; messaging can fail even when a tab exists.

### Fix Focus Areas
- extensions/src/popup/popup.jsx[10-16]

### Suggested change
- Guard `if (!tab?.id) { setStatus('error'); return; }`
- In the `sendMessage` callback, check `chrome.runtime.lastError` and show a user-facing error (e.g., “Cannot read this page”).
- Consider falling back to using `tab.title`/`tab.url` when messaging fails.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Overbroad manifest access ✓ Resolved 🐞 Bug ⛨ Security
Description
The manifest injects the content script on <all_urls> and requests the sensitive cookies
permission even though the extension code shown only reads page metadata on demand, increasing
privacy exposure and Chrome Web Store review risk.
Code

extensions/manifest.json[R11-24]

+  "permissions": ["activeTab", "storage", "scripting","cookies"],
+  "host_permissions": [
+    "https://futuretracker.online/*",
+      "http://localhost:3000/*",
+    "http://localhost:3001/*",
+    "https://*.clerk.accounts.dev/*"
+  ],
+  "background": { "service_worker": "src/background.js", "type": "module" },
+  "content_scripts": [
+    {
+      "matches": ["<all_urls>"],
+      "js": ["src/content.js"],
+      "run_at": "document_idle"
+    }
Relevance

⭐⭐ Medium

No extension precedents; security-hardening feedback is mixed historically (many rejected, some
accepted).

PR-#4
PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The manifest declares <all_urls> injection and cookies permission, while the content script
implementation only extracts basic metadata and does not reference cookie APIs.

extensions/manifest.json[11-25]
extensions/src/content.js[1-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The extension requests broad capabilities (`cookies` permission and `<all_urls>` content script injection) beyond what’s needed for the current feature (read metadata when the user opens the popup).

### Issue Context
The content script only reads `document.title`, `og:*` meta tags, and `window.location.href` when requested.

### Fix Focus Areas
- extensions/manifest.json[11-25]
- extensions/src/content.js[1-17]

### Suggested change
- Narrow `content_scripts.matches` to `http://*/*` and `https://*/*` (or further to expected job sites) and/or inject dynamically using `activeTab` + `chrome.scripting.executeScript`.
- Remove `cookies` if not required; if Clerk requires it, document why and limit host access as tightly as possible.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Clerk load only on install ✓ Resolved 🐞 Bug ☼ Reliability
Description
The MV3 background service worker calls clerkClient.load() only in the onInstalled handler, so
after service worker restarts (common in MV3) there is no code path ensuring Clerk is loaded for
auth sync.
Code

extensions/src/background.js[R4-12]

+const clerkClient = createClerkClient({
+  publishableKey: CLERK_PUBLISHABLE_KEY,
+  syncHost: SYNC_HOST,
+});
+
+chrome.runtime.onInstalled.addListener(async () => {
+  await clerkClient.load();
+  console.log('FutureTracker extension installed, Clerk loaded');
+});
Relevance

⭐⭐ Medium

No historical evidence on MV3 service-worker restart initialization patterns in this repo.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The only clerkClient.load() invocation is inside chrome.runtime.onInstalled, and there is no
other initialization path in the background script.

extensions/src/background.js[1-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`clerkClient.load()` is only triggered on extension installation. MV3 service workers can be suspended and restarted, so relying on `onInstalled` alone risks running without an initialized Clerk client.

### Issue Context
The background service worker is declared in the manifest and is the integration point for Clerk background APIs.

### Fix Focus Areas
- extensions/src/background.js[1-12]

### Suggested change
- Call `await clerkClient.load()` during module initialization (with top-level async pattern) or register additional lifecycle hooks such as `chrome.runtime.onStartup`.
- Add error handling/logging around `load()` failures to aid debugging.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

8. Confusing CORS env example ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
backend/.env.example defines CORS_ORIGIN twice and introduces an EXTENSION_ID variable that
isn’t consumed by backend runtime config, making it unclear which CORS_ORIGIN value should be used
to enable extension CORS.
Code

backend/.env.example[R7-14]

# Frontend URL for CORS (use comma-separated for multiple origins)
CORS_ORIGIN=http://localhost:3000

+
+# Chrome Extension (get ID from chrome://extensions after loading unpacked)
+EXTENSION_ID=your_extension_id_here
+CORS_ORIGIN=http://localhost:3000,chrome-extension://your_extension_id_here
+
Relevance

⭐⭐⭐ High

Team frequently accepts env/docs clarity fixes (doc corrections accepted in PR #4 and PR #5).

PR-#4
PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The env example duplicates the same key (CORS_ORIGIN) and adds EXTENSION_ID, while backend
runtime configuration uses only process.env.CORS_ORIGIN (comma-split).

backend/.env.example[7-14]
backend/src/app.js[61-68]
extensions/readme.md[58-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The example env file sets `CORS_ORIGIN` twice and adds `EXTENSION_ID`, but backend CORS config only reads `CORS_ORIGIN`. This creates ambiguity for developers copying env values.

### Issue Context
Backend CORS config splits `process.env.CORS_ORIGIN` by comma to allow multiple origins.

### Fix Focus Areas
- backend/.env.example[7-14]
- backend/src/app.js[61-68]
- extensions/readme.md[58-62]

### Suggested change
- Keep a single `CORS_ORIGIN=...` line that includes both the web origin and `chrome-extension://<id>`.
- Either remove `EXTENSION_ID` from the example or explicitly label it as a helper placeholder (not read by backend code).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. No LinkedIn matcher/selectors 📎 Requirement gap ≡ Correctness
Description
The extension only extracts generic OpenGraph metadata and does not include any LinkedIn
hostname/URL matching or stable DOM selectors for LinkedIn job detail pages. This fails the required
LinkedIn-specific parsing implementation and documentation expectations.
Code

extensions/src/content.js[R1-14]

+function getMeta(name) {
+  const el = document.querySelector(
+    `meta[property="${name}"], meta[name="${name}"]`
+  );
+  return el ? el.content : null;
+}
+
+chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+  if (msg.type === 'GET_PAGE_METADATA') {
+    sendResponse({
+      title: getMeta('og:title') || document.title,
+      description: getMeta('og:description') || '',
+      url: window.location.href,
+    });
Relevance

⭐ Low

LinkedIn-specific parsing is stated out-of-scope in PR; no history showing strict enforcement here.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR-added content.js returns only og:title, og:description, and window.location.href with no
LinkedIn hostname match or DOM selector-based extraction, and the extension README contains no
LinkedIn selector documentation.

LinkedIn job page parsing implemented with hostname match and stable selectors
extensions/src/content.js[1-17]
extensions/readme.md[1-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The extension currently uses only generic metadata extraction and has no LinkedIn-specific matcher/DOM selector extraction or selector documentation.

## Issue Context
Compliance requires a LinkedIn parsing path (hostname/URL match) using stable DOM selectors, with selectors documented for maintainability.

## Fix Focus Areas
- extensions/src/content.js[1-17]
- extensions/readme.md[1-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Firefox MV3 target missing 📎 Requirement gap ⚙ Maintainability
Description
Only a Chrome MV3 extension is added and the documentation covers only Chrome loading and origins;
there is no Firefox build target, sideloading guidance, or moz-extension CORS origin guidance. This
fails the Firefox parity and documentation requirements.
Code

extensions/readme.md[R1-40]

+# FutureTracker Chrome Extension
+
+Save job/internship listings to FutureTracker with one click.
+
+## Prerequisites
+
+Before setup, make sure these are done in your Clerk dashboard:
+- **Native API** is enabled for your Clerk application
+- **Extension origin** is added as an allowed origin: `chrome-extension://<YOUR_EXTENSION_ID>`
+- **Bot protection** is disabled during development
+
+## Setup
+
+### 1. Install dependencies
+```bash
+cd extensions
+npm install
+```
+
+### 2. Environment variables
+Copy `.env.example` to `.env` and fill in your values:
+VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here
+VITE_API_BASE=http://localhost:3001
+
+### 3. Build the extension
+```bash
+npm run build
+```
+
+### 4. Load in Chrome
+1. Open Chrome and go to `chrome://extensions`
+2. Enable **Developer mode** (top right toggle)
+3. Click **Load unpacked**
+4. Select the `extensions/dist` folder
+
+### 5. Get a stable Extension ID
+1. Go to `chrome://extensions`
+2. Copy your extension ID
+3. Add `chrome-extension://<YOUR_ID>` to allowed origins in Clerk dashboard
+
Relevance

⭐ Low

Firefox parity is explicitly out-of-scope in PR description; no repo history enforcing Firefox
support.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The README is explicitly Chrome-only (title and Chrome loading steps) and provides no
Firefox/about:debugging or moz-extension:// CORS guidance; backend env example only documents
chrome-extension://... origins.

Firefox MV3 build target added with shared code (no duplicated business logic)
Firefox extension can be loaded as a temporary add-on and basic functionality works
Firefox Clerk session sync works after signing in on futuretracker.online
Firefox popup can save an opportunity via POST /api/opportunities and metadata extraction works
Firefox documentation includes setup, sideloading, and CORS origin instructions
extensions/readme.md[1-40]
backend/.env.example[11-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds only a Chrome MV3 extension and lacks a Firefox MV3 build target, loading instructions, Clerk sync verification, and CORS guidance for `moz-extension://<uuid>` origins.

## Issue Context
Compliance requires a Firefox MV3 target with shared business logic and documentation for setup/sideloading and CORS origins.

## Fix Focus Areas
- extensions/readme.md[1-77]
- extensions/manifest.json[1-26]
- backend/.env.example[7-14]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
11. Popup lacks LinkedIn field mapping 📎 Requirement gap ≡ Correctness
Description
The popup UI is populated from a generic GET_PAGE_METADATA response and does not map
LinkedIn-specific fields (canonical job URL, notes like company/location/employment type) for user
review. This can also surface noisy titles (e.g., document.title) instead of a true job title.
Code

extensions/src/popup/popup.jsx[R7-16]

+  const [data, setData] = useState({ title: '', description: '', url: '' });
+  const [status, setStatus] = useState('idle');
+
+  useEffect(() => {
+    chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
+      chrome.tabs.sendMessage(tab.id, { type: 'GET_PAGE_METADATA' }, (resp) => {
+        if (resp) setData(resp);
+      });
+    });
+  }, []);
Relevance

⭐ Low

No historical evidence; current PR explicitly marks LinkedIn/site-specific parsing as out-of-scope.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The popup always requests GET_PAGE_METADATA and sets data directly from the generic response,
while the content script only returns title, description, and url based on OpenGraph metadata
or document.title, with no LinkedIn-specific mapping.

LinkedIn extracted fields correctly mapped to popup review form with safe deadline handling
extensions/src/popup/popup.jsx[7-16]
extensions/src/content.js[8-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The popup is wired only to generic metadata and lacks LinkedIn-specific field mapping (clean title, canonical link, notes fields, and conditional deadline population).

## Issue Context
Compliance expects LinkedIn-extracted fields to be shown in the popup review form with correct mapping and safe deadline handling.

## Fix Focus Areas
- extensions/src/popup/popup.jsx[7-16]
- extensions/src/content.js[8-15]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. No parser registry interface 📎 Requirement gap ⚙ Maintainability
Description
Extraction is hardcoded to a single generic metadata handler and there is no parser registry
implementing canParse(url) / extract(document) with a generic fallback. This blocks adding
maintainable site-specific parsers.
Code

extensions/src/content.js[R1-17]

+function getMeta(name) {
+  const el = document.querySelector(
+    `meta[property="${name}"], meta[name="${name}"]`
+  );
+  return el ? el.content : null;
+}
+
+chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+  if (msg.type === 'GET_PAGE_METADATA') {
+    sendResponse({
+      title: getMeta('og:title') || document.title,
+      description: getMeta('og:description') || '',
+      url: window.location.href,
+    });
+  }
+  return true;
+});
Relevance

⭐ Low

Parser registry/interface work appears out-of-scope; no historical acceptance evidence for this
architecture requirement.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The content script implements only a single generic getMeta() approach and the documented folder
structure lists no parsers/registry modules under extensions/src, indicating the required
interface/registry is not implemented.

Parser registry implemented with required interface and generic fallback
extensions/src/content.js[1-17]
extensions/readme.md[63-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
There is no modular parser registry with the required `canParse(url)` / `extract(document)` interface and generic fallback; extraction is hardcoded.

## Issue Context
Compliance requires a registry-based approach so site-specific parsers can be added while keeping generic parsing as fallback.

## Fix Focus Areas
- extensions/src/content.js[1-17]
- extensions/readme.md[63-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. No Wellfound or ATS parsers 📎 Requirement gap ≡ Correctness
Description
The PR adds only generic OpenGraph metadata extraction and does not implement a Wellfound
(AngelList) parser or any ATS parser (Greenhouse/Lever). This reduces extraction quality on common
job boards and fails the required host support.
Code

extensions/src/content.js[R10-14]

+    sendResponse({
+      title: getMeta('og:title') || document.title,
+      description: getMeta('og:description') || '',
+      url: window.location.href,
+    });
Relevance

⭐ Low

Wellfound/ATS parsers explicitly out-of-scope; no repo history enforcing these host parsers.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
content.js only returns generic title/description/url from OpenGraph metadata and has no
host-based parsing logic for Wellfound or ATS pages.

Wellfound (AngelList) parser extracts job title, company, and canonical link
At least one ATS parser (Greenhouse or Lever) implemented with manual test evidence
extensions/src/content.js[10-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
No site-specific parsers exist for Wellfound/AngelList or a common ATS (Greenhouse/Lever); extraction is generic only.

## Issue Context
Compliance requires at least Wellfound plus one ATS parser and manual test evidence for the supported ATS host.

## Fix Focus Areas
- extensions/src/content.js[1-17]
- extensions/readme.md[49-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. No company/deadline quick-add 📎 Requirement gap ≡ Correctness
Description
The quick-add flow does not extract or display company information and always saves `deadline:
null`, so it cannot autofill a deadline even when available on supported pages. This fails the
required quick-add extraction completeness.
Code

extensions/src/popup/popup.jsx[R39-44]

+      await saveOpportunity(token, {
+        ...data,
+        category: 'internship',
+        status: 'applied',
+        deadline: null,
+      });
Relevance

⭐ Low

No historical evidence; current PR explicitly marks deadline autofill/company extraction as
out-of-scope.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The content script only provides title, description, and url, and the save payload explicitly
sets deadline: null with no company field anywhere in the extracted data or posted payload.

Extension enables quick-add opportunities from job pages with one-click save via popup
extensions/src/content.js[10-14]
extensions/src/popup/popup.jsx[39-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Quick-add payload/UI lacks company extraction and does not support deadline autofill when present; it always posts `deadline: null`.

## Issue Context
Compliance requires company information extraction and deadline autofill support when available (without guessing).

## Fix Focus Areas
- extensions/src/content.js[10-14]
- extensions/src/popup/popup.jsx[39-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread extensions/readme.md Outdated
Comment thread extensions/package.json
Comment thread extensions/src/popup/popup.jsx Outdated
Comment thread extensions/src/lib/clerk.js Outdated
Comment thread extensions/src/background.js Outdated
Comment thread extensions/src/popup/popup.jsx
Comment thread extensions/manifest.json Outdated
Comment thread backend/.env.example 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: 6

🧹 Nitpick comments (6)
extensions/manifest.json (2)

12-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dev-only host permissions shipped alongside production origin.

http://localhost:3000/* and http://localhost:3001/* are included next to the production futuretracker.online origin. Consider gating these via a build-time manifest transform (e.g., in vite.config.js) so only the production host ships in release builds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/manifest.json` around lines 12 - 17, The manifest currently ships
dev-only localhost host_permissions alongside the production origin, so update
the manifest generation to strip those entries from release builds. Adjust the
build-time manifest transform in vite.config.js (or the manifest pipeline that
produces extensions/manifest.json) so host_permissions includes only the
production futuretracker.online and Clerk domains for production, while
preserving localhost entries only in development.

11-25: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

Content script runs on every page despite scripting permission being available.

content_scripts matches <all_urls> and injects unconditionally on document_idle for every page the user visits, even though activeTab + scripting are already declared. Given the popup only needs metadata when the user actively opens it (per content.js's GET_PAGE_METADATA handler), consider injecting the script on-demand with chrome.scripting.executeScript from the popup/background using activeTab, and drop the static content_scripts entry. This reduces the Chrome install-time permission warning ("Read and change your data on all websites") and limits code execution to user-initiated actions only, which also improves Chrome Web Store review odds.

🔒 Suggested direction
-  "permissions": ["activeTab", "storage", "scripting","cookies"],
+  "permissions": ["activeTab", "storage", "scripting", "cookies"],
   "host_permissions": [
     "https://futuretracker.online/*",
     "http://localhost:3000/*",
     "http://localhost:3001/*",
     "https://*.clerk.accounts.dev/*"
   ],
   "background": { "service_worker": "src/background.js", "type": "module" },
-  "content_scripts": [
-    {
-      "matches": ["<all_urls>"],
-      "js": ["src/content.js"],
-      "run_at": "document_idle"
-    }
-  ]
+  // Inject src/content.js on demand via chrome.scripting.executeScript
+  // when the popup opens, using the activeTab permission instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/manifest.json` around lines 11 - 25, The manifest is still
registering a static content script that runs on every page, which defeats the
purpose of using activeTab and scripting. Remove the unconditional
content_scripts entry from the manifest and load src/content.js on demand from
the popup/background flow using chrome.scripting.executeScript when the user
opens the popup or triggers metadata collection. Keep the existing
GET_PAGE_METADATA path in content.js working with the new injection approach,
and rely on activeTab for the temporary page access.
extensions/src/popup/popup.jsx (1)

54-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Labels not associated with their inputs.

<label> elements have no htmlFor, so screen readers can't programmatically associate them with the Title/Description fields.

♿ Suggested fix
-      <label>Title</label>
+      <label htmlFor="opp-title">Title</label>
       <input
+        id="opp-title"
         value={data.title}
         ...
       />
-      <label>Description</label>
+      <label htmlFor="opp-description">Description</label>
       <textarea
+        id="opp-description"
         value={data.description}
         ...
       />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 54 - 71, The Title and
Description labels in popup.jsx are not programmatically associated with their
form controls, so update the input/textarea markup in the popup component to
connect each <label> to its corresponding field using matching identifiers in
the popup rendering logic around the Title and Description inputs. Use the
existing data.title and data.description controls in the popup component to add
the necessary label-to-control linkage so screen readers can correctly identify
each field.
extensions/src/content.js (1)

8-17: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Only return true when actually responding asynchronously.

The listener returns true unconditionally, even for message types it doesn't handle, which needlessly keeps the response channel open and can trigger Chrome's "message channel closed" warnings for unhandled message types.

🔧 Proposed fix
 chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
   if (msg.type === 'GET_PAGE_METADATA') {
     sendResponse({
       title: getMeta('og:title') || document.title,
       description: getMeta('og:description') || '',
       url: window.location.href,
     });
+    return true;
   }
-  return true;
+  return false;
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/content.js` around lines 8 - 17, Update the
chrome.runtime.onMessage.addListener handler in content.js so it only returns
true when GET_PAGE_METADATA is actually handled and sendResponse is used, and
otherwise returns false/undefined for unhandled msg.type values. Keep the
response channel open only for the GET_PAGE_METADATA path by tying the return
value to that branch, using the existing listener and sendResponse logic.
extensions/.env.example (1)

1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing VITE_SYNC_HOST in env example.

extensions/src/lib/clerk.js reads import.meta.env.VITE_SYNC_HOST (falling back to http://localhost:3000), but it's undocumented here. Anyone following this file for prod setup will silently ship with the wrong sync host.

📝 Proposed addition
 # Clerk Authentication (Extension)
 VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here
+VITE_SYNC_HOST=https://futuretracker.online
 
 # Backend API URL
 VITE_API_BASE=http://localhost:3001
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/.env.example` around lines 1 - 5, Add the missing VITE_SYNC_HOST
entry to the extension env example so it matches what
extensions/src/lib/clerk.js reads from import.meta.env.VITE_SYNC_HOST. Update
the .env.example alongside VITE_CLERK_PUBLISHABLE_KEY and VITE_API_BASE to
document the sync host used by the Clerk integration, so production setup
doesn’t rely on the fallback localhost value.
extensions/src/background.js (1)

1-12: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Update the Clerk background worker pattern

  • @clerk/chrome-extension/background is deprecated in @clerk/chrome-extension@^3.1.47; switch to @clerk/chrome-extension/client with background: true.
  • If Clerk is needed in the service worker, initialize it in the listener that actually uses it rather than onInstalled, since that hook runs only once per install/update.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/background.js` around lines 1 - 12, The background worker
setup is using the deprecated Clerk background import and initializing Clerk
only in onInstalled, which runs just once. Update the background entrypoint to
use createClerkClient from `@clerk/chrome-extension/client` with background: true,
and move the clerkClient initialization/load into the service worker listener or
handler that actually needs Clerk rather than onInstalled. Keep the existing
CLERK_PUBLISHABLE_KEY and SYNC_HOST wiring, and adjust the current clerkClient
usage in the background script accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/.env.example`:
- Around line 10-13: The example environment file has a duplicated CORS_ORIGIN
setting, which makes the allowlist ambiguous. Update the backend/.env.example
entry to keep only one CORS_ORIGIN line with the full comma-separated origin
list, and remove the stale duplicate near EXTENSION_ID so the example is
unambiguous.

In `@extensions/src/lib/api.js`:
- Around line 1-14: The saveOpportunity API call needs a timeout and an early
check for a missing auth token. Update saveOpportunity in api.js to
short-circuit when token is falsy instead of sending a Bearer null header, and
add timeout handling around the fetch request so a stalled request fails cleanly
rather than leaving the popup stuck. Use the existing API_BASE and
saveOpportunity symbols to locate the request logic, and make sure the timeout
error is surfaced through the same rejection path as other request failures.

In `@extensions/src/popup/main.jsx`:
- Around line 1-4: The import in main.jsx uses a filename casing that does not
match the actual popup component file, so update the reference in the main
entrypoint to match the exact on-disk casing used by Popup.jsx/popup.jsx. Check
the import of Popup in main.jsx and either rename the file or adjust the import
path so the symbol resolves correctly on case-sensitive filesystems.

In `@extensions/src/popup/popup.jsx`:
- Around line 39-44: The save payload in popup.jsx is hardcoding the listing
metadata, which mislabels non-internship items and assumes every listing is
already applied. Update the save flow around the saveOpportunity call to use the
actual listing type from the current data instead of forcing category:
'internship', and only set status to 'applied' when that reflects the user’s
real action. Make sure the fields passed from the popup’s submit/save handler
preserve the listing’s true category and application state.
- Around line 10-16: The popup’s metadata fetch in useEffect is missing error
handling and a null guard: tab.id is used directly and chrome.runtime.lastError
is ignored when chrome.tabs.sendMessage fails. Update the
chrome.tabs.query/sendMessage flow to verify the active tab exists before
sending, and handle chrome.runtime.lastError in the sendMessage callback so
failures are surfaced instead of leaving the UI blank. Use the existing
useEffect callback and the GET_PAGE_METADATA message path to locate the fix.
- Line 6: The popup auth state is reading isSignedIn before Clerk finishes
initializing, which can briefly send authenticated users down the signed-out
path. Update the popup logic around useAuth() so the signed-in/signed-out branch
is gated by isLoaded as well as isSignedIn, and only render auth-dependent UI
once Clerk has finished loading; use the existing useAuth hook and the popup
component as the place to apply this guard.

---

Nitpick comments:
In `@extensions/.env.example`:
- Around line 1-5: Add the missing VITE_SYNC_HOST entry to the extension env
example so it matches what extensions/src/lib/clerk.js reads from
import.meta.env.VITE_SYNC_HOST. Update the .env.example alongside
VITE_CLERK_PUBLISHABLE_KEY and VITE_API_BASE to document the sync host used by
the Clerk integration, so production setup doesn’t rely on the fallback
localhost value.

In `@extensions/manifest.json`:
- Around line 12-17: The manifest currently ships dev-only localhost
host_permissions alongside the production origin, so update the manifest
generation to strip those entries from release builds. Adjust the build-time
manifest transform in vite.config.js (or the manifest pipeline that produces
extensions/manifest.json) so host_permissions includes only the production
futuretracker.online and Clerk domains for production, while preserving
localhost entries only in development.
- Around line 11-25: The manifest is still registering a static content script
that runs on every page, which defeats the purpose of using activeTab and
scripting. Remove the unconditional content_scripts entry from the manifest and
load src/content.js on demand from the popup/background flow using
chrome.scripting.executeScript when the user opens the popup or triggers
metadata collection. Keep the existing GET_PAGE_METADATA path in content.js
working with the new injection approach, and rely on activeTab for the temporary
page access.

In `@extensions/src/background.js`:
- Around line 1-12: The background worker setup is using the deprecated Clerk
background import and initializing Clerk only in onInstalled, which runs just
once. Update the background entrypoint to use createClerkClient from
`@clerk/chrome-extension/client` with background: true, and move the clerkClient
initialization/load into the service worker listener or handler that actually
needs Clerk rather than onInstalled. Keep the existing CLERK_PUBLISHABLE_KEY and
SYNC_HOST wiring, and adjust the current clerkClient usage in the background
script accordingly.

In `@extensions/src/content.js`:
- Around line 8-17: Update the chrome.runtime.onMessage.addListener handler in
content.js so it only returns true when GET_PAGE_METADATA is actually handled
and sendResponse is used, and otherwise returns false/undefined for unhandled
msg.type values. Keep the response channel open only for the GET_PAGE_METADATA
path by tying the return value to that branch, using the existing listener and
sendResponse logic.

In `@extensions/src/popup/popup.jsx`:
- Around line 54-71: The Title and Description labels in popup.jsx are not
programmatically associated with their form controls, so update the
input/textarea markup in the popup component to connect each <label> to its
corresponding field using matching identifiers in the popup rendering logic
around the Title and Description inputs. Use the existing data.title and
data.description controls in the popup component to add the necessary
label-to-control linkage so screen readers can correctly identify each field.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2899598b-a8e1-4160-8c51-6be474a8eaed

📥 Commits

Reviewing files that changed from the base of the PR and between ffd56aa and e4304c4.

⛔ Files ignored due to path filters (4)
  • extensions/package-lock.json is excluded by !**/package-lock.json
  • extensions/public/icons/128.png is excluded by !**/*.png
  • extensions/public/icons/16.png is excluded by !**/*.png
  • extensions/public/icons/48.png is excluded by !**/*.png
📒 Files selected for processing (15)
  • .gitignore
  • backend/.env.example
  • extensions/.env.example
  • extensions/manifest.json
  • extensions/package.json
  • extensions/postcss.config.js
  • extensions/readme.md
  • extensions/src/background.js
  • extensions/src/content.js
  • extensions/src/lib/api.js
  • extensions/src/lib/clerk.js
  • extensions/src/popup/index.html
  • extensions/src/popup/main.jsx
  • extensions/src/popup/popup.jsx
  • extensions/vite.config.js

Comment thread backend/.env.example
Comment thread extensions/src/lib/api.js
Comment on lines +1 to +14
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:3001';

export async function saveOpportunity(token, payload) {
const res = await fetch(`${API_BASE}/api/opportunities`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
return res.json();
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a request timeout and guard against a missing token.

fetch has no timeout, so a stalled connection leaves the popup's "Saving..." state hanging indefinitely with no way for the user to recover short of closing the popup. Also consider short-circuiting when token is falsy rather than sending Authorization: Bearer null.

🛠️ Suggested fix
 const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:3001';

 export async function saveOpportunity(token, payload) {
-  const res = await fetch(`${API_BASE}/api/opportunities`, {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-      Authorization: `Bearer ${token}`,
-    },
-    body: JSON.stringify(payload),
-  });
-  if (!res.ok) throw new Error(`Save failed: ${res.status}`);
-  return res.json();
+  if (!token) throw new Error('Missing auth token');
+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), 10000);
+  try {
+    const res = await fetch(`${API_BASE}/api/opportunities`, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${token}`,
+      },
+      body: JSON.stringify(payload),
+      signal: controller.signal,
+    });
+    if (!res.ok) throw new Error(`Save failed: ${res.status}`);
+    return res.json();
+  } finally {
+    clearTimeout(timeoutId);
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:3001';
export async function saveOpportunity(token, payload) {
const res = await fetch(`${API_BASE}/api/opportunities`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
return res.json();
}
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:3001';
export async function saveOpportunity(token, payload) {
if (!token) throw new Error('Missing auth token');
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const res = await fetch(`${API_BASE}/api/opportunities`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(payload),
signal: controller.signal,
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
return res.json();
} finally {
clearTimeout(timeoutId);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/lib/api.js` around lines 1 - 14, The saveOpportunity API call
needs a timeout and an early check for a missing auth token. Update
saveOpportunity in api.js to short-circuit when token is falsy instead of
sending a Bearer null header, and add timeout handling around the fetch request
so a stalled request fails cleanly rather than leaving the popup stuck. Use the
existing API_BASE and saveOpportunity symbols to locate the request logic, and
make sure the timeout error is surfaced through the same rejection path as other
request failures.

Comment thread extensions/src/popup/main.jsx
Comment thread extensions/src/popup/popup.jsx Outdated
Comment thread extensions/src/popup/popup.jsx
Comment thread extensions/src/popup/popup.jsx 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.

🧹 Nitpick comments (1)
extensions/src/popup/popup.jsx (1)

55-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Form labels not programmatically associated with inputs.

<label> elements have no htmlFor/id pairing with the Title/Description/URL inputs, so screen readers can't associate them.

♿ Suggested fix
-      <label>Title</label>
+      <label htmlFor="opp-title">Title</label>
       <input
+        id="opp-title"
         value={data.title}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 55 - 67, The form fields in
popup.jsx are using plain <label> elements without programmatic association, so
update the Title, Description, and URL label/input pairs in the popup form to be
linked via matching label and control identifiers. Use the existing
input/textarea elements in the popup component and add stable ids to the fields,
then point each corresponding label’s htmlFor at the matching id so assistive
technologies can correctly identify the controls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@extensions/src/popup/popup.jsx`:
- Around line 55-67: The form fields in popup.jsx are using plain <label>
elements without programmatic association, so update the Title, Description, and
URL label/input pairs in the popup form to be linked via matching label and
control identifiers. Use the existing input/textarea elements in the popup
component and add stable ids to the fields, then point each corresponding
label’s htmlFor at the matching id so assistive technologies can correctly
identify the controls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4db29ba7-5592-4545-902e-8926e7a0c146

📥 Commits

Reviewing files that changed from the base of the PR and between e4304c4 and d3b1fbd.

📒 Files selected for processing (3)
  • extensions/src/content.js
  • extensions/src/lib/clerk.js
  • extensions/src/popup/popup.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/src/content.js

@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.

🧹 Nitpick comments (2)
extensions/src/popup/popup.jsx (1)

45-62: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

getToken() runs outside try and errors are swallowed silently.

await getToken() on Line 47 executes before setStatus('saving') and outside the try block, so if it rejects, handleSave rejects with status stuck at idle and no user feedback. Separately, the catch (e) on Line 59 discards e, hiding the save failure cause. Move the token fetch inside try and log the error.

🛡️ Suggested fix
   async function handleSave() {
     if (!data.title) return alert('Please enter a title!');
-    const token = await getToken();
-    if (!token) return alert('Not signed in!');
     setStatus('saving');
     try {
+      const token = await getToken();
+      if (!token) {
+        setStatus('idle');
+        return alert('Not signed in!');
+      }
       await saveOpportunity(token, {
         title: data.title,
         description: data.description,
         link: data.link,
         category: data.category,
         status: data.status,
       });
       setStatus('saved');
     } catch (e) {
+      console.error('FutureTracker: save failed', e);
       setStatus('error');
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 45 - 62, Move the token fetch in
handleSave into the same try block as saveOpportunity so getToken() failures are
caught and status is updated consistently, and update the catch in popup.jsx to
use the caught error instead of discarding it so the failure is logged or
surfaced with context. Keep the existing handleSave flow and setStatus calls,
but ensure any rejection from getToken or saveOpportunity sets the error state
and does not fail silently.
extensions/src/background.js (1)

9-14: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Handle clerkClient.load() rejection.

load() can reject on network or misconfiguration; the top-level initClerk() call has no .catch/try so failures surface only as an unhandled rejection with no diagnostic. Wrap it so auth-init failures are logged.

🛡️ Suggested fix
 async function initClerk() {
-  await clerkClient.load();
-  console.log('FutureTracker: Clerk loaded');
+  try {
+    await clerkClient.load();
+    console.log('FutureTracker: Clerk loaded');
+  } catch (err) {
+    console.error('FutureTracker: Clerk failed to load', err);
+  }
 }
 
 initClerk();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/background.js` around lines 9 - 14, `initClerk()` in
background.js does not handle failures from `clerkClient.load()`, so auth
initialization can reject without any logged context. Wrap the await in
`initClerk` with try/catch (or add a `.catch` to the `initClerk()` call) and log
the error with a clear message before returning/failing gracefully; use the
`initClerk` and `clerkClient.load()` symbols to locate the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@extensions/src/background.js`:
- Around line 9-14: `initClerk()` in background.js does not handle failures from
`clerkClient.load()`, so auth initialization can reject without any logged
context. Wrap the await in `initClerk` with try/catch (or add a `.catch` to the
`initClerk()` call) and log the error with a clear message before
returning/failing gracefully; use the `initClerk` and `clerkClient.load()`
symbols to locate the fix.

In `@extensions/src/popup/popup.jsx`:
- Around line 45-62: Move the token fetch in handleSave into the same try block
as saveOpportunity so getToken() failures are caught and status is updated
consistently, and update the catch in popup.jsx to use the caught error instead
of discarding it so the failure is logged or surfaced with context. Keep the
existing handleSave flow and setStatus calls, but ensure any rejection from
getToken or saveOpportunity sets the error state and does not fail silently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6cfa2d54-e195-4282-a0c6-8b04b42d78d4

📥 Commits

Reviewing files that changed from the base of the PR and between d3b1fbd and 9d3efba.

⛔ Files ignored due to path filters (1)
  • backend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • backend/.env.example
  • extensions/readme.md
  • extensions/src/background.js
  • extensions/src/popup/popup.jsx
✅ Files skipped from review due to trivial changes (2)
  • extensions/readme.md
  • backend/.env.example

@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.

🧹 Nitpick comments (2)
extensions/src/popup/popup.jsx (2)

40-61: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace alert() with the existing inline status UI.

alert('Please enter a title!') (Line 41) and alert('Not signed in!') (Line 47) use native blocking dialogs inside an extension popup. Chromium extension popups are known to close on blur, and alert()/confirm() dialogs are dismissed when their context loses focus, so these messages can vanish before the user reads them, or close the popup outright, silently discarding entered data. The component already has an inline status pattern (status === 'error' at Line 114) that would be more reliable and consistent here.

♻️ Suggested fix
   async function handleSave() {
-    if (!data.title) return alert('Please enter a title!');
+    if (!data.title) {
+      setStatus('missing-title');
+      return;
+    }
     setStatus('saving');
     try {
       const token = await getToken();
       if (!token) {
-        setStatus('idle');
-        return alert('Not signed in!');
+        setStatus('error');
+        return;
       }

Then render a corresponding message alongside the existing saved/error feedback block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 40 - 61, The `handleSave` flow
in `popup.jsx` uses blocking `alert()` calls for validation and auth errors,
which should be replaced with the existing inline `status` UI. Update
`handleSave` to set an appropriate error status/message instead of calling
`alert('Please enter a title!')` and `alert('Not signed in!')`, and then render
those messages in the same feedback area that already handles `status ===
'saved'` and `status === 'error'` so the popup stays open and the user can see
the message reliably.

66-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Associate <label> elements with their controls.

Labels for Title, Description, URL, Category, and Status are plain text with no htmlFor/id pairing, so screen readers can't associate them with their inputs.

♻️ Example fix (repeat pattern for each field)
-      <label>Title</label>
+      <label htmlFor="ft-title">Title</label>
       <input
+        id="ft-title"
         value={data.title}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 66 - 105, The Title,
Description, URL, Category, and Status labels in popup.jsx are not associated
with their form controls, so update the form in the popup component to pair each
label with its corresponding input/select using matching htmlFor and id values.
Use the existing form fields inside the popup render to add unique ids for the
controls and connect them to their labels so assistive technologies can
correctly identify each field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@extensions/src/popup/popup.jsx`:
- Around line 40-61: The `handleSave` flow in `popup.jsx` uses blocking
`alert()` calls for validation and auth errors, which should be replaced with
the existing inline `status` UI. Update `handleSave` to set an appropriate error
status/message instead of calling `alert('Please enter a title!')` and
`alert('Not signed in!')`, and then render those messages in the same feedback
area that already handles `status === 'saved'` and `status === 'error'` so the
popup stays open and the user can see the message reliably.
- Around line 66-105: The Title, Description, URL, Category, and Status labels
in popup.jsx are not associated with their form controls, so update the form in
the popup component to pair each label with its corresponding input/select using
matching htmlFor and id values. Use the existing form fields inside the popup
render to add unique ids for the controls and connect them to their labels so
assistive technologies can correctly identify each field.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 383033ef-2816-44e7-9eee-d5463525e663

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3efba and ec0b452.

📒 Files selected for processing (2)
  • extensions/src/background.js
  • extensions/src/popup/popup.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/src/background.js

@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.

Caution

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

⚠️ Outside diff range comments (1)
extensions/src/popup/popup.jsx (1)

10-18: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add a URL fallback in the popup.
data.link only comes from GET_PAGE_METADATA, so a chrome.tabs.sendMessage failure leaves the read-only URL field empty and can save the opportunity without its source link. Use tab.url as a fallback when the active tab is queried.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 10 - 18, The popup metadata load
in useEffect only fills data.link from GET_PAGE_METADATA, so if
chrome.tabs.sendMessage fails the URL field stays empty. Update the active-tab
query handler to use tab.url as a fallback when populating state, and keep the
existing GET_PAGE_METADATA merge in the popup.jsx flow so the read-only link is
always available even when message delivery fails.
🧹 Nitpick comments (2)
extensions/src/popup/popup.jsx (2)

70-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mark title input as required for consistency with validation.

handleSave enforces a required title (Line 41), but the input itself has no required/aria-required attribute.

♿ Proposed fix
       <input
         id="ft-title"
         value={data.title}
         onChange={(e) => setData({ ...data, title: e.target.value })}
+        required
+        aria-required="true"
         style={{ width: '100%', marginBottom: '10px', padding: '6px', borderRadius: '4px', border: 'none', background: '`#1e293b`', color: '`#f1f5f9`' }}
       />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 70 - 76, The Title field in the
popup form is validated as required by handleSave, but the input itself is
missing the matching accessibility/HTML requirement. Update the ft-title input
in popup to include required and aria-required so the form semantics and
validation stay consistent; keep the change localized to the input element used
by the popup form.

128-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an aria-live region for save status feedback.

Status messages (saved/error/missing-title/auth-error) toggle in and out via conditional rendering with no live region, so screen readers won't announce the outcome of a save action.

♿ Proposed fix
+      <div aria-live="polite">
         {status === 'saved' && <p style={{ color: '`#22c55e`', textAlign: 'center' }}>Saved successfully ✓</p>}
         {status === 'error' && <p style={{ color: '`#ef4444`', textAlign: 'center' }}>Failed to save. Try again.</p>}
         {status === 'missing-title' && <p style={{ color: '`#ef4444`', textAlign: 'center' }}>Please enter a title!</p>}
         {status === 'auth-error' && <p style={{ color: '`#ef4444`', textAlign: 'center' }}>Not signed in!</p>}
+      </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/src/popup/popup.jsx` around lines 128 - 132, The status feedback
in popup.jsx is conditionally rendered without a live region, so screen readers
may not announce save outcomes. Update the status message area around the
existing saved/error/missing-title/auth-error conditions to use an aria-live
region (and keep the visible message text) so assistive tech announces changes
when the status state updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@extensions/src/popup/popup.jsx`:
- Around line 10-18: The popup metadata load in useEffect only fills data.link
from GET_PAGE_METADATA, so if chrome.tabs.sendMessage fails the URL field stays
empty. Update the active-tab query handler to use tab.url as a fallback when
populating state, and keep the existing GET_PAGE_METADATA merge in the popup.jsx
flow so the read-only link is always available even when message delivery fails.

---

Nitpick comments:
In `@extensions/src/popup/popup.jsx`:
- Around line 70-76: The Title field in the popup form is validated as required
by handleSave, but the input itself is missing the matching accessibility/HTML
requirement. Update the ft-title input in popup to include required and
aria-required so the form semantics and validation stay consistent; keep the
change localized to the input element used by the popup form.
- Around line 128-132: The status feedback in popup.jsx is conditionally
rendered without a live region, so screen readers may not announce save
outcomes. Update the status message area around the existing
saved/error/missing-title/auth-error conditions to use an aria-live region (and
keep the visible message text) so assistive tech announces changes when the
status state updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 506f89b2-9ae8-4ed8-a92e-5f9fc74b4eaa

📥 Commits

Reviewing files that changed from the base of the PR and between ec0b452 and 4ba2871.

📒 Files selected for processing (1)
  • extensions/src/popup/popup.jsx

@Venkat-Kolasani Venkat-Kolasani self-requested a review July 10, 2026 06:41

@Venkat-Kolasani Venkat-Kolasani left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @Maurya-H -- thanks for the work on this extension PR. I reviewed it locally and the extension build succeeds, but I need a few changes before I can merge.

Please reduce extension permissions:
Remove the unused cookies permission.
Avoid always injecting the content script on <all_urls> if possible; prefer on-demand access via activeTab/chrome.scripting, or restrict the match patterns.

Production setup:

Add the actual production backend domain to host_permissions; the documented Render API URL is not currently permitted.

Add a stable extension ID approach (manifest.json key) so Clerk and backend CORS can reliably allow chrome-extension://.

Fix/update documentation:

The backend reads CORS_ORIGIN, not EXTENSION_ID. Update extensions/readme.md accordingly.
Document VITE_SYNC_HOST=https://futuretracker.online.
Correct the folder-structure filename from Popup.jsx to popup.jsx.
Include clear local and production API setup instructions, including the exact CORS value.

Reliability and tests:

Add a timeout/abort handling to the save request so the popup cannot remain stuck on “Saving…” if the API stalls.
Add a test script and at least basic tests for the extension’s metadata/save logic.
Please clean up the trailing whitespace reported by git diff --check.

Once these are addressed, please run and share results for:

npm run test:ci
npm run build
npm run check:architecture
cd backend && npm test
cd extensions && npm run build

Also, the Vercel check needs authorization from the project team; I’ll handle that separately after the PR is otherwise ready. Also share the pictures/screenshot of the chrome extension working after testing end-to-end, Share Proof.

Thank you again for your intrest into futuretracker..

@Maurya-H

Copy link
Copy Markdown
Author
Screenshot (284) Screenshot (283) Screenshot (285)

HI @Venkat-Kolasani !
I have made changes in my PR . but cookies will be there because without it clerk isn't working.

@Maurya-H Maurya-H requested a review from Venkat-Kolasani July 12, 2026 13:00
@Maurya-H

Copy link
Copy Markdown
Author

✅ npm run test:ci → 28/28 passed
✅ npm run check:architecture → No violations
✅ cd backend && npm test → 125/125 passed
✅ cd extensions && npm run build → Built successfully
✅ cd extensions && npm test → 5/5 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.

Feature: Browser Extension for Quick Opportunity Addition

2 participants