Conversation
Align results page with overall app theme. Main results area scrolls while header stays fixed. Analyze another article without scrolling back. AI Discussion panel collapsible into a bottom-right chat button.
📝 WalkthroughWalkthroughLoading, Analyze, and Results frontend pages were rewritten: LoadingPage now reads the article URL from sessionStorage, performs runtime-configured POSTs to /api/process and /api/bias with timeout and detailed Axios error handling, orchestrates timed steps/progress with lifecycle guards, and navigates based on outcomes; Analyze and Results pages received major UI and validation restructures; Next and TS JSX runtime updated. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant LoadingPage as Loading Page
participant SessionStorage as Session Storage
participant ProcessAPI as /api/process
participant BiasAPI as /api/bias
participant Router as Router
User->>LoadingPage: open loading page
LoadingPage->>SessionStorage: read article URL
SessionStorage-->>LoadingPage: return URL
rect rgb(220,240,255)
Note over LoadingPage,ProcessAPI: Parallel network operations (with timeout)
par POST /api/process
LoadingPage->>ProcessAPI: POST { url } (timeout)
ProcessAPI-->>LoadingPage: analysis result
and POST /api/bias
LoadingPage->>BiasAPI: POST { url } (timeout)
BiasAPI-->>LoadingPage: bias score
end
end
LoadingPage->>SessionStorage: save analysis result
LoadingPage->>SessionStorage: save BiasScore
LoadingPage->>LoadingPage: advance steps & update progress (intervals, isMounted guards)
alt both succeed
LoadingPage->>Router: navigate /analyze/results (after final delay)
else error / timeout
LoadingPage->>Router: navigate /analyze (if still mounted)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
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 |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
frontend/app/analyze/page.tsx (1)
32-39: URL validation accepts potentially dangerous protocols.The
URLconstructor considersjavascript:,data:, andfile:URLs as valid. If this URL is later used in an anchorhrefor iframesrc, it could enable XSS attacks.🔎 Proposed fix to restrict to http/https protocols
const validateUrl = (inputUrl: string) => { try { - new URL(inputUrl); - setIsValidUrl(true); + const parsed = new URL(inputUrl); + if (parsed.protocol === "http:" || parsed.protocol === "https:") { + setIsValidUrl(true); + } else { + setIsValidUrl(false); + } } catch { setIsValidUrl(false); } };frontend/app/analyze/loading/page.tsx (3)
52-118: Memory leak: Cleanup function is not returned to useEffect.The cleanup function at lines 108-111 is returned from the inner
runAnalysis()async function, not from theuseEffectcallback. React's cleanup mechanism never receives it, so intervals will keep running after unmount.🔎 Proposed fix to properly handle cleanup
useEffect(() => { + let stepInterval: NodeJS.Timeout | null = null; + let progressInterval: NodeJS.Timeout | null = null; + let isMounted = true; + const runAnalysis = async () => { const storedUrl = sessionStorage.getItem("articleUrl"); if (storedUrl) { setArticleUrl(storedUrl); try { const [processRes, biasRes] = await Promise.all([ axios.post("https://thunder1245-perspective-backend.hf.space/api/process", { url: storedUrl, }), axios.post("https://thunder1245-perspective-backend.hf.space/api/bias", { url: storedUrl, }), ]); sessionStorage.setItem("BiasScore", JSON.stringify(biasRes.data)); - console.log("Bias score saved"); - console.log(biasRes); sessionStorage.setItem( "analysisResult", JSON.stringify(processRes.data) ); - console.log("Analysis result saved"); - console.log(processRes); } catch (err) { console.error("Failed to process article:", err); + if (isMounted) { router.push("/analyze"); + } return; } - const stepInterval = setInterval(() => { + if (!isMounted) return; + + stepInterval = setInterval(() => { setCurrentStep((prev) => { if (prev < steps.length - 1) { return prev + 1; } else { - clearInterval(stepInterval); + if (stepInterval) clearInterval(stepInterval); setTimeout(() => { - router.push("/analyze/results"); + if (isMounted) router.push("/analyze/results"); }, 2000); return prev; } }); }, 2000); - const progressInterval = setInterval(() => { + progressInterval = setInterval(() => { setProgress((prev) => { if (prev < 100) { return prev + 1; } return prev; }); }, 100); - - return () => { - clearInterval(stepInterval); - clearInterval(progressInterval); - }; } else { router.push("/analyze"); } }; runAnalysis(); + + return () => { + isMounted = false; + if (stepInterval) clearInterval(stepInterval); + if (progressInterval) clearInterval(progressInterval); + }; -}, [router]); +}, [router, steps.length]);
60-65: Hardcoded backend URL should use an environment variable.The backend URL
https://thunder1245-perspective-backend.hf.spaceis hardcoded, making it difficult to switch between development, staging, and production environments.🔎 Proposed fix
+const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "https://thunder1245-perspective-backend.hf.space"; const [processRes, biasRes] = await Promise.all([ - axios.post("https://thunder1245-perspective-backend.hf.space/api/process", { + axios.post(`${BACKEND_URL}/api/process`, { url: storedUrl, }), - axios.post("https://thunder1245-perspective-backend.hf.space/api/bias", { + axios.post(`${BACKEND_URL}/api/bias`, { url: storedUrl, }), ]);
59-66: API calls lack timeout configuration.The parallel axios calls have no timeout, which could leave users stuck on the loading screen indefinitely if the backend is slow or unresponsive.
🔎 Proposed fix to add request timeout
const [processRes, biasRes] = await Promise.all([ axios.post("https://thunder1245-perspective-backend.hf.space/api/process", { url: storedUrl, - }), + }, { timeout: 60000 }), // 60 second timeout axios.post("https://thunder1245-perspective-backend.hf.space/api/bias", { url: storedUrl, - }), + }, { timeout: 60000 }), ]);
🧹 Nitpick comments (3)
frontend/app/analyze/page.tsx (1)
51-56: Consider clearing stale session data before navigation.If a previous analysis failed or left partial data, the loading/results pages might display stale information alongside new analysis.
🔎 Proposed fix to clear stale data
const handleAnalyze = () => { if (isValidUrl && url) { + // Clear any stale analysis data from previous sessions + sessionStorage.removeItem("analysisResult"); + sessionStorage.removeItem("BiasScore"); sessionStorage.setItem("articleUrl", url); router.push("/analyze/loading"); } };frontend/app/analyze/results/page.tsx (2)
59-76: Duplicate URL validation logic — extract to shared utility.This
validateUrlandhandleUrlChangelogic is duplicated fromfrontend/app/analyze/page.tsx. Consider extracting to a shared hook or utility.🔎 Example shared hook
Create
frontend/hooks/useUrlValidation.ts:import { useState, useCallback } from "react"; export function useUrlValidation() { const [url, setUrl] = useState(""); const [isValidUrl, setIsValidUrl] = useState(false); const validateUrl = useCallback((inputUrl: string) => { try { const parsed = new URL(inputUrl); // Only allow http/https protocols setIsValidUrl(parsed.protocol === "http:" || parsed.protocol === "https:"); } catch { setIsValidUrl(false); } }, []); const handleUrlChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const inputUrl = e.target.value; setUrl(inputUrl); if (inputUrl.length > 0) { validateUrl(inputUrl); } else { setIsValidUrl(false); } }, [validateUrl]); return { url, setUrl, isValidUrl, setIsValidUrl, handleUrlChange }; }Note: This also has the same security issue — accepts
javascript:URLs. Apply the protocol check fix here as well.
428-428:onKeyPressis deprecated — useonKeyDowninstead.The
onKeyPressevent is deprecated in React and may be removed in future versions.🔎 Proposed fix
-onKeyPress={(e) => e.key === "Enter" && handleSendMessage()} +onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && handleSendMessage()}
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
frontend/app/analyze/loading/page.tsxfrontend/app/analyze/page.tsxfrontend/app/analyze/results/page.tsxfrontend/package.jsonfrontend/tsconfig.json
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/analyze/page.tsx (5)
frontend/components/theme-toggle.tsx (1)
ThemeToggle(14-42)frontend/components/ui/badge.tsx (1)
Badge(41-41)frontend/components/ui/card.tsx (1)
Card(79-79)frontend/components/ui/input.tsx (1)
Input(22-22)frontend/components/ui/button.tsx (1)
Button(56-56)
frontend/app/analyze/loading/page.tsx (2)
frontend/components/theme-toggle.tsx (1)
ThemeToggle(14-42)frontend/components/ui/badge.tsx (1)
Badge(41-41)
🔇 Additional comments (5)
frontend/tsconfig.json (2)
18-18: LGTM!The
jsx: "react-jsx"setting aligns with the modern JSX transform required for React 19 and Next.js 16. This is the correct configuration for the upgraded stack.
31-40: LGTM!The updated
includeandexcludearrays appropriately capture Next.js generated type files, including.next/dev/types/**/*.tsfor development-time type generation with Turbopack.frontend/app/analyze/page.tsx (1)
76-232: LGTM on overall page structure and UX improvements.The redesigned layout with compact hero, elevated URL input card, softer feature grid, and streamlined example URLs aligns well with the PR objectives for improved consistency and flow. The responsive design with flex/grid layouts handles various screen sizes appropriately.
frontend/app/analyze/results/page.tsx (1)
109-458: LGTM on the overall results page UX improvements.The redesigned layout successfully addresses the PR objectives:
- Fixed header keeps navigation accessible without scrolling
- Constrained scrolling to the results area (lines 145-146)
- Collapsible AI Discussion panel with floating button fallback (lines 377-456)
- Inline "Analyze Another Article" box eliminates navigation friction (lines 338-374)
The two-column layout with responsive handling and smooth transitions provides a polished, app-like experience.
frontend/package.json (1)
49-49: No breaking changes to address for this project's Next.js 16 upgrade.The codebase doesn't use middleware, doesn't have custom webpack configuration, and doesn't currently utilize route params or searchParams, so none of the typical Next.js 16 migration steps apply here. The upgrade can proceed without the changes mentioned.
Likely an incorrect or invalid review comment.
- Add URL protocol whitelist to prevent XSS (http/https only) - Implement proper JSON.parse error handling with try/catch - Fix memory leaks from setTimeout/setInterval in loading page - Add useEffect cleanup for intervals and mounted state tracking - Replace console.log with conditional debug logger - Extract backend URL to NEXT_PUBLIC_BACKEND_URL env variable - Add configurable API timeout (60s default) - Guard async operations with isMounted check
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/app/analyze/results/page.tsx (1)
121-144: Store and clear timeout in cleanup for complete resource management.The
isMountedcheck prevents state updates after unmount, which addresses the critical issue. However, the timeout still fires after unmount. For complete cleanup, store the timeout ID in a ref and clear it in the useEffect cleanup function.🔎 Proposed enhancement for timeout cleanup
+const chatTimeoutRef = useRef<NodeJS.Timeout | null>(null); + useEffect(() => { // ... existing initialization code ... return () => { isMounted.current = false; + if (chatTimeoutRef.current) { + clearTimeout(chatTimeoutRef.current); + } }; }, [router]); const handleSendMessage = () => { if (chatInput.trim()) { setChatMessages([...chatMessages, { role: "user", content: chatInput }]); - const timeoutId = setTimeout(() => { + chatTimeoutRef.current = setTimeout(() => { if (isMounted.current) { setChatMessages((prev) => [ ...prev, { role: "assistant", content: "This is a simulated response. In production, this would connect to your AI backend.", }, ]); } }, 1000); setChatInput(""); - - // Note: If you need to clear this specific timeout on unmount, - // you'd need to store it in a ref and clear it in useEffect cleanup } };
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/app/analyze/loading/page.tsxfrontend/app/analyze/page.tsxfrontend/app/analyze/results/page.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/analyze/loading/page.tsx (1)
frontend/components/theme-toggle.tsx (1)
ThemeToggle(14-42)
frontend/app/analyze/results/page.tsx (4)
frontend/components/theme-toggle.tsx (1)
ThemeToggle(14-42)frontend/components/ui/button.tsx (1)
Button(56-56)frontend/components/ui/badge.tsx (1)
Badge(41-41)frontend/components/ui/input.tsx (1)
Input(22-22)
🔇 Additional comments (10)
frontend/app/analyze/results/page.tsx (3)
38-93: Improved error handling for JSON parsing and redirect logic.The try-catch blocks for
JSON.parseand the redirect logic address the previous review concerns effectively. The component now handles invalid session data gracefully and redirects users when analysis data is missing.
146-152: LGTM!The bias level categorization logic is clear and well-structured.
154-503: Well-structured UI implementation with good UX patterns.The layout improvements successfully address the PR objectives:
- Fixed header keeps navigation accessible
- Constrained scrolling to the results area
- Collapsible chat panel prevents distraction
- Clear error messaging with retry options
frontend/app/analyze/page.tsx (2)
32-48: Effective XSS prevention through protocol whitelisting.The protocol whitelist restricts URLs to
http:andhttps:, effectively blocking dangerous protocols likejavascript:,data:, andfile:. This is a solid security improvement.
177-182: Clear user feedback on validation requirements.The error message explicitly mentions "HTTP or HTTPS", helping users understand why certain URLs are rejected.
frontend/app/analyze/loading/page.tsx (5)
18-34: Well-designed configuration and logging utility.The environment-based configuration for backend URL and API timeout provides flexibility across environments. The logger utility properly respects
NODE_ENV, addressing the previous review comment about console.log statements while maintaining useful debugging capabilities in development.
81-102: Efficient parallel API calls with proper timeout handling.The use of
Promise.allfor parallel requests optimizes loading time. The configurable timeout prevents indefinite hangs, and results are properly persisted to sessionStorage for the results page.
103-123: Comprehensive error handling with proper categorization.The error handling distinguishes between timeouts, backend errors, network issues, and request setup problems. The
isMountedcheck before navigation prevents errors after unmount. This provides a solid foundation for debugging and future user-facing error messages.
70-165: Robust lifecycle management with proper interval cleanup.The interval management is exemplary:
- Intervals are tracked and properly cleared on unmount
isMountedflag prevents post-unmount operations- Dependencies correctly include
routerandsteps.length- Parallel API calls complete before progress intervals begin
This addresses potential memory leaks and state update warnings.
167-323: Polished loading UI with accurate progress indication.The two-column responsive layout effectively communicates progress. The constrained progress calculation (
Math.min(progress, (currentStep + 1) * 20)) ensures the progress bar accurately reflects the current processing step, preventing misleading progress jumps.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
frontend/app/analyze/results/page.tsx (3)
38-93: Well implemented error handling and redirect logic.The try/catch blocks around
JSON.parsecalls and the redirect when no valid analysis data exists properly address the previous review concerns. The cleanup function correctly marks the component as unmounted.
95-111: Protocol whitelist correctly implemented.The URL validation now properly rejects dangerous protocols like
javascript:,data:, andfile:, addressing the previous XSS concern.
130-153: Timeout guard implemented but cleanup could be improved.The
isMountedcheck prevents state updates after unmount, which addresses the core issue from the previous review. The comment acknowledges that full timeout cleanup isn't implemented. For complete cleanup, you could store the timeout ID in a ref and clear it in the useEffect cleanup, but the current implementation is functionally safe.
🧹 Nitpick comments (2)
frontend/app/analyze/results/page.tsx (2)
483-483:onKeyPressis deprecated; preferonKeyDown.The
onKeyPressevent is deprecated in React. Consider switching toonKeyDownfor better compatibility.🔎 Proposed fix
- onKeyPress={(e) => e.key === "Enter" && handleSendMessage()} + onKeyDown={(e) => e.key === "Enter" && handleSendMessage()}
501-511: Consider whether the always-visible notification badge is intentional.The red pulsing badge on line 509 appears unconditionally when the chat is collapsed, which could mislead users into thinking there are unread messages. If this is intentional as an attention-grabber, consider removing the
animate-pulseto reduce distraction. If it's meant to indicate unread messages, it should be tied to actual unread state.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/app/analyze/results/page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/app/analyze/results/page.tsx (4)
frontend/components/theme-toggle.tsx (1)
ThemeToggle(14-42)frontend/components/ui/button.tsx (1)
Button(56-56)frontend/components/ui/badge.tsx (1)
Badge(41-41)frontend/components/ui/input.tsx (1)
Input(22-22)
🔇 Additional comments (3)
frontend/app/analyze/results/page.tsx (3)
3-36: LGTM!The imports are appropriate and the state declarations are well-organized. The
isMountedref pattern correctly addresses the previous concern about state updates after unmount.
163-430: LGTM!The header, results display, tabs, and "Analyze Another Article" sections are well-structured with proper styling and responsive design. The error handling UI provides clear user feedback.
432-497: LGTM!The collapsible AI Discussion panel is well-implemented with proper layout, scrollable messages area, and fixed input at the bottom.
Problem
The results page UX feels slightly inconsistent with the rest of the app and interrupts the analysis flow.
Proposed Update
Light UX refinements to improve consistency and flow:
I’ve implemented this locally and here's the before/after screenshots:
Before:
After:
Summary by CodeRabbit
New Features
Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.