feat(ui): P2.6 bookmarklet — save any page to Recall from any browser#28
Conversation
Adds a /bookmarklet popup page and a drag-to-install strip in the dashboard save form. Works in any browser without the extension. How it works: - Bookmarklet JS opens a 400x220 popup at recallsave.vercel.app/bookmarklet?url=PAGE_URL - The popup runs on our domain so it uses existing cookie auth (no CORS, no tokens) - Shows saving → saved (auto-closes after 1.5s) or error states - If not opened as popup, shows a "View your bookmarks" link instead of auto-close - If not logged in, shows login prompt New files: - src/app/bookmarklet/page.tsx — server component, auth guard, URL validation - src/app/bookmarklet/BookmarkletSaver.tsx — client component, all UI states Updated: - src/components/SaveUrlForm.tsx — bookmarklet install strip below save form - README.md — mark Collections + Bookmarklet as Live - CLAUDE.md — mark P2.6 Done Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new section between How It Works and the Resurfacing callout showing all three ways to save to Recall: - Web app (paste URL, any device) - Browser extension (Chrome, Brave, Edge, Arc, Perplexity + all Chromium) - Bookmarklet (Safari, Firefox, any browser — drag-to-install link) The extension and bookmarklet were completely absent from the landing page before this. The Chrome Web Store install button will be added after the extension is published (reminder in CLAUDE.md). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 51 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
Note
|
| Cohort / File(s) | Summary |
|---|---|
Docs & Task Tracking CLAUDE.md, README.md |
Marked P2.6 Bookmarklet done; added post–Chrome Web Store TODO; updated Features/Roadmap tables (Collections and Bookmarklet marked Live) and formatting/alignment in README. |
Bookmarklet Landing & Saver client/src/app/bookmarklet/page.tsx, client/src/app/bookmarklet/BookmarkletSaver.tsx |
Added /bookmarklet page with Supabase server auth check and URL validation; added BookmarkletSaver component that POSTs to POST /api/bookmarks, handles statuses (saving/saved/duplicate/error) and auto-closes when saved. |
Landing UI & SaveUrlForm client/src/app/page.tsx, client/src/components/SaveUrlForm.tsx |
Added "SAVE FROM ANYWHERE" landing section with three cards (Web app, Extension, Bookmarklet) and a draggable javascript: bookmarklet anchor; SaveUrlForm builds a bookmarklet link and renders draggable anchor; hover styles extended for .save-card:hover. |
Sequence Diagram(s)
sequenceDiagram
participant User as User/Browser
participant BMPage as Bookmarklet Page (/bookmarklet)
participant Auth as Supabase Auth
participant Saver as BookmarkletSaver Component
participant API as Server API (/api/bookmarks)
User->>BMPage: Open bookmarklet URL (?url=...)
BMPage->>Auth: supabase.auth.getUser()
Auth-->>BMPage: user or null
alt not authenticated
BMPage-->>User: show "Not logged in" (link to /auth)
else authenticated
BMPage->>BMPage: validate URL (http/https)
alt invalid URL
BMPage-->>User: show "Invalid URL"
else valid URL
BMPage->>Saver: render with validated url
Saver->>API: POST /api/bookmarks (url, auth)
alt 409 Conflict
API-->>Saver: 409
Saver-->>User: show "duplicate" state
else 200 OK
API-->>Saver: 200
Saver-->>User: show "saved" and close window or link to /app
else other error
API-->>Saver: non-OK
Saver-->>User: show error message
end
end
end
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~22 minutes
Possibly related PRs
- feat: add SaveUrlForm to page for in-browser #4: Modifies
SaveUrlFormand landing page UI surfaces that this PR extends with the bookmarklet anchor and landing cards. - feat: AI tagging, extension polish, nav/auth UX + security hardening #22: Changes the bookmarks POST flow/response shape that
BookmarkletSaverrelies on when saving via/api/bookmarks. - M7: Supabase Auth #11: Adds/adjusts Supabase server/client auth utilities used by the
/bookmarkletserver-side auth check.
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | |
| Description check | ❓ Inconclusive | The description provides a clear summary of changes and includes a test plan, but lacks the required template structure with 'What does this PR do?', 'Type of change' checkboxes, and a complete 'Checklist' section. | Restructure the description to follow the template: add 'What does this PR do?' section, specify the 'Type of change' (New feature), and complete all checklist items with their checked status. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title accurately describes the main feature being added: a bookmarklet implementation (P2.6) that allows users to save pages to Recall from any browser. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
p2-bookmarklet
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
Fixes react-hooks/static-components lint error — components must be declared at module scope, not inside another component's function body. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
client/src/components/SaveUrlForm.tsx (1)
72-80: Consider extracting hardcoded bookmarklet URL to a constant or environment variable.The URL
https://recallsave.vercel.appis hardcoded. This works for production but makes local development or staging environments awkward to test. Consider using an environment variable likeNEXT_PUBLIC_APP_URLfor flexibility.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@client/src/components/SaveUrlForm.tsx` around lines 72 - 80, Extract the hardcoded origin in the bookmarklet href into a configurable constant (e.g., NEXT_PUBLIC_APP_URL) and use that constant when building the bookmarklet string in the SaveUrlForm component (the <a> element that currently builds the href with 'https://recallsave.vercel.app'). Read the value from process.env.NEXT_PUBLIC_APP_URL with a sensible default for production, then replace the inline literal in the href construction with that constant so local/staging environments can override the base URL via env.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/src/app/bookmarklet/BookmarkletSaver.tsx`:
- Around line 17-18: The duplicate-detection branch in BookmarkletSaver.tsx (the
if (res.status === 409) { setStatus("duplicate"); } path) is unreachable because
the API route always returns 500 and the Bookmark model has no unique
constraint; either remove this dead branch or implement a proper duplicate flow
by adding a unique constraint on the Bookmark model (userId + url) and updating
the API handler to detect Prisma unique-constraint errors and return 409 so
setStatus("duplicate") becomes reachable; additionally move the window.opener
access (currently read during render around line referencing window.opener) into
a client-only effect (useEffect) or initialize it into state on mount to avoid
SSR/client hydration mismatch.
- Around line 52-54: The render reads window.opener causing potential SSR
hydration mismatch in BookmarkletSaver
(client/src/app/bookmarklet/BookmarkletSaver.tsx); change it to use a
client-only state: add a boolean state like hasOpener initialized false and set
it inside useEffect by reading window.opener, then render based on that state
instead of directly accessing window.opener; update any JSX that currently uses
window.opener to use hasOpener so the initial server render matches the client.
In `@client/src/app/bookmarklet/page.tsx`:
- Around line 19-24: The Logo component is currently defined inside the page
render which violates react-hooks/static-components; move the Logo function
(const Logo = () => ...) out to module scope (top-level of the file) so it is
not recreated on every render, keep its JSX identical and retain the Image
import usage, then update any references in the page render to use the now
top-level Logo component; ensure no props or closure variables are referenced
from the outer render scope so the extracted Logo remains pure.
In `@client/src/app/page.tsx`:
- Line 399: Remove the unused eslint-disable directive in
client/src/app/page.tsx by deleting the line containing
"eslint-disable-next-line no-script-url" (the same unused directive reported in
SaveUrlForm.tsx); ensure no references to "javascript:" or script URL usage
remain in the surrounding JSX and run the linter to confirm the warning is
resolved.
In `@client/src/components/SaveUrlForm.tsx`:
- Line 71: Remove the unused eslint-disable directive "//
eslint-disable-next-line no-script-url" from the SaveUrlForm component in
SaveUrlForm.tsx; locate the comment near the JSX inside the SaveUrlForm
function/component and delete that line, then re-run the linter to confirm no
rule violations remain (and if any real no-script-url violations appear, fix the
offending code instead of disabling the rule).
---
Nitpick comments:
In `@client/src/components/SaveUrlForm.tsx`:
- Around line 72-80: Extract the hardcoded origin in the bookmarklet href into a
configurable constant (e.g., NEXT_PUBLIC_APP_URL) and use that constant when
building the bookmarklet string in the SaveUrlForm component (the <a> element
that currently builds the href with 'https://recallsave.vercel.app'). Read the
value from process.env.NEXT_PUBLIC_APP_URL with a sensible default for
production, then replace the inline literal in the href construction with that
constant so local/staging environments can override the base URL via env.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 68aa8a47-019a-49d1-b057-e225c3623005
📒 Files selected for processing (6)
CLAUDE.mdREADME.mdclient/src/app/bookmarklet/BookmarkletSaver.tsxclient/src/app/bookmarklet/page.tsxclient/src/app/page.tsxclient/src/components/SaveUrlForm.tsx
| if (res.status === 409) { | ||
| setStatus("duplicate"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check Prisma schema for unique constraint on Bookmark model
fd -t f 'schema.prisma' --exec cat {} | grep -A 30 'model Bookmark'Repository: MinitJain/recall
Length of output: 669
🏁 Script executed:
cat -n client/src/app/api/bookmarks/route.ts | head -100Repository: MinitJain/recall
Length of output: 3737
🏁 Script executed:
cat -n client/src/app/bookmarklet/BookmarkletSaver.tsxRepository: MinitJain/recall
Length of output: 4284
Remove dead code: 409 duplicate detection is unreachable.
Lines 17-18 will never execute. The API route at client/src/app/api/bookmarks/route.ts catches all errors from the Prisma create operation (line 70) and returns status 500 with no distinction for duplicate violations. Additionally, the Bookmark schema has no unique constraint on (userId, url), so duplicates are silently accepted as separate bookmarks.
Either add a unique constraint to the Bookmark model, update the API route to detect and return 409 for duplicates, or remove this dead code path.
Also fix hydration mismatch on line 52: window.opener is accessed during render. This will differ between server and client. Move this logic into a client-side effect or use a state that initializes only on the client.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@client/src/app/bookmarklet/BookmarkletSaver.tsx` around lines 17 - 18, The
duplicate-detection branch in BookmarkletSaver.tsx (the if (res.status === 409)
{ setStatus("duplicate"); } path) is unreachable because the API route always
returns 500 and the Bookmark model has no unique constraint; either remove this
dead branch or implement a proper duplicate flow by adding a unique constraint
on the Bookmark model (userId + url) and updating the API handler to detect
Prisma unique-constraint errors and return 409 so setStatus("duplicate") becomes
reachable; additionally move the window.opener access (currently read during
render around line referencing window.opener) into a client-only effect
(useEffect) or initialize it into state on mount to avoid SSR/client hydration
mismatch.
| {window.opener ? "This window will close..." : ( | ||
| <a href="/app" className="text-[var(--accent)] hover:underline">View your bookmarks</a> | ||
| )} |
There was a problem hiding this comment.
Potential hydration mismatch with window.opener check.
Accessing window.opener during render can cause a hydration mismatch since window is undefined during SSR. Consider moving this check into a useEffect or using a state variable initialized client-side.
🛠️ Suggested fix using state
export default function BookmarkletSaver({ url }: { url: string }) {
const [status, setStatus] = useState<"saving" | "saved" | "duplicate" | "error">("saving");
const [errorMsg, setErrorMsg] = useState<string | null>(null);
+ const [isPopup, setIsPopup] = useState(false);
+
+ useEffect(() => {
+ setIsPopup(!!window.opener);
+ }, []);
useEffect(() => {
// ... existing save logic
}, [url]);
// In the JSX:
<p className="text-xs text-[var(--text-dim)]">
- {window.opener ? "This window will close..." : (
+ {isPopup ? "This window will close..." : (
<a href="/app" className="text-[var(--accent)] hover:underline">View your bookmarks</a>
)}
</p>📝 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.
| {window.opener ? "This window will close..." : ( | |
| <a href="/app" className="text-[var(--accent)] hover:underline">View your bookmarks</a> | |
| )} | |
| export default function BookmarkletSaver({ url }: { url: string }) { | |
| const [status, setStatus] = useState<"saving" | "saved" | "duplicate" | "error">("saving"); | |
| const [errorMsg, setErrorMsg] = useState<string | null>(null); | |
| const [isPopup, setIsPopup] = useState(false); | |
| useEffect(() => { | |
| setIsPopup(!!window.opener); | |
| }, []); | |
| useEffect(() => { | |
| // ... existing save logic | |
| }, [url]); | |
| // ... rest of component | |
| return ( | |
| <div> | |
| {/* ... */} | |
| <p className="text-xs text-[var(--text-dim)]"> | |
| {isPopup ? "This window will close..." : ( | |
| <a href="/app" className="text-[var(--accent)] hover:underline">View your bookmarks</a> | |
| )} | |
| </p> | |
| </div> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@client/src/app/bookmarklet/BookmarkletSaver.tsx` around lines 52 - 54, The
render reads window.opener causing potential SSR hydration mismatch in
BookmarkletSaver (client/src/app/bookmarklet/BookmarkletSaver.tsx); change it to
use a client-only state: add a boolean state like hasOpener initialized false
and set it inside useEffect by reading window.opener, then render based on that
state instead of directly accessing window.opener; update any JSX that currently
uses window.opener to use hasOpener so the initial server render matches the
client.
The no-script-url rule is not enabled in this project's ESLint config so the disable directives were producing lint warnings without suppressing anything. The javascript: hrefs are intentional bookmarklet drag targets. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
window.open is blocked by Brave and other privacy browsers without user permission changes. location.href navigates the current tab instead — no popup needed, works in every browser out of the box. Also removed target="_blank" from the bookmarklet login link since the user is now in the current tab, not a popup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users must sign up to save bookmarks. Changed to 'Free to sign up' which is accurate and still a positive selling point. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
client/src/app/page.tsx (1)
368-384: Consider migrating to Tailwind CSS for consistency with guidelines.The new cards use inline styles, which is consistent with the rest of this file but diverges from the coding guideline requiring Tailwind CSS v4. This is a larger refactor that could be addressed in a follow-up PR.
As per coding guidelines: "
client/**/*.{ts,tsx,jsx,js,css}: Use Tailwind CSS v4 for styling".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@client/src/app/page.tsx` around lines 368 - 384, The card uses inline style objects (the outer <div data-animate className="save-card"> and its child <span>, <h3>, <p>, and badge <span>) which violates the Tailwind CSS v4 guideline; replace the inline style prop on that container and its children with equivalent Tailwind utility classes (e.g., layout, spacing, colors, typography, border, rounded, transition and align-self utilities) and remove the style objects while preserving data-animate and the "save-card" identifier so behavior remains unchanged; ensure color tokens map to the project's Tailwind theme (use custom utility classes or theme keys if needed) and update the className strings on the container and child elements (<span>, <h3>, <p>, and badge <span>) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/src/app/page.tsx`:
- Around line 414-428: The bookmarklet base URL is hardcoded in the anchor in
client/src/app/page.tsx and in SaveUrlForm.tsx; replace the literal
"https://recallsave.vercel.app" with a configurable env var NEXT_PUBLIC_APP_URL:
add NEXT_PUBLIC_APP_URL to .env.local, update SaveUrlForm (client component) to
read process.env.NEXT_PUBLIC_APP_URL directly, and for page.tsx (server
component) either pass the value from the server as a prop into the component or
move the bookmarklet anchor into a small client component that reads
NEXT_PUBLIC_APP_URL; update the anchor in page.tsx (the draggable <a> element)
to use that prop or client component so non-production environments (localhost,
previews, staging) use the correct base URL.
---
Nitpick comments:
In `@client/src/app/page.tsx`:
- Around line 368-384: The card uses inline style objects (the outer <div
data-animate className="save-card"> and its child <span>, <h3>, <p>, and badge
<span>) which violates the Tailwind CSS v4 guideline; replace the inline style
prop on that container and its children with equivalent Tailwind utility classes
(e.g., layout, spacing, colors, typography, border, rounded, transition and
align-self utilities) and remove the style objects while preserving data-animate
and the "save-card" identifier so behavior remains unchanged; ensure color
tokens map to the project's Tailwind theme (use custom utility classes or theme
keys if needed) and update the className strings on the container and child
elements (<span>, <h3>, <p>, and badge <span>) accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 82d6cd5f-4d91-447d-a235-e57ea45a4cc3
📒 Files selected for processing (1)
client/src/app/page.tsx
| <a | ||
| href={`javascript:(function(){location.href='https://recallsave.vercel.app/bookmarklet?url='+encodeURIComponent(location.href)})();`} | ||
| draggable | ||
| style={{ | ||
| marginTop: "auto", display: "inline-flex", alignItems: "center", gap: 6, | ||
| fontSize: 12, fontWeight: 600, color: "var(--lp-accent)", | ||
| background: "var(--lp-accent-soft)", borderRadius: 20, | ||
| padding: "4px 12px", cursor: "grab", userSelect: "none", | ||
| border: "1px dashed var(--lp-accent)", textDecoration: "none", | ||
| alignSelf: "flex-start", | ||
| }} | ||
| title="Drag this to your bookmarks bar" | ||
| > | ||
| ← drag to bookmarks bar | ||
| </a> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all occurrences of hardcoded bookmarklet URL in the codebase
echo "=== Searching for hardcoded recallsave.vercel.app URLs ==="
rg -n "recallsave\.vercel\.app" --type ts --type tsx
echo ""
echo "=== Checking if NEXT_PUBLIC_APP_URL env var is defined ==="
fd -t f -e env -e local | xargs cat 2>/dev/null | grep -i "APP_URL" || echo "No APP_URL env var found"
echo ""
echo "=== Checking next.config for env configuration ==="
fd "next.config" | xargs cat 2>/dev/null | head -50Repository: MinitJain/recall
Length of output: 519
🏁 Script executed:
#!/bin/bash
# First, locate the exact file
echo "=== Locating page.tsx ==="
fd "page.tsx" -t f
echo ""
echo "=== Reading lines 414-428 from client/src/app/page.tsx ==="
if [ -f "client/src/app/page.tsx" ]; then
sed -n '414,428p' "client/src/app/page.tsx"
else
echo "File not found at expected location"
fi
echo ""
echo "=== Checking for recallsave.vercel.app in all files ==="
rg "recallsave" --glob "*.ts" --glob "*.tsx" -n
echo ""
echo "=== Checking SaveUrlForm.tsx ==="
fd "SaveUrlForm" -t f | xargs cat 2>/dev/null | head -100Repository: MinitJain/recall
Length of output: 4946
🏁 Script executed:
#!/bin/bash
# Check if page.tsx is a Server or Client Component
echo "=== Checking page.tsx component type (first 30 lines) ==="
head -30 "client/src/app/page.tsx"
echo ""
echo "=== Checking for environment variable usage patterns ==="
rg "NEXT_PUBLIC" client/src --glob "*.ts" --glob "*.tsx" | head -20
echo ""
echo "=== Checking .env files ==="
fd "\.env" -t f | xargs cat 2>/dev/null
echo ""
echo "=== Checking robots.ts and sitemap.ts context (are these metadata?) ==="
head -10 "client/src/app/robots.ts"
echo "---"
head -15 "client/src/app/sitemap.ts"Repository: MinitJain/recall
Length of output: 3113
Extract hardcoded production URL to environment variable to support non-production environments.
The bookmarklet URL https://recallsave.vercel.app is hardcoded in two locations, which breaks:
- Local development (
localhost:3000) - Vercel preview deployments
- Staging environments
Found in:
client/src/app/page.tsx(line 415)client/src/components/SaveUrlForm.tsx(line 72)
Implementation approach:
For SaveUrlForm.tsx (Client Component): Use process.env.NEXT_PUBLIC_APP_URL directly since it's marked with "use client".
For page.tsx (Server Component): Pass the base URL as a prop from the server, or extract the bookmarklet link into a separate client component that reads the environment variable.
Create a NEXT_PUBLIC_APP_URL environment variable in your .env.local:
NEXT_PUBLIC_APP_URL=https://recallsave.vercel.app
Then update both locations to use this variable instead of the hardcoded string.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@client/src/app/page.tsx` around lines 414 - 428, The bookmarklet base URL is
hardcoded in the anchor in client/src/app/page.tsx and in SaveUrlForm.tsx;
replace the literal "https://recallsave.vercel.app" with a configurable env var
NEXT_PUBLIC_APP_URL: add NEXT_PUBLIC_APP_URL to .env.local, update SaveUrlForm
(client component) to read process.env.NEXT_PUBLIC_APP_URL directly, and for
page.tsx (server component) either pass the value from the server as a prop into
the component or move the bookmarklet anchor into a small client component that
reads NEXT_PUBLIC_APP_URL; update the anchor in page.tsx (the draggable <a>
element) to use that prop or client component so non-production environments
(localhost, previews, staging) use the correct base URL.
React replaces javascript: hrefs with an error string as a security precaution, so the dragged bookmark saved the error instead of the code. - page.tsx: use dangerouslySetInnerHTML to render the bookmarklet <a> server-side, bypassing React's sanitization (static content, no XSS risk) - SaveUrlForm.tsx: use useRef + useEffect to set the href on the DOM element directly after mount, same bypass on the client side - Both: replace hardcoded recallsave.vercel.app with NEXT_PUBLIC_APP_URL so localhost and preview deployments use the correct base URL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@client/src/components/SaveUrlForm.tsx`:
- Around line 15-19: The code uses NEXT_PUBLIC_APP_URL with a hardcoded fallback
"https://recallsave.vercel.app" in SaveUrlForm.tsx (appUrl and bookmarkletRef)
and again in app/page.tsx; keep the absolute production fallback as-is (do NOT
switch to window.location.origin because bookmarklets execute in the user's
page), and make both files consistent by referencing NEXT_PUBLIC_APP_URL with
the same default; then update repo setup: add NEXT_PUBLIC_APP_URL to your
.env.example/local setup instructions and add a vercel.json env entry (or set
VERCEL_URL-based logic elsewhere) so deployments can override the value.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e23739e7-6d37-4e08-8ff8-ff67583fa1c8
📒 Files selected for processing (2)
client/src/app/page.tsxclient/src/components/SaveUrlForm.tsx
| const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "https://recallsave.vercel.app"; | ||
| bookmarkletRef.current.setAttribute( | ||
| "href", | ||
| `javascript:(function(){location.href='${appUrl}/bookmarklet?url='+encodeURIComponent(location.href)})();` | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== References to NEXT_PUBLIC_APP_URL =="
rg -n --hidden --glob '!.git' 'NEXT_PUBLIC_APP_URL'
echo
echo "== Env files defining NEXT_PUBLIC_APP_URL (if any) =="
fd -H '^\.(env|env\..*)$' -t f | while read -r f; do
if rg -n 'NEXT_PUBLIC_APP_URL' "$f" >/dev/null; then
echo "-- $f"
rg -n 'NEXT_PUBLIC_APP_URL' "$f"
fi
done
echo
echo "== Docs mentioning setup for NEXT_PUBLIC_APP_URL =="
rg -n --hidden --glob '!.git' --iglob '*readme*' --iglob '*.md' 'NEXT_PUBLIC_APP_URL|bookmarklet'Repository: MinitJain/recall
Length of output: 983
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Vercel and Next.js config files =="
fd -H 'vercel\.json|next\.config\.(js|ts)' -t f
echo
echo "== Contents of vercel.json (if exists) =="
if [ -f vercel.json ]; then
cat vercel.json
else
echo "vercel.json not found"
fi
echo
echo "== Contents of next.config (if exists) =="
if [ -f client/next.config.js ] || [ -f client/next.config.ts ]; then
ls -la client/next.config.* 2>/dev/null || true
[ -f client/next.config.js ] && cat client/next.config.js
[ -f client/next.config.ts ] && cat client/next.config.ts
else
echo "next.config.js/ts not found in client/"
fi
echo
echo "== Environment configuration in package.json scripts =="
if [ -f client/package.json ]; then
jq '.scripts // {}' client/package.json 2>/dev/null || echo "Could not parse package.json"
fi
echo
echo "== Look for .env.example or documentation =="
fd -H '\.env\.|readme|README' -t f | head -20Repository: MinitJain/recall
Length of output: 742
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== README.md content (looking for env/deployment info) =="
if [ -f README.md ]; then
cat README.md
else
echo "README.md not found"
fi
echo
echo "== Check client directory structure =="
ls -la client/ | head -20
echo
echo "== Check for .env.example or .env.local in client/ =="
fd -H '^\.(env|env\..*)' client/ -t f 2>/dev/null || echo "No env files in client/"Repository: MinitJain/recall
Length of output: 11617
Fix bookmarklet fallback URL configuration across two files.
The hardcoded fallback to recallsave.vercel.app appears in both SaveUrlForm.tsx:15 and app/page.tsx:417. This works for production Vercel deployments but has two problems:
- Missing environment configuration:
NEXT_PUBLIC_APP_URLis not documented in setup instructions and novercel.jsondefines it for Vercel deployments. Developers deploying to other domains have no way to override it. - Proposed fix is incorrect for bookmarklets: Using
window.location.originas fallback won't work—bookmarklets execute in the user's browsed site context, not the app domain. The fallback must be an absolute app URL.
Recommended approach:
- Add
NEXT_PUBLIC_APP_URLto the local setup docs (e.g., in.env.localexample) - Create
vercel.jsonto defineNEXT_PUBLIC_APP_URL=https://recallsave.vercel.appfor Vercel deployments, or rely on auto-detection viaVERCEL_URL - Keep hardcoded fallback as-is (production domain is reasonable default)
- Apply the same fix across both files if configuration strategy changes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@client/src/components/SaveUrlForm.tsx` around lines 15 - 19, The code uses
NEXT_PUBLIC_APP_URL with a hardcoded fallback "https://recallsave.vercel.app" in
SaveUrlForm.tsx (appUrl and bookmarkletRef) and again in app/page.tsx; keep the
absolute production fallback as-is (do NOT switch to window.location.origin
because bookmarklets execute in the user's page), and make both files consistent
by referencing NEXT_PUBLIC_APP_URL with the same default; then update repo
setup: add NEXT_PUBLIC_APP_URL to your .env.example/local setup instructions and
add a vercel.json env entry (or set VERCEL_URL-based logic elsewhere) so
deployments can override the value.
Documents every env var the app needs, including NEXT_PUBLIC_APP_URL which was added for the bookmarklet. Unignored .env.example in .gitignore since it contains no secret values. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
In-app UI
Documentation