Skip to content

Improve results page UX and layout#123

Open
Sujatx wants to merge 3 commits intoAOSSIE-Org:mainfrom
Sujatx:feat/results-layout-improvements
Open

Improve results page UX and layout#123
Sujatx wants to merge 3 commits intoAOSSIE-Org:mainfrom
Sujatx:feat/results-layout-improvements

Conversation

@Sujatx
Copy link

@Sujatx Sujatx commented Jan 3, 2026

Problem

The results page UX feels slightly inconsistent with the rest of the app and interrupts the analysis flow.

  • Users need to scroll back up or navigate backward to analyze another article
  • The entire page scrolls, which feels less app-like
  • The AI Discussion panel always stays visible, even when focus is on analysis

Proposed Update

Light UX refinements to improve consistency and flow:

  • Make the results page consistent with the overall app theme
  • Only the main results area scrolls; header and “Analyze another article” stay fixed
  • Allow analyzing another article without scrolling back or navigating away
  • Make the AI Discussion panel collapsible into a small bottom-right chat button

I’ve implemented this locally and here's the before/after screenshots:

Before:

Image

After:

Image

Summary by CodeRabbit

  • New Features

    • Tabbed results (Article, Perspective, Fact-Check) and persistent top header with theme toggle
    • AI discussion panel — simplified simulated chat with collapse/expand and floating reopen button
    • Analysis results persisted in-session for smoother navigation
  • Improvements

    • Redesigned Analyze, Loading, and Results pages with two-column responsive layouts, animated visuals, and refined spacing/typography
    • Enhanced progress UI with dynamic percentage, automated step advancement, and stronger error/retry handling
    • Stricter URL validation (only http/https accepted)
  • Chores

    • Upgraded Next.js to 16.1.1

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

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.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 3, 2026

📝 Walkthrough

Walkthrough

Loading, 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

Cohort / File(s) Summary
Loading / Async Workflow
frontend/app/analyze/loading/page.tsx
Full rewrite: runtime config (BACKEND_URL, API_TIMEOUT), lightweight logger, sessionStorage reads/writes, parallel POSTs to /api/process and /api/bias, detailed Axios error handling, isMounted guards, step/progress intervals with cleanup, and navigation to results or back to analyze on failure. UI replaced with two-column layout, live URL, and dynamic progress bar.
Analyze Page (input & UI)
frontend/app/analyze/page.tsx
UI refactor and stricter URL validation (only http:/https:), redesigned hero/input and examples, elevated card input with action button; stores URL to sessionStorage and navigates to loading.
Results Page (UI & state)
frontend/app/analyze/results/page.tsx
Default export renamed to ResultsPage; state now initializes from sessionStorage (analysisData, BiasScore), explicit URL validation and analyze action, error banner and retry flow, simulated chat, tabbed content, bias visualization, header/ThemeToggle and collapsible AI pane.
Build & TypeScript config
frontend/package.json, frontend/tsconfig.json
next bumped to ^16.1.1; TS jsx changed to react-jsx; include patterns extended (adds .next/dev/types/**/*.ts) and minor formatting 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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰✨ I hopped through code with nimble paws,
Posted URLs and checked the laws,
Two APIs danced as progress grew,
Session saved the score and view,
Results appeared — a carrot clue.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve results page UX and layout' is directly related to the main changes in the PR, which involve substantial UI/UX overhauls across the results page, analyze page, and loading page, including layout restructuring, new UI elements, and improved user experience.
✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 URL constructor considers javascript:, data:, and file: URLs as valid. If this URL is later used in an anchor href or iframe src, 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 the useEffect callback. 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.space is 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 validateUrl and handleUrlChange logic is duplicated from frontend/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: onKeyPress is deprecated — use onKeyDown instead.

The onKeyPress event 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

📥 Commits

Reviewing files that changed from the base of the PR and between 19c83ed and 5185135.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • frontend/app/analyze/loading/page.tsx
  • frontend/app/analyze/page.tsx
  • frontend/app/analyze/results/page.tsx
  • frontend/package.json
  • frontend/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 include and exclude arrays appropriately capture Next.js generated type files, including .next/dev/types/**/*.ts for 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
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 isMounted check 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5185135 and 043d9d9.

📒 Files selected for processing (3)
  • frontend/app/analyze/loading/page.tsx
  • frontend/app/analyze/page.tsx
  • frontend/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.parse and 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: and https:, effectively blocking dangerous protocols like javascript:, data:, and file:. 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.all for 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 isMounted check 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
  • isMounted flag prevents post-unmount operations
  • Dependencies correctly include router and steps.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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.parse calls 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:, and file:, addressing the previous XSS concern.


130-153: Timeout guard implemented but cleanup could be improved.

The isMounted check 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: onKeyPress is deprecated; prefer onKeyDown.

The onKeyPress event is deprecated in React. Consider switching to onKeyDown for 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-pulse to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 043d9d9 and 30f8955.

📒 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 isMounted ref 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant