Skip to content

Minor changes - Redesign Gallery UI and Add Persistent History#6

Open
FlemingJohn wants to merge 6 commits into
FOSSUChennai:mainfrom
FlemingJohn:minor-changes
Open

Minor changes - Redesign Gallery UI and Add Persistent History#6
FlemingJohn wants to merge 6 commits into
FOSSUChennai:mainfrom
FlemingJohn:minor-changes

Conversation

@FlemingJohn

@FlemingJohn FlemingJohn commented Jan 10, 2026

Copy link
Copy Markdown

Symmetric Layout: Side-by-side view for upload and history with perfectly matched heights (450px).
History Gallery: LocalStorage persistence, infinite scroll, and a custom frosted glass scrollbar.
Error Boundary Added global error/404 pages and fixed background scrolling issues.

image

Summary by CodeRabbit

  • Documentation

    • Added contribution guidelines documentation
    • Updated README with project information and setup instructions
  • New Features

    • Added error boundary page for graceful error handling
    • Added 404 not found page
    • Added recent uploads history feature with localStorage persistence
  • Improvements

    • Fixed mouse interaction Y-axis coordinate calculation in visual effects
    • Enhanced custom scrollbar styling

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

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces comprehensive documentation (CONTRIBUTING.md, updated README), adds error handling and 404 page components, implements a client-side upload history feature with localStorage persistence, refines CSS styling with custom scrollbar support, and improves TypeScript types in shader components while removing the disableAnimation prop.

Changes

Cohort / File(s) Summary
Documentation
CONTRIBUTING.md, README.md
New contributing guidelines covering bug reports, features, code contribution, development workflow, UI conventions, and code of conduct. README reframed from Next.js boilerplate to Podu.pics project description with features, architecture, tech stack, prerequisites, environment setup, and local development instructions.
Error Handling & Pages
app/error.tsx, app/not-found.tsx
New GlobalError component (client-side error boundary) rendering fallback UI with warning icon, error title, and "Try Again"/"Back Home" actions. New NotFound component for 404 page with Ghost icon and "Return Home" link. Both styled with Tailwind and Phosphor icons.
Core Feature - History & Upload UI
app/page.tsx
Adds localStorage-backed upload history feature with HistoryItem type, state management to track up to 50 recent uploads, and helper actions (clearHistory, copyHistoryLink, loadMockHistory). New Recent Uploads pane renders conditionally with per-item copy/view actions. Layout refactored to display Upload area and Recent Uploads side-by-side; minor import rename (Image → IconImage) and drag-and-drop UI improvements.
Styling
app/globals.css
Replaces Tailwind @apply utilities with explicit CSS for borders and backgrounds. Introduces .custom-scrollbar block with width, track, thumb, and hover styling for consistent scrollbar appearance.
Component Type Refinement
components/Dither.jsx, components/Dither.tsx
Removed disableAnimation prop from DitheredWaves (time now always updates). Adjusted mouse Y-coordinate calculation to invert Y axis. TypeScript: tightened uniforms type from Map\<string, THREE.Uniform\<any\>> to Map\<string, THREE.Uniform\<number\>>; broadened WaveUniforms index signature to include THREE.Vector2 and THREE.Color. Added explicit type casts for uniform value updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A history of uploads, now traced and kept,
In localStorage's vault, where memories slept.
With error pages dancing and scrollbars refined,
The Podu.pics garden blooms in our mind! 🌼

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: UI redesign (gallery/layout) and persistent history feature with localStorage integration.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@HarshPatel5940

Copy link
Copy Markdown
Member

@FlemingJohn "developed with love" footer is overlapping? above

@HarshPatel5940 HarshPatel5940 removed their assignment Jan 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (5)
components/Dither.tsx (3)

202-205: Fix ref typing to avoid nullability/type errors in TS.
useRef<THREE.Mesh>(null) will fail under strictNullChecks.

Proposed fix
-  const mesh = useRef<THREE.Mesh>(null);
+  const mesh = useRef<THREE.Mesh | null>(null);

180-201: Honor disableAnimation to prevent "does nothing" prop behavior.

The disableAnimation prop is accepted by both DitherProps and DitheredWavesProps, passed from parent to child, and used by external code (e.g., app/page.tsx:162), but the DitheredWaves function does not destructure it and the useFrame hook unconditionally sets u.time.value = clock.getElapsedTime() with no check.

Implement Option A from the original suggestion: destructure disableAnimation and track time manually so the prop actually controls animation state without phase jumps when toggled:

Proposed fix
 function DitheredWaves({
   waveSpeed,
   waveFrequency,
   waveAmplitude,
   waveColor,
   colorNum,
   pixelSize,
+  disableAnimation,
   enableMouseInteraction,
   mouseRadius
 }: DitheredWavesProps) {
   const mesh = useRef<THREE.Mesh>(null);
   const mouseRef = useRef(new THREE.Vector2());
+  const timeRef = useRef(0);
   const { viewport, size, gl } = useThree();

   useFrame(({ clock }) => {
     const u = waveUniformsRef.current;
-    u.time.value = clock.getElapsedTime();
+    // Freeze animation when disableAnimation=true, without jumping phases on re-enable.
+    if (!disableAnimation) {
+      timeRef.current += clock.getDelta();
+    }
+    u.time.value = timeRef.current;

218-227: Use primitive values (size.width, size.height) instead of the size object in the dependency array.

The size object from useThree() is a reactive reference that may change between renders. Including the whole size object in the dependency array can cause unnecessary effect reruns. Extract the primitive values to ensure the effect only runs when the actual width or height values change.

Suggested fix
-  }, [size, gl]);
+  }, [size.width, size.height, gl]);
components/Dither.jsx (2)

225-230: Remove one Y-axis inversion to fix mirrored mouse interaction.

The JavaScript code inverts Y at line 229: (rect.height - (e.clientY - rect.top)), and the fragment shader inverts it again at line 89: vec2(1.0, -1.0). This double inversion causes the interaction to be vertically mirrored—when the user moves the mouse down, the effect appears at the top. Remove the shader's Y inversion by changing line 89 to vec2(1.0, 1.0).


203-223: disableAnimation is accepted but has no effect.
Either honor it by freezing time when true, or remove the prop to avoid misleading the API surface.

Also applies to: 261-289

🤖 Fix all issues with AI agents
In @app/page.tsx:
- Line 419: Trailing whitespace exists in the closing JSX tag `</div >`; remove
the extra space so the tag is `</div>` and scan nearby JSX for any other closing
tags with a space before `>` (e.g., other `</tag >`) and fix them similarly to
eliminate the formatting artifact.
- Line 376: Remove the stray space inside the JSX tag by changing the opening
tag from "< Toaster" to "<Toaster" in the component render (look for the Toaster
usage in page.tsx); ensure any corresponding closing tag or self-closing syntax
remains unchanged and run formatter to confirm spacing is fixed.
- Around line 137-140: Wrap navigator.clipboard.writeText in a try/catch in
copyHistoryLink (and similarly update copyToClipboard) to handle failures: call
await navigator.clipboard.writeText(url) inside try, show toast.success on
success, and in catch log the error and show toast.error with a helpful message
(e.g., "Failed to copy link") so permission or insecure-context failures surface
to the user; include the caught error when logging for diagnostics.
- Around line 82-92: uploadFile currently reads the captured history variable
and computes updatedHistory directly, which can be stale; change setHistory to
use the functional updater (setHistory(prev => { const updated =
[newHistoryItem, ...prev.filter(item => item.id !== metadata.key)].slice(0,50);
localStorage.setItem('podu_history', JSON.stringify(updated)); return updated;
})) so the update always uses the latest state and ensures localStorage is
updated consistently; reference the uploadFile function, the newHistoryItem
construction, setHistory call and 'podu_history' localStorage key when making
this change.

In @README.md:
- Around line 7-8: The README references an image at public/preview.png that is
missing; either add the actual preview image to that path (create a
public/preview.png file with the intended screenshot/preview) or remove the
markdown image reference "![Podu.pics Preview](public/preview.png)" from
README.md so the broken link is gone; ensure the filename and path exactly match
public/preview.png if you add the file.
🧹 Nitpick comments (7)
CONTRIBUTING.md (1)

1-42: Solid contributor guide; consider linking concrete templates/policies.

This reads well. If the repo has issue templates / a CODE_OF_CONDUCT.md, linking them here would make the guidance actionable.

README.md (1)

39-49: Nice setup docs; add an explicit “never commit .env.local” note.
Right now it implies secrets, but doesn’t clearly warn contributors.

Also applies to: 60-63

app/globals.css (1)

118-146: Scrollbar styling is webkit-only; consider adding Firefox support (optional).
This will be a no-op on Firefox unless you also set scrollbar-width / scrollbar-color.

Proposed addition
 .custom-scrollbar::-webkit-scrollbar {
   width: 6px;
 }
@@
 .custom-scrollbar::-webkit-scrollbar-thumb:hover {
   background: rgba(255, 255, 255, 0.2);
 }
+
+/* Firefox */
+.custom-scrollbar {
+  scrollbar-width: thin;
+  scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
+}
app/error.tsx (3)

18-21: Consider integrating an actual error reporting service.

The comment mentions logging to an error reporting service, but currently only uses console.error. For production, consider integrating with a service like Sentry, LogRocket, or a custom error tracking endpoint.


39-50: Navigation approach and handler simplification.

Two minor observations:

  1. Line 40: The callback () => reset() can be simplified to just reset.
  2. Line 46: Using window.location.href causes a full page reload. This may be intentional to fully reset application state after an error, but if you want SPA navigation, consider using Next.js useRouter or a Link component.
♻️ Suggested simplification
 <button
-    onClick={() => reset()}
+    onClick={reset}
     className="flex-1 px-8 py-4 bg-white text-black font-bold rounded-2xl hover:bg-white/90 transition-all duration-200"
 >

53-58: Avoid exposing raw error messages in development UI.

While this block is dev-only, error.message could potentially contain sensitive information (file paths, stack traces, internal details). Consider truncating or sanitizing the message, or displaying error.stack separately for more detailed debugging.

app/page.tsx (1)

142-151: Remove or gate test helper loadMockHistory.

This function appears to be a testing utility but is defined in production code and never invoked in the UI. Consider:

  1. Removing it entirely if no longer needed
  2. Moving it to a separate test utility file
  3. Gating it behind process.env.NODE_ENV === 'development' if needed for local testing
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d53e5f1 and 4192106.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • CONTRIBUTING.md
  • README.md
  • app/error.tsx
  • app/globals.css
  • app/not-found.tsx
  • app/page.tsx
  • components/Dither.jsx
  • components/Dither.tsx
🔇 Additional comments (3)
components/Dither.tsx (1)

134-176: Uniform typing + wrapEffect wrapper looks good.
The stricter THREE.Uniform<number> for RetroEffectImpl matches the actual uniforms used.

app/not-found.tsx (1)

1-39: LGTM! Clean 404 page implementation.

The component correctly uses the SSR import path for Phosphor icons and leverages Next.js's server component model appropriately for a static 404 page. The UI is well-structured with consistent styling.

app/page.tsx (1)

301-362: Well-implemented history gallery UI.

The Recent Uploads section is nicely implemented with:

  • Conditional rendering based on history state
  • Smooth animations using Tailwind's animate-in utilities
  • Proper accessibility with title attributes on buttons
  • Good UX with clear visual hierarchy

One minor suggestion: consider adding aria-label to the icon-only buttons for better screen reader support.

Comment thread app/page.tsx
Comment on lines +82 to +92

// Update history
const newHistoryItem: HistoryItem = {
id: metadata.key,
url: metadata.url,
timestamp: Date.now(),
};

const updatedHistory = [newHistoryItem, ...history.filter(item => item.id !== metadata.key)].slice(0, 50);
setHistory(updatedHistory);
localStorage.setItem('podu_history', JSON.stringify(updatedHistory));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stale closure issue with history state.

The uploadFile function captures the history state at the time of its creation. If uploadFile is called while a previous update is pending or after multiple renders, history may be stale, leading to lost history items.

Use the functional update pattern for setHistory to ensure you're working with the latest state.

🐛 Proposed fix using functional update
-      const updatedHistory = [newHistoryItem, ...history.filter(item => item.id !== metadata.key)].slice(0, 50);
-      setHistory(updatedHistory);
-      localStorage.setItem('podu_history', JSON.stringify(updatedHistory));
+      setHistory(prevHistory => {
+        const updatedHistory = [newHistoryItem, ...prevHistory.filter(item => item.id !== metadata.key)].slice(0, 50);
+        localStorage.setItem('podu_history', JSON.stringify(updatedHistory));
+        return updatedHistory;
+      });
📝 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
// Update history
const newHistoryItem: HistoryItem = {
id: metadata.key,
url: metadata.url,
timestamp: Date.now(),
};
const updatedHistory = [newHistoryItem, ...history.filter(item => item.id !== metadata.key)].slice(0, 50);
setHistory(updatedHistory);
localStorage.setItem('podu_history', JSON.stringify(updatedHistory));
// Update history
const newHistoryItem: HistoryItem = {
id: metadata.key,
url: metadata.url,
timestamp: Date.now(),
};
setHistory(prevHistory => {
const updatedHistory = [newHistoryItem, ...prevHistory.filter(item => item.id !== metadata.key)].slice(0, 50);
localStorage.setItem('podu_history', JSON.stringify(updatedHistory));
return updatedHistory;
});
🤖 Prompt for AI Agents
In @app/page.tsx around lines 82 - 92, uploadFile currently reads the captured
history variable and computes updatedHistory directly, which can be stale;
change setHistory to use the functional updater (setHistory(prev => { const
updated = [newHistoryItem, ...prev.filter(item => item.id !==
metadata.key)].slice(0,50); localStorage.setItem('podu_history',
JSON.stringify(updated)); return updated; })) so the update always uses the
latest state and ensures localStorage is updated consistently; reference the
uploadFile function, the newHistoryItem construction, setHistory call and
'podu_history' localStorage key when making this change.

Comment thread app/page.tsx
Comment on lines +137 to +140
const copyHistoryLink = async (url: string) => {
await navigator.clipboard.writeText(url);
toast.success('Link copied to clipboard');
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for clipboard API.

navigator.clipboard.writeText can fail (e.g., due to permissions or non-secure context). The similar copyToClipboard function at line 118 also lacks error handling. Consider wrapping in try/catch and showing an error toast on failure.

🐛 Proposed fix
  const copyHistoryLink = async (url: string) => {
-   await navigator.clipboard.writeText(url);
-   toast.success('Link copied to clipboard');
+   try {
+     await navigator.clipboard.writeText(url);
+     toast.success('Link copied to clipboard');
+   } catch (err) {
+     toast.error('Failed to copy link');
+   }
  };
📝 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 copyHistoryLink = async (url: string) => {
await navigator.clipboard.writeText(url);
toast.success('Link copied to clipboard');
};
const copyHistoryLink = async (url: string) => {
try {
await navigator.clipboard.writeText(url);
toast.success('Link copied to clipboard');
} catch (err) {
toast.error('Failed to copy link');
}
};
🤖 Prompt for AI Agents
In @app/page.tsx around lines 137 - 140, Wrap navigator.clipboard.writeText in a
try/catch in copyHistoryLink (and similarly update copyToClipboard) to handle
failures: call await navigator.clipboard.writeText(url) inside try, show
toast.success on success, and in catch log the error and show toast.error with a
helpful message (e.g., "Failed to copy link") so permission or insecure-context
failures surface to the user; include the caught error when logging for
diagnostics.

Comment thread app/page.tsx

{/* Toast Notifications with Frosted Glass Styling */}
<Toaster
< Toaster

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix whitespace in JSX tag.

There's an extra space in < Toaster which, while valid JSX, appears to be a formatting artifact.

✏️ Suggested fix
-      < Toaster
+      <Toaster
📝 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
< Toaster
<Toaster
🤖 Prompt for AI Agents
In @app/page.tsx at line 376, Remove the stray space inside the JSX tag by
changing the opening tag from "< Toaster" to "<Toaster" in the component render
(look for the Toaster usage in page.tsx); ensure any corresponding closing tag
or self-closing syntax remains unchanged and run formatter to confirm spacing is
fixed.

Comment thread app/page.tsx
}
/>
</div>
</div >

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix trailing whitespace in closing tag.

Similar formatting artifact with </div >.

✏️ Suggested fix
-    </div >
+    </div>
📝 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
</div >
</div>
🤖 Prompt for AI Agents
In @app/page.tsx at line 419, Trailing whitespace exists in the closing JSX tag
`</div >`; remove the extra space so the tag is `</div>` and scan nearby JSX for
any other closing tags with a space before `>` (e.g., other `</tag >`) and fix
them similarly to eliminate the formatting artifact.

Comment thread README.md
Comment on lines +7 to +8
![Podu.pics Preview](public/preview.png)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

test -f public/preview.png && echo "OK: public/preview.png exists" || echo "MISSING: public/preview.png"

Repository: FOSSUChennai/podu.pics

Length of output: 93


Add the missing preview image to public/preview.png or remove the reference from the README.

The image referenced at line 7 is not present in the repository, causing a broken media link on GitHub.

🤖 Prompt for AI Agents
In @README.md around lines 7 - 8, The README references an image at
public/preview.png that is missing; either add the actual preview image to that
path (create a public/preview.png file with the intended screenshot/preview) or
remove the markdown image reference "![Podu.pics Preview](public/preview.png)"
from README.md so the broken link is gone; ensure the filename and path exactly
match public/preview.png if you add the file.

@FlemingJohn

Copy link
Copy Markdown
Author

@FlemingJohn "developed with love" footer is overlapping? above

okay i will fix it

@HarshPatel5940

Copy link
Copy Markdown
Member

okay i will fix it

Hey, still waiting for this. lmk if you can work on it.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants