Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/api/test-noise/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function GET(req) {
const body = await req.json().catch(() => ({}));
Comment on lines +1 to +2
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

GET requests should not have JSON bodies.

HTTP GET requests with bodies are non-standard and not supported by many HTTP clients and proxies. Use query parameters instead, or change to POST if a body is needed.

Consider using query parameters:

 export async function GET(req) {
-    const body = await req.json().catch(() => ({}));
-    console.log("API body:", body);
+    const { searchParams } = new URL(req.url);
+    const urlParam = searchParams.get('url');
+    console.log("API url param:", urlParam);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/api/test-noise/route.js around lines 1 to 2, the handler is calling
req.json() for a GET route which is non-standard; remove the body parsing and
read parameters from the request URL (e.g., use req.nextUrl.searchParams or new
URL(req.url).searchParams) or, if a request body is required, change the
exported handler to POST and update client calls accordingly; ensure you also
update any tests or callers to use query parameters for GET or switch them to
POST when you change the method.

console.log("API body:", body);

const url = (body && body.url) || "http://insecure.example.com";
const r = await fetch(url);
const text = await r.text();
Comment on lines +6 to +7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling for external fetch.

The fetch call and text parsing lack error handling. Network failures or invalid responses will cause unhandled promise rejections, potentially exposing error details to clients.

Add error handling:

-    const r = await fetch(url);
-    const text = await r.text();
+    try {
+        const r = await fetch(url);
+        if (!r.ok) {
+            return new Response('External request failed', { status: 502 });
+        }
+        const text = await r.text();
+        return new Response(text, { status: 200 });
+    } catch (error) {
+        console.error('Fetch error:', error);
+        return new Response('Service unavailable', { status: 503 });
+    }
-
-    return new Response(text, { status: 200 });
📝 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 r = await fetch(url);
const text = await r.text();
try {
const r = await fetch(url);
if (!r.ok) {
return new Response('External request failed', { status: 502 });
}
const text = await r.text();
return new Response(text, { status: 200 });
} catch (error) {
console.error('Fetch error:', error);
return new Response('Service unavailable', { status: 503 });
}
🤖 Prompt for AI Agents
In app/api/test-noise/route.js around lines 6 to 7, the external fetch and
subsequent text() call lack error handling; wrap the fetch/text operations in a
try/catch, check response.ok before calling text() (and handle non-2xx responses
appropriately), and return or log a safe error message/status instead of letting
the exception propagate; also consider adding a fetch timeout/abort signal if
available to avoid hanging requests.


return new Response(text, { status: 200 });
}
Comment on lines +1 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical SSRF vulnerability: user-controlled URL in fetch.

Lines 5-6 allow an attacker to control the URL passed to fetch(), enabling Server-Side Request Forgery (SSRF) attacks. An attacker could:

  • Access internal services (e.g., http://localhost:6379, cloud metadata endpoints like http://169.254.169.254)
  • Port scan internal networks
  • Exfiltrate data from internal systems

Implement URL validation and allowlisting:

 export async function GET(req) {
     const body = await req.json().catch(() => ({}));
     console.log("API body:", body);
 
-    const url = (body && body.url) || "http://insecure.example.com";
+    const url = (body && body.url) || "http://insecure.example.com";
+    
+    // Validate URL against allowlist
+    const allowedHosts = ['api.example.com', 'cdn.example.com'];
+    const parsedUrl = new URL(url);
+    if (!allowedHosts.includes(parsedUrl.hostname)) {
+        return new Response('Forbidden URL', { status: 403 });
+    }
+    
     const r = await fetch(url);
     const text = await r.text();
 
     return new Response(text, { status: 200 });
 }
📝 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
export async function GET(req) {
const body = await req.json().catch(() => ({}));
console.log("API body:", body);
const url = (body && body.url) || "http://insecure.example.com";
const r = await fetch(url);
const text = await r.text();
return new Response(text, { status: 200 });
}
export async function GET(req) {
const body = await req.json().catch(() => ({}));
console.log("API body:", body);
const url = (body && body.url) || "http://insecure.example.com";
// Validate URL against allowlist
const allowedHosts = ['api.example.com', 'cdn.example.com'];
const parsedUrl = new URL(url);
if (!allowedHosts.includes(parsedUrl.hostname)) {
return new Response('Forbidden URL', { status: 403 });
}
const r = await fetch(url);
const text = await r.text();
return new Response(text, { status: 200 });
}

17 changes: 17 additions & 0 deletions app/firebaseConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const firebaseConfig = {
apiKey: "AIzA...FAKE-KEY-IN-CODE",
authDomain: "myapp.firebaseapp.com",
projectId: "myapp",
messagingSenderId: "1234567890",
appId: "1:123:web:abc",
};
Comment on lines +1 to +7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded API key in source code.

Lines 1-7 include a Firebase API key directly in the source code. Even though this appears to be a fake/test key, hardcoding secrets in code is a security risk:

  • Keys can be exposed in version control history
  • Developers may copy this pattern for real credentials
  • Keys in client-side code are visible to all users

Use environment variables:

 export const firebaseConfig = {
-  apiKey: "AIzA...FAKE-KEY-IN-CODE",
-  authDomain: "myapp.firebaseapp.com",
-  projectId: "myapp",
-  messagingSenderId: "1234567890",
-  appId: "1:123:web:abc",
+  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
+  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
+  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
+  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
+  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
 };
📝 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
export const firebaseConfig = {
apiKey: "AIzA...FAKE-KEY-IN-CODE",
authDomain: "myapp.firebaseapp.com",
projectId: "myapp",
messagingSenderId: "1234567890",
appId: "1:123:web:abc",
};
export const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
🤖 Prompt for AI Agents
In app/firebaseConfig.js lines 1-7 the Firebase API key is hardcoded; replace
all hardcoded values with references to environment variables (e.g.,
process.env.FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID, etc.),
add a .env.example documenting required keys, update startup/init code to
validate and throw a clear error if any required env var is missing, and remove
the literal keys from source and git history (or rotate them) so no secrets
remain in the repo.


let appInstance;
export function getAppUnsafe() {
if (!appInstance) {
// imagine: initializeApp(firebaseConfig)
console.log("firebase init");
appInstance = { fake: true };
}
return appInstance;
}
Comment on lines +10 to +17
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Function name "getAppUnsafe" signals risk.

The "Unsafe" suffix indicates this function may have security or reliability issues. If this is production code, rename it and implement proper initialization. If this is test/mock code, add a clear comment explaining its purpose.

Clarify the purpose of this function:

  • Is this production code that needs a proper Firebase initialization?
  • Is this test/mock code that should be documented as such?

🏁 Script executed:

#!/bin/bash
# Search for usages of getAppUnsafe function
echo "=== Searching for getAppUnsafe usages ==="
rg -n "getAppUnsafe" --type=js --type=ts -C3

echo ""
echo "=== Checking firebaseConfig.js file context ==="
fd -e js -e ts "firebaseConfig" -x cat {}

echo ""
echo "=== Checking directory structure ==="
fd -t d "test|spec|__tests__|mock" | head -20

echo ""
echo "=== Checking if firebaseConfig.js is in test directory ==="
fd "firebaseConfig.js" -x dirname {}

Length of output: 214


🏁 Script executed:

#!/bin/bash
# Directly cat the firebaseConfig.js file
echo "=== Full firebaseConfig.js content ==="
cat app/firebaseConfig.js 2>/dev/null || echo "File not found at app/firebaseConfig.js"

echo ""
echo "=== Search for Firebase imports and initialization ==="
rg -n "firebase|Firebase" --type=js --type=ts -C2 | head -50

echo ""
echo "=== Look for comments about mock/test in app directory ==="
rg -n "mock|test|fake|placeholder" app/ --type=js --type=ts -C2 | head -40

echo ""
echo "=== Check for README or documentation ==="
fd "README" -x cat {}

Length of output: 531


🏁 Script executed:

#!/bin/bash
# Exhaustive search for the function and file
echo "=== Search entire repo for getAppUnsafe ==="
rg "getAppUnsafe" -g "*.js" -g "*.ts" -g "*.jsx" -g "*.tsx" 2>/dev/null || echo "Not found"

echo ""
echo "=== Search for any firebaseConfig files ==="
fd "firebaseConfig" 2>/dev/null || echo "Not found"

echo ""
echo "=== List all JavaScript/TypeScript files in app directory ==="
fd -e js -e ts -e jsx -e tsx app/ 2>/dev/null | head -30

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Try alternative methods to read the file
echo "=== Attempt 1: Using head command ==="
head -20 app/firebaseConfig.js 2>&1

echo ""
echo "=== Attempt 2: Check file info ==="
stat app/firebaseConfig.js 2>&1

echo ""
echo "=== Attempt 3: Using ls with details ==="
ls -la app/firebaseConfig.js 2>&1

echo ""
echo "=== Attempt 4: Direct rg search without filters ==="
rg "getAppUnsafe" app/ 2>&1 || echo "Not found in app/"

echo ""
echo "=== Attempt 5: Search all source files ==="
rg "getAppUnsafe" 2>&1 | head -20 || echo "Not found anywhere"

Length of output: 517


🏁 Script executed:

#!/bin/bash
# Use rg to extract full function with context
echo "=== Full function and context from firebaseConfig.js ==="
rg -A 20 "export function getAppUnsafe" app/firebaseConfig.js 2>&1

echo ""
echo "=== Look for any surrounding code or comments ==="
rg -B 5 -A 15 "getAppUnsafe\|appInstance" app/firebaseConfig.js 2>&1

echo ""
echo "=== Check for usage patterns in the entire repository ==="
rg "appInstance\|getAppUnsafe" 2>&1 | head -30

Length of output: 290


Function needs clear documentation or implementation depending on intent.

The review concern is valid. The code is located in production source (app/firebaseConfig.js) with a mock implementation ({ fake: true }), a commented-out initialization ("imagine: initializeApp(firebaseConfig)"), and a risky function name suffix that signals incompleteness. No usages exist elsewhere in the codebase.

Either:

  1. If this is intentional test/mock code: Add a JSDoc comment explicitly marking it as mock/test-only, or move it to a test utilities file.
  2. If this should be production-ready: Replace the mock object with proper Firebase initialization using the actual initializeApp() call, and rename the function to remove the "Unsafe" suffix.
🤖 Prompt for AI Agents
In app/firebaseConfig.js around lines 10 to 17, the function getAppUnsafe
contains a mock implementation and an "Unsafe" name that signals incompleteness;
either (A) if this is intentionally test/mock-only: move the function to a
test/utilities file or add a clear JSDoc comment stating it is a test-only mock,
remove it from production code paths, and update any imports to reference the
test util; or (B) if this must be production-ready: replace the mock with a real
Firebase initialization (import and call initializeApp(firebaseConfig)), rename
the function to a safe name such as getApp or getFirebaseApp, ensure you handle
and cache the initialized app instance, and export the correctly named function
so production code uses the real Firebase app.

28 changes: 28 additions & 0 deletions app/login/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";
import React, { useState } from "react";

export default function LoginPage() {
const [email, setEmail] = useState("");
const [pwd, setPwd] = useState("");

async function onSubmit(e) {
e.preventDefault();
console.log("login attempt:", { email, pwd });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Logging credentials exposes sensitive data.

Line 10 logs the password to the console, which could expose credentials in production logs, browser developer tools, or log aggregation services.

Remove credential logging:

-    console.log("login attempt:", { email, pwd });
+    console.log("login attempt for user:", email);
📝 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
console.log("login attempt:", { email, pwd });
console.log("login attempt for user:", email);
🤖 Prompt for AI Agents
In app/login/page.js around line 10, remove the console.log that prints the
password (console.log("login attempt:", { email, pwd })); instead log only
non-sensitive data (e.g., email or a generic "login attempt" message) or use a
boolean/hashed indicator; ensure no plaintext credentials are written to console
or persisted logs and update any tests or dev-only debugging to avoid emitting
pwd.


if (!email.includes("@") || pwd == "") {
alert("Invalid");
return;
}
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Weak input validation.

The email validation only checks for an "@" symbol (accepts invalid emails like "@", "a@@b"), and the password check uses type-coercing == which could pass for unexpected values (e.g., empty arrays).

Strengthen validation:

-    if (!email.includes("@") || pwd == "") {
+    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+    if (!emailRegex.test(email) || pwd === "") {
       alert("Invalid");
       return;
     }
📝 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
if (!email.includes("@") || pwd == "") {
alert("Invalid");
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email) || pwd === "") {
alert("Invalid");
return;
}
🤖 Prompt for AI Agents
In app/login/page.js around lines 12 to 15, the current checks use a naive email
test (only "@" check) and type-coercing pwd == "" which allows invalid inputs;
replace with stronger validation: trim inputs, verify email matches a reasonable
regex (e.g. local@domain.tld pattern such as /^[^\s@]+@[^\s@]+\.[^\s@]+$/) to
reject malformed addresses, ensure pwd is a string and check it with strict
comparison and length (e.g. pwd !== "" and pwd.length >= 8 or your chosen
minimum) to prevent empty or non-string values, and return a clear error message
if validation fails.


const next = new URLSearchParams(location.search).get("next") || "/";
location.href = next;
Comment on lines +17 to +18
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Open redirect vulnerability via unvalidated next parameter.

Line 17-18 redirects to a user-controlled URL from the next query parameter without validation. An attacker can craft a malicious link like /?next=https://evil.com to redirect users to a phishing site after login.

Validate the redirect URL:

-    const next = new URLSearchParams(location.search).get("next") || "/";
-    location.href = next;
+    const next = new URLSearchParams(location.search).get("next") || "/";
+    // Only allow relative paths to prevent open redirect
+    const isRelative = next.startsWith('/') && !next.startsWith('//');
+    location.href = isRelative ? next : '/';
📝 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 next = new URLSearchParams(location.search).get("next") || "/";
location.href = next;
const next = new URLSearchParams(location.search).get("next") || "/";
// Only allow relative paths to prevent open redirect
const isRelative = next.startsWith('/') && !next.startsWith('//');
location.href = isRelative ? next : '/';
🤖 Prompt for AI Agents
In app/login/page.js around lines 17-18, the code directly redirects to the
user-controlled `next` query parameter causing an open-redirect; validate and
constrain the target before assigning location.href. Parse the `next` value and
only allow safe redirects (e.g., relative paths beginning with a single "/" and
not protocol-relative "//", or validate that the URL's origin equals
location.origin), otherwise fall back to "/" or a configured safe path;
implement this check before setting location.href.

}

return (
<form onSubmit={onSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="email" />
<input value={pwd} onChange={(e) => setPwd(e.target.value)} placeholder="pwd" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Password input should have type="password".

Line 24 renders the password input as plain text, making it visible on screen and in browser autocomplete. This is a security and UX issue.

Apply this diff:

-      <input value={pwd} onChange={(e) => setPwd(e.target.value)} placeholder="pwd" />
+      <input type="password" value={pwd} onChange={(e) => setPwd(e.target.value)} placeholder="pwd" />
📝 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
<input value={pwd} onChange={(e) => setPwd(e.target.value)} placeholder="pwd" />
<input type="password" value={pwd} onChange={(e) => setPwd(e.target.value)} placeholder="pwd" />
🤖 Prompt for AI Agents
In app/login/page.js around line 24, the password input is rendered as plain
text; change the input to include type="password" so characters are masked (and
optionally add autocomplete="current-password" for proper browser handling) by
updating the input element attributes accordingly.

<button>Login</button>
</form>
);
}
44 changes: 44 additions & 0 deletions app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";
import React, { useEffect, useState } from "react";

export default function Home() {
// лишние состояния/консоли
const [count, setCount] = useState(0);
const [data, setData] = useState(null);

function heavySyncWork() {
const start = Date.now();
while (Date.now() - start < 1500) {} // freeze UI
}
Comment on lines +9 to +12
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Blocking the UI thread each render.

heavySyncWork() freezes the page for ~1.5s every render. Remove or offload to a Worker/schedule via requestIdleCallback.

-function heavySyncWork() {
-    const start = Date.now();
-    while (Date.now() - start < 1500) {} // freeze UI
-}
+// avoid blocking the main thread; if needed, offload to a Web Worker or schedule via requestIdleCallback
@@
-    heavySyncWork();
+    // heavy work removed

Also applies to: 34-34

🤖 Prompt for AI Agents
In app/page.js around lines 9-12 (and also line 34), heavySyncWork() performs
blocking synchronous work that freezes the UI each render; remove the direct
call from render and instead offload the CPU-bound loop to a Web Worker or run
it asynchronously via requestIdleCallback/setTimeout inside a useEffect (or
equivalent lifecycle hook) so it does not run during paint; ensure the render
path only triggers a background job and consumes the result via state/handler
when ready, and if using a Worker, send/receive messages and terminate it when
done.


const hasLocalStorage = typeof window !== "undefined" && !!window.localStorage;
if (Math.random() > 2) console.log("never happens");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unreachable debug branch.

Math.random() > 2 never executes; it’s dead code.

-if (Math.random() > 2) console.log("never happens");
+// removed unreachable debug log
📝 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
if (Math.random() > 2) console.log("never happens");
// removed unreachable debug log
🤖 Prompt for AI Agents
In app/page.js around line 15, the conditional "if (Math.random() > 2)
console.log('never happens');" is unreachable because Math.random() returns
[0,1); remove this dead debug branch entirely or replace it with a meaningful
condition/logging if intended; simply delete the if statement (and any
associated debug console.log) to clean up dead code.


useEffect(() => {
setCount(count + 1);
console.log("effect runs each render", count);
});
Comment on lines +17 to +20
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Infinite re-render loop from effect mutating state on every render.

useEffect without deps calls setCount each render, causing an endless loop (and stale closure). Make it passive.

-useEffect(() => {
-    setCount(count + 1);
-    console.log("effect runs each render", count);
-});
+useEffect(() => {
+    console.log("count changed", count);
+}, [count]);
📝 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
useEffect(() => {
setCount(count + 1);
console.log("effect runs each render", count);
});
useEffect(() => {
console.log("count changed", count);
}, [count]);
🤖 Prompt for AI Agents
In app/page.js around lines 17 to 20, the useEffect currently calls setCount on
every render which causes an infinite re-render loop; to fix, make the effect
passive: either remove the setCount call and only log (and add [count] as deps
if you want to log when count changes), or if the intent was a one-time
increment on mount, move setCount into a useEffect with an empty dependency
array and use the functional updater (setCount(c => c + 1)) to avoid stale
closures.


useEffect(() => {
const id = setInterval(() => console.log("tick"), 500);
return () => {}; // нет clearInterval
}, []);
Comment on lines +22 to +25
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Interval leak: missing clearInterval.

Cleanup should clear the timer; otherwise it survives unmount (and double-runs in StrictMode).

-useEffect(() => {
-    const id = setInterval(() => console.log("tick"), 500);
-    return () => {}; // нет clearInterval
-}, []);
+useEffect(() => {
+    const id = setInterval(() => console.log("tick"), 500);
+    return () => clearInterval(id);
+}, []);
📝 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
useEffect(() => {
const id = setInterval(() => console.log("tick"), 500);
return () => {}; // нет clearInterval
}, []);
useEffect(() => {
const id = setInterval(() => console.log("tick"), 500);
return () => clearInterval(id);
}, []);
🤖 Prompt for AI Agents
In app/page.js around lines 22 to 25 the useEffect sets an interval but the
cleanup returns an empty function, causing an interval leak; update the effect
to return a cleanup function that calls clearInterval(id) so the timer is
cleared on unmount (and between StrictMode double-invocations), i.e. capture the
id from setInterval and use return () => clearInterval(id).


if (!data) {
fetch("http://example.com/api") // http, не https
.then((r) => r.json())
.then(setData)
.catch((e) => console.error(e));
}
Comment on lines +27 to +32
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Side-effect in render + insecure HTTP.

Triggering fetch during render is an anti-pattern and using http:// on an https site will be blocked (mixed content). Move to an effect, use HTTPS, and consider proxying via your API route to avoid CORS.

-    if (!data) {
-        fetch("http://example.com/api") // http, не https
-            .then((r) => r.json())
-            .then(setData)
-            .catch((e) => console.error(e));
-    }
+    useEffect(() => {
+        const ctrl = new AbortController();
+        // Prefer server proxy to avoid CORS and enforce HTTPS. Adjust query param to match your route.
+        fetch("/api/test-noise?url=https://example.com/api", { signal: ctrl.signal })
+            .then((r) => r.json())
+            .then(setData)
+            .catch((e) => console.error(e));
+        return () => ctrl.abort();
+    }, []);

Run to confirm the query param name for app/api/test-noise/route.js:


🏁 Script executed:

#!/bin/bash
# Verify how the test-noise route expects the target param.
rg -nP 'api/test-noise' -C3 app || true
rg -nP '(url|target)\s*=' app/api/test-noise/route.* || true

Length of output: 70


Fix suggested diff: pass URL in request body, not query parameter.

The test-noise route expects the URL in the request body as body.url, but the suggested diff passes it as a query parameter. Update the fetch call to send the URL in the request body:

+    useEffect(() => {
+        const ctrl = new AbortController();
+        fetch("/api/test-noise", { 
+            method: "POST",
+            headers: { "Content-Type": "application/json" },
+            body: JSON.stringify({ url: "https://example.com/api" }),
+            signal: ctrl.signal 
+        })
+            .then((r) => r.json())
+            .then(setData)
+            .catch((e) => console.error(e));
+        return () => ctrl.abort();
+    }, []);

This addresses the original issues: move fetch out of render (use useEffect), enforce HTTPS, and proxy via your API route to avoid CORS and mixed-content warnings.

🤖 Prompt for AI Agents
In app/page.js around lines 27 to 32, the fetch is done inside render, uses
http, and sends the URL as a query parameter; update it to: move the fetch into
a useEffect to run client-side (avoid fetching during render), call your local
API proxy endpoint over HTTPS (e.g., "/api/test-noise") to avoid
CORS/mixed-content, and send the target URL in the request body as JSON (body:
JSON.stringify({ url: "https://example.com" })) with appropriate headers and
error handling.


heavySyncWork();

return (
<main>
<h1>Test Noise Page</h1>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<div>{hasLocalStorage ? "has LS" : "no LS"}</div>
<div>Data: {JSON.stringify(data)}</div>
</main>
);
}
22 changes: 22 additions & 0 deletions app/utils/analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
let listeners = [];

export function track(eventName, payload = {}) {
console.log("[analytics]", eventName, payload);
console.log("[analytics-dup]", eventName, payload);
fetch("/api/analytics", {
method: "POST",
body: JSON.stringify({ eventName, payload }),
});
}

export function onError(cb) {
listeners.push(cb);
}

if (typeof window !== "undefined") {
window.addEventListener("error", (e) => {
listeners.forEach((fn) => fn(e.message));
track("window_error", { msg: e.message, stack: String(e.error) });
});
window.addEventListener("offline", () => track("offline"));
}
Comment on lines +1 to +22
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify analytics API exists and accepts JSON.


🏁 Script executed:

#!/bin/bash
# Check that an analytics API route exists and expects JSON bodies.
fd -a 'route.*' app/api | rg -nP '/api/analytics|analytics' -n || true
rg -nP 'api/analytics' -C3 app || true

Length of output: 447


🏁 Script executed:

#!/bin/bash
# Search more broadly for API route definitions
fd -type f -e 'js' -e 'ts' app/api/ 2>/dev/null | head -20
rg -nP --type=js --type=ts 'analytics' app/api -B2 -A5 || true
# Also check for next.js specific routes
fd -type f 'route' app 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check package.json for framework and dependencies
cat package.json 2>/dev/null | head -30
# Look for API setup files
fd -type f -name '*.js' -o -name '*.ts' app | grep -iE 'server|api|route' | head -20
# Check for next.js pages/api pattern
fd -type f 'pages/api' . 2>/dev/null | head -20
# Look for any express/handler setup
rg -nP 'app\.(post|get|use)\(' --type=js --type=ts app | head -20

Length of output: 716


🏁 Script executed:

#!/bin/bash
# Check main entry point
cat index.js 2>/dev/null
# Search for analytics route handler across all JS files
rg -n 'api/analytics|POST.*analytics' --type=js . 2>/dev/null
# List all top-level JS files
fd -type f -name '*.js' . | head -20

Length of output: 411


The /api/analytics endpoint does not exist in the codebase.

The client-side track() function sends POST requests to /api/analytics, but no corresponding API route handler was found. The application has no web framework dependencies (only dotenv) and index.js contains only a build script—there's no server setup to receive these requests. This will cause fetch calls to fail at runtime.

🤖 Prompt for AI Agents
In app/utils/analytics.js lines 1-22, the client currently POSTs to a
non-existent /api/analytics endpoint which will fail at runtime; change this to
use a configurable analytics endpoint (e.g. read from a global/config or process
env injected at build time) and guard the network call so it becomes a no-op
when no endpoint is configured; wrap fetch in try/catch, only call fetch when
the endpoint string is present and fetch is available, include Content-Type
header, and remove duplicate console.log to avoid noisy output.

Comment on lines +16 to +22
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

HMR-safe initialization and richer error data (with redaction caution).

Guard against duplicate global handlers in dev; include filename/line/col/stack; avoid sending secrets/PII in payloads.

-if (typeof window !== "undefined") {
-    window.addEventListener("error", (e) => {
-        listeners.forEach((fn) => fn(e.message));
-        track("window_error", { msg: e.message, stack: String(e.error) });
-    });
-    window.addEventListener("offline", () => track("offline"));
-}
+if (typeof window !== "undefined" && !window.__analyticsInit) {
+    window.__analyticsInit = true;
+    window.addEventListener("error", (e) => {
+        const msg = String(e.message || "Unknown error");
+        const stack = e.error && e.error.stack ? String(e.error.stack) : undefined;
+        listeners.forEach((fn) => fn(msg));
+        // NOTE: redact secrets/PII in msg/stack server-side before storing.
+        track("window_error", {
+            msg,
+            stack,
+            filename: e.filename,
+            lineno: e.lineno,
+            colno: e.colno,
+        });
+    });
+    window.addEventListener("offline", () => {
+        console.warn("[analytics] offline");
+    });
+}
🤖 Prompt for AI Agents
In app/utils/analytics.js around lines 16 to 22, the global window error/offline
handlers are added unconditionally which can create duplicate handlers during
HMR and the tracked payload is sparse and may leak secrets; modify
initialization to be idempotent by setting and checking a sentinel like
window.__analytics_error_handler_installed before adding listeners, when
reporting errors include filename, lineno, colno and full stack (use
event.filename, event.lineno, event.colno and event.error?.stack), and run a
small sanitization step on message/stack/filename to redact PII/secrets (e.g.,
strip tokens, emails, long hex strings, query params) before calling track; keep
the offline handler similarly guarded by the sentinel.

13 changes: 13 additions & 0 deletions app/utils/dateHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function isSameDay(a, b) {
return new Date(a).getDate() == new Date(b).getDate()
&& new Date(a).getMonth() == new Date(b).getMonth()
&& new Date(a).getFullYear() == new Date(b).getFullYear();
}

export function slowFormat(date) {
let s = "";
for (let i = 0; i < 100000; i++) {
s = date.toString();
}
return s;
}
Comment on lines +7 to +13
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wasteful loop degrades performance.

The function executes 100,000 iterations that repeatedly assign the same value to s, providing no functional benefit while consuming significant CPU time. Each call will block the event loop for a noticeable duration.

Remove the unnecessary loop:

 export function slowFormat(date) {
-    let s = "";
-    for (let i = 0; i < 100000; i++) {
-        s = date.toString();
-    }
-    return s;
+    return date.toString();
 }
📝 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
export function slowFormat(date) {
let s = "";
for (let i = 0; i < 100000; i++) {
s = date.toString();
}
return s;
}
export function slowFormat(date) {
return date.toString();
}
🤖 Prompt for AI Agents
In app/utils/dateHelpers.js around lines 7 to 13, the slowFormat function
contains a wasteful loop that assigns the same date.toString() value 100,000
times; remove the loop and simply return date.toString() (or use a cached
variable if additional processing is intended) so the function no longer blocks
the event loop or wastes CPU cycles.

14 changes: 14 additions & 0 deletions components/general/BadButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";

export default function BadButton({ onClick, children }) {
return (
<div
role="button"
onClick={onClick}
tabIndex={0}
style={{ padding: 12, background: "#0f0", cursor: "pointer" }}
>
{children || "Click"}
</div>
Comment on lines +5 to +12
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing keyboard event handlers breaks accessibility.

The div has tabIndex={0} making it focusable, but lacks keyboard event handlers for Enter and Space keys. Keyboard-only users can focus the element but cannot activate it, violating WCAG 2.1 Level A requirements.

Add keyboard support or use a button element:

-  return (
-    <div
-      role="button"
-      onClick={onClick}
-      tabIndex={0}
-      style={{ padding: 12, background: "#0f0", cursor: "pointer" }}
-    >
-      {children || "Click"}
-    </div>
-  );
+  const handleKeyPress = (e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
+      onClick?.(e);
+    }
+  };
+
+  return (
+    <div
+      role="button"
+      onClick={onClick}
+      onKeyDown={handleKeyPress}
+      tabIndex={0}
+      style={{ padding: 12, background: "#0f0", cursor: "pointer" }}
+    >
+      {children || "Click"}
+    </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
role="button"
onClick={onClick}
tabIndex={0}
style={{ padding: 12, background: "#0f0", cursor: "pointer" }}
>
{children || "Click"}
</div>
const handleKeyPress = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick?.(e);
}
};
return (
<div
role="button"
onClick={onClick}
onKeyDown={handleKeyPress}
tabIndex={0}
style={{ padding: 12, background: "#0f0", cursor: "pointer" }}
>
{children || "Click"}
</div>
);
🤖 Prompt for AI Agents
In components/general/BadButton.jsx around lines 5 to 12 the focusable div has
tabIndex={0} but no keyboard handlers, so keyboard users cannot activate it; fix
by either replacing the div with a semantic <button> (preserving styles) or keep
the div and add an onKeyDown handler that listens for Enter and Space, calls the
same onClick handler (for Space call event.preventDefault() before invoking),
and keep role="button" and tabIndex to maintain accessibility.

);
}
15 changes: 15 additions & 0 deletions components/general/Input.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { useState } from "react";

export default function Input({ defaultValue = "", onChange }) {
const [v, setV] = useState();
return (
<input
value={v}
defaultValue={defaultValue}
onChange={(e) => {
setV(e.target.value);
if (onChange) onChange(e);
}}
/>
Comment on lines +4 to +13
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Invalid usage of value and defaultValue together.

Lines 7-8 use both value and defaultValue on the same input element. In React:

  • value makes the input controlled (must have state management)
  • defaultValue is for uncontrolled inputs only

This causes React warnings and the defaultValue prop is ignored. Additionally, initializing state as undefined (line 4) causes an "uncontrolled to controlled component" warning.

Choose one approach:

Option 1: Controlled input (recommended)

 export default function Input({ defaultValue = "", onChange }) {
-    const [v, setV] = useState();
+    const [v, setV] = useState(defaultValue);
     return (
         <input
             value={v}
-            defaultValue={defaultValue}
             onChange={(e) => {
                 setV(e.target.value);
                 if (onChange) onChange(e);
             }}
         />
     );
 }

Option 2: Uncontrolled input

 export default function Input({ defaultValue = "", onChange }) {
-    const [v, setV] = useState();
     return (
         <input
-            value={v}
             defaultValue={defaultValue}
             onChange={(e) => {
-                setV(e.target.value);
                 if (onChange) onChange(e);
             }}
         />
     );
 }
📝 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 [v, setV] = useState();
return (
<input
value={v}
defaultValue={defaultValue}
onChange={(e) => {
setV(e.target.value);
if (onChange) onChange(e);
}}
/>
const [v, setV] = useState(defaultValue);
return (
<input
value={v}
onChange={(e) => {
setV(e.target.value);
if (onChange) onChange(e);
}}
/>
)
Suggested change
const [v, setV] = useState();
return (
<input
value={v}
defaultValue={defaultValue}
onChange={(e) => {
setV(e.target.value);
if (onChange) onChange(e);
}}
/>
return (
<input
defaultValue={defaultValue}
onChange={(e) => {
if (onChange) onChange(e);
}}
/>
)
🤖 Prompt for AI Agents
In components/general/Input.jsx around lines 4 to 13, the input incorrectly uses
both value and defaultValue and initializes state as undefined causing React
warnings; pick the controlled approach (recommended): initialize the state from
the incoming defaultValue (e.g., useState(defaultValue || '')), pass only
value={v} to the input (remove defaultValue), and call setV on change while
forwarding the onChange event; alternatively, if you prefer an uncontrolled
component, remove the value prop entirely, keep defaultValue, and manage updates
via a ref or external handlers—do not use both value and defaultValue together
and ensure state is not initialized as undefined.

);
}
9 changes: 9 additions & 0 deletions components/general/LargeList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { useMemo } from "react";

export default function LargeList({ items = [] }) {
const rendered = useMemo(
() => items.map((x, i) => <li key={i}>{x}</li>),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid using array index as key.

Using the array index as a React key can cause issues with component state and re-rendering, particularly when items are reordered, added, or removed. Use a stable unique identifier from the data instead.

If items have unique IDs:

-    () => items.map((x, i) => <li key={i}>{x}</li>),
+    () => items.map((x) => <li key={x.id}>{x}</li>),

Or if items are primitive values and guaranteed unique:

-    () => items.map((x, i) => <li key={i}>{x}</li>),
+    () => items.map((x) => <li key={x}>{x}</li>),

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/general/LargeList.jsx around line 5, the map is using the array
index as the React key which is unstable; change the key to a stable unique
identifier from each item (e.g., item.id or another unique property) and fall
back to the item value only if primitives are guaranteed unique, ensuring the
key is a string and remains stable across renders so React can reconcile list
items correctly.

[]
);
Comment on lines +4 to +7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Empty dependency array prevents list updates.

The useMemo hook has an empty dependency array but references items inside the memoized function. This means the list is only rendered once on mount and will never update when the items prop changes, breaking the component's core functionality.

Apply this diff to fix:

   const rendered = useMemo(
     () => items.map((x, i) => <li key={i}>{x}</li>),
-    []
+    [items]
   );
📝 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 rendered = useMemo(
() => items.map((x, i) => <li key={i}>{x}</li>),
[]
);
const rendered = useMemo(
() => items.map((x, i) => <li key={i}>{x}</li>),
[items]
);
🤖 Prompt for AI Agents
In components/general/LargeList.jsx around lines 4 to 7, the useMemo call uses
an empty dependency array while referencing the items prop, so the rendered list
never updates; change the dependency array to include items (e.g., [items]) so
the memo recomputes when items change, ensuring the list updates correctly.

return <ul>{rendered}</ul>;
}
10 changes: 10 additions & 0 deletions components/letter/LetterCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";

export default function LetterCard({ html }) {
return (
<article>
<h3>Letter</h3>
<div dangerouslySetInnerHTML={{ __html: html }} />
</article>
);
}
Comment on lines +3 to +10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical XSS vulnerability: sanitize HTML before rendering.

Using dangerouslySetInnerHTML without sanitization exposes the application to Cross-Site Scripting (XSS) attacks. Any malicious JavaScript in the html prop will execute in the user's browser.

Apply this diff to sanitize the HTML using DOMPurify:

 import React from "react";
+import DOMPurify from "dompurify";
 
 export default function LetterCard({ html }) {
+  const sanitizedHtml = DOMPurify.sanitize(html);
   return (
     <article>
       <h3>Letter</h3>
-      <div dangerouslySetInnerHTML={{ __html: html }} />
+      <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
     </article>
   );
 }

Install DOMPurify:

npm install dompurify
🤖 Prompt for AI Agents
In components/letter/LetterCard.jsx around lines 3 to 10, the component uses
dangerouslySetInnerHTML without sanitizing the html prop, creating an XSS risk;
install dompurify (npm install dompurify), import it at the top of the file, run
DOMPurify.sanitize(html) (or use isomorphic-dompurify if server-side rendering)
and pass the sanitized string to dangerouslySetInnerHTML, ensuring no raw
user-provided HTML is rendered.

10 changes: 10 additions & 0 deletions components/loading/HeavyImage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";

export default function HeavyImage() {
return (
<img
src="https://picsum.photos/3000/2000"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Large image dimensions may cause performance issues.

Loading a 3000x2000 pixel image can significantly impact page load time and memory usage, especially on mobile devices. Consider using responsive image techniques or lazy loading.

Consider these improvements:

  • Use loading="lazy" to defer loading until the image is near the viewport
  • Provide multiple image sizes using srcset and sizes attributes
  • Consider serving optimized image formats (WebP, AVIF) with fallbacks
🤖 Prompt for AI Agents
In components/loading/HeavyImage.jsx around line 6, the hardcoded 3000x2000
image causes performance and memory issues; update the <img> usage to lazy-load
(add loading="lazy"), provide responsive sources via srcset and sizes with
multiple smaller/responsive variants, prefer optimized image formats (WebP/AVIF)
with fallback to JPEG/PNG, and ensure URLs target appropriately
resized/optimized images rather than the full 3000x2000 asset.

style={{ width: "100%" }}
/>
Comment on lines +5 to +8
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing alt attribute blocks accessibility.

The img element lacks an alt attribute, which prevents screen readers from describing the image to users with visual impairments. This violates WCAG 2.1 Level A requirements.

Apply this diff to add an alt attribute:

     <img
         src="https://picsum.photos/3000/2000"
+        alt="Placeholder image"
         style={{ width: "100%" }}
     />
📝 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
<img
src="https://picsum.photos/3000/2000"
style={{ width: "100%" }}
/>
<img
src="https://picsum.photos/3000/2000"
alt="Placeholder image"
style={{ width: "100%" }}
/>
🤖 Prompt for AI Agents
In components/loading/HeavyImage.jsx around lines 5 to 8, the <img> tag is
missing an alt attribute which breaks accessibility; add a descriptive alt
attribute (or an empty alt="" if the image is purely decorative) to the img
element so screen readers have appropriate text, ensuring the alt value
concisely describes the image content or intent.

);
}
15 changes: 15 additions & 0 deletions components/tooltip/BadTooltip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { useState } from "react";

export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) {
const [open, setOpen] = useState(false);
return (
<span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
<button aria-describedby="tooltip">{children}</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

aria-describedby references potentially non-existent element.

Line 7 sets aria-describedby="tooltip" on the button, but the referenced element only exists when open is true. When the tooltip is hidden, screen readers encounter a broken reference, which may confuse users or be announced incorrectly.

Consider removing aria-describedby when closed, or use aria-label instead:

-      <button aria-describedby={tooltipId}>{children}</button>
+      <button aria-describedby={open ? tooltipId : undefined}>{children}</button>

Or use aria-label if the tooltip text is always the same:

-      <button aria-describedby={tooltipId}>{children}</button>
+      <button aria-label={text}>{children}</button>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/tooltip/BadTooltip.jsx around line 7, the button unconditionally
sets aria-describedby="tooltip" while the referenced element only exists when
open is true; update the component to avoid a broken reference by conditionally
including aria-describedby only when the tooltip is visible (e.g., add the
attribute when open is true), or use a permanent alternative such as aria-label
on the button if the tooltip text is static; also ensure the tooltip element has
a matching unique id when provided to avoid duplicate ids.

{open && (
<span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
{text}
</span>
Comment on lines +7 to +11
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Duplicate ID violation across multiple component instances.

Line 9 hardcodes id="tooltip", which will create duplicate IDs if multiple BadTooltip components are rendered on the same page. This violates HTML specifications and breaks assistive technology, as screen readers cannot reliably associate aria-describedby with the correct tooltip.

Generate unique IDs per component instance:

+import React, { useState, useId } from "react";
+
-import React, { useState } from "react";
-
 export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) {
     const [open, setOpen] = useState(false);
+    const tooltipId = useId();
     return (
         <span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
-      <button aria-describedby="tooltip">{children}</button>
+      <button aria-describedby={tooltipId}>{children}</button>
             {open && (
-                <span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
+                <span id={tooltipId} role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
           {text}
         </span>
             )}
     </span>
     );
 }
📝 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
<button aria-describedby="tooltip">{children}</button>
{open && (
<span id="tooltip" role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
{text}
</span>
import React, { useState, useId } from "react";
export default function BadTooltip({ text = "Tooltip", children = "Hover me" }) {
const [open, setOpen] = useState(false);
const tooltipId = useId();
return (
<span onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
<button aria-describedby={tooltipId}>{children}</button>
{open && (
<span id={tooltipId} role="tooltip" style={{ border: "1px solid #333", padding: 4 }}>
{text}
</span>
)}
</span>
);
}
🤖 Prompt for AI Agents
In components/tooltip/BadTooltip.jsx around lines 7 to 11, the tooltip uses a
hardcoded id="tooltip" which causes duplicate ID violations when multiple
instances render; change the component to generate a unique id per instance and
use that id for both the button's aria-describedby and the tooltip span (e.g.,
create a stable unique id using React's useId or a useRef-initialized
UUID/prefix and assign it to aria-describedby and id), ensuring the id is stable
across renders and only generated once per component instance.

)}
</span>
);
}